浏览代码

fix(code-runtime): support SEA startup and immediate control writes

Tianyi Cui 6 天之前
父节点
当前提交
7f5eb6db9a

+ 15 - 2
apps/cli/tests/profiles/headless/tests/ptc.e2e.ts

@@ -1,7 +1,7 @@
 import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
-import { afterEach, describe, expect, it, vi } from 'vitest'
+import { afterEach, describe, expect, it, onTestFinished, vi } from 'vitest'
 import { Context } from '@deepseek-ai/cordis'
 import LlmRuntime, { createUserMessage, ToolCallId, HarnessError  } from '@deepseek-ai/dsh-llm'
 import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
@@ -18,7 +18,9 @@ import * as BashEnvPlugin from '@deepseek-ai/dsh-shell-env'
 import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local'
 import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
 import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
-import { mountRuntime } from '../../../../../../packages/code-runtime/code-runtime-node/tests/setup.ts'
+import NodeRuntime from '@deepseek-ai/dsh-code-runtime-node'
+import Sandbox from '@deepseek-ai/dsh-sandbox-local'
+import SandboxPolicy from '@deepseek-ai/dsh-sandbox-policy'
 import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
 import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
 import * as WorkspaceContext from '@deepseek-ai/dsh-agent-instructions'
@@ -114,6 +116,17 @@ function completion(result: ToolExecutionResult): unknown {
   return value.result
 }
 
+async function mountRuntime(harness: Context): Promise<void> {
+  onTestFinished(async () => { await harness.fiber.dispose() })
+  if (!harness.get('sessions')) await harness.plugin(SessionStore)
+  if (!harness.get('fs')) await harness.plugin(LocalFileSystem)
+  if (!harness.get('subprocess')) await harness.plugin(LocalSubprocessRuntime)
+  if (!harness.get('sandbox')) await harness.plugin(Sandbox, {})
+  if (!harness.get('sessionProjections')) await harness.plugin(SessionProjectionRegistry)
+  if (!harness.get('sandboxPolicy')) await harness.plugin(SandboxPolicy, { mode: 'danger-full-access' })
+  await harness.plugin(NodeRuntime, {})
+}
+
 /** Keyless real-process harness for direct typed-binding acceptance tests. */
 async function typedPtcModeHarness(): Promise<Context> {
   const harness = new Context()

+ 2 - 2
docs/subsystems/code-runtime.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/subsystems/code-runtime.md
-code-runtime.md: efc759db8351a9556ac45376d49d08570fed2a57
-code-runtime.zh.md: 143b9ead9cc0dfceb3b78374b20d64aebbbe41bb
+code-runtime.md: b3d4fbbb4bbfd85e6460ba333cb4c6365e0bc43c
+code-runtime.zh.md: 81e5df9bf401f9123ec7fe7ac2577c80ba27bc70

+ 1 - 1
docs/subsystems/code-runtime.md

@@ -55,7 +55,7 @@ interface CodeRunSpec extends CodeRunRequest {
 interface CodeRunSandbox {
   /** File-effect mode used for this execution. */
   mode: SandboxMode
-  /** Whether an observed failure matches the selected backend's denial diagnostics. */
+  /** Program failure text matched backend diagnostics; not enforcement proof or an exhaustive denial record. */
   denied: boolean
   /** Completeness reported by the selected confining backend; absent for full access. */
   enforcement?: SandboxEnforcement

+ 1 - 1
docs/subsystems/code-runtime.zh.md

@@ -55,7 +55,7 @@ interface CodeRunSpec extends CodeRunRequest {
 interface CodeRunSandbox {
   /** File-effect mode used for this execution. */
   mode: SandboxMode
-  /** Whether an observed failure matches the selected backend's denial diagnostics. */
+  /** Program failure text matched backend diagnostics; not enforcement proof or an exhaustive denial record. */
   denied: boolean
   /** Completeness reported by the selected confining backend; absent for full access. */
   enforcement?: SandboxEnforcement

+ 2 - 2
packages/code-runtime/code-runtime-node/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/code-runtime/code-runtime-node/README.md
-README.md: d0d395922b26e4eea84a27a08757abe71ef99866
-README.zh.md: 1c052adee04ceeab2bcbd3dfe391a5424beb0daa
+README.md: 76e3e4a7cb2d758797ca130f498fe6ec3f697b35
+README.zh.md: 9835128af67d4b57266818aa06ab6cee21412e15

+ 1 - 1
packages/code-runtime/code-runtime-node/README.md

@@ -83,7 +83,7 @@ The host owns policy, deadlines, binding lookup and process cleanup. The child o
 
 ### Launch and control
 
-The host strips erasable types, resolves the executable and bootstrap in the configured execution world, wraps the argv through `ctx.sandbox`, and spawns through `ctx.subprocess`. After adopting the inherited control channel, the child retains only executable-search and Windows system paths in its OS environment and replaces the program-visible `process.env` with an empty dictionary. The retained native paths keep nested Windows process creation functional. Explicit Node arguments avoid inheriting host loader or inspector flags.
+The host strips erasable types, resolves the executable and bootstrap in the configured execution world, wraps the argv through `ctx.sandbox`, and spawns through `ctx.subprocess`. After adopting the inherited control channel, the child retains only executable-search and Windows system paths in its OS environment and replaces the program-visible `process.env` with an empty dictionary. The retained native paths keep nested Windows process creation functional. The heap limit uses Node argv or a provider-created `NODE_OPTIONS` value for packaged executables; ambient loader and inspector flags are discarded.
 
 Length-framed JSON travels separately from stdout/stderr. The host bounds frames and queued writes, validates call identity and declared binding names before dispatch, and refuses invalid traffic. Output capture meters serialized logs plus the completion or diagnostic; fixed result-envelope fields and sandbox metadata are outside that ledger.
 

+ 1 - 1
packages/code-runtime/code-runtime-node/README.zh.md

@@ -83,7 +83,7 @@ Host 负责策略、截止时间、绑定查找和进程清理。子进程负责
 
 ### 启动与控制
 
-Host 擦除可擦除类型,在配置的执行世界中解析可执行文件与 bootstrap,通过 `ctx.sandbox` 包装 argv,并通过 `ctx.subprocess` 启动。接管继承的控制通道后,子进程在 OS 环境中只保留可执行文件搜索路径和 Windows 系统路径,并将程序可见的 `process.env` 替换为空字典。保留的原生路径使嵌套 Windows 进程创建仍可正常工作。显式 Node 参数避免继承 Host 的加载器或调试器标志
+Host 擦除可擦除类型,在配置的执行世界中解析可执行文件与 bootstrap,通过 `ctx.sandbox` 包装 argv,并通过 `ctx.subprocess` 启动。接管继承的控制通道后,子进程在 OS 环境中只保留可执行文件搜索路径和 Windows 系统路径,并将程序可见的 `process.env` 替换为空字典。保留的原生路径使嵌套 Windows 进程创建仍可正常工作。堆上限通过 Node argv 或为打包可执行文件由提供方构造的 `NODE_OPTIONS` 值传递;环境中的加载器和调试器标志会被丢弃
 
 带长度分帧的 JSON 与 stdout/stderr 分开传输。Host 限制帧与排队写入,在分派前验证调用身份和已声明的绑定名,并拒绝无效通信。输出捕获计量序列化日志加完成值或诊断;固定结果信封字段与沙箱元数据不计入该账本。
 

+ 39 - 25
packages/code-runtime/code-runtime-node/src/channel.ts

@@ -7,6 +7,11 @@ import { jsonValueBytesUpTo } from './output-json.ts'
 const stringify = JSON.stringify
 const parse = JSON.parse
 
+interface PendingWrite {
+  promise: Promise<void>
+  finish(error?: Error): void
+}
+
 /** One co-shipped process channel; the consumer owns frame validation and terminal outcomes. */
 export class JsonChannel {
   private readonly stream: Duplex
@@ -19,7 +24,7 @@ export class JsonChannel {
   private payloadBytes = 0
   private queuedBytes = 0
   private closed = false
-  private tail: Promise<void> = Promise.resolve()
+  private readonly writes = new Set<PendingWrite>()
 
   constructor(
     stream: Duplex,
@@ -34,9 +39,14 @@ export class JsonChannel {
     stream.on('data', this.onData)
     stream.on('error', this.onError)
     stream.on('end', this.onEnd)
+    stream.on('close', this.onClose)
   }
 
-  private readonly onError = (error: Error): void => { if (!this.closed) this.failure(error, 'io') }
+  private readonly onError = (error: Error): void => {
+    this.finishWrites(error)
+    if (!this.closed) this.failure(error, 'io')
+  }
+  private readonly onClose = (): void => { this.finishWrites(new Error('control channel closed during a write')) }
   private readonly onEnd = (): void => { if (!this.closed) this.failure(new Error('control channel ended before the program settled'), 'io') }
   private readonly onData = (chunk: Buffer): void => {
     if (this.closed) return
@@ -73,7 +83,7 @@ export class JsonChannel {
   }
 
   /**
-   * Queue a bounded frame and honor stream backpressure.
+   * Submit a bounded frame immediately and await the stream's write receipt.
    * @param message - JSON-only co-shipped protocol value.
    * @returns Resolves when this frame has been written, or rejects after transport failure.
    */
@@ -87,28 +97,31 @@ export class JsonChannel {
     const header = Buffer.alloc(4)
     header.writeUInt32BE(body.length)
     this.queuedBytes += body.length
-    const write = async (): Promise<void> => {
-      if (this.closed) throw new Error('control channel is closed')
-      await new Promise<void>((resolve, reject) => {
-        const stop = (error?: Error): void => {
-          this.stream.off('error', onError)
-          this.stream.off('close', onClose)
-          if (error) reject(error)
-          else resolve()
-        }
-        const onError = (error: Error): void => { stop(error) }
-        const onClose = (): void => { stop(new Error('control channel closed during a write')) }
-        this.stream.once('error', onError)
-        this.stream.once('close', onClose)
-        this.stream.cork()
-        this.stream.write(header)
-        this.stream.write(body, (error) => { stop(error ?? undefined) })
-        this.stream.uncork()
-      })
+    const completion = Promise.withResolvers<void>()
+    const write: PendingWrite = {
+      promise: completion.promise,
+      finish: (error) => {
+        if (!this.writes.delete(write)) return
+        this.queuedBytes -= body.length
+        if (error) completion.reject(error)
+        else completion.resolve()
+      },
     }
-    const result = this.tail.then(write).finally(() => { this.queuedBytes -= body.length })
-    this.tail = result.catch(() => {})
-    return result
+    this.writes.add(write)
+    try {
+      // Writable preserves frame order; a Promise queue would delay logs behind a model hot loop.
+      this.stream.cork()
+      this.stream.write(header)
+      this.stream.write(body, (error) => { write.finish(error ?? undefined) })
+      this.stream.uncork()
+    } catch (error: unknown) {
+      write.finish(error instanceof Error ? error : new Error(String(error)))
+    }
+    return completion.promise
+  }
+
+  private finishWrites(error: Error): void {
+    for (const write of this.writes) write.finish(error)
   }
 
   /** Stop reads and close the owned endpoint; pending writes reject on closure. */
@@ -116,11 +129,12 @@ export class JsonChannel {
     if (this.closed) return
     this.closed = true
     this.payload = undefined
+    this.finishWrites(new Error('control channel is closed'))
     this.stream.off('data', this.onData)
     this.stream.off('end', this.onEnd)
     this.stream.destroy()
   }
 
   /** Wait for accepted writes to finish or fail. */
-  async drain(): Promise<void> { await this.tail }
+  async drain(): Promise<void> { await Promise.allSettled([...this.writes].map(write => write.promise)) }
 }

+ 11 - 3
packages/code-runtime/code-runtime-node/src/index.ts

@@ -210,14 +210,19 @@ export class NodeCodeRuntime extends CodeRuntime {
       // Abort callbacks can settle execution before or during an awaited operation.
       // oxlint-disable-next-line typescript/no-unnecessary-condition
       if (settled) return await result.promise
-      const argv = [executable, `--max-old-space-size=${this.config.maxOldGenerationSizeMb}`, ...bootstrapArgs(this.ctx.fs, this.config, this.config.maxMessageBytes)]
+      const packaged = 'pkg' in process && this.config.bootstrapPath === undefined
+      const heapFlag = `--max-old-space-size=${this.config.maxOldGenerationSizeMb}`
+      const argv = [executable, ...packaged ? [] : [heapFlag], ...bootstrapArgs(this.ctx.fs, this.config, this.config.maxMessageBytes)]
       confined = policy.mode === 'danger-full-access' ? undefined : this.ctx.sandbox.confine(argv, { ...policy, mode: policy.mode })
       if (confined !== undefined) sandbox.enforcement = confined.enforcement
       // Native launchers need executable search and Windows system paths before the child installs its model environment.
       const env: NodeJS.ProcessEnv = Object.fromEntries(Object.keys(process.env)
         .filter(key => !STARTUP_ENVIRONMENT_NAMES.has(key.toUpperCase()))
         .map(key => [key, undefined]))
-      if ('pkg' in process && this.config.bootstrapPath === undefined) env.DSH_CODE_RUNTIME_NODE = '1'
+      if (packaged) {
+        env.DSH_CODE_RUNTIME_NODE = '1'
+        env.NODE_OPTIONS = heapFlag
+      }
       handle = this.ctx.subprocess.spawn({ argv: confined?.argv ?? argv, cwd: spec.cwd, env, stdio: { stdin: 'ignore', stdout: 'pipe', stderr: 'pipe', control: 'pipe' }, graceMs: this.config.graceMs, signal })
       const launched = handle
       if (launched.control === undefined || launched.stdout === undefined || launched.stderr === undefined) {
@@ -328,7 +333,10 @@ export class NodeCodeRuntime extends CodeRuntime {
         else void launched.done.then(processFinished, (failure: unknown) => { finish({ kind: 'worker-exit', message: messageOf(failure) }) })
       })
       channel = transport
-      void launched.done.then((outcome) => { setImmediate(() => { processFinished(outcome) }) }, (error: unknown) => { finish({ kind: confined !== undefined && isRunnerSpawnFailure(error, confined.argv[0], spec.cwd) ? 'sandbox-unavailable' : 'worker-exit', message: messageOf(error) }) })
+      void launched.done.then((outcome) => {
+        // Allow queued control-frame callbacks to run before classifying a command exit.
+        setImmediate(() => { processFinished(outcome) })
+      }, (error: unknown) => { finish({ kind: confined !== undefined && isRunnerSpawnFailure(error, confined.argv[0], spec.cwd) ? 'sandbox-unavailable' : 'worker-exit', message: messageOf(error) }) })
     } catch (error: unknown) {
       finish({ kind: error instanceof SandboxUnavailableError ? 'sandbox-unavailable' : parsing ? 'exception' : 'worker-exit', message: messageOf(error) })
     }

+ 11 - 1
packages/code-runtime/code-runtime-node/tests/channel.spec.ts

@@ -124,7 +124,7 @@ it('ignores callbacks already captured by an emission when an earlier listener c
   }
 })
 
-it('rejects queued writes if the owner closes before they start', async () => {
+it('rejects pending write receipts when the owner closes', async () => {
   const { a } = pair()
   const channel = new JsonChannel(a, 64, () => {}, () => {})
   const pending = channel.send({ n: 1 })
@@ -138,8 +138,10 @@ it('bounds queued frames while a receiver is not reading', async () => {
   const channel = new JsonChannel(stream, 32, () => {}, () => {})
   const pending = channel.send({ value: '1234567890' })
   await expect(channel.send({ value: '1234567890' })).rejects.toThrow('queued bytes')
+  const draining = channel.drain()
   channel.close()
   await expect(pending).rejects.toThrow('closed')
+  await draining
 })
 
 it.each(['error', 'close'])('settles a blocked write when the stream emits %s', async (event) => {
@@ -154,3 +156,11 @@ it.each(['error', 'close'])('settles a blocked write when the stream emits %s',
   if (event === 'error') expect(failure?.message).toBe('transport failed')
   channel.close()
 })
+
+
+it.each([new Error('write failed'), 'write failed'])('rejects a synchronous transport write failure: %s', async (failure) => {
+  const stream = new Duplex({ read() {}, write() { throw failure } })
+  const channel = new JsonChannel(stream, 64, () => {}, () => {})
+  onTestFinished(() => { channel.close() })
+  await expect(channel.send({ value: 1 })).rejects.toThrow('write failed')
+})

+ 3 - 2
packages/code-runtime/code-runtime-node/tests/host-failures.spec.ts

@@ -439,8 +439,9 @@ describe('Node runtime host failures', () => {
       const spec = h.spawn.mock.calls[0]?.[0]
       expect(spec?.env?.DSH_CODE_RUNTIME_NODE).toBe('1')
       expect(Object.hasOwn(spec?.env ?? {}, 'PATH')).toBe(false)
-      expect(spec?.argv.at(-1)).toBe('134217728')
-      expect(Object.entries(spec?.env ?? {}).filter(([, value]) => value !== undefined)).toEqual([['DSH_CODE_RUNTIME_NODE', '1']])
+      expect(spec?.argv).toEqual([process.execPath, '134217728'])
+      expect(Object.fromEntries(Object.entries(spec?.env ?? {}).filter(([, value]) => value !== undefined)))
+        .toEqual({ DSH_CODE_RUNTIME_NODE: '1', NODE_OPTIONS: '--max-old-space-size=512' })
     } finally {
       if (prior === undefined) Reflect.deleteProperty(process, 'pkg')
       else Object.defineProperty(process, 'pkg', prior)

+ 7 - 0
packages/code-runtime/code-runtime-node/tests/runtime.spec.ts

@@ -100,6 +100,13 @@ describe('Node program process', () => {
     expect((await run({ program, bindings: [] })).error?.kind).toBe('timeout')
   })
 
+  it('retains console output emitted immediately before a non-yielding program', async () => {
+    const { run } = await setup({ timeoutMs: 1000, graceMs: 50 })
+    const result = await run({ program: 'console.log("before hot loop"); for (;;) {}', bindings: [] })
+    expect(result.error?.kind).toBe('timeout')
+    expect(result.logs).toEqual(['before hot loop'])
+  })
+
   it('does not pause the deadline while a binding is pending', async () => {
     const { run } = await setup({ timeoutMs: 1000, graceMs: 50 })
     const result = await run({ program: 'void tools.wait({}); for (;;) {}', bindings: bindings({ wait: () => new Promise(() => {}) }) })

+ 1 - 1
packages/code-runtime/code-runtime/src/types.ts

@@ -106,7 +106,7 @@ export interface CodeRunSpec extends CodeRunRequest {
 export interface CodeRunSandbox {
   /** File-effect mode used for this execution. */
   mode: SandboxMode
-  /** Whether an observed failure matches the selected backend's denial diagnostics. */
+  /** Program failure text matched backend diagnostics; not enforcement proof or an exhaustive denial record. */
   denied: boolean
   /** Completeness reported by the selected confining backend; absent for full access. */
   enforcement?: SandboxEnforcement

+ 1 - 1
packages/core/agent-tool-presentation/src/index.ts

@@ -11,7 +11,7 @@
  * process. One row per composition, not one per session.
  *
  * A PTC mode needs a TypeScript code runtime, which is a host-plane service
- * ([`dsh-code-runtime-node`](../../code-runtime/code-runtime-worker/README.md)).
+ * ([`dsh-code-runtime-node`](../../../code-runtime/code-runtime-node/README.md)).
  * This row therefore waits for it rather than assuming it: a preset selecting
  * PTC mode against a deployment that composes no runtime fails at mount, named
  * in the preset's own activation audit, instead of at the first prompt.

+ 6 - 1
packages/spill/spill-policy/package.json

@@ -50,6 +50,11 @@
     "@deepseek-ai/dsh-session": "workspace:^",
     "@deepseek-ai/dsh-spill": "workspace:^",
     "@deepseek-ai/dsh-tools": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
+    "@deepseek-ai/cordis": "workspace:^",
+    "@deepseek-ai/dsh-fs-local": "workspace:^",
+    "@deepseek-ai/dsh-subprocess-local": "workspace:^",
+    "@deepseek-ai/dsh-sandbox-local": "workspace:^",
+    "@deepseek-ai/dsh-sandbox-policy": "workspace:^",
+    "@deepseek-ai/dsh-session-projection": "workspace:^"
   }
 }

+ 19 - 3
packages/spill/spill-policy/tests/spill-policy.spec.ts

@@ -8,12 +8,12 @@
  * result without an `isError`.
  */
 
-import { describe, expect, it, vi } from 'vitest'
+import { describe, expect, it, onTestFinished, vi } from 'vitest'
 import { Context } from '@deepseek-ai/cordis'
 import Loader from '@deepseek-ai/cordis-plugin-loader'
 import { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
 import type { ContentBlock } from '@deepseek-ai/dsh-llm'
-import { SessionId } from '@deepseek-ai/dsh-session'
+import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
 import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
 import ToolRuntime, { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
 import type { ToolDefinition } from '@deepseek-ai/dsh-tools'
@@ -21,7 +21,23 @@ import type { PostToolDecision, ToolExecution, ToolExecutionToken } from '@deeps
 import { SpillLocator, SpillStore } from '@deepseek-ai/dsh-spill'
 import type { SaveTextSpill, SpillRef } from '@deepseek-ai/dsh-spill'
 import * as SpillPolicy from '@deepseek-ai/dsh-spill-policy'
-import { mountRuntime } from '../../../code-runtime/code-runtime-node/tests/setup.ts'
+import NodeRuntime, { type Config as NodeRuntimeConfig } from '@deepseek-ai/dsh-code-runtime-node'
+import FileSystem from '@deepseek-ai/dsh-fs-local'
+import Subprocess from '@deepseek-ai/dsh-subprocess-local'
+import Sandbox from '@deepseek-ai/dsh-sandbox-local'
+import SandboxPolicy from '@deepseek-ai/dsh-sandbox-policy'
+import SessionProjections from '@deepseek-ai/dsh-session-projection'
+
+async function mountRuntime(ctx: Context, config: NodeRuntimeConfig = {}): Promise<void> {
+  onTestFinished(async () => { await ctx.fiber.dispose() })
+  if (!ctx.get('sessions')) await ctx.plugin(SessionStore)
+  if (!ctx.get('fs')) await ctx.plugin(FileSystem)
+  if (!ctx.get('subprocess')) await ctx.plugin(Subprocess)
+  if (!ctx.get('sandbox')) await ctx.plugin(Sandbox, {})
+  if (!ctx.get('sessionProjections')) await ctx.plugin(SessionProjections)
+  if (!ctx.get('sandboxPolicy')) await ctx.plugin(SandboxPolicy, { mode: 'danger-full-access' })
+  await ctx.plugin(NodeRuntime, config)
+}
 
 function observedAgent(ctx: Context, id: string, observe: (type: string, data: unknown) => void) {
   const session = ctx.sessions.create(SessionId(id), { meta: { cwd: process.cwd() } })

+ 15 - 0
pnpm-lock.yaml

@@ -9086,18 +9086,33 @@ importers:
       '@deepseek-ai/dsh-code-runtime-node':
         specifier: workspace:^
         version: link:../../code-runtime/code-runtime-node
+      '@deepseek-ai/dsh-fs-local':
+        specifier: workspace:^
+        version: link:../../fs/fs-local
       '@deepseek-ai/dsh-llm':
         specifier: workspace:^
         version: link:../../llm/llm
       '@deepseek-ai/dsh-output-retention':
         specifier: workspace:^
         version: link:../../util/output-retention
+      '@deepseek-ai/dsh-sandbox-local':
+        specifier: workspace:^
+        version: link:../../sandbox/sandbox-local
+      '@deepseek-ai/dsh-sandbox-policy':
+        specifier: workspace:^
+        version: link:../../sandbox/sandbox-policy
       '@deepseek-ai/dsh-session':
         specifier: workspace:^
         version: link:../../core/session
+      '@deepseek-ai/dsh-session-projection':
+        specifier: workspace:^
+        version: link:../../session/session-projection
       '@deepseek-ai/dsh-spill':
         specifier: workspace:^
         version: link:../spill
+      '@deepseek-ai/dsh-subprocess-local':
+        specifier: workspace:^
+        version: link:../../subprocess/subprocess-local
       '@deepseek-ai/dsh-tools':
         specifier: workspace:^
         version: link:../../core/tools

+ 8 - 1
python/sdk-runtime/runtime-bootstrap.mjs

@@ -1,10 +1,17 @@
 #!/usr/bin/env node
 /** Private entry owned by the Python single-file runtime packaging. */
+import { fileURLToPath } from 'node:url'
 
 const selectorName = 'DSH_SUBPROCESS_RUNNER'
 const selection = process.env[selectorName]
+const aclRunner = process.platform === 'win32'
+  ? fileURLToPath(import.meta.resolve('@deepseek-ai/dsh-sandbox-windows-acl/runner'))
+  : undefined
 
-if (process.env.DSH_CODE_RUNTIME_NODE === '1') {
+if (aclRunner !== undefined && process.argv[2] === aclRunner) {
+  process.argv.splice(1, 1)
+  await import('@deepseek-ai/dsh-sandbox-windows-acl/runner')
+} else if (process.env.DSH_CODE_RUNTIME_NODE === '1') {
   Reflect.deleteProperty(process.env, 'DSH_CODE_RUNTIME_NODE')
   await import('@deepseek-ai/dsh-code-runtime-node/process')
 } else if (selection === undefined) {

+ 1 - 1
scripts/verify-package-readme-model-experience.ts

@@ -52,7 +52,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
   'packages/shell/pwsh-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-pwsh.' },
   'packages/code-runtime/code-runtime': { kind: 'indirect', reason: 'The service interface delegates model rendering to PTC mode in dsh-tools.' },
   'packages/core/agent-tool-presentation': { kind: 'indirect', reason: 'The row only selects between the two projections dsh-tools owns; it registers no prompt, schema, or result of its own.' },
-  'packages/code-runtime/code-runtime-node': { kind: 'indirect', reason: 'The worker backend delegates model rendering to PTC mode in dsh-tools.' },
+  'packages/code-runtime/code-runtime-node': { kind: 'indirect', reason: 'The Node process backend delegates model rendering to PTC mode in dsh-tools.' },
   'packages/experimental/code-runtime-python': { kind: 'indirect', reason: 'Explicit source-checkout compositions delegate model rendering to PTC mode in dsh-tools.' },
   'packages/client/ui-agent-preset': { kind: 'indirect', reason: 'Browser-side settings row; the preset it selects owns every model-facing effect.' },
   'packages/util/crypto': { kind: 'indirect', reason: 'Pure identifier minting; the ids consumers mint with it never enter prompts as semantic content.' },