From 41ef0fcaa9bbe97bc6d7def0d2268fd74ef43277 Mon Sep 17 00:00:00 2001 From: Anlai Lu Date: Fri, 6 Nov 2026 13:49:35 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ptc):=20=E5=A4=8D=E7=94=A8=20embedded?= =?UTF-8?q?=20=E8=BD=BD=E4=BD=93=E7=9A=84=E6=97=A2=E6=9C=89=20process.js?= =?UTF-8?q?=20=E5=90=AF=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ptc-runtime-node/README.i18n.yaml | 8 +++---- .../ptc-runtime/ptc-runtime-node/README.md | 4 ++-- .../ptc-runtime/ptc-runtime-node/README.zh.md | 4 ++-- .../ptc-runtime/ptc-runtime-node/package.json | 4 ---- .../ptc-runtime-node/src/worker.ts | 2 +- .../tests/embedded-carrier.e2e.ts | 23 ++++++++++++++----- .../tests/host-failures.spec.ts | 16 ------------- .../ptc-runtime-node/tests/launch.spec.ts | 2 +- packages/ssh/ssh-helper-runtime/src/index.ts | 10 ++------ python/sdk-runtime/runtime-bootstrap.mjs | 10 ++------ 10 files changed, 31 insertions(+), 52 deletions(-) diff --git a/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml b/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml index c62a05a0b8..1812034c2b 100644 --- a/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml +++ b/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml @@ -24,8 +24,8 @@ en: 2a9fa3292bf07355 zh: db0b5684820915eb /deepseek-ai-dsh-ptc-runtime-node/use-this-package/in-memory-modes: - en: 22c1d0a06eaebe80 - zh: af1c8f0e815906c9 + en: 41c54a052af70659 + zh: becb41300547089e /deepseek-ai-dsh-ptc-runtime-node/use-this-package/deadlines-and-cancellation: en: d383ec7aeab8afec zh: 7c9c49e205fbbcd8 @@ -39,8 +39,8 @@ en: f27d477e3ccb10de zh: 0f14a4899bae6c39 /deepseek-ai-dsh-ptc-runtime-node/understand-the-implementation/source-and-built-bootstraps: - en: 24a5864c1552c69a - zh: a5406693d86d6183 + en: b944263ce0b1d200 + zh: c25a99f01a0d9af5 /deepseek-ai-dsh-ptc-runtime-node/understand-the-implementation/runner-import-handling: en: e2458b4653a620bb zh: 4ae26bd1e6a69430 diff --git a/packages/ptc-runtime/ptc-runtime-node/README.md b/packages/ptc-runtime/ptc-runtime-node/README.md index f4ecfdfc6f..6f5d9f66ac 100644 --- a/packages/ptc-runtime/ptc-runtime-node/README.md +++ b/packages/ptc-runtime/ptc-runtime-node/README.md @@ -91,7 +91,7 @@ The `persistent-in-memory` mode and Script mode with `scriptPersistence: true` r executionMode: persistent-in-memory ``` -Save the patch and pass its path to `dsh headless --patch ` in a composition with PTC tool presentation. The `per-call` mode and Script mode with `scriptPersistence: false` retain no program memory between calls. Changing the configuration reloads the provider and releases its execution resources. Packaged single-executable hosts use their embedded carrier by default; the worker selects `persistent-process.js` for retained execution. Remote script deployments use a `node-script` launch with `launch.bootstrapPath` pointing to this package's built `persistent-process.js`. +Save the patch and pass its path to `dsh headless --patch ` in a composition with PTC tool presentation. The `per-call` mode and Script mode with `scriptPersistence: false` retain no program memory between calls. Changing the configuration reloads the provider and releases its execution resources. Packaged single-executable hosts use their embedded carrier by default; the host sends the selected program format and retention flag with each evaluation, so the embedded `process.js` bootstrap serves both per-call and retained execution. Remote script deployments use a `node-script` launch with `launch.bootstrapPath` pointing to this package's built `persistent-process.js`. In REPL mode, each live Agent keeps its own process and top-level variables, functions, and imported modules across `run_code` calls and turns. Use `await import(...)`, `console.log(...)`, and a final JSON expression; top-level `return` is not supported. Assignments to undeclared names create retained globals under Node REPL’s non-strict evaluation. `_` and `_error` are ordinary retained globals, without interactive REPL result setters. Node REPL declaration rules apply, including errors on redeclaring existing `let`, `const`, or `class` names; assign to a mutable binding or choose a new name. A leading `{ ... }` is a statement block and can contain top-level await and temporary declarations; parenthesize an object expression to return it. Retained functions and saved tool methods call the current evaluation's bindings. All tool calls must be awaited; callbacks from a completed evaluation cannot invoke tools in a later evaluation. @@ -137,7 +137,7 @@ Per-call completion leaves unfinished binding waits pending and stops binding ca ### Source and built bootstraps -Source execution loads an erasable-only bootstrap closure without relying on sibling built exports; its evaluator modules and installed Acorn and birpc dependencies must be available in the execution world. Built script execution uses `process.js` for per-call programs or `persistent-process.js` for retained execution; both delegate to the same loop with a REPL default for hosts that omit program syntax. Embedded carriers launch their private worker through `launch.executable` and set `DSH_PTC_RUNTIME_NODE=persistent` for retained execution. A remote script deployment supplies the matching `launch.bootstrapPath`. `resolveLaunch()` selects the local default during construction; an explicit remote invocation never depends on whether the client itself is packaged. A host path is never assumed to name the same remote file. +Source execution loads an erasable-only bootstrap closure without relying on sibling built exports; its evaluator modules and installed Acorn and birpc dependencies must be available in the execution world. Built script execution uses `process.js` for every mode; `persistent-process.js` delegates to the same loop with a REPL default for hosts that omit program syntax. Embedded carriers launch their private worker through `launch.executable` and carry the program format and retention flag in each evaluation. A remote script deployment supplies the matching `launch.bootstrapPath`. `resolveLaunch()` selects the local default during construction; an explicit remote invocation never depends on whether the client itself is packaged. A host path is never assumed to name the same remote file. Source containing `await` is checked for top-level await before native compilation, which can otherwise treat `await` as an identifier. Acorn parses an async wrapper, and acorn-walk traverses it using Node's REPL visitors, declaration hoisting and character-position edits. Computed class-method keys are traversed while method bodies remain separate evaluations. The transform boxes the final expression; the evaluator unwraps and awaits its value. Source that needs no lowering uses native compilation; submissions without `await` retain native syntax diagnostics. Syntax errors preserve the parser message and available source location and caret, without compiler or transformer stack frames. Dynamic `import()` expressions remain native and use the compiling Script's `importModuleDynamically` option. Both private process bundles include Acorn, acorn-walk and birpc; source execution imports the installed packages. diff --git a/packages/ptc-runtime/ptc-runtime-node/README.zh.md b/packages/ptc-runtime/ptc-runtime-node/README.zh.md index c841dd46b7..111837701b 100644 --- a/packages/ptc-runtime/ptc-runtime-node/README.zh.md +++ b/packages/ptc-runtime/ptc-runtime-node/README.zh.md @@ -91,7 +91,7 @@ profile 编辑器可从 `@deepseek-ai/dsh-ptc-runtime-node/configuration` 导入 executionMode: persistent-in-memory ``` -保存补丁,在使用 PTC 工具展示的配置中通过 `dsh headless --patch ` 加载。`per-call` 模式和 `scriptPersistence: false` 的 Script 模式不跨调用保留程序内存。修改配置会重载提供方并释放其执行资源。单文件打包宿主默认使用内嵌载体,保留模式由 worker 选择 `persistent-process.js`;远端脚本部署使用 `node-script` 启动方式,并将 `launch.bootstrapPath` 指向本包构建出的 `persistent-process.js`。 +保存补丁,在使用 PTC 工具展示的配置中通过 `dsh headless --patch ` 加载。`per-call` 模式和 `scriptPersistence: false` 的 Script 模式不跨调用保留程序内存。修改配置会重载提供方并释放其执行资源。单文件打包宿主默认使用内嵌载体;宿主在每次执行中发送所选的程序格式和保留标志,因此内嵌的 `process.js` bootstrap 同时服务单次执行和保留执行。远端脚本部署使用 `node-script` 启动方式,并将 `launch.bootstrapPath` 指向本包构建出的 `persistent-process.js`。 在 REPL 模式下,每个存活 Agent 保留独立进程,跨 `run_code` 调用和轮次保留顶层变量、函数及导入模块。使用 `await import(...)`、`console.log(...)` 和末尾 JSON 表达式;不支持顶层 `return`。Node REPL 使用非严格模式,给未声明的名称赋值会创建保留的全局变量。`_` 和 `_error` 是普通的保留全局变量,不使用交互式 REPL 的结果设置器。声明遵循 Node REPL 规则,包括重复声明已有 `let`、`const` 或 `class` 名称时报错;应给可变绑定赋值或选择新名称。开头的 `{ ... }` 按语句块执行,可以包含顶层 await 和临时声明;需要返回对象表达式时,用圆括号包裹。保留的函数和工具方法引用调用当前执行的绑定。必须等待所有工具调用;已完成执行的回调不能在后续执行中调用工具。 @@ -137,7 +137,7 @@ Host 通过有序控制管道上的 birpc 调用子进程的 `run()`。同一时 ### 源代码与构建后 bootstrap -源代码执行加载仅含可擦除语法的 bootstrap 依赖,不依赖同级包的构建后导出;执行世界中必须提供求值器模块和已安装的 Acorn 与 birpc 依赖。构建后脚本对一次执行使用 `process.js`,对保留执行使用 `persistent-process.js`;两者委托同一个循环,供未指定程序语法的宿主默认使用 REPL。内嵌载体通过 `launch.executable` 启动私有 worker,并在保留模式下设置 `DSH_PTC_RUNTIME_NODE=persistent`。远端脚本部署提供对应的 `launch.bootstrapPath`。`resolveLaunch()` 在构造时选择本地默认值;显式的远端调用不依赖客户端自身是否打包。不会假设 Host 路径对应同一个远程文件。 +源代码执行加载仅含可擦除语法的 bootstrap 依赖,不依赖同级包的构建后导出;执行世界中必须提供求值器模块和已安装的 Acorn 与 birpc 依赖。构建后脚本对所有模式使用 `process.js`;`persistent-process.js` 委托同一个循环,并为未指定程序语法的宿主提供 REPL 默认值。内嵌载体通过 `launch.executable` 启动私有 worker,并在每次执行中携带程序格式和保留标志。远端脚本部署提供对应的 `launch.bootstrapPath`。`resolveLaunch()` 在构造时选择本地默认值;显式的远端调用不依赖客户端自身是否打包。不会假设 Host 路径对应同一个远程文件。 包含 `await` 的源码会在原生编译前检查顶层 await,否则原生编译可能将 `await` 当作标识符。Acorn 解析包住源码的异步函数,acorn-walk 沿用 Node REPL 的 visitor、声明提升和按字符位置修改源码的方式进行遍历。类方法的计算属性名参与遍历,方法体仍独立求值。转换包装末尾表达式,求值器解包并等待其值。无需转换的源码使用原生编译;不含 `await` 的提交保留原生语法诊断。语法错误保留解析器消息、可用的源码位置与光标,不包含编译器或转换器的调用帧。动态 `import()` 表达式保持原生形式,使用编译该 Script 时的 `importModuleDynamically` 选项。两个私有进程 bundle 都包含 Acorn、acorn-walk 和 birpc;源码执行导入已安装的包。 diff --git a/packages/ptc-runtime/ptc-runtime-node/package.json b/packages/ptc-runtime/ptc-runtime-node/package.json index dd3bcebbca..e10075f7fd 100644 --- a/packages/ptc-runtime/ptc-runtime-node/package.json +++ b/packages/ptc-runtime/ptc-runtime-node/package.json @@ -27,10 +27,6 @@ "types": "./lib/types/process-entry.d.ts", "default": "./lib/process.js" }, - "./persistent-process": { - "types": "./lib/types/persistent-process-entry.d.ts", - "default": "./lib/persistent-process.js" - }, "./locale/*.json": "./locale/*.json" }, "files": [ diff --git a/packages/ptc-runtime/ptc-runtime-node/src/worker.ts b/packages/ptc-runtime/ptc-runtime-node/src/worker.ts index 6390f36323..d350e27a0a 100644 --- a/packages/ptc-runtime/ptc-runtime-node/src/worker.ts +++ b/packages/ptc-runtime/ptc-runtime-node/src/worker.ts @@ -291,7 +291,7 @@ export class ProgramWorker { .filter(key => !STARTUP_ENVIRONMENT_NAMES.has(key.toUpperCase()) && key.toUpperCase() !== 'ELECTRON_RUN_AS_NODE') .map(key => [key, undefined])) if (packaged) { - env.DSH_PTC_RUNTIME_NODE = this.retainsState ? 'persistent' : '1' + env.DSH_PTC_RUNTIME_NODE = '1' env.NODE_OPTIONS = `${heapFlag} --experimental-vm-modules` } const handle = this.ctx.subprocess.spawn({ argv: this.confined?.argv ?? argv, cwd, env, stdio: { stdin: 'ignore', stdout: 'pipe', stderr: 'pipe', control: 'pipe' }, graceMs: this.config.graceMs }) diff --git a/packages/ptc-runtime/ptc-runtime-node/tests/embedded-carrier.e2e.ts b/packages/ptc-runtime/ptc-runtime-node/tests/embedded-carrier.e2e.ts index 7831b099fb..0d5eccff4f 100644 --- a/packages/ptc-runtime/ptc-runtime-node/tests/embedded-carrier.e2e.ts +++ b/packages/ptc-runtime/ptc-runtime-node/tests/embedded-carrier.e2e.ts @@ -5,16 +5,16 @@ import { Context } from '@deepseek-ai/cordis' import { describe, expect, it, onTestFinished } from 'vitest' import { mountRuntime } from './setup.ts' -const runtimePath = process.env.DSH_PACKAGED_RUNTIME +const runtimePath = process.env.DSH_PACKAGED_RUNTIME ?? '' -describe.skipIf(runtimePath === undefined)('packaged embedded carrier', () => { +describe.skipIf(runtimePath === '')('packaged embedded carrier', () => { it('retains state across evaluations through the packaged runtime', async () => { const cwd = await mkdtemp(join(tmpdir(), 'dsh-packaged-ptc-')) const ctx = new Context() onTestFinished(async () => { await ctx.fiber.dispose(); await rm(cwd, { recursive: true, force: true }) }) const { runtime } = await mountRuntime(ctx, { executionMode: 'persistent-in-memory', - launch: { kind: 'embedded', executable: runtimePath as string }, + launch: { kind: 'embedded', executable: runtimePath }, }) const owner = {} const run = (program: string) => @@ -22,10 +22,21 @@ describe.skipIf(runtimePath === undefined)('packaged embedded carrier', () => { expect((await run('var retained = 40; retained')).value).toBe(40) expect((await run('retained += 2; retained')).value).toBe(42) - // pkg exposes the packaged tree at this URL; the CUA provider prompt uses the same scheme. - const bridge = await run('var bridge = await import("file:///snapshot/node/node_modules/@deepseek-ai/dsh-cua/lib/bridge.js"); typeof bridge.connectCua') - expect(bridge.value).toBe('function') await runtime.release(owner) expect((await run('typeof retained')).value).toBe('undefined') }, 120_000) + + it('does not retain state without an owner', async () => { + const cwd = await mkdtemp(join(tmpdir(), 'dsh-packaged-ptc-')) + const ctx = new Context() + onTestFinished(async () => { await ctx.fiber.dispose(); await rm(cwd, { recursive: true, force: true }) }) + const { runtime } = await mountRuntime(ctx, { + launch: { kind: 'embedded', executable: runtimePath }, + }) + const run = (program: string) => + runtime.run(runtime.resolve({ program, bindings: [], cwd })) + + expect((await run('var retained = 40; return retained')).value).toBe(40) + expect((await run('return typeof retained')).value).toBe('undefined') + }, 120_000) }) diff --git a/packages/ptc-runtime/ptc-runtime-node/tests/host-failures.spec.ts b/packages/ptc-runtime/ptc-runtime-node/tests/host-failures.spec.ts index a8338a52ba..40a85c4a92 100644 --- a/packages/ptc-runtime/ptc-runtime-node/tests/host-failures.spec.ts +++ b/packages/ptc-runtime/ptc-runtime-node/tests/host-failures.spec.ts @@ -501,22 +501,6 @@ describe('Node runtime host failures', () => { expect(Object.fromEntries(Object.entries(spec?.env ?? {}).filter(([, value]) => value !== undefined))) .toEqual({ DSH_PTC_RUNTIME_NODE: '1', NODE_OPTIONS: '--max-old-space-size=512 --experimental-vm-modules' }) }) - - it('selects the persistent embedded bootstrap for retained state', async () => { - const h = await setup({ - executionMode: 'persistent-in-memory', - maxMessageBytes: 2048, - launch: { kind: 'embedded', executable: '/remote/helper' }, - }) - h.onBoot(() => { h.complete({ value: encodePtcJsonWire(1) }) }) - const owner = {} - expect((await h.start({ program: '1', bindings: [], stateOwner: owner })).error).toBeUndefined() - const spec = h.spawn.mock.calls[0]?.[0] - expect(spec?.env?.DSH_PTC_RUNTIME_NODE).toBe('persistent') - expect(spec?.argv).toEqual([process.execPath, '2048']) - expect(Object.fromEntries(Object.entries(spec?.env ?? {}).filter(([, value]) => value !== undefined))) - .toEqual({ DSH_PTC_RUNTIME_NODE: 'persistent', NODE_OPTIONS: '--max-old-space-size=512 --experimental-vm-modules' }) - }) }) it('keeps failed per-call cleanup registered until process observation succeeds', async () => { diff --git a/packages/ptc-runtime/ptc-runtime-node/tests/launch.spec.ts b/packages/ptc-runtime/ptc-runtime-node/tests/launch.spec.ts index fdbe674766..2df2766fde 100644 --- a/packages/ptc-runtime/ptc-runtime-node/tests/launch.spec.ts +++ b/packages/ptc-runtime/ptc-runtime-node/tests/launch.spec.ts @@ -34,7 +34,7 @@ it('resolves the local carrier once while preserving an explicit remote script c expect(resolveLaunch()).toEqual({ kind: 'node-script', executable: process.execPath }) }) -it('selects embedded and script carriers without a persistent-only launch restriction', () => { +it('selects embedded and script bootstrap arguments', () => { expect(bootstrapArgs(noMapping, { kind: 'embedded', executable: '/remote/helper' }, 2048)) .toEqual(['2048']) expect(bootstrapArgs(noMapping, { kind: 'node-script', executable: '/remote/node', bootstrapPath: '/remote/persistent-process.js' }, 2048)) diff --git a/packages/ssh/ssh-helper-runtime/src/index.ts b/packages/ssh/ssh-helper-runtime/src/index.ts index 6ff0f8a80c..ecd0e20299 100644 --- a/packages/ssh/ssh-helper-runtime/src/index.ts +++ b/packages/ssh/ssh-helper-runtime/src/index.ts @@ -27,15 +27,9 @@ export async function runSshHelperRuntime(): Promise { const selection = process.env.DSH_SUBPROCESS_RUNNER Reflect.deleteProperty(process.env, 'DSH_SUBPROCESS_RUNNER') - if (process.env.DSH_PTC_RUNTIME_NODE !== undefined) { - const mode = process.env.DSH_PTC_RUNTIME_NODE + if (process.env.DSH_PTC_RUNTIME_NODE === '1') { Reflect.deleteProperty(process.env, 'DSH_PTC_RUNTIME_NODE') - if (mode !== '1' && mode !== 'persistent') { - throw new Error('DSH_PTC_RUNTIME_NODE must be 1 or persistent') - } - await import(mode === 'persistent' - ? '@deepseek-ai/dsh-ptc-runtime-node/persistent-process' - : '@deepseek-ai/dsh-ptc-runtime-node/process') + await import('@deepseek-ai/dsh-ptc-runtime-node/process') } else if (selection !== undefined) { const { runSelectedSubprocessRunner } = await import('@deepseek-ai/dsh-subprocess-local/runner') await runSelectedSubprocessRunner(selection) diff --git a/python/sdk-runtime/runtime-bootstrap.mjs b/python/sdk-runtime/runtime-bootstrap.mjs index fb79f52233..e84efa7dc3 100644 --- a/python/sdk-runtime/runtime-bootstrap.mjs +++ b/python/sdk-runtime/runtime-bootstrap.mjs @@ -84,15 +84,9 @@ if (resource !== undefined) { } else 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_PTC_RUNTIME_NODE !== undefined) { - const mode = process.env.DSH_PTC_RUNTIME_NODE +} else if (process.env.DSH_PTC_RUNTIME_NODE === '1') { Reflect.deleteProperty(process.env, 'DSH_PTC_RUNTIME_NODE') - if (mode !== '1' && mode !== 'persistent') { - throw new Error('DSH_PTC_RUNTIME_NODE must be 1 or persistent') - } - await import(mode === 'persistent' - ? '@deepseek-ai/dsh-ptc-runtime-node/persistent-process' - : '@deepseek-ai/dsh-ptc-runtime-node/process') + await import('@deepseek-ai/dsh-ptc-runtime-node/process') } else if (selection === undefined) { // A DSH_PRIMARY_RUNTIME set or inherited from the environment, including the empty opt-out, takes precedence over the // downloaded cache.