fix(code-runtime): support SEA startup and immediate control writes
This commit is contained in:
1 parent
98d3af0683
commit
7f5eb6db9a
19 files changed
+145
-49
No files matched your search
@@ -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,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
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 限制帧与排队写入,在分派前验证调用身份和已声明的绑定名,并拒绝无效通信。输出捕获计量序列化日志加完成值或诊断;固定结果信封字段与沙箱元数据不计入该账本。
|
||||
|
||||
|
||||
@@ -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)) }
|
||||
}
|
||||
@@ -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) })
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
@@ -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)
|
||||
|
||||
@@ -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(() => {}) }) })
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:^"
|
||||
}
|
||||
}
|
||||
@@ -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() } })
|
||||
|
||||
Generated
+15
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.' },
|
||||
|
||||
Reference in new issue
Block a user