diff --git a/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts b/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts index da0c857eb4..d84121c665 100644 --- a/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts +++ b/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 { + 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 { const harness = new Context() diff --git a/docs/subsystems/code-runtime.i18n.yaml b/docs/subsystems/code-runtime.i18n.yaml index 31d8b7dc2f..79b5ebe965 100644 --- a/docs/subsystems/code-runtime.i18n.yaml +++ b/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 diff --git a/docs/subsystems/code-runtime.md b/docs/subsystems/code-runtime.md index efc759db83..b3d4fbbb4b 100644 --- a/docs/subsystems/code-runtime.md +++ b/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 diff --git a/docs/subsystems/code-runtime.zh.md b/docs/subsystems/code-runtime.zh.md index 143b9ead9c..81e5df9bf4 100644 --- a/docs/subsystems/code-runtime.zh.md +++ b/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 diff --git a/packages/code-runtime/code-runtime-node/README.i18n.yaml b/packages/code-runtime/code-runtime-node/README.i18n.yaml index 3d67a086c0..7286a0e9a7 100644 --- a/packages/code-runtime/code-runtime-node/README.i18n.yaml +++ b/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 diff --git a/packages/code-runtime/code-runtime-node/README.md b/packages/code-runtime/code-runtime-node/README.md index d0d395922b..76e3e4a7cb 100644 --- a/packages/code-runtime/code-runtime-node/README.md +++ b/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. diff --git a/packages/code-runtime/code-runtime-node/README.zh.md b/packages/code-runtime/code-runtime-node/README.zh.md index 1c052adee0..9835128af6 100644 --- a/packages/code-runtime/code-runtime-node/README.zh.md +++ b/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 限制帧与排队写入,在分派前验证调用身份和已声明的绑定名,并拒绝无效通信。输出捕获计量序列化日志加完成值或诊断;固定结果信封字段与沙箱元数据不计入该账本。 diff --git a/packages/code-runtime/code-runtime-node/src/channel.ts b/packages/code-runtime/code-runtime-node/src/channel.ts index b1527801ed..9836a2a2d3 100644 --- a/packages/code-runtime/code-runtime-node/src/channel.ts +++ b/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 + 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 = Promise.resolve() + private readonly writes = new Set() 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 => { - if (this.closed) throw new Error('control channel is closed') - await new Promise((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() + 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 { await this.tail } + async drain(): Promise { await Promise.allSettled([...this.writes].map(write => write.promise)) } } diff --git a/packages/code-runtime/code-runtime-node/src/index.ts b/packages/code-runtime/code-runtime-node/src/index.ts index a38cbdad41..3c1b8c6132 100644 --- a/packages/code-runtime/code-runtime-node/src/index.ts +++ b/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) }) } diff --git a/packages/code-runtime/code-runtime-node/tests/channel.spec.ts b/packages/code-runtime/code-runtime-node/tests/channel.spec.ts index 4f074f840b..76a31a9358 100644 --- a/packages/code-runtime/code-runtime-node/tests/channel.spec.ts +++ b/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') +}) diff --git a/packages/code-runtime/code-runtime-node/tests/host-failures.spec.ts b/packages/code-runtime/code-runtime-node/tests/host-failures.spec.ts index be326bfb82..955f49897f 100644 --- a/packages/code-runtime/code-runtime-node/tests/host-failures.spec.ts +++ b/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) diff --git a/packages/code-runtime/code-runtime-node/tests/runtime.spec.ts b/packages/code-runtime/code-runtime-node/tests/runtime.spec.ts index 86cb7d5dec..1ab6056196 100644 --- a/packages/code-runtime/code-runtime-node/tests/runtime.spec.ts +++ b/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(() => {}) }) }) diff --git a/packages/code-runtime/code-runtime/src/types.ts b/packages/code-runtime/code-runtime/src/types.ts index e1a1393f67..96da20baed 100644 --- a/packages/code-runtime/code-runtime/src/types.ts +++ b/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 diff --git a/packages/core/agent-tool-presentation/src/index.ts b/packages/core/agent-tool-presentation/src/index.ts index 58de91c99f..ded8ff157c 100644 --- a/packages/core/agent-tool-presentation/src/index.ts +++ b/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. diff --git a/packages/spill/spill-policy/package.json b/packages/spill/spill-policy/package.json index 4f9d90d479..96a806808a 100644 --- a/packages/spill/spill-policy/package.json +++ b/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:^" } } diff --git a/packages/spill/spill-policy/tests/spill-policy.spec.ts b/packages/spill/spill-policy/tests/spill-policy.spec.ts index 559546dcaf..1c70bc8954 100644 --- a/packages/spill/spill-policy/tests/spill-policy.spec.ts +++ b/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 { + 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() } }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cba45167bc..49f9770436 100644 --- a/pnpm-lock.yaml +++ b/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 diff --git a/python/sdk-runtime/runtime-bootstrap.mjs b/python/sdk-runtime/runtime-bootstrap.mjs index 153bdcd0e4..144b943d58 100644 --- a/python/sdk-runtime/runtime-bootstrap.mjs +++ b/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) { diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index fc6bae8108..2e6178bf42 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -52,7 +52,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { '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.' },