diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml index 7d8d2db1cf..241dcf71db 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.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 .agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md -2026-07-12-agent-scope-runtime-design.md: 9d70b8048b1d2bb50290d34158d9deb329d5e15e -2026-07-12-agent-scope-runtime-design.zh.md: 278bcede47fee9f67d3d2d2d7135e5357c120161 +2026-07-12-agent-scope-runtime-design.md: 7aebac35d1a5477f0b1d3857e983680ea38bd9f5 +2026-07-12-agent-scope-runtime-design.zh.md: c636bf6f51c58aa5d9f1e3b6f9988b4f4ab5b86c diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md index 9d70b8048b..7aebac35d1 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md +++ b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md @@ -74,7 +74,7 @@ The receiver is a small carrier rather than a transparent proxy for the domain o Scope-aware registries use `ScopedLayers` to own one eager global aggregate and lazily created identity-keyed aggregates. A read resolves the global layer and at most one exact local layer; it never creates state or traverses parentage. Registration visibility and Cordis effect ownership derive from the same context, and reclamation waits until the concrete layer's complete aggregate is empty ([decision](2026-07-12-scoped-layers-store.md)). -Each service retains its domain rule. Named command and prompt views use the shared insertion-ordered shadow merge; tools keep a richer resolver because restrictions filter globals before local tools are added and the reserved Code Mode transport is inserted separately. Prompt variables and tool guards retain live iteration, while tool-provider membership is materialized per assembly. Scope supplies storage lifecycle and named shadowing, not a universal registry view. +Each service retains its domain rule. Named command and prompt views use the shared insertion-ordered shadow merge; tools keep a richer resolver because restrictions filter globals before local tools are added and the reserved PTC mode transport is inserted separately. Prompt variables and tool guards retain live iteration, while tool-provider membership is materialized per assembly. Scope supplies storage lifecycle and named shadowing, not a universal registry view. ### Fused dispatch helpers prevent subject drift @@ -206,7 +206,7 @@ Tool presentation and execution share one private resolver. Prompt assembly rema ### One resolver defines the tool view -The private resolver applies the current presentation mode, live global restrictions, exact local overlay, and local shadowing. Schemas, lookup, execution, Code Mode SDK generation, and restriction validation all use that resolver or its pre-restriction global-name view. +The private resolver applies the current presentation mode, live global restrictions, exact local overlay, and local shadowing. Schemas, lookup, execution, PTC mode SDK generation, and restriction validation all use that resolver or its pre-restriction global-name view. The [subagent composition-controls Agent Note](../feature/2026-07-12-subagent-persona-tool-filter-and-depth.md#tool-filtering-is-one-live-global-view-rule) owns the user-visible allow/deny semantics. The implementation requirement is agreement: a filtered-away global cannot remain executable through a different lookup path, and a locally shadowed definition is the same definition presented and executed. @@ -214,11 +214,11 @@ The [subagent composition-controls Agent Note](../feature/2026-07-12-subagent-pe ### Tool execution owns identity and boundary materialization -The registry assigns every execution a fresh branded `Symbol` token. Nested Code Mode calls carry the outer token as `parent`, so structured output can correlate an inner capture with its enclosing `run_code` result by identity. +The registry assigns every execution a fresh branded `Symbol` token. Nested PTC mode calls carry the outer token as `parent`, so structured output can correlate an inner capture with its enclosing `run_code` result by identity. A fresh registry-assigned Symbol provides collision-free execution identity without a WeakSet membership registry. Callers cannot supply the execution's own token through `ToolExecutionInput`; they only receive the pipeline-owned `ToolExecution` after the registry creates it. This is a trusted typed contract, not a runtime defense against arbitrary casts or JavaScript callers. -Arguments are materialized once where model/tool JSON enters the pipeline. Pre-, around-, and post-execute listeners operate on the typed execution and decisions. Call ID correlation, approval, monotonic guards, and Code Mode nesting remain explicit relational checks. +Arguments are materialized once where model/tool JSON enters the pipeline. Pre-, around-, and post-execute listeners operate on the typed execution and decisions. Call ID correlation, approval, monotonic guards, and PTC mode nesting remain explicit relational checks. After post-execute or outer pipeline normalization, the registry losslessly snapshots the candidate result, converting a snapshot failure into an ordinary error, invokes the call's snapshotted optional `ToolDefinition.finalizeContent` callback, then materializes and freezes the accepted final result once. The callback may replace only content, so structured error identity, contexts, and metadata remain registry-owned even when a tool enforces a last-mile result bound. Every synchronous `tools/result` observer receives that exact committed object, and observer failures are contained individually. An outer pipeline or candidate-snapshot failure is normalized before final content, so observers can discard staged work against the same authoritative boundary. @@ -226,9 +226,9 @@ After post-execute or outer pipeline normalization, the registry losslessly snap SystemPrompt first resolves the global-plus-agent sections, variables, and tool providers into a deterministic registry contribution. The scope-filtered `system-prompt/assemble` waterfall may then reorder, replace, add, or remove any section, variable, or schema. Its returned assembly is authoritative; there is no later restoration pass and no finality metadata on ordinary prompt sections, tool definitions, or provider results. -This is a trusted same-process extension point, not an authority boundary. A listener that changes Code Mode's `run_code` schema or `tools:sdk` instructions, or a structured child's capture schema or instruction, owns preserving a coherent protocol in the assembly it returns. ToolRuntime still reserves `run_code` against ordinary tool registration and restriction because those are registry invariants, but assembly middleware remains free to transform the final model-visible surface. +This is a trusted same-process extension point, not an authority boundary. A listener that changes PTC mode's `run_code` schema or `tools:sdk` instructions, or a structured child's capture schema or instruction, owns preserving a coherent protocol in the assembly it returns. ToolRuntime still reserves `run_code` against ordinary tool registration and restriction because those are registry invariants, but assembly middleware remains free to transform the final model-visible surface. -Scope solves the real isolation problem directly. Structured-output contributions register in the child's exact scope, while Code Mode derives its transport and SDK from the same resolved tool view. A second named-protection system would need another ownership and collision rule across arbitrary schema providers—including providers that intentionally contribute duplicate names—without creating a new trust boundary. +Scope solves the real isolation problem directly. Structured-output contributions register in the child's exact scope, while PTC mode derives its transport and SDK from the same resolved tool view. A second named-protection system would need another ownership and collision rule across arbitrary schema providers—including providers that intentionally contribute duplicate names—without creating a new trust boundary. ### Structured output commits only authoritative outcomes @@ -236,11 +236,11 @@ Structured output combines child-scoped composition with a two-phase execution c For a native call, the observer deletes the stage and commits its value only when that exact execution's final result succeeds. A post-execute block or outer pipeline failure therefore cannot leave a captured value behind. -For a Code Mode SDK call, the inner successful result records `{ parentToken, value }` rather than committing. The observer waits for the `run_code` execution whose token matches `parentToken` and commits only if that outer final result also succeeds. Program failure, runtime abort, or outer post-policy denial discards the pending value. +For a PTC mode SDK call, the inner successful result records `{ parentToken, value }` rather than committing. The observer waits for the `run_code` execution whose token matches `parentToken` and commits only if that outer final result also succeeds. Program failure, runtime abort, or outer post-policy denial discards the pending value. Once a value is pending or committed, a scoped monotonic guard denies later tool calls. The successful structured-output execution calls `exec.concludeTurn()`, so its own immutable result carries `concludesTurn: true` and the loop ends the tool loop at that step. A schema-validation failure remains an ordinary `INVALID_ARGS` tool error and leaves the child able to retry within the same turn. -Pure Code Mode's registry contribution omits `structured_output` from native wire schemas and exposes it through the generated SDK. The assembly waterfall may deliberately change that presentation; execution still validates against the child-scoped definition, and the listener owns the consistency of any alternate model-visible route it creates. +Pure PTC mode's registry contribution omits `structured_output` from native wire schemas and exposes it through the generated SDK. The assembly waterfall may deliberately change that presentation; execution still validates against the child-scoped definition, and the listener owns the consistency of any alternate model-visible route it creates. ### Three execution boundaries are deliberately one-way @@ -332,7 +332,7 @@ The plugin does not police trusted setup by scanning registries or reject prompt The event catalog, service catalog, producer/consumer matrix, configuration catalog, module graph, tool catalog, type-equivalence blocks, and scoped-event resolver map are generated or freshness-gated from source. The [TypeScript semantic-gates Agent Note](../process/2026-07-14-typescript-program-backed-semantic-gates.md) owns Program construction, semantic event discovery, and resolver-generation rules. -Behavioral tests pin scoped routing and disposal, final-entry collision cleanup, publication rollback, ordered quiescence, durable pre/post-commit behavior, live tool filtering across presentation and execution, cooperative prompt assembly, structured-output commit in native and Code Mode, async subagent startup and signal cancellation, worker terminal arbitration, ACP settlement, and process teardown. +Behavioral tests pin scoped routing and disposal, final-entry collision cleanup, publication rollback, ordered quiescence, durable pre/post-commit behavior, live tool filtering across presentation and execution, cooperative prompt assembly, structured-output commit in native and PTC mode, async subagent startup and signal cancellation, worker terminal arbitration, ACP settlement, and process teardown. ## Alternatives considered @@ -385,7 +385,7 @@ The implementation is smaller and its proof follows the same shape as its owners Scope-aware services still maintain global and identity-keyed maps, and operations must carry their real agent explicitly. Async create/resume and subagent start require callers to await ownership transfer and dispose returned handles. -A trusted `system-prompt/assemble` listener can remove or replace Code Mode and structured-output protocol pieces. This is deliberate: the listener owns final composition and must preserve any protocol the deployment expects to remain usable. +A trusted `system-prompt/assemble` listener can remove or replace PTC mode and structured-output protocol pieces. This is deliberate: the listener owns final composition and must preserve any protocol the deployment expects to remain usable. The design trusts typed plugins in the same process. It does not defend against arbitrary casts, stateful getters, mutation that violates readonly contracts, or a plugin deliberately using ambient service access outside the supported composition API. diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md index 278bcede47..c636bf6f51 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md @@ -76,7 +76,7 @@ Receiver 是一个小型载体而非领域对象的透明代理。需要 agent 作用域感知的注册表使用 `ScopedLayers`,拥有一个即时创建的全局 aggregate 和按标识键惰性创建的 aggregate。读取解析全局 layer 和至多一个精确局部 layer;它不创建状态,也从不遍历父级链。注册可见性与 Cordis effect 所有权都从同一个上下文派生,而回收会等待具体 layer 的完整 aggregate 变空(见[决策](2026-07-12-scoped-layers-store.zh.md))。 -每个服务保留其领域规则。命名 command 和提示词视图使用共享的、保持插入顺序的 shadow 合并;工具保留更丰富的 resolver,因为限制会在加入局部工具前过滤全局工具,保留的 Code Mode transport 则单独插入。提示词变量和工具 guard 保持实时迭代,而工具提供方成员关系按每次 assembly 物化。Scope 提供存储生命周期和命名遮蔽,而非通用的注册表视图。 +每个服务保留其领域规则。命名 command 和提示词视图使用共享的、保持插入顺序的 shadow 合并;工具保留更丰富的 resolver,因为限制会在加入局部工具前过滤全局工具,保留的 PTC mode transport 则单独插入。提示词变量和工具 guard 保持实时迭代,而工具提供方成员关系按每次 assembly 物化。Scope 提供存储生命周期和命名遮蔽,而非通用的注册表视图。 ### 融合 dispatch 辅助函数防止主体漂移 @@ -210,7 +210,7 @@ Session 头部、种子和追加的事件是无损 JSON 数据。Session 构造 ### 一个解析器定义工具视图 -私有解析器应用当前展示模式、活跃的全局限制、精确的局部叠加和局部遮蔽。Schema、查找、执行、Code Mode SDK 生成和限制验证都使用该解析器或其限制前的全局名称视图。 +私有解析器应用当前展示模式、活跃的全局限制、精确的局部叠加和局部遮蔽。Schema、查找、执行、PTC mode SDK 生成和限制验证都使用该解析器或其限制前的全局名称视图。 [subagent 组合控制 Agent Note](../feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md#tool-filtering-is-one-live-global-view-rule) 拥有用户可见的 allow/deny 语义。实现要求是一致性:被过滤掉的全局工具不能通过另一条查找路径仍可执行,局部遮蔽的定义就是被展示和执行的同一个定义。 @@ -218,11 +218,11 @@ Session 头部、种子和追加的事件是无损 JSON 数据。Session 构造 ### 工具执行拥有标识和边界物化 -注册表为每次执行分配一个新的带品牌的 `Symbol` token。嵌套的 Code Mode 调用将外层 token 作为 `parent` 携带,因此结构化输出可以通过标识将内层捕获与其外层 `run_code` 结果关联。 +注册表为每次执行分配一个新的带品牌的 `Symbol` token。嵌套的 PTC mode 调用将外层 token 作为 `parent` 携带,因此结构化输出可以通过标识将内层捕获与其外层 `run_code` 结果关联。 注册表分配的新 Symbol 提供无碰撞的执行标识,无需 WeakSet 成员注册表。调用方无法通过 `ToolExecutionInput` 提供执行自身的 token;它们仅在注册表创建后接收流水线拥有的 `ToolExecution`。这是一个可信的类型化约定,而非针对任意强制转换或 JavaScript 调用方的运行时防御。 -参数在模型/工具 JSON 进入流水线时一次性物化。Pre-、around- 和 post-execute 监听器操作类型化的 execution 和决策。Call ID 关联、审批、单调守卫和 Code Mode 嵌套仍然是显式的关系检查。 +参数在模型/工具 JSON 进入流水线时一次性物化。Pre-、around- 和 post-execute 监听器操作类型化的 execution 和决策。Call ID 关联、审批、单调守卫和 PTC mode 嵌套仍然是显式的关系检查。 在 post-execute 或外层流水线完成规范化后,注册表先为候选结果创建无损快照,并将快照失败转为普通错误;随后调用在本次调用创建时已快照的可选 `ToolDefinition.finalizeContent` 回调,最后一次性物化并冻结被接受的最终结果。该回调只能替换内容,因此即使工具强制最后一道结果上限,结构化错误标识、上下文与元数据仍由注册表拥有。每个同步的 `tools/result` 观察者接收该确切的已提交对象,观察者失败被逐个隔离。外层流水线失败或候选快照失败会在最终内容处理之前被规范化,因此观察者可以丢弃针对同一权威边界的暂存工作。 @@ -230,9 +230,9 @@ Session 头部、种子和追加的事件是无损 JSON 数据。Session 构造 SystemPrompt 首先将全局加 agent 的段、变量和工具提供方解析为确定性的注册表贡献。作用域过滤的 `system-prompt/assemble` waterfall 随后可以重排、替换、添加或移除任何段、变量或 schema。其返回的组装结果即为权威;没有后续的恢复步骤,普通提示词段、工具定义或提供方结果上也没有终态元数据。 -这是一个可信的同进程扩展点,而非权限边界。修改 Code Mode 的 `run_code` schema 或 `tools:sdk` 指令,或结构化子级的捕获 schema 或指令的监听器,有责任在其返回的组装中保持协议的一致性。ToolRuntime 仍然保留 `run_code` 不受普通工具注册和限制影响,因为那些是注册表不变式,但 assembly 中间件仍然可以自由变换最终的模型可见表面。 +这是一个可信的同进程扩展点,而非权限边界。修改 PTC mode 的 `run_code` schema 或 `tools:sdk` 指令,或结构化子级的捕获 schema 或指令的监听器,有责任在其返回的组装中保持协议的一致性。ToolRuntime 仍然保留 `run_code` 不受普通工具注册和限制影响,因为那些是注册表不变式,但 assembly 中间件仍然可以自由变换最终的模型可见表面。 -Scope 直接解决了真正的隔离问题。结构化输出贡献注册在子级的精确作用域中,而 Code Mode 从同一个已解析的工具视图派生其传输和 SDK。第二套命名保护系统需要另一套所有权和碰撞规则来覆盖任意 schema 提供方(包括有意贡献重复名称的提供方),却不创建新的信任边界。 +Scope 直接解决了真正的隔离问题。结构化输出贡献注册在子级的精确作用域中,而 PTC mode 从同一个已解析的工具视图派生其传输和 SDK。第二套命名保护系统需要另一套所有权和碰撞规则来覆盖任意 schema 提供方(包括有意贡献重复名称的提供方),却不创建新的信任边界。 @@ -242,11 +242,11 @@ Scope 直接解决了真正的隔离问题。结构化输出贡献注册在子 对于原生调用,观察者仅在该确切执行的最终结果成功时才删除暂存并提交其值。因此 post-execute 阻止或外层流水线失败不会留下已捕获的值。 -对于 Code Mode SDK 调用,内层成功结果记录 `{ parentToken, value }` 而非提交。观察者等待 token 匹配 `parentToken` 的 `run_code` 执行,仅在该外层最终结果也成功时才提交。程序失败、运行时中止或外层 post-policy 拒绝会丢弃待定值。 +对于 PTC mode SDK 调用,内层成功结果记录 `{ parentToken, value }` 而非提交。观察者等待 token 匹配 `parentToken` 的 `run_code` 执行,仅在该外层最终结果也成功时才提交。程序失败、运行时中止或外层 post-policy 拒绝会丢弃待定值。 一旦值处于待定或已提交状态,作用域单调守卫拒绝后续工具调用。成功的结构化输出执行会调用 `exec.concludeTurn()`,因此其自身不可变结果携带 `concludesTurn: true`,循环在该步骤结束工具循环。Schema 验证失败仍然是普通的 `INVALID_ARGS` 工具错误,子级可以在同一轮次内重试。 -纯 Code Mode 的注册表贡献从原生 wire schema 中省略 `structured_output`,并通过生成的 SDK 暴露它。Assembly waterfall 可以有意改变该展示;执行仍然针对子作用域定义进行验证,监听器拥有其创建的任何替代模型可见路由的一致性。 +纯 PTC mode 的注册表贡献从原生 wire schema 中省略 `structured_output`,并通过生成的 SDK 暴露它。Assembly waterfall 可以有意改变该展示;执行仍然针对子作用域定义进行验证,监听器拥有其创建的任何替代模型可见路由的一致性。 @@ -342,7 +342,7 @@ TypeScript 无法管控 JavaScript 强制转换、直接 Cordis dispatch、进 事件目录、服务目录、生产者/消费方矩阵、配置目录、模块图、工具目录、type-equiv 块和作用域事件解析器映射都是从源码生成或受新鲜度门禁约束的。[TypeScript 语义门禁 Agent Note](../process/2026-07-14-typescript-program-backed-semantic-gates.zh.md) 拥有 Program 构造、语义事件发现和解析器生成规则。 -行为测试固定了作用域路由和 dispose、最终写入注册表时的碰撞清理、发布回滚、有序完全停稳、持久化前/后提交行为、跨展示和执行的活跃工具过滤、协作式提示词组装、原生和 Code Mode 中的结构化输出提交、异步 subagent 启动和信号取消、worker 终端仲裁、ACP 结算和进程拆除。 +行为测试固定了作用域路由和 dispose、最终写入注册表时的碰撞清理、发布回滚、有序完全停稳、持久化前/后提交行为、跨展示和执行的活跃工具过滤、协作式提示词组装、原生和 PTC mode 中的结构化输出提交、异步 subagent 启动和信号取消、worker 终端仲裁、ACP 结算和进程拆除。 ## 曾考虑的替代方案 @@ -395,7 +395,7 @@ Worker 消息、进程死亡和持久化输入确实跨越所有权和序列化 作用域感知服务仍然维护全局和按标识键索引的映射,操作必须显式携带其真实 agent。异步创建/恢复和 subagent start 要求调用方等待所有权转移并 dispose 返回的句柄。 -可信的 `system-prompt/assemble` 监听器可以移除或替换 Code Mode 和结构化输出协议片段。这是有意为之:监听器拥有最终组合,必须保持部署期望仍可用的任何协议。 +可信的 `system-prompt/assemble` 监听器可以移除或替换 PTC mode 和结构化输出协议片段。这是有意为之:监听器拥有最终组合,必须保持部署期望仍可用的任何协议。 该设计信任同进程中的类型化插件。它不防御任意强制转换、有状态 getter、违反 readonly 约定的修改,或插件有意在支持的组合 API 之外使用环境服务访问。 diff --git a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml index 29807cfb7c..41d4664d7d 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.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 .agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md -2026-07-19-cooperative-tool-cancellation.md: 781202688a5cbcd7076ee694fc7dd9489683d8e8 -2026-07-19-cooperative-tool-cancellation.zh.md: ec35734eef91c5c774d1be814b221e9fdb8f65fa +2026-07-19-cooperative-tool-cancellation.md: 5ca2b44b24a4af189df27c6f724021a7bf290e2b +2026-07-19-cooperative-tool-cancellation.zh.md: d6d49fb44c5d5629729a8b5b4bad07ea9b2f7ee9 diff --git a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md index 781202688a..5ca2b44b24 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md +++ b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md @@ -16,7 +16,7 @@ Cancellation can arrive before policy, during approval, inside an around-dispatc `ToolExecutionInput.signal` is a required readonly `AbortSignal`. `ToolExecution.signal` and `ToolRunContext.signal` are therefore required and readonly as well. Every typed caller supplies the signal it owns; the registry provides no overload, default controller, never-abort sentinel, or convenience execution path. -`ToolDefinition.execute(args, exec)` keeps its existing signature. `defineTool()` contextually types `exec.signal` as a required `AbortSignal`, so every registered TypeScript tool can observe or forward cancellation without a cast. First-party direct callers and nested Code Mode dispatches pass their current operation signal explicitly. +`ToolDefinition.execute(args, exec)` keeps its existing signature. `defineTool()` contextually types `exec.signal` as a required `AbortSignal`, so every registered TypeScript tool can observe or forward cancellation without a cast. First-party direct callers and nested PTC mode dispatches pass their current operation signal explicitly. The registry trusts this typed same-process contract. It does not perform runtime `AbortSignal` validation or add hostile-input tests for an omitted or malformed signal. Validation remains at parser/config, model/tool JSON, durable/file, worker, process, and wire boundaries; untyped JavaScript that violates the TypeScript interface has no compatibility contract. @@ -46,7 +46,7 @@ This decision requires cancellation at the tool invocation boundary only. Making ## Verification -[`execution-signal-types.spec.ts`](../../../../packages/core/tools/tests/execution-signal-types.spec.ts) proves the required exact signal types, readonly observer and tool views, mutable-but-required around-dispatch view, and `defineTool()` inference. [`tools.spec.ts`](../../../../packages/core/tools/tests/tools.spec.ts) covers pre-aborted materialization, phase skipping, policy and wrapper races, body invocation classification, caller-signal fusion, error precedence, context retention, and quiescent drainage. [`tool-calls.spec.ts`](../../../../packages/core/agent-loop/tests/tool-calls.spec.ts) and [`contract-regressions.spec.ts`](../../../../packages/core/agent-loop/tests/contract-regressions.spec.ts) cover balanced durable results for undispatched siblings. [`code-mode.spec.ts`](../../../../packages/core/tools/tests/code-mode.spec.ts) and first-party integration suites cover explicit forwarding, while [`timeout-policy.spec.ts`](../../../../packages/guard/timeout-policy/tests/timeout-policy.spec.ts) preserves timeout ownership. +[`execution-signal-types.spec.ts`](../../../../packages/core/tools/tests/execution-signal-types.spec.ts) proves the required exact signal types, readonly observer and tool views, mutable-but-required around-dispatch view, and `defineTool()` inference. [`tools.spec.ts`](../../../../packages/core/tools/tests/tools.spec.ts) covers pre-aborted materialization, phase skipping, policy and wrapper races, body invocation classification, caller-signal fusion, error precedence, context retention, and quiescent drainage. [`tool-calls.spec.ts`](../../../../packages/core/agent-loop/tests/tool-calls.spec.ts) and [`contract-regressions.spec.ts`](../../../../packages/core/agent-loop/tests/contract-regressions.spec.ts) cover balanced durable results for undispatched siblings. [`ptc.spec.ts`](../../../../packages/core/tools/tests/ptc.spec.ts) and first-party integration suites cover explicit forwarding, while [`timeout-policy.spec.ts`](../../../../packages/guard/timeout-policy/tests/timeout-policy.spec.ts) preserves timeout ownership. No registry test can prove that arbitrary third-party same-process code observes the signal or stops in bounded time. Capability tests continue to prove cancellation and quiescence at the boundary that owns each side effect. diff --git a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md index ec35734eef..d6d49fb44c 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md @@ -16,7 +16,7 @@ Status: implemented `ToolExecutionInput.signal` 是必填且只读的 `AbortSignal`,因此 `ToolExecution.signal` 和 `ToolRunContext.signal` 也都是必填且只读。每个类型化调用方显式提供自己持有的信号;注册表不提供重载、默认控制器、永不中止哨兵或便捷执行路径。 -`ToolDefinition.execute(args, exec)` 保持现有签名。`defineTool()` 会把 `exec.signal` 上下文推断为必填的 `AbortSignal`,因此每个已注册的 TypeScript 工具都能在无需类型断言的情况下观察或转发取消。所有第一方直接调用方和 Code Mode 嵌套调度都会显式传入当前操作的信号。 +`ToolDefinition.execute(args, exec)` 保持现有签名。`defineTool()` 会把 `exec.signal` 上下文推断为必填的 `AbortSignal`,因此每个已注册的 TypeScript 工具都能在无需类型断言的情况下观察或转发取消。所有第一方直接调用方和 PTC mode 嵌套调度都会显式传入当前操作的信号。 注册表信任这份类型化同进程约定。它不在运行时校验 `AbortSignal`,也不为缺失或畸形信号添加敌意输入测试。校验仍位于解析器与配置、模型与工具 JSON、持久化与文件、worker、进程和协议边界;违反 TypeScript 接口的无类型 JavaScript 不享有兼容性约定。 @@ -46,7 +46,7 @@ Status: implemented ## 验证 -[`execution-signal-types.spec.ts`](../../../../packages/core/tools/tests/execution-signal-types.spec.ts) 证明必填的精确信号类型、观察者与工具的只读视图、环绕调度可替换但不可删除的视图,以及 `defineTool()` 推断。[`tools.spec.ts`](../../../../packages/core/tools/tests/tools.spec.ts) 覆盖进入时已中止的物化与阶段跳过、策略和包装层竞态、工具主体调用分类、调用方信号融合、错误优先级、上下文保留和完全停稳。[`tool-calls.spec.ts`](../../../../packages/core/agent-loop/tests/tool-calls.spec.ts) 与 [`contract-regressions.spec.ts`](../../../../packages/core/agent-loop/tests/contract-regressions.spec.ts) 覆盖为未调度的同批调用补齐持久化结果。[`code-mode.spec.ts`](../../../../packages/core/tools/tests/code-mode.spec.ts) 和第一方集成测试覆盖显式转发,[`timeout-policy.spec.ts`](../../../../packages/guard/timeout-policy/tests/timeout-policy.spec.ts) 保持超时归属。 +[`execution-signal-types.spec.ts`](../../../../packages/core/tools/tests/execution-signal-types.spec.ts) 证明必填的精确信号类型、观察者与工具的只读视图、环绕调度可替换但不可删除的视图,以及 `defineTool()` 推断。[`tools.spec.ts`](../../../../packages/core/tools/tests/tools.spec.ts) 覆盖进入时已中止的物化与阶段跳过、策略和包装层竞态、工具主体调用分类、调用方信号融合、错误优先级、上下文保留和完全停稳。[`tool-calls.spec.ts`](../../../../packages/core/agent-loop/tests/tool-calls.spec.ts) 与 [`contract-regressions.spec.ts`](../../../../packages/core/agent-loop/tests/contract-regressions.spec.ts) 覆盖为未调度的同批调用补齐持久化结果。[`ptc.spec.ts`](../../../../packages/core/tools/tests/ptc.spec.ts) 和第一方集成测试覆盖显式转发,[`timeout-policy.spec.ts`](../../../../packages/guard/timeout-policy/tests/timeout-policy.spec.ts) 保持超时归属。 任何注册表测试都无法证明任意第三方同进程代码会观察信号或在有界时间内停止。各能力的测试仍需在拥有相应副作用的边界证明取消与完全停稳。 diff --git a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml index 84a51ba792..88da18837a 100644 --- a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.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 .agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md -2026-07-20-canonical-tool-output-contract.md: 0b3bd788fd1ab63aa6929827ef82e5ba732e5324 -2026-07-20-canonical-tool-output-contract.zh.md: bbcd519de8d02df14be931b44f8dc44fc06b8f6a +2026-07-20-canonical-tool-output-contract.md: 95f313732c78f4f9acf2a08ce492968e7942e2f2 +2026-07-20-canonical-tool-output-contract.zh.md: 39b7ea88df3c290bd8095fa05bf05497be262d74 diff --git a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md b/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md index 0b3bd788fd..95f313732c 100644 --- a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md +++ b/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md @@ -6,7 +6,7 @@ English | [中文](2026-07-20-canonical-tool-output-contract.zh.md) ## Problem -Tool bodies previously authored model-facing `ContentBlock[]` directly, optionally wrapping it with opaque `meta`. Native function calling therefore had a usable human projection, but a programmatic caller had no stable domain value: Code Mode flattened the blocks back into a string, dynamic tools repeated the content shape, and policy could replace presentation without any way to distinguish that change from replacing the operation's result. Several capability seams already returned richer provider values only to discard them at their model-facing tool boundary. +Tool bodies previously authored model-facing `ContentBlock[]` directly, optionally wrapping it with opaque `meta`. Native function calling therefore had a usable human projection, but a programmatic caller had no stable domain value: PTC mode flattened the blocks back into a string, dynamic tools repeated the content shape, and policy could replace presentation without any way to distinguish that change from replacing the operation's result. Several capability seams already returned richer provider values only to discard them at their model-facing tool boundary. The durable session contract made that presentation authoritative for replay, but persisting every rich intermediate value would enlarge logs, expose implementation data to compaction and migration, and incorrectly turn an execution-local API into session format. The foundation instead needs one typed value during execution and an explicit projection into the existing durable/model-facing content. @@ -34,7 +34,7 @@ type ToolExecutionResult = `tools/post-execute` has two mutually exclusive successful projections. Replacing `content` changes only Native/model presentation and preserves the canonical value and metadata. Replacing `value` revalidates the replacement and recomputes both presentation projections. A block removes the value and becomes a failure. Content replacement is therefore not a confidentiality mechanism: policy that must prevent programmatic access blocks the call or replaces the value. -Canonical values are execution-local. The agent loop persists `tool/result` with only `content`, `error`, and optional `meta`; Code Mode's `tool/code-dispatch` persists the sub-call's rendered `content` and `isError`. Neither event stores the canonical intermediate value, so replay reproduces presentation but cannot reconstruct the programmatic result. When a tool declares `presentationMeta`, it is computed only for a direct surface call; a nested Code dispatch gets no metadata or result card. The outer `run_code` card instead reads final post-policy content and declares no presentation metadata. Generic and tool-owned spill projections similarly skip nested dispatches, whose canonical value never enters model context. +Canonical values are execution-local. The agent loop persists `tool/result` with only `content`, `error`, and optional `meta`; PTC mode's `tool/ptc-dispatch` persists the sub-call's rendered `content` and `isError`. Neither event stores the canonical intermediate value, so replay reproduces presentation but cannot reconstruct the programmatic result. When a tool declares `presentationMeta`, it is computed only for a direct surface call; a nested Code dispatch gets no metadata or result card. The outer `run_code` card instead reads final post-policy content and declares no presentation metadata. Generic and tool-owned spill projections similarly skip nested dispatches, whose canonical value never enters model context. The first-party tools preserve their existing Native text while returning domain DTOs: @@ -66,7 +66,7 @@ MCP bridges preserve protocol blocks through `McpResult<{...}> = { content: Json ## Alternatives considered -- **Return rendered text to Code Mode:** rejected because callers would continue scraping prose for job ids, mount ids, paths, and structured provider results. +- **Return rendered text to PTC mode:** rejected because callers would continue scraping prose for job ids, mount ids, paths, and structured provider results. - **Persist canonical values on `tool/result`:** rejected because nested execution values are not model history, need not survive replay, and would create a session-format and storage commitment unrelated to Native reconstruction. - **Let tools return both value and content:** rejected because two author-owned results can disagree and policy cannot state which one is authoritative. The renderer makes presentation a deterministic projection of the validated value. - **Treat content replacement as value redaction:** rejected because presentation and programmatic access are different consumers; hiding only the former would create a false security boundary. diff --git a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.zh.md b/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.zh.md index bbcd519de8..39b7ea88df 100644 --- a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -工具主体过去直接编写面向模型的 `ContentBlock[]`,并可选择将其与不透明的 `meta` 包装在一起。因此,Native 模式的 Function Calling(函数调用)虽然拥有可供人阅读的投影,但程序化调用方没有稳定的领域值:Code Mode 会将内容块重新展平为字符串,动态工具会重复定义内容形态,策略也可以替换展示内容,却无法区分这项变更究竟是替换展示,还是替换操作结果。多个能力 seam 已经返回了信息更丰富的提供方值,却又在面向模型的工具边界丢弃这些值。 +工具主体过去直接编写面向模型的 `ContentBlock[]`,并可选择将其与不透明的 `meta` 包装在一起。因此,Native 模式的 Function Calling(函数调用)虽然拥有可供人阅读的投影,但程序化调用方没有稳定的领域值:PTC mode 会将内容块重新展平为字符串,动态工具会重复定义内容形态,策略也可以替换展示内容,却无法区分这项变更究竟是替换展示,还是替换操作结果。多个能力 seam 已经返回了信息更丰富的提供方值,却又在面向模型的工具边界丢弃这些值。 持久会话约定将这份展示内容视为回放时的权威来源,但如果持久化每一个信息丰富的中间值,就会扩大日志、使实现数据进入压缩(compaction)和迁移流程,还会错误地把执行期本地 API 变成会话格式的一部分。因此,系统底层需要在执行期间保留一个类型化值,并显式将其投影为现有的持久化内容和模型可见内容。 @@ -34,7 +34,7 @@ type ToolExecutionResult = `tools/post-execute` 为成功结果提供两种互斥的投影方式。替换 `content` 只改变 Native/模型展示,并保留规范值和元数据。替换 `value` 会重新校验替代值,并重新计算两份展示投影。阻止操作会移除值并转为失败。因此,替换内容并不是保密机制:必须阻止程序化访问的策略,应当阻止调用或替换值。 -规范值仅存在于执行期间。agent loop(智能体循环)持久化的 `tool/result` 只包含 `content`、`error` 和可选的 `meta`;Code Mode 的 `tool/code-dispatch` 持久化子调用渲染后的 `content` 与 `isError`。两个事件都不存储规范中间值,因此回放可以重现展示,却无法重建程序化结果。当工具声明 `presentationMeta` 时,系统只会为直接的外层调用计算它;嵌套 Code 分发没有元数据或结果卡片。外层 `run_code` 卡片则读取最终的 post-policy 内容,并且不声明展示元数据。通用以及工具自有的 spill 投影同样跳过嵌套分发,因为它们的规范值永远不会进入模型上下文。 +规范值仅存在于执行期间。agent loop(智能体循环)持久化的 `tool/result` 只包含 `content`、`error` 和可选的 `meta`;PTC mode 的 `tool/ptc-dispatch` 持久化子调用渲染后的 `content` 与 `isError`。两个事件都不存储规范中间值,因此回放可以重现展示,却无法重建程序化结果。当工具声明 `presentationMeta` 时,系统只会为直接的外层调用计算它;嵌套 Code 分发没有元数据或结果卡片。外层 `run_code` 卡片则读取最终的 post-policy 内容,并且不声明展示元数据。通用以及工具自有的 spill 投影同样跳过嵌套分发,因为它们的规范值永远不会进入模型上下文。 第一方工具在保持现有 Native 文本不变的同时返回领域 DTO: @@ -66,7 +66,7 @@ MCP 桥接层通过 `McpResult<{...}> = { content: JsonValue[]; structuredConten ## 备选方案 -- **向 Code Mode 返回渲染后的文本:**不予采纳。调用方仍需从自然语言中提取 job id、挂载 id、路径和结构化提供方结果。 +- **向 PTC mode 返回渲染后的文本:**不予采纳。调用方仍需从自然语言中提取 job id、挂载 id、路径和结构化提供方结果。 - **在 `tool/result` 上持久化规范值:**不予采纳。嵌套执行值不属于模型历史记录,无需在回放后继续存在;持久化还会引入与 Native 重建无关的会话格式和存储承诺。 - **允许工具同时返回值和内容:**不予采纳。由作者分别维护的两份结果可能互相矛盾,策略也无法说明哪一份才是权威结果。渲染器会根据已校验值确定性地产生展示。 - **将内容替换视为值脱敏:**不予采纳。展示内容和程序化访问面向不同消费方;只隐藏前者会制造虚假的安全边界。 diff --git a/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.i18n.yaml index 7d56d900be..dd6e455025 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.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 .agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.md -2026-07-31-code-runtime-portable-identifier-seam.md: 6d75f0b0872a5d4b92403f597f5551276e2d7c9f -2026-07-31-code-runtime-portable-identifier-seam.zh.md: 98f4665f9c9964701cdeb108e411ae08121af9e0 +2026-07-31-code-runtime-portable-identifier-seam.md: 2011b0f6bc8209e628227ddf486aa1143a63688a +2026-07-31-code-runtime-portable-identifier-seam.zh.md: 36af33366d004fedc6b1077a937d6519de743638 diff --git a/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.md b/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.md index 6d75f0b087..2011b0f6bc 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.md +++ b/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.md @@ -6,7 +6,7 @@ English | [中文](2026-07-31-code-runtime-portable-identifier-seam.zh.md) ## Problem -The code-runtime seam promises that a binding-namespace list valid on one backend is valid on every backend, so a Code Mode consumer can hand the same bindings to any registered runtime without knowing its language. The first backend, `dsh-code-runtime-worker-thread`, privately owned the identifier rules that enforce part of that promise: an `IDENTIFIER` regex that allowed the JS-only `$`, a `RESERVED_WORDS` set holding only ECMAScript keywords, and a `RESERVED_ERROR_PROPERTIES` set of three JS `Error` slots. Those rules described the worker's own language, not the seam's portability contract. +The code-runtime seam promises that a binding-namespace list valid on one backend is valid on every backend, so a PTC mode consumer can hand the same bindings to any registered runtime without knowing its language. The first backend, `dsh-code-runtime-worker-thread`, privately owned the identifier rules that enforce part of that promise: an `IDENTIFIER` regex that allowed the JS-only `$`, a `RESERVED_WORDS` set holding only ECMAScript keywords, and a `RESERVED_ERROR_PROPERTIES` set of three JS `Error` slots. Those rules described the worker's own language, not the seam's portability contract. A second backend written against a different language (CPython) would either re-declare its own rules — letting `lambda` pass the worker and fail Python, or `$tools` pass the worker and fail every non-JS backend — or import the worker's, inverting the dependency so a Service Provider reached into a sibling Service Provider. Neither keeps the portability promise real: it would hold only for the backend a caller happened to test against. @@ -25,7 +25,7 @@ The constants live in the Service Definition even though the worker is the only ## Scope -This decision delivers only the Service Definition extension and the worker's adoption of it. The `py-types` renderer and Code Mode language dispatch are owned by the [language-dispatch note](../feature/2026-07-31-code-mode-language-dispatch.md); a Python backend does not exist yet. The Service Definition README keeps its worker-only wording for that reason: linking to a `dsh-code-runtime-python` README that does not exist would break the dead-link gate. +This decision delivers only the Service Definition extension and the worker's adoption of it. The `py-types` renderer and PTC mode language dispatch are owned by the [language-dispatch note](../feature/2026-07-31-ptc-language-dispatch.md); a Python backend does not exist yet. The Service Definition README keeps its worker-only wording for that reason: linking to a `dsh-code-runtime-python` README that does not exist would break the dead-link gate. `RESERVED_BINDING_GLOBALS` encodes the Python bootstrap's concrete design ahead of the backend itself: it seeds exactly `__builtins__`/`__name__` and wraps the program under `__dsh_main__`. A Python backend that seeds any additional module global (`__doc__`, `__loader__`, `__spec__`, `__file__`, `__package__`, …) MUST widen this set in the same change, exactly as adding a language widens `PORTABLE_RESERVED_WORDS` — a name the bootstrap seeds but the set omits is the portability split this contract exists to prevent. diff --git a/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.zh.md b/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.zh.md index 98f4665f9c..36af33366d 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-31-code-runtime-portable-identifier-seam.zh.md @@ -6,7 +6,7 @@ Status: implemented ## Problem -code-runtime seam 承诺:在一个后端上有效的绑定命名空间列表,在每个后端上都有效,因此 Code Mode 消费方可以把同一组绑定交给任何已注册的运行时,而不必知道它的语言。首个后端 `dsh-code-runtime-worker-thread` 私自拥有了执行这项承诺一部分的标识符规则:一个允许 JS 专有 `$` 的 `IDENTIFIER` 正则、一个只含 ECMAScript 关键字的 `RESERVED_WORDS` 集合,以及一个含三个 JS `Error` 槽位的 `RESERVED_ERROR_PROPERTIES` 集合。这些规则描述的是 worker 自身的语言,而非 seam 的可移植性约定。 +code-runtime seam 承诺:在一个后端上有效的绑定命名空间列表,在每个后端上都有效,因此 PTC mode 消费方可以把同一组绑定交给任何已注册的运行时,而不必知道它的语言。首个后端 `dsh-code-runtime-worker-thread` 私自拥有了执行这项承诺一部分的标识符规则:一个允许 JS 专有 `$` 的 `IDENTIFIER` 正则、一个只含 ECMAScript 关键字的 `RESERVED_WORDS` 集合,以及一个含三个 JS `Error` 槽位的 `RESERVED_ERROR_PROPERTIES` 集合。这些规则描述的是 worker 自身的语言,而非 seam 的可移植性约定。 一个针对不同语言(CPython)编写的第二后端,要么重新声明自己的规则——让 `lambda` 通过 worker 却在 Python 上失败,或让 `$tools` 通过 worker 却在每个非 JS 后端上失败——要么导入 worker 的规则,从而反转依赖,使一个 Service Provider 伸手进入另一个兄弟 Service Provider。二者都无法让可移植承诺成真:它只对调用方恰好测试过的那个后端成立。 @@ -25,7 +25,7 @@ Service Definition 同时把可移植标识符子集收窄为 `[A-Za-z_][A-Za-z0 ## Scope -本决策只交付 Service Definition 扩展与 worker 对它的采用。`py-types` 渲染器与 Code Mode 的语言分发归[语言分发 note](../feature/2026-07-31-code-mode-language-dispatch.zh.md) 所有;Python 后端尚不存在。Service Definition README 因此保留仅描述 worker 的措辞:链接到一个不存在的 `dsh-code-runtime-python` README 会破坏死链 gate。 +本决策只交付 Service Definition 扩展与 worker 对它的采用。`py-types` 渲染器与 PTC mode 的语言分发归[语言分发 note](../feature/2026-07-31-ptc-language-dispatch.zh.md) 所有;Python 后端尚不存在。Service Definition README 因此保留仅描述 worker 的措辞:链接到一个不存在的 `dsh-code-runtime-python` README 会破坏死链 gate。 `RESERVED_BINDING_GLOBALS` 先于后端本身编码了 Python bootstrap 的具体设计:它恰好 seed `__builtins__`/`__name__`,并把程序包装在 `__dsh_main__` 之下。任何 seed 额外模块 global(`__doc__`、`__loader__`、`__spec__`、`__file__`、`__package__` 等)的 Python 后端必须在同一改动中扩宽此集合,正如新增一门语言即扩宽 `PORTABLE_RESERVED_WORDS`——bootstrap 会 seed 却不在集合中的名称,正是本约定要防止的可移植性分裂。 diff --git a/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.i18n.yaml index 2a37960848..10370530e9 100644 --- a/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.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 .agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md -2026-08-09-client-conversation-node-assembly.md: 12069d129227cce13eb5f9f39e636921d4bf9efa -2026-08-09-client-conversation-node-assembly.zh.md: 957c2b291293761ff2417f60093b7962bb75bbdf +2026-08-09-client-conversation-node-assembly.md: e37f2ab3bdf9f3943cb1bcd193d5ea578a0b2c19 +2026-08-09-client-conversation-node-assembly.zh.md: be23b321b710f2f56e3e7af870ba554adb355cef diff --git a/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md b/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md index 12069d1292..e37f2ab3bd 100644 --- a/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md +++ b/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md @@ -359,7 +359,7 @@ Conversation tests cover every built-in Chat Definition, Assistant Step data, Tu Slot type/runtime tests pin required parent-provided common inject, the `hookContext` type, Hook isolation across Node contexts, stable factory/Hook identity, and the absence of business-renderer rerenders for unrelated Session publications. Existing entry-owned Observable Hook tests continue to pin the path that does not use a contextual factory. -Assembled Web snapshots, GUI tests, and browser scenarios cover the real plugin graph. Browser evidence compares Assistant streaming→settled, Bash running→settled, and Code Mode root + nested subcalls against master layout. +Assembled Web snapshots, GUI tests, and browser scenarios cover the real plugin graph. Browser evidence compares Assistant streaming→settled, Bash running→settled, and PTC mode root + nested subcalls against master layout. History-path tests cover complete replace, non-overlapping prepend, complete-range deduplication, partial-overlap rejection, empty-page `hasMore` convergence, and scalar live append. Scalar and packed representations of the same Assistant history produce equal Chat and Trajectory State, timing boundaries, and final Nodes; one packed run remains one Match through replace, prepend, Location replay, and registry rebuild. diff --git a/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.zh.md b/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.zh.md index 957c2b2912..be23b321b7 100644 --- a/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.zh.md @@ -359,7 +359,7 @@ Conversation tests 覆盖全部内建 Chat Definition、Assistant Step data、Tu Slot type/runtime tests 固定父注册必须提供声明的 common inject、`hookContext` 类型、不同 Node context 的 Hook 隔离、factory/Hook identity 稳定,以及无关 Session publication 不重渲染业务 renderer。原 entry-owned Observable Hook 测试继续固定未使用 contextual factory 的路径。 -Assembled Web snapshot、GUI 和浏览器场景覆盖真实 plugin graph。浏览器证据比较 Assistant streaming→settled、Bash running→settled 以及 Code Mode root + nested subcalls 与 master 的布局。 +Assembled Web snapshot、GUI 和浏览器场景覆盖真实 plugin graph。浏览器证据比较 Assistant streaming→settled、Bash running→settled 以及 PTC mode root + nested subcalls 与 master 的布局。 历史链路验证同时覆盖完整 replace、非重叠 prepend、完整 range 去重、部分重叠拒绝、空页 `hasMore` 收敛和 scalar live append。相同 Assistant 历史的 scalar 与 packed 表示产生相同 Chat/Trajectory State、timing boundary 与最终 Node;一个 packed run 在 replace、prepend、Location replay 与 registry rebuild 中始终只保留一个 Match。 diff --git a/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.md b/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.md index 0234f8b784..ca3effd6c0 100644 --- a/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.md +++ b/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.md @@ -12,7 +12,7 @@ The direct entry point still needs the same deployment model state as Web-create ## Decision -The shipped `headless` profile contains `dsh-base` and `dsh-headless`. The base supplies the disabled module-HMR default; the headless bundle supplies its persona and tool mode, mounts the Code Mode worker explicitly, and inserts `headless-runner` without overriding that policy. Its tree contains no browser Connection, HTTP server, Web runtime, or browser client. Code Mode and Session persistence are one-shot Agent capabilities independent of Web presentation. +The shipped `headless` profile contains `dsh-base` and `dsh-headless`. The base supplies the disabled module-HMR default; the headless bundle supplies its persona and tool mode, mounts the PTC mode worker explicitly, and inserts `headless-runner` without overriding that policy. Its tree contains no browser Connection, HTTP server, Web runtime, or browser client. PTC mode and Session persistence are one-shot Agent capabilities independent of Web presentation. `headless-runner` is a direct core entry point. After Loader settlement, it reads `ctx.agentDefaultModel.currentSelection()`, creates a fresh persisted Agent through `ctx.agents.create`, installs that `ModelSelection` in the Agent scope, waits for startup quiescence, anchors the Session sequence, submits one ordinary user message, and waits for quiescence again. It awaits `ctx.sessions.flush`, folds its durable event interval for the last non-empty assistant text and final `turn/end` reason, writes the text plus one newline to stdout, and requests bounded launcher shutdown with exit 0 exactly when the reason is `completed`. [Headless reasoning progress](../feature/2026-08-21-headless-reasoning-progress.md) owns the live stderr projection; a terminal `error` reason writes its durable code and message there, and unexpected driver failures also use stderr and exit 1. @@ -34,7 +34,7 @@ Package tests use the real Session store and Agent registry around a scripted Ag | Build a Host-only one-shot bundle around browser RPC | A local one-shot entry point has no client boundary. | | Use the in-process Connection carrier for product-level protocol coverage | Product execution would depend on an unrelated protocol solely to exercise that protocol. | | Give headless a separate provider/model config | Direct and Web creation would have independent defaults and persistence. | -| Omit Code Mode and Session persistence | Both capabilities belong to one-shot Agent execution rather than Web presentation. | +| Omit PTC mode and Session persistence | Both capabilities belong to one-shot Agent execution rather than Web presentation. | | Normalize every tuple containing Web and headless bundles | Bundle lists are an extension surface; only the exact installation-owned tuple is safe to classify. | ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.zh.md b/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.zh.md index f864c94d69..4da5c95393 100644 --- a/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -随附的 `headless` profile 包含 `dsh-base` 与 `dsh-headless`。base 提供默认禁用模块 HMR(热模块替换)的策略;headless 组合包提供自身的 persona 与工具模式、显式挂载 Code Mode worker,并在不覆盖该策略的情况下插入 `headless-runner`。其插件树不包含浏览器 Connection、HTTP server、Web 运行时或浏览器客户端。Code Mode 与会话持久化均为独立于 Web 呈现的一次性 Agent 能力。 +随附的 `headless` profile 包含 `dsh-base` 与 `dsh-headless`。base 提供默认禁用模块 HMR(热模块替换)的策略;headless 组合包提供自身的 persona 与工具模式、显式挂载 PTC mode worker,并在不覆盖该策略的情况下插入 `headless-runner`。其插件树不包含浏览器 Connection、HTTP server、Web 运行时或浏览器客户端。PTC mode 与会话持久化均为独立于 Web 呈现的一次性 Agent 能力。 `headless-runner` 是直接使用核心服务的入口。Loader 完全加载后,它读取 `ctx.agentDefaultModel.currentSelection()`,通过 `ctx.agents.create` 创建一个新的持久化 Agent,在 Agent 作用域中安装该 `ModelSelection`,等待启动工作完全停稳,锚定会话事件序号,提交一条普通用户消息,再次等待完全停稳。随后,它等待 `ctx.sessions.flush`,折叠自身持有的持久事件区间,以取得最后一条非空 assistant 文本和最终 `turn/end` 结束原因,将文本连同一个换行写入 stdout,并且仅在结束原因为 `completed` 时请求启动器以退出状态 0 有界关闭。[Headless 推理进度](../feature/2026-08-21-headless-reasoning-progress.zh.md)负责实时 stderr 投影;结束原因为 `error` 时,其持久化错误码与消息写入 stderr,驱动器的意外失败也写入 stderr 并以 1 退出。 @@ -34,7 +34,7 @@ Status: implemented | 围绕浏览器 RPC 构建纯 Host 一次性组合包 | 本地一次性入口没有客户端边界。 | | 使用进程内 Connection carrier 实现产品级协议覆盖 | 产品执行会仅为测试无关协议而依赖该协议。 | | 为 headless 单独提供提供方/模型配置 | 直接创建与 Web 创建会拥有彼此独立的默认值和持久化。 | -| 省略 Code Mode 与会话持久化 | 两项能力都属于一次性 Agent 执行,而不是 Web 呈现。 | +| 省略 PTC mode 与会话持久化 | 两项能力都属于一次性 Agent 执行,而不是 Web 呈现。 | | 规范化所有包含 Web 与 headless 组合包的元组 | 组合包列表是扩展面;只有精确的安装过程所属元组可以安全分类。 | ## 后果 diff --git a/.agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-25-rename-code-mode-to-ptc.i18n.yaml similarity index 57% rename from .agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.i18n.yaml rename to .agents/notes/implemented/architecture/2026-08-25-rename-code-mode-to-ptc.i18n.yaml index 6aae8b549f..4ffac0a8b3 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-25-rename-code-mode-to-ptc.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # 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 .agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.md -2026-08-07-code-mode-executor-collapse.md: 76265d5dd5f37f03c8e56366bf2791ddd2f7cb18 -2026-08-07-code-mode-executor-collapse.zh.md: f2d33993eb815d3f6dcd952557527c9aab5faefb +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-25-rename-code-mode-to-ptc.md +2026-08-25-rename-code-mode-to-ptc.md: 5637a17b8fcda3eee831e25cbad5662d1a93320b +2026-08-25-rename-code-mode-to-ptc.zh.md: 39cec2dfba133c388f80095f1f40934fb4f0325c diff --git a/.agents/notes/implemented/architecture/2026-08-25-rename-code-mode-to-ptc.md b/.agents/notes/implemented/architecture/2026-08-25-rename-code-mode-to-ptc.md new file mode 100644 index 0000000000..5637a17b8f --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-25-rename-code-mode-to-ptc.md @@ -0,0 +1,37 @@ +# Agent Note: Rename code-mode to ptc — the transport is named PTC, user-facing copy says PTC mode + +Status: implemented + +English | [中文](2026-08-25-rename-code-mode-to-ptc.zh.md) + +## Problem + +The tool-registry presentation mode that exposes tools through a generated SDK and the `run_code` transport shipped under the name Code Mode, while the client preset that selects it already shipped as "PTC mode" (locale `presetPtcName: 'PTC mode'`, zh `PTC 模式`). One feature had two names: config values, plugin and event names, files, and documentation said `code`/`code-mode`, and the user-facing name said "PTC mode". A pre-release rename must update every reference together — no compatibility aliases. + +## Decision + +The feature is renamed to PTC (programmatic tool calls). Code identifiers use `ptc` — the transport is not a sibling of plan-mode, so the identifier does not carry `-mode`. User-facing prose keeps "PTC mode" (EN) / "PTC 模式" (zh), matching the shipped preset name. + +Renamed in this PR: + +- config value `tools.mode: 'code'` → `'ptc'` (`ToolPresentationMode` and the zod unions in `dsh-tools` and `dsh-agent-tool-presentation`) +- preset directory `presets/code/` → `presets/ptc/` (preset id `ptc`) +- source and test files `code-mode.ts` → `ptc.ts` and friends; root demo `demo:code-mode` → `demo:ptc` (`scripts/demo-ptc.mjs`) +- the dispatch waterfall `tools/code-dispatch-log` → `tools/ptc-dispatch-log` and types `CodeDispatch*` → `PtcDispatch*` +- prompt rule `tools:code-only` → `tools:ptc-only` +- prose "Code Mode" → "PTC mode" / "PTC 模式" in docs, READMEs, and the eight implemented Agent Notes whose topic names the feature (those files were renamed in place) + +Deferred to the stacked persistence PR: the session-persistent vocabulary — the durable event types `tool/code-dispatch` / `tool/code-dispatch-start`, the logged plugin name `tools-code-mode`, and the sub-call id segment `:code:`. That PR is blocked until the `SESSION_FORMAT_VERSION` v0→v1 migration lands with it. + +Kept unchanged: `run_code` and its `code` parameter (they name the program payload, not the mode), `CodeSdkLanguage`, `CodeRunFailedError`, the `dsh-code-runtime*` package family, the third-party `codex-code-mode-host` binary name, and every frozen archived note. + +## Alternatives considered + +- **`ptc-mode` identifiers** — rejected: PTC is a tool-presentation transport, not a mode in the plan-mode sense, and the identifier should not claim that kinship. +- **Surface-only rename** — rejected: the pre-release stance updates every reference together. +- **Renaming `run_code` too** — rejected: the tool name describes running a program, not the mode, and is model-facing API surface. +- **Renaming the durable event vocabulary in this PR** — rejected: renaming `tool/code-dispatch*` without a format bump would make pre-rename session logs unreadable; that rename belongs to the stacked persistence PR that lands together with the v0→v1 migration. + +## Consequences + +Configs with `mode: code` and preset ids `code` are unsupported on this build. The session-persistent vocabulary still says `tool/code-dispatch*`, `tools-code-mode`, and `:code:`, so existing session logs load unchanged and no `SESSION_FORMAT_VERSION` bump is needed yet. The stacked persistence PR renames that vocabulary and is blocked until the v0→v1 migration lands with it (the version mechanics are the [session-log-version note](2026-08-10-session-log-version-mechanism.md)). Keyless snapshot refreshes carry this PR's vocabulary; the persistence PR refreshes the dispatch-bearing fixtures. The shipped decision this note renames is [the PTC foundation note](../feature/2026-06-15-ptc.md). diff --git a/.agents/notes/implemented/architecture/2026-08-25-rename-code-mode-to-ptc.zh.md b/.agents/notes/implemented/architecture/2026-08-25-rename-code-mode-to-ptc.zh.md new file mode 100644 index 0000000000..39cec2dfba --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-25-rename-code-mode-to-ptc.zh.md @@ -0,0 +1,37 @@ +# Agent Note: 将 code-mode 重命名为 ptc——传输层命名为 PTC,用户文案使用 PTC mode + +Status: implemented + +[English](2026-08-25-rename-code-mode-to-ptc.md) | 中文 + +## 问题 + +通过生成的 SDK 与 `run_code` 传输层向模型呈现工具的注册表模式,发布时用的名字是 Code Mode;而选择该模式的客户端预设早已以「PTC mode」发布(locale `presetPtcName: 'PTC mode'`,中文 `PTC 模式`)。同一功能有两套名字:配置值、插件与事件名、文件与文档写的是 `code`/`code-mode`,用户可见的名字却是「PTC mode」。预发布阶段的更名必须一次性更新所有引用——不加任何兼容别名。 + +## 决策 + +该功能更名为 PTC(programmatic tool calls,程序化工具调用)。代码标识符使用 `ptc`——该传输层并非 plan-mode 的同类模式,因此标识符不带 `-mode`。用户可见文案沿用「PTC mode」(英文)/「PTC 模式」(中文),与已发布的预设名一致。 + +本 PR 完成的重命名包括: + +- 配置值 `tools.mode: 'code'` → `'ptc'`(`ToolPresentationMode` 以及 `dsh-tools`、`dsh-agent-tool-presentation` 中的 zod union) +- 预设目录 `presets/code/` → `presets/ptc/`(预设 id 为 `ptc`) +- 源文件与测试文件 `code-mode.ts` → `ptc.ts` 等;根 demo `demo:code-mode` → `demo:ptc`(`scripts/demo-ptc.mjs`) +- 分发 waterfall `tools/code-dispatch-log` → `tools/ptc-dispatch-log`,类型 `CodeDispatch*` → `PtcDispatch*` +- 提示词规则 `tools:code-only` → `tools:ptc-only` +- 文档、README 与八个以该功能命名的 implemented Agent Note 中的文案 "Code Mode" → "PTC mode"/"PTC 模式"(这些 Note 文件一并就地改名) + +延后到堆叠的持久化 PR:会话持久词汇——持久事件类型 `tool/code-dispatch`/`tool/code-dispatch-start`、日志中的插件名 `tools-code-mode`、子调用 id 段 `:code:`。该 PR 被阻塞,直到 `SESSION_FORMAT_VERSION` v0→v1 迁移与其一同落地。 + +保持不变:`run_code` 及其 `code` 参数(它们描述程序载荷,而非模式)、`CodeSdkLanguage`、`CodeRunFailedError`、`dsh-code-runtime*` 包族、第三方二进制名 `codex-code-mode-host`,以及所有冻结的 archived Note。 + +## 备选方案 + +- **使用 `ptc-mode` 标识符**——否决:PTC 是工具呈现传输层,不是 plan-mode 意义上的模式,标识符不应宣示这种亲缘关系。 +- **仅重命名表面**——否决:预发布立场要求一次性更新所有引用。 +- **连 `run_code` 一起改名**——否决:该工具名描述的是运行程序,不是模式,而且是对模型可见的 API 表面。 +- **在本 PR 中一并重命名持久事件词汇**——否决:在没有格式版本提升的情况下重命名 `tool/code-dispatch*` 会让更名前的会话日志无法读取;该重命名属于与 v0→v1 迁移一同落地的堆叠持久化 PR。 + +## 后果 + +配置中写 `mode: code`、预设 id 为 `code`,在本构建上不再受支持。会话持久词汇仍为 `tool/code-dispatch*`、`tools-code-mode` 与 `:code:`,因此既有会话日志照常读取,无需 `SESSION_FORMAT_VERSION` 提升。堆叠的持久化 PR 负责重命名该词汇,并被阻塞到 v0→v1 迁移与其一同落地(版本机制见 [session log 版本机制 Note](2026-08-10-session-log-version-mechanism.zh.md))。无密钥的 snapshot refresh 携带本 PR 的词汇;持久化 PR 刷新包含分发的夹具。本 Note 所更名的已发布决策是 [PTC 基础 Note](../feature/2026-06-15-ptc.zh.md)。 diff --git a/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.i18n.yaml index c7d62f839f..478178ef58 100644 --- a/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.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 .agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.md -2026-08-25-sparse-first-party-prompt-section-orders.md: 2bf2e7441b449a6cfbd5b845f1d7e97b3fab09ae -2026-08-25-sparse-first-party-prompt-section-orders.zh.md: 624ed51f4d40848c72091097900ef05ec35fdd2e +2026-08-25-sparse-first-party-prompt-section-orders.md: 4b2568f18d104a77d00f91bb98f64047ecd85fa9 +2026-08-25-sparse-first-party-prompt-section-orders.zh.md: 4dfb0d0bd87ff5f245c28f3dbab6a48ba898f924 diff --git a/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.md b/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.md index 2bf2e7441b..4b2568f18d 100644 --- a/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.md +++ b/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.md @@ -22,7 +22,7 @@ The allocation preserves the established first-party sequence except for two del |---|---| | Product opening | `harness:identity` −1000, `harness:source` −900, `app:web-surface` −800, `deployment:persona` 0 | | Work modes | `plan:policy` 500, `team:policy` 600 | -| Invocation prelude | `tools:code-only` 800, `context:file-reference` 900 | +| Invocation prelude | `tools:ptc-only` 800, `context:file-reference` 900 | | Local tools | `tool:bash` 1000, `tool:pwsh` 1010, `tool:read` 1100, `tool:write` 1200, `tool:edit` 1300, `tool:glob` 1400, `tool:grep` 1500, `tool:jobs` 1600, `tool:pty` 1700 | | Higher-level tools | `tool:web_search` 2000, `tool:web_fetch` 2100, `tool:lsp` 2200, `tool:session-query` 2300, `tool:goal` 2400, `tool:cordis` 2500, `tool:workflow` 2600, `tool:ralph` 2700, continuable-subagent guidance 2800, `tool:report` 2900 | | Generated protocol | `tools:sdk` 5000 | diff --git a/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.zh.md b/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.zh.md index 624ed51f4d..4dfb0d0bd8 100644 --- a/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-25-sparse-first-party-prompt-section-orders.zh.md @@ -22,7 +22,7 @@ Status: implemented |---|---| | 产品开场 | `harness:identity` −1000、`harness:source` −900、`app:web-surface` −800、`deployment:persona` 0 | | 工作模式 | `plan:policy` 500、`team:policy` 600 | -| 调用前置说明 | `tools:code-only` 800、`context:file-reference` 900 | +| 调用前置说明 | `tools:ptc-only` 800、`context:file-reference` 900 | | 本地工具 | `tool:bash` 1000、`tool:pwsh` 1010、`tool:read` 1100、`tool:write` 1200、`tool:edit` 1300、`tool:glob` 1400、`tool:grep` 1500、`tool:jobs` 1600、`tool:pty` 1700 | | 高层工具 | `tool:web_search` 2000、`tool:web_fetch` 2100、`tool:lsp` 2200、`tool:session-query` 2300、`tool:goal` 2400、`tool:cordis` 2500、`tool:workflow` 2600、`tool:ralph` 2700、可继续运行的 subagent 指导 2800、`tool:report` 2900 | | 生成协议 | `tools:sdk` 5000 | diff --git a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.i18n.yaml similarity index 58% rename from .agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.i18n.yaml rename to .agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.i18n.yaml index 4d6d2f9225..5bd489c829 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # 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 .agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md -2026-07-26-code-dispatch-log-spill.md: a4b8deee86b1f6102e48e34079a93a74dfcfa288 -2026-07-26-code-dispatch-log-spill.zh.md: 5e53d761367ddce7747109e49c7facea9bcdae49 +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.md +2026-08-07-ptc-executor-collapse.md: 272748283a872662c3ee02276240303a5c7d54fa +2026-08-07-ptc-executor-collapse.zh.md: c65feb3aef6a471801cae1cdf3c49b7330b0951b diff --git a/.agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.md b/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.md similarity index 66% rename from .agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.md rename to .agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.md index 76265d5dd5..906e3fc734 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.md +++ b/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.md @@ -1,12 +1,12 @@ -# Agent Note: Code Mode collapses the executor, not just the wire +# Agent Note: PTC mode collapses the executor, not just the wire Status: implemented -English | [中文](2026-08-07-code-mode-executor-collapse.zh.md) +English | [中文](2026-08-07-ptc-executor-collapse.zh.md) ## Problem -`mode: 'code'` collapsed only the announcement surface, not the execution surface. `wireSchemas()` sent the model exactly one tool — `run_code` — but the executor resolved every call through `get()`, which returns the full visible map plus the reserved transport. A model that emitted a native tool name (`write`, `read`, `bash`, `subagent`, …) bypassed `run_code` entirely: the call traversed the normal pipeline and executed, even though no schema for it had ever been advertised. Providers do not intercept unadvertised tool names, so schema omission enforced nothing. +`mode: 'ptc'` collapsed only the announcement surface, not the execution surface. `wireSchemas()` sent the model exactly one tool — `run_code` — but the executor resolved every call through `get()`, which returns the full visible map plus the reserved transport. A model that emitted a native tool name (`write`, `read`, `bash`, `subagent`, …) bypassed `run_code` entirely: the call traversed the normal pipeline and executed, even though no schema for it had ever been advertised. Providers do not intercept unadvertised tool names, so schema omission enforced nothing. The package contract names this exact anti-pattern: schema omission is not enforcement when a direct caller can bypass it; denial must be tested through the executor. @@ -16,7 +16,7 @@ The package contract names this exact anti-pattern: schema omission is not enfor Four execution-path lookups — `executionMode`, `dispatchToolBody`, `postExecute`, `normalizeDispatchResult` — go through `resolveExecution`. `createExecution` applies the same collapse via the shared `collapses(name, nested)` predicate so it can distinguish a collapsed call from a genuinely unknown name before the policy pipeline. The public registry view (`get`) and SDK projection (`schemas`) keep their semantics: presentation, inspection, and binding enumeration still see the full visible set. The wire (`wireSchemas`) and the executor now agree. A collapsed call with non-JSON-serializable arguments reports the parameter `TypeError` (the invalid-args contract), not `UNKNOWN_TOOL` — the body still never runs and policy still does not. -The collapse is a security-relevant invariant, so acceptance is pinned through the executor: a model-direct native call under `code` returns `UNKNOWN_TOOL`, the same tool via an SDK sub-dispatch succeeds, and `native`/`both` direct calls plus `run_code` itself are unchanged. The base [Code Mode foundation](../feature/2026-06-15-code-mode.md) owns the transport design this note layers the execution boundary onto. +The collapse is a security-relevant invariant, so acceptance is pinned through the executor: a model-direct native call under `code` returns `UNKNOWN_TOOL`, the same tool via an SDK sub-dispatch succeeds, and `native`/`both` direct calls plus `run_code` itself are unchanged. The base [PTC mode foundation](../feature/2026-06-15-ptc.md) owns the transport design this note layers the execution boundary onto. ## Alternatives considered @@ -26,7 +26,7 @@ The view is consumed by presenters, `tool-cordis` inspection, and the SDK binder ### Filter at the agent-loop entry -The loop is not the only executor caller, and the distinction that matters (model-direct vs transport sub-dispatch) rides on the execution input, not at the loop boundary. An entry filter would also re-encode mode semantics the registry already owns. +The loop is not the only executor caller, and the distinction that matters (model-direct vs transport sub-dispatch) rides on the execution input, not at the loop boundary. An entry filter would also re-enPTC mode semantics the registry already owns. ### Reject via a shipped guard @@ -38,9 +38,9 @@ No provider guarantees interception of unadvertised names; the reported session ## Consequences -- `mode: 'code'` now enforces what it announces: a model-direct native call becomes `UNKNOWN_TOOL`, which the model can correct by routing through `run_code` (a pre-aborted call still resolves `ABORTED_BEFORE_DISPATCH`, per the cancellation contract). +- `mode: 'ptc'` now enforces what it announces: a model-direct native call becomes `UNKNOWN_TOOL`, which the model can correct by routing through `run_code` (a pre-aborted call still resolves `ABORTED_BEFORE_DISPATCH`, per the cancellation contract). - `both` and `native` behavior is unchanged; SDK sub-dispatches are unchanged (the `parent` token is the discriminator). - A collapsed call is rejected at `prepare`, BEFORE the extensible policy pipeline: pre-execute listeners, approval `ask`, and guards never observe it. `executionMode` also fails closed (`exclusive`), so scheduling has no observable difference. - Native-tool guidance sections (`tool:read`, `tool:write`, `tool:bash`, etc.) remain in the system prompt because they describe capabilities available through the generated SDK as well as native function calls, and several carry cross-tool routing policy (`read` over `bash cat`, `read` before `write` for the default fs-observation-policy, `subagent` over `workflow`) that no single tool description can hold. The executor collapse, not prompt filtering, prevents model-direct native calls. -- The prompt STATES the collapse, in the `tools:code-only` section ordered ahead of first-party per-tool guidance. Those sections name their tool without qualifying how it is reached, so a model that read only them emitted a native call, received `UNKNOWN_TOOL` for a tool the same prompt declared, and concluded the deployment was inconsistent rather than correcting itself. The denial carries the route for the same reason. The TypeScript SDK section repeats the distinction next to the generated declarations, labels them as program-only bindings, and states that only separately supplied tool schemas grant direct-call availability. Because the declaration list can otherwise be read as native tool availability, the section emits a complete `run_code` call around `tools.bash(...)` when the current `bash` parameter schema accepts the example arguments. `both` renders the rule empty: its native calls do execute, so stating it there would be false — which is why `both-mode-turn` no longer shares `code-mode-turn`'s expected prompt. +- The prompt STATES the collapse, in the `tools:ptc-only` section ordered ahead of first-party per-tool guidance. Those sections name their tool without qualifying how it is reached, so a model that read only them emitted a native call, received `UNKNOWN_TOOL` for a tool the same prompt declared, and concluded the deployment was inconsistent rather than correcting itself. The denial carries the route for the same reason. The TypeScript SDK section repeats the distinction next to the generated declarations, labels them as program-only bindings, and states that only separately supplied tool schemas grant direct-call availability. Because the declaration list can otherwise be read as native tool availability, the section emits a complete `run_code` call around `tools.bash(...)` when the current `bash` parameter schema accepts the example arguments. `both` renders the rule empty: its native calls do execute, so stating it there would be false — which is why `both-mode-turn` no longer shares `ptc-turn`'s expected prompt. - Any future composite transport that sets a `parent` token opts its sub-dispatches into the full table, matching the nested-call semantics the token already documents. diff --git a/.agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.zh.md b/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.zh.md similarity index 69% rename from .agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.zh.md rename to .agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.zh.md index f2d33993eb..13dfe7b303 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.zh.md @@ -1,12 +1,12 @@ -# Agent Note: Code Mode 塌缩执行器而非仅通告面 +# Agent Note: PTC mode 塌缩执行器而非仅通告面 Status: implemented -[English](2026-08-07-code-mode-executor-collapse.md) | 中文 +[English](2026-08-07-ptc-executor-collapse.md) | 中文 ## 问题 -`mode: 'code'` 只塌缩了通告面,没有塌缩执行面。`wireSchemas()` 只向模型发送一个工具——`run_code`——但执行器通过 `get()` 解析所有调用,而 `get()` 返回完整的可见工具表外加保留的传输工具。模型一旦发出原生工具名(`write`、`read`、`bash`、`subagent` 等),就能完全绕过 `run_code`:调用照常走完整流水线并执行成功,尽管它的 schema 从未被通告过。模型提供方不拦截未通告的工具名,因此不发 schema 等于没有约束。 +`mode: 'ptc'` 只塌缩了通告面,没有塌缩执行面。`wireSchemas()` 只向模型发送一个工具——`run_code`——但执行器通过 `get()` 解析所有调用,而 `get()` 返回完整的可见工具表外加保留的传输工具。模型一旦发出原生工具名(`write`、`read`、`bash`、`subagent` 等),就能完全绕过 `run_code`:调用照常走完整流水线并执行成功,尽管它的 schema 从未被通告过。模型提供方不拦截未通告的工具名,因此不发 schema 等于没有约束。 包契约点名了这个反模式:当直接调用方可以绕过时,schema 省略不算强制执行;拒绝必须经执行器验证。 @@ -16,7 +16,7 @@ Status: implemented 执行链路的四处查表——`executionMode`、`dispatchToolBody`、`postExecute`、`normalizeDispatchResult`——改走 `resolveExecution`。`createExecution` 通过共享的 `collapses(name, nested)` 谓词应用同一塌缩,以便在策略流水线之前区分被塌缩的调用与真正未知的名字。公共注册表视图(`get`)与 SDK 投影(`schemas`)语义不变:展示、检查与绑定枚举仍看到完整可见集合。通告(`wireSchemas`)与执行器现在一致。带非 JSON 可序列化参数的塌缩调用报告参数 `TypeError`(invalid-args 契约),而非 `UNKNOWN_TOOL`——函数体仍不会运行,策略也不会执行。 -塌缩是安全相关的不变量,因此验收经执行器钉死:`code` 模式下模型直呼原生工具返回 `UNKNOWN_TOOL`;同一工具经 SDK 子调用成功;`native`/`both` 模式直呼与 `run_code` 本身行为不变。本 note 把执行边界叠加在基础 [Code Mode 基础](../feature/2026-06-15-code-mode.zh.md) 之上,传输设计由后者拥有。 +塌缩是安全相关的不变量,因此验收经执行器钉死:`code` 模式下模型直呼原生工具返回 `UNKNOWN_TOOL`;同一工具经 SDK 子调用成功;`native`/`both` 模式直呼与 `run_code` 本身行为不变。本 note 把执行边界叠加在基础 [PTC mode 基础](../feature/2026-06-15-ptc.zh.md) 之上,传输设计由后者拥有。 ## 备选方案 @@ -38,9 +38,9 @@ guard 是可选的插件扩展;安全不变量不能依赖部署恰好组装 ## 后果 -- `mode: 'code'` 现在兑现其通告:模型直呼原生工具变为 `UNKNOWN_TOOL`,模型可以通过改走 `run_code` 自行纠正(已中止的调用仍按取消契约解析为 `ABORTED_BEFORE_DISPATCH`)。 +- `mode: 'ptc'` 现在兑现其通告:模型直呼原生工具变为 `UNKNOWN_TOOL`,模型可以通过改走 `run_code` 自行纠正(已中止的调用仍按取消契约解析为 `ABORTED_BEFORE_DISPATCH`)。 - `both` 与 `native` 行为不变;SDK 子调用不变(判别信号是 `parent` token)。 - 被塌缩的调用在 `prepare` 阶段即被拒绝——在可扩展策略流水线之前:pre-execute 监听器、approval `ask` 与 guard 永远不会观察到它。`executionMode` 同样 fail-closed(`exclusive`),调度无可观察差异。 - 原生工具指引段(`tool:read`、`tool:write`、`tool:bash` 等)保留在系统提示词中,因为它们同时描述了通过生成 SDK 及原生函数调用可用的能力,其中若干段还承载着任何单个工具描述都装不下的跨工具路由策略(`read` 优先于 `bash cat`、默认 fs-observation-policy 要求先 `read` 再 `write`、一两个委派用 `subagent` 而非 `workflow`)。防止模型直呼原生工具的是执行器塌缩,而非提示词过滤。 -- 提示词会**声明**这条塌缩,位于 first-party 逐工具指导之前的 `tools:code-only` 段。那些段只写出工具名而不限定其可达方式,因此只读到它们的模型会发出原生调用,为一个同一份提示词刚刚声明过的工具收到 `UNKNOWN_TOOL`,进而判定部署不一致,而不是自行纠正。拒绝信息给出正确路径也是同一原因。TypeScript SDK 段在生成声明旁再次区分两者,将其标为只能在程序内使用的绑定,并说明只有单独提供的工具 schema 才赋予直呼权限。声明列表可能被误读为原生工具可用性,因此当当前 `bash` 参数 schema 接受示例参数时,该段会给出以 `run_code` 包住 `tools.bash(...)` 的完整调用。`both` 下该规则渲染为空:它的原生调用确实会执行,在那里声明就是假话——这也是 `both-mode-turn` 不再与 `code-mode-turn` 共用期望提示词的原因。 +- 提示词会**声明**这条塌缩,位于 first-party 逐工具指导之前的 `tools:ptc-only` 段。那些段只写出工具名而不限定其可达方式,因此只读到它们的模型会发出原生调用,为一个同一份提示词刚刚声明过的工具收到 `UNKNOWN_TOOL`,进而判定部署不一致,而不是自行纠正。拒绝信息给出正确路径也是同一原因。TypeScript SDK 段在生成声明旁再次区分两者,将其标为只能在程序内使用的绑定,并说明只有单独提供的工具 schema 才赋予直呼权限。声明列表可能被误读为原生工具可用性,因此当当前 `bash` 参数 schema 接受示例参数时,该段会给出以 `run_code` 包住 `tools.bash(...)` 的完整调用。`both` 下该规则渲染为空:它的原生调用确实会执行,在那里声明就是假话——这也是 `both-mode-turn` 不再与 `ptc-turn` 共用期望提示词的原因。 - 未来任何设置 `parent` token 的组合传输,其子调用自动走全表,与该 token 已有的嵌套调用语义一致。 diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml b/.agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml similarity index 63% rename from .agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml rename to .agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml index 13987435b3..03bad269c4 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # 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 .agents/notes/implemented/feature/2026-06-15-code-mode.md -2026-06-15-code-mode.md: dbf77557c9f1aa59deed443b74ca3ef83137d773 -2026-06-15-code-mode.zh.md: 091623adcf1b543b104a20482f7a0106a6437f1d +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-15-ptc.md +2026-06-15-ptc.md: 144ef987cf5a7c589237401a1adfec3cb6825504 +2026-06-15-ptc.zh.md: 24a6f813ec3eaa551c7e2ac8fcdd9762104de15b diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.md b/.agents/notes/implemented/feature/2026-06-15-ptc.md similarity index 75% rename from .agents/notes/implemented/feature/2026-06-15-code-mode.md rename to .agents/notes/implemented/feature/2026-06-15-ptc.md index dbf77557c9..144ef987cf 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.md +++ b/.agents/notes/implemented/feature/2026-06-15-ptc.md @@ -1,8 +1,8 @@ -# Agent Note: Code Mode — the model writes TypeScript against the tool registry +# Agent Note: PTC mode — the model writes TypeScript against the tool registry Status: implemented -English | [中文](2026-06-15-code-mode.zh.md) +English | [中文](2026-06-15-ptc.zh.md) ## Problem @@ -10,7 +10,7 @@ In the registry's native presentation, the agent loop advertises every visible c For multi-step tool work this is token-heavy and serial. The model cannot compose tools — loop over a result set, branch on an intermediate value, fan out, post-process — without a full model round-trip per call, and each round-trip drags the entire intermediate result back into context whether the model needs it or not. -Cloudflare's [Code Mode](https://blog.cloudflare.com/code-mode/) proposes an alternative grounded in a simple observation: LLMs are better at writing code than at emitting tool calls, because they have seen millions of lines of real code and comparatively few contrived tool-calling traces. Instead of one tool call per step, the model writes a TypeScript program against a generated API over the tools, the program executes in a sandboxed runtime, and the model curates what comes back — only what it prints or returns — instead of every intermediate result. +Cloudflare's [PTC mode](https://blog.cloudflare.com/ptc/) proposes an alternative grounded in a simple observation: LLMs are better at writing code than at emitting tool calls, because they have seen millions of lines of real code and comparatively few contrived tool-calling traces. Instead of one tool call per step, the model writes a TypeScript program against a generated API over the tools, the program executes in a sandboxed runtime, and the model curates what comes back — only what it prints or returns — instead of every intermediate result. Tool presentation belongs to the registry that owns tool visibility: implementing a second presentation as an after-the-fact waterfall transform would make correctness depend on listener order and fight [reconstructable requests](../architecture/2026-07-05-reconstructable-requests.md). The execution substrate is also part of the foundation rather than a placeholder: Node `worker_threads` provides a separate isolate, an empty environment, heap caps, and termination of a hot synchronous loop, while fitting the harness's existing trust model (§Trust posture). @@ -18,43 +18,43 @@ Tool presentation belongs to the registry that owns tool visibility: implementin Three decisions, each elaborated in its own section below: -1. **Code Mode is a first-class presentation mode of `ToolRuntime`** (`dsh-tools`), selected by a validated `mode` config: `'native'` (the default, contributing the visible capability schemas), `'code'` (the registry contributes only its reserved `run_code` transport plus a generated SDK `.d.ts` in the system prompt), or `'both'` (native schemas and the transport + SDK). The registry constructs its canonical contribution at the source; the cooperative prompt-assembly result remains authoritative, and the logged request header records exactly that returned presentation. +1. **PTC mode is a first-class presentation mode of `ToolRuntime`** (`dsh-tools`), selected by a validated `mode` config: `'native'` (the default, contributing the visible capability schemas), `'code'` (the registry contributes only its reserved `run_code` transport plus a generated SDK `.d.ts` in the system prompt), or `'both'` (native schemas and the transport + SDK). The registry constructs its canonical contribution at the source; the cooperative prompt-assembly result remains authoritative, and the logged request header records exactly that returned presentation. 2. **Code execution is a capability seam** — `packages/code-runtime/` contains the Service Definition package `@deepseek-ai/dsh-code-runtime`, which owns `ctx.codeRuntime` ([capability seams](../architecture/2026-06-13-capability-seams.md); Consumer = `dsh-tools`, with core-consumes-a-seam precedent in `agent-loop` → `dsh-llm`). The runtime knows nothing about tools: it is handed a program and named async bindings, runs the program, and reports `{ value, logs, error? }`. Language and substrate are backend properties, so a future Python or container backend is another Service Provider package, not a redesign. 3. **The shipped implementation is `@deepseek-ai/dsh-code-runtime-worker-thread`**: one fresh Node worker thread per run, executing the model's TypeScript after type-strip, with bindings bridged over the message port, an empty environment, configurable heap/output/time caps, and hard termination. Its trust posture is bash-equivalent by design — no unsafe-acknowledgement flags — because the harness already ships `dsh-bash-local`, which executes arbitrary model-written shell commands with strictly *more* ambient authority. -This note owns Code Mode's presentation, composition, isolation, and settlement foundation. The later [typed tool-return Agent Note](2026-07-20-code-mode-typed-tool-returns.md) owns the generated output map, canonical binding values, `ToolCallError`, and the lossless outer-output boundary. +This note owns PTC mode's presentation, composition, isolation, and settlement foundation. The later [typed tool-return Agent Note](2026-07-20-ptc-typed-tool-returns.md) owns the generated output map, canonical binding values, `ToolCallError`, and the lossless outer-output boundary. ### The registry owns the mode -`ToolRuntime` gains a schemastery-validated config (`static Config`), its first: `mode: 'native' | 'code' | 'both'`, default `'native'`. A deployment flips it from `cordis.yml` (`tools: { mode: code }`) — no code edit, per the no-hardcoded-tunables convention. +`ToolRuntime` gains a schemastery-validated config (`static Config`), its first: `mode: 'native' | 'code' | 'both'`, default `'native'`. A deployment flips it from `cordis.yml` (`tools: { mode: ptc }`) — no code edit, per the no-hardcoded-tunables convention. **Wire tool list.** The registry contributes visible capabilities in `'native'`, only `run_code` in `'code'`, and both in `'both'`. The final `PromptAssembly.tools` list is logged in the request header. `run_code` is a reserved presentation transport outside registration and restriction layers; direct prompt providers and the assembly waterfall remain responsible for their own contributions. -**Interaction with `toolOrder`:** a configured `systemPrompt.toolOrder` naming native capabilities rejects every assembly under `mode: 'code'`, because those names are outside that mode's wire-validation universe. This is correct behavior, not a bug: a deployment using Code Mode updates its order config or drops it. +**Interaction with `toolOrder`:** a configured `systemPrompt.toolOrder` naming native capabilities rejects every assembly under `mode: 'ptc'`, because those names are outside that mode's wire-validation universe. This is correct behavior, not a bug: a deployment using PTC mode updates its order config or drops it. -**SDK prompt section.** In `'code'` and `'both'`, the lazy `tools:sdk` section in the tool-guidance order band renders the loaded runtime's language declarations plus fixed usage instructions for the scope's visible capabilities (TypeScript by default; the [language-dispatch note](2026-07-31-code-mode-language-dispatch.md) added Python and the `ctx.codeRuntime.language` renderer table). It shares lookup and execution visibility, excludes `run_code`, and sorts tools lexicographically for byte-stable output. +**SDK prompt section.** In `'code'` and `'both'`, the lazy `tools:sdk` section in the tool-guidance order band renders the loaded runtime's language declarations plus fixed usage instructions for the scope's visible capabilities (TypeScript by default; the [language-dispatch note](2026-07-31-ptc-language-dispatch.md) added Python and the `ctx.codeRuntime.language` renderer table). It shares lookup and execution visibility, excludes `run_code`, and sorts tools lexicographically for byte-stable output. -**Assembly ownership.** `run_code` and `tools:sdk` enter the trusted `system-prompt/assemble` waterfall as normal assembly inputs. A scoped `tools:sdk` section may shadow the global default before dispatch, and a listener may remove or replace either contribution. The waterfall's returned assembly is final, so whoever changes these inputs owns preserving a viable Code Mode protocol when the deployment expects Code Mode to remain usable; no restoration pass overrides deliberate composition. +**Assembly ownership.** `run_code` and `tools:sdk` enter the trusted `system-prompt/assemble` waterfall as normal assembly inputs. A scoped `tools:sdk` section may shadow the global default before dispatch, and a listener may remove or replace either contribution. The waterfall's returned assembly is final, so whoever changes these inputs owns preserving a viable PTC mode protocol when the deployment expects PTC mode to remain usable; no restoration pass overrides deliberate composition. **Codegen.** `jsonSchemaToTs()` maps the `defineTool` JSON-Schema subset to TypeScript, carries schema descriptions into JSDoc, and degrades unsupported constructs to `unknown`. The SDK exposes tools as quoted object keys, supporting arbitrary names without aliases or collisions. Typing is advisory because the runtime strips types before execution. ### The run_code tool and the dispatch bridge -Under `'code'` and `'both'` the registry owns `run_code` as a reserved presentation transport with two required parameters, `{ code: string; description: string }` (the description labels the call in UIs, the bash precedent). It is represented by a normal `ToolDefinition` for dispatch but stays outside the filterable capability layers, so restrictions cannot accidentally remove Code Mode's only entry point. Calls traverse the complete tool pipeline — `tools/pre-execute` → monotonic guards → `tools/execute` around dispatch → `tools/post-execute` → optional definition-owned `finalizeContent` → immutable `tools/result` notification — exactly like native calls; a permission plugin can inspect the program text before it runs, and final-result observers see the normalized outer outcome. Its `execute(args, exec)`: +Under `'code'` and `'both'` the registry owns `run_code` as a reserved presentation transport with two required parameters, `{ code: string; description: string }` (the description labels the call in UIs, the bash precedent). It is represented by a normal `ToolDefinition` for dispatch but stays outside the filterable capability layers, so restrictions cannot accidentally remove PTC mode's only entry point. Calls traverse the complete tool pipeline — `tools/pre-execute` → monotonic guards → `tools/execute` around dispatch → `tools/post-execute` → optional definition-owned `finalizeContent` → immutable `tools/result` notification — exactly like native calls; a permission plugin can inspect the program text before it runs, and final-result observers see the normalized outer outcome. Its `execute(args, exec)`: -1. **Build bindings.** One run-scoped signal follows outer cancellation and is aborted whenever the run settles. Each visible tool binding snapshots lossless-JSON arguments, enters the native-contract dispatch pool (the [live-parallel note](2026-07-26-code-mode-live-parallel-dispatch.md) owns the scheduling design), executes with a deterministic call id and the outer token as `parent`, defers returned contexts through the outer execution, and logs the `tool/code-dispatch-start`/`tool/code-dispatch` pair, the settle side carrying the full rendered result content. Success returns the tool's final canonical JSON value; failure becomes the program-visible `ToolCallError`. Every sub-call retains its own immutable execution identity and traverses the full tool pipeline. +1. **Build bindings.** One run-scoped signal follows outer cancellation and is aborted whenever the run settles. Each visible tool binding snapshots lossless-JSON arguments, enters the native-contract dispatch pool (the [live-parallel note](2026-07-26-ptc-live-parallel-dispatch.md) owns the scheduling design), executes with a deterministic call id and the outer token as `parent`, defers returned contexts through the outer execution, and logs the `tool/ptc-dispatch-start`/`tool/ptc-dispatch` pair, the settle side carrying the full rendered result content. Success returns the tool's final canonical JSON value; failure becomes the program-visible `ToolCallError`. Every sub-call retains its own immutable execution identity and traverses the full tool pipeline. 2. **Runs the program**: `ctx.codeRuntime.run({ program: args.code, bindings: [{ global: 'tools', functions }], signal: runController.signal })`. The runtime receives the run-scoped signal, not only the caller's outer signal, so any way the outer run settles also aborts work inside the runtime. 3. **Settle after quiescence.** When the runtime settles, the bridge aborts outstanding work and drains the dispatch queue before returning. Success returns captured logs and the completion value as canonical output; the registry renders that value into durable `tool/result.content`, which the result card reads directly. A runtime failure becomes `CodeRunFailedError`; backend rejection uses the registry's normal error boundary. Both produce structured error results, and no sub-call can append after `run_code` settles. **Sub-call contexts are deferred through the parent.** Injecting inside `run_code` would break parent call/result adjacency, so `ToolRunContext.deferContext()` collects every sub-result `additionalContexts` entry in dispatch order. The registry carries that array even when the program later throws, and the loop appends each entry only after the outer result and every sibling result in the step. An outer post-execute block discards tool-deferred entries and exposes only contexts explicitly attached by the blocking decision. -**Concurrency is bounded, not serialized.** Each run owns a dispatch queue that starts calls strictly in submission order and classifies each one through `registry.executionMode`, the same fail-closed `isConcurrencySafe` contract the native loop uses. Consecutive parallel-classified calls overlap up to `maxParallelSubCalls` (default 10; `1` restores serial dispatch); an exclusive call drains the pool and runs alone. Settlement abandons queued calls that have not started. This note shipped the serialized placeholder; the [live-parallel Agent Note](2026-07-26-code-mode-live-parallel-dispatch.md) owns the scheduler that replaced it. +**Concurrency is bounded, not serialized.** Each run owns a dispatch queue that starts calls strictly in submission order and classifies each one through `registry.executionMode`, the same fail-closed `isConcurrencySafe` contract the native loop uses. Consecutive parallel-classified calls overlap up to `maxParallelSubCalls` (default 10; `1` restores serial dispatch); an exclusive call drains the pool and runs alone. Settlement abandons queued calls that have not started. This note shipped the serialized placeholder; the [live-parallel Agent Note](2026-07-26-ptc-live-parallel-dispatch.md) owns the scheduler that replaced it. **Presentation.** `run_code`'s render intent is decided here per the [render-intent Agent Note](../architecture/2026-07-02-tool-render-intent-union.md): `presentCall` creates a `generic` card with `kind: 'execute'`, the program text as its title, and the same program text as `rawInput`; `run_code` intentionally declares no `presentResult`, so the TUI and host/client runtime (Web) complete that card through their generic raw-content fallback using the final durable `tool/result.content`, including captured logs plus the returned value, failure, or post-policy spill preview. This is not a `terminal` card: that card's semantics are "a shell command in a working directory", which a program is not. See the [result-card completeness note](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md). -### Observability: `tool/code-dispatch` +### Observability: `tool/ptc-dispatch` -Each sub-dispatch appends a log-only `tool/code-dispatch-start` event at pool entry and a `tool/code-dispatch` settle event containing parent and child call ids, tool identity, normalized arguments, and the complete rendered `content`/`isError` outcome. It remains outside model history but available to persistence and UIs. Appends occur inside the open `run_code` turn. Direct executions without an agent still run but cannot log the event. +Each sub-dispatch appends a log-only `tool/ptc-dispatch-start` event at pool entry and a `tool/ptc-dispatch` settle event containing parent and child call ids, tool identity, normalized arguments, and the complete rendered `content`/`isError` outcome. It remains outside model history but available to persistence and UIs. Appends occur inside the open `run_code` turn. Direct executions without an agent still run but cannot log the event. ### The code-runtime seam @@ -64,9 +64,9 @@ Each sub-dispatch appends a log-only `tool/code-dispatch-start` event at pool en - `CodeBindingNamespace = { global: string; functions: Record Promise>; errorClass?: { name: string; memberNameProperty: string } }` — the runtime exposes each namespace as a global object of async functions inside the program; the optional descriptor asks the runtime to inject a real program-visible rejection class without teaching the seam consumer-specific names. `CodeJsonValue` is this dependency-light seam's structural lossless-JSON type, so binding arguments and resolutions cross the implementation's serialization boundary whole. - `CodeRunResult = { value?: CodeJsonValue; logs: string[]; error?: CodeRunFailure }` — program execution outcomes resolve as the `error` field. `run()` may reject only for caller/seam misuse (for example a duplicate binding namespace); consumers still contain a non-conforming backend rejection at their own error boundary. - `CodeRunFailure = { kind: 'exception' | 'timeout' | 'abort' | 'worker-exit' | 'invalid-output' | 'output-limit'; message: string }` — orthogonal outcomes reported independently per [defensive patterns](../../../../docs/defensive-patterns.md); a timed-out run is not an exception, an abort is not a timeout, a lossy completion is not an overflow, and a substrate exit is none of them. -- Two readonly backend descriptors, informational not gating: `language` (what the program must be written in — `'typescript'` for the first backend; a Python backend says `'python'` and pairs with its own SDK generator on the presentation side) and `isolation` (`'worker-thread'` for the shipped backend; `'process'`, `'container'`, … for future ones). `dsh-tools` accepts any `language` with a registered SDK renderer and `run_code` flavor (TypeScript and Python ship; see the [language-dispatch note](2026-07-31-code-mode-language-dispatch.md)) and fails the assembly loudly otherwise, the same misconfiguration idiom as `toolOrder` violations (as when `mode` is non-native with no `ctx.codeRuntime` loaded at all). +- Two readonly backend descriptors, informational not gating: `language` (what the program must be written in — `'typescript'` for the first backend; a Python backend says `'python'` and pairs with its own SDK generator on the presentation side) and `isolation` (`'worker-thread'` for the shipped backend; `'process'`, `'container'`, … for future ones). `dsh-tools` accepts any `language` with a registered SDK renderer and `run_code` flavor (TypeScript and Python ship; see the [language-dispatch note](2026-07-31-ptc-language-dispatch.md)) and fails the assembly loudly otherwise, the same misconfiguration idiom as `toolOrder` violations (as when `mode` is non-native with no `ctx.codeRuntime` loaded at all). -Requests contain every runtime input; implementations own validated timeout and cap defaults. The registry looks up the optional runtime only when Code Mode is assembled, so native mode does not depend on one. Missing or language-incompatible runtimes fail loudly. Alternate substrates or languages can replace the implementation behind the same seam, paired with the appropriate SDK generator. +Requests contain every runtime input; implementations own validated timeout and cap defaults. The registry looks up the optional runtime only when PTC mode is assembled, so native mode does not depend on one. Missing or language-incompatible runtimes fail loudly. Alternate substrates or languages can replace the implementation behind the same seam, paired with the appropriate SDK generator. ### The worker-thread runtime @@ -74,18 +74,18 @@ Requests contain every runtime input; implementations own validated timeout and 1. **Type-strip host-side** with Node's built-in `stripTypeScriptTypes` (`node:module`; present across the repo's whole engines range, `^22.19.0 || >=24.0.0`, and position-preserving, so runtime error line numbers match the model's source). Strip-only mode rejects non-erasable syntax (`enum`, namespaces) — that rejection returns as `error.kind: 'exception'` with Node's message, the SDK instructions say "erasable TypeScript only", and the model self-corrects like any other program error. A syntax-level failure never spawns a worker. 2. **Spawn one fresh `Worker` per run** from the package's own bootstrap module: `env: {}` (truly empty — stronger than the scrubbed-env rule for spawned commands), `resourceLimits` from config, `stdout`/`stderr` captured into `logs` rather than inherited. No pooling and no cross-run state: a program's world dies with its worker, which keeps runs reconstructable from the log alone and makes state bleed unrepresentable. -3. **Execute** in the bootstrap: the stripped program becomes the body of an `AsyncFunction` whose parameters are the binding globals, any consumer-declared rejection classes, and a capturing `console` shim, so top-level `await` and `return` work. Code Mode declares `ToolCallError` with member property `toolName`; the runtime materializes that real constructor without hardcoding tools. A lossless JSON completion crosses exactly; `undefined` remains absence, a lossy value is `invalid-output`, and an oversized outer result is `output-limit` rather than an inspected-string substitute. +3. **Execute** in the bootstrap: the stripped program becomes the body of an `AsyncFunction` whose parameters are the binding globals, any consumer-declared rejection classes, and a capturing `console` shim, so top-level `await` and `return` work. PTC mode declares `ToolCallError` with member property `toolName`; the runtime materializes that real constructor without hardcoding tools. A lossless JSON completion crosses exactly; `undefined` remains absence, a lossy value is `invalid-output`, and an oversized outer result is `output-limit` rather than an inspected-string substitute. 4. **Bridge bindings over the message port**: each binding function in the worker posts `{ id, global, name, args }` and awaits the reply; the host validates the name against the request's bindings, invokes, and replies `{ id, ok, value }` or `{ id, ok: false, message }` (a host-side binding rejection becomes a program-side rejection). The worker-side namespace objects are built null-prototype via `defineProperty`, so a binding named `__proto__`, `constructor`, or `toString` is an ordinary own property, not a prototype collision. Unknown names, duplicate ids, and post-settlement messages are rejected or ignored — the port protocol assumes a hostile peer, because the peer runs model code. 5. **Enforce independent budgets.** `computeMs` meters worker busy time, allowing slow awaited tools without excusing a hot loop. `maxWallMs` bounds total elapsed time, including unresolved waits. `maxOutputBytes` bounds only the combined serialized outer logs, completion, or diagnostic; intermediate binding values have no byte cap. Expiry, cancellation, and completion terminate the worker, and heap exits or outer overflow are explicit failures. 6. **Dispose to quiescence**: the service's own disposal terminates in-flight workers and *awaits* their exits before resolving, per [defensive patterns](../../../../docs/defensive-patterns.md). ### Trust posture -The worker runtime provides containment, not a security boundary: model code can reach Node APIs and has authority comparable to the bash tool. `worker.terminate()` stops the thread but not OS processes it spawned. Code Mode uses the same `tools/pre-execute` policy gate as bash and adds an empty environment, heap limits, a separate isolate, and hard termination of the program itself. Deployments that need a hard multi-tenant boundary need a container-class backend for both code and bash; the runtime's isolation descriptor lets them distinguish that backend. +The worker runtime provides containment, not a security boundary: model code can reach Node APIs and has authority comparable to the bash tool. `worker.terminate()` stops the thread but not OS processes it spawned. PTC mode uses the same `tools/pre-execute` policy gate as bash and adds an empty environment, heap limits, a separate isolate, and hard termination of the program itself. Deployments that need a hard multi-tenant boundary need a container-class backend for both code and bash; the runtime's isolation descriptor lets them distinguish that backend. ### What the model sees -The SDK instructs the model to write an async body in the loaded runtime's language (an erasable-TypeScript body by default; a Python `async` body under a Python runtime — see the [language-dispatch note](2026-07-31-code-mode-language-dispatch.md)), call tools through `await tools.name(args)`, catch rejected tool calls when needed, and return or log only the output that should re-enter context. Both flavors state the same contract in their own primitive: independent read-only calls MAY overlap under `Promise.all` (TypeScript) or `asyncio.gather` (Python), mutating calls run alone in submission order, and dependent work sequences with `await`. The declaration prefix can be as large as native schemas, especially in `'both'`, but remains stable for provider caching. +The SDK instructs the model to write an async body in the loaded runtime's language (an erasable-TypeScript body by default; a Python `async` body under a Python runtime — see the [language-dispatch note](2026-07-31-ptc-language-dispatch.md)), call tools through `await tools.name(args)`, catch rejected tool calls when needed, and return or log only the output that should re-enter context. Both flavors state the same contract in their own primitive: independent read-only calls MAY overlap under `Promise.all` (TypeScript) or `asyncio.gather` (Python), mutating calls run alone in submission order, and dependent work sequences with `await`. The declaration prefix can be as large as native schemas, especially in `'both'`, but remains stable for provider caching. The transport's own `description` and both SDK instruction flavors open by naming `code` and `description` as the call's two required arguments. Prose that describes the call as passing a program leaves the second argument discoverable only through the parameter schema, and a model that emits `{code}` alone loses the whole written program to an `INVALID_ARGS` rejection. @@ -97,8 +97,8 @@ Deployments switching to `'code'` must update any native-only `toolOrder`. Assem - **Worker runtime:** Real-worker tests cover typed binding values and failures, every lossless JSON completion root, invalid and over-limit output, exact combined ledger boundaries, compute and wall budgets, hostile binding traffic, empty environment, and disposal to quiescence. A built-package test runs the worker entry under plain Node. - **Registry integration:** Tests cover code generation, all presentation modes, reserved-name and restriction rules, scoped visibility, authoritative assembly rewrites, `toolOrder`, runtime compatibility failures, full-pipeline sub-dispatch, parent-token correlation, serialization, cancellation and queue drain, JSON normalization, error propagation, log events, ordered context deferral across successful and failed programs, outer-block suppression, and HMR cleanup. -- **With-key e2e:** A real model composes two bash calls in one program; another discovers nested workspace instructions through a Code Mode fs dispatch. The tests verify collapsed request headers, correlated dispatch events, resulting files, deferred context, and model behavior. -- **Snapshot:** The `code-mode-turn`, `both-mode-turn`, and `code-mode-workspace-context` fixtures pin SDK text, header tool lists, dispatch events, deferred context, and result cards. +- **With-key e2e:** A real model composes two bash calls in one program; another discovers nested workspace instructions through a PTC mode fs dispatch. The tests verify collapsed request headers, correlated dispatch events, resulting files, deferred context, and model behavior. +- **Snapshot:** The `ptc-turn`, `both-mode-turn`, and `ptc-workspace-context` fixtures pin SDK text, header tool lists, dispatch events, deferred context, and result cards. ## Alternatives considered @@ -106,9 +106,9 @@ Deployments switching to `'code'` must update any native-only `toolOrder`. Assem **`node:vm` as the reference runtime, with hardening deferred.** Rejected: `node:vm` is not isolation (prototype-chain escapes reach the host realm) and cannot interrupt a hot loop. A worker thread provides a separate isolate, empty environment, `resourceLimits`, and reliable `terminate()` at bash-equivalent trust, so the reference and production implementation are one package without an unsafe-acknowledgement ceremony. -**Result elision / summarization over native tool-calling.** Addresses only the context-bloat half of the problem: trimming old `tool-result`s is cheap to add as a logged surface replacement under reconstructable requests, but still pays one model round-trip per call and cannot express loops, branches, or joins. Complementary, not competing; it can layer under Code Mode for residual native calls. +**Result elision / summarization over native tool-calling.** Addresses only the context-bloat half of the problem: trimming old `tool-result`s is cheap to add as a logged surface replacement under reconstructable requests, but still pays one model round-trip per call and cannot express loops, branches, or joins. Complementary, not competing; it can layer under PTC mode for residual native calls. -**Parallel native dispatch in the loop.** The other answer to round-trip cost at decision time; it was blocked on concurrency-safety metadata and offers no composition either way — it parallelizes calls the model already decided on in one step. Code Mode's queue decision kept the two compatible, and both later shipped: the metadata as `isConcurrencySafe` (the [parallel tool-call note](2026-07-10-parallel-tool-call-execution.md)), and native rolling-pool dispatch plus per-tool binding parallelism on the same classifier. +**Parallel native dispatch in the loop.** The other answer to round-trip cost at decision time; it was blocked on concurrency-safety metadata and offers no composition either way — it parallelizes calls the model already decided on in one step. PTC mode's queue decision kept the two compatible, and both later shipped: the metadata as `isConcurrencySafe` (the [parallel tool-call note](2026-07-10-parallel-tool-call-execution.md)), and native rolling-pool dispatch plus per-tool binding parallelism on the same classifier. **Always-exclusive (Cloudflare-faithful, no mode).** Rejected for this SDK's primary consumer: a coding agent's bread-and-butter single calls (`bash`, `read`, `edit`) are already ideal as native calls, and forcing every edit through a program taxes the common case. The mode config keeps the faithful form (`'code'`) one line away without imposing it. @@ -126,10 +126,10 @@ Deployments switching to `'code'` must update any native-only `toolOrder`. Assem **Prompt cost of the SDK, especially under `'both'`.** The `.d.ts` can rival the native schemas it complements; `'both'` carries two representations. Prefix stability + provider caching amortize per-session cost; the mode is per-deployment; the Agent Note makes no unconditional-savings claim. Measured guidance (when to prefer which mode) is explicitly post-ship learning. -**Registry scope growth.** `dsh-tools` absorbs codegen, a tool, a bridge, and an event. Package modules separate these responsibilities (`ts-types.ts` and `code-mode.ts` beside `schema.ts`, `json-schema.ts`, and `presentation.ts`), while `ctx.codeRuntime` owns all code-runtime-specific implementation. +**Registry scope growth.** `dsh-tools` absorbs codegen, a tool, a bridge, and an event. Package modules separate these responsibilities (`ts-types.ts` and `ptc.ts` beside `schema.ts`, `json-schema.ts`, and `presentation.ts`), while `ctx.codeRuntime` owns all code-runtime-specific implementation. **Large lossless JSON values can exhaust memory.** Tool bindings snapshot lossless JSON before dispatch and return canonical JSON resolutions whole. The runtime validates both sides of the worker port and applies no per-binding byte cap; structured-clone cost and process or worker memory are the practical bounds. The combined outer-output ledger for logs, the completion value, and a failure diagnostic is the only byte-capped boundary. -**Sub-dispatch overlap is bounded by tool safety claims, not by the caller.** A program's `Promise.all` or `asyncio.gather` buys wall-clock parallelism only across calls the tool itself classifies concurrency-safe; a run of exclusive calls still costs its round-trips in sequence, and models may over-expect. Both flavors' SDK instructions state the real contract. This note shipped the serialized placeholder that made the risk absolute; the [live-parallel Agent Note](2026-07-26-code-mode-live-parallel-dispatch.md) owns the scheduler and its overlap cap. +**Sub-dispatch overlap is bounded by tool safety claims, not by the caller.** A program's `Promise.all` or `asyncio.gather` buys wall-clock parallelism only across calls the tool itself classifies concurrency-safe; a run of exclusive calls still costs its round-trips in sequence, and models may over-expect. Both flavors' SDK instructions state the real contract. This note shipped the serialized placeholder that made the risk absolute; the [live-parallel Agent Note](2026-07-26-ptc-live-parallel-dispatch.md) owns the scheduler and its overlap cap. **Budget metering reads the event loop, not a flag.** Busy-time polling (`eventLoopUtilization()`) is coarser than an exact CPU meter — a budget expires up to one poll interval late — and its correctness claim ("a pending dispatch cannot pause it") is load-bearing against a hostile program. Both sides are unit-tested (hot loop with a pending decoy dispatch dies at `computeMs`; idle-on-slow-binding survives to `maxWallMs`), and the poll interval is an internal constant, not config — nothing a deployment could mis-tune into a bypass. `maxWallMs` is config, and it reaches `setTimeout`, which clamps a delay above `MAX_TIMER_DELAY_MS` (2^31-1 ms) to 1 ms; a positivity check alone therefore accepts a 25-day ceiling that expires on the first tick and times out every run. The worker runtime range-checks the field at load for that reason. `computeMs` needs no upper bound because it is compared against measured utilization instead of being handed to a timer. diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md b/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md similarity index 75% rename from .agents/notes/implemented/feature/2026-06-15-code-mode.zh.md rename to .agents/notes/implemented/feature/2026-06-15-ptc.zh.md index 091623adcf..24a6f813ec 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md +++ b/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md @@ -1,8 +1,8 @@ -# Agent Note: Code Mode——模型针对工具注册表编写 TypeScript +# Agent Note: PTC mode——模型针对工具注册表编写 TypeScript Status: implemented -[English](2026-06-15-code-mode.md) | 中文 +[English](2026-06-15-ptc.md) | 中文 ## 问题 @@ -10,7 +10,7 @@ Status: implemented 对于多步工具操作,这种方式 token 开销大且串行。模型无法组合工具——遍历结果集、根据中间值分支、扇出、后处理——每次调用都需要一次完整的模型往返,而每次往返都会把完整的中间结果拖回上下文,不管模型是否需要。 -Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一种替代方案,基于一个简单的观察:LLM(大语言模型)编写代码的能力优于发出工具调用,因为它们见过数百万行真实代码,而人为构造的工具调用 trace 相对很少。模型不再每步发出一次工具调用,而是针对工具生成的 API 编写一段 TypeScript 程序,程序在沙箱运行时中执行,模型只筛选返回的内容——仅限它 print 或 return 的部分——而非所有中间结果。 +Cloudflare 的 [PTC mode](https://blog.cloudflare.com/ptc/) 提出了一种替代方案,基于一个简单的观察:LLM(大语言模型)编写代码的能力优于发出工具调用,因为它们见过数百万行真实代码,而人为构造的工具调用 trace 相对很少。模型不再每步发出一次工具调用,而是针对工具生成的 API 编写一段 TypeScript 程序,程序在沙箱运行时中执行,模型只筛选返回的内容——仅限它 print 或 return 的部分——而非所有中间结果。 工具呈现属于掌管工具可见性的注册表:如果把第二种呈现方式实现为事后的 waterfall(瀑布式事件)变换,正确性将依赖监听器顺序,并与[可重建请求](../architecture/2026-07-05-reconstructable-requests.zh.md)冲突。执行基底同样属于基础设施而非占位实现:Node `worker_threads` 提供独立 isolate、空环境、堆上限以及对热同步循环的终止能力,同时契合 harness 既有的信任模型(§信任姿态)。 @@ -18,43 +18,43 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一 三项决策,各自在下方独立小节中展开: -1. **Code Mode 是 `ToolRuntime`(`dsh-tools`)的一等呈现模式**,通过经校验的 `mode` 配置选择:`'native'`(默认,贡献可见能力 schema)、`'code'`(注册表仅贡献其保留的 `run_code` 传输通道加一份生成的 SDK `.d.ts` 到系统提示词中)或 `'both'`(原生 schema 加传输通道 + SDK)。注册表在源头构建其规范贡献;协作式提示词组装的结果仍具权威性,记录在日志中的请求头精确反映该返回的呈现。 +1. **PTC mode 是 `ToolRuntime`(`dsh-tools`)的一等呈现模式**,通过经校验的 `mode` 配置选择:`'native'`(默认,贡献可见能力 schema)、`'code'`(注册表仅贡献其保留的 `run_code` 传输通道加一份生成的 SDK `.d.ts` 到系统提示词中)或 `'both'`(原生 schema 加传输通道 + SDK)。注册表在源头构建其规范贡献;协作式提示词组装的结果仍具权威性,记录在日志中的请求头精确反映该返回的呈现。 2. **代码执行是一个能力 seam**——`packages/code-runtime/` 包含 Service Definition 包 `@deepseek-ai/dsh-code-runtime`,拥有 `ctx.codeRuntime`([能力 seam](../architecture/2026-06-13-capability-seams.zh.md);消费方 = `dsh-tools`,core 消费 seam 的先例见 `agent-loop` → `dsh-llm`)。运行时对工具一无所知:它接收一段程序和命名的异步绑定,执行程序,报告 `{ value, logs, error? }`。语言和基底是后端属性,因此未来的 Python 或容器后端只是另一个 Service Provider 包,而非重新设计。 3. **交付的实现是 `@deepseek-ai/dsh-code-runtime-worker-thread`**:每次运行 spawn 一个全新的 Node worker 线程,对模型的 TypeScript 进行 type-strip 后执行,绑定通过消息端口桥接,环境为空,堆/输出/时间上限可配置,并支持硬终止。其信任姿态在设计上等同于 bash——无需 unsafe-acknowledgement flag——因为 harness 已经交付了 `dsh-bash-local`,后者以严格*更高*的环境权限执行模型编写的任意 shell 命令。 -本说明负责定义 Code Mode 的呈现、组合、隔离与结算基础。后续的[类型化工具返回值 Agent Note](2026-07-20-code-mode-typed-tool-returns.zh.md)负责定义生成的输出映射、规范绑定值、`ToolCallError` 和无损外层输出边界。 +本说明负责定义 PTC mode 的呈现、组合、隔离与结算基础。后续的[类型化工具返回值 Agent Note](2026-07-20-ptc-typed-tool-returns.zh.md)负责定义生成的输出映射、规范绑定值、`ToolCallError` 和无损外层输出边界。 ### 注册表拥有模式 -`ToolRuntime` 获得一个经 schemastery 校验的配置(`static Config`),这是它的第一个配置:`mode: 'native' | 'code' | 'both'`,默认 `'native'`。部署通过 `cordis.yml` 翻转模式(`tools: { mode: code }`),无需改代码,遵循 no-hardcoded-tunables 约定。 +`ToolRuntime` 获得一个经 schemastery 校验的配置(`static Config`),这是它的第一个配置:`mode: 'native' | 'code' | 'both'`,默认 `'native'`。部署通过 `cordis.yml` 翻转模式(`tools: { mode: ptc }`),无需改代码,遵循 no-hardcoded-tunables 约定。 **协议工具列表。** 注册表在 `'native'` 下贡献可见能力,在 `'code'` 下仅贡献 `run_code`,在 `'both'` 下两者都贡献。最终的 `PromptAssembly.tools` 列表记录在请求头中。`run_code` 是一个保留的呈现传输通道,位于注册和限制层之外;直接提示词提供方和组装 waterfall 仍各自负责自己的贡献。 -**与 `toolOrder` 的交互:** 如果配置的 `systemPrompt.toolOrder` 引用了原生能力名称,在 `mode: 'code'` 下会拒绝所有组装,因为那些名称不在该模式的协议校验范围内。这是正确行为而非 bug:使用 Code Mode 的部署需要更新其 order 配置或移除它。 +**与 `toolOrder` 的交互:** 如果配置的 `systemPrompt.toolOrder` 引用了原生能力名称,在 `mode: 'ptc'` 下会拒绝所有组装,因为那些名称不在该模式的协议校验范围内。这是正确行为而非 bug:使用 PTC mode 的部署需要更新其 order 配置或移除它。 -**SDK 提示词段。** 在 `'code'` 和 `'both'` 下,tool-guidance order band 中的惰性 `tools:sdk` 段为当前 scope 的可见能力渲染所加载运行时语言的声明加固定的使用说明(默认 TypeScript;[语言分发 note](2026-07-31-code-mode-language-dispatch.zh.md) 加入了 Python 与按 `ctx.codeRuntime.language` 选择的渲染器表)。它共享查找和执行可见性,排除 `run_code`,并按字典序排列工具以获得字节稳定的输出。 +**SDK 提示词段。** 在 `'code'` 和 `'both'` 下,tool-guidance order band 中的惰性 `tools:sdk` 段为当前 scope 的可见能力渲染所加载运行时语言的声明加固定的使用说明(默认 TypeScript;[语言分发 note](2026-07-31-ptc-language-dispatch.zh.md) 加入了 Python 与按 `ctx.codeRuntime.language` 选择的渲染器表)。它共享查找和执行可见性,排除 `run_code`,并按字典序排列工具以获得字节稳定的输出。 -**组装所有权。** `run_code` 和 `tools:sdk` 作为正常的组装输入进入受信任的 `system-prompt/assemble` waterfall。一个 scoped 的 `tools:sdk` 段可以在分发前遮蔽全局默认值,监听器也可以移除或替换任一贡献。waterfall 返回的组装结果是最终的,因此修改这些输入的人有责任在部署期望 Code Mode 可用时保持协议面的完整性;没有恢复 pass 会覆盖有意的组合。 +**组装所有权。** `run_code` 和 `tools:sdk` 作为正常的组装输入进入受信任的 `system-prompt/assemble` waterfall。一个 scoped 的 `tools:sdk` 段可以在分发前遮蔽全局默认值,监听器也可以移除或替换任一贡献。waterfall 返回的组装结果是最终的,因此修改这些输入的人有责任在部署期望 PTC mode 可用时保持协议面的完整性;没有恢复 pass 会覆盖有意的组合。 **代码生成。** `jsonSchemaToTs()` 将 `defineTool` 的 JSON Schema 子集映射为 TypeScript,将 schema 描述带入 JSDoc,不支持的构造降级为 `unknown`。SDK 将工具暴露为带引号的对象键,支持任意名称而无需别名或冲突处理。类型是建议性的,因为运行时在执行前会剥离类型。 ### run_code 工具与分发桥 -在 `'code'` 和 `'both'` 下,注册表拥有 `run_code` 作为保留的呈现传输通道,带两个必需参数 `{ code: string; description: string }`(description 为 UI 标注该调用,沿用 bash 的先例)。它由一个正常的 `ToolDefinition` 表示以供分发,但位于可过滤的能力层之外,因此限制规则不会意外移除 Code Mode 的唯一入口。调用遍历完整的工具流水线——`tools/pre-execute` → 单调性守卫 → `tools/execute` 包裹分发 → `tools/post-execute` → 由定义拥有的可选 `finalizeContent` → 不可变的 `tools/result` 通知——与原生调用完全一致;权限插件可以在程序运行前检查程序文本,最终结果观察者看到的是规范化的外层结果。其 `execute(args, exec)`: +在 `'code'` 和 `'both'` 下,注册表拥有 `run_code` 作为保留的呈现传输通道,带两个必需参数 `{ code: string; description: string }`(description 为 UI 标注该调用,沿用 bash 的先例)。它由一个正常的 `ToolDefinition` 表示以供分发,但位于可过滤的能力层之外,因此限制规则不会意外移除 PTC mode 的唯一入口。调用遍历完整的工具流水线——`tools/pre-execute` → 单调性守卫 → `tools/execute` 包裹分发 → `tools/post-execute` → 由定义拥有的可选 `finalizeContent` → 不可变的 `tools/result` 通知——与原生调用完全一致;权限插件可以在程序运行前检查程序文本,最终结果观察者看到的是规范化的外层结果。其 `execute(args, exec)`: -1. **构建绑定。** 一个 run 级别的 signal 跟随外层取消,并在 run 结算时被 abort。每个可见工具绑定都会对无损 JSON 参数创建快照,进入原生约定的分发池(调度设计由[实时并行 Agent Note](2026-07-26-code-mode-live-parallel-dispatch.zh.md) 负责),以确定性的 call id 和外层 token 作为 `parent` 执行,通过外层 execution 延后返回的上下文,并记录 `tool/code-dispatch-start`/`tool/code-dispatch` 事件对,其中结算侧携带完整渲染后的结果内容。成功时返回工具最终的规范 JSON 值;失败则变为程序可见的 `ToolCallError`。每个子调用保留自己不可变的执行标识,并遍历完整的工具流水线。 +1. **构建绑定。** 一个 run 级别的 signal 跟随外层取消,并在 run 结算时被 abort。每个可见工具绑定都会对无损 JSON 参数创建快照,进入原生约定的分发池(调度设计由[实时并行 Agent Note](2026-07-26-ptc-live-parallel-dispatch.zh.md) 负责),以确定性的 call id 和外层 token 作为 `parent` 执行,通过外层 execution 延后返回的上下文,并记录 `tool/ptc-dispatch-start`/`tool/ptc-dispatch` 事件对,其中结算侧携带完整渲染后的结果内容。成功时返回工具最终的规范 JSON 值;失败则变为程序可见的 `ToolCallError`。每个子调用保留自己不可变的执行标识,并遍历完整的工具流水线。 2. **运行程序**:`ctx.codeRuntime.run({ program: args.code, bindings: [{ global: 'tools', functions }], signal: runController.signal })`。运行时接收的是 run 级别的 signal 而非仅调用方的外层 signal,因此外层 run 以任何方式结算都会同时 abort 运行时内部的工作。 3. **完全停稳后结算。** 运行时结算后,桥 abort 未完成的工作并排空分发队列后再返回。成功时返回捕获的日志和完成值,将其作为规范输出;注册表再把该值渲染为持久化的 `tool/result.content`,供结果卡片直接读取。运行时失败变为 `CodeRunFailedError`;后端拒绝使用注册表的正常错误边界。两者都产生结构化的错误结果,且 `run_code` 结算后不允许子调用追加。 **子调用上下文通过父调用延后。** 在 `run_code` 内部注入会破坏父调用/结果的相邻性,因此 `ToolRunContext.deferContext()` 按分发顺序收集每个子结果的 `additionalContexts` 条目。即使程序后来抛出异常,注册表仍携带该数组;循环只在外层结果与步骤中所有兄弟结果之后追加每个条目。外层 post-execute 阻止会丢弃工具延后的条目,只暴露阻止 decision 显式附加的上下文。 -**并发是有界的,而非被序列化。** 每次 run 拥有一个分发队列,严格按提交顺序启动调用,并通过 `registry.executionMode` 对每个调用分类——与原生循环所用的 fail-closed `isConcurrencySafe` 约定相同。连续的 parallel 类调用最多重叠 `maxParallelSubCalls` 个(默认 10;设为 `1` 恢复串行分发);exclusive 类调用会排空池并单独运行。结算时放弃尚未开始的排队调用。本 note 交付的是被序列化的占位实现;取代它的调度器由[实时并行 Agent Note](2026-07-26-code-mode-live-parallel-dispatch.zh.md) 负责。 +**并发是有界的,而非被序列化。** 每次 run 拥有一个分发队列,严格按提交顺序启动调用,并通过 `registry.executionMode` 对每个调用分类——与原生循环所用的 fail-closed `isConcurrencySafe` 约定相同。连续的 parallel 类调用最多重叠 `maxParallelSubCalls` 个(默认 10;设为 `1` 恢复串行分发);exclusive 类调用会排空池并单独运行。结算时放弃尚未开始的排队调用。本 note 交付的是被序列化的占位实现;取代它的调度器由[实时并行 Agent Note](2026-07-26-ptc-live-parallel-dispatch.zh.md) 负责。 **呈现。** `run_code` 的 render intent 按[呈现意图 Agent Note](../architecture/2026-07-02-tool-render-intent-union.zh.md)在此决定:`presentCall` 创建一个 `generic` 卡片,`kind: 'execute'`,以程序文本作为标题,并将同一程序文本作为 `rawInput`;`run_code` 有意不声明 `presentResult`,因此 TUI 和宿主/客户端运行时(Web)会通过通用原始内容回退机制,使用最终持久化的 `tool/result.content` 补全该卡片,其中包括捕获的日志,以及返回值、失败信息或 post-policy spill 预览。这不是 `terminal` 卡片:该卡片的语义是「工作目录中的 shell 命令」,程序不是。参见[结果卡片完整性说明](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md)。 -### 可观测性:`tool/code-dispatch` +### 可观测性:`tool/ptc-dispatch` -每次子分发在进入分发池时追加一个仅日志的 `tool/code-dispatch-start` 事件,并以一个 `tool/code-dispatch` 结算事件收尾,后者包含父子 call id、工具标识、规范化参数以及完整渲染后的 `content`/`isError` 结果。它不进入模型历史,但可供持久化和 UI 使用。追加发生在开放的 `run_code` 轮次内。没有 agent 的直接执行仍然运行,但无法记录该事件。 +每次子分发在进入分发池时追加一个仅日志的 `tool/ptc-dispatch-start` 事件,并以一个 `tool/ptc-dispatch` 结算事件收尾,后者包含父子 call id、工具标识、规范化参数以及完整渲染后的 `content`/`isError` 结果。它不进入模型历史,但可供持久化和 UI 使用。追加发生在开放的 `run_code` 轮次内。没有 agent 的直接执行仍然运行,但无法记录该事件。 ### code-runtime seam @@ -64,9 +64,9 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一 - `CodeBindingNamespace = { global: string; functions: Record Promise>; errorClass?: { name: string; memberNameProperty: string } }`——运行时将每个命名空间作为程序内部的全局异步函数对象暴露;可选描述符要求运行时注入真正的、程序可见的 reject 类,而无需让 seam 获知消费方专用名称。`CodeJsonValue` 是这个低依赖 seam 的结构化无损 JSON 类型,因此绑定参数与返回值可以完整跨越实现的序列化边界。 - `CodeRunResult = { value?: CodeJsonValue; logs: string[]; error?: CodeRunFailure }`——程序执行失败时,执行 promise 仍会 fulfill,并通过 `error` 字段返回失败结果。只有调用方/seam 误用(例如重复的绑定命名空间)时,`run()` 才会 reject;消费方仍在自己的错误边界处理不合规后端的拒绝。 - `CodeRunFailure = { kind: 'exception' | 'timeout' | 'abort' | 'worker-exit' | 'invalid-output' | 'output-limit'; message: string }`——按[防御性模式](../../../../docs/defensive-patterns.zh.md)独立报告的正交结果;超时的 run 不是异常,abort 不是超时,有损完成值不是溢出,基底退出也与上述情况相互独立。 -- 两个只读的后端描述符,仅供信息参考而非门禁判定:`language`(程序必须使用的语言——首个后端为 `'typescript'`;Python 后端声明 `'python'`,并在呈现侧配对自己的 SDK 生成器)和 `isolation`(交付的后端为 `'worker-thread'`;未来可为 `'process'`、`'container'` 等)。`dsh-tools` 接受任何注册了 SDK 渲染器与 `run_code` flavor 的 `language`(TypeScript 与 Python 已交付;见[语言分发 note](2026-07-31-code-mode-language-dispatch.zh.md)),否则组装会显式失败,与 `toolOrder` 违规时的配置错误惯用法相同(如 `mode` 为非 native 但根本没有加载 `ctx.codeRuntime`)。 +- 两个只读的后端描述符,仅供信息参考而非门禁判定:`language`(程序必须使用的语言——首个后端为 `'typescript'`;Python 后端声明 `'python'`,并在呈现侧配对自己的 SDK 生成器)和 `isolation`(交付的后端为 `'worker-thread'`;未来可为 `'process'`、`'container'` 等)。`dsh-tools` 接受任何注册了 SDK 渲染器与 `run_code` flavor 的 `language`(TypeScript 与 Python 已交付;见[语言分发 note](2026-07-31-ptc-language-dispatch.zh.md)),否则组装会显式失败,与 `toolOrder` 违规时的配置错误惯用法相同(如 `mode` 为非 native 但根本没有加载 `ctx.codeRuntime`)。 -请求包含所有运行时输入;实现方拥有经校验的超时和上限默认值。注册表仅在组装 Code Mode 时查找可选的运行时,因此 native 模式不依赖它。缺失或语言不兼容的运行时会显式失败。替代基底或语言可以在同一 seam 背后替换实现,配对相应的 SDK 生成器。 +请求包含所有运行时输入;实现方拥有经校验的超时和上限默认值。注册表仅在组装 PTC mode 时查找可选的运行时,因此 native 模式不依赖它。缺失或语言不兼容的运行时会显式失败。替代基底或语言可以在同一 seam 背后替换实现,配对相应的 SDK 生成器。 ### worker-thread 运行时 @@ -74,18 +74,18 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一 1. **宿主侧 type-strip**,使用 Node 内置的 `stripTypeScriptTypes`(`node:module`;在本仓库的整个引擎范围 `^22.19.0 || >=24.0.0` 内可用,且会保留源码位置,因此运行时错误行号与模型源码一致)。仅剥离模式拒绝不可擦除的语法(`enum`、namespaces)——该拒绝以 `error.kind: 'exception'` 加 Node 的消息返回,SDK 说明写明「仅限可擦除 TypeScript」,模型像处理其他程序错误一样自我修正。语法级失败不会 spawn worker。 2. **每次 run spawn 一个全新 `Worker`**,来自包自身的 bootstrap 模块:`env: {}`(真正为空——比 spawn 命令的 scrubbed-env 规则更严格),`resourceLimits` 来自配置,`stdout`/`stderr` 捕获到 `logs` 而非继承。不做池化,不跨 run 保留状态:程序的世界随 worker 消亡,这使得 run 仅从日志即可重建,状态泄漏不可表达。 -3. **在 bootstrap 中执行**:剥离后的程序成为一个 `AsyncFunction` 的函数体,其参数是绑定全局变量、消费方声明的 reject 类和一个捕获式 `console` shim,因此顶层 `await` 和 `return` 可用。Code Mode 声明 `ToolCallError`,成员属性为 `toolName`;运行时无需硬编码工具即可实体化真正的构造函数。无损 JSON 完成值会精确跨越边界;`undefined` 仍表示缺席,有损值产生 `invalid-output`,过大的外层结果产生 `output-limit`,而不会退化为检查格式化后的字符串替代品。 +3. **在 bootstrap 中执行**:剥离后的程序成为一个 `AsyncFunction` 的函数体,其参数是绑定全局变量、消费方声明的 reject 类和一个捕获式 `console` shim,因此顶层 `await` 和 `return` 可用。PTC mode 声明 `ToolCallError`,成员属性为 `toolName`;运行时无需硬编码工具即可实体化真正的构造函数。无损 JSON 完成值会精确跨越边界;`undefined` 仍表示缺席,有损值产生 `invalid-output`,过大的外层结果产生 `output-limit`,而不会退化为检查格式化后的字符串替代品。 4. **通过消息端口桥接绑定**:worker 中的每个绑定函数发送 `{ id, global, name, args }` 并等待回复;宿主根据请求的绑定校验名称、调用、并回复 `{ id, ok, value }` 或 `{ id, ok: false, message }`(宿主侧绑定拒绝变为程序侧 rejection)。worker 侧的命名空间对象通过 `defineProperty` 构建为 null-prototype,因此名为 `__proto__`、`constructor` 或 `toString` 的绑定是普通自有属性,而非原型链碰撞。未知名称、重复 id 和结算后消息被拒绝或忽略——端口协议假设对端是恶意的,因为对端运行的是模型代码。 5. **强制独立预算。** `computeMs` 计量 worker 忙碌时间,允许慢速的 awaited 工具而不放过热循环。`maxWallMs` 约束总经过时间,包括未解析的等待。`maxOutputBytes` 只约束序列化后的外层日志、完成值或诊断的组合;中间绑定值没有字节数上限。到期、取消和完成都终止 worker,堆退出或外层溢出会作为显式失败报告。 6. **dispose(资源释放)至完全停稳**:服务自身的 dispose 终止进行中的 worker 并*等待*其退出后再 resolve,遵循[防御性模式](../../../../docs/defensive-patterns.zh.md)。 ### 信任姿态 -worker 运行时只能约束程序的运行,而不构成安全边界:模型代码可以访问 Node API,权限与 bash 工具相当。`worker.terminate()` 停止线程但不停止它 spawn 的 OS 进程。Code Mode 使用与 bash 相同的 `tools/pre-execute` 策略门禁,并额外提供空环境、堆限制、独立 isolate 和对程序本身的硬终止。需要硬多租户边界的部署需要为代码和 bash 都使用容器级后端;运行时的 isolation 描述符让它们能区分该后端。 +worker 运行时只能约束程序的运行,而不构成安全边界:模型代码可以访问 Node API,权限与 bash 工具相当。`worker.terminate()` 停止线程但不停止它 spawn 的 OS 进程。PTC mode 使用与 bash 相同的 `tools/pre-execute` 策略门禁,并额外提供空环境、堆限制、独立 isolate 和对程序本身的硬终止。需要硬多租户边界的部署需要为代码和 bash 都使用容器级后端;运行时的 isolation 描述符让它们能区分该后端。 ### 模型看到的内容 -SDK 指示模型编写一个所加载运行时语言的异步函数体(默认可擦除 TypeScript;Python 运行时下为 Python `async` 函数体——见[语言分发 note](2026-07-31-code-mode-language-dispatch.zh.md)),通过 `await tools.name(args)` 调用工具,在需要时捕获被拒绝的工具调用,并仅 return 或 log 应重新进入上下文的输出。两种 flavor 用各自的原语陈述同一约定:相互独立的只读调用可以(MAY)在 `Promise.all`(TypeScript)或 `asyncio.gather`(Python)下重叠,有副作用的调用按提交顺序单独运行,有依赖的工作用 `await` 排序。声明前缀可能与原生 schema 一样大,尤其在 `'both'` 下,但对提供方缓存保持稳定。 +SDK 指示模型编写一个所加载运行时语言的异步函数体(默认可擦除 TypeScript;Python 运行时下为 Python `async` 函数体——见[语言分发 note](2026-07-31-ptc-language-dispatch.zh.md)),通过 `await tools.name(args)` 调用工具,在需要时捕获被拒绝的工具调用,并仅 return 或 log 应重新进入上下文的输出。两种 flavor 用各自的原语陈述同一约定:相互独立的只读调用可以(MAY)在 `Promise.all`(TypeScript)或 `asyncio.gather`(Python)下重叠,有副作用的调用按提交顺序单独运行,有依赖的工作用 `await` 排序。声明前缀可能与原生 schema 一样大,尤其在 `'both'` 下,但对提供方缓存保持稳定。 传输自身的 `description` 与两种 flavor 的 SDK 说明都以点名 `code` 和 `description` 这两个必填参数开头。把该调用描述成「传入一个程序」的散文会让第二个参数只能从参数 schema 中发现,而只发出 `{code}` 的模型会因 `INVALID_ARGS` 被拒,连同已写好的整个程序一起丢失。 @@ -97,8 +97,8 @@ SDK 指示模型编写一个所加载运行时语言的异步函数体(默认 - **Worker 运行时:** 真实 worker 测试覆盖类型化的绑定值与失败、每一种无损 JSON 根类型的完成值、无效和超限输出、精确的组合账本边界、compute 和 wall 预算、恶意绑定流量、空环境以及 dispose 至完全停稳。一个构建后包测试在纯 Node 下运行 worker 入口。 - **注册表集成:** 测试覆盖代码生成、所有呈现模式、保留名称和限制规则、scoped 可见性、权威组装重写、`toolOrder`、运行时兼容性失败、完整流水线子分发、parent-token 关联、序列化、取消和队列排空、JSON 规范化、错误传播、日志事件、成功与失败程序中的有序上下文延后、外层阻止抑制以及 HMR(热模块替换)清理。 -- **带密钥 e2e:** 真实模型在一个程序中组合两次 bash 调用;另一个模型通过 Code Mode fs 分发发现嵌套的工作区指令。测试验证折叠的请求头、关联的分发事件、结果文件、延后上下文和模型行为。 -- **快照:** `code-mode-turn`、`both-mode-turn` 和 `code-mode-workspace-context` fixture(测试前置数据)固定 SDK 文本、请求头工具列表、分发事件、延后上下文和结果卡片。 +- **带密钥 e2e:** 真实模型在一个程序中组合两次 bash 调用;另一个模型通过 PTC mode fs 分发发现嵌套的工作区指令。测试验证折叠的请求头、关联的分发事件、结果文件、延后上下文和模型行为。 +- **快照:** `ptc-turn`、`both-mode-turn` 和 `ptc-workspace-context` fixture(测试前置数据)固定 SDK 文本、请求头工具列表、分发事件、延后上下文和结果卡片。 ## 曾考虑的替代方案 @@ -106,9 +106,9 @@ SDK 指示模型编写一个所加载运行时语言的异步函数体(默认 **`node:vm` 作为参考运行时,加固推迟。** 否决:`node:vm` 不是隔离(原型链逃逸可达宿主 realm)且无法中断热循环。worker 线程提供独立 isolate、空环境、`resourceLimits` 和可靠的 `terminate()`,信任等级等同于 bash,因此参考实现和生产实现是同一个包,无需 unsafe-acknowledgement 仪式。 -**在原生工具调用上做结果省略/摘要。** 仅解决问题中上下文膨胀这一半:裁剪旧 `tool-result` 作为可重建请求下的日志化表面替换成本低,但仍需每次调用一次模型往返,且无法表达循环、分支或汇合。互补而非竞争;它可以在 Code Mode 下为残余的原生调用分层。 +**在原生工具调用上做结果省略/摘要。** 仅解决问题中上下文膨胀这一半:裁剪旧 `tool-result` 作为可重建请求下的日志化表面替换成本低,但仍需每次调用一次模型往返,且无法表达循环、分支或汇合。互补而非竞争;它可以在 PTC mode 下为残余的原生调用分层。 -**循环中的并行原生分发。** 决策当时对往返成本的另一个答案;它被并发安全元数据阻塞,且无论如何都不提供组合能力——它并行化的是模型在一步中已经决定的调用。Code Mode 的队列决策保持了两者兼容,两者后来都已交付:元数据即 `isConcurrencySafe`(见[并行工具调用 note](2026-07-10-parallel-tool-call-execution.zh.md)),原生 rolling-pool 分发加每工具绑定并行化则基于同一个分类器。 +**循环中的并行原生分发。** 决策当时对往返成本的另一个答案;它被并发安全元数据阻塞,且无论如何都不提供组合能力——它并行化的是模型在一步中已经决定的调用。PTC mode 的队列决策保持了两者兼容,两者后来都已交付:元数据即 `isConcurrencySafe`(见[并行工具调用 note](2026-07-10-parallel-tool-call-execution.zh.md)),原生 rolling-pool 分发加每工具绑定并行化则基于同一个分类器。 **始终排他(忠于 Cloudflare,无模式)。** 否决,因为本 SDK 的主要消费方是编码 agent:其日常的单次调用(`bash`、`read`、`edit`)作为原生调用已经是最优的,强制每次编辑都通过程序会给常见场景增加负担。mode 配置让忠实形式(`'code'`)只需一行配置即可启用,而不强加于人。 @@ -126,10 +126,10 @@ SDK 指示模型编写一个所加载运行时语言的异步函数体(默认 **SDK 的提示词成本,尤其在 `'both'` 下。** `.d.ts` 可能与它补充的原生 schema 体量相当;`'both'` 携带两种表示。前缀稳定性 + 提供方缓存摊销了每会话成本;mode 按部署配置;本 Agent Note 不做无条件节省的声明。何时优先使用哪种模式的量化指导明确属于上线后学习。 -**注册表 scope 增长。** `dsh-tools` 吸收了代码生成、一个工具、一个桥和一个事件。包内模块把这些职责分开(`ts-types.ts`、`code-mode.ts` 与 `schema.ts`、`json-schema.ts`、`presentation.ts` 并列),所有 code-runtime 专用实现都由 `ctx.codeRuntime` 提供。 +**注册表 scope 增长。** `dsh-tools` 吸收了代码生成、一个工具、一个桥和一个事件。包内模块把这些职责分开(`ts-types.ts`、`ptc.ts` 与 `schema.ts`、`json-schema.ts`、`presentation.ts` 并列),所有 code-runtime 专用实现都由 `ctx.codeRuntime` 提供。 **大型无损 JSON 值可能耗尽内存。** 工具绑定会在分发前对无损 JSON 创建快照,并完整返回规范 JSON 返回值。运行时会校验 worker 端口两侧,但不对单次绑定设置字节数上限;结构化克隆成本以及进程或 worker 内存构成实际边界。只有包含日志、完成值和失败诊断的组合外层输出账本受字节数上限约束。 -**子分发的重叠由工具自身的安全声明限定,而非由调用方决定。** 程序里的 `Promise.all` 或 `asyncio.gather` 只在工具自己分类为并发安全的调用之间换来挂钟并行性;一串 exclusive 调用仍要按顺序付出各自的往返开销,模型可能过度期望。两种 flavor 的 SDK 说明都陈述了真实约定。本 note 交付的是使该风险绝对化的序列化占位实现;调度器及其重叠上限由[实时并行 Agent Note](2026-07-26-code-mode-live-parallel-dispatch.zh.md) 负责。 +**子分发的重叠由工具自身的安全声明限定,而非由调用方决定。** 程序里的 `Promise.all` 或 `asyncio.gather` 只在工具自己分类为并发安全的调用之间换来挂钟并行性;一串 exclusive 调用仍要按顺序付出各自的往返开销,模型可能过度期望。两种 flavor 的 SDK 说明都陈述了真实约定。本 note 交付的是使该风险绝对化的序列化占位实现;调度器及其重叠上限由[实时并行 Agent Note](2026-07-26-ptc-live-parallel-dispatch.zh.md) 负责。 **预算计量读取事件循环,而非 flag。** 忙碌时间轮询(`eventLoopUtilization()`)比精确 CPU 计量更粗糙——预算到期最多延迟一个轮询间隔——且其正确性声明(「pending 的分发不能暂停它」)是抵御恶意程序的关键。两种情况均有单元测试(带 pending 诱饵分发的热循环会在耗尽 `computeMs` 预算时终止;等待慢速绑定的空闲程序则会持续运行至 `maxWallMs`),轮询间隔是内部常量而非配置——部署无法将其误调为绕过手段。`maxWallMs` 是配置项,且会传入 `setTimeout`,后者会把超过 `MAX_TIMER_DELAY_MS`(2^31-1 ms)的延迟夹到 1 ms;因此仅有正数校验会放行一个 25 天的上限,它在第一个 tick 就到期,使每次运行都超时。worker 运行时正因如此在加载时对该字段做范围校验。`computeMs` 不需要上界,因为它对照的是实测占用率,而不是交给定时器。 diff --git a/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.i18n.yaml b/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.i18n.yaml index 1242784a2c..75cc5ae485 100644 --- a/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.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 .agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.md -2026-06-17-filesystem-tool-schemas.md: 7680b1200314b5cfe89f8243e2690c748d8c00fe -2026-06-17-filesystem-tool-schemas.zh.md: d75c3f83d7be6dde2c9b70a40411433c581ce830 +2026-06-17-filesystem-tool-schemas.md: 889129682f876a04c8ff7be0ce9118cc7b721e55 +2026-06-17-filesystem-tool-schemas.zh.md: 704bd4692bec760fa9ec8d01489ea48d67363d0d diff --git a/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.md b/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.md index 7680b12003..889129682f 100644 --- a/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.md +++ b/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.md @@ -90,7 +90,7 @@ The following are deliberately out of scope for the first filesystem schema pass - Directory listing, glob, grep, and search tools. - Binary-safe read/write operations. - PDF/image/multimodal `read`. -- Code Mode projection values for filesystem tools. +- PTC mode projection values for filesystem tools. - A canonical edit diff format. ## Testing diff --git a/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.zh.md b/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.zh.md index d75c3f83d7..704bd4692b 100644 --- a/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.zh.md +++ b/.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.zh.md @@ -90,7 +90,7 @@ schema 不将 `expected_hash`、`expected_version` 或 `create_only` 作为面 - 目录列表、glob、grep 和搜索工具。 - 二进制安全的读/写操作。 - PDF/图片/多模态 `read`。 -- 文件系统工具的 Code Mode 投影值。 +- 文件系统工具的 PTC mode 投影值。 - 规范的 edit diff 格式。 ## 测试 diff --git a/.agents/notes/implemented/feature/2026-06-24-workspace-context.i18n.yaml b/.agents/notes/implemented/feature/2026-06-24-workspace-context.i18n.yaml index 01d5ed79ff..98ac192fd7 100644 --- a/.agents/notes/implemented/feature/2026-06-24-workspace-context.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-24-workspace-context.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 .agents/notes/implemented/feature/2026-06-24-workspace-context.md -2026-06-24-workspace-context.md: b9009bd482b23e77bf60a9f45b228258a8d22343 -2026-06-24-workspace-context.zh.md: 96df776d565f064382587c64adb54f4562c98668 +2026-06-24-workspace-context.md: b676ab146b1449d15cfd20ff667899745344c0bf +2026-06-24-workspace-context.zh.md: e75adf39f3000e36aa2360b0225828cb5262a88c diff --git a/.agents/notes/implemented/feature/2026-06-24-workspace-context.md b/.agents/notes/implemented/feature/2026-06-24-workspace-context.md index b9009bd482..b676ab146b 100644 --- a/.agents/notes/implemented/feature/2026-06-24-workspace-context.md +++ b/.agents/notes/implemented/feature/2026-06-24-workspace-context.md @@ -38,7 +38,7 @@ The baseline is a user-role `` with `Instructions from: ` ### Dynamic Discovery And Refresh -After a successful first-party `read`, `write`, or `edit` call, the immutable `tools/result` observer reconciles the touched descendant chain and every scope already known to the session, then queues an `Additional instructions from: ` system-reminder in the agent inbox for the next request. Under Code Mode, successful sub-dispatch touches bubble through opaque parent execution tokens until the top-level result settles. A touch produced inside an agent-loop step does not begin its asynchronous projection until the durable `step/end`; a direct tool execution outside an open step projects immediately. The two boundaries keep result and step adjacency deterministic without making the tool pipeline await filesystem discovery. +After a successful first-party `read`, `write`, or `edit` call, the immutable `tools/result` observer reconciles the touched descendant chain and every scope already known to the session, then queues an `Additional instructions from: ` system-reminder in the agent inbox for the next request. Under PTC mode, successful sub-dispatch touches bubble through opaque parent execution tokens until the top-level result settles. A touch produced inside an agent-loop step does not begin its asynchronous projection until the durable `step/end`; a direct tool execution outside an open step projects immediately. The two boundaries keep result and step adjacency deterministic without making the tool pipeline await filesystem discovery. A content edit appends `Updated instructions from: `, states that the new content replaces the previous content, and includes the complete current file. If precedence changes from one candidate to another, the message also names the previous path and says it no longer applies. If no candidate remains, the plugin appends `Instructions removed: ` and states that the previously loaded instructions no longer apply. diff --git a/.agents/notes/implemented/feature/2026-06-24-workspace-context.zh.md b/.agents/notes/implemented/feature/2026-06-24-workspace-context.zh.md index 96df776d56..e75adf39f3 100644 --- a/.agents/notes/implemented/feature/2026-06-24-workspace-context.zh.md +++ b/.agents/notes/implemented/feature/2026-06-24-workspace-context.zh.md @@ -38,7 +38,7 @@ Status: implemented ### 动态发现与刷新 -第一方 `read`、`write` 或 `edit` 调用成功后,不可变的 `tools/result` 观察器会协调被触及的后代路径链,以及该会话已经知道的每个作用域,然后在 agent inbox 中排入一条 `Additional instructions from: ` system-reminder,供下一次请求使用。在 Code Mode 下,成功的子分派 touch 会沿不透明的父级执行 token 逐层上浮,直到顶层结果落定。在 agent loop 步骤内产生的 touch,须等持久 `step/end` 后才开始异步投影;打开的步骤之外直接执行工具时,则立即投影。这两个边界在不让工具流水线等待文件系统发现的前提下,保证结果/步骤的相邻关系具有确定性。 +第一方 `read`、`write` 或 `edit` 调用成功后,不可变的 `tools/result` 观察器会协调被触及的后代路径链,以及该会话已经知道的每个作用域,然后在 agent inbox 中排入一条 `Additional instructions from: ` system-reminder,供下一次请求使用。在 PTC mode 下,成功的子分派 touch 会沿不透明的父级执行 token 逐层上浮,直到顶层结果落定。在 agent loop 步骤内产生的 touch,须等持久 `step/end` 后才开始异步投影;打开的步骤之外直接执行工具时,则立即投影。这两个边界在不让工具流水线等待文件系统发现的前提下,保证结果/步骤的相邻关系具有确定性。 内容编辑会追加 `Updated instructions from: `,说明新内容取代先前内容,并包含当前的完整文件。如果优先级从一个候选项变为另一个,消息还会指出先前路径并说明它不再适用。如果没有候选项保留,插件会追加 `Instructions removed: `,并说明先前加载的指令不再适用。 diff --git a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml index be71df12c0..0f1df83a62 100644 --- a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.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 .agents/notes/implemented/feature/2026-06-30-hook-bridges.md -2026-06-30-hook-bridges.md: c78460808c029b432f2d556485b0f9069dae4c4a -2026-06-30-hook-bridges.zh.md: 5499d641cacd3af764d2be7c450f4fc92b15e078 +2026-06-30-hook-bridges.md: 30d481f8bda2c65433863e55a75c862cc42479f0 +2026-06-30-hook-bridges.zh.md: 5d994bb0634aeca774d2265e1eba8487e8011ce2 diff --git a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.md b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.md index c78460808c..30d481f8bd 100644 --- a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.md +++ b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.md @@ -41,7 +41,7 @@ Every bridge `inject()` and additional-context input explicitly passes `{ kind: ### Adding context is not a veto — delegate, then prepend -A hook that only attaches `additionalContext` (no block/deny) is NOT a decision the bridge should return on its own: returning `enter` from a waterfall listener WITHOUT calling `next()` short-circuits every later `agent/pre-step` / `tools/post-execute` listener, so a policy/sandbox plugin registered after the bridge would never see the prompt. Each bridge therefore delegates via `next()` before adding its context to a downstream enter decision. The bridge preserves every downstream message, while a downstream pre-step rejection drops the whole claimed batch because no step opens. Post-tool decisions retain their independent ordered `additionalContexts` semantics, including Code Mode deferral through the outer `run_code` result. Only a real `deny`/`block` from the hook itself short-circuits. Tests assert a later listener can still reject a prompt after a context-only hook and that retained prompt and post-tool contexts remain separate. +A hook that only attaches `additionalContext` (no block/deny) is NOT a decision the bridge should return on its own: returning `enter` from a waterfall listener WITHOUT calling `next()` short-circuits every later `agent/pre-step` / `tools/post-execute` listener, so a policy/sandbox plugin registered after the bridge would never see the prompt. Each bridge therefore delegates via `next()` before adding its context to a downstream enter decision. The bridge preserves every downstream message, while a downstream pre-step rejection drops the whole claimed batch because no step opens. Post-tool decisions retain their independent ordered `additionalContexts` semantics, including PTC mode deferral through the outer `run_code` result. Only a real `deny`/`block` from the hook itself short-circuits. Tests assert a later listener can still reject a prompt after a context-only hook and that retained prompt and post-tool contexts remain separate. ### CLAUDE_PROJECT_DIR defaults to the session workspace diff --git a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.zh.md b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.zh.md index 5499d641ca..5d994bb063 100644 --- a/.agents/notes/implemented/feature/2026-06-30-hook-bridges.zh.md +++ b/.agents/notes/implemented/feature/2026-06-30-hook-bridges.zh.md @@ -41,7 +41,7 @@ CC 桥接的 `ask` 结果是一条真正的权限路径,而非终态桥接决 ### 添加上下文不是否决——先 delegate,再 prepend -仅附加 `additionalContext`(没有 block/deny)的钩子并不是桥接可以独自返回的决策:在 waterfall(瀑布式事件)监听器中不调用 `next()` 就返回 `enter`,会短路其后的每个 `agent/pre-step` / `tools/post-execute` 监听器,使注册在桥接之后的策略/沙箱插件看不到该提示词。因此,每个桥接都会先通过 `next()` 委托,再将自身上下文加入下游 enter 决策。桥接会保留所有下游消息;下游 pre-step reject 会丢弃整个已领取批次,因为步骤从未打开。工具后决策仍保留独立的有序 `additionalContexts` 语义,包括 Code Mode 通过外层 `run_code` 结果延迟上下文。只有钩子本身真正返回 `deny`/`block` 才会短路。测试断言:仅上下文钩子之后,较晚的监听器仍能 reject 提示词,且保留的提示词和工具后上下文仍彼此分离。 +仅附加 `additionalContext`(没有 block/deny)的钩子并不是桥接可以独自返回的决策:在 waterfall(瀑布式事件)监听器中不调用 `next()` 就返回 `enter`,会短路其后的每个 `agent/pre-step` / `tools/post-execute` 监听器,使注册在桥接之后的策略/沙箱插件看不到该提示词。因此,每个桥接都会先通过 `next()` 委托,再将自身上下文加入下游 enter 决策。桥接会保留所有下游消息;下游 pre-step reject 会丢弃整个已领取批次,因为步骤从未打开。工具后决策仍保留独立的有序 `additionalContexts` 语义,包括 PTC mode 通过外层 `run_code` 结果延迟上下文。只有钩子本身真正返回 `deny`/`block` 才会短路。测试断言:仅上下文钩子之后,较晚的监听器仍能 reject 提示词,且保留的提示词和工具后上下文仍彼此分离。 ### CLAUDE_PROJECT_DIR 默认为会话工作区 diff --git a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.i18n.yaml b/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.i18n.yaml index 5ec6161319..e32202b83d 100644 --- a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.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 .agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md -2026-07-07-mcp-client-plugin.md: cde9cf1130d6b6b971ddf35c64e34ad6d9fb7dee -2026-07-07-mcp-client-plugin.zh.md: 0cf69a97bc714efcc4083fc2b37519dfc10cdf32 +2026-07-07-mcp-client-plugin.md: 59983abc6288534a82aef903a8c6bed93db24be6 +2026-07-07-mcp-client-plugin.zh.md: 791b6f9eb10bef6a481caa106a0bda31b803dc4c diff --git a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md b/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md index cde9cf1130..59983abc62 100644 --- a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md +++ b/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md @@ -141,10 +141,10 @@ Tools are never silently skipped; which tools are available never depends on plu A unified `execute` handler for all tools from one MCP server: 1. Resolve `rawName` (the executor closes over it) and call `client.callTool({ name: rawName, arguments }, { signal: exec.signal })` with the configured timeout — the public name is never sent to the server. -2. Preserve canonical success as `{ content: JsonValue[], structuredContent? }`; complete MCP JSON blocks remain the programmatic/Code Mode value. `isError: true` throws before any image persistence so the registry owns the failure path. +2. Preserve canonical success as `{ content: JsonValue[], structuredContent? }`; complete MCP JSON blocks remain the programmatic/PTC mode value. `isError: true` throws before any image persistence so the registry owns the failure path. 3. Prepare a separate ordered Native projection. Text runs join with `'\n'`; resource links preserve name and URI as text; audio, embedded resources, malformed blocks, and unknown types become explicit diagnostics. If any image exists, the bridge strictly decodes the complete batch, resolves the calling agent's latest exact route, requires an attachment store plus explicit model image input, and delegates all-member validation and ordered persistence to `AttachmentStore.saveImages()`. Any decode, capability, or storage refusal renders every image as diagnostic text and returns no partial references. 4. Keep `output.render` synchronous and pure. The executor stages its richer projection in a generation-local `WeakMap` keyed by the exact execution; `finalizeContent` installs it only when the registry's post-execute result still has the original canonical value and fallback content. A policy block, value replacement, or content replacement remains authoritative, and a re-sync cannot let an older generation consume new execution state. -5. Code Mode receives the untouched canonical value. Its generic dispatch bridge defers a successful final content sequence containing an image through the outer `run_code` result, so MCP requires no private parent-token special case. +5. PTC mode receives the untouched canonical value. Its generic dispatch bridge defers a successful final content sequence containing an image through the outer `run_code` result, so MCP requires no private parent-token special case. 6. Cancellation: `exec.signal` (from the agent loop's cancel) is passed through to the MCP SDK's `callTool`, exact-model lookup, and the pre-storage gate. ### Subprocess environment (stdio transport) @@ -197,9 +197,9 @@ Rejected. Programmatic callers need protocol-complete MCP blocks and `structured Rejected. Core already owns the role-neutral content vocabulary, and a second service would duplicate its logging and ordering contracts. `output.render` is pure, synchronous, and replayable, so attachment I/O belongs in async execution with an exact finalization handoff. -### Let each image-returning tool special-case Code Mode parents +### Let each image-returning tool special-case PTC mode parents -Rejected. That couples leaf tools to composite-tool internals and misses future rich tools. The generic Code Mode bridge observes the final post-policy content and forwards image-bearing results uniformly. +Rejected. That couples leaf tools to composite-tool internals and misses future rich tools. The generic PTC mode bridge observes the final post-policy content and forwards image-bearing results uniformly. ## Testing @@ -207,7 +207,7 @@ Coverage is named per tier; each behavior lives at the cheapest tier that can ex - **Unit** (`tests/mcp-client.spec.ts`, `tests/apply.spec.ts`, mocked MCP SDK): the `publicToolName` algorithm (clean, normalize, truncate-and-hash, determinism, distinct-identity separation), raw-vs-public wire discipline, cross-server and native-tool coexistence, duplicate-`serverName` load failure and reservation release, invalid-tool-list rejection, generation swap/rollback, failed-re-sync retention, lossless canonical results, mixed rich ordering, atomic malformed batches, exact capability/store refusal, explicit non-image diagnostics, post-execute policy precedence, cancellation, and config schema validation. 100% per-file coverage gates the package. - **E2E** (`tests/mcp-client.e2e.ts`, keyless): the real MCP protocol against the in-repo fixture server, `@modelcontextprotocol/server-everything`, and `@modelcontextprotocol/server-filesystem` over stdio, and against an in-process `StreamableHTTPServerTransport` server over Streamable HTTP — discovery under the namespace, dotted-name normalization end to end, execution round-trips, durable image save/read with base64 retained only in the canonical value, explicit refusal without an image route, duplicate-`serverName` rejection, and disposal. -- **Snapshot**: the assembled ACP example owns the transport-visible inline-image transcript and the Code Mode image-forwarding transcript; package E2E owns the real MCP wire because the runnable snapshot must stay keyless and deterministic rather than spawning third-party server packages. MCP tool cards still use the generic-card fallback and require no package-specific UI snapshot. +- **Snapshot**: the assembled ACP example owns the transport-visible inline-image transcript and the PTC mode image-forwarding transcript; package E2E owns the real MCP wire because the runnable snapshot must stay keyless and deterministic rather than spawning third-party server packages. MCP tool cards still use the generic-card fallback and require no package-specific UI snapshot. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.zh.md b/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.zh.md index 0cf69a97bc..791b6f9eb1 100644 --- a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.zh.md +++ b/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.zh.md @@ -141,10 +141,10 @@ MCP 仅保证工具名在[单个服务器内](https://modelcontextprotocol.io/sp 为来自同一个 MCP 服务器的所有工具提供统一的 `execute` 处理器: 1. 解析 `rawName`(执行器闭包持有它),以配置的超时时间调用 `client.callTool({ name: rawName, arguments }, { signal: exec.signal })`——公开名称永远不发送给服务器。 -2. 把规范成功值保留为 `{ content: JsonValue[], structuredContent? }`;完整 MCP JSON 块仍是程序化调用/Code Mode 值。`isError: true` 会在持久化任何图片前抛出,使失败路径归注册表所有。 +2. 把规范成功值保留为 `{ content: JsonValue[], structuredContent? }`;完整 MCP JSON 块仍是程序化调用/PTC mode 值。`isError: true` 会在持久化任何图片前抛出,使失败路径归注册表所有。 3. 另行准备有序 Native 投影。连续文本块以 `'\n'` 连接;资源链接以文本保留名称和 URI;音频、嵌入资源、格式错误的块和未知类型成为明确诊断。只要存在图片,桥接层就严格解码完整批次,解析调用 agent 的最新确切路由,要求附件存储以及模型明确支持图片输入,再把全成员校验和有序持久化委托给 `AttachmentStore.saveImages()`。任何解码、能力或存储拒绝都会把全部图片渲染为诊断文本,且不返回部分引用。 4. 保持 `output.render` 同步且纯净。执行器把更丰富的投影暂存在按同步世代创建、以确切执行为键的 `WeakMap` 中;只有注册表的 post-execute 结果仍保留原规范值和兜底内容时,`finalizeContent` 才安装该投影。策略阻止、值替换或内容替换仍具有权威性,重新同步也无法让旧世代消费新执行状态。 -5. Code Mode 接收未改动的规范值。其通用分发桥接层会把包含图片的成功最终内容序列经外层 `run_code` 结果延后,因此 MCP 无需私有父 token 特例。 +5. PTC mode 接收未改动的规范值。其通用分发桥接层会把包含图片的成功最终内容序列经外层 `run_code` 结果延后,因此 MCP 无需私有父 token 特例。 6. 取消:`exec.signal`(来自 agent loop 的取消)透传给 MCP SDK 的 `callTool`、确切模型查询和存储前门禁。 ### 子进程环境(stdio 传输) @@ -197,9 +197,9 @@ MCP 仅保证工具名在[单个服务器内](https://modelcontextprotocol.io/sp 不予采用。核心已经拥有角色无关的内容词汇,第二套服务会重复其日志与顺序契约。`output.render` 必须纯净、同步且可回放,因此附件 I/O 属于异步执行,再经确切的最终化交接安装结果。 -### 让每个返回图片的工具分别特殊处理 Code Mode 父调用 +### 让每个返回图片的工具分别特殊处理 PTC mode 父调用 -不予采用。这会把叶子工具与组合工具内部机制耦合,并漏掉未来丰富工具。通用 Code Mode 桥接层观察最终 post-policy 内容,统一转发含图片结果。 +不予采用。这会把叶子工具与组合工具内部机制耦合,并漏掉未来丰富工具。通用 PTC mode 桥接层观察最终 post-policy 内容,统一转发含图片结果。 ## 测试 @@ -207,7 +207,7 @@ MCP 仅保证工具名在[单个服务器内](https://modelcontextprotocol.io/sp - **单元测试**(`tests/mcp-client.spec.ts`、`tests/apply.spec.ts`,mock MCP SDK):`publicToolName` 算法(干净名称、规范化、截断加 hash、确定性、不同标识的分离)、raw 与 public 的协议纪律、跨服务器与原生工具共存、重复 `serverName` 加载失败与预留释放、无效工具列表拒绝、注册代切换/回滚、重新同步失败时保留上一代注册、无损规范结果、丰富内容混合顺序、格式错误批次原子性、确切能力/存储拒绝、明确的非图片诊断、post-execute 策略优先级、取消,以及配置 schema 校验。100% 逐文件覆盖率门禁约束该包。 - **E2E**(`tests/mcp-client.e2e.ts`,无需密钥):使用真实 MCP 协议对接仓库内的 fixture(测试前置数据)服务器、`@modelcontextprotocol/server-everything` 和 `@modelcontextprotocol/server-filesystem`(stdio 传输),以及进程内 `StreamableHTTPServerTransport` 服务器(Streamable HTTP 传输)——命名空间下的发现、带点号名称的端到端规范化、执行往返、持久图片保存/读取且 base64 只保留在规范值中、缺少图片路由时明确拒绝、重复 `serverName` 拒绝,以及 dispose。 -- **快照**:组装后的 ACP 示例负责传输可见的内联图片 transcript 与 Code Mode 图片转发 transcript;包 E2E 负责真实 MCP 协议,因为可运行快照必须保持无密钥且确定,而不是 spawn 第三方服务器包。MCP 工具卡片仍使用通用卡片兜底,无需包专属 UI 快照。 +- **快照**:组装后的 ACP 示例负责传输可见的内联图片 transcript 与 PTC mode 图片转发 transcript;包 E2E 负责真实 MCP 协议,因为可运行快照必须保持无密钥且确定,而不是 spawn 第三方服务器包。MCP 工具卡片仍使用通用卡片兜底,无需包专属 UI 快照。 ## 后果 diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml index c3d47a1d34..3c267eaf73 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.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 .agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md -2026-07-10-parallel-tool-call-execution.md: 81690377e22966eb28ef850a41a4c05c6fa534f0 -2026-07-10-parallel-tool-call-execution.zh.md: 3cee5533b7686f461548b97880e594fffee234d2 +2026-07-10-parallel-tool-call-execution.md: 870ab3bd6f48e7e2aad63baafb55020fd4c6ccd5 +2026-07-10-parallel-tool-call-execution.zh.md: ccab1d9b912ce6332bb493859bcefdb4b173b0d3 diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md index 81690377e2..870ab3bd6f 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md @@ -48,7 +48,7 @@ Each started call appends `tool/call` immediately before its pre-execute gate. C An abort before a group starts records no calls from that group. An abort during a group stops replenishment, waits for already-started calls, commits their results in order, drains accepted batch context after those results, and then ends the step through the existing abort path. Calls that never start have no audit event. An unexpected scheduler failure stops new dispatches, waits for every already-started dispatch to settle, and rethrows the first failure. Because that failure is terminal internal state rather than a tool outcome, the loop does not invent tool results for rejected or uncommitted calls. -Code Mode remains outside this scheduler because the model emits one native `run_code` call. `run_code` and its internal dispatch queue remain serial; native sibling calls in `mode: 'both'` use the normal scheduler. +PTC mode remains outside this scheduler because the model emits one native `run_code` call. `run_code` and its internal dispatch queue remain serial; native sibling calls in `mode: 'both'` use the normal scheduler. ## Safety contract @@ -60,7 +60,7 @@ Any shared state touched during execution must be concurrency-safe. This include `maxParallelToolCalls` is a positive AgentLoop deployment cap shared by every agent the factory creates. It defaults to `10`; `1` preserves serial execution. Exact fields and defaults live in the generated [configuration catalog](../../../../docs/config-catalog.md). -The shipped declarations are conservative. Web search, web fetch, filesystem read, the session-query trace/read tools, and subagent delegation opt in — delegation because a child works in its own session and its run never mutates the parent session, with sibling workspace coordination owned by the model ([parallel subagent Agent Note](2026-08-09-parallel-subagent-delegations.md)). Filesystem writes and edits, bash tools, the session-query search tools, workflow, user interaction, todo mutation, Code Mode, and Cordis mutation tools remain exclusive. Bash has no proven input-sensitive classifier and remains exclusive. +The shipped declarations are conservative. Web search, web fetch, filesystem read, the session-query trace/read tools, and subagent delegation opt in — delegation because a child works in its own session and its run never mutates the parent session, with sibling workspace coordination owned by the model ([parallel subagent Agent Note](2026-08-09-parallel-subagent-delegations.md)). Filesystem writes and edits, bash tools, the session-query search tools, workflow, user interaction, todo mutation, PTC mode, and Cordis mutation tools remain exclusive. Bash has no proven input-sensitive classifier and remains exclusive. Filesystem read relies on a narrow recorder exception: its synchronous observation updates may settle out of order, but write and edit re-check the observed version before mutation, so stale state only produces `FS_STALE_VERSION`. @@ -68,7 +68,7 @@ Filesystem read relies on a narrow recorder exception: its synchronous observati Unit coverage pins fail-closed classification, typed argument validation, grouping, barriers, live reclassification after registry replacement, the rolling cap, distinct execution objects, middleware order, ordered results and context, abort draining, and scheduler-failure quiescence. First-party tests pin each parallel declaration. -Snapshot coverage pins the visible multi-call transcript: pending calls may overlap while completed results remain model-ordered. Code Mode coverage pins its serial boundary. No provider-backed e2e is required because scheduling is deterministic loop behavior. +Snapshot coverage pins the visible multi-call transcript: pending calls may overlap while completed results remain model-ordered. PTC mode coverage pins its serial boundary. No provider-backed e2e is required because scheduling is deterministic loop behavior. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md index 3cee5533b7..ccab1d9b91 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md @@ -48,7 +48,7 @@ Status: implemented 如果在一组启动前中止,系统不会记录该组的任何调用。如果在一组执行期间中止,系统会停止补充池,等待已启动的调用,按顺序提交其结果,在这些结果之后排空已接受的批次上下文,然后通过现有中止路径结束该步骤。从未启动的调用没有审计事件。调度器发生意外故障时,会停止新的派发,等待每项已启动的派发结算,并重新抛出第一个故障。由于该故障是内部终态,而非工具结果,循环不会为被拒绝或未提交的调用虚构工具结果。 -Code Mode 仍不使用此调度器,因为模型只会发出一个原生 `run_code` 调用。`run_code` 及其内部派发队列仍按串行方式执行;`mode: 'both'` 中的原生并列调用使用常规调度器。 +PTC mode 仍不使用此调度器,因为模型只会发出一个原生 `run_code` 调用。`run_code` 及其内部派发队列仍按串行方式执行;`mode: 'both'` 中的原生并列调用使用常规调度器。 ## 安全约定 @@ -60,7 +60,7 @@ Code Mode 仍不使用此调度器,因为模型只会发出一个原生 `run_c `maxParallelToolCalls` 是 AgentLoop 的正整数部署上限,由工厂创建的所有 agent(智能体)共享。默认值为 `10`;`1` 保持串行执行。字段和默认值的精确定义见生成的[配置目录](../../../../docs/config-catalog.zh.md)。 -当前实现中的声明保持保守。Web 搜索、Web 获取、文件系统读取、会话查询的 trace/read 工具和 subagent 委派选择并行;委派之所以并行,是因为子 agent 在自己的会话中工作,其运行绝不变更父会话,并列委派间的工作区协调由模型负责([并行 subagent Agent Note](2026-08-09-parallel-subagent-delegations.zh.md))。文件系统写入与编辑、bash 工具、会话查询的 search 工具、工作流、用户交互、todo 变更、Code Mode 以及 Cordis 变更工具仍按独占方式执行。Bash 没有已证明的输入敏感分类器,因此仍按独占方式执行。 +当前实现中的声明保持保守。Web 搜索、Web 获取、文件系统读取、会话查询的 trace/read 工具和 subagent 委派选择并行;委派之所以并行,是因为子 agent 在自己的会话中工作,其运行绝不变更父会话,并列委派间的工作区协调由模型负责([并行 subagent Agent Note](2026-08-09-parallel-subagent-delegations.zh.md))。文件系统写入与编辑、bash 工具、会话查询的 search 工具、工作流、用户交互、todo 变更、PTC mode 以及 Cordis 变更工具仍按独占方式执行。Bash 没有已证明的输入敏感分类器,因此仍按独占方式执行。 文件系统读取依赖一个范围很窄的记录器例外:其同步观察更新可以不按顺序结算,但写入和编辑在变更前会重新检查已观察的版本,因此陈旧状态只会导致 `FS_STALE_VERSION`。 @@ -68,7 +68,7 @@ Code Mode 仍不使用此调度器,因为模型只会发出一个原生 `run_c 单元测试固定了按安全侧原则进行的分类、类型化参数验证、分组、屏障、注册表替换后的运行时重新分类、滚动上限、独立执行对象、中间件顺序、有序结果与上下文、中止排空,以及调度器故障后的完全停稳。第一方测试固定了每项并行声明。 -快照测试固定了可见的多调用 transcript(文本记录):待处理调用可以重叠执行,已完成结果仍按模型顺序排列。Code Mode 测试固定了其串行边界。此调度属于确定性循环行为,因此无需依赖提供方的 e2e 测试。 +快照测试固定了可见的多调用 transcript(文本记录):待处理调用可以重叠执行,已完成结果仍按模型顺序排列。PTC mode 测试固定了其串行边界。此调度属于确定性循环行为,因此无需依赖提供方的 e2e 测试。 ## 备选方案 diff --git a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml index 927c70ef33..c13694c791 100644 --- a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.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 .agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md -2026-07-12-subagent-persona-tool-filter-and-depth.md: 4e4fa3c0039004c50f9c803a951253a7f603f909 -2026-07-12-subagent-persona-tool-filter-and-depth.zh.md: e2fb1508d780d39f587d279ab097f86ed7602c80 +2026-07-12-subagent-persona-tool-filter-and-depth.md: 7ba9768df3679da6b07728cf64237c47d4c73b2f +2026-07-12-subagent-persona-tool-filter-and-depth.zh.md: d3a8240542d896a27e82b1be1b491c241003e27e diff --git a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md index 4e4fa3c003..7ba9768df3 100644 --- a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md +++ b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md @@ -34,7 +34,7 @@ This uses the normal system-prompt registration mechanism rather than a second p ### Tool filtering is one live global-view rule -The tool filter controls capability visibility and executable lookup together. An in-process provider installs `ToolRuntime.restrict()` in the child's scope before publication, and the registry's single resolver applies the same result to wire tool schemas, lookup, execution, and Code Mode SDK generation. Independently registered system-prompt sections are outside `ToolRuntime`, so filtering a tool does not remove that plugin's standalone guidance. +The tool filter controls capability visibility and executable lookup together. An in-process provider installs `ToolRuntime.restrict()` in the child's scope before publication, and the registry's single resolver applies the same result to wire tool schemas, lookup, execution, and PTC mode SDK generation. Independently registered system-prompt sections are outside `ToolRuntime`, so filtering a tool does not remove that plugin's standalone guidance. Resolution follows these rules: @@ -85,7 +85,7 @@ A security design would need a separate authority representation, propagation ru **Snapshot allowed global tools at child creation.** A frozen allow-set makes future registration uniformly unavailable, but it changes hot-registration semantics and starts an authorization design. The implemented filter stays a live registry predicate and documents allow-versus-deny behavior directly. -**Hide only tool schemas.** Presentation-only filtering lets the model execute a tool that the prompt says does not exist through Code Mode or a forged call. One resolver governs both presentation and execution instead. +**Hide only tool schemas.** Presentation-only filtering lets the model execute a tool that the prompt says does not exist through PTC mode or a forged call. One resolver governs both presentation and execution instead. **Encode the depth cap as an automatic tool filter.** A creation-time filter snapshots a decision that may depend on runtime state, affects only one configured tool name, and does not protect direct service callers or alternate delegation tools. The provider instead enforces the absolute cap at every start. diff --git a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md index e2fb1508d7..d3a8240542 100644 --- a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md +++ b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md @@ -36,7 +36,7 @@ subagent 启动有三个独立的组合控制:`persona`、`toolFilter` 和 `ma ### 工具过滤是一条作用于实时全局视图的规则 -工具过滤同时控制能力可见性和可执行查找。进程内提供方在发布前于子 agent 作用域中安装 `ToolRuntime.restrict()`,注册表的单一解析器对协议格式(wire format)的工具 schema、查找、执行和 Code Mode SDK 生成施加相同的结果。独立注册的系统提示词段落不在 `ToolRuntime` 内,因此过滤一个工具不会移除该插件的独立指导文本。 +工具过滤同时控制能力可见性和可执行查找。进程内提供方在发布前于子 agent 作用域中安装 `ToolRuntime.restrict()`,注册表的单一解析器对协议格式(wire format)的工具 schema、查找、执行和 PTC mode SDK 生成施加相同的结果。独立注册的系统提示词段落不在 `ToolRuntime` 内,因此过滤一个工具不会移除该插件的独立指导文本。 解析遵循以下规则: @@ -87,7 +87,7 @@ subagent 启动有三个独立的组合控制:`persona`、`toolFilter` 和 `ma **在子 agent 创建时快照允许的全局工具。** 冻结的 allow 集合使未来注册统一不可用,但它改变了热注册语义并开启了授权设计。已实现的过滤器保持为活跃的注册表谓词,并直接记录 allow 与 deny 的行为。 -**仅隐藏工具 schema。** 仅呈现层的过滤让模型可以通过 Code Mode 或伪造调用执行一个提示词声称不存在的工具。改为由一个解析器同时管控呈现和执行。 +**仅隐藏工具 schema。** 仅呈现层的过滤让模型可以通过 PTC mode 或伪造调用执行一个提示词声称不存在的工具。改为由一个解析器同时管控呈现和执行。 **把深度上限编码为自动工具过滤器。** 创建时过滤器会快照一个可能依赖运行时状态的决策,只影响一个已配置工具名,且不保护直接服务调用方或替代委派工具。提供方改为在每次启动时强制绝对上限。 diff --git a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml deleted file mode 100644 index 0ed1b87308..0000000000 --- a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# 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 .agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md -2026-07-20-code-mode-typed-tool-returns.md: a8a251f5f0d39f4deedc42e08eb45c2b5fa11807 -2026-07-20-code-mode-typed-tool-returns.zh.md: 2589d63d3fb900c88fb15ab68107e75735d1be44 diff --git a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml index cb77a58801..c7a1607d19 100644 --- a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.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 .agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md -2026-07-20-dsh-cli-personal-config.md: 8e9604072464f2b5adbd3b2058c54fab732d06d6 -2026-07-20-dsh-cli-personal-config.zh.md: 4863b5dbe19c125ab0b9829ffbffb673ea8d5961 +2026-07-20-dsh-cli-personal-config.md: db1e5638f06b66f74d4761d1eab4e5248e6def0b +2026-07-20-dsh-cli-personal-config.zh.md: d796d9887421aa7c8e9f3e8d1de66c74e10c139d diff --git a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md index 8e96040724..db1e5638f0 100644 --- a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md +++ b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md @@ -42,7 +42,7 @@ The TUI and Web register the exact personal path through Cordis HMR after boot. - An installed `dsh` command can run from any directory, while source users invoke `pnpm dsh` from the checkout; both can apply personal providers, models, installed bundle entries, and other Loader entries with no checkout edit. The behavior was verified end to end against a personal Anthropic proxy with Opus 4.8, including a bash tool round trip. - Because an id-targeted patch replaces the whole `config`, a personal override restates the base fields it keeps and can drift when the base entry changes shape; the loader's entry-not-found/name-mismatch warnings and [`dsh --dump-config`](../../../../apps/cli/README.md#profiles) (which prints the composed tree those patches produce) are the diagnostics. -- Personal patches resolve ids against the booted file's own tree, so nested-include overlays (Code Mode) are not personalized; live-run parity for those leaves is deferred. +- Personal patches resolve ids against the booted file's own tree, so nested-include overlays (PTC mode) are not personalized; live-run parity for those leaves is deferred. - `dsh-app-boot` depends on `js-yaml` and imports the include's `!!js` YAML dialect (`entryListSchema`) directly, and, like `apps/cli`, depends on `@deepseek-ai/dsh-home-paths` for `resolveDshHome`. - Live watching belongs only to long-running TUI and Web processes. Headless automation gets deterministic startup configuration and exits without retaining a watcher. diff --git a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md index 4863b5dbe1..d796d98874 100644 --- a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md +++ b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md @@ -42,7 +42,7 @@ TUI 和 Web 启动后通过 Cordis HMR(热模块替换)注册确切的个人 - 已安装的 `dsh` 命令可从任意目录运行,源码用户则从 checkout 调用 `pnpm dsh`;两者都无需修改 checkout 即可应用个人提供方、模型、已安装组合包的配置项和其他 Loader 配置项。该行为已针对个人 Anthropic 代理与 Opus 4.8 端到端验证,包括一次 bash 工具往返。 - 由于按 id 定位的补丁替换整个 `config`,个人覆盖必须复述它保留的基础字段,并可能随基础配置项形态变化而漂移;诊断手段是 loader 的「配置项未找到/名称不匹配」警告和 [`dsh --dump-config`](../../../../apps/cli/README.zh.md#profiles)(打印这些补丁合成出的配置树)。 -- 个人补丁只在被启动文件自身的树里解析 id,因此嵌套 include 的 overlay(Code Mode)不会被个性化;这些叶子的实际运行等价性暂缓。 +- 个人补丁只在被启动文件自身的树里解析 id,因此嵌套 include 的 overlay(PTC mode)不会被个性化;这些叶子的实际运行等价性暂缓。 - `dsh-app-boot` 依赖 `js-yaml`,并直接导入 include 的 `!!js` YAML 方言(`entryListSchema`);与 `apps/cli` 一样依赖 `@deepseek-ai/dsh-home-paths` 以获取 `resolveDshHome`。 - 只有长时间运行的 TUI 和 Web 进程进行实时监视。无头自动化使用确定性的启动配置,退出时不会保留 watcher。 diff --git a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-ui-foundation.i18n.yaml b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.i18n.yaml similarity index 57% rename from .agents/notes/implemented/feature/2026-07-26-code-dispatch-ui-foundation.i18n.yaml rename to .agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.i18n.yaml index 34b44986bf..249e799554 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-ui-foundation.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # 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 .agents/notes/implemented/feature/2026-07-26-code-dispatch-ui-foundation.md -2026-07-26-code-dispatch-ui-foundation.md: 2b919dfb978ef3df0e65d2e0e410e2bd07c75a46 -2026-07-26-code-dispatch-ui-foundation.zh.md: 33599c5689973d05ca6ec98fbf919cea6fc1cd4d +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.md +2026-07-20-ptc-typed-tool-returns.md: 113ea2c305984ce95b0d85d70f6773abbc0929db +2026-07-20-ptc-typed-tool-returns.zh.md: 13b2f6e381a30b4db1cd818ab47d3e30c8e0c84a diff --git a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.md similarity index 73% rename from .agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md rename to .agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.md index a8a251f5f0..113ea2c305 100644 --- a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md +++ b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.md @@ -1,22 +1,22 @@ -# Agent Note: Typed tool returns in Code Mode +# Agent Note: Typed tool returns in PTC mode Status: implemented -English | [中文](2026-07-20-code-mode-typed-tool-returns.zh.md) +English | [中文](2026-07-20-ptc-typed-tool-returns.zh.md) ## Problem -Code Mode originally projected each nested tool result back from `ContentBlock[]` into one string. That preserved the human-readable Native presentation but erased the canonical result the tool had already produced: programs had to scrape job ids and dynamic mount ids from prose, structured search and workflow results lost their shape, and non-text blocks became placeholders. The generated SDK could describe arguments but could only promise `Promise` regardless of the tool's real output. +PTC mode originally projected each nested tool result back from `ContentBlock[]` into one string. That preserved the human-readable Native presentation but erased the canonical result the tool had already produced: programs had to scrape job ids and dynamic mount ids from prose, structured search and workflow results lost their shape, and non-text blocks became placeholders. The generated SDK could describe arguments but could only promise `Promise` regardless of the tool's real output. The runtime also treated binding values and the final program value as presentation data. Separate log and completion caps could replace an oversized or non-cloneable completion with inspected text even though intermediate values do not enter model context. That made programmatic composition lossy and confused the memory boundary with the prompt boundary. -The [canonical tool-output contract](../architecture/2026-07-20-canonical-tool-output-contract.md) establishes one validated execution-time value and a separate Native renderer. Code Mode should consume that value directly, preserve it across the worker boundary, and bound only the final output the program deliberately returns to the model. +The [canonical tool-output contract](../architecture/2026-07-20-canonical-tool-output-contract.md) establishes one validated execution-time value and a separate Native renderer. PTC mode should consume that value directly, preserve it across the worker boundary, and bound only the final output the program deliberately returns to the model. ## Decision -Code Mode is a typed projection of the visible tool registry. Each successful binding resolves to the final canonical `JsonValue` after post-execute policy, while a failed binding rejects with a real `ToolCallError`. Intermediate values remain inside the run and cross the worker boundary whole. The outer `run_code` logs, completion value, or failure diagnostic enter the configurable output ledger and model-facing spill pipeline; a successfully settled sub-call whose final Native content contains an image additionally defers that complete ordered content through the parent result as logged, source-attributed context. +PTC mode is a typed projection of the visible tool registry. Each successful binding resolves to the final canonical `JsonValue` after post-execute policy, while a failed binding rejects with a real `ToolCallError`. Intermediate values remain inside the run and cross the worker boundary whole. The outer `run_code` logs, completion value, or failure diagnostic enter the configurable output ledger and model-facing spill pipeline; a successfully settled sub-call whose final Native content contains an image additionally defers that complete ordered content through the parent result as logged, source-attributed context. -This note owns the return and failure contract layered on the original [Code Mode foundation](2026-06-15-code-mode.md). The unified schema vocabulary is owned by the [JSON-value schema DSL note](../architecture/2026-07-20-unified-json-value-schema-dsl.md), and Native rendering and policy projection remain owned by the canonical-output note. +This note owns the return and failure contract layered on the original [PTC mode foundation](2026-06-15-ptc.md). The unified schema vocabulary is owned by the [JSON-value schema DSL note](../architecture/2026-07-20-unified-json-value-schema-dsl.md), and Native rendering and policy projection remain owned by the canonical-output note. ### Generated SDK @@ -51,7 +51,7 @@ declare const tools: { Before dispatch the bridge snapshots binding arguments as lossless JSON and snapshots the detached value again for an independent durable summary event. Host-side detachment, immutable execution, and output-schema projection all use iterative traversals rather than nested structured clone or recursive freezing. `undefined`, non-finite numbers, `-0`, sparse arrays, cycles, functions, and exotic objects reject that call before the tool runs. Successful dispatch returns `ToolExecutionResult.value`; Native `content`, metadata, and internal error information do not cross to the program. Image-bearing final content is not a second binding value: the bridge ferries it after the outer result so the next model request can see the durable image, while post-execute block/content replacement remains authoritative and text-only results are not duplicated. -Code Mode declares its rejection capability on the runtime request as `{ name: "ToolCallError", memberNameProperty: "toolName" }`. The runtime Service Definition treats those names as data: the worker materializes and injects the actual constructor used for `tools` binding failures, so `error instanceof ToolCallError` works without making a generic runtime know about tools. The worker constructs failures and defines their public fields through module-captured error and property-definition intrinsics plus null-prototype descriptors, so model mutations cannot replace the promised rejection with a worker failure. The error has the standard `Error` message plus the exact `toolName`; it deliberately omits `ToolFailure.info`, error codes, and Native content. This is an exception contract for control flow, not a failure union for programmatic classification. +PTC mode declares its rejection capability on the runtime request as `{ name: "ToolCallError", memberNameProperty: "toolName" }`. The runtime Service Definition treats those names as data: the worker materializes and injects the actual constructor used for `tools` binding failures, so `error instanceof ToolCallError` works without making a generic runtime know about tools. The worker constructs failures and defines their public fields through module-captured error and property-definition intrinsics plus null-prototype descriptors, so model mutations cannot replace the promised rejection with a worker failure. The error has the standard `Error` message plus the exact `toolName`; it deliberately omits `ToolFailure.info`, error codes, and Native content. This is an exception contract for control flow, not a failure union for programmatic classification. Binding arguments and resolutions are revalidated as lossless JSON on both sides of the hostile worker protocol and have no byte cap. Before crossing through structured clone, each detached value is encoded as a flat pre-order token stream whose transport nesting is bounded; the receiver rebuilds it iteratively. Valid application nesting therefore has neither a JavaScript call-stack depth cap nor a platform-specific nested structured-clone limit. At module initialization the worker captures its own realm's `Array.prototype` and `Object.prototype` identities, the native function-source intrinsic used only to recognize foreign-realm plain-container prototypes, and every structural and metering intrinsic used by the JSON boundary. Property writes use null-prototype descriptors, while private array and set operations invoke captured methods without consulting mutable global or prototype slots. Model code can therefore replace helpers such as `Object.keys`, `Array.isArray`, collection methods, string methods, or `Buffer.byteLength`, rewrite intrinsic-prototype constructor slots, or add descriptor-shaped fields to `Object.prototype` without changing validation, wire transport, or byte accounting. The foreign-realm native function-source check still rejects user-authored constructors that imitate `Object` or `Array`. The dependency-light runtime Service Definition names its structural equivalent `CodeJsonValue` so it need not depend on the session-owned canonical type; the generated SDK and tool API use `JsonValue`. Intermediate values are not prompt-truncated, context-spilled, or persisted. This preserves full acquired search, workflow, task, filesystem, and MCP values for programmatic filtering while leaving provider and executor acquisition limits truthful. @@ -73,13 +73,13 @@ Temporary Cordis Plugins follow the same rule: `cordis_mount` returns `{ id, plu ### Persistence, metadata, and spill -Nested dispatch logs the sub-call's full rendered `content`/`isError` on `tool/code-dispatch` but does not persist canonical values. `tool/result` continues to persist only rendered content, error, and optional metadata. A successful final content sequence containing an image is also wrapped in a source-attributed user message and deferred through the outer result; the normal session event makes that model-visible input reconstructable. `SESSION_FORMAT_VERSION` remains unchanged (pre-release shape churn does not bump it) and replay cannot recreate intermediate canonical program values. +Nested dispatch logs the sub-call's full rendered `content`/`isError` on `tool/ptc-dispatch` but does not persist canonical values. `tool/result` continues to persist only rendered content, error, and optional metadata. A successful final content sequence containing an image is also wrapped in a source-attributed user message and deferred through the outer result; the normal session event makes that model-visible input reconstructable. `SESSION_FORMAT_VERSION` remains unchanged (pre-release shape churn does not bump it) and replay cannot recreate intermediate canonical program values. The opaque `exec.parent` token marks nested calls. Presentation metadata and generic or tool-owned spill projections skip those calls because they have no direct result card and their canonical values never enter context. The outer `run_code` call alone produces one card and may spill its final post-policy presentation; `run_code` intentionally declares neither a result presenter nor presentation metadata, so UI adapters complete the card through their generic raw-content fallback using durable `tool/result.content`. ## Testing -Compile-time and snapshot tests pin exact `ToolArgsMap`, `ToolOutputMap`, `ToolName`, schema-to-TypeScript coverage, exotic names, and assembled Code Mode image forwarding. Registry and real-worker tests cover scalar, array, object, and null values; raw string rendering; absent `undefined`; consumer-declared real rejection classes, including `ToolCallError`; invalid arguments and completions, including intrinsic-looking forged prototypes; model-mutated JSON-boundary globals, prototype methods, constructor slots, and inherited descriptor fields; typed binding failures after those mutations; large uncapped intermediate bindings; nested spill suppression; generic image-bearing context deferral plus post-execute replacement/block precedence; exact and over-limit 64 MiB accounting; combined logs/value/diagnostic accounting; giant thrown stacks; bounded failure spill; hostile forged traffic; and built-package execution. +Compile-time and snapshot tests pin exact `ToolArgsMap`, `ToolOutputMap`, `ToolName`, schema-to-TypeScript coverage, exotic names, and assembled PTC mode image forwarding. Registry and real-worker tests cover scalar, array, object, and null values; raw string rendering; absent `undefined`; consumer-declared real rejection classes, including `ToolCallError`; invalid arguments and completions, including intrinsic-looking forged prototypes; model-mutated JSON-boundary globals, prototype methods, constructor slots, and inherited descriptor fields; typed binding failures after those mutations; large uncapped intermediate bindings; nested spill suppression; generic image-bearing context deferral plus post-execute replacement/block precedence; exact and over-limit 64 MiB accounting; combined logs/value/diagnostic accounting; giant thrown stacks; bounded failure spill; hostile forged traffic; and built-package execution. Keyless real-worker integration tests pin the two handle workflows that prose results could not safely support. A background bash call returns its job id, the outer run settles, and a later run polls that id to completion; separate cases prove pre-abort creates no task, post-publication call abort preserves the task, foreground execution stays signal-coupled, and `job_kill` owns cancellation. A Cordis program reads an active or pending mount's id and `waitingFor` fields directly, unmounts by that id, and confirms removal without parsing rendered text. @@ -93,13 +93,13 @@ Keyless real-worker integration tests pin the two handle workflows that prose re **Silently inspect or truncate an oversized completion.** Rejected because changing a JSON value into a string is lossy and type-incorrect. The explicit `output-limit` failure lets the model choose a smaller result, while the retained logs and diagnostic can still use normal outer spill. -**Require each rich leaf tool to inspect `exec.parent` and defer itself.** Rejected because it couples leaf tools to Code Mode internals, duplicates policy handling, and misses future rich tools. The dispatch bridge owns generic forwarding from the already settled final result. +**Require each rich leaf tool to inspect `exec.parent` and defer itself.** Rejected because it couples leaf tools to PTC mode internals, duplicates policy handling, and misses future rich tools. The dispatch bridge owns generic forwarding from the already settled final result. **Expose Native rich content as part of every binding's canonical value.** Rejected because a canonical value is lossless JSON and tool-specific; attachment blocks are a model projection with durable lifecycle semantics. Keeping the value and projection separate preserves typed programs without dropping images from later model context. ## Consequences -Code programs can compose tools through stable values instead of reverse-engineering Native prose. Native and Both Mode retain their existing text and UI presentation, while Code Mode receives output-schema types and exact runtime JSON. Tool authors must treat the canonical value as their programmatic API and put display-only formatting in the renderer. +Code programs can compose tools through stable values instead of reverse-engineering Native prose. Native and Both Mode retain their existing text and UI presentation, while PTC mode receives output-schema types and exact runtime JSON. Tool authors must treat the canonical value as their programmatic API and put display-only formatting in the renderer. The worker performs bounded-depth flat-wire transport and lossless validation but does not make intermediate values cheap or durable. Outer overflow is an explicit failed run, and error handling remains intentionally human-guided rather than a versioned code union. @@ -110,7 +110,7 @@ The worker performs bounded-depth flat-wire transport and lossless validation bu - Intermediate canonical values are execution-local and unavailable to replay because durable events persist only presentation and bounded summaries. - Intermediate values have no byte cap and can exhaust process or worker memory through retention, flat-wire copies, or structured-clone cost. - The 64 MiB hard cap applies only to the outer variable payloads, excluding fixed result-envelope syntax and presentation whitespace; spill cannot recover bytes rejected beyond that cap. -- Provider or executor acquisition limits may already have discarded source data before a canonical value reaches Code Mode. +- Provider or executor acquisition limits may already have discarded source data before a canonical value reaches PTC mode. - Unsupported MCP output schemas fall back to `JsonValue`; admitted MCP images use the generic deferred projection, while audio and embedded-resource payloads remain diagnostic-only. - There is one result card per outer `run_code`, never per nested call. - Code failures expose `ToolCallError` message and tool name only, without a programmatic error-code union. diff --git a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.zh.md similarity index 71% rename from .agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md rename to .agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.zh.md index 2589d63d3f..13b2f6e381 100644 --- a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md +++ b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.zh.md @@ -1,22 +1,22 @@ -# Agent Note: Code Mode 的类型化工具返回值 +# Agent Note: PTC mode 的类型化工具返回值 Status: implemented -[English](2026-07-20-code-mode-typed-tool-returns.md) | 中文 +[English](2026-07-20-ptc-typed-tool-returns.md) | 中文 ## 问题 -Code Mode 过去会把每个嵌套工具的结果从 `ContentBlock[]` 重新投影为一个字符串。这样虽然保留了适合人类阅读的 Native 呈现,却丢失了工具已经生成的规范结果:程序只能从自然语言中提取 job id 和动态挂载 id;结构化搜索与工作流结果失去原有形态;非文本块则变为占位符。生成的 SDK 可以描述参数,却无论工具实际输出为何都只能承诺 `Promise`。 +PTC mode 过去会把每个嵌套工具的结果从 `ContentBlock[]` 重新投影为一个字符串。这样虽然保留了适合人类阅读的 Native 呈现,却丢失了工具已经生成的规范结果:程序只能从自然语言中提取 job id 和动态挂载 id;结构化搜索与工作流结果失去原有形态;非文本块则变为占位符。生成的 SDK 可以描述参数,却无论工具实际输出为何都只能承诺 `Promise`。 运行时还把绑定值和程序最终返回值当作展示数据。日志和完成值分别设置上限,导致过大或无法克隆的完成值可能被替换为检查后生成的文本,而中间值本来就不会进入模型上下文。这种设计使程序化组合产生信息损失,也混淆了内存边界与提示词边界。 -[规范工具输出约定](../architecture/2026-07-20-canonical-tool-output-contract.zh.md)确立了单一、经过校验的执行期值,并将 Native 渲染器与之分离。Code Mode 应直接消费该值,在跨越 worker 边界时完整保留它,并且只限制程序有意返回给模型的最终输出。 +[规范工具输出约定](../architecture/2026-07-20-canonical-tool-output-contract.zh.md)确立了单一、经过校验的执行期值,并将 Native 渲染器与之分离。PTC mode 应直接消费该值,在跨越 worker 边界时完整保留它,并且只限制程序有意返回给模型的最终输出。 ## 决策 -Code Mode 是可见工具注册表的类型化投影。每个成功的绑定调用都会解析为 post-execute 策略处理后的最终规范 `JsonValue`,失败的绑定调用则会以真正的 `ToolCallError` 拒绝 Promise。中间值只存在于本次运行中,并完整跨越 worker 边界。外层 `run_code` 的日志、完成值或失败诊断会进入可配置的输出账本以及面向模型的输出落盘流水线;如果成功结算的子调用最终 Native 内容包含图片,其完整有序内容还会经父结果延后为写入日志且带来源归属的上下文。 +PTC mode 是可见工具注册表的类型化投影。每个成功的绑定调用都会解析为 post-execute 策略处理后的最终规范 `JsonValue`,失败的绑定调用则会以真正的 `ToolCallError` 拒绝 Promise。中间值只存在于本次运行中,并完整跨越 worker 边界。外层 `run_code` 的日志、完成值或失败诊断会进入可配置的输出账本以及面向模型的输出落盘流水线;如果成功结算的子调用最终 Native 内容包含图片,其完整有序内容还会经父结果延后为写入日志且带来源归属的上下文。 -本文档定义叠加在原始 [Code Mode 基础](2026-06-15-code-mode.zh.md)之上的返回值与失败约定。统一 schema 词汇由 [JSON 值 schema DSL Agent Note](../architecture/2026-07-20-unified-json-value-schema-dsl.zh.md)负责定义;Native 渲染与策略投影仍由规范输出 Agent Note 负责定义。 +本文档定义叠加在原始 [PTC mode 基础](2026-06-15-ptc.zh.md)之上的返回值与失败约定。统一 schema 词汇由 [JSON 值 schema DSL Agent Note](../architecture/2026-07-20-unified-json-value-schema-dsl.zh.md)负责定义;Native 渲染与策略投影仍由规范输出 Agent Note 负责定义。 ### 生成的 SDK @@ -51,7 +51,7 @@ declare const tools: { 分发前,桥接层会把绑定参数快照为无损 JSON,再对分离后的值生成一次快照,供独立的持久摘要事件使用。宿主侧的值分离、执行数据的不可变处理与输出 schema 投影均采用迭代遍历,而不使用嵌套结构化克隆或递归冻结。`undefined`、非有限数、`-0`、稀疏数组、循环引用、函数和非普通对象都会使该调用在工具运行前被拒绝。成功分发会返回 `ToolExecutionResult.value`;Native `content`、元数据和内部错误信息不会传入程序。含图片的最终内容不是第二份绑定值:桥接层会在外层结果之后转运它,使下一次模型请求可以看到持久图片;post-execute 阻止/内容替换仍具有权威性,纯文本结果不会重复。 -Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProperty: "toolName" }` 声明其以异常拒绝 Promise 的能力。运行时 Service Definition 只把这些名称视为数据:worker 会动态生成并注入真正用于 `tools` 绑定失败的构造函数,因此无需让通用运行时了解工具,`error instanceof ToolCallError` 也能成立。worker 使用模块初始化时捕获的 Error 构造函数与属性定义内建方法,配合原型为 null 的属性描述符,构造失败对象并定义其公开字段,因此模型代码的修改不会把约定承诺的 reject 变成 worker 失败。该错误包含标准的 `Error` 消息和确切的 `toolName`,并有意省略 `ToolFailure.info`、错误代码与 Native 内容。这是一项用于控制流的异常约定,而不是供程序分类的失败联合。 +PTC mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProperty: "toolName" }` 声明其以异常拒绝 Promise 的能力。运行时 Service Definition 只把这些名称视为数据:worker 会动态生成并注入真正用于 `tools` 绑定失败的构造函数,因此无需让通用运行时了解工具,`error instanceof ToolCallError` 也能成立。worker 使用模块初始化时捕获的 Error 构造函数与属性定义内建方法,配合原型为 null 的属性描述符,构造失败对象并定义其公开字段,因此模型代码的修改不会把约定承诺的 reject 变成 worker 失败。该错误包含标准的 `Error` 消息和确切的 `toolName`,并有意省略 `ToolFailure.info`、错误代码与 Native 内容。这是一项用于控制流的异常约定,而不是供程序分类的失败联合。 绑定参数与绑定返回值会在不可信 worker 协议的两端重新校验为无损 JSON,且不设字节上限。每个分离后的值在通过结构化克隆跨越边界前,都会编码为扁平的前序 token 流,其传输结构的嵌套深度有界;接收方再以迭代方式重建该值。因此,有效应用数据的嵌套深度既不受 JavaScript 调用栈深度上限限制,也不受特定平台对嵌套结构化克隆施加的上限限制。模块初始化时,worker 会捕获自身 JavaScript 运行域中 `Array.prototype` 和 `Object.prototype` 的引用、仅用于识别其他运行域普通容器原型、可获取原生函数源码的内建函数,以及 JSON 边界用于结构处理和计量的全部内建方法。属性写入使用原型为 null 的属性描述符;内部的数组与集合操作直接调用捕获的方法,不会访问可变的全局或原型槽位。因此,即使模型代码替换 `Object.keys`、`Array.isArray`、集合方法、字符串方法或 `Buffer.byteLength` 等辅助方法,重写内建原型的构造函数槽位,或向 `Object.prototype` 添加形如属性描述符的字段,也不会改变校验、协议传输或字节计量。面向其他运行域的原生函数源码检查仍会拒绝由用户编写、冒充 `Object` 或 `Array` 的构造函数。为保持依赖轻量,运行时 Service Definition 将结构等价类型命名为 `CodeJsonValue`,从而无需依赖会话侧拥有的规范类型;生成的 SDK 和工具 API 则使用 `JsonValue`。这些值不会经过提示词截断、上下文 spill 或持久化。因此,程序可以完整筛选已经采集的搜索、工作流、任务、文件系统与 MCP 值,同时提供方和执行器的采集上限仍会实际生效。 @@ -73,13 +73,13 @@ Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProper ### 持久化、元数据与 spill -嵌套分发在 `tool/code-dispatch` 上记录子调用完整渲染后的 `content`/`isError`,但不会持久化规范值。`tool/result` 继续只持久化渲染后的内容、错误和可选元数据。包含图片的成功最终内容序列还会包装成带来源归属的用户消息,并经外层结果延后;普通会话事件使该模型可见输入可以重建。`SESSION_FORMAT_VERSION` 保持不变(预发布阶段的形状变动不递增版本号),回放也无法重建程序的规范中间值。 +嵌套分发在 `tool/ptc-dispatch` 上记录子调用完整渲染后的 `content`/`isError`,但不会持久化规范值。`tool/result` 继续只持久化渲染后的内容、错误和可选元数据。包含图片的成功最终内容序列还会包装成带来源归属的用户消息,并经外层结果延后;普通会话事件使该模型可见输入可以重建。`SESSION_FORMAT_VERSION` 保持不变(预发布阶段的形状变动不递增版本号),回放也无法重建程序的规范中间值。 不透明的 `exec.parent` token 用于标识嵌套调用。由于这些调用没有直接对应的结果卡片,而且其规范值永远不会进入上下文,展示元数据以及通用或工具自有的 spill 投影都会跳过它们。只有外层 `run_code` 调用会生成一张卡片,并且可能对 post-policy 处理后的最终展示执行 spill;`run_code` 有意既不声明结果展示器,也不声明展示元数据,因此 UI 适配器会通过通用的原始内容回退机制,使用持久化的 `tool/result.content` 补全该卡片。 ## 测试 -编译期测试与快照测试锁定了精确的 `ToolArgsMap`、`ToolOutputMap`、`ToolName`、schema 到 TypeScript 的覆盖范围、特殊名称,以及组装后的 Code Mode 图片转发。注册表与真实 worker 测试覆盖标量、数组、对象和 null 值;字符串原文渲染;缺席的 `undefined`;消费方声明、实际用于拒绝 Promise 的异常类,包括 `ToolCallError`;无效参数与完成值,包括伪装为内建原型的伪造原型;模型代码修改过的 JSON 边界全局对象、原型方法、构造函数槽位,以及继承而来的属性描述符字段;上述修改后的类型化绑定失败;不设上限的大型中间绑定值;嵌套输出落盘抑制;通用含图片上下文延后以及 post-execute 替换/阻止优先级;64 MiB 上限内外的精确计量;日志、值与诊断的组合计量;抛出的超大堆栈;有界失败的输出落盘;不可信对端伪造的流量;以及构建后包的执行。 +编译期测试与快照测试锁定了精确的 `ToolArgsMap`、`ToolOutputMap`、`ToolName`、schema 到 TypeScript 的覆盖范围、特殊名称,以及组装后的 PTC mode 图片转发。注册表与真实 worker 测试覆盖标量、数组、对象和 null 值;字符串原文渲染;缺席的 `undefined`;消费方声明、实际用于拒绝 Promise 的异常类,包括 `ToolCallError`;无效参数与完成值,包括伪装为内建原型的伪造原型;模型代码修改过的 JSON 边界全局对象、原型方法、构造函数槽位,以及继承而来的属性描述符字段;上述修改后的类型化绑定失败;不设上限的大型中间绑定值;嵌套输出落盘抑制;通用含图片上下文延后以及 post-execute 替换/阻止优先级;64 MiB 上限内外的精确计量;日志、值与诊断的组合计量;抛出的超大堆栈;有界失败的输出落盘;不可信对端伪造的流量;以及构建后包的执行。 无密钥的真实 worker 集成测试锁定了自然语言结果无法安全支持的两种句柄工作流。后台 bash 调用返回 job id,外层运行结束,之后的运行再根据该 id 轮询直至任务完成;其他用例分别证明,预先中止不会创建任务、发布后的调用取消会保留任务、前台执行仍与信号耦合,并且由 `job_kill` 负责取消。Cordis 程序会直接读取 active 或 pending 挂载的 id 和 `waitingFor` 字段,按该 id 卸载,并在不解析渲染文本的情况下确认挂载已移除。 @@ -93,13 +93,13 @@ Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProper **静默检查格式化或截断过大的完成值:**不予采纳。把 JSON 值改成字符串既有损又违反类型。显式的 `output-limit` 失败让模型可以选择返回更小的结果,而保留的日志和诊断仍可使用普通的外层 spill 机制。 -**要求每个丰富叶子工具检查 `exec.parent` 并自行延后。** 不予采用,因为这会把叶子工具与 Code Mode 内部机制耦合、重复策略处理,并遗漏未来丰富工具。分发桥接层负责从已经结算的最终结果通用转发。 +**要求每个丰富叶子工具检查 `exec.parent` 并自行延后。** 不予采用,因为这会把叶子工具与 PTC mode 内部机制耦合、重复策略处理,并遗漏未来丰富工具。分发桥接层负责从已经结算的最终结果通用转发。 **把 Native 丰富内容暴露为每个绑定规范值的一部分。** 不予采用,因为规范值是无损 JSON 且由工具定义;附件块是具有持久生命周期语义的模型投影。保持值与投影分离,既能保留类型化程序,也不会从后续模型上下文中丢弃图片。 ## 后果 -Code Mode 程序可以通过稳定值组合工具,无需逆向解析 Native 自然语言。Native 和 Both Mode 保留现有文本与 UI 展示,Code Mode 则获得输出 schema 类型和精确的运行时 JSON。工具作者必须把规范值视为程序化 API,并将仅用于展示的格式化放入渲染器。 +PTC mode 程序可以通过稳定值组合工具,无需逆向解析 Native 自然语言。Native 和 Both Mode 保留现有文本与 UI 展示,PTC mode 则获得输出 schema 类型和精确的运行时 JSON。工具作者必须把规范值视为程序化 API,并将仅用于展示的格式化放入渲染器。 worker 会以嵌套深度有界的扁平协议格式传输数据并执行无损校验,但不会降低中间值的开销,也不会使其具备持久性。外层输出溢出会显式导致运行失败,错误处理则有意由人类引导,而不是依赖带版本的错误代码联合。 @@ -110,7 +110,7 @@ worker 会以嵌套深度有界的扁平协议格式传输数据并执行无损 - 中间规范值仅存在于执行期间,无法用于回放,因为持久事件只存储展示和有界摘要。 - 中间值没有字节上限,可能因值的保留、扁平协议格式副本或结构化克隆开销而耗尽进程或 worker 内存。 - 64 MiB 硬上限只适用于外层可变负载,不计固定的结果封装语法与展示空白;spill 无法恢复超出该上限后被拒绝的字节。 -- 提供方或执行器的采集上限可能在规范值到达 Code Mode 前就已丢弃部分源数据。 +- 提供方或执行器的采集上限可能在规范值到达 PTC mode 前就已丢弃部分源数据。 - 不支持的 MCP 输出 schema 会回退为 `JsonValue`;已准入的 MCP 图片使用通用延后投影,而音频和嵌入资源载荷仍只提供诊断。 - 每个外层 `run_code` 只有一张结果卡片,嵌套调用不会各自生成卡片。 -- Code Mode 失败只暴露 `ToolCallError` 的消息与工具名,不提供程序可用的错误代码联合。 +- PTC mode 失败只暴露 `ToolCallError` 的消息与工具名,不提供程序可用的错误代码联合。 diff --git a/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.i18n.yaml b/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.i18n.yaml index e195ff0f4b..bbdf2836d7 100644 --- a/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.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 .agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.md -2026-07-22-web-multimodal-image-input-and-durable-attachments.md: cc94357aae24bd0ca20ee73488f14de98ffd8ca7 -2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md: 90667c13e2917a77ffd0bcee6386ded690573bcf +2026-07-22-web-multimodal-image-input-and-durable-attachments.md: 135939b536e39db10fe1011501b7a7ce2b0956b2 +2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md: 3207e1f6a7fc5db65f4f4c7a51c2ae765f634cb7 diff --git a/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.md b/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.md index cc94357aae..135939b536 100644 --- a/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.md +++ b/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.md @@ -128,7 +128,7 @@ Pi-AI and the direct DeepSeek adapter resolve `ctx.attachments` at request time, Core supports structured assistant image blocks, but no current production provider route is certified for image output. Any future output-capable adapter must retrieve provider bytes under bounded size and time policy, validate them through the same attachment service, persist them, and only then publish the atomic `ImageBlock`. A URL in assistant Markdown remains text and is never downloaded automatically. -Provider-neutral token estimation does not guess visual pricing from image dimensions; provider-reported usage remains authoritative. ACP advertises image prompts only when its configured exact route and attachment deployment can accept them, persists inline input before publishing the user event, and re-reads committed assistant image references for native ACP image updates. MCP keeps canonical raw blocks for programmatic callers while projecting admitted images to durable core blocks; Code Mode carries any settled image-bearing sub-result through the outer result as logged source-attributed context. +Provider-neutral token estimation does not guess visual pricing from image dimensions; provider-reported usage remains authoritative. ACP advertises image prompts only when its configured exact route and attachment deployment can accept them, persists inline input before publishing the user event, and re-reads committed assistant image references for native ACP image updates. MCP keeps canonical raw blocks for programmatic callers while projecting admitted images to durable core blocks; PTC mode carries any settled image-bearing sub-result through the outer result as logged source-attributed context. Compaction replays the selected conversation prefix, including image references, into the configured summarization route. A visual-capable route uses the same deterministic request versions as ordinary turns. A text-only route receives the same deterministic attachment placeholders as any other LLM request. The synthesized checkpoint remains text-only, and `compaction-basic` rejects image summary output with `UNSUPPORTED_CONTENT`. @@ -159,13 +159,13 @@ Malformed base64, unsupported or mismatched media, truncated image payloads, exc | `packages/client/ui-conversation` | Per-session draft images, attachment rail, user and assistant image controls, and original preview. | | `packages/acp/acp` | Conditional native image capability, atomic inline-image admission, and verified assistant-image delivery. | | `packages/mcp/mcp-client` | Lossless canonical MCP results plus capability-gated durable image projection and explicit diagnostics for unsupported rich blocks. | -| `packages/core/tools` | Generic Code Mode forwarding of settled image-bearing sub-results after the outer result. | +| `packages/core/tools` | Generic PTC mode forwarding of settled image-bearing sub-results after the outer result. | The attachment packages form the interface/implementation side of one capability seam. Composer behavior stays in the conversation object layer, provider conversion stays in adapters, and no change is required in `agent-loop`. ### Implementation -The implemented capability includes shared prepare-once batch admission, provider-independent masters, deterministic request versions, DeepSeek Files reuse, stable crop handles, role-neutral image blocks, Pi-AI and DeepSeek input conversion, durable Web/ACP/MCP ordering, Web upload/read protocol, conditional ACP image support, lossless MCP results with durable image projection, Code Mode rich-result forwarding, bounded Web requests, draft and historical image UI, compaction handling, and keyless assembled coverage. +The implemented capability includes shared prepare-once batch admission, provider-independent masters, deterministic request versions, DeepSeek Files reuse, stable crop handles, role-neutral image blocks, Pi-AI and DeepSeek input conversion, durable Web/ACP/MCP ordering, Web upload/read protocol, conditional ACP image support, lossless MCP results with durable image projection, PTC mode rich-result forwarding, bounded Web requests, draft and historical image UI, compaction handling, and keyless assembled coverage. No compatibility shim is required for the pre-release prompt wire; all call sites and fixtures change with the introducing slice. @@ -201,11 +201,11 @@ Rejected because the core already has the role-neutral `ContentBlock` vocabulary ### Normalize MCP results into core content as the canonical tool value -Rejected because Code Mode and programmatic callers need the complete MCP JSON blocks and optional `structuredContent`; replacing that value with a Native projection would make the bridge lossy. MCP retains the protocol value and prepares a separate model projection, with final post-execute policy remaining authoritative. +Rejected because PTC mode and programmatic callers need the complete MCP JSON blocks and optional `structuredContent`; replacing that value with a Native projection would make the bridge lossy. MCP retains the protocol value and prepares a separate model projection, with final post-execute policy remaining authoritative. ### Perform attachment reads and writes inside synchronous output renderers -Rejected because tool renderers are pure, synchronous, and replayable. MCP prepares image projection during async execution and installs it only at the registry's finalization boundary; ACP performs async admission and output conversion in its transport lifecycle. Code Mode forwarding observes the already settled final content instead of giving individual image tools private parent-token behavior. +Rejected because tool renderers are pure, synchronous, and replayable. MCP prepares image projection during async execution and installs it only at the registry's finalization boundary; ACP performs async admission and output conversion in its transport lifecycle. PTC mode forwarding observes the already settled final content instead of giving individual image tools private parent-token behavior. ## Testing @@ -213,7 +213,7 @@ Rejected because tool renderers are pure, synchronous, and replayable. MCP prepa - Host and protocol tests cover persist-before-event ordering, absence of base64 in logs, session-scoped authorization, capability rejection, upload limits, bounded HTTP request bodies, image-admission/model-selection ordering, text-only queue edits, and text-only request projection. - Client unit tests cover paste and drop, mixed clipboard text, image-only send, draft restoration, ordering, draft/session-scope/application object-URL cleanup, and a deferred historical read that completes after disposal; the keyless assembled built-client lane (`apps/web/tests/image-display.expected.e2e.ts`, `pnpm run test:web`) covers the historical user and assistant galleries over the authorized attachment route, the original-size lightbox, and the composer paste rail. - Adapter and compaction tests cover deterministic Pi-AI request versions, DeepSeek Files upload and reuse, stale-id recovery, text-only projection, recursively nested tool-result images, shared summary request versions, and explicit image-output rejection. -- Attachment, MCP, ACP, and Code Mode tests cover all-member validation before writes, mixed text/image ordering, no inline base64 in durable events, exact route-capability gates, explicit unsupported-content diagnostics, post-execute replacement/block precedence, cancellation during admission, verified assistant-image delivery, and generic nested-image forwarding. A keyless assembled ACP snapshot sends a real inline PNG and pins only its durable reference in the session log. +- Attachment, MCP, ACP, and PTC mode tests cover all-member validation before writes, mixed text/image ordering, no inline base64 in durable events, exact route-capability gates, explicit unsupported-content diagnostics, post-execute replacement/block precedence, cancellation during admission, verified assistant-image delivery, and generic nested-image forwarding. A keyless assembled ACP snapshot sends a real inline PNG and pins only its durable reference in the session log. - Credentialed real-API tests cover the configured Anthropic route and the built-in `deepseek-official` Files path. The DeepSeek test does not use a custom provider entry. - The current production adapter set has no certified image-output route; output-provider certification remains outside version one. diff --git a/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md b/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md index 90667c13e2..3207e1f6a7 100644 --- a/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md +++ b/.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md @@ -128,7 +128,7 @@ Pi-AI 与直接 DeepSeek 适配器都会在请求时解析 `ctx.attachments`, 核心层支持结构化助手图片块,但当前没有任何生产提供方路径通过图片输出认证。未来任何支持输出的适配器都必须在有界的大小和时间策略下获取提供方字节,通过同一个附件服务校验并持久化字节,之后才能以原子方式发布 `ImageBlock`。助手 Markdown 中的 URL 仍是文本,绝不自动下载。 -提供方无关的 token 估算不会根据图片尺寸猜测视觉定价;提供方返回的用量仍是权威值。只有配置的确切路由与附件部署可以接受图片时,ACP(Agent Client Protocol)才公布图片提示词能力;它会在发布用户事件前持久化内联输入,并重新读取已提交的助手图片引用来发送原生 ACP 图片更新。MCP 为程序化调用方保留规范原始块,同时把已准入图片投影为持久核心块;Code Mode 会把任何已经结算且含图片的子结果经外层结果转运为带来源归属且写入日志的上下文。 +提供方无关的 token 估算不会根据图片尺寸猜测视觉定价;提供方返回的用量仍是权威值。只有配置的确切路由与附件部署可以接受图片时,ACP(Agent Client Protocol)才公布图片提示词能力;它会在发布用户事件前持久化内联输入,并重新读取已提交的助手图片引用来发送原生 ACP 图片更新。MCP 为程序化调用方保留规范原始块,同时把已准入图片投影为持久核心块;PTC mode 会把任何已经结算且含图片的子结果经外层结果转运为带来源归属且写入日志的上下文。 压缩会把选定的会话前缀和其中的图片引用回放到已配置的摘要生成路径。支持视觉的路径使用与普通轮次相同的确定性请求版本。纯文本路径接收与其他 LLM 请求相同的确定性附件占位符。合成的检查点仍仅包含文本,`compaction-basic` 会以 `UNSUPPORTED_CONTENT` 拒绝包含图片的摘要输出。 @@ -159,13 +159,13 @@ Pi-AI 与直接 DeepSeek 适配器都会在请求时解析 `ctx.attachments`, | `packages/client/ui-conversation` | 每个会话的草稿图片、附件栏、用户与助手图片控件和原图预览。 | | `packages/acp/acp` | 条件式原生图片能力、原子内联图片准入,以及经过校验的助手图片交付。 | | `packages/mcp/mcp-client` | 无损规范 MCP 结果、经能力门禁的持久图片投影,以及针对不受支持丰富块的明确诊断。 | -| `packages/core/tools` | 在外层结果之后通用转发已经结算且含图片的 Code Mode 子结果。 | +| `packages/core/tools` | 在外层结果之后通用转发已经结算且含图片的 PTC mode 子结果。 | 附件包构成一个能力 seam 的接口与实现侧。输入区行为留在会话对象层,提供方转换留在适配器中,无需修改 `agent-loop`。 ### 实现 -已实现能力包括只准备一次的共享批量准入、与提供方无关的主版本、确定性请求版本、DeepSeek Files 复用、稳定裁剪句柄、角色无关图片块、Pi-AI 和 DeepSeek 输入转换、Web/ACP/MCP 持久化顺序、Web 上传与读取协议、条件式 ACP 图片支持、带持久图片投影的无损 MCP 结果、Code Mode 丰富结果转发、有界 Web 请求、草稿与历史图片 UI、压缩处理,以及组装后的无密钥覆盖。 +已实现能力包括只准备一次的共享批量准入、与提供方无关的主版本、确定性请求版本、DeepSeek Files 复用、稳定裁剪句柄、角色无关图片块、Pi-AI 和 DeepSeek 输入转换、Web/ACP/MCP 持久化顺序、Web 上传与读取协议、条件式 ACP 图片支持、带持久图片投影的无损 MCP 结果、PTC mode 丰富结果转发、有界 Web 请求、草稿与历史图片 UI、压缩处理,以及组装后的无密钥覆盖。 预发布提示词协议不需要兼容包装层;引入相应切片时会同时修改所有调用点和 fixture。 @@ -201,11 +201,11 @@ UI 状态可能陈旧,也无法保护直接 SDK、ACP、回放或未收录模 ### 把 MCP 结果规范化为核心内容,并将其作为规范工具值 -不予采用,因为 Code Mode 和程序化调用方需要完整 MCP JSON 块及可选 `structuredContent`;用 Native 投影替换该值会让桥接有损。MCP 保留协议值,并另行准备模型投影;最终 post-execute 策略仍具有权威性。 +不予采用,因为 PTC mode 和程序化调用方需要完整 MCP JSON 块及可选 `structuredContent`;用 Native 投影替换该值会让桥接有损。MCP 保留协议值,并另行准备模型投影;最终 post-execute 策略仍具有权威性。 ### 在同步输出渲染器中执行附件读写 -不予采用,因为工具渲染器必须纯净、同步且可回放。MCP 在异步执行期间准备图片投影,只在注册表最终化边界安装;ACP 在自己的传输生命周期中执行异步准入和输出转换。Code Mode 转发观察已经结算的最终内容,而不是让各图片工具各自处理私有父 token 行为。 +不予采用,因为工具渲染器必须纯净、同步且可回放。MCP 在异步执行期间准备图片投影,只在注册表最终化边界安装;ACP 在自己的传输生命周期中执行异步准入和输出转换。PTC mode 转发观察已经结算的最终内容,而不是让各图片工具各自处理私有父 token 行为。 ## 测试 @@ -213,7 +213,7 @@ UI 状态可能陈旧,也无法保护直接 SDK、ACP、回放或未收录模 - 宿主与协议测试覆盖先持久化再追加事件的顺序、日志中不含 base64、会话作用域授权、能力拒绝、上传限制、大小受限的 HTTP 请求体、图片准入与模型选择的排序、仅文本的队列编辑,以及纯文本请求投影。 - 客户端单元测试覆盖粘贴与拖放、混合剪贴板文本、仅图片发送、草稿恢复、顺序、草稿、会话作用域和应用层级的对象 URL 清理,以及一项在释放后才完成的延迟历史读取;keyless 的组装后构建产物通道(`apps/web/tests/image-display.expected.e2e.ts`,`pnpm run test:web`)覆盖经授权附件路由渲染的历史用户与助手图片画廊、原图 lightbox,以及 composer 粘贴缩略图条。 - 适配器与压缩测试覆盖确定性 Pi-AI 请求版本、DeepSeek Files 上传与复用、陈旧 ID 恢复、纯文本投影、递归嵌套在工具结果中的图片、共享摘要请求版本,以及明确拒绝图片输出。 -- 附件、MCP、ACP 与 Code Mode 测试覆盖写入前校验全部成员、图文混合顺序、持久事件不含内联 base64、确切路由能力门禁、明确的不支持内容诊断、post-execute 替换/阻止优先级、准入期间取消、经过校验的助手图片交付,以及通用嵌套图片转发。组装后的无密钥 ACP 快照发送真实内联 PNG,并在会话日志中只固定其持久引用。 +- 附件、MCP、ACP 与 PTC mode 测试覆盖写入前校验全部成员、图文混合顺序、持久事件不含内联 base64、确切路由能力门禁、明确的不支持内容诊断、post-execute 替换/阻止优先级、准入期间取消、经过校验的助手图片交付,以及通用嵌套图片转发。组装后的无密钥 ACP 快照发送真实内联 PNG,并在会话日志中只固定其持久引用。 - 需要凭据的实际 API 测试会覆盖配置的 Anthropic 路由和内置 `deepseek-official` Files 路径。DeepSeek 测试不使用自定义提供方条目。 - 当前生产适配器集合没有经过认证的图片输出路由;输出提供方认证仍不在第一版范围内。 diff --git a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md b/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md deleted file mode 100644 index a4b8deee86..0000000000 --- a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: Spilling the durable copy of Code Mode sub-dispatch results - -Status: implemented - -English | [中文](2026-07-26-code-dispatch-log-spill.zh.md) - -> Scope: limiting the `tool/code-dispatch` event's content with the existing spill implementation. The [host foundation note](2026-07-26-code-dispatch-ui-foundation.md) deliberately accepted the unlimited log and deferred spill support to this change; the [live-parallel note](2026-07-26-code-mode-live-parallel-dispatch.md) defines the event pair that this listener processes. - -## Problem - -After full-content dispatch logging was added, a `run_code` program that reads a large file wrote the complete rendered text into the session log without a limit or spill policy, while native results were limited to `maxInlineBytes` before logging. This treated the most likely large results differently: sub-calls are intended for bulk data work, and each affected turn added megabytes to the JSONL. - -## Decision - -**A `tools/code-dispatch-log` waterfall on the registry, with spill policy as its first listener.** - -- **Extension point**: `tools/code-dispatch-log` is a scope-filtered waterfall that the bridge runs over each settled sub-dispatch before appending `tool/code-dispatch`. The bridge receives the registry's private `shapeDispatchLog` invoker as a capability closure in `RunCodeBridgeOptions`; the waterfall is the public contract, and the invoker does not add a service method. If a listener throws, the invoker reports any thrown value safely and uses the original settled content. The `CodeDispatchLog` payload carries the outer execution, the `agent` routing key, the sub-call identity, and the default content: the rendered result projection that a native `tool/result` would carry, while the program receives the structured `value`. A listener can replace only the durable copy, which the model never sees. The listener runs as tracked work outside the program's result path. When more than `maxParallelSubCalls` log tasks are pending, the ordered commit loop waits, so a slow spill backend limits later sub-call starts instead of accumulating unlimited pending I/O. Run settlement still waits for every task inside the open turn. -- **Policy**: `dsh-spill-policy` registers a listener for this event and uses the same replacement code as its model-result listener: the same `maxInlineBytes` limit, preview and locator, within-limit invariant, and best-effort fallback. The spill artifact is labeled `dispatch` under the sub-call id. UIs and replay read its full text through the same path used for spilled native results, so both result kinds render with the same information. -- **One deliberate difference**: the model-result listener skips `read` to prevent a `read → spill → read again` loop. The dispatch-log listener also replaces oversized `read` sub-call content because a log copy is not model context, so that loop cannot occur, and `read` is the tool most likely to produce a large log entry. - -## Alternatives considered - -**Apply a plain byte limit inside the bridge without spill storage.** Rejected: truncation without a locator loses data that replay or UIs may need and restores the less informative "truncated summary" rendering that earlier changes removed. - -**Spill inside the bridge directly by calling `ctx.spillStore` from `code-mode.ts`.** Rejected: the registry would require the spill capability. The waterfall keeps this policy with the other spill decisions and allows compositions to omit it; omitting `maxInlineBytes` still makes the listener a no-op. - -**Reuse `tools/post-execute` for nested calls instead of a new event.** Rejected: post-execute can change the program-facing result, so nested calls deliberately skip it and programs receive complete data. The durable copy needs a separate listener that runs after the program has its value. - -## Consequences - -Code Mode dispatch entries in the session log now have the configured byte limit, and the README's Known Limitations entry about unlimited dispatch logging now points here. Old logs with oversized dispatch content still replay because the event fields are unchanged; only future appends contain less text. The web UI renders spilled sub-call output as preview and locator text through the same path as native results, with no special case. diff --git a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.zh.md b/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.zh.md deleted file mode 100644 index 5e53d76136..0000000000 --- a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.zh.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: 将 Code Mode 子分发结果的持久化副本纳入 spill 机制 - -Status: implemented - -[English](2026-07-26-code-dispatch-log-spill.md) | 中文 - -> 范围:用既有的 spill 实现限制 `tool/code-dispatch` 事件的内容。[宿主侧基础 Agent Note](2026-07-26-code-dispatch-ui-foundation.zh.md) 有意接受了不设上限的日志,并把 spill 支持留到本次更改;[实时并行 Agent Note](2026-07-26-code-mode-live-parallel-dispatch.zh.md) 定义了该监听器处理的事件对。 - -## 问题 - -加入完整内容的分发日志后,读取大文件的 `run_code` 程序会把完整的渲染文本写进会话日志,既没有上限,也不经过 spill 策略;原生结果则会在记录之前限制在 `maxInlineBytes` 以内。两类结果受到不同处理,而为批量数据工作设计的子调用最可能产生巨大结果;每个受影响的轮次都会让 JSONL 增长数 MB。 - -## 决策 - -**在注册表上增设 `tools/code-dispatch-log` waterfall(瀑布式事件),spill 策略作为其第一个监听器。** - -- **扩展点**:`tools/code-dispatch-log` 是一个按作用域过滤的 waterfall,桥接层会在追加 `tool/code-dispatch` 之前,对每个已结算的子分发运行它。桥接层通过 `RunCodeBridgeOptions` 以能力闭包形式接收注册表私有的 `shapeDispatchLog` 调用器;waterfall 是公开约定,该调用器不会增加服务方法。监听器抛出异常时,调用器会安全地报告任意抛出值,并使用原始的已结算内容。`CodeDispatchLog` 载荷包含外层执行、`agent` 路由键、子调用标识和默认内容;默认内容是原生 `tool/result` 会携带的渲染后结果投影,而程序收到结构化 `value`。监听器只能替换持久化副本,模型不会看到这份副本。监听器作为受跟踪任务在程序的返回路径之外运行。待处理日志任务超过 `maxParallelSubCalls` 时,有序提交循环会等待,因此慢速 spill 后端会限制后续子调用启动,而不会无限累积待完成 I/O。run 结算仍会等待开放轮次内的全部任务完成。 -- **策略**:`dsh-spill-policy` 为该事件注册监听器,并复用面向模型结果的监听器所用的替换代码:相同的 `maxInlineBytes` 上限、预览和定位符、不超上限不变式,以及尽力而为回退。spill 产物以 `dispatch` 为标签,记录在子调用 id 名下。UI 与回放通过被 spill 的原生结果所用的同一路径读取全文,因此两类结果会渲染出相同的信息。 -- **一处有意差异**:面向模型结果的监听器跳过 `read`,以防出现 `read → spill → read again` 循环。分发日志监听器也会替换过大的 `read` 子调用内容,因为日志副本不是模型上下文,该循环不会发生,而 `read` 最可能产生巨大的日志条目。 - -## 曾考虑的替代方案 - -**在桥接层内部使用普通字节数上限,不存入 spill。** 否决:没有定位符的截断会丢失回放或 UI 可能需要的数据,还会恢复之前更改已经移除的、信息较少的「截断摘要」渲染。 - -**直接在桥接层内做 spill,即从 `code-mode.ts` 调用 `ctx.spillStore`。** 否决:注册表会要求提供 spill 能力。waterfall 把该策略与其他 spill 决策放在一起,并允许组合不加载它;省略 `maxInlineBytes` 时,该监听器仍不执行任何操作。 - -**让嵌套调用复用 `tools/post-execute`,而不是新增一个事件。** 否决:post-execute 可以修改面向程序的结果,因此嵌套调用有意跳过它,让程序取得完整数据。持久化副本需要一个单独的监听器,在程序取得其值之后运行。 - -## 后果 - -会话日志中的 Code Mode 分发条目现在遵守已配置的字节数上限,README 中关于分发日志不设上限的「已知限制」条目现在指向本篇。携带超大分发内容的旧日志仍可回放,因为事件字段没有变化;只有今后的追加包含更少文本。Web UI 经由与原生结果相同的路径,把被 spill 的子调用输出渲染为预览和定位符文本,不需要特殊处理。 diff --git a/.agents/notes/implemented/feature/2026-07-26-code-mode-chat-subcall-rows.i18n.yaml b/.agents/notes/implemented/feature/2026-07-26-code-mode-chat-subcall-rows.i18n.yaml deleted file mode 100644 index bc71339309..0000000000 --- a/.agents/notes/implemented/feature/2026-07-26-code-mode-chat-subcall-rows.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# 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 .agents/notes/implemented/feature/2026-07-26-code-mode-chat-subcall-rows.md -2026-07-26-code-mode-chat-subcall-rows.md: 4bf608c36de78818b3ab40c6798ee6eafabe99a1 -2026-07-26-code-mode-chat-subcall-rows.zh.md: 6e11f65313fb35dc202cb2cd230884b8862d9f13 diff --git a/.agents/notes/implemented/feature/2026-07-26-code-mode-live-parallel-dispatch.i18n.yaml b/.agents/notes/implemented/feature/2026-07-26-code-mode-live-parallel-dispatch.i18n.yaml deleted file mode 100644 index 7478d79c43..0000000000 --- a/.agents/notes/implemented/feature/2026-07-26-code-mode-live-parallel-dispatch.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# 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 .agents/notes/implemented/feature/2026-07-26-code-mode-live-parallel-dispatch.md -2026-07-26-code-mode-live-parallel-dispatch.md: 998152ee43d93e5190062cb632e20a3123cda4f9 -2026-07-26-code-mode-live-parallel-dispatch.zh.md: e70e23a326e645749fe961d1d55a47fc5753ee75 diff --git a/.agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.i18n.yaml b/.agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.i18n.yaml new file mode 100644 index 0000000000..5b72a7564f --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# 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 .agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.md +2026-07-26-ptc-chat-subcall-rows.md: ab0d796e5c44d4edefaf7be709c272a756043107 +2026-07-26-ptc-chat-subcall-rows.zh.md: c4dfc72a8f29de8c0eaf6b80e0788a6773df3d11 diff --git a/.agents/notes/implemented/feature/2026-07-26-code-mode-chat-subcall-rows.md b/.agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.md similarity index 53% rename from .agents/notes/implemented/feature/2026-07-26-code-mode-chat-subcall-rows.md rename to .agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.md index 4bf608c36d..ab0d796e5c 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-mode-chat-subcall-rows.md +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.md @@ -1,20 +1,20 @@ -# Agent Note: Code Mode chat rendering — sub-calls as native rows under the parent +# Agent Note: PTC mode chat rendering — sub-calls as native rows under the parent Status: implemented -English | [中文](2026-07-26-code-mode-chat-subcall-rows.zh.md) +English | [中文](2026-07-26-ptc-chat-subcall-rows.zh.md) -> Scope: how the web chat view renders a `run_code` turn — the client-side half of the Code Mode UI stack, built on the [host foundation](2026-07-26-code-dispatch-ui-foundation.md) (full-content `tool/code-dispatch`, the required `description` parameter). The [toolview dissolution](../architecture/2026-07-23-toolview-dissolution.md) owns the slot model this rides on. +> Scope: how the web chat view renders a `run_code` turn — the client-side half of the PTC mode UI stack, built on the [host foundation](2026-07-26-ptc-dispatch-ui-foundation.md) (full-content `tool/ptc-dispatch`, the required `description` parameter). The [toolview dissolution](../architecture/2026-07-23-toolview-dissolution.md) owns the slot model this rides on. ## Problem -With Code Mode enabled, the chat view showed one opaque `run_code` row: raw program text as the summary, sub-calls invisible everywhere. The settled product requirement is the opposite: each sub-call must render *identically* to a native tool call — same row components, same custom registrations, same details panel — while the transcript stays honest about the fact that the model made ONE call. +With PTC mode enabled, the chat view showed one opaque `run_code` row: raw program text as the summary, sub-calls invisible everywhere. The settled product requirement is the opposite: each sub-call must render *identically* to a native tool call — same row components, same custom registrations, same details panel — while the transcript stays honest about the fact that the model made ONE call. ## Decision **Sub-calls are standard Tool call blocks attached recursively to their parent outside the surface flow, rendered through the same keyed slot as native rows, and always visible under their parent.** -- **Data layer**: Runtime's `ToolCallTree` folds in-window `tool/code-dispatch-start` and `tool/code-dispatch` events into a private per-parent index, then projects running and settled children onto recursive `ToolCallBlock.subCalls`. Live Session projection and `projectConversationHistory` share that fold; copy-on-write parent arrays and path-copy projection keep unrelated roots and siblings reference-stable. Sub-calls never join `nodes` — the surface flow remains exactly the model-visible turn structure. The events are narrowed structurally at the wire-consumer boundary, which also rejects cyclic parent relationships (dsh-tools' host types cannot enter the client program because the host/client `Context` merges collide). +- **Data layer**: Runtime's `ToolCallTree` folds in-window `tool/ptc-dispatch-start` and `tool/ptc-dispatch` events into a private per-parent index, then projects running and settled children onto recursive `ToolCallBlock.subCalls`. Live Session projection and `projectConversationHistory` share that fold; copy-on-write parent arrays and path-copy projection keep unrelated roots and siblings reference-stable. Sub-calls never join `nodes` — the surface flow remains exactly the model-visible turn structure. The events are narrowed structurally at the wire-consumer boundary, which also rejects cyclic parent relationships (dsh-tools' host types cannot enter the client program because the host/client `Context` merges collide). - **Render layer**: `ChatView` passes each parent with its recursive children through the whole-Tool `'conversation.chat.tool'` seat. ui-tool's `ToolCallTree` renders the parent followed by `[data-subcalls]` nests, and every atomic call dispatches through the same `'tool.call.toolview'` keyed slot with `entryKey = Tool name` and the same `GenericToolCard` fallback. A keyed registration therefore takes over descendant and top-level calls without registration changes. Running parents (`runningCalls`) receive accumulated dispatches in the same recursive block, so child rows stream in during the run. - **`run_code` presentation**: a new `code` row variant (classifier `run_code → code`, `Code` title, `IconCodeOutline16`) summarizes with the model-authored `description` and expands to the program itself (monospace on the markdown code-block fill) rather than the args JSON envelope. - **Details panel**: `materialFor` recursively searches `nodes` and `runningCalls`, so a selected descendant callId resolves to full args and complete output through the identical rendering path as a native settled call. @@ -23,10 +23,10 @@ With Code Mode enabled, the chat view showed one opaque `run_code` row: raw prog **Sub-calls flat in the surface flow (fold them into `nodes`).** Rejected: misrepresents the transcript — the model made one call; nesting under the parent preserves the code↔calls association and keeps the fold's model-visible-order invariant untouched. -**Hidden until the parent row expands.** Rejected by product decision: the sub-calls ARE the story of a Code Mode turn; hiding them re-creates the opacity this feature removes. The parent's expand toggle reveals only the program. +**Hidden until the parent row expands.** Rejected by product decision: the sub-calls ARE the story of a PTC mode turn; hiding them re-creates the opacity this feature removes. The parent's expand toggle reveals only the program. **A dedicated sub-call row component.** Rejected: the whole point is identity with native rows; a parallel component would drift. The nest wrapper (indent + left edge) is the only sub-call-specific chrome. ## Consequences -Custom toolview registrations apply to sub-calls for free — and deliberately: there is no per-registration opt-out short of the component reading its own context, which no current consumer needs. Selection highlighting reaches nested rows through the same `selectedCallId` channel (group membership searches the whole tree). Trajectory/waterfall now draw sub-call spans from the dispatch timing pair ([live parallel dispatch](2026-07-26-code-mode-live-parallel-dispatch.md)); without that timing a waterfall span would be a lie. Fixture turn 64 (`?fixture`) plus the `code-mode-round` browser e2e (recorded real round, keyless replay) pin the full surface; the jsdom and Runtime suites pin slot dispatch, error states, recursive details resolution, history projection, and reference-stable path copying. +Custom toolview registrations apply to sub-calls for free — and deliberately: there is no per-registration opt-out short of the component reading its own context, which no current consumer needs. Selection highlighting reaches nested rows through the same `selectedCallId` channel (group membership searches the whole tree). Trajectory/waterfall now draw sub-call spans from the dispatch timing pair ([live parallel dispatch](2026-07-26-ptc-live-parallel-dispatch.md)); without that timing a waterfall span would be a lie. Fixture turn 64 (`?fixture`) plus the `ptc-round` browser e2e (recorded real round, keyless replay) pin the full surface; the jsdom and Runtime suites pin slot dispatch, error states, recursive details resolution, history projection, and reference-stable path copying. diff --git a/.agents/notes/implemented/feature/2026-07-26-code-mode-chat-subcall-rows.zh.md b/.agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.zh.md similarity index 51% rename from .agents/notes/implemented/feature/2026-07-26-code-mode-chat-subcall-rows.zh.md rename to .agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.zh.md index 6e11f65313..c4dfc72a8f 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-mode-chat-subcall-rows.zh.md +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-chat-subcall-rows.zh.md @@ -1,20 +1,20 @@ -# Agent Note: Code Mode 的 chat 渲染——子调用作为父行之下的原生行 +# Agent Note: PTC mode 的 chat 渲染——子调用作为父行之下的原生行 Status: implemented -[English](2026-07-26-code-mode-chat-subcall-rows.md) | 中文 +[English](2026-07-26-ptc-chat-subcall-rows.md) | 中文 -> 范围:Web chat 视图如何渲染一个 `run_code` 轮次,即 Code Mode UI 栈的客户端侧部分,构建在[宿主侧基础](2026-07-26-code-dispatch-ui-foundation.zh.md)之上(携带完整内容的 `tool/code-dispatch`、必填的 `description` 参数)。本篇所依托的 slot 模型归 [toolview 溶解](../architecture/2026-07-23-toolview-dissolution.zh.md)所有。 +> 范围:Web chat 视图如何渲染一个 `run_code` 轮次,即 PTC mode UI 栈的客户端侧部分,构建在[宿主侧基础](2026-07-26-ptc-dispatch-ui-foundation.zh.md)之上(携带完整内容的 `tool/ptc-dispatch`、必填的 `description` 参数)。本篇所依托的 slot 模型归 [toolview 溶解](../architecture/2026-07-23-toolview-dissolution.zh.md)所有。 ## 问题 -启用 Code Mode 后,chat 视图过去只显示一条不透明的 `run_code` 行:摘要就是原始程序文本,子调用则处处不可见。已敲定的产品要求恰恰相反:每个子调用都必须与原生工具调用渲染得*完全一致*——同样的行组件、同样的自定义注册、同样的详情面板——同时 transcript(文本记录)仍须如实反映模型只发起了一次调用这一事实。 +启用 PTC mode 后,chat 视图过去只显示一条不透明的 `run_code` 行:摘要就是原始程序文本,子调用则处处不可见。已敲定的产品要求恰恰相反:每个子调用都必须与原生工具调用渲染得*完全一致*——同样的行组件、同样的自定义注册、同样的详情面板——同时 transcript(文本记录)仍须如实反映模型只发起了一次调用这一事实。 ## 决策 **子调用是在 surface 流之外递归附着到父级的标准工具调用块,经由与原生行相同的 keyed slot 渲染,并始终显示在父级之下。** -- **数据层**:运行时的 `ToolCallTree` 把窗口内的 `tool/code-dispatch-start` 与 `tool/code-dispatch` 事件折入私有的逐父级索引,再把运行中和已结算的子级投影到递归的 `ToolCallBlock.subCalls` 上。实时会话投影与 `projectConversationHistory` 共享这一折叠过程;逐父级的写时复制数组和路径复制投影让无关根节点与兄弟节点保持引用稳定。子调用永不进入 `nodes`——surface 流始终精确等于模型可见的轮次结构。这些事件在 wire 消费方边界作结构性收窄,该边界也会拒绝成环的父子关系(dsh-tools 的宿主类型无法进入客户端程序,因为宿主端与客户端两侧的 `Context` 声明合并会冲突)。 +- **数据层**:运行时的 `ToolCallTree` 把窗口内的 `tool/ptc-dispatch-start` 与 `tool/ptc-dispatch` 事件折入私有的逐父级索引,再把运行中和已结算的子级投影到递归的 `ToolCallBlock.subCalls` 上。实时会话投影与 `projectConversationHistory` 共享这一折叠过程;逐父级的写时复制数组和路径复制投影让无关根节点与兄弟节点保持引用稳定。子调用永不进入 `nodes`——surface 流始终精确等于模型可见的轮次结构。这些事件在 wire 消费方边界作结构性收窄,该边界也会拒绝成环的父子关系(dsh-tools 的宿主类型无法进入客户端程序,因为宿主端与客户端两侧的 `Context` 声明合并会冲突)。 - **渲染层**:`ChatView` 通过整体工具 seat `'conversation.chat.tool'` 传递每个父调用及其递归子调用。ui-tool 的 `ToolCallTree` 先渲染 parent,再渲染 `[data-subcalls]` 嵌套;每个原子调用都通过同一个 `'tool.call.toolview'` keyed slot,以工具名称作为 `entryKey`,并共用 `GenericToolCard` fallback。一个 keyed 注册因此无需变化即可同时接管任意后代与顶层调用。运行中的 parent(`runningCalls`)在同一个递归块中接收已累积的 dispatch,使 child 行在运行期间实时流入。 - **`run_code` 的呈现**:新增一种 `code` 行变体(分类器映射 `run_code → code`、标题 `Code`、图标 `IconCodeOutline16`),以模型撰写的 `description` 作摘要,展开后显示程序本身(在 markdown 代码块的填充底色上以等宽字体呈现),而非参数的 JSON 封装。 - **详情面板**:`materialFor` 递归搜索 `nodes` 与 `runningCalls`,因此被选中的后代 callId 会经由与已完结的原生调用完全相同的渲染路径,解析出完整参数与完整输出。 @@ -23,10 +23,10 @@ Status: implemented **把子调用平铺进 surface 流(折入 `nodes`)。** 否决:这会歪曲 transcript——模型只发起了一次调用;嵌套在父行之下既保住代码↔调用的关联,也让折叠过程的模型可见顺序不变式原封不动。 -**隐藏子调用,展开父行后才显示。** 由产品决策否决:子调用正是一个 Code Mode 轮次的核心内容;把它们藏起来,等于重新制造出本功能所要消除的那种不透明。父行的展开开关只用于显示程序本身。 +**隐藏子调用,展开父行后才显示。** 由产品决策否决:子调用正是一个 PTC mode 轮次的核心内容;把它们藏起来,等于重新制造出本功能所要消除的那种不透明。父行的展开开关只用于显示程序本身。 **专用的子调用行组件。** 否决:本功能的全部要义就在于与原生行保持同一性;一个平行组件必然漂移。嵌套包装层(缩进 + 左侧边线)是子调用唯一的专属视觉装饰。 ## 后果 -自定义 toolview 注册无需额外改动即可适用于子调用——而且是刻意为之:不存在按注册粒度的退出机制,唯一的出路是组件自行读取自身上下文,而当前没有任何消费方需要这么做。选中高亮经由同一条 `selectedCallId` 通道到达嵌套行(分组归属会搜索整棵树)。trajectory/waterfall 现在依据分发计时事件对([实时并行分发](2026-07-26-code-mode-live-parallel-dispatch.zh.md))绘制子调用 span;缺少计时,waterfall 上的 span 就是在撒谎。fixture(测试前置数据)的轮次 64(`?fixture`),加上 `code-mode-round` 浏览器 e2e(录制的真实轮次、无密钥回放),共同锁定整个界面;jsdom 与运行时测试套件则锁定 slot 分发、错误状态、递归详情解析、历史投影与引用稳定的路径复制。 +自定义 toolview 注册无需额外改动即可适用于子调用——而且是刻意为之:不存在按注册粒度的退出机制,唯一的出路是组件自行读取自身上下文,而当前没有任何消费方需要这么做。选中高亮经由同一条 `selectedCallId` 通道到达嵌套行(分组归属会搜索整棵树)。trajectory/waterfall 现在依据分发计时事件对([实时并行分发](2026-07-26-ptc-live-parallel-dispatch.zh.md))绘制子调用 span;缺少计时,waterfall 上的 span 就是在撒谎。fixture(测试前置数据)的轮次 64(`?fixture`),加上 `ptc-round` 浏览器 e2e(录制的真实轮次、无密钥回放),共同锁定整个界面;jsdom 与运行时测试套件则锁定 slot 分发、错误状态、递归详情解析、历史投影与引用稳定的路径复制。 diff --git a/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-log-spill.i18n.yaml b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-log-spill.i18n.yaml new file mode 100644 index 0000000000..385bfcfaaf --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-log-spill.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# 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 .agents/notes/implemented/feature/2026-07-26-ptc-dispatch-log-spill.md +2026-07-26-ptc-dispatch-log-spill.md: ca88e751bada63229f658907812742175f523137 +2026-07-26-ptc-dispatch-log-spill.zh.md: b115e746c01b5c22cee42ed14e6bdb1c4409694c diff --git a/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-log-spill.md b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-log-spill.md new file mode 100644 index 0000000000..ca88e751ba --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-log-spill.md @@ -0,0 +1,31 @@ +# Agent Note: Spilling the durable copy of PTC mode sub-dispatch results + +Status: implemented + +English | [中文](2026-07-26-ptc-dispatch-log-spill.zh.md) + +> Scope: limiting the `tool/ptc-dispatch` event's content with the existing spill implementation. The [host foundation note](2026-07-26-ptc-dispatch-ui-foundation.md) deliberately accepted the unlimited log and deferred spill support to this change; the [live-parallel note](2026-07-26-ptc-live-parallel-dispatch.md) defines the event pair that this listener processes. + +## Problem + +After full-content dispatch logging was added, a `run_code` program that reads a large file wrote the complete rendered text into the session log without a limit or spill policy, while native results were limited to `maxInlineBytes` before logging. This treated the most likely large results differently: sub-calls are intended for bulk data work, and each affected turn added megabytes to the JSONL. + +## Decision + +**A `tools/ptc-dispatch-log` waterfall on the registry, with spill policy as its first listener.** + +- **Extension point**: `tools/ptc-dispatch-log` is a scope-filtered waterfall that the bridge runs over each settled sub-dispatch before appending `tool/ptc-dispatch`. The bridge receives the registry's private `shapeDispatchLog` invoker as a capability closure in `RunCodeBridgeOptions`; the waterfall is the public contract, and the invoker does not add a service method. If a listener throws, the invoker reports any thrown value safely and uses the original settled content. The `PtcDispatchLog` payload carries the outer execution, the `agent` routing key, the sub-call identity, and the default content: the rendered result projection that a native `tool/result` would carry, while the program receives the structured `value`. A listener can replace only the durable copy, which the model never sees. The listener runs as tracked work outside the program's result path. When more than `maxParallelSubCalls` log tasks are pending, the ordered commit loop waits, so a slow spill backend limits later sub-call starts instead of accumulating unlimited pending I/O. Run settlement still waits for every task inside the open turn. +- **Policy**: `dsh-spill-policy` registers a listener for this event and uses the same replacement code as its model-result listener: the same `maxInlineBytes` limit, preview and locator, within-limit invariant, and best-effort fallback. The spill artifact is labeled `dispatch` under the sub-call id. UIs and replay read its full text through the same path used for spilled native results, so both result kinds render with the same information. +- **One deliberate difference**: the model-result listener skips `read` to prevent a `read → spill → read again` loop. The dispatch-log listener also replaces oversized `read` sub-call content because a log copy is not model context, so that loop cannot occur, and `read` is the tool most likely to produce a large log entry. + +## Alternatives considered + +**Apply a plain byte limit inside the bridge without spill storage.** Rejected: truncation without a locator loses data that replay or UIs may need and restores the less informative "truncated summary" rendering that earlier changes removed. + +**Spill inside the bridge directly by calling `ctx.spillStore` from `ptc.ts`.** Rejected: the registry would require the spill capability. The waterfall keeps this policy with the other spill decisions and allows compositions to omit it; omitting `maxInlineBytes` still makes the listener a no-op. + +**Reuse `tools/post-execute` for nested calls instead of a new event.** Rejected: post-execute can change the program-facing result, so nested calls deliberately skip it and programs receive complete data. The durable copy needs a separate listener that runs after the program has its value. + +## Consequences + +PTC mode dispatch entries in the session log now have the configured byte limit, and the README's Known Limitations entry about unlimited dispatch logging now points here. Old logs with oversized dispatch content still replay because the event fields are unchanged; only future appends contain less text. The web UI renders spilled sub-call output as preview and locator text through the same path as native results, with no special case. diff --git a/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-log-spill.zh.md b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-log-spill.zh.md new file mode 100644 index 0000000000..b115e746c0 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-log-spill.zh.md @@ -0,0 +1,31 @@ +# Agent Note: 将 PTC mode 子分发结果的持久化副本纳入 spill 机制 + +Status: implemented + +[English](2026-07-26-ptc-dispatch-log-spill.md) | 中文 + +> 范围:用既有的 spill 实现限制 `tool/ptc-dispatch` 事件的内容。[宿主侧基础 Agent Note](2026-07-26-ptc-dispatch-ui-foundation.zh.md) 有意接受了不设上限的日志,并把 spill 支持留到本次更改;[实时并行 Agent Note](2026-07-26-ptc-live-parallel-dispatch.zh.md) 定义了该监听器处理的事件对。 + +## 问题 + +加入完整内容的分发日志后,读取大文件的 `run_code` 程序会把完整的渲染文本写进会话日志,既没有上限,也不经过 spill 策略;原生结果则会在记录之前限制在 `maxInlineBytes` 以内。两类结果受到不同处理,而为批量数据工作设计的子调用最可能产生巨大结果;每个受影响的轮次都会让 JSONL 增长数 MB。 + +## 决策 + +**在注册表上增设 `tools/ptc-dispatch-log` waterfall(瀑布式事件),spill 策略作为其第一个监听器。** + +- **扩展点**:`tools/ptc-dispatch-log` 是一个按作用域过滤的 waterfall,桥接层会在追加 `tool/ptc-dispatch` 之前,对每个已结算的子分发运行它。桥接层通过 `RunCodeBridgeOptions` 以能力闭包形式接收注册表私有的 `shapeDispatchLog` 调用器;waterfall 是公开约定,该调用器不会增加服务方法。监听器抛出异常时,调用器会安全地报告任意抛出值,并使用原始的已结算内容。`PtcDispatchLog` 载荷包含外层执行、`agent` 路由键、子调用标识和默认内容;默认内容是原生 `tool/result` 会携带的渲染后结果投影,而程序收到结构化 `value`。监听器只能替换持久化副本,模型不会看到这份副本。监听器作为受跟踪任务在程序的返回路径之外运行。待处理日志任务超过 `maxParallelSubCalls` 时,有序提交循环会等待,因此慢速 spill 后端会限制后续子调用启动,而不会无限累积待完成 I/O。run 结算仍会等待开放轮次内的全部任务完成。 +- **策略**:`dsh-spill-policy` 为该事件注册监听器,并复用面向模型结果的监听器所用的替换代码:相同的 `maxInlineBytes` 上限、预览和定位符、不超上限不变式,以及尽力而为回退。spill 产物以 `dispatch` 为标签,记录在子调用 id 名下。UI 与回放通过被 spill 的原生结果所用的同一路径读取全文,因此两类结果会渲染出相同的信息。 +- **一处有意差异**:面向模型结果的监听器跳过 `read`,以防出现 `read → spill → read again` 循环。分发日志监听器也会替换过大的 `read` 子调用内容,因为日志副本不是模型上下文,该循环不会发生,而 `read` 最可能产生巨大的日志条目。 + +## 曾考虑的替代方案 + +**在桥接层内部使用普通字节数上限,不存入 spill。** 否决:没有定位符的截断会丢失回放或 UI 可能需要的数据,还会恢复之前更改已经移除的、信息较少的「截断摘要」渲染。 + +**直接在桥接层内做 spill,即从 `ptc.ts` 调用 `ctx.spillStore`。** 否决:注册表会要求提供 spill 能力。waterfall 把该策略与其他 spill 决策放在一起,并允许组合不加载它;省略 `maxInlineBytes` 时,该监听器仍不执行任何操作。 + +**让嵌套调用复用 `tools/post-execute`,而不是新增一个事件。** 否决:post-execute 可以修改面向程序的结果,因此嵌套调用有意跳过它,让程序取得完整数据。持久化副本需要一个单独的监听器,在程序取得其值之后运行。 + +## 后果 + +会话日志中的 PTC mode 分发条目现在遵守已配置的字节数上限,README 中关于分发日志不设上限的「已知限制」条目现在指向本篇。携带超大分发内容的旧日志仍可回放,因为事件字段没有变化;只有今后的追加包含更少文本。Web UI 经由与原生结果相同的路径,把被 spill 的子调用输出渲染为预览和定位符文本,不需要特殊处理。 diff --git a/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-ui-foundation.i18n.yaml b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-ui-foundation.i18n.yaml new file mode 100644 index 0000000000..deef5e0a47 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-ui-foundation.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# 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 .agents/notes/implemented/feature/2026-07-26-ptc-dispatch-ui-foundation.md +2026-07-26-ptc-dispatch-ui-foundation.md: d91f914a564654a7e3925caf367478b282ddbdc4 +2026-07-26-ptc-dispatch-ui-foundation.zh.md: 8a26f0a1d4ccd4937b856b1c8f3a8be38d8c4174 diff --git a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-ui-foundation.md b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-ui-foundation.md similarity index 55% rename from .agents/notes/implemented/feature/2026-07-26-code-dispatch-ui-foundation.md rename to .agents/notes/implemented/feature/2026-07-26-ptc-dispatch-ui-foundation.md index 2b919dfb97..d91f914a56 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-ui-foundation.md +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-ui-foundation.md @@ -1,26 +1,26 @@ -# Agent Note: Code Mode UI foundation — run_code description and native-parity dispatch logging +# Agent Note: PTC mode UI foundation — run_code description and native-parity dispatch logging Status: implemented -English | [中文](2026-07-26-code-dispatch-ui-foundation.zh.md) +English | [中文](2026-07-26-ptc-dispatch-ui-foundation.zh.md) -> Scope: the host-side contract changes that let a UI render a Code Mode turn with the same fidelity as native tool calls — the foundation the other Code Mode UI notes build on. The [Code Mode foundation](2026-06-15-code-mode.md) owns the transport design; this note owns the model-visible `description` parameter, the full-content `tool/code-dispatch` payload, and the temporary `DSH_TOOLS_MODE` enablement switch for the `dsh` config tree. +> Scope: the host-side contract changes that let a UI render a PTC mode turn with the same fidelity as native tool calls — the foundation the other PTC mode UI notes build on. The [PTC mode foundation](2026-06-15-ptc.md) owns the transport design; this note owns the model-visible `description` parameter, the full-content `tool/ptc-dispatch` payload, and the temporary `DSH_TOOLS_MODE` enablement switch for the `dsh` config tree. ## Problem -A `run_code` turn was opaque in every product surface. The call card's title was the raw program text — unreadable at row width, and unlike `bash` (whose required `description` labels the card while the command rides the expanded input) there was no model-authored label at all. The `tool/code-dispatch` event carried only a 200-char, cwd-normalized `resultSummary` of each sub-call, so no UI could ever show what a sub-call actually returned: the web conversation view ([chat sub-call rows](2026-07-26-code-mode-chat-subcall-rows.md)) renders sub-calls through the exact components that render native `tool/result` cards, and a bounded summary cannot feed a native-parity card. And the `dsh web` composition had no way to enable Code Mode at all — the `tools` row pinned the schema default and the runtime was absent from the tree. +A `run_code` turn was opaque in every product surface. The call card's title was the raw program text — unreadable at row width, and unlike `bash` (whose required `description` labels the card while the command rides the expanded input) there was no model-authored label at all. The `tool/ptc-dispatch` event carried only a 200-char, cwd-normalized `resultSummary` of each sub-call, so no UI could ever show what a sub-call actually returned: the web conversation view ([chat sub-call rows](2026-07-26-ptc-chat-subcall-rows.md)) renders sub-calls through the exact components that render native `tool/result` cards, and a bounded summary cannot feed a native-parity card. And the `dsh web` composition had no way to enable PTC mode at all — the `tools` row pinned the schema default and the runtime was absent from the tree. ## Decision Three changes, one per obstacle: 1. **`run_code` gains a required `description` parameter** (bash's exact contract: active voice, 5-10 words, shown in the UI; whitespace-only rejected at execute). `presentCall` now titles the card with the description and moves the program to `rawInput`. The prompt-side cost is a few tokens per call; the return is that every surface — TUI card, ACP title, web row — gets a human-readable label without parsing TypeScript. -2. **`tool/code-dispatch` logs the sub-call's complete model-facing outcome** — `content: ContentBlock[]` + `isError`, the `tool/result` vocabulary — replacing `resultSummary` and deleting the summarize/cwd-normalization machinery outright. A UI renders a sub-call through the identical code path as a native result, including error text and non-text blocks. The event stays log-only (`deriveMessages()` ignores it): nothing about model context changes. +2. **`tool/ptc-dispatch` logs the sub-call's complete model-facing outcome** — `content: ContentBlock[]` + `isError`, the `tool/result` vocabulary — replacing `resultSummary` and deleting the summarize/cwd-normalization machinery outright. A UI renders a sub-call through the identical code path as a native result, including error text and non-text blocks. The event stays log-only (`deriveMessages()` ignores it): nothing about model context changes. 3. **`DSH_TOOLS_MODE` env var on the `dsh` config tree** (`native`|`code`|`both`; unset keeps the schema default): the `tools` row reads it via `!!js`, and the worker code runtime is mounted unconditionally (Loader metadata was static when this shipped — no conditional row existed; the later [`disabled` interpolation decision](../architecture/2026-08-11-loader-entry-disabled-interpolation.md) makes one possible but changes nothing here — a native boot only registers the service, workers spawn per run). This is an explicitly temporary configuration hook: per-session tool-presentation selection owned by the web UI is the design goal, and the env var dies when that lands. ## Alternatives considered -**Keep a bounded summary (raised cap, or a cap + `truncated` flag).** Rejected: the stack's settled requirement is that sub-call rows and details render *identically* to native calls; any cap forces a second, degraded render path plus truncation UI. The cost accepted instead: a program that reads a large file logs the rendered content verbatim on the dispatch event — uncapped, outside spill policy, growing the session log by the same bytes. Spill integration for the logged copy shipped as [code-dispatch log spill](2026-07-26-code-dispatch-log-spill.md). +**Keep a bounded summary (raised cap, or a cap + `truncated` flag).** Rejected: the stack's settled requirement is that sub-call rows and details render *identically* to native calls; any cap forces a second, degraded render path plus truncation UI. The cost accepted instead: a program that reads a large file logs the rendered content verbatim on the dispatch event — uncapped, outside spill policy, growing the session log by the same bytes. Spill integration for the logged copy shipped as [ptc-dispatch log spill](2026-07-26-ptc-dispatch-log-spill.md). **A `--tools-mode` CLI flag or profile key.** Deferred, not rejected: the flag grammar suggests permanence, and the profile json is user config — both would harden a seam the per-session design intends to remove. An env var reads as the workaround it is. @@ -28,4 +28,4 @@ Three changes, one per obstacle: ## Consequences -Session format keeps `SESSION_FORMAT_VERSION` 0 (pre-release churn does not bump; old logs with `resultSummary` simply carry an extra unread field and lack `content` — v0 makes no compatibility promise). Existing code-mode snapshot fixtures were re-recorded. Model-visible surface grew: the `run_code` schema (one required parameter) and every code-mode system prompt/tool-schema snapshot changed. The web UI work builds directly on the new event payload; live per-sub-call running state reshaped this event into a dispatch start/end pair ([live parallel dispatch](2026-07-26-code-mode-live-parallel-dispatch.md)). +Session format keeps `SESSION_FORMAT_VERSION` 0 (pre-release churn does not bump; old logs with `resultSummary` simply carry an extra unread field and lack `content` — v0 makes no compatibility promise). Existing ptc snapshot fixtures were re-recorded. Model-visible surface grew: the `run_code` schema (one required parameter) and every ptc system prompt/tool-schema snapshot changed. The web UI work builds directly on the new event payload; live per-sub-call running state reshaped this event into a dispatch start/end pair ([live parallel dispatch](2026-07-26-ptc-live-parallel-dispatch.md)). diff --git a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-ui-foundation.zh.md b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-ui-foundation.zh.md similarity index 57% rename from .agents/notes/implemented/feature/2026-07-26-code-dispatch-ui-foundation.zh.md rename to .agents/notes/implemented/feature/2026-07-26-ptc-dispatch-ui-foundation.zh.md index 33599c5689..8a26f0a1d4 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-dispatch-ui-foundation.zh.md +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-dispatch-ui-foundation.zh.md @@ -1,26 +1,26 @@ -# Agent Note: Code Mode 的 UI 基础——run_code 的 description 参数,以及与原生同等保真的分发日志 +# Agent Note: PTC mode 的 UI 基础——run_code 的 description 参数,以及与原生同等保真的分发日志 Status: implemented -[English](2026-07-26-code-dispatch-ui-foundation.md) | 中文 +[English](2026-07-26-ptc-dispatch-ui-foundation.md) | 中文 -> 范围:让 UI 能以与原生工具调用相同的保真度渲染 Code Mode 轮次的宿主侧约定变更,即其他 Code Mode UI Agent Note 赖以构建的基础。传输设计归 [Code Mode 基础](2026-06-15-code-mode.zh.md)所有;模型可见的 `description` 参数、携带完整内容的 `tool/code-dispatch` 载荷,以及 `dsh` 配置树上临时的 `DSH_TOOLS_MODE` 启用开关,归本篇所有。 +> 范围:让 UI 能以与原生工具调用相同的保真度渲染 PTC mode 轮次的宿主侧约定变更,即其他 PTC mode UI Agent Note 赖以构建的基础。传输设计归 [PTC mode 基础](2026-06-15-ptc.zh.md)所有;模型可见的 `description` 参数、携带完整内容的 `tool/ptc-dispatch` 载荷,以及 `dsh` 配置树上临时的 `DSH_TOOLS_MODE` 启用开关,归本篇所有。 ## 问题 -`run_code` 轮次过去在每个产品界面上都不透明。调用卡片的标题就是原始程序文本,在行宽内无法阅读;而且不同于 `bash`(其必填的 `description` 用作卡片标签,命令本身放在展开后的输入里),`run_code` 完全没有模型撰写的标签。`tool/code-dispatch` 事件过去只携带每个子调用的 `resultSummary`(上限 200 字符、经 cwd 归一化),因此任何 UI 都无从展示子调用实际返回的内容:Web 对话视图([chat 子调用行](2026-07-26-code-mode-chat-subcall-rows.zh.md))会用渲染原生 `tool/result` 卡片的同一批组件来渲染子调用,而有界摘要无法支撑一张与原生同等保真的卡片。同时,`dsh web` 组合此前根本无法启用 Code Mode:`tools` 行钉死在 schema 默认值上,配置树里也完全没有该运行时。 +`run_code` 轮次过去在每个产品界面上都不透明。调用卡片的标题就是原始程序文本,在行宽内无法阅读;而且不同于 `bash`(其必填的 `description` 用作卡片标签,命令本身放在展开后的输入里),`run_code` 完全没有模型撰写的标签。`tool/ptc-dispatch` 事件过去只携带每个子调用的 `resultSummary`(上限 200 字符、经 cwd 归一化),因此任何 UI 都无从展示子调用实际返回的内容:Web 对话视图([chat 子调用行](2026-07-26-ptc-chat-subcall-rows.zh.md))会用渲染原生 `tool/result` 卡片的同一批组件来渲染子调用,而有界摘要无法支撑一张与原生同等保真的卡片。同时,`dsh web` 组合此前根本无法启用 PTC mode:`tools` 行钉死在 schema 默认值上,配置树里也完全没有该运行时。 ## 决策 三项变更,每项对应一个障碍: 1. **`run_code` 新增必填的 `description` 参数**(与 bash 完全相同的约定:主动语态、5-10 个词、展示在 UI 中;仅含空白的取值在执行时被拒绝)。`presentCall` 现在以该 description 作为卡片标题,并把程序文本移入 `rawInput`。提示词侧的成本是每次调用多出几个 token;换来的是每个界面——TUI 卡片、ACP(Agent Client Protocol)标题、Web 行——都无需解析 TypeScript 就能获得可供人阅读的标签。 -2. **`tool/code-dispatch` 记录子调用面向模型的完整结果**(`content: ContentBlock[]` 加 `isError`,即 `tool/result` 的词汇),取代 `resultSummary`,并把摘要与 cwd 归一化机制彻底删除。UI 渲染子调用走的代码路径与渲染原生结果完全相同,包括错误文本和非文本块。该事件仍仅用于日志(`deriveMessages()` 忽略它):模型上下文没有任何变化。 +2. **`tool/ptc-dispatch` 记录子调用面向模型的完整结果**(`content: ContentBlock[]` 加 `isError`,即 `tool/result` 的词汇),取代 `resultSummary`,并把摘要与 cwd 归一化机制彻底删除。UI 渲染子调用走的代码路径与渲染原生结果完全相同,包括错误文本和非文本块。该事件仍仅用于日志(`deriveMessages()` 忽略它):模型上下文没有任何变化。 3. **`dsh` 配置树上的 `DSH_TOOLS_MODE` 环境变量**(`native`|`code`|`both`;未设置时保持 schema 默认值):`tools` 行通过 `!!js` 读取它,worker 代码运行时则无条件挂载(本项交付时 loader 元数据仍是静态的,因此不存在条件行;后来的 [`disabled` 插值决策](../architecture/2026-08-11-loader-entry-disabled-interpolation.zh.md) 让条件行成为可能,但此处不变——native 启动只是注册该服务,worker 要到每次运行时才 spawn)。这是一个明确标注为临时的配置钩子:设计目标是让 Web UI 拥有按会话的工具模式选择,该目标落地后,这个环境变量随即退役。 ## 曾考虑的替代方案 -**保留有界摘要(提高上限,或上限加 `truncated` 标志)。** 否决:本堆叠 PR(Pull Request)链已敲定的要求是,子调用的行与详情必须与原生调用渲染得*完全一致*;任何上限都会强制引入第二条降级的渲染路径,外加截断 UI。转而接受的代价是:读取大文件的程序会把渲染后的内容原样记录在分发事件上,不设上限、位于 spill 策略之外,并以同样的字节数增大会话日志。持久化副本的 spill 集成已作为 [code-dispatch 日志 spill](2026-07-26-code-dispatch-log-spill.zh.md) 交付。 +**保留有界摘要(提高上限,或上限加 `truncated` 标志)。** 否决:本堆叠 PR(Pull Request)链已敲定的要求是,子调用的行与详情必须与原生调用渲染得*完全一致*;任何上限都会强制引入第二条降级的渲染路径,外加截断 UI。转而接受的代价是:读取大文件的程序会把渲染后的内容原样记录在分发事件上,不设上限、位于 spill 策略之外,并以同样的字节数增大会话日志。持久化副本的 spill 集成已作为 [ptc-dispatch 日志 spill](2026-07-26-ptc-dispatch-log-spill.zh.md) 交付。 **一个 `--tools-mode` CLI(命令行界面)标志或 profile 配置键。** 推迟,而非否决:标志语法暗示永久性,profile JSON 又是用户配置;两者都会固化这个 seam,而按会话选择的设计本就打算移除它。环境变量则如实呈现了它权宜之计的本质。 @@ -28,4 +28,4 @@ Status: implemented ## 后果 -会话格式保持 `SESSION_FORMAT_VERSION` 为 0(预发布阶段的变动不递增版本号;携带 `resultSummary` 的旧日志只是多出一个不被读取的字段并缺少 `content`;v0 不作任何兼容性承诺)。既有的 Code Mode 快照 fixture(测试前置数据)已重新录制。模型可见范围扩大了:`run_code` 的 schema(新增一个必填参数)以及每一份 Code Mode 系统提示词/工具 schema 快照都发生了变化。Web UI 工作直接构建在新的事件载荷之上;每个子调用的实时运行状态已把本事件重塑为一对分发 start/end 事件([实时并行分发](2026-07-26-code-mode-live-parallel-dispatch.zh.md))。 +会话格式保持 `SESSION_FORMAT_VERSION` 为 0(预发布阶段的变动不递增版本号;携带 `resultSummary` 的旧日志只是多出一个不被读取的字段并缺少 `content`;v0 不作任何兼容性承诺)。既有的 PTC mode 快照 fixture(测试前置数据)已重新录制。模型可见范围扩大了:`run_code` 的 schema(新增一个必填参数)以及每一份 PTC mode 系统提示词/工具 schema 快照都发生了变化。Web UI 工作直接构建在新的事件载荷之上;每个子调用的实时运行状态已把本事件重塑为一对分发 start/end 事件([实时并行分发](2026-07-26-ptc-live-parallel-dispatch.zh.md))。 diff --git a/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.i18n.yaml b/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.i18n.yaml new file mode 100644 index 0000000000..0ed68d66a7 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# 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 .agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.md +2026-07-26-ptc-live-parallel-dispatch.md: 647142d8bc23de211f2a788468c13c45f2139104 +2026-07-26-ptc-live-parallel-dispatch.zh.md: 7e66cdfa9aec020cd463df984da3ab20f83214e9 diff --git a/.agents/notes/implemented/feature/2026-07-26-code-mode-live-parallel-dispatch.md b/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.md similarity index 73% rename from .agents/notes/implemented/feature/2026-07-26-code-mode-live-parallel-dispatch.md rename to .agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.md index 998152ee43..647142d8bc 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-mode-live-parallel-dispatch.md +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.md @@ -1,27 +1,27 @@ -# Agent Note: Code Mode live dispatch lifecycle and native-contract parallelism +# Agent Note: PTC mode live dispatch lifecycle and native-contract parallelism Status: implemented -English | [中文](2026-07-26-code-mode-live-parallel-dispatch.zh.md) +English | [中文](2026-07-26-ptc-live-parallel-dispatch.zh.md) -> Scope: the `tool/code-dispatch-start` event, per-sub-call running state in the web chat, and the bridge's scheduler reusing the native concurrency contract. Builds on the [host foundation](2026-07-26-code-dispatch-ui-foundation.md) and [chat sub-call rows](2026-07-26-code-mode-chat-subcall-rows.md); the native contract itself is owned by the [parallel tool-call note](2026-07-10-parallel-tool-call-execution.md). +> Scope: the `tool/ptc-dispatch-start` event, per-sub-call running state in the web chat, and the bridge's scheduler reusing the native concurrency contract. Builds on the [host foundation](2026-07-26-ptc-dispatch-ui-foundation.md) and [chat sub-call rows](2026-07-26-ptc-chat-subcall-rows.md); the native contract itself is owned by the [parallel tool-call note](2026-07-10-parallel-tool-call-execution.md). ## Problem -Two gaps remained after the host foundation and chat sub-call rows shipped. Sub-call rows appeared only when each dispatch *settled* — while one ran, the UI showed nothing for it, so a slow sub-call read as a stalled parent. And the bridge serialized every binding call ("even `Promise.all` executes one at a time"), a placeholder from before tools carried concurrency metadata: `isConcurrencySafe` now exists, the loop scheduler already runs native siblings in bounded pools, and a Code Mode program awaiting three independent reads paid 3× the latency the native path would. +Two gaps remained after the host foundation and chat sub-call rows shipped. Sub-call rows appeared only when each dispatch *settled* — while one ran, the UI showed nothing for it, so a slow sub-call read as a stalled parent. And the bridge serialized every binding call ("even `Promise.all` executes one at a time"), a placeholder from before tools carried concurrency metadata: `isConcurrencySafe` now exists, the loop scheduler already runs native siblings in bounded pools, and a PTC mode program awaiting three independent reads paid 3× the latency the native path would. ## Decision **One lifecycle pair, one scheduling contract, shared with native.** -- **Event pair**: `tool/code-dispatch-start` (parent/sub ids, name, normalized args) is appended when the scheduler actually starts a call — not at submission, so a queued call abandoned by run settlement logs nothing. The existing `tool/code-dispatch` settles the pair (same `subCallId`); every started call settles exactly once (aborts settle as `isError` outcomes through the pipeline). Timing = the two events' `time` fields. Both stay log-only; model context is untouched; format stays v0. +- **Event pair**: `tool/ptc-dispatch-start` (parent/sub ids, name, normalized args) is appended when the scheduler actually starts a call — not at submission, so a queued call abandoned by run settlement logs nothing. The existing `tool/ptc-dispatch` settles the pair (same `subCallId`); every started call settles exactly once (aborts settle as `isError` outcomes through the pipeline). Timing = the two events' `time` fields. Both stay log-only; model context is untouched; format stays v0. - **Bridge scheduler**: submitted calls are classified at start time via `registry.executionMode` (the SAME fail-closed `isConcurrencySafe` contract the loop uses) and start strictly in submission order. One single-lane driver owns every ORDERED stage — the start append, `prepare` (pre-execute/guards), the head-of-line `finalize`/`finish` commit (post-execute + context deferral + settle append) — so ordered policy stages never overlap each other and only the around-dispatch/body stage runs concurrently, exactly the native loop's sequencing (`fillPool` awaits `startCall` then `commitReady`). Consecutive parallel-classified calls overlap up to `maxParallelSubCalls` (a `Config` field validated by the Loader schema AND re-validated at direct construction, default 10 — the loop scheduler's own default; `1` restores serial dispatch); an exclusive call drains the pool, runs alone, and holds its barrier until its COMMIT completes (post-execute included), like a native exclusive group. Run settlement aborts in-flight dispatches and abandons queued-unstarted ones (binding rejection, no events), then drains to quiescence — including a commit already mid-flight when the program returned — before the outer result closes the turn. - **Client**: Runtime's `ToolCallTree` stores a start event as a `RunningToolCall` child and projects it through the parent's recursive `subCalls` (rows derive the running ring from that shape, exactly as for native in-flight calls). Its settle replaces the private-index entry in place, preserving start order under parallel completion and carrying the start's `time` as `callTime` (duration source). A settle with no observed start (window cut mid-pair, or a pre-start-event log) appends directly, so old logs keep rendering. -- **SDK prompt**: the model-facing "calls execute sequentially" sentence is replaced with the true contract (independent safe calls may overlap under `Promise.all`; dependent work sequences with `await`) — a model-visible change, re-recorded across every code-mode snapshot. +- **SDK prompt**: the model-facing "calls execute sequentially" sentence is replaced with the true contract (independent safe calls may overlap under `Promise.all`; dependent work sequences with `await`) — a model-visible change, re-recorded across every ptc snapshot. ## Alternatives considered -**Unrestricted parallelism (let `Promise.all` overlap everything).** Rejected: writes could race; the native scheduler exists precisely because the tool, not the caller, owns the safety claim. One concurrency vocabulary across native and Code Mode was the settled requirement. +**Unrestricted parallelism (let `Promise.all` overlap everything).** Rejected: writes could race; the native scheduler exists precisely because the tool, not the caller, owns the safety claim. One concurrency vocabulary across native and PTC mode was the settled requirement. **Emit the start event at submission instead of pool entry.** Rejected: a submission-time start would show queued-but-never-run calls as "running" and would force a third "abandoned" terminal event to reconcile the log. Start-at-entry keeps the invariant *started ⇔ settles exactly once* and needs no third event. @@ -29,4 +29,4 @@ Two gaps remained after the host foundation and chat sub-call rows shipped. Sub- ## Consequences -Programs get native-grade latency for independent reads with no new model-side API — `Promise.all` simply works better, and prompt guidance changed accordingly. The web UI shows per-sub-call running rings live (fixture emits start/settle pairs; jsdom pins the running shape; the runtime spec pins in-place settlement, out-of-order completion, and callTime pairing). Trajectory/waterfall sub-call spans draw truthful timing from the pair. Spill bounding ([code-dispatch log spill](2026-07-26-code-dispatch-log-spill.md)) inherits the settle event as its single bounding point. +Programs get native-grade latency for independent reads with no new model-side API — `Promise.all` simply works better, and prompt guidance changed accordingly. The web UI shows per-sub-call running rings live (fixture emits start/settle pairs; jsdom pins the running shape; the runtime spec pins in-place settlement, out-of-order completion, and callTime pairing). Trajectory/waterfall sub-call spans draw truthful timing from the pair. Spill bounding ([ptc-dispatch log spill](2026-07-26-ptc-dispatch-log-spill.md)) inherits the settle event as its single bounding point. diff --git a/.agents/notes/implemented/feature/2026-07-26-code-mode-live-parallel-dispatch.zh.md b/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.zh.md similarity index 73% rename from .agents/notes/implemented/feature/2026-07-26-code-mode-live-parallel-dispatch.zh.md rename to .agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.zh.md index e70e23a326..7e66cdfa9a 100644 --- a/.agents/notes/implemented/feature/2026-07-26-code-mode-live-parallel-dispatch.zh.md +++ b/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.zh.md @@ -1,27 +1,27 @@ -# Agent Note: Code Mode 的实时分发生命周期,以及复用原生约定的并行执行 +# Agent Note: PTC mode 的实时分发生命周期,以及复用原生约定的并行执行 Status: implemented -[English](2026-07-26-code-mode-live-parallel-dispatch.md) | 中文 +[English](2026-07-26-ptc-live-parallel-dispatch.md) | 中文 -> 范围:`tool/code-dispatch-start` 事件、Web chat 中每个子调用的运行状态,以及桥接层调度器对原生并发约定的复用。构建在[宿主侧基础](2026-07-26-code-dispatch-ui-foundation.zh.md)与 [chat 子调用行](2026-07-26-code-mode-chat-subcall-rows.zh.md)之上;原生约定本身归[并行工具调用 Agent Note](2026-07-10-parallel-tool-call-execution.zh.md) 所有。 +> 范围:`tool/ptc-dispatch-start` 事件、Web chat 中每个子调用的运行状态,以及桥接层调度器对原生并发约定的复用。构建在[宿主侧基础](2026-07-26-ptc-dispatch-ui-foundation.zh.md)与 [chat 子调用行](2026-07-26-ptc-chat-subcall-rows.zh.md)之上;原生约定本身归[并行工具调用 Agent Note](2026-07-10-parallel-tool-call-execution.zh.md) 所有。 ## 问题 -宿主侧基础与 chat 子调用行交付之后仍留有两个缺口。子调用行过去只在每次分发*结算*后才出现:某次分发运行期间,UI 对它毫无展示,于是一个慢的子调用看上去就像父调用卡住了。而桥接层过去把每一次绑定调用都串行化(「即使 `Promise.all` 也一次只执行一个」),这是工具尚未携带并发元数据时留下的占位实现:如今 `isConcurrencySafe` 已经存在,agent loop(智能体循环)调度器早已在有界并发池中运行原生兄弟调用,而一个等待三个独立读取的 Code Mode 程序,付出的延迟却是原生路径的 3 倍。 +宿主侧基础与 chat 子调用行交付之后仍留有两个缺口。子调用行过去只在每次分发*结算*后才出现:某次分发运行期间,UI 对它毫无展示,于是一个慢的子调用看上去就像父调用卡住了。而桥接层过去把每一次绑定调用都串行化(「即使 `Promise.all` 也一次只执行一个」),这是工具尚未携带并发元数据时留下的占位实现:如今 `isConcurrencySafe` 已经存在,agent loop(智能体循环)调度器早已在有界并发池中运行原生兄弟调用,而一个等待三个独立读取的 PTC mode 程序,付出的延迟却是原生路径的 3 倍。 ## 决策 **一对生命周期事件,一份调度约定,与原生共用。** -- **事件对**:`tool/code-dispatch-start`(父/子 id、名称、规范化参数)在调度器真正启动某个调用时才追加,而非在提交时,因此因 run 结算而被放弃的排队调用不会留下任何日志。既有的 `tool/code-dispatch` 结算该事件对(`subCallId` 相同);每个已启动的调用恰好结算一次(中止也会作为 `isError` 结果经由流水线结算)。计时即这两个事件的 `time` 字段。两个事件仍仅用于日志;模型上下文不受影响;格式保持 v0。 +- **事件对**:`tool/ptc-dispatch-start`(父/子 id、名称、规范化参数)在调度器真正启动某个调用时才追加,而非在提交时,因此因 run 结算而被放弃的排队调用不会留下任何日志。既有的 `tool/ptc-dispatch` 结算该事件对(`subCallId` 相同);每个已启动的调用恰好结算一次(中止也会作为 `isError` 结果经由流水线结算)。计时即这两个事件的 `time` 字段。两个事件仍仅用于日志;模型上下文不受影响;格式保持 v0。 - **桥接层调度器**:已提交的调用在启动那一刻经 `registry.executionMode` 分类(与 loop 所用完全相同、故障时默认判为不安全的 `isConcurrencySafe` 约定),并严格按提交顺序启动。所有有序阶段——start 事件追加、`prepare`(pre-execute/守卫)、队首 `finalize`/`finish` 提交(post-execute + 上下文延迟提交 + settle 事件追加)——由单通道驱动器独占执行,因此有序策略阶段彼此绝不重叠,只有 around-dispatch/工具体阶段并发运行,与原生 loop 的时序完全一致(`fillPool` 先 await `startCall` 再 `commitReady`)。连续被分类为可并行的调用可以重叠执行,上限为 `maxParallelSubCalls`(`Config` 字段,Loader schema 校验之外直接构造时也重新校验,默认值 10,即 loop 调度器自身的默认值;设为 `1` 即恢复串行分发);独占调用则先排空池、独自运行,且其屏障保持到自身提交(含 post-execute)完成为止,与原生独占分组一致。run 结算时会中止仍在运行的分发,并放弃已排队未启动的分发(绑定调用被拒绝,不产生事件),随后排空到完全停稳——包括程序返回时已在途的提交——之后外层结果才结束该轮次。 - **客户端侧**:运行时的 `ToolCallTree` 把 start 事件存为 `RunningToolCall` 子级,并通过父级递归的 `subCalls` 投影出来(行组件从该形状推导出运行指示环,与原生运行中的调用处理完全一致)。其结算事件会原位替换私有索引中的条目,即使并行完成也保持启动顺序不变,并把 start 事件的 `time` 作为 `callTime`(时长来源)带入。未观察到对应 start 的结算事件(窗口切在事件对中间,或日志录制于 start 事件引入之前)会直接追加,因此旧日志仍能照常渲染。 -- **SDK 提示词**:面向模型的「调用按顺序执行」一句替换为真实约定(相互独立的安全调用可以在 `Promise.all` 下重叠执行;相互依赖的工作以 `await` 顺序衔接);这是模型可见的变更,每一份 Code Mode 快照都已重新录制。 +- **SDK 提示词**:面向模型的「调用按顺序执行」一句替换为真实约定(相互独立的安全调用可以在 `Promise.all` 下重叠执行;相互依赖的工作以 `await` 顺序衔接);这是模型可见的变更,每一份 PTC mode 快照都已重新录制。 ## 曾考虑的替代方案 -**不加限制的并行(让 `Promise.all` 重叠一切)。** 否决:写操作可能产生竞态;原生调度器之所以存在,正是因为安全性声明归工具所有,而不归调用方。原生与 Code Mode 使用同一套并发词汇,是已敲定的要求。 +**不加限制的并行(让 `Promise.all` 重叠一切)。** 否决:写操作可能产生竞态;原生调度器之所以存在,正是因为安全性声明归工具所有,而不归调用方。原生与 PTC mode 使用同一套并发词汇,是已敲定的要求。 **在提交时而非入池时发出 start 事件。** 否决:提交即发 start 会把排了队却从未运行的调用显示成「运行中」,还得强行引入第三种「已放弃」终态事件才能使日志自洽。入池才发 start 保住了*已启动 ⇔ 恰好结算一次*这一不变式,且不需要第三种事件。 @@ -29,4 +29,4 @@ Status: implemented ## 后果 -程序不需要任何新的模型侧 API,独立读取就获得了原生级的延迟:`Promise.all` 直接变得更好用,提示词指引也随之修改。Web UI 实时显示每个子调用的运行指示环:fixture(测试前置数据)发出成对的 start/settle 事件;jsdom 锁定运行中形状;运行时测试锁定原位结算、乱序完成与 callTime 配对。trajectory/waterfall 的子调用 span 从这对事件取得如实的计时。spill 边界划定([code-dispatch 日志 spill](2026-07-26-code-dispatch-log-spill.zh.md))则以结算事件作为唯一的边界点。 +程序不需要任何新的模型侧 API,独立读取就获得了原生级的延迟:`Promise.all` 直接变得更好用,提示词指引也随之修改。Web UI 实时显示每个子调用的运行指示环:fixture(测试前置数据)发出成对的 start/settle 事件;jsdom 锁定运行中形状;运行时测试锁定原位结算、乱序完成与 callTime 配对。trajectory/waterfall 的子调用 span 从这对事件取得如实的计时。spill 边界划定([ptc-dispatch 日志 spill](2026-07-26-ptc-dispatch-log-spill.zh.md))则以结算事件作为唯一的边界点。 diff --git a/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.i18n.yaml b/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.i18n.yaml index 3b1f9f9754..b319069d32 100644 --- a/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.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 .agents/notes/implemented/feature/2026-07-28-web-terminal-card.md -2026-07-28-web-terminal-card.md: a607d160f3529b4e7eb94e34be5a0a91a4010619 -2026-07-28-web-terminal-card.zh.md: 4c7fa5d35dfc85ce38db13402e3e56ccfb519584 +2026-07-28-web-terminal-card.md: 656e91a7a5f5e0e312726e1c02caa36cc06aa5e0 +2026-07-28-web-terminal-card.zh.md: cfcca327c1240c5d1b723fefc39e3cf962a8279e diff --git a/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.md b/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.md index a607d160f3..656e91a7a5 100644 --- a/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.md +++ b/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.md @@ -49,7 +49,7 @@ One premise of that split has since weakened: [tool rows stopped being details-p `TerminalBlock` reads only the terminal view's fields, so it stays a pure function of what the render intent carries — no session lookups, replay-safe like the presenters that produce the view. A UI without the terminal capability still gets the bridge's fenced fallback; nothing about the tool's result shape changed. -A `run_code` sub-dispatch does not reach a terminal card on the shipped wire: `session.ts` folds `tool/code-dispatch(-start)` with `callView: null`/`resultView: null`, and the host's `viewFor` presents only top-level `tool/call`/`tool/result`, so a nested bash call keeps the generic flattened form. Both arms are pinned — the resolution path with views injected, and the no-view shape the wire actually delivers — so the gap is recorded rather than implied. Carrying presenter views through the code-dispatch wire is that boundary's own change. +A `run_code` sub-dispatch does not reach a terminal card on the shipped wire: `session.ts` folds `tool/ptc-dispatch(-start)` with `callView: null`/`resultView: null`, and the host's `viewFor` presents only top-level `tool/call`/`tool/result`, so a nested bash call keeps the generic flattened form. Both arms are pinned — the resolution path with views injected, and the no-view shape the wire actually delivers — so the gap is recorded rather than implied. Carrying presenter views through the ptc-dispatch wire is that boundary's own change. Inline rendering is licensed for the terminal intent alone. A future intent that wants it needs its own bound and its own decision, argued against the reason recorded here rather than against the panel-only convention on its own. diff --git a/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.zh.md b/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.zh.md index 4c7fa5d35d..cfcca327c1 100644 --- a/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.zh.md +++ b/.agents/notes/implemented/feature/2026-07-28-web-terminal-card.zh.md @@ -51,7 +51,7 @@ Web client 却对它视而不见。`packages/client/ui-tool/src/client/tool/mode `TerminalBlock` 只读取 terminal 视图携带的字段,因此它始终是渲染意图内容的纯函数——不查会话状态,与产出该视图的 presenter 一样可安全回放。不具备终端能力的 UI 仍从桥接层拿到围栏式回退;工具的结果形态未作任何改动。 -在当前已交付的 wire 上,`run_code` 子派发不会得到终端卡片:`session.ts` 把 `tool/code-dispatch(-start)` 折叠为 `callView: null`/`resultView: null`,而 host 的 `viewFor` 只呈现顶层的 `tool/call`/`tool/result`,因此嵌套的 bash 调用保持通用的压平形式。两条分支都已钉住——注入视图后的解析路径,以及 wire 实际投递的无视图形态——因此这个缺口是被记录下来的,而非暗含的。把 presenter 视图贯穿 code-dispatch wire 属于该边界自身的改动。 +在当前已交付的 wire 上,`run_code` 子派发不会得到终端卡片:`session.ts` 把 `tool/ptc-dispatch(-start)` 折叠为 `callView: null`/`resultView: null`,而 host 的 `viewFor` 只呈现顶层的 `tool/call`/`tool/result`,因此嵌套的 bash 调用保持通用的压平形式。两条分支都已钉住——注入视图后的解析路径,以及 wire 实际投递的无视图形态——因此这个缺口是被记录下来的,而非暗含的。把 presenter 视图贯穿 ptc-dispatch wire 属于该边界自身的改动。 内嵌渲染的许可仅授予 terminal 意图。将来想要内嵌的意图需要有自己的边界与自己的决定,且需针对此处记录的理由来论证,而不是仅针对「只在面板」这条约定本身。 diff --git a/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.i18n.yaml b/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.i18n.yaml index 7adf65e02a..0664c01644 100644 --- a/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.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 .agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.md -2026-07-30-web-read-card-frontend.md: 98e31d4192f7522f9d0e23bce56372a70f8c50b6 -2026-07-30-web-read-card-frontend.zh.md: 50e1ca8d05a38d13de15fdd6cdd713fd1758ffd4 +2026-07-30-web-read-card-frontend.md: ee03e966ce4475625dace7fb4f4bcee9dc4fa9c8 +2026-07-30-web-read-card-frontend.zh.md: 6c26e4f2c3747b142a62c2f490994cac6c56dd99 diff --git a/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.md b/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.md index 98e31d4192..ee03e966ce 100644 --- a/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.md +++ b/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.md @@ -36,7 +36,7 @@ Whole-row collapse/expand (defaulting every tool call to collapsed) is owned by `ui-primitives` gains `ReadBlock` and `highlightLines`; no new runtime dependency (shiki was already present for `CodeBlock`). `ReadBlock` reads only the read view's fields, so it stays a pure function of what the render intent carries — no session lookups, replay-safe like the presenters that produce the view. A UI without the read capability still gets the backend's `content` fallback (the envelope-stripped text) through the generic card, unchanged. -A read row in the Web chat now carries the file content resident, a deliberate density increase over a summary-only row, bounded by the chat cap. A `run_code` sub-dispatch does not reach a read card on the shipped wire for the same reason a nested bash call does not reach a terminal card: `session.ts` folds `tool/code-dispatch(-start)` with `resultView: null`, so a nested read keeps the generic flattened form. +A read row in the Web chat now carries the file content resident, a deliberate density increase over a summary-only row, bounded by the chat cap. A `run_code` sub-dispatch does not reach a read card on the shipped wire for the same reason a nested bash call does not reach a terminal card: `session.ts` folds `tool/ptc-dispatch(-start)` with `resultView: null`, so a nested read keeps the generic flattened form. ## Testing diff --git a/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.zh.md b/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.zh.md index 50e1ca8d05..6c26e4f2c3 100644 --- a/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.zh.md +++ b/.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.zh.md @@ -36,7 +36,7 @@ Status: implemented `ui-primitives` 增加 `ReadBlock` 和 `highlightLines`;没有新的运行时依赖(shiki 已因 `CodeBlock` 存在)。`ReadBlock` 只读取读取视图的字段,因此保持为渲染意图所承载内容的纯函数 —— 无会话查询,与产出该视图的 presenter 一样可安全回放。没有读取能力的 UI 仍通过通用卡片拿到后端的 `content` 回退(剥掉外壳的文本),保持不变。 -Web 聊天里的读取行现在常驻承载文件内容,是相对纯摘要行的一次刻意的密度增加,受聊天上限约束。按已发布的协议格式,`run_code` 子派发不会到达读取卡片,与嵌套 bash 调用到不了终端卡片同因:`session.ts` 把 `tool/code-dispatch(-start)` 折叠为 `resultView: null`,因此嵌套读取保持通用的摊平形式。 +Web 聊天里的读取行现在常驻承载文件内容,是相对纯摘要行的一次刻意的密度增加,受聊天上限约束。按已发布的协议格式,`run_code` 子派发不会到达读取卡片,与嵌套 bash 调用到不了终端卡片同因:`session.ts` 把 `tool/ptc-dispatch(-start)` 折叠为 `resultView: null`,因此嵌套读取保持通用的摊平形式。 ## Testing diff --git a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.i18n.yaml deleted file mode 100644 index b811dee04f..0000000000 --- a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# 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 .agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md -2026-07-31-code-mode-language-dispatch.md: fe298b9a7cf14856f1bb10ac7f02d28ea3b67842 -2026-07-31-code-mode-language-dispatch.zh.md: a9a358e85c433fc406017cb2967fbd52e42bc1d4 diff --git a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml index d00a3fb636..db0e71accf 100644 --- a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.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 .agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md -2026-07-31-even-out-shipped-tool-rosters.md: 8d1af039c99fe6616745340fd1ef78b62e15b0ca -2026-07-31-even-out-shipped-tool-rosters.zh.md: b79486516dd3f7b54e27a3e7870ce42cd84a2ebd +2026-07-31-even-out-shipped-tool-rosters.md: f1ff22fd90e2936c8441d0627d2eb05286f10fbb +2026-07-31-even-out-shipped-tool-rosters.zh.md: a7bb27f4ab66a1ad76b1a2a0852ebbfbc7d3e00a diff --git a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md index 8d1af039c9..f1ff22fd90 100644 --- a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md +++ b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md @@ -52,7 +52,7 @@ Beyond the committed tests, both surfaces were driven against a real key from th **Sandbox the TUI in the same change.** Rejected as a separate decision that does not belong in a roster change: the TUI mounts unrestricted executors, and replacing them alters what an existing surface does rather than what it offers. That decision needs its own evidence — not least because the TUI has no `approval/request` answerer, so an escalation there fails closed instead of prompting. -**Enable Code Mode.** Its trust posture is bash-equivalent by design and its tool calls pass the same `tools/pre-execute` gate as bash, so it is not the same call as the model-code tools above. Rejected here anyway: `both` changes every model-visible request on both surfaces, and `code` replaces the wire rather than adding to it — either is a presentation decision, not a roster one. +**Enable PTC mode.** Its trust posture is bash-equivalent by design and its tool calls pass the same `tools/pre-execute` gate as bash, so it is not the same call as the model-code tools above. Rejected here anyway: `both` changes every model-visible request on both surfaces, and `code` replaces the wire rather than adding to it — either is a presentation decision, not a roster one. **Mount an MCP server by default.** Rejected because a shipped default would have to name one, and any choice spawns a third-party child process on every user's machine outside the sandbox. The dependency ships instead. diff --git a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md index b79486516d..a7bb27f4ab 100644 --- a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md +++ b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md @@ -52,7 +52,7 @@ Status: implemented **在同一次改动里给 TUI 加沙箱。** 不予采纳,因为这是一个不属于工具清单改动的独立决定:TUI 挂的是不受限执行器,替换它们会改变一个既有 surface 做什么,而非它提供什么。这个决定需要自己的证据——尤其因为 TUI 没有 `approval/request` 的应答方,升权请求在那里会 fail-closed,而不是弹出提示。 -**开启 Code Mode。** 它的信任立场按设计与 bash 同级,工具调用要过与 bash 相同的 `tools/pre-execute` 闸门,所以它与上面那些模型写码工具不是同一个判断。在这里仍被否决:`both` 会改变两个 surface 上每一个模型可见请求,而 `code` 是把线路替换而非加一个——两者都是呈现方式的决定,不是工具清单的决定。 +**开启 PTC mode。** 它的信任立场按设计与 bash 同级,工具调用要过与 bash 相同的 `tools/pre-execute` 闸门,所以它与上面那些模型写码工具不是同一个判断。在这里仍被否决:`both` 会改变两个 surface 上每一个模型可见请求,而 `code` 是把线路替换而非加一个——两者都是呈现方式的决定,不是工具清单的决定。 **默认挂一台 MCP 服务器。**否决,因为交付默认值必须点名一台,而任何选择都会在每个用户的机器上、在沙箱之外 spawn 一个第三方子进程。改为交付依赖。 diff --git a/.agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.i18n.yaml new file mode 100644 index 0000000000..b583e43bfc --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# 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 .agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.md +2026-07-31-ptc-language-dispatch.md: c23e7b7ee1ebd85c98a92621721fca22eef48d63 +2026-07-31-ptc-language-dispatch.zh.md: 2b0110631761744a5067e541b4baf2a7d50a4d21 diff --git a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md b/.agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.md similarity index 88% rename from .agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md rename to .agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.md index fe298b9a7c..c23e7b7ee1 100644 --- a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md +++ b/.agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.md @@ -1,25 +1,25 @@ -# Agent Note: Code Mode language dispatch and the Python SDK renderer +# Agent Note: PTC mode language dispatch and the Python SDK renderer Status: implemented -English | [中文](2026-07-31-code-mode-language-dispatch.zh.md) +English | [中文](2026-07-31-ptc-language-dispatch.zh.md) ## Problem -Code Mode generated one SDK flavor: TypeScript. `ToolRuntime` hard-coded `renderToolsSdk` for the `tools:sdk` section and `requireCodeRuntime` rejected any `ctx.codeRuntime.language !== 'typescript'`. Adding a CPython backend means a program's source language is no longer fixed: the same visible tool registry must project a Python SDK when a Python runtime is loaded, and the model-facing `run_code` schema strings ("Execute a Python program …") must match the SDK section's language so the model never sees a TypeScript instruction over a Python runtime. +PTC mode generated one SDK flavor: TypeScript. `ToolRuntime` hard-coded `renderToolsSdk` for the `tools:sdk` section and `requireCodeRuntime` rejected any `ctx.codeRuntime.language !== 'typescript'`. Adding a CPython backend means a program's source language is no longer fixed: the same visible tool registry must project a Python SDK when a Python runtime is loaded, and the model-facing `run_code` schema strings ("Execute a Python program …") must match the SDK section's language so the model never sees a TypeScript instruction over a Python runtime. -This is the tool-facing half of the multi-language Code Mode split; the [code-runtime seam](../../../../packages/code-runtime/code-runtime/README.md) already carries `CodeRuntime.language`. This note owns only how `dsh-tools` dispatches on that field. The backend that implements `language: 'python'` is owned by its own note, delivered separately. +This is the tool-facing half of the multi-language PTC mode split; the [code-runtime seam](../../../../packages/code-runtime/code-runtime/README.md) already carries `CodeRuntime.language`. This note owns only how `dsh-tools` dispatches on that field. The backend that implements `language: 'python'` is owned by its own note, delivered separately. ## Decision Language selection is a lookup on `ctx.codeRuntime.language`, resolved lazily at prompt assembly, against two parallel tables in `dsh-tools`: -- `SDK_RENDERERS` (index.ts) maps a language to its `tools:sdk` renderer — `typescript → renderToolsSdk`, `python → renderToolsSdkPy`. The `tools:sdk` section reads the loaded runtime's language and picks the renderer; `requireCodeRuntime` rejects a `mode: code`/`both` runtime whose language is absent from the table, naming the known languages. -- `RUN_CODE_FLAVORS` (code-mode.ts) maps a language to its two model-facing `run_code` strings (tool `description` and the `code` parameter description), so a language's SDK section and its transport schema always agree. +- `SDK_RENDERERS` (index.ts) maps a language to its `tools:sdk` renderer — `typescript → renderToolsSdk`, `python → renderToolsSdkPy`. The `tools:sdk` section reads the loaded runtime's language and picks the renderer; `requireCodeRuntime` rejects a `mode: ptc`/`both` runtime whose language is absent from the table, naming the known languages. +- `RUN_CODE_FLAVORS` (ptc.ts) maps a language to its two model-facing `run_code` strings (tool `description` and the `code` parameter description), so a language's SDK section and its transport schema always agree. Both tables are read with `Object.hasOwn` before use so a language named `toString`/`constructor` cannot resolve an inherited `Object.prototype` member as a renderer. The two guards differ in reachability: `SDK_RENDERERS`' in-callback guard is unreachable because `requireCodeRuntime` validated the same `const` table earlier in the same callback (it carries a `/* v8 ignore */`), while `RUN_CODE_FLAVORS`' guard is the primary, publicly reachable rejection — any language absent from the flavor table hits it through `run_code`'s language-aware getters, which the public `schemas()` reaches without passing `requireCodeRuntime` first; the test reads one of those getters off the definition directly, under a language absent from both tables. A language present in `SDK_RENDERERS` but not `RUN_CODE_FLAVORS` is drift the shared `CodeSdkLanguage` `satisfies` pins reject at `typecheck`, so it is not an input either guard can see; what the guards still own is a mounted runtime reporting a language absent from both tables. Schema emission reads the runtime through `peekRuntime()` rather than `requireRuntime()`: `undefined` (no runtime mounted, reached by definition readers and `schemas()`, of which the doc-catalog harvest is the only shipped one and none of which feeds a model because assembly passes `requireCodeRuntime` first) degrades to the TypeScript flavor, whereas a mounted unknown language fails loud — this is NOT the silent fallback rejected below, which concerns emitting a wrong-language SDK for a real runtime. Adding a backend language is three parallel edits — a `CodeSdkLanguage` member and the two table entries — plus its renderer and the prose that names the well-known values instead of deriving them (the seam's `dsh-code-runtime` README pair, its `CodeRuntime.language` JSDoc, and the `docs/subsystems/code-runtime.md` pair; this package's own README pair and its `Config.mode` JSDoc — no gate checks any of it), with no `agent-loop` or registry-structure change. -`code-mode.ts` depends only on the runtime Service Definition (`@deepseek-ai/dsh-code-runtime`), never on a concrete backend; dispatch is by `runtime.language` at run time. The tool layer is therefore independent of the Python protocol and backend — it needs only the service's `language` field. +`ptc.ts` depends only on the runtime Service Definition (`@deepseek-ai/dsh-code-runtime`), never on a concrete backend; dispatch is by `runtime.language` at run time. The tool layer is therefore independent of the Python protocol and backend — it needs only the service's `language` field. ### The Python SDK renderer @@ -32,7 +32,7 @@ The standard that cap serves is grammatical validity, and the boundary is delibe ## Alternatives considered - **A `language` config field on `ToolRuntime`.** Deployment would then have two places to name the language (the loaded runtime and the tools config) that can disagree; the loaded runtime is the single source of truth, so the registry reads it rather than duplicating it. -- **Importing the Python backend into `code-mode.ts` to detect it.** That would couple the tool layer to a concrete backend and force the protocol/backend PRs to land first. Runtime dispatch on `language` keeps the layer backend-agnostic and independently shippable. +- **Importing the Python backend into `ptc.ts` to detect it.** That would couple the tool layer to a concrete backend and force the protocol/backend PRs to land first. Runtime dispatch on `language` keeps the layer backend-agnostic and independently shippable. - **A default renderer for an unknown language.** A silent fallback would emit a TypeScript SDK over, e.g., a Ruby runtime — the model would see instructions in the wrong language. Failing loud at assembly is the repository's misconfiguration stance. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.zh.md b/.agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.zh.md similarity index 88% rename from .agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.zh.md rename to .agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.zh.md index a9a358e85c..2b01106317 100644 --- a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.zh.md +++ b/.agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.zh.md @@ -1,25 +1,25 @@ -# Agent Note: Code Mode 语言分发与 Python SDK 渲染器 +# Agent Note: PTC mode 语言分发与 Python SDK 渲染器 Status: implemented -[English](2026-07-31-code-mode-language-dispatch.md) | 中文 +[English](2026-07-31-ptc-language-dispatch.md) | 中文 ## 问题 -Code Mode 只生成一种 SDK 形态:TypeScript。`ToolRuntime` 为 `tools:sdk` 段硬编码了 `renderToolsSdk`,且 `requireCodeRuntime` 会拒绝任何 `ctx.codeRuntime.language !== 'typescript'`。引入 CPython 后端后,程序的源语言不再固定:同一个可见工具注册表在加载 Python 运行时时必须投射出 Python SDK,而面向模型的 `run_code` schema 字符串("Execute a Python program …")也必须与 SDK 段的语言一致,模型才不会在 Python 运行时下看到 TypeScript 指令。 +PTC mode 只生成一种 SDK 形态:TypeScript。`ToolRuntime` 为 `tools:sdk` 段硬编码了 `renderToolsSdk`,且 `requireCodeRuntime` 会拒绝任何 `ctx.codeRuntime.language !== 'typescript'`。引入 CPython 后端后,程序的源语言不再固定:同一个可见工具注册表在加载 Python 运行时时必须投射出 Python SDK,而面向模型的 `run_code` schema 字符串("Execute a Python program …")也必须与 SDK 段的语言一致,模型才不会在 Python 运行时下看到 TypeScript 指令。 -这是多语言 Code Mode 拆分中面向工具的那一半;[代码运行时 seam](../../../../packages/code-runtime/code-runtime/README.zh.md) 已经携带 `CodeRuntime.language`。本 Note 只负责 `dsh-tools` 如何在该字段上分发。实现 `language: 'python'` 的后端由它自己的 Note 负责,单独交付。 +这是多语言 PTC mode 拆分中面向工具的那一半;[代码运行时 seam](../../../../packages/code-runtime/code-runtime/README.zh.md) 已经携带 `CodeRuntime.language`。本 Note 只负责 `dsh-tools` 如何在该字段上分发。实现 `language: 'python'` 的后端由它自己的 Note 负责,单独交付。 ## 决策 语言选择就是对 `ctx.codeRuntime.language` 的查表,在提示词装配时惰性解析,查 `dsh-tools` 里两张平行的表: -- `SDK_RENDERERS`(index.ts)把语言映射到它的 `tools:sdk` 渲染器——`typescript → renderToolsSdk`、`python → renderToolsSdkPy`。`tools:sdk` 段读取所加载运行时的语言并选出渲染器;`requireCodeRuntime` 拒绝其语言不在表中的 `mode: code`/`both` 运行时,并列出已知语言。 -- `RUN_CODE_FLAVORS`(code-mode.ts)把语言映射到它那两条面向模型的 `run_code` 字符串(工具 `description` 与 `code` 参数描述),使一种语言的 SDK 段与它的传输 schema 始终一致。 +- `SDK_RENDERERS`(index.ts)把语言映射到它的 `tools:sdk` 渲染器——`typescript → renderToolsSdk`、`python → renderToolsSdkPy`。`tools:sdk` 段读取所加载运行时的语言并选出渲染器;`requireCodeRuntime` 拒绝其语言不在表中的 `mode: ptc`/`both` 运行时,并列出已知语言。 +- `RUN_CODE_FLAVORS`(ptc.ts)把语言映射到它那两条面向模型的 `run_code` 字符串(工具 `description` 与 `code` 参数描述),使一种语言的 SDK 段与它的传输 schema 始终一致。 两张表在使用前都以 `Object.hasOwn` 读取,这样名为 `toString`/`constructor` 的语言不会把继承自 `Object.prototype` 的成员解析成渲染器。两个守卫的可达性不同:`SDK_RENDERERS` 的回调内守卫不可达,因为 `requireCodeRuntime` 已在同一回调更早处校验过同一张 `const` 表(它带 `/* v8 ignore */`);而 `RUN_CODE_FLAVORS` 的守卫是主要的、可公开到达的拒绝路径——任何缺席 flavor 表的语言都经 `run_code` 的语言感知 getter 到达它,而公共 `schemas()` 抵达那些 getter 时并未先过 `requireCodeRuntime`;测试直读 definition 上的其中一个 getter,用的是对两张表都缺席的语言。「在 `SDK_RENDERERS` 里却不在 `RUN_CODE_FLAVORS` 里」这种漂移已由共享的 `CodeSdkLanguage` `satisfies` 在 `typecheck` 处拒绝,两个守卫都看不到这种输入;它们如今负责的是所挂载运行时报告了一门两张表都缺席的语言。schema 发射通过 `peekRuntime()` 而非 `requireRuntime()` 读取运行时:`undefined`(无运行时,由直读 definition 的读者与 `schemas()` 到达,其中 doc-catalog 采集是唯一已交付的一个,而它们都不会喂给模型,因为组装路径先过 `requireCodeRuntime`)降级到 TypeScript flavor,而挂载了未知语言则 fail loud——这不是下方被否决的静默回退,那指的是为真实运行时发出错误语言的 SDK。新增一门后端语言是三处并列编辑——一个 `CodeSdkLanguage` 成员加两条表项——再加它的渲染器,以及点名已知值而非从中派生的散文(seam 侧的 `dsh-code-runtime` README 双语对、它的 `CodeRuntime.language` JSDoc 与 `docs/subsystems/code-runtime.md` 双语对;本包自己的 README 双语对与它的 `Config.mode` JSDoc,无任何 gate 检查其中任何一处),不动 `agent-loop`,也不动注册表结构。 -`code-mode.ts` 只依赖运行时 Service Definition(`@deepseek-ai/dsh-code-runtime`),绝不依赖具体后端;分发在运行时按 `runtime.language` 进行。因此工具层独立于 Python 协议和后端——它只需要服务的 `language` 字段。 +`ptc.ts` 只依赖运行时 Service Definition(`@deepseek-ai/dsh-code-runtime`),绝不依赖具体后端;分发在运行时按 `runtime.language` 进行。因此工具层独立于 Python 协议和后端——它只需要服务的 `language` 字段。 ### Python SDK 渲染器 @@ -32,7 +32,7 @@ Code Mode 只生成一种 SDK 形态:TypeScript。`ToolRuntime` 为 `tools:sdk ## 考虑过的替代方案 - **在 `ToolRuntime` 上加一个 `language` 配置字段。** 那样部署方就会有两处命名语言(所加载的运行时与 tools 配置)且可能相互矛盾;所加载的运行时是唯一真源,故注册表读取它而不复制它。 -- **把 Python 后端 import 进 `code-mode.ts` 来检测它。** 那会把工具层耦合到具体后端,并迫使协议/后端 PR(Pull Request)先落地。按 `language` 运行时分发使该层保持后端无关、可独立发布。 +- **把 Python 后端 import 进 `ptc.ts` 来检测它。** 那会把工具层耦合到具体后端,并迫使协议/后端 PR(Pull Request)先落地。按 `language` 运行时分发使该层保持后端无关、可独立发布。 - **为未知语言提供默认渲染器。** 静默回退会在比如 Ruby 运行时上发出 TypeScript SDK——模型会看到错误语言的指令。在装配处 fail loud 是本仓库对错误配置的立场。 ## 后果 diff --git a/.agents/notes/implemented/feature/2026-07-31-web-default-search.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-web-default-search.i18n.yaml index a54ef0e945..b023332a1e 100644 --- a/.agents/notes/implemented/feature/2026-07-31-web-default-search.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-31-web-default-search.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 .agents/notes/implemented/feature/2026-07-31-web-default-search.md -2026-07-31-web-default-search.md: efec6e1e94089d3bbd79296ff0eb2cb55ce005b3 -2026-07-31-web-default-search.zh.md: 825f0ee3f899c108039f6a0db59f0dfe2822cb72 +2026-07-31-web-default-search.md: dfd76176aa03741df38f60c6d30116f87ced4106 +2026-07-31-web-default-search.zh.md: 19fe20d7573accbcef45ab4db8c339335e2cc08b diff --git a/.agents/notes/implemented/feature/2026-07-31-web-default-search.md b/.agents/notes/implemented/feature/2026-07-31-web-default-search.md index efec6e1e94..dfd76176aa 100644 --- a/.agents/notes/implemented/feature/2026-07-31-web-default-search.md +++ b/.agents/notes/implemented/feature/2026-07-31-web-default-search.md @@ -34,4 +34,4 @@ The default mount does not create a Web-specific permission policy. `web_search` ## Consequences -Native model requests on every shared-base surface carry the `web_search` schema and search guidance; Web/headless Code Mode exposes the same search capability beneath `run_code`. Search adds a complete auxiliary model call and may use the native server tool multiple times; its exact secret-free request remains reconstructable from the initiating session log. The shipped Web `cordis`, `code`, and `standard` presets additionally expose `web_fetch` with public-address enforcement and no per-call approval. The Web snapshot lane boots the shipped tree, drives a replayed `web_search` call through the real DeepSeek provider against a local Messages fixture, asserts the durable auxiliary request and structured result, and pins the settled browser presentation. Composition smokes pin the shared search roster and per-preset fetch choices; the built composition dump pins the one-minute shipped search budget; provider tests pin missing, stored, and rotated credential behavior plus literal and ambient compatibility. +Native model requests on every shared-base surface carry the `web_search` schema and search guidance; Web/headless PTC mode exposes the same search capability beneath `run_code`. Search adds a complete auxiliary model call and may use the native server tool multiple times; its exact secret-free request remains reconstructable from the initiating session log. The shipped Web `cordis`, `ptc`, and `standard` presets additionally expose `web_fetch` with public-address enforcement and no per-call approval. The Web snapshot lane boots the shipped tree, drives a replayed `web_search` call through the real DeepSeek provider against a local Messages fixture, asserts the durable auxiliary request and structured result, and pins the settled browser presentation. Composition smokes pin the shared search roster and per-preset fetch choices; the built composition dump pins the one-minute shipped search budget; provider tests pin missing, stored, and rotated credential behavior plus literal and ambient compatibility. diff --git a/.agents/notes/implemented/feature/2026-07-31-web-default-search.zh.md b/.agents/notes/implemented/feature/2026-07-31-web-default-search.zh.md index 825f0ee3f8..19fe20d757 100644 --- a/.agents/notes/implemented/feature/2026-07-31-web-default-search.zh.md +++ b/.agents/notes/implemented/feature/2026-07-31-web-default-search.zh.md @@ -10,7 +10,7 @@ Status: implemented ## 决策 -`apps/cli/config/base.cordis.yml` 明确挂载 `dsh-web`,配置 `searchProvider: deepseek-official` 与 `fetchProvider: http`,同时挂载 `dsh-web-search-deepseek`、`dsh-web-fetch-http`,并以 `fetch: false` 和 `searchTimeoutMs: 60000` 挂载 `dsh-tool-web`。因此,共享 base 只会暴露 `web_search`,除非产品 preset 启用抓取;已交付的 Web `cordis`、`code` 与 `standard` preset 会启用抓取。显式提供方 id 使选择不受注册顺序影响,同时个人覆盖层或 `--config` 覆盖层仍可替换或禁用这些配置项。已交付的一分钟预算用于覆盖一次辅助 DeepSeek Messages 请求及服务端检索,同时保持 `dsh-tool-web` 提供方无关的 30 秒默认值不变,以供自定义组合使用。[Web 能力 seam 决策](../architecture/2026-06-24-web-capability-seam.zh.md)负责公开抓取安全策略与 Web preset 默认值。 +`apps/cli/config/base.cordis.yml` 明确挂载 `dsh-web`,配置 `searchProvider: deepseek-official` 与 `fetchProvider: http`,同时挂载 `dsh-web-search-deepseek`、`dsh-web-fetch-http`,并以 `fetch: false` 和 `searchTimeoutMs: 60000` 挂载 `dsh-tool-web`。因此,共享 base 只会暴露 `web_search`,除非产品 preset 启用抓取;已交付的 Web `cordis`、`ptc` 与 `standard` preset 会启用抓取。显式提供方 id 使选择不受注册顺序影响,同时个人覆盖层或 `--config` 覆盖层仍可替换或禁用这些配置项。已交付的一分钟预算用于覆盖一次辅助 DeepSeek Messages 请求及服务端检索,同时保持 `dsh-tool-web` 提供方无关的 30 秒默认值不变,以供自定义组合使用。[Web 能力 seam 决策](../architecture/2026-06-24-web-capability-seam.zh.md)负责公开抓取安全策略与 Web preset 默认值。 DeepSeek 搜索使用与官方会话适配器相同的 `DEEPSEEK_API_KEY` 凭据引用。提供方在每次搜索内部通过可选的 `ctx.credentials` 服务解析该引用;只有未挂载该 seam 的组合才会回退到启动进程的环境变量,非空的 `apiKey` 字面值仍作为程序化配置的最后兜底。因此,由 Web 的 Models 页存储或轮换的密钥无需重启即可用于下一次搜索,提供方也无需保留该值。由于 `WebSearchProvider.available()` 是同步方法,它会将已安装解析器视为本地可用;若动态凭据缺失,操作会以提供方专属错误码 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败,而稳定的工具 schema 仍保持注册。 @@ -34,4 +34,4 @@ DeepSeek 搜索使用与官方会话适配器相同的 `DEEPSEEK_API_KEY` 凭据 ## 后果 -每个共享 base surface 的原生模型请求都会携带 `web_search` schema 与搜索指引;Web/无头 Code Mode 通过 `run_code` 公开相同的搜索能力。搜索会增加一次完整的辅助模型调用,并可能多次使用原生服务器工具;发起会话的日志仍可精确重建其不含密钥的请求。已交付的 Web `cordis`、`code` 与 `standard` preset 还会暴露 `web_fetch`,实施公开地址强制校验且无需逐次审批。Web 快照通道会启动已交付配置树,使用本地 Messages fixture(测试前置数据),经由真实 DeepSeek 提供方驱动一次回放的 `web_search` 调用,断言持久化的辅助请求与结构化结果,并固定最终浏览器呈现。组合冒烟测试会固定共享搜索清单与各 preset 的抓取选择;构建后组合配置的转储固定已交付的一分钟搜索预算;提供方测试固定缺失、已存储及已轮换凭据的行为,以及字面值与环境变量的兼容性。 +每个共享 base surface 的原生模型请求都会携带 `web_search` schema 与搜索指引;Web/无头 PTC 模式 通过 `run_code` 公开相同的搜索能力。搜索会增加一次完整的辅助模型调用,并可能多次使用原生服务器工具;发起会话的日志仍可精确重建其不含密钥的请求。已交付的 Web `cordis`、`ptc` 与 `standard` preset 还会暴露 `web_fetch`,实施公开地址强制校验且无需逐次审批。Web 快照通道会启动已交付配置树,使用本地 Messages fixture(测试前置数据),经由真实 DeepSeek 提供方驱动一次回放的 `web_search` 调用,断言持久化的辅助请求与结构化结果,并固定最终浏览器呈现。组合冒烟测试会固定共享搜索清单与各 preset 的抓取选择;构建后组合配置的转储固定已交付的一分钟搜索预算;提供方测试固定缺失、已存储及已轮换凭据的行为,以及字面值与环境变量的兼容性。 diff --git a/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.i18n.yaml b/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.i18n.yaml index 0122488dac..29480a8bfb 100644 --- a/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.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 .agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.md -2026-08-05-per-agent-tool-presentation.md: 1883a38f8541a524b9bf02628af7db3b015742d2 -2026-08-05-per-agent-tool-presentation.zh.md: fab41b81987f9b02b04f7260ed04893a59121e0c +2026-08-05-per-agent-tool-presentation.md: b85af656040ef365bb6ee6bfcd3d47686180d4d4 +2026-08-05-per-agent-tool-presentation.zh.md: e6d81ae072df905fddaa655da06abbb4f1ab73e6 diff --git a/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.md b/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.md index 1883a38f85..b85af65604 100644 --- a/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.md +++ b/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.md @@ -1,4 +1,4 @@ -# Agent Note: Per-agent tool presentation, and the `code` preset +# Agent Note: Per-agent tool presentation, and the `ptc` preset Status: implemented @@ -6,7 +6,7 @@ English | [中文](2026-08-05-per-agent-tool-presentation.zh.md) ## Problem -Agent presets compose an agent's tools per session, but not the FORM those tools reach the model in. Code Mode — one `run_code` tool plus a generated TypeScript SDK, replacing a call sequence with one program — was a deployment-wide `mode` field on the host's `dsh-tools` row. A deployment either ran every session in Code Mode or none, so the obvious product shape ("代码模式" beside 标准/极简/创造 in the preset picker) had nothing to hang on. +Agent presets compose an agent's tools per session, but not the FORM those tools reach the model in. PTC mode — one `run_code` tool plus a generated TypeScript SDK, replacing a call sequence with one program — was a deployment-wide `mode` field on the host's `dsh-tools` row. A deployment either ran every session in PTC mode or none, so the obvious product shape ("代码模式" beside 标准/极简/创造 in the preset picker) had nothing to hang on. The naive reading of "move tools down to the agent plane" does not work. `ctx.tools` has host-plane consumers that cannot follow it: `dsh-agent-loop` reads the registry's private scheduler seam, `dsh-apiproxy` reads its presenters to render tool cards, and every tool plugin registers into it. By the stack's own rule — a service moves into a preset only when ALL of its consumers move with it — the registry stays where it is. @@ -14,16 +14,16 @@ The naive reading of "move tools down to the agent plane" does not work. `ctx.to Split the registry from its projection. The registry stays host-plane; the **presentation** becomes scope state inside it, alongside the scoped restrictions and guards that already live there. -`ToolRuntime.presentAs(mode)` is scoped-only and mirrors `restrict()`: it writes one cell on the calling scope's `ToolLayer` through `ScopedLayers.effect`, so it unwinds with the scope that declared it. In the shipped Web surface that scope is an agent preset's standing mount — the `code` preset carries the `tool-presentation` row — so one declaration covers every agent joined to that preset, and `modeFor(scope)` takes the nearest declaration on the chain. It resolves against the config `mode`, which becomes the default for scopes declaring nothing rather than a process-wide fact. The three reads that decided presentation — the wire schemas, the `run_code` entry in the visibility view, and the generated SDK section — take the scope's mode instead of the service's. +`ToolRuntime.presentAs(mode)` is scoped-only and mirrors `restrict()`: it writes one cell on the calling scope's `ToolLayer` through `ScopedLayers.effect`, so it unwinds with the scope that declared it. In the shipped Web surface that scope is an agent preset's standing mount — the `ptc` preset carries the `tool-presentation` row — so one declaration covers every agent joined to that preset, and `modeFor(scope)` takes the nearest declaration on the chain. It resolves against the config `mode`, which becomes the default for scopes declaring nothing rather than a process-wide fact. The three reads that decided presentation — the wire schemas, the `run_code` entry in the visibility view, and the generated SDK section — take the scope's mode instead of the service's. Two consequences fell out and are load-bearing: - **`run_code` is appended per scope.** Previously the transport entered every view whenever the transport existed. Per-agent, a native agent must not find `run_code` in its dispatch table because some other agent in the process presents it — so the append is conditional on that scope's own mode, and the transport is built lazily on first need. -- **The reserved name is now unconditional.** `run_code` was rejected as a registration only while a code mode was configured. Any agent may now select a code mode, so a name that was free to take under a native deployment would become a collision the moment a preset mounted. +- **The reserved name is now unconditional.** `run_code` was rejected as a registration only while a PTC mode was configured. Any agent may now select a PTC mode, so a name that was free to take under a native deployment would become a collision the moment a preset mounted. -The SDK prompt section is registered globally by a code-mode deployment (unchanged) and additionally per scope by `presentAs`, where it shadows by name. Its body renders empty for a native scope, which the prompt renderer drops — that is what keeps an agent opting OUT of a code-mode deployment free of an SDK section. +The SDK prompt section is registered globally by a ptc deployment (unchanged) and additionally per scope by `presentAs`, where it shadows by name. Its body renders empty for a native scope, which the prompt renderer drops — that is what keeps an agent opting OUT of a ptc deployment free of an SDK section. -The preset expresses the choice through one row, `@deepseek-ai/dsh-agent-tool-presentation`, whose whole body is a `presentAs` call. A code mode waits for `ctx.codeRuntime` through `ctx.inject` rather than assuming it: the runtime is host-plane, and a pending row is what `dsh-agent-presets` already reports as an unusable mount, naming the row — so a preset selecting Code Mode against a runtime-less deployment fails where an operator can act. +The preset expresses the choice through one row, `@deepseek-ai/dsh-agent-tool-presentation`, whose whole body is a `presentAs` call. A PTC mode waits for `ctx.codeRuntime` through `ctx.inject` rather than assuming it: the runtime is host-plane, and a pending row is what `dsh-agent-presets` already reports as an unusable mount, naming the row — so a preset selecting PTC mode against a runtime-less deployment fails where an operator can act. ## Alternatives considered @@ -41,6 +41,6 @@ The preset expresses the choice through one row, `@deepseek-ai/dsh-agent-tool-pr Two sessions in one process can now present differently, so "which tools does the model see" is no longer answerable from the deployment config alone; it requires the agent. Every diagnostic that quotes a mode now quotes the scope's, not the service's. -`ctx.tools.schemas(agent)` remains the agent's CAPABILITY catalog and is unchanged by presentation — only the assembly's tools collapse. Tests asserting what the model receives must read the assembly; `web-agent-presets.spec.ts` asserts both sides of that distinction for the shipped `code` preset. +`ctx.tools.schemas(agent)` remains the agent's CAPABILITY catalog and is unchanged by presentation — only the assembly's tools collapse. Tests asserting what the model receives must read the assembly; `web-agent-presets.spec.ts` asserts both sides of that distinction for the shipped `ptc` preset. -The shipped roster is four presets (标准/代码/极简/创造), so any golden listing them moves. A deployment that composes no code runtime can compose no code-mode preset; the shipped Web overlay carries one, the base composition does not. +The shipped roster is four presets (标准/代码/极简/创造), so any golden listing them moves. A deployment that composes no code runtime can compose no ptc preset; the shipped Web overlay carries one, the base composition does not. diff --git a/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.zh.md b/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.zh.md index fab41b8198..e6d81ae072 100644 --- a/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.zh.md +++ b/.agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.zh.md @@ -1,4 +1,4 @@ -# Agent Note: 按 agent 的工具呈现方式,以及 `code` 预设 +# Agent Note: 按 agent 的工具呈现方式,以及 `ptc` 预设 Status: implemented @@ -6,7 +6,7 @@ Status: implemented ## 问题 -agent preset 已经能按会话组装一个 agent 的工具,却管不了这些工具以何种**形态**抵达模型。Code Mode——一个 `run_code` 工具加一份生成的 TypeScript SDK,用一段程序替代一串调用——此前是宿主 `dsh-tools` 那一行上的部署级 `mode` 字段。一个部署要么所有会话都跑 Code Mode,要么一个都不跑,于是那个显而易见的产品形态(预设选择器里「代码模式」与标准/极简/创造并列)无处安放。 +agent preset 已经能按会话组装一个 agent 的工具,却管不了这些工具以何种**形态**抵达模型。PTC mode——一个 `run_code` 工具加一份生成的 TypeScript SDK,用一段程序替代一串调用——此前是宿主 `dsh-tools` 那一行上的部署级 `mode` 字段。一个部署要么所有会话都跑 PTC mode,要么一个都不跑,于是那个显而易见的产品形态(预设选择器里「代码模式」与标准/极简/创造并列)无处安放。 「把 tools 下沉到 agent 平面」这个字面读法行不通。`ctx.tools` 有一批跟不下来的宿主平面消费者:`dsh-agent-loop` 读它私有的调度器 seam,`dsh-apiproxy` 读它的 presenter 来渲染工具卡,每个工具插件都往里注册。按本 stack 自己的规则——只有**所有**消费者一起下沉,服务才能下沉——注册表必须留在原地。 @@ -14,16 +14,16 @@ agent preset 已经能按会话组装一个 agent 的工具,却管不了这些 把注册表和它的投影拆开。注册表留在宿主平面;**呈现方式**变成它内部按 scope 的状态,与已经住在那里的作用域限制和守卫并列。 -`ToolRuntime.presentAs(mode)` 只接受 scoped 上下文,形状照抄 `restrict()`:它通过 `ScopedLayers.effect` 在调用方 scope 的 `ToolLayer` 上写一个单元,因此会随声明它的那个 scope 一起卸载。在随附的 Web 界面里那个 scope 是某个 agent preset 的常驻挂载——`code` preset 携带 `tool-presentation` 行——因此一份声明覆盖加入该 preset 的每个 agent,而 `modeFor(scope)` 取作用域链上最近的那份声明。它与 config 的 `mode` 一并解析,后者于是成为「未作声明的 scope」的默认值,而不再是进程级事实。原先决定呈现方式的三处读取——wire schema、可见性视图里的 `run_code` 条目、以及生成的 SDK 段——改为读取该 scope 的模式,而非服务的。 +`ToolRuntime.presentAs(mode)` 只接受 scoped 上下文,形状照抄 `restrict()`:它通过 `ScopedLayers.effect` 在调用方 scope 的 `ToolLayer` 上写一个单元,因此会随声明它的那个 scope 一起卸载。在随附的 Web 界面里那个 scope 是某个 agent preset 的常驻挂载——`ptc` preset 携带 `tool-presentation` 行——因此一份声明覆盖加入该 preset 的每个 agent,而 `modeFor(scope)` 取作用域链上最近的那份声明。它与 config 的 `mode` 一并解析,后者于是成为「未作声明的 scope」的默认值,而不再是进程级事实。原先决定呈现方式的三处读取——wire schema、可见性视图里的 `run_code` 条目、以及生成的 SDK 段——改为读取该 scope 的模式,而非服务的。 有两个随之而来的结果,且都是承重的: - **`run_code` 按 scope 追加。** 此前只要传输存在,它就进入每一个视图。按 agent 之后,一个 native agent 不能因为进程里别的 agent 呈现了它、就在自己的分发表里看到 `run_code`——因此这次追加以该 scope 自身的模式为条件,传输也改为首次需要时才构建。 -- **保留名现在无条件生效。** `run_code` 此前只在配置了 code 模式时才被拒绝注册。如今任何 agent 都可能选择 code 模式,因此一个在 native 部署下可以随便占用的名字,会在某个 preset 挂载的那一刻变成冲突。 +- **保留名现在无条件生效。** `run_code` 此前只在配置了 PTC 模式时才被拒绝注册。如今任何 agent 都可能选择 PTC 模式,因此一个在 native 部署下可以随便占用的名字,会在某个 preset 挂载的那一刻变成冲突。 -SDK 提示词段由 code 模式的部署全局注册(不变),并由 `presentAs` 额外按 scope 注册一份,后者按名字遮蔽前者。它的正文对 native scope 渲染为空,而提示词渲染器会丢弃空段——正是这一点让「在 code 模式部署下选择退出」的 agent 不带 SDK 段。 +SDK 提示词段由 PTC 模式的部署全局注册(不变),并由 `presentAs` 额外按 scope 注册一份,后者按名字遮蔽前者。它的正文对 native scope 渲染为空,而提示词渲染器会丢弃空段——正是这一点让「在 PTC 模式部署下选择退出」的 agent 不带 SDK 段。 -preset 用一行来表达这个选择:`@deepseek-ai/dsh-agent-tool-presentation`,其全部内容就是一次 `presentAs` 调用。code 类模式通过 `ctx.inject` 等待 `ctx.codeRuntime` 而非假定它存在:运行时在宿主平面,而一个 pending 的行正是 `dsh-agent-presets` 已经会报告的「不可用挂载」并会指名该行——于是在无运行时的部署上选择 Code Mode 的 preset,会在操作者能够动手的地方失败。 +preset 用一行来表达这个选择:`@deepseek-ai/dsh-agent-tool-presentation`,其全部内容就是一次 `presentAs` 调用。code 类模式通过 `ctx.inject` 等待 `ctx.codeRuntime` 而非假定它存在:运行时在宿主平面,而一个 pending 的行正是 `dsh-agent-presets` 已经会报告的「不可用挂载」并会指名该行——于是在无运行时的部署上选择 PTC mode 的 preset,会在操作者能够动手的地方失败。 ## 考虑过的替代方案 @@ -41,6 +41,6 @@ preset 用一行来表达这个选择:`@deepseek-ai/dsh-agent-tool-presentatio 同一进程内的两个会话现在可以有不同的呈现方式,因此「模型看到哪些工具」不再能只凭部署配置回答,必须给出 agent。凡是引用模式的诊断信息,现在引用的都是该 scope 的,而不是服务的。 -`ctx.tools.schemas(agent)` 仍然是该 agent 的**能力**清单,不受呈现方式影响——坍缩的只是 assembly 里的工具。断言「模型收到什么」的测试必须读 assembly;`web-agent-presets.spec.ts` 对随附的 `code` 预设同时断言了这个区分的两侧。 +`ctx.tools.schemas(agent)` 仍然是该 agent 的**能力**清单,不受呈现方式影响——坍缩的只是 assembly 里的工具。断言「模型收到什么」的测试必须读 assembly;`web-agent-presets.spec.ts` 对随附的 `ptc` 预设同时断言了这个区分的两侧。 -随附的名单变成四个预设(标准/代码/极简/创造),因此任何列出它们的 golden 都会变动。未组装 code 运行时的部署无法组装任何 code 模式的 preset;随附的 Web overlay 带了一个,base 组装没有。 +随附的名单变成四个预设(标准/代码/极简/创造),因此任何列出它们的 golden 都会变动。未组装 code 运行时的部署无法组装任何 PTC 模式的 preset;随附的 Web overlay 带了一个,base 组装没有。 diff --git a/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.i18n.yaml b/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.i18n.yaml index b0c70c4df4..4c276c6b0d 100644 --- a/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.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 .agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.md -2026-08-10-minimal-read-image-tool.md: 19306a35fe709a04d94090a62056575b4d51f7bc -2026-08-10-minimal-read-image-tool.zh.md: c7562c433e909d1f81361c0ced56318795e6469e +2026-08-10-minimal-read-image-tool.md: 8880032b2648846df679ea8fa3301d182a95c06b +2026-08-10-minimal-read-image-tool.zh.md: aec34e19fc58037b031f7d4116d2fa664b2b45b5 diff --git a/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.md b/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.md index 19306a35fe..8880032b26 100644 --- a/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.md +++ b/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.md @@ -15,7 +15,7 @@ Both image-reading operations live in `dsh-tool-fs` and publish ordinary logged - **`read_image` reads a filesystem path.** Extension selects the declared PNG/JPEG/WebP/GIF media type; the attachment store's magic-byte and pixel validation stays authoritative. Bytes travel `ctx.fs.stat` → bounded `ctx.fs.readBytes` → `ctx.attachments.saveImage` → `fs/observed`. The tool result contains metadata and an `ImageBlock`. - **`FileSystem.readBytes(target, signal, maxBytes)`** is a new required provider primitive: the byte bound lives at the seam so no backend can buffer an unbounded file, with the stat-size short-circuit and a one-byte-past-cap stream guard against post-stat growth (`FS_TOO_LARGE`). - **Registration is composition-conditional, execution is route-gated.** The tools register only under `ctx.inject(['attachments'], …)`. Before I/O, the strict gate resolves the calling route through `ctx.llm.resolveModelInfo` and requires `image` in `inputModalities`; unknown capability refuses. A text-only route can still consume prior durable images because the shared LLM runtime projects them to placeholders at request assembly. -- **Code Mode forwards the image out-of-band**: a nested dispatch returns the canonical value (execution-local, no image block) and defers a `user`-role context message carrying the envelope and image, so the picture still reaches the next request. +- **PTC mode forwards the image out-of-band**: a nested dispatch returns the canonical value (execution-local, no image block) and defers a `user`-role context message carrying the envelope and image, so the picture still reaches the next request. - **llm-replay models may declare `inputModalities`**, which lets keyless ACP snapshots cover the image-capable result and the text-only refusal. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.zh.md b/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.zh.md index c7562c433e..aec34e19fc 100644 --- a/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.zh.md +++ b/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.zh.md @@ -15,7 +15,7 @@ Status: implemented - **`read_image` 读取文件系统路径。** 扩展名选择声明的 PNG/JPEG/WebP/GIF 媒体类型,附件存储的魔数与像素校验保持权威。字节沿 `ctx.fs.stat` → 有界 `ctx.fs.readBytes` → `ctx.attachments.saveImage` → `fs/observed` 流动。工具结果包含元数据和一个 `ImageBlock`。 - **`FileSystem.readBytes(target, signal, maxBytes)`** 是新的必备提供方原语:字节上限放在 seam 上,任何后端都无法无界缓冲文件;stat 大小先短路,随后的流最多多读一个字节以防 stat 之后的增长(`FS_TOO_LARGE`)。 - **注册随组合条件挂载,执行按路由门禁。** 工具只在 `ctx.inject(['attachments'], …)` 作用域内注册。执行时在 I/O 之前通过 `ctx.llm.resolveModelInfo` 解析调用路由,并要求 `inputModalities` 包含 `image`;能力未知即拒绝。纯文本路由仍可使用此前的持久图片,因为共享 LLM 运行时会在请求组装时把图片投影为占位符。 -- **Code Mode 以带外方式转发图像**:嵌套分派返回规范值(仅限本次执行,不含图像块),并延迟提交一条携带信封和图像的 `user` 角色上下文消息,图片仍会到达下一次请求。 +- **PTC mode 以带外方式转发图像**:嵌套分派返回规范值(仅限本次执行,不含图像块),并延迟提交一条携带信封和图像的 `user` 角色上下文消息,图片仍会到达下一次请求。 - **llm-replay 模型可以声明 `inputModalities`**,因此 keyless ACP 快照可以覆盖支持图片的结果和纯文本拒绝。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.i18n.yaml index c72c3384fa..2061bd878b 100644 --- a/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.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 .agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.md -2026-07-26-web-syntax-highlighting-shiki.md: 967df645db3df0a33f9cd1f34e22e912a348c30e -2026-07-26-web-syntax-highlighting-shiki.zh.md: 3ac1d6bc17954418566b683c2a1d64dfb79f3434 +2026-07-26-web-syntax-highlighting-shiki.md: ba50318d306426d886af25f01eba616ab919d237 +2026-07-26-web-syntax-highlighting-shiki.zh.md: 5533bcb5348db152a8f9466eae7d3d8d9a726162 diff --git a/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.md b/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.md index 967df645db..ba50318d30 100644 --- a/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.md +++ b/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.md @@ -4,7 +4,7 @@ Status: implemented English | [中文](2026-07-26-web-syntax-highlighting-shiki.zh.md) -> Scope: the web client's one syntax-highlighting system — the dependency ruling, the singleton shape, the token-sheet contract, and the consuming surfaces. Fifth PR of the Code Mode UI stack; the [chat sub-call rows note](../feature/2026-07-26-code-mode-chat-subcall-rows.md) shipped the `run_code` program body this exists to make readable. Styling ground rules are owned by [the web styling ruling](2026-07-19-web-styling-system.md). +> Scope: the web client's one syntax-highlighting system — the dependency ruling, the singleton shape, the token-sheet contract, and the consuming surfaces. Fifth PR of the PTC mode UI stack; the [chat sub-call rows note](../feature/2026-07-26-ptc-chat-subcall-rows.md) shipped the `run_code` program body this exists to make readable. Styling ground rules are owned by [the web styling ruling](2026-07-19-web-styling-system.md). ## Problem diff --git a/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.zh.md b/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.zh.md index 3ac1d6bc17..5533bcb534 100644 --- a/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.zh.md +++ b/.agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.zh.md @@ -4,7 +4,7 @@ Status: implemented [English](2026-07-26-web-syntax-highlighting-shiki.md) | 中文 -> 范围:web client 唯一的一套语法高亮体系——依赖裁决、单例形态、token 表约定与各消费表面。本篇是 Code Mode UI 堆叠 PR(Pull Request)链的第五个 PR;[chat 子调用行 Agent Note](../feature/2026-07-26-code-mode-chat-subcall-rows.zh.md)交付了 `run_code` 程序正文,而本体系存在的意义正是让它可读。样式的基本规则由 [Web 样式体系裁决](2026-07-19-web-styling-system.zh.md)规定。 +> 范围:web client 唯一的一套语法高亮体系——依赖裁决、单例形态、token 表约定与各消费表面。本篇是 PTC mode UI 堆叠 PR(Pull Request)链的第五个 PR;[chat 子调用行 Agent Note](../feature/2026-07-26-ptc-chat-subcall-rows.zh.md)交付了 `run_code` 程序正文,而本体系存在的意义正是让它可读。样式的基本规则由 [Web 样式体系裁决](2026-07-19-web-styling-system.zh.md)规定。 ## 问题 diff --git a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.i18n.yaml index b238e98e3e..efabfb37a1 100644 --- a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.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 .agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md -2026-07-20-remove-stdio-and-echo-agents.md: ad8b7fcf5c79b5e3512d7fd908a41e2de47a7bce -2026-07-20-remove-stdio-and-echo-agents.zh.md: be30a44a3bdf807cc5dc6db54617c0b31e28a02b +2026-07-20-remove-stdio-and-echo-agents.md: cb0f53737a89c5324eb3712c22380b02cef7cb77 +2026-07-20-remove-stdio-and-echo-agents.zh.md: f7374fa6fba0b91a1af29f9a934d35d415c3affb diff --git a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md index ad8b7fcf5c..cb0f53737a 100644 --- a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md +++ b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.md @@ -30,7 +30,7 @@ Keyless validation is test-owned. The Headless Loader smoke uses a fixture adapt TUI and Headless Loader coverage run the real app packages in source and built modes. PTY-driven subprocess coverage is reserved for the TUI lifecycle; other entry-point smokes use the one-shot pipe protocol. Headless proves its task/result and tool-call contracts. Generated graphs and repository searches reject stale package, command, leaf, SDK-interface, `createStdioChat`, and `StdioRuntime` references. -The built `dsh` bin rejects a piped TUI launch before Loader boot and points at `dsh --profile headless`; `apps/cli/tests/built-bin.e2e.ts` pins the product one-shot entry under plain Node, including output and invalid arguments. `apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts` pins product persistence, while `apps/cli/tests/headless-shutdown.e2e.ts` owns bounded signal escalation. The headless expected-output test preserves assembled canonical events without creating a second CLI contract. Code Mode runs through the headless profile's `DSH_TOOLS_MODE=code` composition. Time-context integration uses its package-owned Loader composition for two ordered turns, while its package tests own finer elapsed-time behavior. +The built `dsh` bin rejects a piped TUI launch before Loader boot and points at `dsh --profile headless`; `apps/cli/tests/built-bin.e2e.ts` pins the product one-shot entry under plain Node, including output and invalid arguments. `apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts` pins product persistence, while `apps/cli/tests/headless-shutdown.e2e.ts` owns bounded signal escalation. The headless expected-output test preserves assembled canonical events without creating a second CLI contract. PTC mode runs through the headless profile's `DSH_TOOLS_MODE=ptc` composition. Time-context integration uses its package-owned Loader composition for two ordered turns, while its package tests own finer elapsed-time behavior. ## Alternatives considered diff --git a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md index be30a44a3b..f7374fa6fb 100644 --- a/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-20-remove-stdio-and-echo-agents.zh.md @@ -30,7 +30,7 @@ DeepSeek Harness 在 TUI 和 Headless coding agent 之外,还提供了两个 TUI 与 Headless 的 Loader 覆盖以源码和构建产物两种模式运行真实 app 包。由 PTY 驱动的子进程覆盖仅用于 TUI 生命周期;其他入口冒烟测试使用单次管道协议。Headless 验证任务/结果约定和工具调用约定。生成图谱与仓库搜索会拒绝陈旧的包、命令、叶节点、SDK 接口、`createStdioChat` 和 `StdioRuntime` 引用。 -构建后的 `dsh` 可执行文件会在 Loader 启动前拒绝通过管道启动 TUI,并指向 `dsh --profile headless`;`apps/cli/tests/built-bin.e2e.ts` 在普通 Node 下固定产品的一次性入口,包括输出和无效参数。`apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts` 固定产品持久化,`apps/cli/tests/headless-shutdown.e2e.ts` 则负责有界信号升级。headless 预期输出测试保留组装后的规范事件,而不会创建第二套 CLI(命令行界面)约定。Code Mode 通过 headless profile 的 `DSH_TOOLS_MODE=code` 组合运行。时间上下文集成通过包自有 Loader 组合执行两个有序轮次,而更细粒度的耗时行为由时间上下文的包级测试负责。 +构建后的 `dsh` 可执行文件会在 Loader 启动前拒绝通过管道启动 TUI,并指向 `dsh --profile headless`;`apps/cli/tests/built-bin.e2e.ts` 在普通 Node 下固定产品的一次性入口,包括输出和无效参数。`apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts` 固定产品持久化,`apps/cli/tests/headless-shutdown.e2e.ts` 则负责有界信号升级。headless 预期输出测试保留组装后的规范事件,而不会创建第二套 CLI(命令行界面)约定。PTC mode 通过 headless profile 的 `DSH_TOOLS_MODE=ptc` 组合运行。时间上下文集成通过包自有 Loader 组合执行两个有序轮次,而更细粒度的耗时行为由时间上下文的包级测试负责。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml index 8c085a2343..b7f2ca1524 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.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 .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md -2026-07-22-plan-specific-collaboration-state.md: 20c363d00cfe2bcc2f01af101370f5214c948361 -2026-07-22-plan-specific-collaboration-state.zh.md: 63157b1959236e728e94a38d7ca6397a975595ef +2026-07-22-plan-specific-collaboration-state.md: 1387e69e628c5fb33b61a06214ffd67669668a13 +2026-07-22-plan-specific-collaboration-state.zh.md: 47d36eb97a85d483e40afb5fb9c44f1bf2c92d5e diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md index 20c363d00c..1387e69e62 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md +++ b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md @@ -26,7 +26,7 @@ Sandbox mode and approval policy remain separate enforcement axes. Plan mode nei `plan/mode` is log-only and non-surface, so resume, fork, and compaction recover the state without a live mirror. A spawned agent begins inactive because there is no creation-time plan option. Pending user selections flush before the affected request assembly at initial or continuation pre-step, or on a request-recovery retry; a failed durable append leaves the intent pending for a later boundary. -The active state contributes the deployment's section at first-party prompt order 500. Inactive state contributes no section, while `exit_plan_mode` remains registered in both states, so a transition changes the logged request header but not native tool schemas or the Code Mode SDK. A user-driven transition appends one plugin-sourced notice only when the last request header described the opposite state; a pre-first-request or net-zero selection adds none, and an approved tool exit relies on its tool result instead of a second notice. +The active state contributes the deployment's section at first-party prompt order 500. Inactive state contributes no section, while `exit_plan_mode` remains registered in both states, so a transition changes the logged request header but not native tool schemas or the PTC mode SDK. A user-driven transition appends one plugin-sourced notice only when the last request header described the opposite state; a pre-first-request or net-zero selection adds none, and an approved tool exit relies on its tool result instead of a second notice. ### Reviewed exit @@ -59,7 +59,7 @@ The tool renders the submitted plan as a generic card titled by its first headin ## Verification -- Package tests retain boundary ordering, retry, append-failure, HMR disposal, prompt assembly, stable native and Code Mode schemas, review outcomes, and invariant coverage through the boolean service. +- Package tests retain boundary ordering, retry, append-failure, HMR disposal, prompt assembly, stable native and PTC mode schemas, review outcomes, and invariant coverage through the boolean service. - Command tests cover bare `/plan`, `/plan `, active `/plan off`, pending-entry cancellation, inactive idempotence, absence of `/mode` and `/review`, and effect-scoped removal. - The keyless TUI scenarios enter through `/plan `, leave through `/plan off`, and prove that each committed `plan/mode` precedes the request header it changes, the entry message is logged under plan guidance, and the post-exit request omits that guidance. - The complete `exit_plan_mode` review arc is package-tested but has no assembled-application snapshot after the interactive ACP scenarios were retired; current keyless TUI scenarios cover command entry and direct exit only. diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md index 63157b1959..47d36eb97a 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md @@ -26,7 +26,7 @@ Plan mode 拥有一个 plan 专用产品包:位于 `packages/plan/plan-mode/` `plan/mode` 仅记录到日志且不进入表层,因此恢复、fork 和压缩(compaction)都能恢复该状态,无需实时镜像。spawn 出的 agent(智能体)初始处于未激活状态,因为创建时没有 plan 选项。待生效的用户选择会在初始或续步 pre-step 时,或在请求恢复重试时,于受影响的请求组装前写入日志;持久追加失败会让意图保持待定,留到后续边界处理。 -激活状态在 first-party 提示词顺序 500 处贡献部署提供的区段。未激活状态不贡献区段,但 `exit_plan_mode` 在两种状态下都保持注册,因此状态转换会改变已记录的请求头,却不改变原生工具 schema 或 Code Mode SDK。用户发起的转换只会在上一条请求头描述相反状态时追加一条来源为插件的通知;第一次请求前的选择或最终状态未变化的选择不会追加通知,经批准的工具退出则依赖其工具结果,不再追加第二条通知。 +激活状态在 first-party 提示词顺序 500 处贡献部署提供的区段。未激活状态不贡献区段,但 `exit_plan_mode` 在两种状态下都保持注册,因此状态转换会改变已记录的请求头,却不改变原生工具 schema 或 PTC mode SDK。用户发起的转换只会在上一条请求头描述相反状态时追加一条来源为插件的通知;第一次请求前的选择或最终状态未变化的选择不会追加通知,经批准的工具退出则依赖其工具结果,不再追加第二条通知。 ### 经评审的退出 @@ -59,7 +59,7 @@ Plan mode 拥有一个 plan 专用产品包:位于 `packages/plan/plan-mode/` ## 验证 -- 包测试通过布尔服务继续覆盖边界顺序、重试、追加失败、HMR(热模块替换)资源释放、提示词组装、稳定的原生 schema 与 Code Mode schema、评审结果和不变式。 +- 包测试通过布尔服务继续覆盖边界顺序、重试、追加失败、HMR(热模块替换)资源释放、提示词组装、稳定的原生 schema 与 PTC mode schema、评审结果和不变式。 - 命令测试覆盖不带参数的 `/plan`、`/plan `、激活状态下的 `/plan off`、取消待生效的进入选择、未激活状态下的幂等性、不存在 `/mode` 和 `/review`,以及随 effect 作用域移除。 - 无密钥 TUI 场景通过 `/plan ` 进入、通过 `/plan off` 退出,并证明每个已提交的 `plan/mode` 都先于其所改变的请求头,进入消息在 plan 引导下记录到日志,且退出后的请求不含该引导。 - 完整的 `exit_plan_mode` 评审流程有包测试,但交互式 ACP 场景退役后没有组装应用快照;当前无密钥 TUI 场景只覆盖命令进入和直接退出。 diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml index a6ce4ad6b0..12184063d7 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.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 .agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md -2026-07-28-remove-synthetic-log-only-turns.md: 2b0add1a916021cfc1790c8f4fd5684305d81be8 -2026-07-28-remove-synthetic-log-only-turns.zh.md: 59fa8e4fcc6301cf6d04cab0d572e0c4ff15731b +2026-07-28-remove-synthetic-log-only-turns.md: 6fe23b0b34c49cc79d912f86e9ffe548b8e08d19 +2026-07-28-remove-synthetic-log-only-turns.zh.md: ccdda3f7606bc160dbb4d896348ed49bd046c6a7 diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md index 2b0add1a91..6fe23b0b34 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md @@ -36,7 +36,7 @@ The historical [universal turn-enclosure decision](../../archived/architecture/2 ## Verification -Core invariant tests accept an unknown plugin event between turns while continuing to reject built-in execution events there. Hook, plan-mode, Code Mode dispatch, and approval invariant companions reject their execution-scoped events when no turn is open; the compaction companion separately accepts a balanced `turn: null` manual bracket between turns and requires numeric owners to match an open turn. Session-title service tests pin one direct fallback event under concurrent refresh, detached-session rejection, and newest-revision acceptance. JSONL and SQLite round trips preserve a title appended after `turn/end` through the persistence lifecycle drain, and fork tests retain a standalone log-only tail while rejecting boundaries inside an open turn. A keyless assembled ACP snapshot delays the model-backed title until after `turn/end` and pins one standalone provider title with no synthetic turn. Generated API and type-equivalence catalogs contain no removed symbol. +Core invariant tests accept an unknown plugin event between turns while continuing to reject built-in execution events there. Hook, plan-mode, PTC mode dispatch, and approval invariant companions reject their execution-scoped events when no turn is open; the compaction companion separately accepts a balanced `turn: null` manual bracket between turns and requires numeric owners to match an open turn. Session-title service tests pin one direct fallback event under concurrent refresh, detached-session rejection, and newest-revision acceptance. JSONL and SQLite round trips preserve a title appended after `turn/end` through the persistence lifecycle drain, and fork tests retain a standalone log-only tail while rejecting boundaries inside an open turn. A keyless assembled ACP snapshot delays the model-backed title until after `turn/end` and pins one standalone provider title with no synthetic turn. Generated API and type-equivalence catalogs contain no removed symbol. ## Consequences diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md index 59fa8e4fcc..ccdda3f760 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md @@ -36,7 +36,7 @@ Status: implemented ## 验证 -核心不变量测试会接受轮次之间的未知插件事件,同时继续拒绝位于该处的内置执行事件。钩子、plan-mode、Code Mode 分发和审批的不变量配套组件会在没有开放轮次时拒绝其执行作用域事件;压缩配套组件则另外接受轮次之间平衡的 `turn: null` 手动标记对,并要求数字 owner匹配一个开放轮次。会话标题服务测试会在并发刷新、拒绝已脱离会话和接受最新修订的场景下,固定一个直接追加的回退事件。JSONL 和 SQLite 往返测试会通过持久化生命周期排空保留追加在 `turn/end` 之后的标题;fork 测试会保留独立纯日志尾部,同时拒绝位于开放轮次内的边界。一个无密钥、经完整组装的 ACP(Agent Client Protocol)快照会将模型生成的标题延迟到 `turn/end` 之后,并固定一个不含合成轮次的独立提供方标题。生成的 API 和类型等价性目录不含任何已移除符号。 +核心不变量测试会接受轮次之间的未知插件事件,同时继续拒绝位于该处的内置执行事件。钩子、plan-mode、PTC mode 分发和审批的不变量配套组件会在没有开放轮次时拒绝其执行作用域事件;压缩配套组件则另外接受轮次之间平衡的 `turn: null` 手动标记对,并要求数字 owner匹配一个开放轮次。会话标题服务测试会在并发刷新、拒绝已脱离会话和接受最新修订的场景下,固定一个直接追加的回退事件。JSONL 和 SQLite 往返测试会通过持久化生命周期排空保留追加在 `turn/end` 之后的标题;fork 测试会保留独立纯日志尾部,同时拒绝位于开放轮次内的边界。一个无密钥、经完整组装的 ACP(Agent Client Protocol)快照会将模型生成的标题延迟到 `turn/end` 之后,并固定一个不含合成轮次的独立提供方标题。生成的 API 和类型等价性目录不含任何已移除符号。 ## 后果 diff --git a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml index e65f1cd3b2..4125e5a52e 100644 --- a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.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 .agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md -2026-07-29-shared-base-config-overlays.md: 674ac9c6a386fc6402d18b06b638ce8209855b15 -2026-07-29-shared-base-config-overlays.zh.md: 6001cfbadb0b8e21528903d64572d1e4d09e9db2 +2026-07-29-shared-base-config-overlays.md: de9dcafeab46c21a4fea85f36d73e3fbc9736d9a +2026-07-29-shared-base-config-overlays.zh.md: 0e5b76df33be433391572f08b639dd85c0f30b8e diff --git a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md index 674ac9c6a3..de9dcafeab 100644 --- a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md +++ b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md @@ -24,13 +24,13 @@ Precedence is list order, last write winning per row: base, then the surface ove A patch replaces its target row's whole `config` rather than merging. Therefore, a row whose value differs per surface lives in the overlays, never in the base, so no row is patched by three layers at once. Session identity cannot ride a config key at all — it moved to `dsh-agent-loop`'s `CONFIGURED_AGENT_IDENTITIES_KEY`, as the launcher-owned identity record documented. -The TUI tests live in `apps/cli/tests/`, the Cordis-toolset e2e in `packages/extensions/tool-cordis/tests/`, and the supported Code Mode demo runs `dsh --profile headless` with `DSH_TOOLS_MODE=code`. +The TUI tests live in `apps/cli/tests/`, the Cordis-toolset e2e in `packages/extensions/tool-cordis/tests/`, and the supported PTC mode demo runs `dsh --profile headless` with `DSH_TOOLS_MODE=ptc`. ## Alternatives considered **Leave both trees flat and duplicated.** Rejected: 43 rows maintained twice is the defect, and a gate asserting they stay identical would freeze the duplication rather than remove it. -**Nest the overlays as includes (`code-mode` → `tui` → `base`).** Rejected after testing the Loader: patches do not cross an include boundary, so the outer file's patches are dropped with only a warning. A three-level chain left `tools` unpatchable, and a base behind one include made every personal patch a silent no-op. +**Nest the overlays as includes (`ptc` → `tui` → `base`).** Rejected after testing the Loader: patches do not cross an include boundary, so the outer file's patches are dropped with only a warning. A three-level chain left `tools` unpatchable, and a base behind one include made every personal patch a silent no-op. **Put the union of all rows in the base and have each overlay disable what it does not want.** Rejected: the base stops meaning "shared", and each surface carries rows it exists only to switch off. @@ -46,7 +46,7 @@ A patch whose `id` matches no row stays a no-op rather than an error. That is de ## Verification -Composition is checked by booting each tree through the real Loader and inspecting settled entries, not by reading YAML; both surfaces settle with zero unloaded rows, and Web starts its `httpServer` with sandboxed Bash and filesystem providers. Code Mode remains covered by the ACP overlay and programmatic TUI snapshots rather than a separate shipped TUI application. +Composition is checked by booting each tree through the real Loader and inspecting settled entries, not by reading YAML; both surfaces settle with zero unloaded rows, and Web starts its `httpServer` with sandboxed Bash and filesystem providers. PTC mode remains covered by the ACP overlay and programmatic TUI snapshots rather than a separate shipped TUI application. All eight terminal snapshot scenarios replay byte-identically after moving, and the 14-case PTY smoke passes, including two cases that assert a personal overlay reaches an **inserted** row — the behavior the vendored `plugin-include` fix enables ([`vendor/README.md`](../../../../vendor/README.md) local modification 8, covered by `packages/boot/app-boot/tests/config-reload.spec.ts`). diff --git a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md index 6001cfbadb..0e5b76df33 100644 --- a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md @@ -24,13 +24,13 @@ Status: implemented patch 会整体替换目标配置项的 `config` 而不合并。因此,取值因 surface 而异的配置项住在 overlay 中,绝不住在 base 里,从而没有任何配置项会被三层同时 patch。会话身份根本不能经由配置键传递——它迁移到了 `dsh-agent-loop` 的 `CONFIGURED_AGENT_IDENTITIES_KEY`,正如启动器持有身份的记录所述。 -TUI 测试位于 `apps/cli/tests/`,Cordis 工具集 e2e 位于 `packages/extensions/tool-cordis/tests/`,受支持的 Code Mode demo 则以 `DSH_TOOLS_MODE=code` 运行 `dsh --profile headless`。 +TUI 测试位于 `apps/cli/tests/`,Cordis 工具集 e2e 位于 `packages/extensions/tool-cordis/tests/`,受支持的 PTC mode demo 则以 `DSH_TOOLS_MODE=ptc` 运行 `dsh --profile headless`。 ## 备选方案 **保留两棵平铺且重复的树。** 拒绝:43 个配置项维护两份正是缺陷本身,而用一个门禁断言二者保持一致只会固化重复,而非消除它。 -**把 overlay 嵌套成 include(`code-mode` → `tui` → `base`)。** 在对 Loader 实测后拒绝:patch 不会跨越 include 边界,因此外层文件的 patch 只会伴随一条告警被丢弃。三层链条使 `tools` 无法被 patch,而位于一层 include 之后的 base,会让每个个人 patch 都变成静默的空操作。 +**把 overlay 嵌套成 include(`ptc` → `tui` → `base`)。** 在对 Loader 实测后拒绝:patch 不会跨越 include 边界,因此外层文件的 patch 只会伴随一条告警被丢弃。三层链条使 `tools` 无法被 patch,而位于一层 include 之后的 base,会让每个个人 patch 都变成静默的空操作。 **把所有配置项的并集放进 base,由各 overlay 禁用自己不需要的部分。** 拒绝:base 将不再意味着「共享」,而每个 surface 都要携带仅为将其关闭而存在的配置项。 @@ -46,7 +46,7 @@ TUI 测试位于 `apps/cli/tests/`,Cordis 工具集 e2e 位于 `packages/exten ## 验证 -组合的正确性通过用真实 Loader 启动每棵树并检查已就绪的条目来核对,而不是靠阅读 YAML:两个界面都能完全就绪,且没有未加载项;Web 会以沙箱化 Bash 与文件系统提供方启动 `httpServer`。Code Mode 继续由 ACP overlay 与程序化 TUI 快照覆盖,而不再维护独立交付的 TUI 应用。 +组合的正确性通过用真实 Loader 启动每棵树并检查已就绪的条目来核对,而不是靠阅读 YAML:两个界面都能完全就绪,且没有未加载项;Web 会以沙箱化 Bash 与文件系统提供方启动 `httpServer`。PTC mode 继续由 ACP overlay 与程序化 TUI 快照覆盖,而不再维护独立交付的 TUI 应用。 全部八个终端快照场景在迁移后逐字节重放一致,14 个用例的 PTY 冒烟测试全部通过,其中两个用例断言个人 overlay 能触达一个 **insert 进来的**配置项——这正是 vendored `plugin-include` 修复所启用的行为([`vendor/README.md`](../../../../vendor/README.md) 本地修改第 8 条,由 `packages/boot/app-boot/tests/config-reload.spec.ts` 覆盖)。 diff --git a/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.i18n.yaml index 277f33f997..4e47e34616 100644 --- a/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.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 .agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.md -2026-08-10-default-presets-single-editor.md: 82f254079080aeb88d76f4e7cc2c7ab195646ec4 -2026-08-10-default-presets-single-editor.zh.md: 1d89662bcc1acd664e7014a65b54110beb5ffb5c +2026-08-10-default-presets-single-editor.md: 3d1c1dea4f9fcc676a8146033f7785dfd3aaf481 +2026-08-10-default-presets-single-editor.zh.md: 5212156a6a3c11e16165eca04bd141337380e172 diff --git a/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.md b/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.md index 82f2540790..3d1c1dea4f 100644 --- a/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.md +++ b/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.md @@ -10,7 +10,7 @@ The `standard`, `code`, and `cordis` presets exposed both the `read`/`write`/`ed ## Decision -The `standard`, `code`, and `cordis` preset configurations mount `dsh-tool-fs` and `dsh-tool-fs-search`, but do not mount `dsh-tool-str-replace-editor`. Code Mode therefore omits `str_replace_editor` from both its registry and generated SDK. The `minimal` preset continues to mount `dsh-tool-str-replace-editor`, and deployments or user-authored presets may still mount the plugin explicitly. +The `standard`, `code`, and `cordis` preset configurations mount `dsh-tool-fs` and `dsh-tool-fs-search`, but do not mount `dsh-tool-str-replace-editor`. PTC mode therefore omits `str_replace_editor` from both its registry and generated SDK. The `minimal` preset continues to mount `dsh-tool-str-replace-editor`, and deployments or user-authored presets may still mount the plugin explicitly. This decision narrows the preset roster rather than removing the tool package or its Python runtime support. The earlier [shared-roster decision](../feature/2026-07-31-even-out-shipped-tool-rosters.md) continues to own why surface-neutral tools live in preset composition; this note owns the editor exception. @@ -22,4 +22,4 @@ This decision narrows the preset roster rather than removing the tool package or ## Consequences -General-purpose agents use `read`, `write`, and `edit` for filesystem mutations, while the minimal agent retains `str_replace_editor`. Preset composition tests pin its absence from the standard roster, the Cordis roster, and the Code Mode SDK, while the minimal assertions continue to pin its presence. +General-purpose agents use `read`, `write`, and `edit` for filesystem mutations, while the minimal agent retains `str_replace_editor`. Preset composition tests pin its absence from the standard roster, the Cordis roster, and the PTC mode SDK, while the minimal assertions continue to pin its presence. diff --git a/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.zh.md b/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.zh.md index 1d89662bcc..5212156a6a 100644 --- a/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.zh.md +++ b/.agents/notes/implemented/simplification/2026-08-10-default-presets-single-editor.zh.md @@ -10,7 +10,7 @@ Status: implemented ## 决策 -`standard`、`code` 和 `cordis` preset 配置挂载 `dsh-tool-fs` 与 `dsh-tool-fs-search`,但不挂载 `dsh-tool-str-replace-editor`。因此 Code Mode 的注册表和生成的 SDK 均不包含 `str_replace_editor`。`minimal` preset 继续挂载 `dsh-tool-str-replace-editor`,部署配置或用户自定义 preset 仍可显式挂载该插件。 +`standard`、`code` 和 `cordis` preset 配置挂载 `dsh-tool-fs` 与 `dsh-tool-fs-search`,但不挂载 `dsh-tool-str-replace-editor`。因此 PTC mode 的注册表和生成的 SDK 均不包含 `str_replace_editor`。`minimal` preset 继续挂载 `dsh-tool-str-replace-editor`,部署配置或用户自定义 preset 仍可显式挂载该插件。 此决策收窄 preset 工具清单,不移除工具包及其 Python 运行时支持。较早的[共享清单决策](../feature/2026-07-31-even-out-shipped-tool-rosters.zh.md)继续说明与 surface 无关的工具为何归 preset 组合所有;本记录说明编辑器例外。 @@ -22,4 +22,4 @@ Status: implemented ## 后果 -通用 agent 使用 `read`、`write` 和 `edit` 完成文件系统修改,minimal agent 保留 `str_replace_editor`。preset 组合测试固定其不会出现在 standard 清单、Cordis 清单及 Code Mode SDK 中,同时 minimal 断言继续固定其存在。 +通用 agent 使用 `read`、`write` 和 `edit` 完成文件系统修改,minimal agent 保留 `str_replace_editor`。preset 组合测试固定其不会出现在 standard 清单、Cordis 清单及 PTC mode SDK 中,同时 minimal 断言继续固定其存在。 diff --git a/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.i18n.yaml index 70c3619952..9768def496 100644 --- a/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.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 .agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.md -2026-08-24-owner-local-profile-tests-and-guides.md: d41bac22bfca907a5601d8fdb279b9a20ecc50d4 -2026-08-24-owner-local-profile-tests-and-guides.zh.md: 87ddfb670ef1375ae48bee36f92f71fd103f461d +2026-08-24-owner-local-profile-tests-and-guides.md: 26a62dd9c28a55d5195ab9a8a56c5369bf521362 +2026-08-24-owner-local-profile-tests-and-guides.zh.md: fa65c09b8b886b1bf442ff5a52976e06032bb75c diff --git a/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.md b/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.md index d41bac22bf..26a62dd9c2 100644 --- a/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.md +++ b/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.md @@ -14,7 +14,7 @@ There is no top-level `examples/` tree. Named `dsh` profiles are the only Node a Optional user overlays are shipped assets under `apps/cli/config/examples/`, where their bare plugin names resolve through the CLI application manifest. The GitHub review, Schedule, memory MCP, and runtime Cordis guides live under `docs/user/` and link those assets. The runnable Python SDK program and minimal overlay live under `python/sdk/examples/`. -The `demo:acp` and `demo:cordis` scripts are absent. ACP starts through `dsh --profile acp`; the Cordis guide starts `dsh web` with its explicit overlay. `demo:code-mode` remains as a thin wrapper over `dsh --profile headless` with `DSH_TOOLS_MODE=code`. +The `demo:acp` and `demo:cordis` scripts are absent. ACP starts through `dsh --profile acp`; the Cordis guide starts `dsh web` with its explicit overlay. `demo:ptc` remains as a thin wrapper over `dsh --profile headless` with `DSH_TOOLS_MODE=ptc`. ## Alternatives considered diff --git a/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.zh.md b/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.zh.md index 87ddfb670e..fa65c09b8b 100644 --- a/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.zh.md +++ b/.agents/notes/implemented/simplification/2026-08-24-owner-local-profile-tests-and-guides.zh.md @@ -14,7 +14,7 @@ Status: implemented 可选用户 overlay 作为交付资产位于 `apps/cli/config/examples/`,其中的裸插件名通过 CLI 应用 manifest 解析。GitHub 评审、Schedule、记忆 MCP 与运行时 Cordis 指南位于 `docs/user/` 并链接这些资产。可运行的 Python SDK 程序与极简 overlay 位于 `python/sdk/examples/`。 -仓库不存在 `demo:acp` 与 `demo:cordis` 脚本。ACP 通过 `dsh --profile acp` 启动;Cordis 指南使用显式 overlay 启动 `dsh web`。`demo:code-mode` 继续作为薄 wrapper,以 `DSH_TOOLS_MODE=code` 运行 `dsh --profile headless`。 +仓库不存在 `demo:acp` 与 `demo:cordis` 脚本。ACP 通过 `dsh --profile acp` 启动;Cordis 指南使用显式 overlay 启动 `dsh web`。`demo:ptc` 继续作为薄 wrapper,以 `DSH_TOOLS_MODE=ptc` 运行 `dsh --profile headless`。 ## 考虑过的替代方案 diff --git a/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml b/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml index 5c561d9188..7c63922271 100644 --- a/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml +++ b/.agents/notes/proposed/feature/2026-08-04-task-surface.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 .agents/notes/proposed/feature/2026-08-04-task-surface.md -2026-08-04-task-surface.md: dbf73976b606a4a45202c1b29b79f5cfbd77ac1c -2026-08-04-task-surface.zh.md: ecf8764b7b1da7a4a874dd78c6f69df80fb36f36 +2026-08-04-task-surface.md: db4472e98146902cad59112cee3a9098cb736fa1 +2026-08-04-task-surface.zh.md: 8487c3f5f5b40a55a709373b83eb7425ef5e532f diff --git a/.agents/notes/proposed/feature/2026-08-04-task-surface.md b/.agents/notes/proposed/feature/2026-08-04-task-surface.md index dbf73976b6..db4472e981 100644 --- a/.agents/notes/proposed/feature/2026-08-04-task-surface.md +++ b/.agents/notes/proposed/feature/2026-08-04-task-surface.md @@ -81,7 +81,7 @@ Limits are schema-backed configuration on the Task Surface service. The initial `show_task_surface` accepts `{ model: TaskSurfaceModelV1 }`. The Host parses and normalizes the complete model, rejects the call when that Session already has an open Task Surface, mints `surfaceId`, and returns canonical `{ surfaceId, model }` with the normalized model. `presentationMeta` persists `value.model`, so the projector and executor cannot disagree about normalization. The Native result names the Surface and explains that an ordinary message bypasses it when the client cannot render the panel. The tool then calls `exec.concludeTurn()` so the agent does not continue past the requested human checkpoint. -The tool definition omits `isConcurrencySafe`. Under the existing tool-registry contract, omission classifies every call as an exclusive ordering barrier; no new `ToolDefinition` field is introduced. The tool is composed only in Web profiles that mount both the Host service and Web renderer. Version 1 supports `native` and `both` tool modes; a `code`-only profile does not advertise it because Code Mode dispatch is nested and cannot carry its presentation metadata to the outer result. +The tool definition omits `isConcurrencySafe`. Under the existing tool-registry contract, omission classifies every call as an exclusive ordering barrier; no new `ToolDefinition` field is introduced. The tool is composed only in Web profiles that mount both the Host service and Web renderer. Version 1 supports `native` and `both` tool modes; a `code`-only profile does not advertise it because PTC mode dispatch is nested and cannot carry its presentation metadata to the outer result. The browser-safe domain package imports the type-only `Branded` primitive from `@deepseek-ai/dsh-brand` and owns all three Task Surface IDs. The canonical value is execution-local under the [canonical tool output contract](../../implemented/architecture/2026-07-20-canonical-tool-output-contract.md). Replay therefore uses `output.presentationMeta(args, value)` to persist this tagged payload with `tool/result.meta`: diff --git a/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md b/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md index ecf8764b7b..8487c3f5f5 100644 --- a/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md +++ b/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md @@ -81,7 +81,7 @@ Task Surface 服务通过受 schema 校验的配置定义限制。初始默认 `show_task_surface` 接收 `{ model: TaskSurfaceModelV1 }`。Host 解析并规范化完整模型;若该会话已有一个打开的 Task Surface,则拒绝调用;否则生成 `surfaceId`,并返回带规范化模型的规范值 `{ surfaceId, model }`。`presentationMeta` 持久化 `value.model`,使投影器和执行器不会对规范化结果产生分歧。Native 结果会指明该 Surface,并说明客户端无法渲染面板时,可以通过普通消息绕过它。随后工具调用 `exec.concludeTurn()`,防止 agent 越过所要求的人工检查点继续执行。 -工具定义省略 `isConcurrencySafe`。根据现有工具注册表约定,省略该字段会将每次调用归类为独占排序屏障,无需新增 `ToolDefinition` 字段。该工具只会组装到同时挂载 Host 服务和 Web 渲染器的 Web profile 中。版本 1 支持 `native` 和 `both` 工具模式;仅支持 `code` 的 profile 不会向模型公布该工具,因为 Code Mode 分发属于嵌套调用,无法把呈现元数据传到外层结果。 +工具定义省略 `isConcurrencySafe`。根据现有工具注册表约定,省略该字段会将每次调用归类为独占排序屏障,无需新增 `ToolDefinition` 字段。该工具只会组装到同时挂载 Host 服务和 Web 渲染器的 Web profile 中。版本 1 支持 `native` 和 `both` 工具模式;仅支持 `code` 的 profile 不会向模型公布该工具,因为 PTC mode 分发属于嵌套调用,无法把呈现元数据传到外层结果。 浏览器安全的领域包从 `@deepseek-ai/dsh-brand` 以仅类型方式导入 `Branded` 原语,并拥有全部三个 Task Surface ID。根据[规范工具输出约定](../../implemented/architecture/2026-07-20-canonical-tool-output-contract.zh.md),规范值仅存在于本次执行中。因此,回放通过 `output.presentationMeta(args, value)` 将以下带标签的载荷随 `tool/result.meta` 一并持久化: diff --git a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.i18n.yaml b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.i18n.yaml index b84fc76dbd..7027d11d80 100644 --- a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.i18n.yaml +++ b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.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 .agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.md -2026-07-04-prune-dead-core-spine-api.md: ae6ea2d763c1f84b32d1a24bae32f412c77c9976 -2026-07-04-prune-dead-core-spine-api.zh.md: 8e76827de10e119ed1e0d9fbf8a44e8eaf21575c +2026-07-04-prune-dead-core-spine-api.md: ecf4e3aa032e47a26d65edec3817e2a50026da40 +2026-07-04-prune-dead-core-spine-api.zh.md: 81cee28d9f9819802b2276b90a4db51035046e1c diff --git a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.md b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.md index ae6ea2d763..ecf4e3aa03 100644 --- a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.md +++ b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.md @@ -27,7 +27,7 @@ The production corpus is `packages/*/*/src`, example sources/config, and runtime | `CompactionResult.startSeq`, `summarySeq`, `endSeq`, and `summary` | The production consumer reads only shadowed range/seq/token accounting; the durable log owns summary and event identity. | Remove the four result echoes while keeping both shared transcript renderers. | | `BasicCompactionEngine` estimation/summarization visibility | No outside production caller invokes the five methods; the implemented Agent Note names only `estimateContentTokens()` and `summarize()` as subclass hooks. | Make those two `protected` and the three orchestration-only estimators private. | | `CodeLogEntry.source`/`level` and `RunCodeMeta.dispatches` | Every production consumer maps logs to text; no presenter/model path reads the other fields or the persisted dispatch count. | Make code-runtime logs strings (or text-only entries) and remove result-meta dispatch plumbing; keep the local counter that mints deterministic dispatch ids. | -| `CodeRuntime.language` and `CodeRuntime.isolation` | The worker backend supplies the only production values, while Code Mode and every other production caller invoke only `run()`. | Remove the unread descriptors while preserving the worker's language, isolation, budgets, cancellation, and disposal behavior. | +| `CodeRuntime.language` and `CodeRuntime.isolation` | The worker backend supplies the only production values, while PTC mode and every other production caller invoke only `run()`. | Remove the unread descriptors while preserving the worker's language, isolation, budgets, cancellation, and disposal behavior. | | `ToolNotFoundError.toolName`, `SystemPrompt.config`, and `BashTask.command` | Each stored public value has no production reader. | Drop the unread field while retaining error messages, resolved configuration behavior, and task lifecycle. | | Backend package-root implementation helpers | The exact inventory below is called only through relative same-package imports. Production namespace imports mount the retained plugin contract without reading these properties; named root consumers are tests. | Retain each adapter/provider/service and its config/error contract; stop exporting the listed helper functions/constants at package roots. | | Consumer package-root implementation helpers | The exact inventory below has only same-package production callers. Production namespace imports mount plugin contracts without reading helper properties; named root consumers are tests. | Retain plugin contracts and stable error codes; move tests to package-local modules or public behavior and stop exporting the listed helpers at package roots. | diff --git a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.zh.md b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.zh.md index 8e76827de1..81cee28d9f 100644 --- a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.zh.md +++ b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-api.zh.md @@ -27,7 +27,7 @@ Status: proposed | `CompactionResult.startSeq`、`summarySeq`、`endSeq` 与 `summary` | 生产消费方只读取 shadowed range/seq/token 统计;持久日志拥有 summary 和事件标识。 | 移除四个结果回显,保留两个共享的 transcript(文本记录)渲染器。 | | `BasicCompactionEngine` 的估算/摘要方法可见性 | 没有包外生产调用者调用这五个方法;已实现的 Agent Note 只将 `estimateContentTokens()` 和 `summarize()` 命名为子类钩子。 | 将这两个方法改为 `protected`,其余三个编排专用的估算器改为 private。 | | `CodeLogEntry.source`/`level` 与 `RunCodeMeta.dispatches` | 每个生产消费方都将日志映射为文本;没有 presenter/模型路径读取其他字段或持久化的 dispatch 计数。 | 将 code-runtime 日志改为字符串(或纯文本条目),移除 result-meta 的 dispatch 管道;保留用于生成确定性 dispatch id 的本地计数器。 | -| `CodeRuntime.language` 与 `CodeRuntime.isolation` | worker 后端提供唯一的生产值,而 Code Mode 及其他所有生产调用方只调用 `run()`。 | 移除未读描述符,同时保留 worker 的语言、隔离、预算、取消与资源释放行为。 | +| `CodeRuntime.language` 与 `CodeRuntime.isolation` | worker 后端提供唯一的生产值,而 PTC mode 及其他所有生产调用方只调用 `run()`。 | 移除未读描述符,同时保留 worker 的语言、隔离、预算、取消与资源释放行为。 | | `ToolNotFoundError.toolName`、`SystemPrompt.config` 与 `BashTask.command` | 每个存储的公开值都没有生产读取者。 | 移除未读字段,保留错误消息、已解析的配置行为和任务生命周期。 | | 后端包根实现辅助函数 | 下方精确清单仅通过相对路径的同包导入调用。生产命名空间导入挂载的是保留的插件约定,不读取这些属性;包根命名导入的消费方都是测试。 | 保留每个适配器/提供方/服务及其配置/错误约定;停止在包根导出所列辅助函数/常量。 | | 消费方包根实现辅助函数 | 下方精确清单只有同包生产调用者。生产命名空间导入挂载的是插件约定,不读取辅助属性;包根命名导入的消费方都是测试。 | 保留插件约定和稳定的错误码;将测试迁移到包内模块或公开行为,停止在包根导出所列辅助函数。 | diff --git a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.i18n.yaml b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.i18n.yaml index 201991e0a3..fdadd105b9 100644 --- a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.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 .agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.md -2026-07-12-prune-unused-skill-registry-api.md: c84c6d8d61e13ebe7e1a5d3d29260637e38dbf9f -2026-07-12-prune-unused-skill-registry-api.zh.md: 0fada10b8c086b982dd402adccd42b9e48d129a7 +2026-07-12-prune-unused-skill-registry-api.md: 23fb0163296a1cf6cd8700ad745f60e52371a3b1 +2026-07-12-prune-unused-skill-registry-api.zh.md: e1496aab7bc77dc04600a95bea8a5db3f8955053 diff --git a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.md b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.md index c84c6d8d61..23fb016329 100644 --- a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.md +++ b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.md @@ -21,7 +21,7 @@ Amend the skill-system Agent Note, README, JSDoc, catalogs, and tests. Agent-sco ## Acceptance criteria - Skill collection has one provider-backed path, a cwd-only completed-cache key, and a revision epoch only for in-flight invalidation; retained skill fields have a production reader or a recorded deliberate extension contract. -- Agent-scoped prompt sections, variables, tool providers, tool guards, and structured-output commit behavior in native and Code Mode remain unchanged. +- Agent-scoped prompt sections, variables, tool providers, tool guards, and structured-output commit behavior in native and PTC mode remain unchanged. - Typecheck, coverage, snapshots, doc-sync, module-graph verification, build, and hygiene pass. ## Risks diff --git a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.zh.md b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.zh.md index 0fada10b8c..e1496aab7b 100644 --- a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.zh.md +++ b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.zh.md @@ -21,7 +21,7 @@ skill(技能)服务的嵌入式运行时子系统中,`ctx.skills.register( ## 验收标准 - skill 收集只有一条提供方驱动的路径,已完成缓存仅以 cwd 为键,revision epoch 仅用于使进行中的发现操作失效;保留的 skill 字段要么有生产读取方,要么有记录在案的有意扩展约定。 -- agent 作用域的系统提示词段、变量、工具提供方、工具守卫,以及原生模式和 Code Mode 下的 structured-output 提交行为保持不变。 +- agent 作用域的系统提示词段、变量、工具提供方、工具守卫,以及原生模式和 PTC mode 下的 structured-output 提交行为保持不变。 - 类型检查、覆盖率、快照、doc-sync(文档同步门禁)、module-graph 校验、构建与 hygiene 全部通过。 ## 风险 diff --git a/AGENTS.md b/AGENTS.md index a92ef8a9cf..956e28ea45 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,7 +80,7 @@ pnpm run doc-sync # all documentation gates; leaf list in scripts/run-gate pnpm run test:docs # quick documentation checks (no build; doc-quick aggregate) pnpm run website:build # VitePress build (doubles as dead-link check) pnpm dsh --profile headless "task" # run one task from source (needs DEEPSEEK_API_KEY) -pnpm run demo:code-mode -- "task" # headless Code Mode run (needs key) +pnpm run demo:ptc -- "task" # headless PTC mode run (needs key) ``` ### Host sandbox failures diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 616dcd1909..ce770592a0 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -118,18 +118,18 @@ pnpm applies local patches to the following packages at install time, so shipped The project owner authorizes distribution of every version of the official `@anthropic-ai/claude-agent-sdk` package and the official Claude Code CLI/platform payloads that each version declares through `optionalDependencies`. This identity-scoped authorization does not classify their declared terms as permissive and does not cover any unrelated runtime package; version, declared-license, and payload-set changes still require the ordinary dependency, lockfile, compatibility, terms, and notices review. -The installed SDK 0.3.241 declares the following optional platform packages. Each carries the official Claude Code 2.1.241 executable; the package identities and versions come from the SDK manifest, while the declared license field is verified against the platform payload installed for the current host. +The installed SDK 0.3.220 declares the following optional platform packages. Each carries the official Claude Code 2.1.220 executable; the package identities and versions come from the SDK manifest, while the declared license field is verified against the platform payload installed for the current host. | Optional platform package | Version | Declared license | | --- | --- | --- | -| [`@anthropic-ai/claude-agent-sdk-darwin-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-darwin-arm64) | 0.3.241 | SEE LICENSE IN LICENSE.md | -| [`@anthropic-ai/claude-agent-sdk-darwin-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-darwin-x64) | 0.3.241 | SEE LICENSE IN LICENSE.md | -| [`@anthropic-ai/claude-agent-sdk-linux-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-arm64) | 0.3.241 | SEE LICENSE IN LICENSE.md | -| [`@anthropic-ai/claude-agent-sdk-linux-arm64-musl`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-arm64-musl) | 0.3.241 | SEE LICENSE IN LICENSE.md | -| [`@anthropic-ai/claude-agent-sdk-linux-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-x64) | 0.3.241 | SEE LICENSE IN LICENSE.md | -| [`@anthropic-ai/claude-agent-sdk-linux-x64-musl`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-x64-musl) | 0.3.241 | SEE LICENSE IN LICENSE.md | -| [`@anthropic-ai/claude-agent-sdk-win32-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-win32-arm64) | 0.3.241 | SEE LICENSE IN LICENSE.md | -| [`@anthropic-ai/claude-agent-sdk-win32-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-win32-x64) | 0.3.241 | SEE LICENSE IN LICENSE.md | +| [`@anthropic-ai/claude-agent-sdk-darwin-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-darwin-arm64) | 0.3.220 | SEE LICENSE IN LICENSE.md | +| [`@anthropic-ai/claude-agent-sdk-darwin-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-darwin-x64) | 0.3.220 | SEE LICENSE IN LICENSE.md | +| [`@anthropic-ai/claude-agent-sdk-linux-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-arm64) | 0.3.220 | SEE LICENSE IN LICENSE.md | +| [`@anthropic-ai/claude-agent-sdk-linux-arm64-musl`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-arm64-musl) | 0.3.220 | SEE LICENSE IN LICENSE.md | +| [`@anthropic-ai/claude-agent-sdk-linux-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-x64) | 0.3.220 | SEE LICENSE IN LICENSE.md | +| [`@anthropic-ai/claude-agent-sdk-linux-x64-musl`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-x64-musl) | 0.3.220 | SEE LICENSE IN LICENSE.md | +| [`@anthropic-ai/claude-agent-sdk-win32-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-win32-arm64) | 0.3.220 | SEE LICENSE IN LICENSE.md | +| [`@anthropic-ai/claude-agent-sdk-win32-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-win32-x64) | 0.3.220 | SEE LICENSE IN LICENSE.md | ## Development-only npm dependencies diff --git a/apps/cli/tests/profiles/headless/tests/code-mode.e2e.ts b/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts similarity index 91% rename from apps/cli/tests/profiles/headless/tests/code-mode.e2e.ts rename to apps/cli/tests/profiles/headless/tests/ptc.e2e.ts index ca34a38628..ea761cce71 100644 --- a/apps/cli/tests/profiles/headless/tests/code-mode.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts @@ -27,7 +27,7 @@ import CordisHostRunner from '@deepseek-ai/dsh-cordis-host-runner' import * as ToolCordis from '@deepseek-ai/dsh-tool-cordis' /** - * With-key Code Mode proof: a real model receives only `run_code`, composes two + * With-key PTC mode proof: a real model receives only `run_code`, composes two * sub-calls, writes a file, and returns curated output while the log records * each `tool/code-dispatch`. The keyless Loader smoke is in the sibling test. */ @@ -54,7 +54,7 @@ async function codeModeHarness(cwd: string): Promise { await harness.plugin(LlmRuntime) await harness.plugin(SessionStore) await harness.plugin(SystemPrompt, { persona: PERSONA }) - await harness.plugin(ToolRuntime, { mode: 'code' }) + await harness.plugin(ToolRuntime, { mode: 'ptc' }) await harness.plugin(AgentRegistry) await harness.plugin(AgentLoop, { agents: [] }) await harness.plugin(LlmDeepSeek) @@ -71,7 +71,7 @@ async function workspaceCodeModeHarness(): Promise { await harness.plugin(LlmRuntime) await harness.plugin(SessionStore) await harness.plugin(SystemPrompt, { persona: PERSONA }) - await harness.plugin(ToolRuntime, { mode: 'code' }) + await harness.plugin(ToolRuntime, { mode: 'ptc' }) await harness.plugin(AgentRegistry) await harness.plugin(LocalFileSystem, { cwd: '/' }) await harness.plugin(ToolFs) @@ -85,7 +85,7 @@ async function workspaceCodeModeHarness(): Promise { let keylessCall = 0 const testToolSignal = new AbortController().signal -/** Execute one outer Code Mode call through the real registry and worker. */ +/** Execute one outer PTC mode call through the real registry and worker. */ function runCode( harness: Context, code: string, @@ -115,7 +115,7 @@ function completion(result: ToolExecutionResult): unknown { async function typedCodeModeHarness(): Promise { const harness = new Context() await harness.plugin(SystemPrompt) - await harness.plugin(ToolRuntime, { mode: 'code' }) + await harness.plugin(ToolRuntime, { mode: 'ptc' }) await harness.plugin(WorkerThreadCodeRuntime, {}) return harness } @@ -132,7 +132,7 @@ async function backgroundCodeModeHarness(cwd: string): Promise { return harness } -describe('Code Mode typed values: keyless real-worker contracts', () => { +describe('PTC mode typed values: keyless real-worker contracts', () => { it('crosses a large intermediate value intact and exposes only typed tool failure fields', async () => { ctx = await typedCodeModeHarness() ctx.tools.register(defineTool({ @@ -187,7 +187,7 @@ describe('Code Mode typed values: keyless real-worker contracts', () => { }) it('returns a background job id, settles the outer run, and polls that id to completion', async () => { - workdir = await mkdtemp(join(tmpdir(), 'dsh-code-mode-background-')) + workdir = await mkdtemp(join(tmpdir(), 'dsh-ptc-background-')) ctx = await backgroundCodeModeHarness(workdir) const jobId = completion(await runCode(ctx, ` @@ -210,7 +210,7 @@ describe('Code Mode typed values: keyless real-worker contracts', () => { }, 15_000) it('pre-abort spawns nothing; post-publication abort leaves job_kill as the cancellation owner', async () => { - workdir = await mkdtemp(join(tmpdir(), 'dsh-code-mode-task-cancel-')) + workdir = await mkdtemp(join(tmpdir(), 'dsh-ptc-task-cancel-')) ctx = await backgroundCodeModeHarness(workdir) const pre = new AbortController() @@ -247,7 +247,7 @@ describe('Code Mode typed values: keyless real-worker contracts', () => { }, 15_000) it('keeps foreground bash coupled to the outer signal', async () => { - workdir = await mkdtemp(join(tmpdir(), 'dsh-code-mode-foreground-cancel-')) + workdir = await mkdtemp(join(tmpdir(), 'dsh-ptc-foreground-cancel-')) ctx = await backgroundCodeModeHarness(workdir) const controller = new AbortController() const startedAt = Date.now() @@ -266,16 +266,16 @@ describe('Code Mode typed values: keyless real-worker contracts', () => { await ctx.plugin(CordisHostRunner) await ctx.plugin(ToolCordis) const agent = { - id: SessionId('code-mode-cordis'), + id: SessionId('ptc-cordis'), session: { append: vi.fn() }, } as unknown as Agent const value = completion(await runCode(ctx, ` const activeDefinition = await tools.cordis_define({ plugin: { kind: 'new', idPrefix: 'active' }, - name: 'active-code-mode-plugin', + name: 'active-ptc-plugin', purpose: 'prove an active Host half', - code: { host: "return { name: 'active-code-mode-plugin', apply(ctx) {} }" }, + code: { host: "return { name: 'active-ptc-plugin', apply(ctx) {} }" }, }); const active = await tools.cordis_run({ pluginId: activeDefinition.pluginId, @@ -284,9 +284,9 @@ describe('Code Mode typed values: keyless real-worker contracts', () => { }); const pendingDefinition = await tools.cordis_define({ plugin: { kind: 'new', idPrefix: 'queue' }, - name: 'pending-code-mode-plugin', + name: 'pending-ptc-plugin', purpose: 'prove a Host half waiting for a Service', - code: { host: "return { name: 'pending-code-mode-plugin', inject: ['missing-code-mode-service'], apply(ctx) {} }" }, + code: { host: "return { name: 'pending-ptc-plugin', inject: ['missing-ptc-service'], apply(ctx) {} }" }, }); const pending = await tools.cordis_run({ pluginId: pendingDefinition.pluginId, @@ -329,7 +329,7 @@ describe('Code Mode typed values: keyless real-worker contracts', () => { packageId: 'pkg-2', pluginRunId: 'run-2', status: 'waiting', - waitingFor: ['missing-code-mode-service'], + waitingFor: ['missing-ptc-service'], }, removed: { pluginId: 'active-1', wasRunning: true }, beforeContainsId: true, @@ -349,11 +349,11 @@ function waitForIdle(harness: Context, agent: Agent): Promise { }) } -describe.skipIf(!process.env.DEEPSEEK_API_KEY)('Code Mode: real model writes a program over real tools', () => { +describe.skipIf(!process.env.DEEPSEEK_API_KEY)('PTC mode: real model writes a program over real tools', () => { it('collapses the wire tool list to [run_code], bridges sub-calls, and returns curated output', async () => { - workdir = await mkdtemp(join(tmpdir(), 'dsh-code-mode-e2e-')) + workdir = await mkdtemp(join(tmpdir(), 'dsh-ptc-e2e-')) ctx = await codeModeHarness(workdir) - const agent = ctx.agentLoop.create(SessionId('e2e-code-mode'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }) + const agent = ctx.agentLoop.create(SessionId('e2e-ptc'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }) agent.followup(createUserMessage({ content: [{ @@ -396,14 +396,14 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('Code Mode: real model writes a p }, 180_000) it('projects nested workspace instructions discovered by an fs sub-call', async () => { - workdir = await mkdtemp(join(tmpdir(), 'dsh-code-mode-workspace-e2e-')) + workdir = await mkdtemp(join(tmpdir(), 'dsh-ptc-workspace-e2e-')) await mkdir(join(workdir, '.git'), { recursive: true }) await mkdir(join(workdir, 'pkg/deep'), { recursive: true }) - await writeFile(join(workdir, 'pkg/AGENTS.md'), `If asked for the Code Mode workspace handshake, reply with exactly ${WORKSPACE_PROBE} and nothing else.\n`) + await writeFile(join(workdir, 'pkg/AGENTS.md'), `If asked for the PTC mode workspace handshake, reply with exactly ${WORKSPACE_PROBE} and nothing else.\n`) await writeFile(join(workdir, 'pkg/deep/task.txt'), 'Touch this file to discover the nested instructions.\n') ctx = await workspaceCodeModeHarness() const handle = await ctx.agents.create({ - sessionId: SessionId('e2e-code-mode-workspace-session'), + sessionId: SessionId('e2e-ptc-workspace-session'), meta: { cwd: workdir }, agentOptions: { provider: 'deepseek-official', model: 'deepseek-v4-flash' }, }) @@ -411,7 +411,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('Code Mode: real model writes a p handle.agent.followup(createUserMessage({ content: [{ type: 'text', - text: 'Use one run_code program to call tools.read on pkg/deep/task.txt. After it finishes, answer: Code Mode workspace handshake?', + text: 'Use one run_code program to call tools.read on pkg/deep/task.txt. After it finishes, answer: PTC mode workspace handshake?', }], source: { kind: 'user' } })) await waitForIdle(ctx, handle.agent) diff --git a/apps/cli/tests/web-agent-presets.e2e.ts b/apps/cli/tests/web-agent-presets.e2e.ts index 7903cb6e9e..a08ef12d8a 100644 --- a/apps/cli/tests/web-agent-presets.e2e.ts +++ b/apps/cli/tests/web-agent-presets.e2e.ts @@ -216,7 +216,7 @@ describe('the shipped Web composition', () => { it('supplies both shipped presets, and only those, from the system root', async () => { const listed = await ctx.agentPresets.list() - expect(listed.map(preset => preset.id).sort()).toEqual(['code', 'cordis', 'minimal', 'standard']) + expect(listed.map(preset => preset.id).sort()).toEqual(['cordis', 'minimal', 'ptc', 'standard']) expect(listed.every(preset => preset.trust === 'system')).toBe(true) expect(ctx.agentPresets.defaultId).toBe('standard') }) @@ -350,18 +350,18 @@ describe('the shipped Web composition', () => { } }) - it('presents `code` as Code Mode without disturbing a native session beside it', async () => { + it('presents `ptc` as PTC mode without disturbing a native session beside it', async () => { const coded = await ctx.agents.create({ - sessionId: SessionId('preset-code'), - setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'code').then(() => undefined), + sessionId: SessionId('preset-ptc'), + setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'ptc').then(() => undefined), }) const native = await ctx.agents.create({ - sessionId: SessionId('preset-code-native'), + sessionId: SessionId('preset-ptc-native'), setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'standard').then(() => undefined), }) try { // One tool reaches the MODEL: the transport. The registry's catalog for - // this agent is unchanged — a code mode collapses the presentation, not + // this agent is unchanged — PTC mode collapses the presentation, not // the capabilities — so the assembly is what carries the claim. const assembly = await ctx.systemPrompt.assemble({ scope: coded.agent }) expect(assembly.tools.map(tool => tool.name)).toEqual(['run_code']) @@ -948,7 +948,7 @@ describe('a composition that configures its own preset roots', () => { ]) const listed = await rootsCtx.agentPresets.list() - expect(listed.map(preset => preset.id).sort()).toEqual(['code', 'cordis', 'minimal', 'standard', 'team-spec']) + expect(listed.map(preset => preset.id).sort()).toEqual(['cordis', 'minimal', 'ptc', 'standard', 'team-spec']) expect(listed.every(preset => preset.broken === undefined)).toBe(true) // The shipped root comes first: a configured directory claiming a shipped // id is shadowed, never the other way around. diff --git a/apps/cli/tests/windows-shell.spec.ts b/apps/cli/tests/windows-shell.spec.ts index f94ce582a6..5c9045e441 100644 --- a/apps/cli/tests/windows-shell.spec.ts +++ b/apps/cli/tests/windows-shell.spec.ts @@ -104,7 +104,7 @@ describe('the shipped shell composition (real bundle layers)', () => { describe('shipped agent presets gate both shell tools by platform', () => { const presetRoot = SHIPPED_PRESET_ROOT - it.each(['standard', 'code', 'cordis'])('preset %s gates its shell tool rows by platform', (preset) => { + it.each(['standard', 'ptc', 'cordis'])('preset %s gates its shell tool rows by platform', (preset) => { const entries: unknown = yaml.load( readFileSync(join(presetRoot, preset, 'agent.cordis.yml'), 'utf8'), { schema: entryListSchema }, diff --git a/apps/web/tests/expected/agent-preset-authoring/created.expected.md b/apps/web/tests/expected/agent-preset-authoring/created.expected.md index d82f48d8c1..8827af14ad 100644 --- a/apps/web/tests/expected/agent-preset-authoring/created.expected.md +++ b/apps/web/tests/expected/agent-preset-authoring/created.expected.md @@ -33,8 +33,8 @@ - text: 复制 - listitem: - 'button "设为默认: PTC 模式"': - - text: PTC 模式 内置 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 - - code: code + - text: PTC 模式 内置 具备标准模式的全部能力,并通过 PTC 模式 SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 + - code: ptc - 'button "查看: PTC 模式"': - img - text: 查看 diff --git a/apps/web/tests/expected/agent-preset-authoring/damaged.expected.md b/apps/web/tests/expected/agent-preset-authoring/damaged.expected.md index 0869bd7f3f..ed0ec29cc9 100644 --- a/apps/web/tests/expected/agent-preset-authoring/damaged.expected.md +++ b/apps/web/tests/expected/agent-preset-authoring/damaged.expected.md @@ -33,8 +33,8 @@ - text: 复制 - listitem: - 'button "设为默认: PTC 模式"': - - text: PTC 模式 内置 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 - - code: code + - text: PTC 模式 内置 具备标准模式的全部能力,并通过 PTC 模式 SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 + - code: ptc - 'button "查看: PTC 模式"': - img - text: 查看 diff --git a/apps/web/tests/expected/agent-preset-authoring/section.expected.md b/apps/web/tests/expected/agent-preset-authoring/section.expected.md index c8d983cb34..ec0e49aece 100644 --- a/apps/web/tests/expected/agent-preset-authoring/section.expected.md +++ b/apps/web/tests/expected/agent-preset-authoring/section.expected.md @@ -33,8 +33,8 @@ - text: 复制 - listitem: - 'button "设为默认: PTC 模式"': - - text: PTC 模式 内置 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 - - code: code + - text: PTC 模式 内置 具备标准模式的全部能力,并通过 PTC 模式 SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 + - code: ptc - 'button "查看: PTC 模式"': - img - text: 查看 diff --git a/apps/web/tests/expected/agent-preset-selection/menu.expected.md b/apps/web/tests/expected/agent-preset-selection/menu.expected.md index 4633f83fde..d0a80be760 100644 --- a/apps/web/tests/expected/agent-preset-selection/menu.expected.md +++ b/apps/web/tests/expected/agent-preset-selection/menu.expected.md @@ -2,7 +2,7 @@ - menuitem "Standard mode Full coding agent with file editing, shell, file and web search, skills, planning, goals, subagents, and workflows.": - text: Standard mode Full coding agent with file editing, shell, file and web search, skills, planning, goals, subagents, and workflows. - img - - menuitem "PTC mode All Standard mode capabilities, with tools exposed through the Code Mode SDK so the model can combine multi-step operations in one TypeScript program." + - menuitem "PTC mode All Standard mode capabilities, with tools exposed through the PTC mode SDK so the model can combine multi-step operations in one TypeScript program." - menuitem "Minimal mode Two-tool coding agent with persistent bash and str_replace_editor." - menuitem "Creator mode Built for creating custom agent presets, with all Standard mode capabilities plus runtime inspection, plugin experiments, and preset-authoring guidance." - menuitem "Refusing mode Resolves, then refuses to start." diff --git a/apps/web/tests/image-display.expected.e2e.ts b/apps/web/tests/image-display.expected.e2e.ts index 55dcedff13..3b5f8d00f2 100644 --- a/apps/web/tests/image-display.expected.e2e.ts +++ b/apps/web/tests/image-display.expected.e2e.ts @@ -1,5 +1,5 @@ // @vitest-environment jsdom -// Multimodal image surfaces over the BUILT client graph (the code-mode-fixture +// Multimodal image surfaces over the BUILT client graph (the ptc-fixture // idiom: real bundles via AppWebEntry, keyless fixture Connection RPC). // Opens the fixture history session whose turn 73 carries an image in BOTH a // user message and an assistant message, and pins the product surfaces: the diff --git a/apps/web/tests/code-mode-round.e2e.ts b/apps/web/tests/ptc-round.e2e.ts similarity index 90% rename from apps/web/tests/code-mode-round.e2e.ts rename to apps/web/tests/ptc-round.e2e.ts index 819521e6be..1098f6c203 100644 --- a/apps/web/tests/code-mode-round.e2e.ts +++ b/apps/web/tests/ptc-round.e2e.ts @@ -1,4 +1,4 @@ -// Code Mode browser round trip with nested sub-calls and details selection. +// PTC mode browser round trip with nested sub-calls and details selection. // Record: DSH_SNAPSHOT=record rewrites session.jsonl, then a keyless // DSH_SNAPSHOT=refresh regenerates ui.expected.md. import { readFile } from 'node:fs/promises' @@ -13,15 +13,15 @@ import { } from './scaffold.ts' import { connectFreshWorkspace, expandOwningTurnProcess, newEnglishPage, saveFailureShot } from './support.ts' -const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/code-mode-round/session.jsonl', import.meta.url)) -const UI_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/code-mode-round/ui.expected.md', import.meta.url)) +const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/ptc-round/session.jsonl', import.meta.url)) +const UI_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/ptc-round/ui.expected.md', import.meta.url)) const MODE = webSnapshotMode() // Elicits the successful and failed sub-rows this scenario asserts. const PROMPT = 'Using ONE run_code program: run bash `echo CODE_ROUND_OK`, then read the file missing.txt ' + 'catching its error in the program. Return an object with both outcomes. Then reply DONE and stop.' -describe('web e2e: Code Mode round renders nested sub-calls', () => { +describe('web e2e: PTC mode round renders nested sub-calls', () => { let scaffold: WebScaffold let browser: Browser let page: Page @@ -30,7 +30,7 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { beforeAll(async () => { scaffold = await launchWebScaffold({ - toolsMode: 'code', + toolsMode: 'ptc', compareReplaySession: true, ...(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 }), }) @@ -49,7 +49,7 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { }) it('drives the recorded prompt to a settled turn (all modes)', async () => { - onTestFailed(() => saveFailureShot(page, 'web-e2e-code-mode-drive')) + onTestFailed(() => saveFailureShot(page, 'web-e2e-ptc-drive')) if (MODE !== 'record') { expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT]) } @@ -89,7 +89,7 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { }) it.skipIf(MODE === 'record')('renders the code parent row with always-visible nested sub-rows', async () => { - onTestFailed(() => saveFailureShot(page, 'web-e2e-code-mode-rows')) + onTestFailed(() => saveFailureShot(page, 'web-e2e-ptc-rows')) await expect.poll(() => page.getByText('DONE', { exact: true }).count(), { timeout: 15_000 }).toBeGreaterThanOrEqual(1) // The parent run_code row wears the code variant with the model-authored // description as its summary (the presentCall contract). @@ -103,7 +103,7 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { }, 60_000) it.skipIf(MODE === 'record')('a bash sub-row click leaves the default details panel closed', async () => { - onTestFailed(() => saveFailureShot(page, 'web-e2e-code-mode-details')) + onTestFailed(() => saveFailureShot(page, 'web-e2e-ptc-details')) const nest = page.locator('[data-subcalls]').first() const frame = page.locator('[style*="grid-template-columns"]').first() expect(await frame.getAttribute('data-details-collapsed')).toBe('true') @@ -113,7 +113,7 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => { }) it.skipIf(MODE === 'record')('matches the expanded conversation aria golden with stable anchors', async () => { - onTestFailed(() => saveFailureShot(page, 'web-e2e-code-mode-aria')) + onTestFailed(() => saveFailureShot(page, 'web-e2e-ptc-aria')) const snapshot = await captureExpandedTurnProcessAria( page, '[class*="centerCol"]', diff --git a/apps/web/tests/scaffold.ts b/apps/web/tests/scaffold.ts index b664348c25..6a98024ec1 100644 --- a/apps/web/tests/scaffold.ts +++ b/apps/web/tests/scaffold.ts @@ -289,7 +289,7 @@ export interface LaunchOptions { * yml default. The code runtime row is always in the tree, so no extra * insertion is needed. */ - toolsMode?: 'native' | 'code' | 'both' + toolsMode?: 'native' | 'ptc' | 'both' /** * Insert the opt-in model-facing Cordis tool provider into the shipped tree. * Record and replay use the same tool surface, so captured request headers diff --git a/apps/web/tests/smoke-real.e2e.ts b/apps/web/tests/smoke-real.e2e.ts index ba28fd09ad..d5d1b0c6c8 100644 --- a/apps/web/tests/smoke-real.e2e.ts +++ b/apps/web/tests/smoke-real.e2e.ts @@ -590,16 +590,16 @@ describe('dsh web keyless CLI smoke', () => { } }, 120_000) - it('DSH_TOOLS_MODE=code collapses the provider wire tools to run_code with the SDK prompt section', async () => { + it('DSH_TOOLS_MODE=ptc collapses the provider wire tools to run_code with the SDK prompt section', async () => { requireDist() - const workspace = mkdtempSync(join(tmpdir(), 'dsh-web-code-mode-')) + const workspace = mkdtempSync(join(tmpdir(), 'dsh-web-ptc-')) - interface CodeModeProviderRequest { + interface PtcModeProviderRequest { messages?: { role?: string; content?: string }[] tools?: { function?: { name?: string } }[] } - let resolveProviderRequest!: (request: CodeModeProviderRequest) => void - const providerRequest = new Promise((resolve) => { + let resolveProviderRequest!: (request: PtcModeProviderRequest) => void + const providerRequest = new Promise((resolve) => { resolveProviderRequest = resolve }) const provider = createServer((request, response) => { @@ -607,7 +607,7 @@ describe('dsh web keyless CLI smoke', () => { request.setEncoding('utf8') request.on('data', (chunk: string) => { body += chunk }) request.on('end', () => { - resolveProviderRequest(JSON.parse(body) as CodeModeProviderRequest) + resolveProviderRequest(JSON.parse(body) as PtcModeProviderRequest) response.writeHead(200, { 'content-type': 'text/event-stream' }) response.end([ 'data: {"choices":[{"delta":{"role":"assistant","content":null,"reasoning_content":""}}]}', @@ -629,9 +629,9 @@ describe('dsh web keyless CLI smoke', () => { cwd: workspace, env: { ...process.env, - DEEPSEEK_API_KEY: 'keyless-web-code-mode', + DEEPSEEK_API_KEY: 'keyless-web-ptc', DEEPSEEK_BASE_URL: `http://127.0.0.1:${address.port}`, - DSH_TOOLS_MODE: 'code', + DSH_TOOLS_MODE: 'ptc', DSH_HOME: join(workspace, '.dsh'), DSH_AGENTS_HOME: join(workspace, '.agents'), TSX_TSCONFIG_PATH: join(REPO_ROOT, 'tsconfig.json'), diff --git a/apps/web/tests/trajectory-image-display.expected.e2e.ts b/apps/web/tests/trajectory-image-display.expected.e2e.ts index 94607563d8..315d3baa1e 100644 --- a/apps/web/tests/trajectory-image-display.expected.e2e.ts +++ b/apps/web/tests/trajectory-image-display.expected.e2e.ts @@ -1,5 +1,5 @@ // @vitest-environment jsdom -// Trajectory image surfaces over the BUILT client graph (the code-mode-fixture +// Trajectory image surfaces over the BUILT client graph (the ptc-fixture // idiom: real bundles via AppWebEntry, keyless fixture Connection RPC). // Opens the fixture history session whose turn 73 carries an image in BOTH a // user message and an assistant message, and pins the Trajectory surfaces: diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index f261db5614..2357de9d14 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -59,7 +59,7 @@ "tests/sidebar-scrollbar.e2e.ts", "tests/rail-search-expand.e2e.ts", "tests/conversation-column-overflow.e2e.ts", - "tests/code-mode-round.e2e.ts", + "tests/ptc-round.e2e.ts", "tests/composer-draft-scroll.e2e.ts", "tests/cordis-tool-round.e2e.ts", "tests/web-search-round.e2e.ts", diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index b9d870136b..9c48fe3636 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.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/config-catalog.md -config-catalog.md: 3b05dbf6a9e4a591e876bd6993522223146288fb -config-catalog.zh.md: cbcc3d1acc9664ce3c92eb7dcb3fc0d12a8c466d +config-catalog.md: cb2dfbdee786b2b6203e4e6f4f08fe8ad1d84a2f +config-catalog.zh.md: 60ccf2bf7a001caa2e0fd305476c0ef73fc8db2c diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 3b05dbf6a9..cb2dfbdee7 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -3126,13 +3126,13 @@ Requires: `systemPrompt` /** Plugin config: how the registered tools are presented to the model. */ export interface Config { /** - * Model presentation. `native` (default) sends every visible schema; `code` + * Model presentation. `native` (default) sends every visible schema; `ptc` * sends only `run_code` plus a generated SDK prompt and collapses the * executor to the same surface (a model-direct call may only name * `run_code`; `run_code` SDK sub-dispatches keep every visible tool); `both` - * sends both forms. Code modes require a `ctx.codeRuntime` whose `language` + * sends both forms. PTC mode requires a `ctx.codeRuntime` whose `language` * has a registered SDK renderer (TypeScript or Python) and fail prompt - * assembly when it is absent or has no renderer. Under `code`, native names + * assembly when it is absent or has no renderer. Under `ptc`, native names * in `toolOrder` are invalid. */ mode?: ToolPresentationMode @@ -3147,7 +3147,7 @@ export interface Config { } /** How the registry presents its tools to the model (see {@link Config.mode}). */ -export type ToolPresentationMode = 'native' | 'code' | 'both' +export type ToolPresentationMode = 'native' | 'ptc' | 'both' ``` Source: [`packages/core/tools/src/index.ts:655`](../packages/core/tools/src/index.ts) diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index cbcc3d1acc..60ccf2bf7a 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -3128,13 +3128,13 @@ export interface Config { /** Plugin config: how the registered tools are presented to the model. */ export interface Config { /** - * Model presentation. `native` (default) sends every visible schema; `code` + * Model presentation. `native` (default) sends every visible schema; `ptc` * sends only `run_code` plus a generated SDK prompt and collapses the * executor to the same surface (a model-direct call may only name * `run_code`; `run_code` SDK sub-dispatches keep every visible tool); `both` - * sends both forms. Code modes require a `ctx.codeRuntime` whose `language` + * sends both forms. PTC mode requires a `ctx.codeRuntime` whose `language` * has a registered SDK renderer (TypeScript or Python) and fail prompt - * assembly when it is absent or has no renderer. Under `code`, native names + * assembly when it is absent or has no renderer. Under `ptc`, native names * in `toolOrder` are invalid. */ mode?: ToolPresentationMode @@ -3149,7 +3149,7 @@ export interface Config { } /** How the registry presents its tools to the model (see {@link Config.mode}). */ -export type ToolPresentationMode = 'native' | 'code' | 'both' +export type ToolPresentationMode = 'native' | 'ptc' | 'both' ``` 来源:[`packages/core/tools/src/index.ts:655`](../packages/core/tools/src/index.ts) diff --git a/docs/cookbook/adding-a-tool.i18n.yaml b/docs/cookbook/adding-a-tool.i18n.yaml index 3aaabe5ef5..9d02664a90 100644 --- a/docs/cookbook/adding-a-tool.i18n.yaml +++ b/docs/cookbook/adding-a-tool.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/cookbook/adding-a-tool.md -adding-a-tool.md: 9ec025426a2304f4bda56ddb8e6f5c42e3e8f4a8 -adding-a-tool.zh.md: 2c73b40ff53b5b58507b716ff32dd1fea26148f4 +adding-a-tool.md: f6b2703f3db69a6abc04dbd0fd9893555eed0602 +adding-a-tool.zh.md: d4831e2be061c2565e88004062cbb1558491bb9d diff --git a/docs/cookbook/adding-a-tool.md b/docs/cookbook/adding-a-tool.md index 9ec025426a..f6b2703f3d 100644 --- a/docs/cookbook/adding-a-tool.md +++ b/docs/cookbook/adding-a-tool.md @@ -50,7 +50,7 @@ Registration is effect-based: disposing the plugin fiber unregisters the tool. S ## Long-running work -Gate `run_in_background` with producer config, then register through `ctx.jobs.start({ kind, label, owner: exec.agent, run })`. The registry rejects a pre-aborted invocation before the producer body; the runtime validates ownership and task-controller availability before `run()` starts work, then supplies the id, session fence, generic control tools, notices, and owner cleanup. A successful background branch returns a typed canonical handle such as `{ kind: 'background', jobId }`; its Native renderer may keep human prose such as `started background job bash-1`, but Code Mode must never parse that prose to recover the id. +Gate `run_in_background` with producer config, then register through `ctx.jobs.start({ kind, label, owner: exec.agent, run })`. The registry rejects a pre-aborted invocation before the producer body; the runtime validates ownership and task-controller availability before `run()` starts work, then supplies the id, session fence, generic control tools, notices, and owner cleanup. A successful background branch returns a typed canonical handle such as `{ kind: 'background', jobId }`; its Native renderer may keep human prose such as `started background job bash-1`, but PTC mode must never parse that prose to recover the id. The producer supplies synchronous `cancel`, non-rejecting `done` that settles after resource cleanup, and optional consuming `readOutput` with bounded-output formatting. A pre-aborted call is a failure because no task exists whose id could satisfy the successful output schema. Once `ctx.jobs.start()` publishes the id, use a task-owned cancellation signal rather than `exec.signal`: later outer-call cancellation stops waiting for the call but does not kill published work; `job_kill`, owner disposal, and service teardown own that lifetime. Foreground work remains coupled to `exec.signal`. See the [background job runtime Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md) and `dsh-tool-bash` for a stream producer. @@ -58,9 +58,9 @@ The producer supplies synchronous `cancel`, non-rejecting `done` that settles af Prefer not to build deployment policy into the tool. Use `tools/pre-execute` for extensible allow/deny/ask policy (the [permission-gate example](extension-cookbook.md#a-hook-plugin-permission-gate-example)), `ctx.tools.guard()` for a final monotonic deny that later listeners cannot undo, `tools/execute` to wrap dispatch with a deadline, retry, or metrics collection, `tools/post-execute` to replace presentation content or the returned value, block the result, or attach model-facing context, and `tools/result` to observe the immutable normalized outcome. A content replacement leaves programmatic access to `value` intact; confidentiality policy blocks or replaces the value. A sandboxing implementation can also run inside the tool's executor implementation; the [`dsh-tools` README](../../packages/core/tools/README.md#extension-points) defines each extension point's inputs, order, return values, and failure behavior. -## Code Mode reaches your tool for free +## PTC mode reaches your tool for free -In [Code Mode](../../packages/core/tools/README.md), every visible registered tool is available as `await tools.(args)` without extra integration. The generated `ToolArgsMap` and `ToolOutputMap` derive exact argument and canonical-return types from the same schemas, and calls re-enter the normal execution pipeline. A successful call resolves to the final canonical JSON value after policy, not to rendered Native content. A failed call rejects with the real `ToolCallError`; programs can inspect only its `name`, `toolName`, and human-readable `message`, not internal error codes or a failure union. +In [PTC mode](../../packages/core/tools/README.md), every visible registered tool is available as `await tools.(args)` without extra integration. The generated `ToolArgsMap` and `ToolOutputMap` derive exact argument and canonical-return types from the same schemas, and calls re-enter the normal execution pipeline. A successful call resolves to the final canonical JSON value after policy, not to rendered Native content. A failed call rejects with the real `ToolCallError`; programs can inspect only its `name`, `toolName`, and human-readable `message`, not internal error codes or a failure union. Design `output.schema` as a useful programmatic API: return handles and fields directly, allow scalar/array/null roots when they are the honest value, and keep human explanation in `output.render`. Intermediate values are execution-local, are not persisted or prompt-truncated, and have no byte cap, so the producer's truthful acquisition bounds and process memory still matter. Only the outer `run_code` logs/result cross the configurable output cap and model-facing spill pipeline. diff --git a/docs/cookbook/adding-a-tool.zh.md b/docs/cookbook/adding-a-tool.zh.md index 2c73b40ff5..d4831e2be0 100644 --- a/docs/cookbook/adding-a-tool.zh.md +++ b/docs/cookbook/adding-a-tool.zh.md @@ -50,7 +50,7 @@ export function apply(ctx: Context) { ## 长时间运行的工作 -通过 producer 配置控制 `run_in_background`,然后使用 `ctx.jobs.start({ kind, label, owner: exec.agent, run })` 注册任务。注册表会在进入 producer 主体前将已预先中止的调用判为失败;运行时会在 `run()` 启动工作前校验 owner 和任务控制器是否可用,随后提供 id、会话围栏、通用控制工具、通知和 owner cleanup。成功的后台分支会返回类型化的规范句柄,如 `{ kind: 'background', jobId }`;其 Native 渲染器可以保留 `started background job bash-1` 这类供人阅读的自然语言,但 Code Mode 绝不能通过解析该文本取得 id。 +通过 producer 配置控制 `run_in_background`,然后使用 `ctx.jobs.start({ kind, label, owner: exec.agent, run })` 注册任务。注册表会在进入 producer 主体前将已预先中止的调用判为失败;运行时会在 `run()` 启动工作前校验 owner 和任务控制器是否可用,随后提供 id、会话围栏、通用控制工具、通知和 owner cleanup。成功的后台分支会返回类型化的规范句柄,如 `{ kind: 'background', jobId }`;其 Native 渲染器可以保留 `started background job bash-1` 这类供人阅读的自然语言,但 PTC mode 绝不能通过解析该文本取得 id。 producer 提供同步的 `cancel`、在资源清理后 settle 且不 reject 的 `done`,以及可选的消费式 `readOutput`(负责有界输出的格式化)。预先中止的调用属于失败,因为此时没有任务,其 id 无法满足成功输出 schema。`ctx.jobs.start()` 发布 id 后,应使用任务自有的取消信号,而不是 `exec.signal`:之后取消外层调用只会停止等待本次调用,不会终止已经发布的工作;该生命周期归 `job_kill`、owner dispose 和服务 teardown 所有。前台工作仍与 `exec.signal` 耦合。流式 producer 的示例和完整约定见[后台任务运行时 Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md)与 `dsh-tool-bash`。 @@ -60,9 +60,9 @@ producer 提供同步的 `cancel`、在资源清理后 settle 且不 reject 的 尽量不要把部署策略内建到工具中。使用 `tools/pre-execute` 实现可扩展的允许/拒绝/询问策略(见[权限门禁示例](extension-cookbook.zh.md#a-hook-plugin-permission-gate-example));使用 `ctx.tools.guard()` 设置最终的单调拒绝,后续监听器无法撤销;使用 `tools/execute` 为分发添加截止时间、重试或指标收集;使用 `tools/post-execute` 替换展示内容或返回值、阻止结果,或附加模型可见上下文;使用 `tools/result` 观测不可变的归一化结果而不改变它。替换内容不会阻止程序化访问 `value`;保密策略会屏蔽或替换该值。沙箱实现也可以在工具的执行器实现中运行;[`dsh-tools` README](../../packages/core/tools/README.zh.md#extension-points) 定义每个扩展点的输入、顺序、返回值和失败行为。 -## Code Mode 自动触达你的工具 +## PTC mode 自动触达你的工具 -在 [Code Mode](../../packages/core/tools/README.zh.md) 中,每个可见的已注册工具都可通过 `await tools.(args)` 调用,无需额外集成。生成的 `ToolArgsMap` 和 `ToolOutputMap` 会根据同一组 schema 分别派生精确的参数类型与规范返回类型,调用则重新进入正常的执行流水线。成功调用会解析为策略处理后的最终规范 JSON 值,而不是渲染后的 Native 内容。失败调用会以真正的 `ToolCallError` reject;程序只能检查其 `name`、`toolName` 和可供人阅读的 `message`,无法取得内部错误代码或失败联合。 +在 [PTC mode](../../packages/core/tools/README.zh.md) 中,每个可见的已注册工具都可通过 `await tools.(args)` 调用,无需额外集成。生成的 `ToolArgsMap` 和 `ToolOutputMap` 会根据同一组 schema 分别派生精确的参数类型与规范返回类型,调用则重新进入正常的执行流水线。成功调用会解析为策略处理后的最终规范 JSON 值,而不是渲染后的 Native 内容。失败调用会以真正的 `ToolCallError` reject;程序只能检查其 `name`、`toolName` 和可供人阅读的 `message`,无法取得内部错误代码或失败联合。 请把 `output.schema` 设计为实用的程序化 API:直接返回句柄与字段;当标量、数组或 null 确实就是结果时,允许采用相应的根类型;将面向人类的解释放入 `output.render`。中间值只存在于执行期间,不会被持久化或按提示词上限截断,也不设字节上限,因此生产方如实声明的采集边界和进程内存仍然重要。只有外层 `run_code` 日志/结果会受到可配置输出上限和面向模型的 spill 流水线约束。 diff --git a/docs/cookbook/extension-cookbook.i18n.yaml b/docs/cookbook/extension-cookbook.i18n.yaml index 24c280ae11..b24e6b88b2 100644 --- a/docs/cookbook/extension-cookbook.i18n.yaml +++ b/docs/cookbook/extension-cookbook.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/cookbook/extension-cookbook.md -extension-cookbook.md: 7b8c55837038aa421850449ebf553eb1ef47218f -extension-cookbook.zh.md: 899fcca4ad3c8977044da5a17e40283905abc752 +extension-cookbook.md: bb3f327691d1ff6199407c241f4fa77cd4b23b87 +extension-cookbook.zh.md: db8f0850bdada35cb6d33eed874c44f20d7be9d3 diff --git a/docs/cookbook/extension-cookbook.md b/docs/cookbook/extension-cookbook.md index 7b8c558370..bb3f327691 100644 --- a/docs/cookbook/extension-cookbook.md +++ b/docs/cookbook/extension-cookbook.md @@ -96,7 +96,7 @@ Shipped applications contribute profile layers through `packages/bundle/*/cordis Every product feature maps to a listener on a documented extension point — the microkernel claim made checkable ([microkernel Agent Note](../../.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md)). No row modifies the loop. -`system-prompt/assemble` is an expert cooperative whole-assembly transform: its returned assembly is authoritative, so listener authors own preserving active Code Mode and structured-output protocol contributions. Prefer `ctx.tools.restrict()` for tool filtering that must stay aligned across presentation, lookup, and execution. +`system-prompt/assemble` is an expert cooperative whole-assembly transform: its returned assembly is authoritative, so listener authors own preserving active PTC mode and structured-output protocol contributions. Prefer `ctx.tools.restrict()` for tool filtering that must stay aligned across presentation, lookup, and execution. | Product feature | Plugin mechanism | |---|---| diff --git a/docs/cookbook/extension-cookbook.zh.md b/docs/cookbook/extension-cookbook.zh.md index 899fcca4ad..db8f0850bd 100644 --- a/docs/cookbook/extension-cookbook.zh.md +++ b/docs/cookbook/extension-cookbook.zh.md @@ -100,7 +100,7 @@ export function apply(ctx: Context) { 每个产品功能都映射到一个文档化扩展点上的监听器——微内核声明由此可验证([微内核 Agent Note](../../.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md))。没有任何一行修改循环本身。 -`system-prompt/assemble` 是一个专家协作式的整体装配变换:其返回的装配结果具有权威性,因此监听器作者有责任保留活跃的 Code Mode 和结构化输出协议的贡献。对于需要在展示、查找和执行之间保持对齐的工具过滤,优先使用 `ctx.tools.restrict()`。 +`system-prompt/assemble` 是一个专家协作式的整体装配变换:其返回的装配结果具有权威性,因此监听器作者有责任保留活跃的 PTC mode 和结构化输出协议的贡献。对于需要在展示、查找和执行之间保持对齐的工具过滤,优先使用 `ctx.tools.restrict()`。 | 产品功能 | 插件机制 | |---|---| diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 14704caf43..c45e00ced7 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.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/development.md -development.md: 2647c0eddacb22d1f96e1e780e4c9f1d16b4ae0d -development.zh.md: b7ac5fb4d46de68f502dbcab8eef50944055718f +development.md: 7fc6ae14266253b9e50a1a5f3e9ee6f8e6fbcde7 +development.zh.md: 79e25b489d4c7c3f425460d5bffd0c0265f5b02b diff --git a/docs/development.md b/docs/development.md index 2647c0edda..7fc6ae1426 100644 --- a/docs/development.md +++ b/docs/development.md @@ -140,10 +140,10 @@ The one-shot Headless coding agent needs `DEEPSEEK_API_KEY` in the environment o pnpm dsh --profile headless "summarize this workspace" ``` -The Code Mode demo runs the same headless profile with code presentation enabled: +The PTC mode demo runs the same headless profile with code presentation enabled: ```sh -pnpm run demo:code-mode -- "summarize this workspace" +pnpm run demo:ptc -- "summarize this workspace" ``` ### TODO markers diff --git a/docs/development.zh.md b/docs/development.zh.md index b7ac5fb4d4..79e25b489d 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -144,10 +144,10 @@ pnpm run build pnpm dsh --profile headless "summarize this workspace" ``` -Code Mode 演示启用代码式工具展示,并运行同一个 headless profile: +PTC mode 演示启用代码式工具展示,并运行同一个 headless profile: ```sh -pnpm run demo:code-mode -- "summarize this workspace" +pnpm run demo:ptc -- "summarize this workspace" ``` ### TODO 标记 diff --git a/docs/event-producer-consumer.i18n.yaml b/docs/event-producer-consumer.i18n.yaml index 749e1e0695..7883982b1b 100644 --- a/docs/event-producer-consumer.i18n.yaml +++ b/docs/event-producer-consumer.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/event-producer-consumer.md -event-producer-consumer.md: 8c30d5a945e2abb21c1e8dad5d6aa4e472c18b23 -event-producer-consumer.zh.md: 29304d9ac855a36ffc8a5e6766bedc841cf2738c +event-producer-consumer.md: 8c3a8a3f0e19f50e5a3164e2f5f4ac0a4e1046eb +event-producer-consumer.zh.md: 65b8fba3dd5b9654ffbd037dded15abe85c5885c diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 8c30d5a945..8c3a8a3f0e 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -59,10 +59,10 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`system-prompt`](../packages/core/system-prompt) | | `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - | | `tools/change` | `emit` | [`packages/core/tools/src/index.ts:207`](../packages/core/tools/src/index.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`), [`tools`](../packages/core/tools) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) | -| `tools/code-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:189`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | | `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:163`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` | | `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:175`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | | `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:152`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs) | +| `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:189`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | | `tools/result` | `emit` | [`packages/core/tools/src/index.ts:197`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | | `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:85`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes` | | `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:34`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `inspector`, `modules` | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index 29304d9ac8..65b8fba3dd 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -61,10 +61,10 @@ | `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`system-prompt`](../packages/core/system-prompt) | | `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - | | `tools/change` | `emit` | [`packages/core/tools/src/index.ts:207`](../packages/core/tools/src/index.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`), [`tools`](../packages/core/tools) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) | -| `tools/code-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:189`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | | `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:163`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` | | `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:175`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | | `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:152`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs) | +| `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:189`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | | `tools/result` | `emit` | [`packages/core/tools/src/index.ts:197`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | | `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:85`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes` | | `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:34`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `inspector`, `modules` | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index 6a61b25f17..7613f6fa12 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.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/persistence-catalog.md -persistence-catalog.md: 6a48b9c674375c6b5fa8b296afb7658a9d508168 -persistence-catalog.zh.md: 367b1c1a11324cea057ff03d0c456d531edc0183 +persistence-catalog.md: 2143f7881edad36a986869fcbb458ae464014a44 +persistence-catalog.zh.md: e37d6ed5fd1c7bd3730c411312fef186c89a4b20 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 6a48b9c674..2143f7881e 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -870,7 +870,7 @@ Source: [`packages/core/session/src/types.ts:268`](../packages/core/session/src/ * before returning), so its execution-enclosure relation holds by * construction. */ -'tool/code-dispatch': CodeDispatchEventData +'tool/code-dispatch': PtcDispatchEventData ``` Source: [`packages/core/tools/src/types.ts:56`](../packages/core/tools/src/types.ts) @@ -893,7 +893,7 @@ Source: [`packages/core/tools/src/types.ts:56`](../packages/core/tools/src/types * with `tool/code-dispatch` by `subCallId` (timing = the two events' * `time` fields). */ -'tool/code-dispatch-start': CodeDispatchStartEventData +'tool/code-dispatch-start': PtcDispatchStartEventData ``` Source: [`packages/core/tools/src/types.ts:40`](../packages/core/tools/src/types.ts) diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 367b1c1a11..e37d6ed5fd 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -852,9 +852,9 @@ export type SessionEvent = { 来源:[`packages/core/session/src/types.ts:266`](../packages/core/session/src/types.ts) - + -#### `tool/code-dispatch` — log-only +#### `tool/ptc-dispatch` — log-only ```ts persistence-catalog /** @@ -872,14 +872,14 @@ export type SessionEvent = { * before returning), so its execution-enclosure relation holds by * construction. */ -'tool/code-dispatch': CodeDispatchEventData +'tool/code-dispatch': PtcDispatchEventData ``` 来源:[`packages/core/tools/src/types.ts:56`](../packages/core/tools/src/types.ts) - + -#### `tool/code-dispatch-start` — log-only +#### `tool/ptc-dispatch-start` — log-only ```ts persistence-catalog /** @@ -895,7 +895,7 @@ export type SessionEvent = { * with `tool/code-dispatch` by `subCallId` (timing = the two events' * `time` fields). */ -'tool/code-dispatch-start': CodeDispatchStartEventData +'tool/code-dispatch-start': PtcDispatchStartEventData ``` 来源:[`packages/core/tools/src/types.ts:40`](../packages/core/tools/src/types.ts) diff --git a/docs/subsystems/code-runtime.i18n.yaml b/docs/subsystems/code-runtime.i18n.yaml index 97886685e5..228ed14b56 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: ae760487eff19f6a0b91620b92007d0a86eb1589 -code-runtime.zh.md: b48e1a01b2817d9dafb25ac96673d00cf7d9ed08 +code-runtime.md: 0f633df9fc657d9d80fc04df3bc8ad6fafdddcb2 +code-runtime.zh.md: 43b78ce49575741f7ae6c4e2751b63b7562fc99a diff --git a/docs/subsystems/code-runtime.md b/docs/subsystems/code-runtime.md index ae760487ef..0f633df9fc 100644 --- a/docs/subsystems/code-runtime.md +++ b/docs/subsystems/code-runtime.md @@ -2,7 +2,7 @@ English | [中文](code-runtime.zh.md) -The code-execution seam — a [capability seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md) whose Service Definition ([dsh-code-runtime](../../packages/code-runtime/code-runtime), `ctx.codeRuntime`) runs one model-written program against host-provided async bindings and reports what it printed and returned. Code execution is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](core.md). Backends differ by execution substrate and source language, both readonly descriptors on the service; the worker-thread Service Provider and tool-registry Consumer are specified by the [Code Mode foundation](../../.agents/notes/implemented/feature/2026-06-15-code-mode.md) and [typed-return contract](../../.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md). +The code-execution seam — a [capability seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md) whose Service Definition ([dsh-code-runtime](../../packages/code-runtime/code-runtime), `ctx.codeRuntime`) runs one model-written program against host-provided async bindings and reports what it printed and returned. Code execution is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](core.md). Backends differ by execution substrate and source language, both readonly descriptors on the service; the worker-thread Service Provider and tool-registry Consumer are specified by the [PTC mode foundation](../../.agents/notes/implemented/feature/2026-06-15-ptc.md) and [typed-return contract](../../.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.md). Source: [`packages/code-runtime/code-runtime/src/types.ts`](../../packages/code-runtime/code-runtime/src/types.ts) @@ -61,7 +61,7 @@ interface CodeRunResult { ## Bindings: host functions as program globals -Each `CodeBindingNamespace` becomes one global object of async callables inside the program (the Code Mode consumer passes one: `tools`). Arguments and resolutions must be lossless JSON and cross without a seam-level byte cap; the runtime may bridge them through structured clone. A namespace may declare a program-visible error class without making the runtime know the consumer's names: the runtime injects the real constructor and turns rejected calls into its instances. A runtime also treats binding names as hostile input (`__proto__` is an ordinary own property, never a prototype collision): +Each `CodeBindingNamespace` becomes one global object of async callables inside the program (the PTC mode consumer passes one: `tools`). Arguments and resolutions must be lossless JSON and cross without a seam-level byte cap; the runtime may bridge them through structured clone. A namespace may declare a program-visible error class without making the runtime know the consumer's names: the runtime injects the real constructor and turns rejected calls into its instances. A runtime also treats binding names as hostile input (`__proto__` is an ordinary own property, never a prototype collision): ```ts type-equiv /** @@ -69,7 +69,7 @@ Each `CodeBindingNamespace` becomes one global object of async callables inside * injects a real error constructor under `name`; rejected member calls become * its instances and expose the exact member name through * `memberNameProperty`. Both strings are runtime data rather than knowledge - * of a particular consumer such as Code Mode. + * of a particular consumer such as PTC mode. */ interface CodeBindingErrorClass { /** Constructor global and resulting `Error.name`; same portable identifier rule as {@link CodeBindingNamespace.global}. */ diff --git a/docs/subsystems/code-runtime.zh.md b/docs/subsystems/code-runtime.zh.md index b48e1a01b2..43b78ce495 100644 --- a/docs/subsystems/code-runtime.zh.md +++ b/docs/subsystems/code-runtime.zh.md @@ -2,7 +2,7 @@ [English](code-runtime.md) | 中文 -代码执行 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md):其 Service Definition([dsh-code-runtime](../../packages/code-runtime/code-runtime),`ctx.codeRuntime`)使用宿主提供的异步绑定运行一段模型编写的程序,并报告其打印内容与返回值。代码执行是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](core.zh.md) 中。各后端的执行基底与源语言不同,这两项均为服务上的只读描述符;worker-thread Service Provider 与工具注册表 Consumer 的约定见 [Code Mode 基础设计](../../.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md) 和[类型化返回约定](../../.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md)。 +代码执行 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md):其 Service Definition([dsh-code-runtime](../../packages/code-runtime/code-runtime),`ctx.codeRuntime`)使用宿主提供的异步绑定运行一段模型编写的程序,并报告其打印内容与返回值。代码执行是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](core.zh.md) 中。各后端的执行基底与源语言不同,这两项均为服务上的只读描述符;worker-thread Service Provider 与工具注册表 Consumer 的约定见 [PTC mode 基础设计](../../.agents/notes/implemented/feature/2026-06-15-ptc.zh.md) 和[类型化返回约定](../../.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.zh.md)。 源码:[`packages/code-runtime/code-runtime/src/types.ts`](../../packages/code-runtime/code-runtime/src/types.ts) @@ -61,7 +61,7 @@ interface CodeRunResult { ## 绑定:宿主函数作为程序全局变量 -每个 `CodeBindingNamespace` 在程序内成为一个由异步可调用函数组成的全局对象(Code Mode Consumer 传入一个:`tools`)。参数与返回值必须是无损 JSON,且跨越边界时不受 seam 层字节上限约束;运行时可以通过结构化克隆桥接它们。命名空间可以声明程序可见的错误类,而无需让运行时知道 Consumer 的名称:运行时会注入真实构造函数,并将被拒绝的调用转为该类的实例。运行时也将绑定名视为不可信输入(`__proto__` 是普通自有属性,绝不会发生原型碰撞): +每个 `CodeBindingNamespace` 在程序内成为一个由异步可调用函数组成的全局对象(PTC mode Consumer 传入一个:`tools`)。参数与返回值必须是无损 JSON,且跨越边界时不受 seam 层字节上限约束;运行时可以通过结构化克隆桥接它们。命名空间可以声明程序可见的错误类,而无需让运行时知道 Consumer 的名称:运行时会注入真实构造函数,并将被拒绝的调用转为该类的实例。运行时也将绑定名视为不可信输入(`__proto__` 是普通自有属性,绝不会发生原型碰撞): ```ts type-equiv /** @@ -69,7 +69,7 @@ interface CodeRunResult { * injects a real error constructor under `name`; rejected member calls become * its instances and expose the exact member name through * `memberNameProperty`. Both strings are runtime data rather than knowledge - * of a particular consumer such as Code Mode. + * of a particular consumer such as PTC mode. */ interface CodeBindingErrorClass { /** Constructor global and resulting `Error.name`; same portable identifier rule as {@link CodeBindingNamespace.global}. */ diff --git a/docs/tool-catalog.i18n.yaml b/docs/tool-catalog.i18n.yaml index 569d755993..acb9055376 100644 --- a/docs/tool-catalog.i18n.yaml +++ b/docs/tool-catalog.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/tool-catalog.md -tool-catalog.md: 16142c2f7d98cf1037b034d2836c742b62f26594 -tool-catalog.zh.md: 533caacc7a923b5ea36f527292f420b610f1c488 +tool-catalog.md: 91ff093e79cc05a2c08b5aa1130378440cd963f3 +tool-catalog.zh.md: 48627ed04fd264f9ea28842a94596667b9629ec5 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 16142c2f7d..91ff093e79 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -16,7 +16,7 @@ This table connects model-visible tool names to the plugin package and service s | Tool package | Model-visible names | Requires | Writes / affects | Shipped aliases | Deployment note | | --- | --- | --- | --- | --- | --- | | `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`, `ctx.userQuestions` | `tool/call`, `tool/result after a UI/provider answers the question` | - | ask_user_question pauses the tool call until the active UI provider returns a human answer. | -| `@deepseek-ai/dsh-tools` | `run_code` | `ctx.tools`, `ctx.codeRuntime (execution time)`, `ctx.systemPrompt` | `tool/call`, `one tool/code-dispatch-start + tool/code-dispatch pair per bridged sub-call`, `tool/result` | - | Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: code` / `mode: both` (see the Code Mode Agent Note). Under `code` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. | +| `@deepseek-ai/dsh-tools` | `run_code` | `ctx.tools`, `ctx.codeRuntime (execution time)`, `ctx.systemPrompt` | `tool/call`, `one tool/code-dispatch-start + tool/code-dispatch pair per bridged sub-call`, `tool/result` | - | Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: ptc` / `mode: both` (see the PTC mode Agent Note). Under `ptc` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. | | `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`, `ctx.systemPrompt`, `ctx.userQuestions (execution time, opportunistic)` | `tool/call`, `plan/mode inactive on an approved review`, `tool/result` | - | exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary. | | `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs at call time for run_in_background` | `tool/call`, `tool/result` | - | The bash tool is the model-facing consumer of the bash executor seam. A `run_in_background` run registers with the generic `ctx.jobs` runtime and is collected/stopped through the `job_*` tools from `@deepseek-ai/dsh-tool-jobs`; the `enableRunInBackground` config (default true) removes the parameter entirely when disabled. | | `@deepseek-ai/dsh-tool-pwsh` | `pwsh` | `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs at call time for run_in_background` | `tool/call`, `tool/result` | - | The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.shell`); it mirrors the bash tool call-for-call minus sandbox controls — `run_in_background` runs register with the generic `ctx.jobs` runtime and are collected/stopped through the `job_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-shell-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\...` paths and `$env:NAME` variables. | @@ -144,9 +144,9 @@ Execute a TypeScript program against the available tools. Takes two required arg } ``` -Source: [`packages/core/tools/src/code-mode.ts`](../packages/core/tools/src/code-mode.ts) +Source: [`packages/core/tools/src/ptc.ts`](../packages/core/tools/src/ptc.ts) -Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: code` / `mode: both` (see the Code Mode Agent Note). Under `code` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. +Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: ptc` / `mode: both` (see the PTC mode Agent Note). Under `ptc` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index 533caacc7a..48627ed04f 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -20,7 +20,7 @@ | 工具包 | 模型可见名称 | 依赖 | 写入/影响 | 随产品发布的别名 | 部署说明 | | --- | --- | --- | --- | --- | --- | | `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`、`ctx.userQuestions` | `tool/call`、`tool/result after a UI/provider answers the question` | - | ask_user_question 会暂停工具调用,直到当前 UI 提供方返回人类答案。 | -| `@deepseek-ai/dsh-tools` | `run_code` | `ctx.tools`、`ctx.codeRuntime (execution time)`、`ctx.systemPrompt` | `tool/call`、`one tool/code-dispatch-start + tool/code-dispatch pair per bridged sub-call`、`tool/result` | - | 在 `mode: code`/`mode: both` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 Code Mode Agent Note)。在 `code` 下,它是注册表对协议格式(wire format)的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。 | +| `@deepseek-ai/dsh-tools` | `run_code` | `ctx.tools`、`ctx.codeRuntime (execution time)`、`ctx.systemPrompt` | `tool/call`、`one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`、`tool/result` | - | 在 `mode: ptc`/`mode: both` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 PTC mode Agent Note)。在 `code` 下,它是注册表对协议格式(wire format)的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。 | | `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`、`ctx.systemPrompt`、`ctx.userQuestions (execution time, opportunistic)` | `tool/call`、`plan/mode inactive on an approved review`、`tool/result` | - | 规划未激活时,exit_plan_mode 仍保留在面向模型的 schema 中,这样状态转换不会在规划策略变更之外额外造成工具目录变动。其执行路径会拒绝规划模式之外的调用;在规划模式下,它通过用户交互 seam 提交计划(批准/根据反馈继续规划),批准后会在步骤边界记录规划模式已停用。 | | `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.tools`、`ctx.shell`、`ctx.systemPrompt`、`ctx.shellEnv`、`ctx.jobs at call time for run_in_background` | `tool/call`、`tool/result` | - | bash 工具是 bash 执行器 seam 面向模型的消费方。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具(来自 `@deepseek-ai/dsh-tool-jobs`)收集/停止;禁用 `enableRunInBackground` 配置(默认为 true)后,该参数会被完全移除。 | | `@deepseek-ai/dsh-tool-pwsh` | `pwsh` | `ctx.tools`、`ctx.shell`、`ctx.systemPrompt`、`ctx.shellEnv`、`ctx.jobs at call time for run_in_background` | `tool/call`、`tool/result` | - | pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费方(由 `@deepseek-ai/dsh-pwsh-local` 等 PowerShell 执行器为 `ctx.shell` 提供后端);除沙箱接口外,它逐项对应 bash 工具调用。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具收集/停止;托管的 `DSH_*` 环境来自 `@deepseek-ai/dsh-shell-env`。每次调用都在新进程中运行,不使用持久 PTY 会话。路径采用原生 `C:\...` 形式,变量采用 `$env:NAME`。 | @@ -148,9 +148,9 @@ ask_user_question 会暂停工具调用,直到当前 UI 提供方返回人类 } ``` -来源:[`packages/core/tools/src/code-mode.ts`](../packages/core/tools/src/code-mode.ts) +来源:[`packages/core/tools/src/ptc.ts`](../packages/core/tools/src/ptc.ts) -在 `mode: code`/`mode: both` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 Code Mode Agent Note)。在 `code` 下,它是注册表对协议格式的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。 +在 `mode: ptc`/`mode: both` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 PTC mode Agent Note)。在 `code` 下,它是注册表对协议格式的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。 diff --git a/docs/tool-execution-pipeline.i18n.yaml b/docs/tool-execution-pipeline.i18n.yaml index bd8de3ba60..62ee38c9dd 100644 --- a/docs/tool-execution-pipeline.i18n.yaml +++ b/docs/tool-execution-pipeline.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/tool-execution-pipeline.md -tool-execution-pipeline.md: d04d2e4e5093fee92f8921f0eb0112c960a81bb8 -tool-execution-pipeline.zh.md: 15627023d3be6ac2b3aae70c2ef01ef9f1077d3e +tool-execution-pipeline.md: a799c68a60f6782ef3bb79c52f89cbc78d762ab3 +tool-execution-pipeline.zh.md: 04a242f47abe55ce43ac536cc5261a3b075d8b4f diff --git a/docs/tool-execution-pipeline.md b/docs/tool-execution-pipeline.md index d04d2e4e50..a799c68a60 100644 --- a/docs/tool-execution-pipeline.md +++ b/docs/tool-execution-pipeline.md @@ -57,6 +57,6 @@ flowchart TD allResults --> context ``` -Filesystem read-before-edit checks stay below `tool-fs` on `fs/*` events. Generic pre/post waterfalls host hooks and approval policy; `ctx.approval` resolves asks before monotonic guards, and owner policy that must not be reordered remains a registered guard. Around-dispatch concerns such as timeouts wrap `tools/execute`. The registry losslessly snapshots the candidate result and normalizes a snapshot failure before the visible definition's snapshotted `finalizeContent` callback enforces its synchronous content-only invariant. `tools/result` then observes the immutable, lossless-JSON outcome. This lets hooks span tool families without coupling the tools to one policy service. Code Mode sends both the reserved `run_code` transport and its serialized sub-calls through the pipeline; sub-calls carry the parent token, log `tool/code-dispatch`, return denials as binding rejections, and omit `additionalContexts` to preserve call/result adjacency. +Filesystem read-before-edit checks stay below `tool-fs` on `fs/*` events. Generic pre/post waterfalls host hooks and approval policy; `ctx.approval` resolves asks before monotonic guards, and owner policy that must not be reordered remains a registered guard. Around-dispatch concerns such as timeouts wrap `tools/execute`. The registry losslessly snapshots the candidate result and normalizes a snapshot failure before the visible definition's snapshotted `finalizeContent` callback enforces its synchronous content-only invariant. `tools/result` then observes the immutable, lossless-JSON outcome. This lets hooks span tool families without coupling the tools to one policy service. PTC mode sends both the reserved `run_code` transport and its serialized sub-calls through the pipeline; sub-calls carry the parent token, log `tool/code-dispatch`, return denials as binding rejections, and omit `additionalContexts` to preserve call/result adjacency. Maintenance mode: curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs. diff --git a/docs/tool-execution-pipeline.zh.md b/docs/tool-execution-pipeline.zh.md index 15627023d3..04a242f47a 100644 --- a/docs/tool-execution-pipeline.zh.md +++ b/docs/tool-execution-pipeline.zh.md @@ -59,6 +59,6 @@ flowchart TD allResults --> context ``` -文件系统的先读后编辑检查位于 `tool-fs` 之下,通过 `fs/*` 事件实现。通用的前置/后置 waterfall 承载钩子与审批策略;`ctx.approval` 在单调守卫之前处理询问,而不得重新排序的所有者策略仍作为已注册的守卫。超时等环绕分发关注点对 `tools/execute` 进行包装。注册表会对候选结果进行无损快照;如果快照失败,则会先将失败规范化,之后再由可见定义中已随快照固定的 `finalizeContent` 回调强制执行其同步且仅限内容的不变式。随后,`tools/result` 会观察不可变、可由 JSON 无损表示的结果。这样一来,钩子便可跨越不同工具系列,而无需让工具与某个策略服务耦合。Code Mode 会将保留的 `run_code` 传输及其序列化子调用都送入流水线;子调用携带父级 token、记录 `tool/code-dispatch`、将拒绝呈现为具有约束力的驳回,并省略 `additionalContexts`,以保持调用与结果相邻。 +文件系统的先读后编辑检查位于 `tool-fs` 之下,通过 `fs/*` 事件实现。通用的前置/后置 waterfall 承载钩子与审批策略;`ctx.approval` 在单调守卫之前处理询问,而不得重新排序的所有者策略仍作为已注册的守卫。超时等环绕分发关注点对 `tools/execute` 进行包装。注册表会对候选结果进行无损快照;如果快照失败,则会先将失败规范化,之后再由可见定义中已随快照固定的 `finalizeContent` 回调强制执行其同步且仅限内容的不变式。随后,`tools/result` 会观察不可变、可由 JSON 无损表示的结果。这样一来,钩子便可跨越不同工具系列,而无需让工具与某个策略服务耦合。PTC mode 会将保留的 `run_code` 传输及其序列化子调用都送入流水线;子调用携带父级 token、记录 `tool/ptc-dispatch`、将拒绝呈现为具有约束力的驳回,并省略 `additionalContexts`,以保持调用与结果相邻。 维护模式:英文源文件包含人工维护的 Mermaid 流程图,并由生成器写出;本中文文件作为经评审对侧通过双语配对维护。确切的工具 schema 与事件签名位于生成的目录中。 diff --git a/docs/user/develop/basic/tool.i18n.yaml b/docs/user/develop/basic/tool.i18n.yaml index 99fdfe2139..0c7c5bbc82 100644 --- a/docs/user/develop/basic/tool.i18n.yaml +++ b/docs/user/develop/basic/tool.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/user/develop/basic/tool.md -tool.md: 24a82d277e626ba78760a0afb1c41e2a157eb8ee -tool.zh.md: a07bef588f5a18093cf2eb1971b855bf40d1d0b6 +tool.md: aeed5cf0e742cdde33bdcb86bfac4de471ae2597 +tool.zh.md: 25538e42b7777fe3b05b361379a8bce70d6ec5f5 diff --git a/docs/user/develop/basic/tool.md b/docs/user/develop/basic/tool.md index 24a82d277e..aeed5cf0e7 100644 --- a/docs/user/develop/basic/tool.md +++ b/docs/user/develop/basic/tool.md @@ -48,5 +48,5 @@ Open `http://127.0.0.1:3080` and ask: `Use the greet tool to greet Ada.` The mod ## Next steps - [Plugin configuration](./config.md) — make the greeting configurable. -- [Tool authoring reference](../../../cookbook/adding-a-tool.md) — look up nested schemas, canonical values, background work, policy hooks, Code Mode, and UI cards. +- [Tool authoring reference](../../../cookbook/adding-a-tool.md) — look up nested schemas, canonical values, background work, policy hooks, PTC mode, and UI cards. - [Capability layering](../practice/index.md) — split a replaceable capability into Service Definition, Service Provider, and Consumer packages. diff --git a/docs/user/develop/basic/tool.zh.md b/docs/user/develop/basic/tool.zh.md index a07bef588f..25538e42b7 100644 --- a/docs/user/develop/basic/tool.zh.md +++ b/docs/user/develop/basic/tool.zh.md @@ -48,5 +48,5 @@ pnpm dsh web --patch ./scratch-plugin/cordis.yml ## 下一步 - [插件配置](./config.zh.md) — 让问候语可配置。 -- [工具编写参考](../../../cookbook/adding-a-tool.zh.md) — 查阅嵌套 schema、规范值、后台工作、策略钩子、Code Mode 和 UI 卡片。 +- [工具编写参考](../../../cookbook/adding-a-tool.zh.md) — 查阅嵌套 schema、规范值、后台工作、策略钩子、PTC mode 和 UI 卡片。 - [能力分层](../practice/index.zh.md) — 将可替换能力拆分为 Service Definition、Service Provider 和 Consumer 三类包。 diff --git a/package.json b/package.json index e6d9253314..fac0df28e8 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,7 @@ "release:verify-packed-install": "tsx scripts/release/verify-packed-install.ts", "release:publish": "tsx scripts/release/publish.ts", "dsh": "node --import tsx/esm apps/cli/src/bin.ts", - "demo:code-mode": "node scripts/demo-code-mode.mjs", + "demo:ptc": "node scripts/demo-ptc.mjs", "demo:inspector": "node --import tsx/esm apps/cli/src/bin.ts web --patch ./packages/experimental/inspector/cordis.source.patch.yml", "mock:llm": "node --import tsx packages/test-support/llm-mock-server/src/bin.ts", "dev:web": "tsx scripts/dev-web.ts --poll", diff --git a/packages/bundle/headless/cordis.patch.yml b/packages/bundle/headless/cordis.patch.yml index 80cc07be60..d1246b79ba 100644 --- a/packages/bundle/headless/cordis.patch.yml +++ b/packages/bundle/headless/cordis.patch.yml @@ -11,11 +11,11 @@ - id: tools config: - # Keep the same temporary process-wide Code Mode opt-in as the Web surface. + # Keep the same temporary process-wide PTC mode opt-in as the Web surface. mode: !!js process.env.DSH_TOOLS_MODE - insert: - # Code Mode is a core execution capability, not a Web component. + # PTC mode is a core execution capability, not a Web component. - id: code-runtime name: '@deepseek-ai/dsh-code-runtime-worker-thread' diff --git a/packages/bundle/web-app/cordis.patch.yml b/packages/bundle/web-app/cordis.patch.yml index f63af2885a..9ee698044e 100644 --- a/packages/bundle/web-app/cordis.patch.yml +++ b/packages/bundle/web-app/cordis.patch.yml @@ -30,8 +30,8 @@ - id: tools config: - # TEMPORARY workaround: DSH_TOOLS_MODE (native|code|both) opts a whole dsh - # process into Code Mode while per-session tool-presentation selection is being + # TEMPORARY workaround: DSH_TOOLS_MODE (native|ptc|both) opts a whole dsh + # process into PTC mode while per-session tool-presentation selection is being # designed; unset keeps the schema default (native). Remove the env seam # once the web UI owns the choice per session. mode: !!js process.env.DSH_TOOLS_MODE diff --git a/packages/client/ui-agent-preset/src/client/locales.ts b/packages/client/ui-agent-preset/src/client/locales.ts index 50d7650703..b8e9f8b6e2 100644 --- a/packages/client/ui-agent-preset/src/client/locales.ts +++ b/packages/client/ui-agent-preset/src/client/locales.ts @@ -5,7 +5,7 @@ export type AgentPresetSettingsKey = | 'title' | 'description' | 'loading' | 'error' | 'userTrust' | 'seatHint' | 'headerHint' | 'nav' | 'sectionIntro' | 'builtIn' | 'setDefault' | 'view' | 'presetStandardName' | 'presetStandardDescription' - | 'presetCodeName' | 'presetCodeDescription' + | 'presetPtcName' | 'presetPtcDescription' | 'presetMinimalName' | 'presetMinimalDescription' | 'presetCordisName' | 'presetCordisDescription' | 'duplicate' | 'duplicateUnavailable' | 'delete' | 'presetId' | 'presetIdPlaceholder' | 'copyOf' @@ -37,9 +37,9 @@ export const en: Record = { presetStandardName: 'Standard mode', presetStandardDescription: 'Full coding agent with file editing, shell, file and web search, skills, planning, goals, subagents, and workflows.', - presetCodeName: 'PTC mode', - presetCodeDescription: - 'All Standard mode capabilities, with tools exposed through the Code Mode SDK so the model can combine multi-step operations in one TypeScript program.', + presetPtcName: 'PTC mode', + presetPtcDescription: + 'All Standard mode capabilities, with tools exposed through the PTC mode SDK so the model can combine multi-step operations in one TypeScript program.', presetMinimalName: 'Minimal mode', presetMinimalDescription: 'Two-tool coding agent with persistent bash and str_replace_editor.', @@ -101,8 +101,8 @@ export const zh: Record = { view: '查看', presetStandardName: '标准模式', presetStandardDescription: '功能完整的编码 Agent,支持文件编辑、Shell、文件与网页检索、Skills、计划、目标、子代理和工作流。', - presetCodeName: 'PTC 模式', - presetCodeDescription: '具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。', + presetPtcName: 'PTC 模式', + presetPtcDescription: '具备标准模式的全部能力,并通过 PTC 模式 SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。', presetMinimalName: '极简模式', presetMinimalDescription: '仅提供持久 bash 与 str_replace_editor 的双工具编码 Agent。', presetCordisName: '创造模式', @@ -170,7 +170,7 @@ interface PresetLocaleKeys { const BUILT_IN_PRESET_KEYS: Readonly>> = { standard: { name: 'presetStandardName', description: 'presetStandardDescription' }, - code: { name: 'presetCodeName', description: 'presetCodeDescription' }, + ptc: { name: 'presetPtcName', description: 'presetPtcDescription' }, minimal: { name: 'presetMinimalName', description: 'presetMinimalDescription' }, cordis: { name: 'presetCordisName', description: 'presetCordisDescription' }, } diff --git a/packages/client/ui-agent-preset/tests/locales.client.spec.ts b/packages/client/ui-agent-preset/tests/locales.client.spec.ts index 02623e7d7b..1d8b40cc67 100644 --- a/packages/client/ui-agent-preset/tests/locales.client.spec.ts +++ b/packages/client/ui-agent-preset/tests/locales.client.spec.ts @@ -8,7 +8,7 @@ const translate = (bundle: typeof en) => (key: keyof typeof en): string => bundl describe('preset display copy', () => { it.each([ ['standard', 'presetStandardName', 'presetStandardDescription'], - ['code', 'presetCodeName', 'presetCodeDescription'], + ['ptc', 'presetPtcName', 'presetPtcDescription'], ['minimal', 'presetMinimalName', 'presetMinimalDescription'], ['cordis', 'presetCordisName', 'presetCordisDescription'], ] as const)('localizes the shipped %s preset in English and Chinese', (id, nameKey, descriptionKey) => { diff --git a/packages/code-runtime/code-runtime-worker-thread/tests/bootstrap.spec.ts b/packages/code-runtime/code-runtime-worker-thread/tests/bootstrap.spec.ts index a2aac6d9c2..a7966d7417 100644 --- a/packages/code-runtime/code-runtime-worker-thread/tests/bootstrap.spec.ts +++ b/packages/code-runtime/code-runtime-worker-thread/tests/bootstrap.spec.ts @@ -62,7 +62,7 @@ async function rejectionOf(promise: Promise): Promise { const BOOT = { maxOutputBytes: 65_536 } const TOOL_ERROR_CLASS = { name: 'ToolCallError', memberNameProperty: 'toolName' } as const -/** One worker declaration for the Code Mode tools namespace. */ +/** One worker declaration for the PTC mode tools namespace. */ function toolNamespace(names: string[]) { return { global: 'tools', names, errorClass: TOOL_ERROR_CLASS } } diff --git a/packages/code-runtime/code-runtime/src/types.ts b/packages/code-runtime/code-runtime/src/types.ts index a204b113ac..6a5adda0be 100644 --- a/packages/code-runtime/code-runtime/src/types.ts +++ b/packages/code-runtime/code-runtime/src/types.ts @@ -25,7 +25,7 @@ export type CodeJsonValue = null | boolean | number | string | CodeJsonValue[] | * injects a real error constructor under `name`; rejected member calls become * its instances and expose the exact member name through * `memberNameProperty`. Both strings are runtime data rather than knowledge - * of a particular consumer such as Code Mode. + * of a particular consumer such as PTC mode. */ export interface CodeBindingErrorClass { /** Constructor global and resulting `Error.name`; same portable identifier rule as {@link CodeBindingNamespace.global}. */ diff --git a/packages/core/agent-loop/tests/tool-calls.spec.ts b/packages/core/agent-loop/tests/tool-calls.spec.ts index f65454949c..354ed920f8 100644 --- a/packages/core/agent-loop/tests/tool-calls.spec.ts +++ b/packages/core/agent-loop/tests/tool-calls.spec.ts @@ -690,7 +690,7 @@ describe('tool-call scheduler: failure quiescence', () => { }) }) -describe('code-mode native-tool denial through the agent loop', () => { +describe('PTC mode native-tool denial through the agent loop', () => { /** A minimal in-process code runtime for test purposes — never actually runs. */ class FakeCodeRuntime extends CodeRuntime { readonly language = 'typescript' @@ -705,7 +705,7 @@ describe('code-mode native-tool denial through the agent loop', () => { await ctx.plugin(LlmRuntime) await ctx.plugin(SessionStore) await ctx.plugin(SystemPrompt, { persona: '' }) - await ctx.plugin(ToolRuntime, { mode: 'code' }) + await ctx.plugin(ToolRuntime, { mode: 'ptc' }) // eslint-disable-next-line @typescript-eslint/no-explicit-any -- FakeCodeRuntime is an internal test helper with an opaque type shape await ctx.plugin(FakeCodeRuntime as any) await ctx.plugin(AgentRegistry) diff --git a/packages/core/agent-tool-presentation/package.json b/packages/core/agent-tool-presentation/package.json index 382c0caf61..ed795edc23 100644 --- a/packages/core/agent-tool-presentation/package.json +++ b/packages/core/agent-tool-presentation/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-agent-tool-presentation", - "description": "Agent-plane presentation selector: composes one agent's tools as Code Mode, native, or both", + "description": "Agent-plane presentation selector: composes one agent's tools as PTC mode, native, or both", "version": "0.1.1-rc.2", "publishConfig": { "access": "public" diff --git a/packages/core/agent-tool-presentation/src/index.ts b/packages/core/agent-tool-presentation/src/index.ts index c4105f1ec2..5db95894fc 100644 --- a/packages/core/agent-tool-presentation/src/index.ts +++ b/packages/core/agent-tool-presentation/src/index.ts @@ -7,13 +7,13 @@ * consumers, so it cannot move into a preset. What a preset CAN own is the * presentation: `ctx.tools.presentAs()` declares it for the mounting SCOPE, * which is the preset's standing mount, so the declaration covers every agent - * joined to that preset and a Code Mode preset runs beside native ones in one + * joined to that preset and a PTC mode preset runs beside native ones in one * process. One row per composition, not one per session. * - * A code mode needs a TypeScript code runtime, which is a host-plane service + * A PTC mode needs a TypeScript code runtime, which is a host-plane service * ([`dsh-code-runtime-worker-thread`](../../code-runtime/code-runtime-worker/README.md)). * This row therefore waits for it rather than assuming it: a preset selecting - * Code Mode against a deployment that composes no runtime fails at mount, named + * 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. * @module @deepseek-ai/dsh-agent-tool-presentation */ @@ -48,7 +48,7 @@ export interface Config { /** Runtime schema. */ export const Config: z = z.object({ - mode: z.union(['native', 'code', 'both'] as const).required(), + mode: z.union(['native', 'ptc', 'both'] as const).required(), }) /** diff --git a/packages/core/agent-tool-presentation/tests/agent-tool-presentation.spec.ts b/packages/core/agent-tool-presentation/tests/agent-tool-presentation.spec.ts index 13505fbbb4..3110036e26 100644 --- a/packages/core/agent-tool-presentation/tests/agent-tool-presentation.spec.ts +++ b/packages/core/agent-tool-presentation/tests/agent-tool-presentation.spec.ts @@ -63,9 +63,9 @@ describe('the tool-presentation row', () => { expect(inject).toEqual(['tools']) }) - it('gives its own agent Code Mode and leaves the rest native', async () => { + it('gives its own agent PTC mode and leaves the rest native', async () => { const ctx = await host() - const coded = await mount(ctx, { mode: 'code' }, 'coded') + const coded = await mount(ctx, { mode: 'ptc' }, 'coded') const plain = await mount(ctx, { mode: 'native' }, 'plain') const codedAssembly = await ctx.systemPrompt.assemble({ scope: coded.agent }) @@ -87,7 +87,7 @@ describe('the tool-presentation row', () => { it('restores the deployment default when the agent unloads', async () => { const ctx = await host() - const { agent, row } = await mount(ctx, { mode: 'code' }) + const { agent, row } = await mount(ctx, { mode: 'ptc' }) await row.dispose() @@ -101,7 +101,7 @@ describe('the tool-presentation row', () => { it('waits for a code runtime the deployment does not compose', async () => { const ctx = await host({ runtime: false }) - const { agent, row } = await mount(ctx, { mode: 'code' }) + const { agent, row } = await mount(ctx, { mode: 'ptc' }) // Pending, not applied: `dsh-agent-presets` rejects a mount holding a row // that never reached a usable state, naming this id — so the preset fails @@ -113,7 +113,7 @@ describe('the tool-presentation row', () => { it('applies once the runtime arrives', async () => { const ctx = await host({ runtime: false }) - const { agent } = await mount(ctx, { mode: 'code' }) + const { agent } = await mount(ctx, { mode: 'ptc' }) await ctx.plugin(StubRuntime) diff --git a/packages/core/scope/src/scoped-events.generated.ts b/packages/core/scope/src/scoped-events.generated.ts index da93075512..e7f24ca4cb 100644 --- a/packages/core/scope/src/scoped-events.generated.ts +++ b/packages/core/scope/src/scoped-events.generated.ts @@ -29,10 +29,10 @@ const scopedSubjectResolvers: Readonly (args[1] as Record)['scope'], - 'tools/code-dispatch-log': args => (args[0] as Record)['agent'], 'tools/execute': args => (args[0] as Record)['agent'], 'tools/post-execute': args => (args[0] as Record)['agent'], 'tools/pre-execute': args => (args[0] as Record)['agent'], + 'tools/ptc-dispatch-log': args => (args[0] as Record)['agent'], 'tools/result': args => (args[0] as Record)['agent'], 'user-questions/request': args => (args[0] as Record)['agent'], }) diff --git a/packages/core/scope/tests/invariant.spec.ts b/packages/core/scope/tests/invariant.spec.ts index 96c1488ea2..2adc3d787b 100644 --- a/packages/core/scope/tests/invariant.spec.ts +++ b/packages/core/scope/tests/invariant.spec.ts @@ -74,7 +74,7 @@ describe('scoped-dispatch invariants', () => { ['approval/request', [{ agent, toolName: 'echo' }, () => Promise.resolve('unavailable')]], ['goal/changed', [{ agent, change: { operation: 'create', ref: { id: 'goal-a', revision: 1 } } }]], ['system-prompt/assemble', [[], { scope: agent }]], - ['tools/code-dispatch-log', [{ exec: { callId: 'c', name: 't', arguments: {} }, agent, subCallId: 'c:code:1', name: 't', isError: false, content: [] }, () => Promise.resolve([])]], + ['tools/ptc-dispatch-log', [{ exec: { callId: 'c', name: 't', arguments: {} }, agent, subCallId: 'c:code:1', name: 't', isError: false, content: [] }, () => Promise.resolve([])]], ['tools/execute', [{ callId: 'c', name: 't', arguments: {}, agent }, () => Promise.resolve({ content: [], isError: false })]], ['tools/post-execute', [{ callId: 'c', name: 't', arguments: {}, agent }, { content: [], isError: false }, () => Promise.resolve({ kind: 'accept' })]], ['tools/pre-execute', [{ callId: 'c', name: 't', arguments: {}, agent }, () => Promise.resolve({ kind: 'allow' })]], diff --git a/packages/core/system-prompt/src/index.ts b/packages/core/system-prompt/src/index.ts index c4b5f81ad5..f0f0715971 100644 --- a/packages/core/system-prompt/src/index.ts +++ b/packages/core/system-prompt/src/index.ts @@ -134,7 +134,7 @@ export const FIRST_PARTY_SECTION_ORDER = { DEPLOYMENT_PERSONA: 0, PLAN_POLICY: 500, TEAM_POLICY: 600, - CODE_ONLY: 800, + PTC_ONLY: 800, FILE_REFERENCE: 900, TOOL_BASH: 1000, TOOL_PWSH: 1010, diff --git a/packages/core/tools/src/index.ts b/packages/core/tools/src/index.ts index 8cabf75d10..f0b855cb8e 100644 --- a/packages/core/tools/src/index.ts +++ b/packages/core/tools/src/index.ts @@ -21,8 +21,8 @@ import type {} from '@deepseek-ai/dsh-user-approval' import type { ToolCallView, ToolResultView } from './presentation.ts' import { assertSupportedJsonSchema, validateJsonSchemaValue } from './json-schema.ts' import type { JsonSchemaNode } from './json-schema.ts' -import { createRunCodeTool, RUN_CODE_NAME, SDK_SECTION_ORDER } from './code-mode.ts' -import type { CodeSdkLanguage } from './code-mode.ts' +import { createRunCodeTool, RUN_CODE_NAME, SDK_SECTION_ORDER } from './ptc.ts' +import type { CodeSdkLanguage } from './ptc.ts' import { renderToolsSdk } from './ts-types.ts' import type { ToolSdkSchema } from './ts-types.ts' import { renderToolsSdkPy } from './py-types.ts' @@ -33,7 +33,7 @@ import { renderToolsSdkPy } from './py-types.ts' * section under a non-native mode; a runtime whose language is not a key * fails the assembly loudly (same idiom as `toolOrder` violations). Adding a * new backend language is three parallel edits — a {@link CodeSdkLanguage} - * member, an entry here, and a `RUN_CODE_FLAVORS` entry in `code-mode.ts` for + * member, an entry here, and a `RUN_CODE_FLAVORS` entry in `ptc.ts` for * its `run_code` schema strings — plus the renderer function this table points * at. The `satisfies` clause pins this table's key set to that union, which * the flavor table is checked against too, so any of the three left out is a @@ -44,18 +44,18 @@ import { renderToolsSdkPy } from './py-types.ts' * {@link Config.mode} JSDoc. */ /** - * Prompt order of the `code` collapse statement: after the persona and before + * Prompt order of the `ptc` collapse statement: after the persona and before * per-tool guidance, so the model reads which tools it may call before it * reads what each one is for. */ -const COLLAPSE_SECTION_ORDER = FIRST_PARTY_SECTION_ORDER.CODE_ONLY +const COLLAPSE_SECTION_ORDER = FIRST_PARTY_SECTION_ORDER.PTC_ONLY /** - * The model-facing statement of the `code` collapse. Names the consequence + * The model-facing statement of the `ptc` collapse. Names the consequence * (the call fails) and the route (inside the program), because a rule the * model can only discover by being denied is one it corrects too late. */ -const CODE_ONLY_INSTRUCTION = `\`${RUN_CODE_NAME}\` is the only tool you can call directly — a tool call naming any other tool fails. Reach every tool the SDK declares below from inside the program.` +const PTC_ONLY_INSTRUCTION = `\`${RUN_CODE_NAME}\` is the only tool you can call directly — a tool call naming any other tool fails. Reach every tool the SDK declares below from inside the program.` const SDK_RENDERERS: Record string> = { typescript: renderToolsSdk, @@ -99,9 +99,9 @@ export { } from './json-schema.ts' export type { JsonValue } from '@deepseek-ai/dsh-session' -export type { CodeDispatchEventData, CodeDispatchStartEventData } from './types.ts' +export type { PtcDispatchEventData, PtcDispatchStartEventData } from './types.ts' -export { CodeRunFailedError, RUN_CODE_NAME } from './code-mode.ts' +export { CodeRunFailedError, RUN_CODE_NAME } from './ptc.ts' export { jsonSchemaToTs, renderToolsSdk } from './ts-types.ts' export { jsonSchemaToPy, renderToolsSdkPy } from './py-types.ts' export { defineContentToolFixture, type ContentToolFixtureOptions } from './testing.ts' @@ -186,7 +186,7 @@ declare module '@deepseek-ai/cordis' { * @param dispatch - the parent execution, sub-call identity, and the settled content to log. * @mode waterfall */ - 'tools/code-dispatch-log'(this: Scoped, dispatch: CodeDispatchLog, next: () => Promise): Promise + 'tools/ptc-dispatch-log'(this: Scoped, dispatch: PtcDispatchLog, next: () => Promise): Promise /** * Observe the frozen, lossless-JSON final outcome. Listener failures are contained. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): keyed by `exec.agent`. @@ -325,11 +325,11 @@ export interface ToolExecutionInput { /** The agent on whose behalf the call runs (set by the agent loop). */ readonly agent?: Agent /** - * Opaque token of the enclosing transport execution, when one exists. Code - * Mode sets this on SDK sub-dispatches so commit-style observers can wait for + * Opaque token of the enclosing transport execution, when one exists. PTC + * mode sets this on SDK sub-dispatches so commit-style observers can wait for * the outer `run_code` outcome without receiving its live mutable execution. * The token also marks the call as a transport sub-dispatch rather than a - * model-direct call: under `mode: 'code'`, only calls WITH a parent may + * model-direct call: under `mode: 'ptc'`, only calls WITH a parent may * execute a native tool name — a model-direct call (no parent) is denied as * `UNKNOWN_TOOL` before the policy pipeline. See {@link ToolRuntime.execute}. */ @@ -348,14 +348,14 @@ export type ToolExecutionMode = /** * One settled `run_code` sub-dispatch about to be logged, as seen by the - * `tools/code-dispatch-log` waterfall: the parent execution (session owner, + * `tools/ptc-dispatch-log` waterfall: the parent execution (session owner, * outer call identity), the sub-call identity, and the outcome whose durable * copy a listener may reshape. `content` is the RENDERED result projection * (what a native `tool/result` would carry) — the program itself received * the structured `value` (or just the error message on failure); only the * `tool/code-dispatch` event's copy changes. */ -export interface CodeDispatchLog { +export interface PtcDispatchLog { /** The outer `run_code` execution. */ readonly exec: ToolExecution /** The calling agent (the scope routing key and the spill owner), when the outer call has one. */ @@ -649,18 +649,18 @@ function errorInfo(error: unknown): ToolErrorInfo | undefined { } /** How the registry presents its tools to the model (see {@link Config.mode}). */ -export type ToolPresentationMode = 'native' | 'code' | 'both' +export type ToolPresentationMode = 'native' | 'ptc' | 'both' /** Plugin config: how the registered tools are presented to the model. */ export interface Config { /** - * Model presentation. `native` (default) sends every visible schema; `code` + * Model presentation. `native` (default) sends every visible schema; `ptc` * sends only `run_code` plus a generated SDK prompt and collapses the * executor to the same surface (a model-direct call may only name * `run_code`; `run_code` SDK sub-dispatches keep every visible tool); `both` - * sends both forms. Code modes require a `ctx.codeRuntime` whose `language` + * sends both forms. PTC mode requires a `ctx.codeRuntime` whose `language` * has a registered SDK renderer (TypeScript or Python) and fail prompt - * assembly when it is absent or has no renderer. Under `code`, native names + * assembly when it is absent or has no renderer. Under `ptc`, native names * in `toolOrder` are invalid. */ mode?: ToolPresentationMode @@ -676,7 +676,7 @@ export interface Config { /** * Per-scope filter over global tools. Restrictions intersect and do not affect - * scoped registrations or the reserved Code Mode transport. + * scoped registrations or the reserved PTC mode transport. */ export interface ToolRestriction { /** Global tool names that stay visible; everything else is removed. */ @@ -789,7 +789,7 @@ export class ToolRuntime extends Service { static inject = ['systemPrompt'] static Config: z = z.object({ - mode: z.union(['native', 'code', 'both'] as const).default('native'), + mode: z.union(['native', 'ptc', 'both'] as const).default('native'), maxParallelSubCalls: z.natural().min(1).default(10), }) @@ -822,7 +822,7 @@ export class ToolRuntime extends Service { * a code mode is no longer known when the service is constructed, and the * transport is stateless beyond its closures over `this`. */ - private codeTransport: ToolDefinition | undefined + private ptcTransport: ToolDefinition | undefined constructor(ctx: Context, config: Config = {}) { super(ctx, 'tools') @@ -854,20 +854,20 @@ export class ToolRuntime extends Service { */ private collapseSection(): { name: string; order: number; text: (context: { scope?: ScopeKey }) => string } { return { - name: 'tools:code-only', + name: 'tools:ptc-only', order: COLLAPSE_SECTION_ORDER, // The SAME predicate the executor denies by, so the prompt cannot state // a rule the registry does not enforce (see `collapses`). - text: context => this.modeFor(context.scope) === 'code' ? CODE_ONLY_INSTRUCTION : '', + text: context => this.modeFor(context.scope) === 'ptc' ? PTC_ONLY_INSTRUCTION : '', } } /** - * The generated-SDK prompt section, registered globally by a code-mode + * The generated-SDK prompt section, registered globally by a PTC mode * deployment and per scope by {@link presentAs}. * * The body regenerates from the CALLING scope, and renders empty for an - * agent presenting natively — an agent that opted out under a code-mode + * agent presenting natively — an agent that opted out under a PTC mode * deployment still sees the global registration, and an empty section is * dropped from the rendered prompt. * @returns the section registration. @@ -920,7 +920,7 @@ export class ToolRuntime extends Service { * @returns the shared transport definition. */ private requireCodeTransport(): ToolDefinition { - this.codeTransport ??= createRunCodeTool(this, { + this.ptcTransport ??= createRunCodeTool(this, { requireRuntime: () => this.requireCodeRuntime(this.defaultMode), // The language-aware description/parameters getters read the runtime // without demanding one, so a native-default process can still project @@ -929,7 +929,7 @@ export class ToolRuntime extends Service { maxParallel: this.maxParallelSubCalls, shapeDispatchLog: dispatch => this.shapeDispatchLog(dispatch), }) - return this.codeTransport + return this.ptcTransport } /** @@ -938,7 +938,7 @@ export class ToolRuntime extends Service { * declaration covers every agent joined under it. * * Scoped only, and one declaration per scope: this is how an agent preset - * composes Code Mode agents beside native ones in the same process, and a + * composes PTC mode agents beside native ones in the same process, and a * process-global override would be the `mode` config field instead. * @param mode - the presentation the covered agents' models see. * @returns the exact disposer that restores the deployment default. @@ -961,7 +961,7 @@ export class ToolRuntime extends Service { { label: 'tools.presentAs()' }, ) // The SDK and collapse sections are per scope for the same reason the - // mode is. Under a deployment that already defaults to a code mode this + // mode is. Under a deployment that already defaults to PTC mode this // shadows the global registration with an identical body, which costs // nothing and keeps one rule instead of a case analysis. if (mode !== 'native') { @@ -991,7 +991,7 @@ export class ToolRuntime extends Service { // language with no SDK renderer. this.requireCodeRuntime(mode) const schemas = [...view.visible.values()].map(definition => this.schemaOf(definition, false)) - if (mode === 'code') { + if (mode === 'ptc') { return { schemas: schemas.filter(schema => schema.name === RUN_CODE_NAME), knownNames: [RUN_CODE_NAME], @@ -1013,7 +1013,7 @@ export class ToolRuntime extends Service { * language between them would hand a program written against one SDK to the * other. Binding it is deferred until a second backend ships (the first * point it is testable); rationale in the - * [language-dispatch note](../../../../.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md). + * [language-dispatch note](../../../../.agents/notes/implemented/feature/2026-07-31-ptc-language-dispatch.md). */ private requireCodeRuntime(mode: ToolPresentationMode): CodeRuntime { const runtime = this.ctx.get('codeRuntime') @@ -1051,7 +1051,7 @@ export class ToolRuntime extends Service { // so a name free to take under the deployment default would become a // collision the moment a preset mounted. if (name === RUN_CODE_NAME) { - throw new Error(`tool name "${RUN_CODE_NAME}" is reserved for the Code Mode presentation transport and cannot be registered or shadowed`) + throw new Error(`tool name "${RUN_CODE_NAME}" is reserved for the PTC mode presentation transport and cannot be registered or shadowed`) } return this.layers.effect( this.ctx, @@ -1082,7 +1082,7 @@ export class ToolRuntime extends Service { ...deny !== undefined ? { deny: new Set(deny) } : {}, } if ([...allow ?? [], ...deny ?? []].includes(RUN_CODE_NAME)) { - throw new Error(`tools.restrict() cannot name reserved Code Mode presentation transport "${RUN_CODE_NAME}"; restrict end-capability tools instead`) + throw new Error(`tools.restrict() cannot name reserved PTC mode presentation transport "${RUN_CODE_NAME}"; restrict end-capability tools instead`) } const known = this.view(scope).restrictableNames const unknown = [...allow ?? [], ...deny ?? []].filter(name => !known.has(name)) @@ -1234,7 +1234,7 @@ export class ToolRuntime extends Service { return [...this.view(scope).visible.values()].map(definition => this.schemaOf(definition, true)) } - /** Project visible callable tools onto the generated Code Mode SDK contract. */ + /** Project visible callable tools onto the generated PTC mode SDK contract. */ private sdkSchemas(scope?: ScopeKey): ToolSdkSchema[] { return [...this.view(scope).visible.values()] .filter(definition => definition.name !== RUN_CODE_NAME) @@ -1284,7 +1284,7 @@ export class ToolRuntime extends Service { } /** - * Run the `tools/code-dispatch-log` waterfall over one settled sub-dispatch + * Run the `tools/ptc-dispatch-log` waterfall over one settled sub-dispatch * and return the content the bridge should log on `tool/code-dispatch`. * Contained: when a listener throws, the method logs the original settled * content; that failure must not fail the dispatch or omit the settle event. Private: @@ -1292,20 +1292,20 @@ export class ToolRuntime extends Service { * receives it as a capability parameter (the `requireRuntime` idiom) — the * waterfall, not this invoker, is the public extension point. */ - private async shapeDispatchLog(dispatch: CodeDispatchLog): Promise { + private async shapeDispatchLog(dispatch: PtcDispatchLog): Promise { try { return await this.ctx.waterfall( - scopeTarget(this, dispatch.agent), 'tools/code-dispatch-log', dispatch, + scopeTarget(this, dispatch.agent), 'tools/ptc-dispatch-log', dispatch, () => Promise.resolve(dispatch.content), ) } catch (error: unknown) { - this.ctx.logger.warn(`tools: code-dispatch-log listener failed for ${dispatch.name}: ${errorMessage(error)}; logging the original settled content`) + this.ctx.logger.warn(`tools: ptc-dispatch-log listener failed for ${dispatch.name}: ${errorMessage(error)}; logging the original settled content`) return dispatch.content } } /** - * Whether the `code` mode collapse denies a model-direct call: only the + * Whether the `ptc` mode collapse denies a model-direct call: only the * reserved `run_code` transport may be named. Nested sub-dispatches (a * `parent` token set) bypass the collapse. One home for the * security-relevant predicate, shared by {@link resolveExecution} and @@ -1321,7 +1321,7 @@ export class ToolRuntime extends Service { * @param nested - whether the call is a transport sub-dispatch, not a model-direct call. */ private collapses(name: string, scope: ScopeKey | undefined, nested: boolean): boolean { - return !nested && this.modeFor(scope) === 'code' && name !== RUN_CODE_NAME + return !nested && this.modeFor(scope) === 'ptc' && name !== RUN_CODE_NAME } /** diff --git a/packages/core/tools/src/json-schema.ts b/packages/core/tools/src/json-schema.ts index 9191bcfbfa..701652ceb8 100644 --- a/packages/core/tools/src/json-schema.ts +++ b/packages/core/tools/src/json-schema.ts @@ -1,5 +1,5 @@ /** - * Enforced JSON Schema subset shared by tool outputs, generated Code Mode + * Enforced JSON Schema subset shared by tool outputs, generated PTC mode * types, subagents, and workflows. The subset accepts any JSON root, an * annotation-only schema for unconstrained JSON, one scalar `type`, object * `properties`/`required`/boolean `additionalProperties`, array `items`, diff --git a/packages/core/tools/src/code-mode.ts b/packages/core/tools/src/ptc.ts similarity index 98% rename from packages/core/tools/src/code-mode.ts rename to packages/core/tools/src/ptc.ts index 0d97797154..bd60b9f97b 100644 --- a/packages/core/tools/src/code-mode.ts +++ b/packages/core/tools/src/ptc.ts @@ -1,9 +1,9 @@ /** - * Code Mode `run_code` transport. Programs call the registry's agent-visible + * PTC mode `run_code` transport. Programs call the registry's agent-visible * tools through nested executions scheduled under the native concurrency * contract; each sub-dispatch is logged for reconstruction, while only the * outer curated result enters model history. - * @module @deepseek-ai/dsh-tools/src/code-mode + * @module @deepseek-ai/dsh-tools/src/ptc */ import { ToolCallId, createUserMessage, HarnessError } from '@deepseek-ai/dsh-llm' @@ -14,10 +14,10 @@ import type { JsonValue } from '@deepseek-ai/dsh-session' import { FIRST_PARTY_SECTION_ORDER } from '@deepseek-ai/dsh-system-prompt' import { defineTool, parameterSchemaSpecToJsonSchema } from './schema.ts' import { TOOL_RUNTIME_SCHEDULER } from './index.ts' -import type { CodeDispatchLog, ToolDefinition, ToolExecutionResult, ToolRuntime, ToolRunContext } from './index.ts' +import type { PtcDispatchLog, ToolDefinition, ToolExecutionResult, ToolRuntime, ToolRunContext } from './index.ts' import type {} from './types.ts' -/** The model-facing name of the Code Mode tool. */ +/** The model-facing name of the PTC mode tool. */ export const RUN_CODE_NAME = 'run_code' /** The `tools:sdk` section order, after per-tool guidance sections. */ @@ -72,7 +72,7 @@ const PYTHON_FLAVOR: RunCodeFlavor = { } /** - * The languages Code Mode ships a presentation for. Both per-language tables — + * The languages PTC mode ships a presentation for. Both per-language tables — * {@link RUN_CODE_FLAVORS} here and `SDK_RENDERERS` in {@link ./index.ts} — are * checked against this union with `satisfies`, so a language added to one and * not the other fails `typecheck` instead of waiting for a runtime that reports @@ -259,7 +259,7 @@ function renderValue(value: JsonValue): string { return typeof value === 'string' ? value : renderJsonValue(value) } -/** Canonical value returned by the outer Code Mode transport. */ +/** Canonical value returned by the outer PTC mode transport. */ type RunCodeOutput = { logs: string[]; result?: JsonValue } /** @@ -279,8 +279,8 @@ export interface RunCodeBridgeOptions { peekRuntime: () => CodeRuntime | undefined /** The run's overlap cap for parallel-classified sub-calls (the registry passes its validated `maxParallelSubCalls`). */ maxParallel: number - /** Runs the contained `tools/code-dispatch-log` waterfall over one settled sub-dispatch (the registry's private invoker). */ - shapeDispatchLog: (dispatch: CodeDispatchLog) => Promise + /** Runs the contained `tools/ptc-dispatch-log` waterfall over one settled sub-dispatch (the registry's private invoker). */ + shapeDispatchLog: (dispatch: PtcDispatchLog) => Promise } /** diff --git a/packages/core/tools/src/py-types.ts b/packages/core/tools/src/py-types.ts index bb03ed64ee..74669a9739 100644 --- a/packages/core/tools/src/py-types.ts +++ b/packages/core/tools/src/py-types.ts @@ -1,11 +1,11 @@ /** - * Code Mode codegen — Python flavor. The pure projection from registered tool schemas to the + * PTC mode codegen — Python flavor. The pure projection from registered tool schemas to the * Python SDK text the model programs against under `runtime.language === 'python'`. Sibling of * {@link ./ts-types.ts | ts-types.ts}; the two files are two projections of the same registry * store, keyed by the loaded {@link @deepseek-ai/dsh-code-runtime#CodeRuntime.language | code * runtime's language}. * - * Under `mode: 'code'` the native tool schemas are omitted from the request, so this generated + * Under `mode: 'ptc'` the native tool schemas are omitted from the request, so this generated * SDK is the model's ONLY source for each tool's argument names, required fields, types, * descriptions, and canonical output shapes; under `mode: 'both'` the native schemas ship * alongside it and it is one of two. Object-shaped arguments and outputs therefore render as one @@ -32,7 +32,7 @@ const IDENTIFIER = /^[\p{XID_Start}_]\p{XID_Continue}*$/u * Python identifiers are not ASCII: `路径` is as legal a field name as `path`, * and rejecting it would degrade the whole enclosing object, dropping every * field's name, requiredness, and type — information whose only source under - * `mode: 'code'` is this generated text. + * `mode: 'ptc'` is this generated text. * * NFKC stability is a second and separate condition, because CPython * normalizes identifiers at compile time while JSON keys are compared as @@ -163,7 +163,7 @@ interface RenderState { * (`SyntaxError: source code string cannot contain null bytes`), whether it * sits in a docstring or in a comment, so one such byte anywhere in a schema * description would make the whole generated SDK unparseable — under - * `mode: 'code'`, the model's only declaration of the tools. The rest are + * `mode: 'ptc'`, the model's only declaration of the tools. The rest are * legal but invisible; escaping them with the same rule keeps the emitted text * readable and the treatment uniform. * @@ -236,7 +236,7 @@ function describe(schema: object): string | undefined { * Backslashes are doubled first, every quote is escaped, and a trailing * backslash cannot survive: a description ending in `"` or an odd backslash * would otherwise merge with (or escape) the closing triple quote and make - * the generated block — Code Mode's only SDK — syntactically invalid Python. + * the generated block — PTC mode's only SDK — syntactically invalid Python. */ function docLines(description: unknown, indent: number): string[] { const collapsed = describe({ description }) @@ -579,7 +579,7 @@ function renderType(schema: unknown, className: string, state: RenderState): str } } // TypedDict syntax cannot express openness, so an open object states it - // in-band: the annotation is advisory either way, and `mode: 'code'` + // in-band: the annotation is advisory either way, and `mode: 'ptc'` // omits the native schemas, making this line the model's only signal // that extra keys are accepted. if (node.additionalProperties !== false) { @@ -778,7 +778,7 @@ export function renderToolsSdkPy(schemas: ToolSdkSchema[]): string { // of that method's body. Emitted before the `async def` it would instead // become the `Tools` class docstring (for the first tool) or a dead // expression (for every later one), leaving every method undocumented — - // and under `mode: 'code'` this SDK is the model's only description of + // and under `mode: 'ptc'` this SDK is the model's only description of // what a tool does. A docstring is a complete body, so the `...` stub is // only for the description-less case. const doc = docLines(schema.description, 2) diff --git a/packages/core/tools/src/ts-types.ts b/packages/core/tools/src/ts-types.ts index a5d36a5ce3..d92c987817 100644 --- a/packages/core/tools/src/ts-types.ts +++ b/packages/core/tools/src/ts-types.ts @@ -1,5 +1,5 @@ /** - * Code Mode codegen: the pure projection from registered tool schemas to the TypeScript SDK + * PTC mode codegen: the pure projection from registered tool schemas to the TypeScript SDK * text the model programs against (the `tools:sdk` prompt section). Sibling of * `json-schema.ts` — `schemas()` (native function calling) and this module (the generated * `declare const tools` API) are two projections of the same store. @@ -9,7 +9,7 @@ import type { ToolSchema } from '@deepseek-ai/dsh-llm' import { assertSupportedJsonSchema } from './json-schema.ts' import type { JsonSchemaNode, JsonSchemaScalar } from './json-schema.ts' -/** Internal Code Mode projection: the model-facing schema plus the canonical output schema. */ +/** Internal PTC mode projection: the model-facing schema plus the canonical output schema. */ export interface ToolSdkSchema extends ToolSchema { /** Validated canonical value returned by the tool binding. */ output: JsonSchemaNode @@ -246,7 +246,7 @@ export function jsonSchemaToTs(schema: unknown, indent = 0): string { } } -/** The fixed model-facing usage contract rendered above the declarations (see the Code Mode Agent Note's "What the model sees"). */ +/** The fixed model-facing usage contract rendered above the declarations (see the PTC mode Agent Note's "What the model sees"). */ const SDK_INSTRUCTIONS = `## Writing code for run_code \`run_code\` takes two required arguments: \`code\` — the body of an async TypeScript function (erasable syntax only — no \`enum\` or namespaces; type annotations are advisory, the code runs type-stripped) — and \`description\`, a short summary of what the program does. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly.` diff --git a/packages/core/tools/src/types.ts b/packages/core/tools/src/types.ts index 6de9c10714..dfdf34f37f 100644 --- a/packages/core/tools/src/types.ts +++ b/packages/core/tools/src/types.ts @@ -7,17 +7,12 @@ import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand' import type { ContentBlock } from '@deepseek-ai/dsh-llm/types' -/** Payload recorded when one nested Code Mode Tool dispatch starts. */ -export interface CodeDispatchStartEventData { - rootCallId: ToolCallId - parentCallId: ToolCallId - subCallId: ToolCallId name: string arguments: unknown } -/** Payload recorded when one nested Code Mode Tool dispatch settles. */ -export interface CodeDispatchEventData extends CodeDispatchStartEventData { +/** Payload recorded when one nested PTC mode Tool dispatch settles. */ +export interface PtcDispatchEventData extends PtcDispatchStartEventData { isError: boolean content: ContentBlock[] } @@ -37,7 +32,7 @@ declare module '@deepseek-ai/dsh-session/types' { * with `tool/code-dispatch` by `subCallId` (timing = the two events' * `time` fields). */ - 'tool/code-dispatch-start': CodeDispatchStartEventData + 'tool/code-dispatch-start': PtcDispatchStartEventData /** * One bridged sub-dispatch SETTLING: the pairing ids (matching the * `tool/code-dispatch-start` with the same `subCallId`), the tool `name` @@ -53,6 +48,6 @@ declare module '@deepseek-ai/dsh-session/types' { * before returning), so its execution-enclosure relation holds by * construction. */ - 'tool/code-dispatch': CodeDispatchEventData + 'tool/code-dispatch': PtcDispatchEventData } } diff --git a/packages/core/tools/tests/invariant.spec.ts b/packages/core/tools/tests/invariant.spec.ts index 24c3c443f4..db3c0b905c 100644 --- a/packages/core/tools/tests/invariant.spec.ts +++ b/packages/core/tools/tests/invariant.spec.ts @@ -89,7 +89,7 @@ describe('tool-pipeline invariants', () => { expect(() => { emitResult(ctx, anonymous, outcome()) }).toThrow(/non-empty name and callId/) }) - it('requires code-dispatch records to be turn-enclosed', async () => { + it('requires ptc-dispatch records to be turn-enclosed', async () => { const ctx = await setup() const session = ctx.sessions.create() const data = { @@ -204,7 +204,7 @@ describe('tool-pipeline invariants', () => { }).not.toThrow() }) - it('replays enclosed code-dispatch records on late registration', async () => { + it('replays enclosed ptc-dispatch records on late registration', async () => { const ctx = new Context() await ctx.plugin(SessionStore) const session = ctx.sessions.create() @@ -223,7 +223,7 @@ describe('tool-pipeline invariants', () => { await expect(ctx.plugin(ToolsInvariant).then(() => undefined)).resolves.toBeUndefined() }) - it('rejects an unenclosed code-dispatch record on late registration', async () => { + it('rejects an unenclosed ptc-dispatch record on late registration', async () => { const ctx = new Context() await ctx.plugin(SessionStore) ctx.sessions.create().append('tool/code-dispatch-start', { diff --git a/packages/core/tools/tests/code-mode.spec.ts b/packages/core/tools/tests/ptc.spec.ts similarity index 92% rename from packages/core/tools/tests/code-mode.spec.ts rename to packages/core/tools/tests/ptc.spec.ts index 6bad231737..4031d22cdc 100644 --- a/packages/core/tools/tests/code-mode.spec.ts +++ b/packages/core/tools/tests/ptc.spec.ts @@ -15,7 +15,7 @@ import type { JsonValue, SessionEventMap } from '@deepseek-ai/dsh-session' const testToolSignal = new AbortController().signal /** - * Code Mode unit tier (per the Agent Note's plan): provider contribution per mode, + * PTC mode unit tier (per the Agent Note's plan): provider contribution per mode, * misconfiguration rejections, the run_code dispatch bridge (serialization, * abort, JSON normalization, error mapping, events, quiescence), and HMR * safety — all against an in-repo fake runtime, exactly the @@ -50,7 +50,7 @@ interface SetupOptions { async function setup(options: SetupOptions = {}) { const ctx = new Context() await ctx.plugin(SystemPrompt, { ...options.toolOrder ? { toolOrder: options.toolOrder } : {} }) - await ctx.plugin(ToolRuntime, { mode: options.mode ?? 'code', ...options.maxParallelSubCalls !== undefined ? { maxParallelSubCalls: options.maxParallelSubCalls } : {} }) + await ctx.plugin(ToolRuntime, { mode: options.mode ?? 'ptc', ...options.maxParallelSubCalls !== undefined ? { maxParallelSubCalls: options.maxParallelSubCalls } : {} }) let runtime: FakeRuntime | undefined if (options.runtime !== false) { await ctx.plugin(FakeRuntime, options.runtime ?? {}) @@ -124,8 +124,8 @@ describe('mode-aware wire contribution', () => { expect(assembly.sections.some(section => section.name === 'tools:sdk')).toBe(false) }) - it("mode 'code' contributes exactly [run_code] plus the SDK section declaring the other tools", async () => { - const { ctx, systemPrompt } = await setup({ mode: 'code' }) + it("mode 'ptc' contributes exactly [run_code] plus the SDK section declaring the other tools", async () => { + const { ctx, systemPrompt } = await setup({ mode: 'ptc' }) registerEcho(ctx) const assembly = await systemPrompt.assemble() expect(assembly.tools.map(tool => tool.name)).toEqual([RUN_CODE_NAME]) @@ -136,8 +136,8 @@ describe('mode-aware wire contribution', () => { expect(sdk?.text).not.toContain('tools.bash(') }) - it("mode 'code' states the run_code-only rule BEFORE the per-tool guidance that names each tool", async () => { - const { ctx, systemPrompt } = await setup({ mode: 'code' }) + it("mode 'ptc' states the run_code-only rule BEFORE the per-tool guidance that names each tool", async () => { + const { ctx, systemPrompt } = await setup({ mode: 'ptc' }) registerEcho(ctx) // Stand in for a real tool's guidance section, which names its tool without // saying how it is reached. @@ -149,11 +149,11 @@ describe('mode-aware wire contribution', () => { const assembly = await systemPrompt.assemble() const names = assembly.sections.map(section => section.name) - const rule = assembly.sections.find(section => section.name === 'tools:code-only') + const rule = assembly.sections.find(section => section.name === 'tools:ptc-only') expect(rule?.text).toContain(`\`${RUN_CODE_NAME}\` is the only tool you can call directly`) // The rule is worthless after the guidance it qualifies. - expect(names.indexOf('tools:code-only')).toBeLessThan(names.indexOf('tool:echo')) - expect(names.indexOf('tools:code-only')).toBeLessThan(names.indexOf('tools:sdk')) + expect(names.indexOf('tools:ptc-only')).toBeLessThan(names.indexOf('tool:echo')) + expect(names.indexOf('tools:ptc-only')).toBeLessThan(names.indexOf('tools:sdk')) }) it("mode 'both' omits the run_code-only rule, because native calls do execute there", async () => { @@ -162,12 +162,12 @@ describe('mode-aware wire contribution', () => { const assembly = await systemPrompt.assemble() // Registered (the deployment is non-native) but empty, so the renderer // drops it: `both` executes the native call the rule would forbid. - expect(assembly.sections.find(section => section.name === 'tools:code-only')?.text).toBe('') + expect(assembly.sections.find(section => section.name === 'tools:ptc-only')?.text).toBe('') expect(assembly.tools.map(tool => tool.name)).toContain('echo') }) - it('projects deeply nested output schemas into the Code Mode SDK without structured-clone recursion', async () => { - const { ctx, systemPrompt } = await setup({ mode: 'code' }) + it('projects deeply nested output schemas into the PTC mode SDK without structured-clone recursion', async () => { + const { ctx, systemPrompt } = await setup({ mode: 'ptc' }) let output: JsonSchemaNode = { type: 'string' } for (let depth = 0; depth < 5_000; depth++) { output = { oneOf: [output, { type: 'null' }] } @@ -190,7 +190,7 @@ describe('mode-aware wire contribution', () => { expect(sdk).toContain('deep_output: string | null') }) - it.each(['code', 'both'] as const)('treats expert assembly output as authoritative in mode %s', async (mode) => { + it.each(['ptc', 'both'] as const)('treats expert assembly output as authoritative in mode %s', async (mode) => { const { ctx, systemPrompt } = await setup({ mode }) registerEcho(ctx) ctx.on('system-prompt/assemble', async (_assembly, _context, next) => { @@ -207,7 +207,7 @@ describe('mode-aware wire contribution', () => { expect(assembly.tools.some(tool => tool.name === RUN_CODE_NAME)).toBe(false) }) - it.each(['code', 'both'] as const)('lets one scope shadow the default SDK section in mode %s', async (mode) => { + it.each(['ptc', 'both'] as const)('lets one scope shadow the default SDK section in mode %s', async (mode) => { const { ctx, systemPrompt } = await setup({ mode }) registerEcho(ctx) const { scope, agent } = await mintAgentScope(ctx) @@ -231,7 +231,7 @@ describe('mode-aware wire contribution', () => { expect(assembly.sections.some(section => section.name === 'tools:sdk')).toBe(true) }) - it.each(['code', 'both'] as const)('keeps the run_code transport outside scoped allow-list filtering in mode %s', async (mode) => { + it.each(['ptc', 'both'] as const)('keeps the run_code transport outside scoped allow-list filtering in mode %s', async (mode) => { const { ctx, systemPrompt, runtime } = await setup({ mode }) registerEcho(ctx, 'echo') registerEcho(ctx, 'hidden') @@ -239,7 +239,7 @@ describe('mode-aware wire contribution', () => { const lift = scope.ctx.tools.restrict({ allow: ['echo'] }) const assembly = await systemPrompt.assemble({ scope: agent }) - expect(assembly.tools.map(tool => tool.name)).toEqual(mode === 'code' + expect(assembly.tools.map(tool => tool.name)).toEqual(mode === 'ptc' ? [RUN_CODE_NAME] : ['echo', RUN_CODE_NAME]) const sdk = assembly.sections.find(section => section.name === 'tools:sdk')?.text @@ -256,12 +256,12 @@ describe('mode-aware wire contribution', () => { lift() const unrestricted = await systemPrompt.assemble({ scope: agent }) - expect(unrestricted.tools.map(tool => tool.name)).toEqual(mode === 'code' + expect(unrestricted.tools.map(tool => tool.name)).toEqual(mode === 'ptc' ? [RUN_CODE_NAME] : ['echo', 'hidden', RUN_CODE_NAME]) }) - it.each(['code', 'both'] as const)('keeps the run_code transport outside scoped deny-list filtering in mode %s', async (mode) => { + it.each(['ptc', 'both'] as const)('keeps the run_code transport outside scoped deny-list filtering in mode %s', async (mode) => { const { ctx, systemPrompt, runtime } = await setup({ mode }) registerEcho(ctx, 'denied') registerEcho(ctx, 'kept') @@ -269,7 +269,7 @@ describe('mode-aware wire contribution', () => { scope.ctx.tools.restrict({ deny: ['denied'] }) const assembly = await systemPrompt.assemble({ scope: agent }) - expect(assembly.tools.map(tool => tool.name)).toEqual(mode === 'code' + expect(assembly.tools.map(tool => tool.name)).toEqual(mode === 'ptc' ? [RUN_CODE_NAME] : ['kept', RUN_CODE_NAME]) const sdk = assembly.sections.find(section => section.name === 'tools:sdk')?.text @@ -285,7 +285,7 @@ describe('mode-aware wire contribution', () => { expect(result.content).toEqual([{ type: 'text', text: 'kept' }]) }) - it.each(['code', 'both'] as const)('reserves run_code against scoped shadows and explicit restrictions in mode %s', async (mode) => { + it.each(['ptc', 'both'] as const)('reserves run_code against scoped shadows and explicit restrictions in mode %s', async (mode) => { const { ctx, systemPrompt } = await setup({ mode }) const { scope, agent } = await mintAgentScope(ctx) const impostor = defineContentToolFixture({ @@ -295,10 +295,10 @@ describe('mode-aware wire contribution', () => { execute: () => Promise.resolve([{ type: 'text' as const, text: 'impostor' }]), }) - expect(() => scope.ctx.tools.register(impostor)).toThrow(/reserved for the Code Mode presentation transport/) - expect(() => ctx.tools.register(impostor)).toThrow(/reserved for the Code Mode presentation transport/) - expect(() => scope.ctx.tools.restrict({ allow: [RUN_CODE_NAME] })).toThrow(/cannot name reserved Code Mode presentation transport/) - expect(() => scope.ctx.tools.restrict({ deny: [RUN_CODE_NAME] })).toThrow(/cannot name reserved Code Mode presentation transport/) + expect(() => scope.ctx.tools.register(impostor)).toThrow(/reserved for the PTC mode presentation transport/) + expect(() => ctx.tools.register(impostor)).toThrow(/reserved for the PTC mode presentation transport/) + expect(() => scope.ctx.tools.restrict({ allow: [RUN_CODE_NAME] })).toThrow(/cannot name reserved PTC mode presentation transport/) + expect(() => scope.ctx.tools.restrict({ deny: [RUN_CODE_NAME] })).toThrow(/cannot name reserved PTC mode presentation transport/) scope.ctx.systemPrompt.section({ name: 'scoped-note', order: FIRST_PARTY_SECTION_ORDER.TOOLS_SDK - 10, @@ -322,7 +322,7 @@ describe('mode-aware wire contribution', () => { expect(result.content).toEqual([{ type: 'text', text: '(run_code completed with no output)' }]) }) - it.each(['code', 'both'] as const)('keeps run_code in the toolOrder universe without exposing it as a restriction target in mode %s', async (mode) => { + it.each(['ptc', 'both'] as const)('keeps run_code in the toolOrder universe without exposing it as a restriction target in mode %s', async (mode) => { const { ctx, systemPrompt } = await setup({ mode, toolOrder: [RUN_CODE_NAME, ''], @@ -331,7 +331,7 @@ describe('mode-aware wire contribution', () => { const { agent } = await mintAgentScope(ctx) const assembly = await systemPrompt.assemble({ scope: agent }) - expect(assembly.tools.map(tool => tool.name)).toEqual(mode === 'code' + expect(assembly.tools.map(tool => tool.name)).toEqual(mode === 'ptc' ? [RUN_CODE_NAME] : [RUN_CODE_NAME, 'echo']) }) @@ -361,7 +361,7 @@ describe('mode-aware wire contribution', () => { }) it('renders byte-identical SDK text across consecutive assemblies of an unchanged tool set', async () => { - const { ctx, systemPrompt } = await setup({ mode: 'code' }) + const { ctx, systemPrompt } = await setup({ mode: 'ptc' }) registerEcho(ctx) const first = await systemPrompt.assemble() const second = await systemPrompt.assemble() @@ -370,17 +370,17 @@ describe('mode-aware wire contribution', () => { }) it('rejects every assembly when a non-native mode has no code runtime', async () => { - const { systemPrompt } = await setup({ mode: 'code', runtime: false }) + const { systemPrompt } = await setup({ mode: 'ptc', runtime: false }) await expect(systemPrompt.assemble()).rejects.toThrow(/requires a code runtime/) }) it('rejects every assembly when the runtime language has no registered SDK renderer', async () => { - const { systemPrompt } = await setup({ mode: 'code', runtime: { language: 'ruby' } }) + const { systemPrompt } = await setup({ mode: 'ptc', runtime: { language: 'ruby' } }) await expect(systemPrompt.assemble()).rejects.toThrow(/no SDK renderer registered for runtime language "ruby"/) }) it('assembles under a python runtime by picking the Python SDK renderer', async () => { - const { ctx, systemPrompt } = await setup({ mode: 'code', runtime: { language: 'python' } }) + const { ctx, systemPrompt } = await setup({ mode: 'ptc', runtime: { language: 'python' } }) registerEcho(ctx) const assembly = await systemPrompt.assemble() const sdk = assembly.sections.find(section => section.name === 'tools:sdk') @@ -406,7 +406,7 @@ describe('mode-aware wire contribution', () => { }) it('emits a TypeScript-flavored run_code schema under a typescript runtime', async () => { - const { ctx, systemPrompt } = await setup({ mode: 'code', runtime: { language: 'typescript' } }) + const { ctx, systemPrompt } = await setup({ mode: 'ptc', runtime: { language: 'typescript' } }) registerEcho(ctx) const assembly = await systemPrompt.assemble() const runCodeSchema = assembly.tools.find(tool => tool.name === RUN_CODE_NAME) @@ -421,7 +421,7 @@ describe('mode-aware wire contribution', () => { }) it('emits a Python-flavored run_code schema under a python runtime (matches the SDK language)', async () => { - const { ctx, systemPrompt } = await setup({ mode: 'code', runtime: { language: 'python' } }) + const { ctx, systemPrompt } = await setup({ mode: 'ptc', runtime: { language: 'python' } }) registerEcho(ctx) const assembly = await systemPrompt.assemble() const runCodeSchema = assembly.tools.find(tool => tool.name === RUN_CODE_NAME) @@ -442,7 +442,7 @@ describe('mode-aware wire contribution', () => { // which throws when the schema is projected. Assembly's // requireCodeRuntime rejects such a language earlier; this reaches the // guard on its own. - const { ctx } = await setup({ mode: 'code', runtime: { language: 'ruby' } }) + const { ctx } = await setup({ mode: 'ptc', runtime: { language: 'ruby' } }) const definition = ctx.tools.get(RUN_CODE_NAME) // Names the known languages, symmetric with the SDK_RENDERERS guard: this // is the reachable rejection, so it must be at least as diagnosable. @@ -457,15 +457,15 @@ describe('mode-aware wire contribution', () => { // returns undefined there, so the flavor getter degrades to the TS default // rather than throwing. None of those readers feeds a model: assembly goes // through wireSchemas, which requires a runtime first. - const { ctx } = await setup({ mode: 'code', runtime: false }) + const { ctx } = await setup({ mode: 'ptc', runtime: false }) const definition = ctx.tools.get(RUN_CODE_NAME) expect(definition?.description).toContain('Execute a TypeScript program') const params = definition?.parameters as { properties: { code: { description: string } } } expect(params.properties.code.description).toBe('The program: the body of an async TypeScript function.') }) - it("rejects the assembly when toolOrder names a native tool that mode 'code' no longer contributes", async () => { - const { ctx, systemPrompt } = await setup({ mode: 'code', toolOrder: ['echo', ''] }) + it("rejects the assembly when toolOrder names a native tool that mode 'ptc' no longer contributes", async () => { + const { ctx, systemPrompt } = await setup({ mode: 'ptc', toolOrder: ['echo', ''] }) registerEcho(ctx) await expect(systemPrompt.assemble()).rejects.toThrow(/toolOrder lists unregistered tool "echo"/) }) @@ -474,7 +474,7 @@ describe('mode-aware wire contribution', () => { const ctx = new Context() await ctx.plugin(SystemPrompt, {}) await ctx.plugin(FakeRuntime, {}) - const fiber = await ctx.plugin(ToolRuntime, { mode: 'code' }) + const fiber = await ctx.plugin(ToolRuntime, { mode: 'ptc' }) expect(ctx.tools.get(RUN_CODE_NAME)).toBeDefined() await fiber.dispose() const assembly = await ctx.systemPrompt.assemble() @@ -520,7 +520,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { } it('overlaps concurrency-safe calls under Promise.all and logs a start event per dispatch', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const gated = registerGated(ctx, 'safe_read', true) const { agent, events } = fakeAgent() runtime.behavior = async (request) => { @@ -548,7 +548,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { }) it('an exclusive call bars overlap: safe calls drain first, it runs alone, later calls wait', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const safe = registerGated(ctx, 'safe_read', true) const unsafe = registerGated(ctx, 'writer', false) runtime.behavior = async (request) => { @@ -578,7 +578,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { }) it('maxParallelSubCalls caps the overlap window', async () => { - const { ctx, runtime } = await setup({ mode: 'code', maxParallelSubCalls: 2 }) + const { ctx, runtime } = await setup({ mode: 'ptc', maxParallelSubCalls: 2 }) const gated = registerGated(ctx, 'safe_read', true) runtime.behavior = async (request) => { const tools = request.bindings[0]!.functions @@ -603,7 +603,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { }) it('a tool unregistered between binding enumeration and dispatch fails as unknown tool', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const calls: unknown[] = [] const dispose = ctx.tools.register(defineTool({ name: 'ephemeral', @@ -635,7 +635,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { }) it('ordered pre-execute never overlaps: a slow policy on one call delays the next start', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const gated = registerGated(ctx, 'safe_read', true) const stages: string[] = [] let releaseGate: (() => void) | undefined @@ -671,7 +671,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { }) it('an exclusive call holds its barrier through post-execute: the next start waits for the commit', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const writer = registerGated(ctx, 'writer', false) const reader = registerGated(ctx, 'safe_read', true) const stages: string[] = [] @@ -708,7 +708,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { }) it('run settlement drains a commit already in progress: the settle event is appended inside the turn', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const gated = registerGated(ctx, 'safe_read', true) const { agent, events } = fakeAgent() let releasePost: (() => void) | undefined @@ -742,7 +742,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { }) it('post-execute and context commitment stay in submission order under out-of-order completion', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const gated = registerGated(ctx, 'safe_read', true) const postOrder: string[] = [] ctx.on('tools/post-execute', async (postExec, _result, next): Promise => { @@ -778,7 +778,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { }) it('a queued-unstarted call abandoned by run settlement logs no start event', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const gated = registerGated(ctx, 'writer', false) const { agent, events } = fakeAgent() const abandoned: string[] = [] @@ -810,7 +810,7 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { describe('the run_code dispatch bridge', () => { it('bridges tool calls, returns only the curated output, and logs one event per dispatch', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const calls = registerEcho(ctx) const { agent, events } = fakeAgent() runtime.behavior = async (request) => { @@ -841,7 +841,7 @@ describe('the run_code dispatch bridge', () => { }) it('exposes only an opaque parent token to nested result observers', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) registerEcho(ctx) runtime.behavior = async (request) => { await request.bindings[0]!.functions.echo!({ value: 'nested' }) @@ -868,7 +868,7 @@ describe('the run_code dispatch bridge', () => { }) it('forwards a nested terminal conclusion onto the successful run_code result', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) ctx.tools.register(defineTool({ name: 'finalize', description: 'Terminal tool.', @@ -908,7 +908,7 @@ describe('the run_code dispatch bridge', () => { }) it('serializes Promise.all dispatches: tool executions never overlap, in submission order', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const intervals: [string, string][] = [] let active = 0 ctx.tools.register(defineTool({ @@ -946,7 +946,7 @@ describe('the run_code dispatch bridge', () => { }) it('rejects the program-side call when the tool errors, with the tool error text', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) ctx.tools.register(defineContentToolFixture({ name: 'fail', description: 'Always fails.', @@ -965,10 +965,10 @@ describe('the run_code dispatch bridge', () => { expect(result.content[0]).toEqual({ type: 'text', text: 'caught: deliberate failure' }) }) - it('a throwing tools/code-dispatch-log listener is contained: the original settled content is logged', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + it('a throwing tools/ptc-dispatch-log listener is contained: the original settled content is logged', async () => { + const { ctx, runtime } = await setup({ mode: 'ptc' }) registerEcho(ctx) - ctx.on('tools/code-dispatch-log', () => { throw new Error('log-content listener failed') }) + ctx.on('tools/ptc-dispatch-log', () => { throw new Error('log-content listener failed') }) const { agent, events } = fakeAgent() runtime.behavior = async (request) => { const value = await request.bindings[0]!.functions.echo!({ value: 'x' }) @@ -981,7 +981,7 @@ describe('the run_code dispatch bridge', () => { }) it('a throwing tools/pre-execute listener settles the sub-call without post-execute', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const calls = registerEcho(ctx) const postExecuted: string[] = [] ctx.on('tools/pre-execute', (exec, next) => { @@ -1012,7 +1012,7 @@ describe('the run_code dispatch bridge', () => { }) it('a tools/pre-execute deny reaches the program as a binding rejection', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) registerEcho(ctx) ctx.on('tools/pre-execute', (exec, next) => { if (exec.name === 'echo') return Promise.resolve({ kind: 'deny' as const, reason: 'not on my watch' }) @@ -1032,7 +1032,7 @@ describe('the run_code dispatch bridge', () => { }) it('rejects a binding argument that is not lossless JSON, dispatching nothing', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const calls = registerEcho(ctx) const { agent, events } = fakeAgent() runtime.behavior = async (request) => { @@ -1050,7 +1050,7 @@ describe('the run_code dispatch bridge', () => { }) it('dispatches and logs independent snapshots of the same lossless JSON value', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const calls = registerEcho(ctx) const { agent, events } = fakeAgent() runtime.behavior = async (request) => { @@ -1065,7 +1065,7 @@ describe('the run_code dispatch bridge', () => { }) it('defers sub-call additionalContexts onto the outer run_code result', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) registerEcho(ctx) ctx.on('tools/post-execute', (exec, _result, next): Promise => { if (exec.name === 'echo') { @@ -1101,7 +1101,7 @@ describe('the run_code dispatch bridge', () => { }) it('defers image-bearing final sub-call content onto the outer run_code result', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) ctx.tools.register(defineContentToolFixture({ name: 'image_result', description: 'Return one durable image.', @@ -1136,7 +1136,7 @@ describe('the run_code dispatch bridge', () => { it('does not defer images removed by a nested post-execute decision', async () => { for (const decision of ['block', 'replace'] as const) { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) ctx.tools.register(defineContentToolFixture({ name: 'image_result', description: 'Return one durable image.', @@ -1197,7 +1197,7 @@ describe('the run_code dispatch bridge', () => { }) it('converts a failed run into a structured isError result carrying kind, message, and captured logs', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) runtime.behavior = () => Promise.resolve({ logs: ['got this far'], error: { kind: 'timeout', message: 'compute budget exhausted (300ms busy)' }, @@ -1218,7 +1218,7 @@ describe('the run_code dispatch bridge', () => { }) it('aborting the outer signal aborts the in-flight sub-dispatch and abandons queued ones', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const seen: string[] = [] let sawAbort = false ctx.tools.register(defineContentToolFixture({ @@ -1252,7 +1252,7 @@ describe('the run_code dispatch bridge', () => { }) it('a runtime that starts a binding call and then REJECTS still reaches quiescence before returning', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const { agent, events } = fakeAgent() let sawAbort = false let started!: () => void @@ -1287,7 +1287,7 @@ describe('the run_code dispatch bridge', () => { }) it('runs without an owning agent: dispatches work, event logging is skipped', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const calls = registerEcho(ctx) runtime.behavior = async (request) => { await request.bindings[0]!.functions.echo!({ value: 'x' }) @@ -1301,14 +1301,14 @@ describe('the run_code dispatch bridge', () => { it('executing run_code under a missing runtime is a structured isError, not a crash', async () => { const ctx = new Context() await ctx.plugin(SystemPrompt, {}) - await ctx.plugin(ToolRuntime, { mode: 'code' }) + await ctx.plugin(ToolRuntime, { mode: 'ptc' }) const result = await runCode(ctx, 'program') expect(result.isError).toBe(true) expect((result.content[0] as { text: string }).text).toContain('requires a code runtime') }) it('presents the model-authored description as the execute-card title over the program input', async () => { - const { ctx } = await setup({ mode: 'code' }) + const { ctx } = await setup({ mode: 'ptc' }) const tool = ctx.tools.get(RUN_CODE_NAME)! // The description labels the card (the bash description precedent); the // program itself remains the expanded raw input. @@ -1321,7 +1321,7 @@ describe('the run_code dispatch bridge', () => { }) it('rejects a whitespace-only description with a structured isError', async () => { - const { ctx } = await setup({ mode: 'code' }) + const { ctx } = await setup({ mode: 'ptc' }) const result = await runCode(ctx, 'return 1', { description: ' ' }) expect(result.isError).toBe(true) expect((result.content[0] as { text: string }).text).toContain('invalid description') @@ -1333,7 +1333,7 @@ describe('the run_code dispatch bridge', () => { ['logs plus result', { logs: ['printed'], value: 'returned' }, 'printed\nreturned'], ['no output', { logs: [] }, '(run_code completed with no output)'], ] as [string, CodeRunResult, string][])('keeps %s in durable content without a result presenter', async (_name, output, text) => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) runtime.behavior = () => Promise.resolve(output) const result = await runCode(ctx, 'return 1') @@ -1347,7 +1347,7 @@ describe('the run_code dispatch bridge', () => { }) it('keeps a post-policy spill preview in durable content without a result presenter', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const preview = 'HEAD\n\n(Omitted 100 bytes. Full formatted result stored at: /tmp/run-code.txt.)\n\nTAIL' runtime.behavior = () => Promise.resolve({ logs: ['printed'], value: 'returned' }) ctx.on('tools/post-execute', (exec, _result, next): Promise => { @@ -1363,7 +1363,7 @@ describe('the run_code dispatch bridge', () => { }) it('keeps canonical failure content durable without a result presenter', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) runtime.behavior = () => Promise.resolve({ logs: ['captured before failure'], error: { kind: 'output-limit', message: 'outer output exceeded 8 bytes' }, @@ -1381,7 +1381,7 @@ describe('the run_code dispatch bridge', () => { }) it('logs the complete sub-result content verbatim, non-text blocks and long text included', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const { agent, events } = fakeAgent() const long = 'x'.repeat(300) ctx.tools.register(defineTool({ @@ -1414,7 +1414,7 @@ describe('the run_code dispatch bridge', () => { }) it('rejects undefined, getter-throwing, exotic, and unrepresentable binding arguments before dispatch', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const calls = registerEcho(ctx) const { agent, events } = fakeAgent() runtime.behavior = async (request) => { @@ -1446,7 +1446,7 @@ describe('the run_code dispatch bridge', () => { }) it('dispatches and durably logs binding arguments deeper than the structured-clone call stack', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const depth = 5_000 let observedDepth = 0 let observedLeaf: JsonValue | undefined @@ -1497,7 +1497,7 @@ describe('the run_code dispatch bridge', () => { }) it('gives the tool and durable log the same immutable argument value', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const { agent, events } = fakeAgent() let mutationSucceeded: boolean | undefined ctx.tools.register(defineContentToolFixture({ @@ -1521,7 +1521,7 @@ describe('the run_code dispatch bridge', () => { }) it('exposes a tool named __proto__ as an ordinary own binding', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) ctx.tools.register(defineTool({ name: '__proto__', description: 'A prototype-colliding tool name.', @@ -1544,7 +1544,7 @@ describe('the run_code dispatch bridge', () => { }) it('renders every non-string JSON root as pretty JSON while preserving strings raw', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) runtime.behavior = () => Promise.resolve({ logs: [], value: { n: 42, ok: true } }) expect((await runCode(ctx, 'object')).content[0]).toEqual({ type: 'text', text: '{\n "n": 42,\n "ok": true\n}' }) runtime.behavior = () => Promise.resolve({ logs: [], value: {} }) @@ -1567,7 +1567,7 @@ describe('the run_code dispatch bridge', () => { }) it('renders deeply nested JSON without recursive traversal or quadratic indentation', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) let value: JsonValue = { emptyArray: [], emptyObject: {}, @@ -1588,7 +1588,7 @@ describe('the run_code dispatch bridge', () => { }) it('short-circuits a pre-aborted outer signal before the code runtime', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const calls = registerEcho(ctx) runtime.behavior = (request) => { // The fake honors the seam contract for an already-aborted signal. @@ -1612,7 +1612,7 @@ describe('the run_code dispatch bridge', () => { }) it('reports cancellation after rejecting a late binding without dispatching it', async () => { - const { ctx, runtime } = await setup({ mode: 'code' }) + const { ctx, runtime } = await setup({ mode: 'ptc' }) const calls = registerEcho(ctx) const controller = new AbortController() runtime.behavior = async (request) => { @@ -1632,7 +1632,7 @@ describe('the run_code dispatch bridge', () => { }) it('a tool/code-dispatch event never derives a model message', () => { - const session = Session.create(SessionId('code-mode-derive')) + const session = Session.create(SessionId('ptc-derive')) session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) @@ -1653,14 +1653,14 @@ describe('the run_code dispatch bridge', () => { it('direct construction rejects a non-positive parallel sub-call cap at load', async () => { const ctx = new Context() await ctx.plugin(SystemPrompt, {}) - expect(() => new ToolRuntime(ctx, { mode: 'code', maxParallelSubCalls: 0 })) + expect(() => new ToolRuntime(ctx, { mode: 'ptc', maxParallelSubCalls: 0 })) .toThrow('maxParallelSubCalls must be a positive integer') }) it('direct construction in code mode defaults the parallel sub-call cap', async () => { const ctx = new Context() await ctx.plugin(SystemPrompt, {}) - const registry = new ToolRuntime(ctx, { mode: 'code' }) + const registry = new ToolRuntime(ctx, { mode: 'ptc' }) expect(registry.get(RUN_CODE_NAME)).toBeDefined() }) @@ -1675,7 +1675,7 @@ describe('the run_code dispatch bridge', () => { it('denies a model-direct native-tool call under code mode as UNKNOWN_TOOL', async () => { const ctx = new Context() await ctx.plugin(SystemPrompt, {}) - const registry = new ToolRuntime(ctx, { mode: 'code' }) + const registry = new ToolRuntime(ctx, { mode: 'ptc' }) registerEcho(ctx, 'write') const result = await registry.execute({ signal: testToolSignal, @@ -1695,7 +1695,7 @@ describe('the run_code dispatch bridge', () => { it('routes a pre-aborted collapsed call through ABORTED_BEFORE_DISPATCH', async () => { const ctx = new Context() await ctx.plugin(SystemPrompt, {}) - const registry = new ToolRuntime(ctx, { mode: 'code' }) + const registry = new ToolRuntime(ctx, { mode: 'ptc' }) registerEcho(ctx, 'write') const aborted = new AbortController() aborted.abort() @@ -1713,17 +1713,17 @@ describe('the run_code dispatch bridge', () => { /** * Presentation is per agent, because an agent preset composes it: one - * deployment runs a Code Mode agent beside native ones, and neither may see + * deployment runs a PTC mode agent beside native ones, and neither may see * the other's catalog. The deployment `mode` is the default those agents * shadow, not a process-wide fact. */ describe('per-agent presentation', () => { - it('gives one agent Code Mode while the deployment stays native', async () => { + it('gives one agent PTC mode while the deployment stays native', async () => { const { ctx, systemPrompt } = await setup({ mode: 'native' }) const calls = registerEcho(ctx) const { scope, agent } = await mintAgentScope(ctx) - scope.ctx.tools.presentAs('code') + scope.ctx.tools.presentAs('ptc') const coded = await systemPrompt.assemble({ scope: agent }) expect(coded.tools.map(tool => tool.name)).toEqual([RUN_CODE_NAME]) @@ -1753,8 +1753,8 @@ describe('per-agent presentation', () => { const calls = registerEcho(ctx) // The preset's standing scope declares once; the agent only PARENTS to it // (the per-preset standing mount configuration has no per-agent declaration). - const standing = await mintAgentScope(ctx, 'preset:code-like') - standing.scope.ctx.tools.presentAs('code') + const standing = await mintAgentScope(ctx, 'preset:ptc-like') + standing.scope.ctx.tools.presentAs('ptc') const joined = await mintAgentScope(ctx, 'joined-agent') bindScopeParent(joined.agent, standing.agent) const loner = await mintAgentScope(ctx, 'loner-agent') @@ -1803,7 +1803,7 @@ describe('per-agent presentation', () => { registerEcho(ctx) const coded = await mintAgentScope(ctx, 'coded') const plain = await mintAgentScope(ctx, 'plain') - coded.scope.ctx.tools.presentAs('code') + coded.scope.ctx.tools.presentAs('ptc') // Not merely hidden from the prompt: the transport one agent presents must // not be dispatchable by another that never presented it. @@ -1812,8 +1812,8 @@ describe('per-agent presentation', () => { expect(ctx.tools.get(RUN_CODE_NAME)).toBeUndefined() }) - it('lets an agent opt out of a code-mode deployment', async () => { - const { ctx, systemPrompt } = await setup({ mode: 'code' }) + it('lets an agent opt out of a PTC mode deployment', async () => { + const { ctx, systemPrompt } = await setup({ mode: 'ptc' }) registerEcho(ctx) const { scope, agent } = await mintAgentScope(ctx) @@ -1830,7 +1830,7 @@ describe('per-agent presentation', () => { const { ctx, systemPrompt } = await setup({ mode: 'native' }) registerEcho(ctx) const { scope, agent } = await mintAgentScope(ctx) - const dispose = scope.ctx.tools.presentAs('code') + const dispose = scope.ctx.tools.presentAs('ptc') dispose() @@ -1842,18 +1842,18 @@ describe('per-agent presentation', () => { it('refuses a second declaration for the same agent', async () => { const { ctx } = await setup({ mode: 'native' }) const { scope } = await mintAgentScope(ctx) - scope.ctx.tools.presentAs('code') + scope.ctx.tools.presentAs('ptc') // Two answers to "which form does the model see" is a contradiction, and // silently keeping either one would make the composition unreadable. expect(() => scope.ctx.tools.presentAs('both')) - .toThrow('conflicts with "code" already declared') + .toThrow('conflicts with "ptc" already declared') }) it('refuses an unscoped declaration', async () => { const { ctx } = await setup({ mode: 'native' }) - expect(() => ctx.tools.presentAs('code')) + expect(() => ctx.tools.presentAs('ptc')) .toThrow('requires a scoped context') }) diff --git a/packages/core/tools/tests/py-types.spec.ts b/packages/core/tools/tests/py-types.spec.ts index 0caf9daab6..92fc269332 100644 --- a/packages/core/tools/tests/py-types.spec.ts +++ b/packages/core/tools/tests/py-types.spec.ts @@ -428,7 +428,7 @@ describe('renderToolsSdkPy', () => { // `路径` satisfies `xid_start xid_continue*`, so CPython accepts it as an // attribute and as the `TypedDict` key. Rejecting it would degrade the // whole object, dropping every SIBLING field's name, requiredness and type - // too — and under `mode: 'code'` the native schemas are omitted, so + // too — and under `mode: 'ptc'` the native schemas are omitted, so // nothing else carries them. The nested class name is from the field, so // `camelCase` has to pass the same characters through instead of splitting // on them. @@ -1037,7 +1037,7 @@ describe('renderToolsSdkPy', () => { // `__debug__` is a legal identifier and dunder-form, so it clears both the // identifier rule and the name-mangling rule, but CPython rejects the // annotation at COMPILE time (`SyntaxError: cannot assign to __debug__`) — - // and this block is Code Mode's only SDK, so it must always parse. + // and this block is PTC mode's only SDK, so it must always parse. const t: ToolSdkSchema = { name: 'debugger', description: '', @@ -1111,7 +1111,7 @@ describe('renderToolsSdkPy', () => { // `compile()` raises `SyntaxError: source code string cannot contain null // bytes` for a NUL ANYWHERE in the source text, including inside a string // literal or a comment, so a NUL that survives normalization into a - // docstring or a `#` field comment stops this block — Code Mode's only SDK — + // docstring or a `#` field comment stops this block — PTC mode's only SDK — // from parsing at all. The whitespace collapse does not remove it (a NUL is // not whitespace). Rendering it as a visible escape keeps the source // parseable and still shows the model what the schema said. @@ -1156,7 +1156,7 @@ describe('renderToolsSdkPy', () => { // This is the NUL case, not the invisible-character case: Python source // must be UTF-8-encodable, and `compile()` raises `UnicodeEncodeError: // surrogates not allowed` for a lone surrogate in a string literal and in a - // `#` comment alike, so one would stop this block — Code Mode's only SDK — + // `#` comment alike, so one would stop this block — PTC mode's only SDK — // from parsing. A wire description reaches it: `JSON.parse` on a `"\ud800"` // escape yields exactly this code point. const high = renderToolsSdkPy([described('a\ud800b')]) diff --git a/packages/fs/tool-fs/tests/read-image.spec.ts b/packages/fs/tool-fs/tests/read-image.spec.ts index 583685a699..7244444245 100644 --- a/packages/fs/tool-fs/tests/read-image.spec.ts +++ b/packages/fs/tool-fs/tests/read-image.spec.ts @@ -65,7 +65,7 @@ class CatalogAdapter extends LlmAdapter { } } -/** In-process Code Mode seam fake that invokes the real registry bindings. */ +/** In-process PTC mode seam fake that invokes the real registry bindings. */ class FakeRuntime extends CodeRuntime { readonly language = 'typescript' readonly isolation = 'fake' @@ -101,7 +101,7 @@ async function setup(options: SetupOptions = {}) { const ctx = new Context() await ctx.plugin(SystemPrompt) await ctx.plugin(ToolRuntime, { mode: options.toolMode ?? 'native' }) - if (options.toolMode === 'code' || options.toolMode === 'both') { + if (options.toolMode === 'ptc' || options.toolMode === 'both') { await ctx.plugin(FakeRuntime) } await ctx.plugin(LocalFileSystem, { cwd: dir }) @@ -226,9 +226,9 @@ describe('read_image happy path', () => { expect(result.isError).toBe(false) }) - it('forwards a nested Code Mode image through the outer run_code context', async () => { + it('forwards a nested PTC mode image through the outer run_code context', async () => { await writeFile(join(dir, 'red.png'), PNG_1X1) - const ctx = await setup({ toolMode: 'code' }) + const ctx = await setup({ toolMode: 'ptc' }) const runtime = ctx.codeRuntime as FakeRuntime runtime.behavior = async (request) => { const value = await request.bindings[0]!.functions.read_image!({ file_path: 'red.png' }) @@ -237,7 +237,7 @@ describe('read_image happy path', () => { const result = await call(ctx, RUN_CODE_NAME, { code: 'return await tools.read_image({ file_path: "red.png" })', - description: 'Read the image through Code Mode', + description: 'Read the image through PTC mode', }, agentOn('vision-model')) expect(result.isError).toBe(false) diff --git a/packages/mcp/mcp-client/src/tools.ts b/packages/mcp/mcp-client/src/tools.ts index 8c4780f71d..4c6ab40fbd 100644 --- a/packages/mcp/mcp-client/src/tools.ts +++ b/packages/mcp/mcp-client/src/tools.ts @@ -36,7 +36,7 @@ export interface ToolBridgeOptions { /** State for one sync generation: the current set of disposers keyed by public name. */ export type ToolDisposers = Map void> -/** Canonical MCP result exposed to Code Mode without discarding protocol blocks. */ +/** Canonical MCP result exposed to PTC mode without discarding protocol blocks. */ export type McpResult = { content: JsonValue[] structuredContent?: Structured diff --git a/packages/plan/plan-mode/tests/plan-mode.spec.ts b/packages/plan/plan-mode/tests/plan-mode.spec.ts index f08769c155..f9717e0f39 100644 --- a/packages/plan/plan-mode/tests/plan-mode.spec.ts +++ b/packages/plan/plan-mode/tests/plan-mode.spec.ts @@ -138,7 +138,7 @@ function registerNamedTools(ctx: Context, names: string[]): void { } } -/** Assert the mapped Code Mode SDK includes the stable plan exit binding and test tools. */ +/** Assert the mapped PTC mode SDK includes the stable plan exit binding and test tools. */ function expectPlanCodeSdkBindings(sdk: string): void { expect(sdk).toContain('interface ToolArgsMap {') expect(sdk).toContain('read: Record;') @@ -456,9 +456,9 @@ describe('the soft layer', () => { .toEqual(['exit_plan_mode', 'read', 'added-later']) }) - it('keeps run_code the only wire tool in plan mode under the registry Code Mode; the SDK gains the exit binding', async () => { + it('keeps run_code the only wire tool in plan mode under the registry PTC mode; the SDK gains the exit binding', async () => { // Minimal scriptable runtime: the SDK section resolves ctx.codeRuntime at - // assembly time (the code-mode.spec fake's shape). + // assembly time (the ptc.spec fake's shape). class FakeRuntime extends CodeRuntime { readonly language = 'typescript' readonly isolation = 'fake' @@ -466,7 +466,7 @@ describe('the soft layer', () => { } const ctx = new Context() await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRuntime, { mode: 'code' }) + await ctx.plugin(ToolRuntime, { mode: 'ptc' }) await ctx.plugin(FakeRuntime) await ctx.plugin(PlanModeController, PLAN_CONFIG) registerNamedTools(ctx, ['read', 'write']) @@ -500,7 +500,7 @@ describe('the soft layer', () => { expectPlanCodeSdkBindings(sdk) }) - it('keeps the Code Mode SDK byte-identical across mode switches', async () => { + it('keeps the PTC mode SDK byte-identical across mode switches', async () => { class FakeRuntime extends CodeRuntime { readonly language = 'typescript' readonly isolation = 'fake' @@ -508,7 +508,7 @@ describe('the soft layer', () => { } const withPlanMode = new Context() await withPlanMode.plugin(SystemPrompt) - await withPlanMode.plugin(ToolRuntime, { mode: 'code' }) + await withPlanMode.plugin(ToolRuntime, { mode: 'ptc' }) await withPlanMode.plugin(FakeRuntime) await withPlanMode.plugin(PlanModeController, PLAN_CONFIG) registerNamedTools(withPlanMode, ['read', 'write']) @@ -523,7 +523,7 @@ describe('the soft layer', () => { // with a deployment that does not compose plan mode at all. const bare = new Context() await bare.plugin(SystemPrompt) - await bare.plugin(ToolRuntime, { mode: 'code' }) + await bare.plugin(ToolRuntime, { mode: 'ptc' }) await bare.plugin(FakeRuntime) registerNamedTools(bare, ['read', 'write']) const bareSdk = (await bare.systemPrompt.assemble({ agent })).sections.find(section => section.name === 'tools:sdk')?.text ?? '' @@ -854,8 +854,8 @@ describe('exit_plan_mode', () => { expect(asked[0]?.questions[0]?.options?.map(option => option.label)).toEqual(['Approve', 'Keep planning']) }) - it('carries the exact plan through a Code Mode review and logs the nested dispatch', async () => { - const plan = '# Code Mode plan\n\nUse the existing seam.' + it('carries the exact plan through a PTC mode review and logs the nested dispatch', async () => { + const plan = '# PTC mode plan\n\nUse the existing seam.' class ExitRuntime extends CodeRuntime { readonly language = 'typescript' readonly isolation = 'fake' @@ -867,7 +867,7 @@ describe('exit_plan_mode', () => { } const ctx = new Context() await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRuntime, { mode: 'code' }) + await ctx.plugin(ToolRuntime, { mode: 'ptc' }) await ctx.plugin(ExitRuntime) await ctx.plugin(PlanModeController, PLAN_CONFIG) await ctx.plugin(AgentRegistry) @@ -879,7 +879,7 @@ describe('exit_plan_mode', () => { return Promise.resolve({ answers: [{ id: 'plan-review', selected: ['Approve'] }] }) }, }) - const agent = await agentWithSession(ctx, 'code-mode-exit', { active: true }) + const agent = await agentWithSession(ctx, 'ptc-exit', { active: true }) const result = await ctx.tools.execute({ callId: ToolCallId(`call-exit-${++callCounter}`), diff --git a/packages/preset/agent-presets/presets/code/preset.yml b/packages/preset/agent-presets/presets/code/preset.yml deleted file mode 100644 index fc0836f479..0000000000 --- a/packages/preset/agent-presets/presets/code/preset.yml +++ /dev/null @@ -1,3 +0,0 @@ -name: PTC 模式 -description: 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 -order: 2 diff --git a/packages/preset/agent-presets/presets/cordis/skills/editing-cordis-compositions/SKILL.md b/packages/preset/agent-presets/presets/cordis/skills/editing-cordis-compositions/SKILL.md index c9adee9961..14ddd3b1d7 100644 --- a/packages/preset/agent-presets/presets/cordis/skills/editing-cordis-compositions/SKILL.md +++ b/packages/preset/agent-presets/presets/cordis/skills/editing-cordis-compositions/SKILL.md @@ -9,7 +9,7 @@ Every capability in this harness is a plugin row in a `cordis.yml`. There is no ## Off-limits -**Never edit, delete, or overwrite a preset that ships with the deployment** — the `agent-presets` directory beside the deployment's own config, which supplies `standard`, `code`, `minimal`, and `cordis`. Never escalate the sandbox to reach it, even when a change there looks quicker. An upgrade overwrites that install, and corrupting `cordis` disables preset authoring itself. Reading a shipped composition is the intended way to start; writing to one is not, and neither is editing the host composition to work around a preset limitation. +**Never edit, delete, or overwrite a preset that ships with the deployment** — the `agent-presets` directory beside the deployment's own config, which supplies `standard`, `ptc`, `minimal`, and `cordis`. Never escalate the sandbox to reach it, even when a change there looks quicker. An upgrade overwrites that install, and corrupting `cordis` disables preset authoring itself. Reading a shipped composition is the intended way to start; writing to one is not, and neither is editing the host composition to work around a preset limitation. To change what a shipped preset does, copy it and edit the copy. Locally authored presets under the user root are yours to create, edit, and delete. diff --git a/packages/preset/agent-presets/presets/code/agent.cordis.yml b/packages/preset/agent-presets/presets/ptc/agent.cordis.yml similarity index 98% rename from packages/preset/agent-presets/presets/code/agent.cordis.yml rename to packages/preset/agent-presets/presets/ptc/agent.cordis.yml index e3bbe8fad2..3c8406f907 100644 --- a/packages/preset/agent-presets/presets/code/agent.cordis.yml +++ b/packages/preset/agent-presets/presets/ptc/agent.cordis.yml @@ -1,4 +1,4 @@ -# The `code` agent preset: the standard coding agent, presented as Code Mode. +# The `ptc` agent preset: the standard coding agent, presented as PTC mode. # # Everything in `standard` is here unchanged. What is added is the `tool-presentation` # row: instead of one tool call per action, the model writes a TypeScript @@ -259,10 +259,10 @@ # ── presentation ──────────────────────────────────────────────────────────── -# Code Mode for this agent alone. The row waits for the host's `codeRuntime` +# PTC mode for this agent alone. The row waits for the host's `codeRuntime` # rather than assuming it: a deployment that composes no TypeScript runtime # fails this preset at mount, naming this id, instead of at the first request. - id: tool-presentation name: '@deepseek-ai/dsh-agent-tool-presentation' config: - mode: code + mode: ptc diff --git a/packages/preset/agent-presets/presets/ptc/preset.yml b/packages/preset/agent-presets/presets/ptc/preset.yml new file mode 100644 index 0000000000..e0351de114 --- /dev/null +++ b/packages/preset/agent-presets/presets/ptc/preset.yml @@ -0,0 +1,3 @@ +name: PTC 模式 +description: 具备标准模式的全部能力,并通过 PTC 模式 SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 +order: 2 diff --git a/packages/preset/agent-presets/tests/shipped-root.spec.ts b/packages/preset/agent-presets/tests/shipped-root.spec.ts index 85ede1a90a..d45f3a1373 100644 --- a/packages/preset/agent-presets/tests/shipped-root.spec.ts +++ b/packages/preset/agent-presets/tests/shipped-root.spec.ts @@ -56,7 +56,7 @@ describe('the shipped preset root', () => { const ctx = await roster({ includeUserRoot: false }) const listed = await ctx.agentPresets.list() - expect(listed.map(preset => preset.id).sort()).toEqual(['code', 'cordis', 'minimal', 'standard']) + expect(listed.map(preset => preset.id).sort()).toEqual(['cordis', 'minimal', 'ptc', 'standard']) expect(listed.every(preset => preset.trust === 'system')).toBe(true) // Not `broken === undefined`: health asks whether each row's package is // installed above the base, and the shipped rows name packages the @@ -96,7 +96,7 @@ describe('the shipped preset root', () => { }) it('enables web_fetch in each tool-bearing Web app preset', async () => { - for (const id of ['cordis', 'code', 'standard']) { + for (const id of ['cordis', 'ptc', 'standard']) { const source = await readFile(join(SHIPPED_PRESET_ROOT, id, 'agent.cordis.yml'), 'utf8') const entries: unknown = yaml.load(source, { schema: entryListSchema }) if (!Array.isArray(entries)) throw new TypeError(`${id} preset must contain a Cordis entry list`) diff --git a/packages/spill/spill-policy/src/index.ts b/packages/spill/spill-policy/src/index.ts index d6ebb1b664..058972c9eb 100644 --- a/packages/spill/spill-policy/src/index.ts +++ b/packages/spill/spill-policy/src/index.ts @@ -11,7 +11,7 @@ * The policy only decides WHEN to spill and composes the notice. * * A second arm applies the SAME cap to the durable log: the - * `tools/code-dispatch-log` waterfall bounds the `tool/code-dispatch` event's + * `tools/ptc-dispatch-log` waterfall bounds the `tool/ptc-dispatch` event's * copy of an oversized `run_code` sub-call result (the program's value is * untouched; UIs and replay read the full text through the spill artifact). * @@ -208,13 +208,13 @@ export function apply(ctx: Context, config: Config): void { return { kind: 'accept', content: replaced, ...decision.additionalContexts ? { additionalContexts: decision.additionalContexts } : {} } }, { prepend: true }) - // The durable-log arm: bound the `tool/code-dispatch` event's copy of an + // The durable-log arm: bound the `tool/ptc-dispatch` event's copy of an // oversized sub-call result the same way the model-facing arm bounds an // outer result. The program's returned value is untouched (it already // crossed the worker boundary whole); only the session log's copy shrinks // to preview + locator, so replay and UIs read the full text through the // spill artifact exactly as they do for spilled native results. - ctx.on('tools/code-dispatch-log', async (dispatch, next): Promise => { + ctx.on('tools/ptc-dispatch-log', async (dispatch, next): Promise => { const content = await next() // `read` sub-calls spill too: the log copy is not model context, so the // read → spill → read-again loop the post-execute arm avoids cannot diff --git a/packages/spill/spill-policy/tests/spill-policy.spec.ts b/packages/spill/spill-policy/tests/spill-policy.spec.ts index 433d2c1797..375a9b3fd7 100644 --- a/packages/spill/spill-policy/tests/spill-policy.spec.ts +++ b/packages/spill/spill-policy/tests/spill-policy.spec.ts @@ -186,11 +186,11 @@ describe('oversized plain-text replacement', () => { }) }) -describe('outer Code Mode failure capture', () => { +describe('outer PTC mode failure capture', () => { it('spills the bounded output-limit diagnostic through the ordinary outer-result policy', async () => { const ctx = new Context() await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRuntime, { mode: 'code' }) + await ctx.plugin(ToolRuntime, { mode: 'ptc' }) await ctx.plugin(StubStore) await ctx.plugin(SpillPolicy, { maxInlineBytes: 200 }) await ctx.plugin(WorkerThreadCodeRuntime, { maxOutputBytes: 500 }) @@ -239,7 +239,7 @@ describe('the durable dispatch-log arm', () => { async function runCodeWith(program: string, maxInlineBytes: number, extraTools: ToolDefinition[] = []) { const ctx = new Context() await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRuntime, { mode: 'code' }) + await ctx.plugin(ToolRuntime, { mode: 'ptc' }) await ctx.plugin(StubStore) await ctx.plugin(SpillPolicy, { maxInlineBytes }) await ctx.plugin(WorkerThreadCodeRuntime, {}) @@ -313,7 +313,7 @@ describe('the durable dispatch-log arm', () => { it('a slow spill backend never delays the program value or a later dispatch slot', async () => { const ctx = new Context() await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRuntime, { mode: 'code' }) + await ctx.plugin(ToolRuntime, { mode: 'ptc' }) await ctx.plugin(StubStore) await ctx.plugin(SpillPolicy, { maxInlineBytes: 100 }) await ctx.plugin(WorkerThreadCodeRuntime, {}) @@ -377,7 +377,7 @@ describe('the durable dispatch-log arm', () => { // lane holds inside the second commit, so the THIRD dispatch cannot start // until a pending save drains — the bound is observable as its missing // start event. - await ctx.plugin(ToolRuntime, { mode: 'code', maxParallelSubCalls: 1 }) + await ctx.plugin(ToolRuntime, { mode: 'ptc', maxParallelSubCalls: 1 }) await ctx.plugin(StubStore) await ctx.plugin(SpillPolicy, { maxInlineBytes: 100 }) await ctx.plugin(WorkerThreadCodeRuntime, {}) @@ -430,7 +430,7 @@ describe('the durable dispatch-log arm', () => { it('a saveText failure keeps the complete content in the durable log (best-effort)', async () => { const ctx = new Context() await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRuntime, { mode: 'code' }) + await ctx.plugin(ToolRuntime, { mode: 'ptc' }) await ctx.plugin(StubStore) await ctx.plugin(SpillPolicy, { maxInlineBytes: 100 }) await ctx.plugin(WorkerThreadCodeRuntime, {}) diff --git a/packages/subagent/subagent-in-process-driver/src/structured.ts b/packages/subagent/subagent-in-process-driver/src/structured.ts index 1b764d7a4c..170a6f706b 100644 --- a/packages/subagent/subagent-in-process-driver/src/structured.ts +++ b/packages/subagent/subagent-in-process-driver/src/structured.ts @@ -4,7 +4,7 @@ * scope, so concurrent runs do not interact and disposal leaves no global residue. The prompt * contribution is ordinary reconstructed request state. * - * Capture commits only after the authoritative `tools/result` succeeds; Code Mode capture also + * Capture commits only after the authoritative `tools/result` succeeds; PTC mode capture also * waits for the enclosing `run_code` result. The terminal result marker and monotonic tool * guard prevent later calls from reopening a completed structured run. * @module @deepseek-ai/dsh-subagent-in-process-driver/structured @@ -124,7 +124,7 @@ export function attachStructuredRuntime(childCtx: Context, schema: ObjectJsonSch /* v8 ignore else -- sequential agent-loop dispatch lets the guard block every later supported call */ if (captured === undefined) captured = { value: entry.value } } else { - /* v8 ignore else -- Code Mode serializes sub-dispatches, so the guard blocks every later supported call */ + /* v8 ignore else -- PTC mode serializes sub-dispatches, so the guard blocks every later supported call */ if (captured === undefined && pending === undefined) { pending = { parent: exec.parent, value: entry.value } } @@ -135,7 +135,7 @@ export function attachStructuredRuntime(childCtx: Context, schema: ObjectJsonSch const entry = pending pending = undefined if (result.isError) return - /* v8 ignore else -- Code Mode serializes outer executions, so the guard blocks every later supported call */ + /* v8 ignore else -- PTC mode serializes outer executions, so the guard blocks every later supported call */ if (captured === undefined) captured = { value: entry.value } }) diff --git a/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts b/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts index 8f21ee4483..7cafcd8e08 100644 --- a/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts +++ b/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts @@ -59,7 +59,7 @@ async function setup(script: Script, options: SetupOptions = {}) { await mountAgentLoopTestDependencies(ctx, { tools: { mode: options.toolMode ?? 'native' }, }) - if (options.toolMode === 'code' || options.toolMode === 'both') { + if (options.toolMode === 'ptc' || options.toolMode === 'both') { ctx.provide('codeRuntime', { language: 'typescript', isolation: 'test', @@ -383,11 +383,11 @@ describe('in-process structured output', () => { await run.dispose() }) - it('keeps pure Code Mode at one wire tool and exposes structured capture through the SDK only', async () => { + it('keeps pure PTC mode at one wire tool and exposes structured capture through the SDK only', async () => { const { ctx, parent, adapter } = await setup([ toolCallResponse('c1', RUN_CODE_NAME, { code: 'return await tools.structured_output({ answer: 12 })', description: 'Capture the structured answer' }), ], { - toolMode: 'code', + toolMode: 'ptc', codeRun: async (request) => { const capture = request.bindings.at(0)?.functions[STRUCTURED_OUTPUT_TOOL] if (!capture) throw new Error('structured_output binding missing') @@ -414,7 +414,7 @@ describe('in-process structured output', () => { toolCallResponse('c1', RUN_CODE_NAME, { code: 'await tools.structured_output({ answer: 12 }); throw new Error("boom")', description: 'Capture then fail the program' }), textResponse('outer code failed'), ], { - toolMode: 'code', + toolMode: 'ptc', codeRun: async (request) => { const capture = request.bindings.at(0)?.functions[STRUCTURED_OUTPUT_TOOL] if (!capture) throw new Error('structured_output binding missing') @@ -443,7 +443,7 @@ describe('in-process structured output', () => { toolCallResponse('c1', RUN_CODE_NAME, { code: 'return await tools.structured_output({ answer: 12 })', description: 'Capture the structured answer' }), textResponse('outer code was blocked'), ], { - toolMode: 'code', + toolMode: 'ptc', codeRun: async (request) => { const capture = request.bindings.at(0)?.functions[STRUCTURED_OUTPUT_TOOL] if (!capture) throw new Error('structured_output binding missing') diff --git a/packages/terminal/tool-terminal/tests/tools.spec.ts b/packages/terminal/tool-terminal/tests/tools.spec.ts index 406ae59778..15151f7bd2 100644 --- a/packages/terminal/tool-terminal/tests/tools.spec.ts +++ b/packages/terminal/tool-terminal/tests/tools.spec.ts @@ -186,7 +186,7 @@ describe('tool-terminal foreground API', () => { expect(empty).toMatchObject({ isError: false, value: [] }) }) - it('projects every terminal DTO into the generated Code Mode output map', async () => { + it('projects every terminal DTO into the generated PTC mode output map', async () => { const { ctx } = await setup(false) const schemas = TOOL_NAMES.map((toolName): ToolSdkSchema => { const definition = ctx.tools.get(toolName) diff --git a/scripts/demo-code-mode.mjs b/scripts/demo-ptc.mjs similarity index 58% rename from scripts/demo-code-mode.mjs rename to scripts/demo-ptc.mjs index a7de910ca4..d0c5c059bc 100644 --- a/scripts/demo-code-mode.mjs +++ b/scripts/demo-ptc.mjs @@ -1,8 +1,8 @@ -/** Run one headless task through the shipped Code Mode composition. Requires a model credential. */ +/** Run one headless task through the shipped PTC mode composition. Requires a model credential. */ import { spawn } from 'node:child_process' const task = process.argv.slice(2).join(' ').trim() - || 'Inspect this repository with Code Mode and report its top-level architecture.' + || 'Inspect this repository with PTC mode and report its top-level architecture.' const child = spawn(process.execPath, [ '--import', @@ -13,6 +13,6 @@ const child = spawn(process.execPath, [ task, ], { stdio: 'inherit', - env: { ...process.env, DSH_TOOLS_MODE: 'code' }, + env: { ...process.env, DSH_TOOLS_MODE: 'ptc' }, }) child.on('exit', (code, signal) => { process.exit(signal !== null ? 1 : code ?? 1) }) diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index dcca3d0722..1bae81253b 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -517,7 +517,7 @@ export const LINK_MAP: Readonly> = { TeamWaitResult: 'agent-team.md', UpdateTeamTaskRequest: 'agent-team.md', TokenMeasurement: 'token-meter.md', - CodeDispatchLog: 'tools.md', + PtcDispatchLog: 'tools.md', PostToolDecision: 'tools.md', PreToolDecision: 'tools.md', ToolDefinition: 'tools.md', diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 65876fd5d0..0ea6043121 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -353,7 +353,7 @@ const SERVICE_ROLES: ServiceRole[] = [ title: 'Tool registry and guarded execution pipeline', mode: 'core', consumers: ['agent-loop', 'tool-ask-user', 'tool-bash', 'tool-cordis', 'tool-fs', 'tool-terminal', 'tool-skill', 'tool-subagent', 'tool-todo', 'tool-web'], - note: 'Registers capabilities, owns Code Mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation.', + note: 'Registers capabilities, owns PTC mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation.', }, { key: 'userQuestions', @@ -525,7 +525,7 @@ const SERVICE_ROLES: ServiceRole[] = [ mode: 'seam', implementations: ['code-runtime-worker-thread'], consumers: ['tools'], - note: 'Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the tool registry consumes it for Code Mode).', + note: 'Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the tool registry consumes it for PTC mode).', }, { key: 'fs', @@ -1432,7 +1432,7 @@ function renderToolPipeline(): string { ' allResults --> context', '```', '', - 'Filesystem read-before-edit checks stay below `tool-fs` on `fs/*` events. Generic pre/post waterfalls host hooks and approval policy; `ctx.approval` resolves asks before monotonic guards, and owner policy that must not be reordered remains a registered guard. Around-dispatch concerns such as timeouts wrap `tools/execute`. The registry losslessly snapshots the candidate result and normalizes a snapshot failure before the visible definition\'s snapshotted `finalizeContent` callback enforces its synchronous content-only invariant. `tools/result` then observes the immutable, lossless-JSON outcome. This lets hooks span tool families without coupling the tools to one policy service. Code Mode sends both the reserved `run_code` transport and its serialized sub-calls through the pipeline; sub-calls carry the parent token, log `tool/code-dispatch`, return denials as binding rejections, and omit `additionalContexts` to preserve call/result adjacency.', + 'Filesystem read-before-edit checks stay below `tool-fs` on `fs/*` events. Generic pre/post waterfalls host hooks and approval policy; `ctx.approval` resolves asks before monotonic guards, and owner policy that must not be reordered remains a registered guard. Around-dispatch concerns such as timeouts wrap `tools/execute`. The registry losslessly snapshots the candidate result and normalizes a snapshot failure before the visible definition\'s snapshotted `finalizeContent` callback enforces its synchronous content-only invariant. `tools/result` then observes the immutable, lossless-JSON outcome. This lets hooks span tool families without coupling the tools to one policy service. PTC mode sends both the reserved `run_code` transport and its serialized sub-calls through the pipeline; sub-calls carry the parent token, log `tool/code-dispatch`, return denials as binding rejections, and omit `additionalContexts` to preserve call/result adjacency.', '', ...maintenanceFooter(maintenance), ].join('\n') diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts index 6cfbc4d618..36e0b0833c 100644 --- a/scripts/gen-tool-catalog.ts +++ b/scripts/gen-tool-catalog.ts @@ -204,16 +204,16 @@ const TOOL_PACKAGES: ToolPackage[] = [ { pkg: '@deepseek-ai/dsh-tools', dir: 'tools', - source: 'packages/core/tools/src/code-mode.ts', + source: 'packages/core/tools/src/ptc.ts', requires: ['ctx.tools', 'ctx.codeRuntime (execution time)', 'ctx.systemPrompt'], writes: ['tool/call', 'one tool/code-dispatch-start + tool/code-dispatch pair per bridged sub-call', 'tool/result'], // The registry's OWN tool: run_code exists only under a non-native mode // (the registry registers it in its constructor; the code runtime is read // at assembly/execution time, so the schema harvest needs none mounted). - toolsConfig: { mode: 'code' }, + toolsConfig: { mode: 'ptc' }, async mount() {}, note: - 'Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: code` / `mode: both` (see the Code Mode Agent Note). Under `code` it is the registry\'s only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime\'s language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result.', + 'Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: ptc` / `mode: both` (see the PTC mode Agent Note). Under `ptc` it is the registry\'s only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime\'s language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result.', }, { pkg: '@deepseek-ai/dsh-plan-mode', diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index b54672eb87..3820d48ea4 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -793,7 +793,7 @@ }, { "doc": "docs/subsystems/tools.md", - "symbol": "CodeDispatchLog", + "symbol": "PtcDispatchLog", "source": "packages/core/tools/src/index.ts" }, { diff --git a/scripts/verify-application-entrypoints.spec.ts b/scripts/verify-application-entrypoints.spec.ts index b58cb7b74b..a03240eb1c 100644 --- a/scripts/verify-application-entrypoints.spec.ts +++ b/scripts/verify-application-entrypoints.spec.ts @@ -86,12 +86,12 @@ describe('application entrypoints', () => { it('rejects a classified demo wrapper that launches a package entry', () => { const root = fixture() - write(root, 'package.json', JSON.stringify({ scripts: { 'demo:code-mode': 'node scripts/demo-code-mode.mjs' } })) - write(root, 'scripts/demo-code-mode.mjs', "spawn('node', ['packages/example/app/src/bin.ts'])\n") + write(root, 'package.json', JSON.stringify({ scripts: { 'demo:ptc': 'node scripts/demo-ptc.mjs' } })) + write(root, 'scripts/demo-ptc.mjs', "spawn('node', ['packages/example/app/src/bin.ts'])\n") expect(applicationEntrypointViolations(root)).toEqual([ - 'scripts/demo-code-mode.mjs: application demo wrapper must launch apps/cli/src/bin.ts', - 'scripts/demo-code-mode.mjs: application demo wrapper must not launch a package entry directly', + 'scripts/demo-ptc.mjs: application demo wrapper must launch apps/cli/src/bin.ts', + 'scripts/demo-ptc.mjs: application demo wrapper must not launch a package entry directly', ]) }) diff --git a/scripts/verify-application-entrypoints.ts b/scripts/verify-application-entrypoints.ts index 964383a62f..d80deb1dce 100644 --- a/scripts/verify-application-entrypoints.ts +++ b/scripts/verify-application-entrypoints.ts @@ -48,7 +48,7 @@ const EXECUTABLE_SOURCE_ALLOWLIST = new Map([ /** Root demos are application wrappers and therefore must visibly select dsh. */ const ROOT_DEMO_POLICIES = new Map([ - ['demo:code-mode', { kind: 'dsh-wrapper', wrapper: 'scripts/demo-code-mode.mjs' }], + ['demo:ptc', { kind: 'dsh-wrapper', wrapper: 'scripts/demo-ptc.mjs' }], ['demo:inspector', { kind: 'dsh-direct' }], ]) diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 46e80a86c1..1caf31ec91 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -49,10 +49,10 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/shell/shell-env': { kind: 'indirect', reason: 'The env service exposes managed DSH_* facts through the shell tools (dsh-tool-bash/dsh-tool-pwsh); it registers no prompt or schema of its own.' }, 'packages/shell/bash-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-bash.' }, '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 Code Mode in dsh-tools.' }, + '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-worker-thread': { kind: 'indirect', reason: 'The worker backend delegates model rendering to Code Mode in dsh-tools.' }, - 'packages/code-runtime/code-runtime-python': { kind: 'indirect', reason: 'The CPython subprocess backend delegates model rendering to Code Mode in dsh-tools.' }, + 'packages/code-runtime/code-runtime-worker-thread': { kind: 'indirect', reason: 'The worker backend delegates model rendering to PTC mode in dsh-tools.' }, + 'packages/code-runtime/code-runtime-python': { kind: 'indirect', reason: 'The CPython subprocess backend delegates 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.' }, 'packages/core/agent-default-model': { kind: 'indirect', reason: 'The service supplies a ModelSelection; request assembly and adapters own the model-visible request.' }, diff --git a/snapshots/session/code-mode-read-image/snapshot.yml b/snapshots/session/code-mode-read-image/snapshot.yml deleted file mode 100644 index 36c929b8e5..0000000000 --- a/snapshots/session/code-mode-read-image/snapshot.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 1 -scenario: code-mode-read-image -profile: headless -composition: code-image -recording: authored -header: - class: code-image - pin: true - toolSchemasSource: code-mode-turn -platform: posix -workspace: - final: true diff --git a/snapshots/session/code-mode-workspace-context/snapshot.yml b/snapshots/session/code-mode-workspace-context/snapshot.yml deleted file mode 100644 index e16b7e5793..0000000000 --- a/snapshots/session/code-mode-workspace-context/snapshot.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 1 -scenario: code-mode-workspace-context -profile: headless -composition: code-workspace-context -recording: authored -header: - class: code-workspace-context - pin: true - systemPromptSource: code-mode-turn - toolSchemasSource: code-mode-turn -replay: - override: true diff --git a/snapshots/session/cordis-inspect-jsdoc/cordis.yml b/snapshots/session/cordis-inspect-jsdoc/cordis.yml index febc5da6c5..2da7998319 100644 --- a/snapshots/session/cordis-inspect-jsdoc/cordis.yml +++ b/snapshots/session/cordis-inspect-jsdoc/cordis.yml @@ -1,4 +1,4 @@ -# Add Code Mode and Cordis tools to the base spawn/workflow stack, exercising +# Add PTC mode and Cordis tools to the base spawn/workflow stack, exercising # all four boundaries in one ACP snapshot. - id: agent-default-model name: '@deepseek-ai/dsh-agent-default-model' diff --git a/snapshots/session/code-mode-read-image/cordis.snapshot.yml b/snapshots/session/ptc-read-image/cordis.snapshot.yml similarity index 94% rename from snapshots/session/code-mode-read-image/cordis.snapshot.yml rename to snapshots/session/ptc-read-image/cordis.snapshot.yml index a187ccc673..fdf773028d 100644 --- a/snapshots/session/code-mode-read-image/cordis.snapshot.yml +++ b/snapshots/session/ptc-read-image/cordis.snapshot.yml @@ -1,4 +1,4 @@ -# Keyless replay combines Code Mode with the durable image store and an exact +# Keyless replay combines PTC mode with the durable image store and an exact # image-capable replay route. The scenario generates its tiny PNG inside the # run_code program, then exercises read_image as a nested dispatch. - id: llm-deepseek @@ -25,7 +25,7 @@ - id: tools name: '@deepseek-ai/dsh-tools' config: - mode: code + mode: ptc - id: system-prompt name: '@deepseek-ai/dsh-system-prompt' diff --git a/snapshots/session/code-mode-read-image/cordis.yml b/snapshots/session/ptc-read-image/cordis.yml similarity index 91% rename from snapshots/session/code-mode-read-image/cordis.yml rename to snapshots/session/ptc-read-image/cordis.yml index 8e469c69d1..01575986b5 100644 --- a/snapshots/session/code-mode-read-image/cordis.yml +++ b/snapshots/session/ptc-read-image/cordis.yml @@ -1,4 +1,4 @@ -# Code Mode image overlay: mounts the worker runtime and durable attachment +# PTC mode image overlay: mounts the worker runtime and durable attachment # store so a nested read_image result can cross the generic rich-result bridge. # The live config selects the shipped vision route for manual use. - id: agent-default-model @@ -21,7 +21,7 @@ - id: tools name: '@deepseek-ai/dsh-tools' config: - mode: code + mode: ptc - id: system-prompt name: '@deepseek-ai/dsh-system-prompt' diff --git a/snapshots/session/code-mode-read-image/session.jsonl b/snapshots/session/ptc-read-image/session.jsonl similarity index 100% rename from snapshots/session/code-mode-read-image/session.jsonl rename to snapshots/session/ptc-read-image/session.jsonl diff --git a/snapshots/session/ptc-read-image/snapshot.yml b/snapshots/session/ptc-read-image/snapshot.yml new file mode 100644 index 0000000000..e7a2fa6085 --- /dev/null +++ b/snapshots/session/ptc-read-image/snapshot.yml @@ -0,0 +1,12 @@ +version: 1 +scenario: ptc-read-image +profile: headless +composition: ptc-image +recording: authored +header: + class: ptc-image + pin: true + toolSchemasSource: ptc-turn +platform: posix +workspace: + final: true diff --git a/snapshots/session/code-mode-read-image/system-prompt.expected.md b/snapshots/session/ptc-read-image/system-prompt.expected.md similarity index 100% rename from snapshots/session/code-mode-read-image/system-prompt.expected.md rename to snapshots/session/ptc-read-image/system-prompt.expected.md diff --git a/snapshots/session/code-mode-read-image/workspace.expected/red.png b/snapshots/session/ptc-read-image/workspace.expected/red.png similarity index 100% rename from snapshots/session/code-mode-read-image/workspace.expected/red.png rename to snapshots/session/ptc-read-image/workspace.expected/red.png diff --git a/snapshots/session/code-mode-turn/cordis.snapshot.yml b/snapshots/session/ptc-turn/cordis.snapshot.yml similarity index 93% rename from snapshots/session/code-mode-turn/cordis.snapshot.yml rename to snapshots/session/ptc-turn/cordis.snapshot.yml index bce8a6a4d5..805d0c6ce8 100644 --- a/snapshots/session/code-mode-turn/cordis.snapshot.yml +++ b/snapshots/session/ptc-turn/cordis.snapshot.yml @@ -1,4 +1,4 @@ -# Keyless Code Mode combines the runtime/registry changes with the +# Keyless PTC mode combines the runtime/registry changes with the # DeepSeek-to-replay swap in one profile patch. - id: llm-deepseek name: '@deepseek-ai/dsh-llm-deepseek' @@ -24,7 +24,7 @@ - id: tools name: '@deepseek-ai/dsh-tools' config: - mode: code + mode: ptc - id: system-prompt name: '@deepseek-ai/dsh-system-prompt' diff --git a/snapshots/session/code-mode-turn/cordis.yml b/snapshots/session/ptc-turn/cordis.yml similarity index 91% rename from snapshots/session/code-mode-turn/cordis.yml rename to snapshots/session/ptc-turn/cordis.yml index 830ffe0bf0..46cbe0345a 100644 --- a/snapshots/session/code-mode-turn/cordis.yml +++ b/snapshots/session/ptc-turn/cordis.yml @@ -1,4 +1,4 @@ -# Code Mode adds `ctx.codeRuntime` and changes the registry to one wire tool, +# PTC mode adds `ctx.codeRuntime` and changes the registry to one wire tool, # `run_code`, plus its generated TypeScript SDK prompt. The demo and snapshot # recorder apply this profile patch; replay applies its sibling patch. - id: agent-default-model @@ -21,7 +21,7 @@ - id: tools name: '@deepseek-ai/dsh-tools' config: - mode: code + mode: ptc - id: system-prompt name: '@deepseek-ai/dsh-system-prompt' diff --git a/snapshots/session/code-mode-turn/session.jsonl b/snapshots/session/ptc-turn/session.jsonl similarity index 100% rename from snapshots/session/code-mode-turn/session.jsonl rename to snapshots/session/ptc-turn/session.jsonl diff --git a/snapshots/session/code-mode-turn/snapshot.yml b/snapshots/session/ptc-turn/snapshot.yml similarity index 53% rename from snapshots/session/code-mode-turn/snapshot.yml rename to snapshots/session/ptc-turn/snapshot.yml index f9be9378f0..82b648e356 100644 --- a/snapshots/session/code-mode-turn/snapshot.yml +++ b/snapshots/session/ptc-turn/snapshot.yml @@ -1,8 +1,8 @@ version: 1 -scenario: code-mode-turn +scenario: ptc-turn profile: headless -composition: code +composition: ptc recording: live header: - class: code + class: ptc pin: true diff --git a/snapshots/session/code-mode-turn/system-prompt.expected.md b/snapshots/session/ptc-turn/system-prompt.expected.md similarity index 100% rename from snapshots/session/code-mode-turn/system-prompt.expected.md rename to snapshots/session/ptc-turn/system-prompt.expected.md diff --git a/snapshots/session/code-mode-turn/tool-schemas.expected.json b/snapshots/session/ptc-turn/tool-schemas.expected.json similarity index 100% rename from snapshots/session/code-mode-turn/tool-schemas.expected.json rename to snapshots/session/ptc-turn/tool-schemas.expected.json diff --git a/snapshots/session/code-mode-workspace-context/cordis.snapshot.yml b/snapshots/session/ptc-workspace-context/cordis.snapshot.yml similarity index 87% rename from snapshots/session/code-mode-workspace-context/cordis.snapshot.yml rename to snapshots/session/ptc-workspace-context/cordis.snapshot.yml index eb65aa20de..59292c9b9f 100644 --- a/snapshots/session/code-mode-workspace-context/cordis.snapshot.yml +++ b/snapshots/session/ptc-workspace-context/cordis.snapshot.yml @@ -1,5 +1,5 @@ -# Keyless replay counterpart of code-mode-workspace-context.cordis.yml. It adds -# Code Mode to the default filesystem suite and swaps in replay. +# Keyless replay counterpart of ptc-workspace-context.cordis.yml. It adds +# PTC mode to the default filesystem suite and swaps in replay. - id: llm-deepseek name: '@deepseek-ai/dsh-llm-deepseek' disabled: true @@ -24,7 +24,7 @@ - id: tools name: '@deepseek-ai/dsh-tools' config: - mode: code + mode: ptc - id: system-prompt name: '@deepseek-ai/dsh-system-prompt' diff --git a/snapshots/session/code-mode-workspace-context/cordis.yml b/snapshots/session/ptc-workspace-context/cordis.yml similarity index 89% rename from snapshots/session/code-mode-workspace-context/cordis.yml rename to snapshots/session/ptc-workspace-context/cordis.yml index 11e7ef2b25..391e99f4f9 100644 --- a/snapshots/session/code-mode-workspace-context/cordis.yml +++ b/snapshots/session/ptc-workspace-context/cordis.yml @@ -1,4 +1,4 @@ -# Code Mode agent-instructions snapshot recording overlay. The default filesystem +# PTC mode agent-instructions snapshot recording overlay. The default filesystem # tools trigger nested instruction discovery after a read. - id: agent-default-model name: '@deepseek-ai/dsh-agent-default-model' @@ -20,7 +20,7 @@ - id: tools name: '@deepseek-ai/dsh-tools' config: - mode: code + mode: ptc - id: system-prompt name: '@deepseek-ai/dsh-system-prompt' diff --git a/snapshots/session/code-mode-workspace-context/replay.override.json b/snapshots/session/ptc-workspace-context/replay.override.json similarity index 100% rename from snapshots/session/code-mode-workspace-context/replay.override.json rename to snapshots/session/ptc-workspace-context/replay.override.json diff --git a/snapshots/session/code-mode-workspace-context/session.jsonl b/snapshots/session/ptc-workspace-context/session.jsonl similarity index 100% rename from snapshots/session/code-mode-workspace-context/session.jsonl rename to snapshots/session/ptc-workspace-context/session.jsonl diff --git a/snapshots/session/ptc-workspace-context/snapshot.yml b/snapshots/session/ptc-workspace-context/snapshot.yml new file mode 100644 index 0000000000..65874ba567 --- /dev/null +++ b/snapshots/session/ptc-workspace-context/snapshot.yml @@ -0,0 +1,12 @@ +version: 1 +scenario: ptc-workspace-context +profile: headless +composition: ptc-workspace-context +recording: authored +header: + class: ptc-workspace-context + pin: true + systemPromptSource: ptc-turn + toolSchemasSource: ptc-turn +replay: + override: true diff --git a/snapshots/session/code-mode-workspace-context/workspace/AGENTS.md b/snapshots/session/ptc-workspace-context/workspace/AGENTS.md similarity index 100% rename from snapshots/session/code-mode-workspace-context/workspace/AGENTS.md rename to snapshots/session/ptc-workspace-context/workspace/AGENTS.md diff --git a/snapshots/session/code-mode-workspace-context/workspace/nested/AGENTS.md b/snapshots/session/ptc-workspace-context/workspace/nested/AGENTS.md similarity index 100% rename from snapshots/session/code-mode-workspace-context/workspace/nested/AGENTS.md rename to snapshots/session/ptc-workspace-context/workspace/nested/AGENTS.md diff --git a/snapshots/session/code-mode-workspace-context/workspace/nested/task.txt b/snapshots/session/ptc-workspace-context/workspace/nested/task.txt similarity index 100% rename from snapshots/session/code-mode-workspace-context/workspace/nested/task.txt rename to snapshots/session/ptc-workspace-context/workspace/nested/task.txt diff --git a/snapshots/session/skill-load/session.jsonl b/snapshots/session/skill-load/session.jsonl index 06629e31f4..a0d21122d3 100644 --- a/snapshots/session/skill-load/session.jsonl +++ b/snapshots/session/skill-load/session.jsonl @@ -22,7 +22,7 @@ {"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} {"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Load the requested skill."},{"type":"tool-call","id":"call_skill_load","name":"skill","arguments":"{\"name\":\"editing-cordis-compositions\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":100,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":5}},"sourceEventSeqs":[13,14,15,16,17,18,19,20],"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_skill_load","name":"skill","arguments":"{\"name\":\"editing-cordis-compositions\"}"}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_skill_load"},"content":[{"type":"tool-result","toolCallId":"call_skill_load","content":[{"type":"text","text":"\n\nBase directory for this skill: {{cwd}}/.dsh/skills/editing-cordis-compositions\nResolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed.\n\n\n\n# Editing Cordis compositions\n\nEvery capability in this harness is a plugin row in a `cordis.yml`. There is no separate configuration language: changing what an agent can do means changing which rows are composed for it.\n\n## Off-limits\n\n**Never edit, delete, or overwrite a preset that ships with the deployment** — the `agent-presets` directory beside the deployment's own config, which supplies `standard`, `code`, `minimal`, and `cordis`. Never escalate the sandbox to reach it, even when a change there looks quicker. An upgrade overwrites that install, and corrupting `cordis` disables preset authoring itself. Reading a shipped composition is the intended way to start; writing to one is not, and neither is editing the host composition to work around a preset limitation.\n\nTo change what a shipped preset does, copy it and edit the copy. Locally authored presets under the user root are yours to create, edit, and delete.\n\n## Decide the plane first\n\nTwo planes, and the choice is not about how \"agent-related\" something feels — it is about whether the thing must be shared.\n\n**Host composition.** The registries themselves (`tools`, `systemPrompt`, `agents`, `agent-loop`, `sessions`), anything crossing sessions (persistence, session query, storage, settings, credentials, telemetry), the sandbox and approval stack, the model route, and the subagent registry with its spawn/fork backends. One instance for the process.\n\n**Agent preset.** What one session contributes to those registries: its tool plugins, its persona and prompt sections, its compaction policy. One instance per session, mounted under that session's scope and unwound with it.\n\n**A service with a consumer outside the agent plane cannot move into a preset.** `subagents` is the worked example: the registry answers cross-session queries for the host api-proxy, so a per-session copy both starves that host row — it waits forever for a service nothing provides — and collides on the second session, since a provider name registers once. The preset contributes the delegation *tools*; the registry and its backends stay host-side.\n\nA preset is a directory holding one `agent.cordis.yml`, optionally beside a `preset.yml` carrying display metadata — `name` and `description` (and, for shipped presets, a roster `order`). Write the metadata too: a preset without it shows up in every picker as its bare directory name.\n\nLocally authored presets live one directory per preset under `${DSH_HOME:-$HOME/.dsh}/.agent-presets/`, and the shipped set sits beside the deployment's own config. Use those when the user asks where to look. A deployment can configure other roots, so the path you read or edit comes from `list()` or `resolve()` — which is also where `copy()` reports what it just created.\n\n## The roster service\n\n`ctx.agentPresets` owns discovery, authoring, and mounting. You reach it by mounting a temporary plugin that injects it and registers a tool for yourself — `cordis_mount` returns only the mount acknowledgement, so a registered tool is how a service answer gets back to you, and it becomes callable on your next step.\n\nRead `cordis_inspect what:\"api\" name:\"agentPresets\"` for the current signatures before writing the code. What this skill relies on:\n\n- `list()` — every preset with its `id`, `trust` (`system` for the shipped set, `user` for authored ones), and the absolute `path` of its composition file. This is how you locate any composition without knowing the install layout; the directory is that path's parent.\n- `read(id)` — one preset's composition text, without a file tool or a path.\n- `copy(from, id, name?)` — the only authoring write (see below).\n- `standingKeyFor(id)` — mount-validate one preset (see below).\n\n```js\nreturn {\n name: 'preset-tools',\n inject: ['agentPresets', 'tools'],\n apply(ctx) {\n harness.registerTool(ctx, harness.defineTool({\n name: 'preset_check',\n description: 'Mount-validate one preset by id.',\n parameters: { id: { type: 'string', required: true } },\n output: { schema: { type: 'string' }, render(_a, v) { return [{ type: 'text', text: v }] } },\n async execute(args) {\n try {\n await ctx.agentPresets.standingKeyFor(args.id)\n return 'mounted OK'\n } catch (error) {\n return error.message\n }\n },\n }))\n },\n}\n```\n\nUnmount the plugin with `cordis_unmount` when you are done; it is a probe, not a capability to leave behind.\n\n## Authoring a preset\n\n1. **Start from a copy.** `copy(from, id, name)` copies a whole preset directory into the user root — composition, metadata, skill directories, assets. It validates the id against `[a-z0-9][a-z0-9-]*` (it becomes the directory name, so no leading hyphen), refuses an id any root already supplies, rolls a failed copy back, and rewrites the copy's `preset.yml` to keep the source's description while dropping its name and roster `order`. Prefer it over a shell copy: it needs no sandbox escalation, it lands the copy in whichever root this deployment made writable, and the copy is exactly as loadable as its source. `resolve(id)` then names the file it created — that path, not a guessed one, is what the following edits target. `standard` is the full coding agent and the usual source.\n2. **Expect the file sandbox on every edit after the copy.** The user preset root lies outside the session workspace, so under the default `workspace-write` policy the first write there is denied. Only writes are: reading any composition by absolute path needs no escalation. Retry that exact command once with `sandbox_permissions` escalation and a short justification — the user sees and approves it. Batch your writes (one heredoc per file) rather than escalating many small commands. `copy()` itself runs host-side and needs none of this; the edits do.\n3. **Write the copy's `description`** in `preset.yml`, and its `name` if you passed none to `copy()`.\n4. **Edit `agent.cordis.yml`** row by row, keeping the plane rule and the realm rule.\n5. **Mount-validate the result**, then hand off to the user for a real session — both under *Verifying a change*.\n\nA composition written from scratch usually forgets a group realm or a consumer row; a copy starts loadable.\n\n## The rule that catches people\n\n**A row that publishes a service may not sit loose in a preset.** Registering a service without an isolate realm puts it in the process-global realm, so the second session mounting that preset collides with the first. The mount rejects it rather than letting the collision surface later.\n\nWhether a row publishes a service is not visible from its name, and package READMEs are absent from an installed deployment. Read it off the live runtime instead: `cordis_inspect what:\"services\"` lists every service with the fiber that owns it, so a service attributed to a fiber other than the row you are adding is one that row consumes rather than provides. For a row not in your current composition, mount-validate and read the rejection — it names the offending service.\n\nWhen a preset genuinely owns a service, wrap the provider **and every consumer that reaches it** in one group carrying an `isolate` realm. The shipped `standard` composition does this for `workflows`, which nothing outside an agent reads — its `delegation` group, with the delegation tools omitted here:\n\n```yaml\n- id: delegation\n name: cordis:group\n group: true\n isolate:\n workflows: true\n config:\n - id: workflow-worker-thread\n name: '@deepseek-ai/dsh-workflow-worker-thread'\n config:\n provider: spawn\n - id: tool-workflow\n name: '@deepseek-ai/dsh-tool-workflow'\n```\n\n`true` means a realm private to each mounting session. A string label instead joins subtrees into one shared realm; `provide()` still throws on the second registration under that symbol, so a label does not pool instances and is not what a preset needs.\n\nA consumer left outside the group resolves the host's registry, which the preset did not populate, and then contributes nothing. Mount-validation catches that as a row that never activated.\n\nRealms are for services a preset owns, not for every group. A host capability the preset only consumes must stay outside a realm, or the row cannot resolve it: `tool-bash`, `tool-jobs`, and `tool-goal` publish nothing and sit loose in `standard`, which explains in comments which host instance each one resolves and why a realm would break it. Wrapping a consumer row in a realm of its own is the same error as leaving one outside its provider's realm.\n\n## Verifying a change\n\n**`standingKeyFor(id)` is the check.** It composes the preset's plugin subtree for real — the same mount a session start performs, minus the agent — and rejects the four ways a composition fails:\n\n- a row whose package does not resolve (`Cannot find package …`);\n- a row whose config is invalid (`invalid config: $. missing required value`);\n- a row that never activated (`N row(s) did not activate: : waiting for `);\n- a service published into the root realm, which arrives as one of two messages. A name the host does not supply lands in the root realm and the mount audit rejects it: `row(s) published process-global service(s) []; a preset service must sit behind an isolate realm or move to the host composition` — this is the shape a preset's own forgotten realm takes. A name the host already supplies collides before the audit: `service \"\" has been registered at `. Both name the offending service.\n\nIt returns normally when the composition mounts. Run it as the final check on a finished edit rather than after every line: a successful mount installs a standing generation that lives until the process exits, while a failed one disposes its subtree and leaves nothing behind.\n\n**Do not treat the roster's `broken` field as validation.** `list()` reports `broken` from a shape check — the file parses in the loader's YAML dialect and holds named rows — which every failure above passes. It catches a damaged file, not an unusable composition.\n\n`cordis_inspect` reports THIS session's composition, so it confirms what a row does in the runtime you are already in, never what your new preset will do.\n\nAfter a clean mount-validation, ask the user to start a session on the new preset and confirm the tool list; the preset decides tool schemas and prompt sections, and only a real session shows the agent that composition produces.\n\n`cordis_mount` evaluates JavaScript against the live runtime and disappears on restart. It is for probing, not for shipping a capability: a capability belongs in a composition file.\n\n## Native product subagents\n\nCodex and Claude Code providers are independent optional Profile Bundles. Install only the products a Profile needs, then restart the Profile so its Host registers those providers:\n\n```sh\ndsh plugin --profile add @deepseek-ai/dsh-subagent-codex\ndsh plugin --profile add @deepseek-ai/dsh-subagent-claude-code\ndsh plugin --profile remove @deepseek-ai/dsh-subagent-codex\ndsh plugin --profile remove @deepseek-ai/dsh-subagent-claude-code\n```\n\nEach Bundle owns its Host availability; the preset separately grants one Agent its ordinary delegation tool. Never move a product provider into the preset and never add a product-specific settings field. Removing one package withdraws only that provider on the next Profile start.\n\nCopy these disabled templates from a shipped full preset and remove `disabled` only for the products the user requested:\n\n```yaml\n- id: tool-subagent-codex\n name: '@deepseek-ai/dsh-tool-subagent'\n disabled: true\n config:\n provider: codex\n toolName: subagent_codex\n backgroundMode: one-shot\n maxDepth: provider-managed\n\n- id: tool-subagent-claude-code\n name: '@deepseek-ai/dsh-tool-subagent'\n disabled: true\n config:\n provider: claude-code\n toolName: subagent_claude_code\n backgroundMode: one-shot\n maxDepth: provider-managed\n```\n\nFor additional named Codex or Claude Code instances, mount a separate host-plane provider row for each instance with a unique `providerName`, then add a separate preset tool row whose `provider` exactly matches that name and whose `toolName` is also unique. Keep the shipped rows for the default `codex` and `claude-code` names; do not reuse one tool row for several providers or derive either name from permission or environment settings.\n\nThe two rows are independent. Leaving both disabled preserves the copied preset, enabling one exposes only that product tool, and enabling both exposes both. Production `dsh` does not install either optional provider: before enabling a row, install the matching `@deepseek-ai/dsh-subagent-codex` or `@deepseek-ai/dsh-subagent-claude-code` Bundle in the Profile and restart it. Each Bundle registers its dormant default provider and exclusively uses its pinned package-local platform CLI; additional named instances use extra host-plane rows from the same installed package. A preset cannot provide that host dependency. `backgroundMode: one-shot` keeps omitted or `false` calls in the foreground and lets explicit `run_in_background: true` return a generic Job id. Full presets already carry `tool-jobs`, while the base host carries the job registry; retain both so `job_output`, `job_list`, `job_kill`, cancellation, and completion notices stay available. Installing a Bundle or composing a preset row does not start a product, authenticate an account, select a model, probe credentials, or manage native product settings.\n\n## What not to move into a preset\n\n`agent-loop` registers the one agent factory and throws on a second. The registries own the per-session layering and cannot themselves be per-session. Session persistence must stay host-side or the session list fragments. The sandbox, approval, and permission rows are a deliberate boundary: a preset is exactly as privileged as the plugins it names, so letting one relax its own confinement would defeat the confinement.\n\n"}],"isError":false}],"role":"user","id":"{{message:5}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_skill_load"},"content":[{"type":"tool-result","toolCallId":"call_skill_load","content":[{"type":"text","text":"\n\nBase directory for this skill: {{cwd}}/.dsh/skills/editing-cordis-compositions\nResolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed.\n\n\n\n# Editing Cordis compositions\n\nEvery capability in this harness is a plugin row in a `cordis.yml`. There is no separate configuration language: changing what an agent can do means changing which rows are composed for it.\n\n## Off-limits\n\n**Never edit, delete, or overwrite a preset that ships with the deployment** — the `agent-presets` directory beside the deployment's own config, which supplies `standard`, `ptc`, `minimal`, and `cordis`. Never escalate the sandbox to reach it, even when a change there looks quicker. An upgrade overwrites that install, and corrupting `cordis` disables preset authoring itself. Reading a shipped composition is the intended way to start; writing to one is not, and neither is editing the host composition to work around a preset limitation.\n\nTo change what a shipped preset does, copy it and edit the copy. Locally authored presets under the user root are yours to create, edit, and delete.\n\n## Decide the plane first\n\nTwo planes, and the choice is not about how \"agent-related\" something feels — it is about whether the thing must be shared.\n\n**Host composition.** The registries themselves (`tools`, `systemPrompt`, `agents`, `agent-loop`, `sessions`), anything crossing sessions (persistence, session query, storage, settings, credentials, telemetry), the sandbox and approval stack, the model route, and the subagent registry with its spawn/fork backends. One instance for the process.\n\n**Agent preset.** What one session contributes to those registries: its tool plugins, its persona and prompt sections, its compaction policy. One instance per session, mounted under that session's scope and unwound with it.\n\n**A service with a consumer outside the agent plane cannot move into a preset.** `subagents` is the worked example: the registry answers cross-session queries for the host api-proxy, so a per-session copy both starves that host row — it waits forever for a service nothing provides — and collides on the second session, since a provider name registers once. The preset contributes the delegation *tools*; the registry and its backends stay host-side.\n\nA preset is a directory holding one `agent.cordis.yml`, optionally beside a `preset.yml` carrying display metadata — `name` and `description` (and, for shipped presets, a roster `order`). Write the metadata too: a preset without it shows up in every picker as its bare directory name.\n\nLocally authored presets live one directory per preset under `${DSH_HOME:-$HOME/.dsh}/.agent-presets/`, and the shipped set sits beside the deployment's own config. Use those when the user asks where to look. A deployment can configure other roots, so the path you read or edit comes from `list()` or `resolve()` — which is also where `copy()` reports what it just created.\n\n## The roster service\n\n`ctx.agentPresets` owns discovery, authoring, and mounting. You reach it by mounting a temporary plugin that injects it and registers a tool for yourself — `cordis_mount` returns only the mount acknowledgement, so a registered tool is how a service answer gets back to you, and it becomes callable on your next step.\n\nRead `cordis_inspect what:\"api\" name:\"agentPresets\"` for the current signatures before writing the code. What this skill relies on:\n\n- `list()` — every preset with its `id`, `trust` (`system` for the shipped set, `user` for authored ones), and the absolute `path` of its composition file. This is how you locate any composition without knowing the install layout; the directory is that path's parent.\n- `read(id)` — one preset's composition text, without a file tool or a path.\n- `copy(from, id, name?)` — the only authoring write (see below).\n- `standingKeyFor(id)` — mount-validate one preset (see below).\n\n```js\nreturn {\n name: 'preset-tools',\n inject: ['agentPresets', 'tools'],\n apply(ctx) {\n harness.registerTool(ctx, harness.defineTool({\n name: 'preset_check',\n description: 'Mount-validate one preset by id.',\n parameters: { id: { type: 'string', required: true } },\n output: { schema: { type: 'string' }, render(_a, v) { return [{ type: 'text', text: v }] } },\n async execute(args) {\n try {\n await ctx.agentPresets.standingKeyFor(args.id)\n return 'mounted OK'\n } catch (error) {\n return error.message\n }\n },\n }))\n },\n}\n```\n\nUnmount the plugin with `cordis_unmount` when you are done; it is a probe, not a capability to leave behind.\n\n## Authoring a preset\n\n1. **Start from a copy.** `copy(from, id, name)` copies a whole preset directory into the user root — composition, metadata, skill directories, assets. It validates the id against `[a-z0-9][a-z0-9-]*` (it becomes the directory name, so no leading hyphen), refuses an id any root already supplies, rolls a failed copy back, and rewrites the copy's `preset.yml` to keep the source's description while dropping its name and roster `order`. Prefer it over a shell copy: it needs no sandbox escalation, it lands the copy in whichever root this deployment made writable, and the copy is exactly as loadable as its source. `resolve(id)` then names the file it created — that path, not a guessed one, is what the following edits target. `standard` is the full coding agent and the usual source.\n2. **Expect the file sandbox on every edit after the copy.** The user preset root lies outside the session workspace, so under the default `workspace-write` policy the first write there is denied. Only writes are: reading any composition by absolute path needs no escalation. Retry that exact command once with `sandbox_permissions` escalation and a short justification — the user sees and approves it. Batch your writes (one heredoc per file) rather than escalating many small commands. `copy()` itself runs host-side and needs none of this; the edits do.\n3. **Write the copy's `description`** in `preset.yml`, and its `name` if you passed none to `copy()`.\n4. **Edit `agent.cordis.yml`** row by row, keeping the plane rule and the realm rule.\n5. **Mount-validate the result**, then hand off to the user for a real session — both under *Verifying a change*.\n\nA composition written from scratch usually forgets a group realm or a consumer row; a copy starts loadable.\n\n## The rule that catches people\n\n**A row that publishes a service may not sit loose in a preset.** Registering a service without an isolate realm puts it in the process-global realm, so the second session mounting that preset collides with the first. The mount rejects it rather than letting the collision surface later.\n\nWhether a row publishes a service is not visible from its name, and package READMEs are absent from an installed deployment. Read it off the live runtime instead: `cordis_inspect what:\"services\"` lists every service with the fiber that owns it, so a service attributed to a fiber other than the row you are adding is one that row consumes rather than provides. For a row not in your current composition, mount-validate and read the rejection — it names the offending service.\n\nWhen a preset genuinely owns a service, wrap the provider **and every consumer that reaches it** in one group carrying an `isolate` realm. The shipped `standard` composition does this for `workflows`, which nothing outside an agent reads — its `delegation` group, with the delegation tools omitted here:\n\n```yaml\n- id: delegation\n name: cordis:group\n group: true\n isolate:\n workflows: true\n config:\n - id: workflow-worker-thread\n name: '@deepseek-ai/dsh-workflow-worker-thread'\n config:\n provider: spawn\n - id: tool-workflow\n name: '@deepseek-ai/dsh-tool-workflow'\n```\n\n`true` means a realm private to each mounting session. A string label instead joins subtrees into one shared realm; `provide()` still throws on the second registration under that symbol, so a label does not pool instances and is not what a preset needs.\n\nA consumer left outside the group resolves the host's registry, which the preset did not populate, and then contributes nothing. Mount-validation catches that as a row that never activated.\n\nRealms are for services a preset owns, not for every group. A host capability the preset only consumes must stay outside a realm, or the row cannot resolve it: `tool-bash`, `tool-jobs`, and `tool-goal` publish nothing and sit loose in `standard`, which explains in comments which host instance each one resolves and why a realm would break it. Wrapping a consumer row in a realm of its own is the same error as leaving one outside its provider's realm.\n\n## Verifying a change\n\n**`standingKeyFor(id)` is the check.** It composes the preset's plugin subtree for real — the same mount a session start performs, minus the agent — and rejects the four ways a composition fails:\n\n- a row whose package does not resolve (`Cannot find package …`);\n- a row whose config is invalid (`invalid config: $. missing required value`);\n- a row that never activated (`N row(s) did not activate: : waiting for `);\n- a service published into the root realm, which arrives as one of two messages. A name the host does not supply lands in the root realm and the mount audit rejects it: `row(s) published process-global service(s) []; a preset service must sit behind an isolate realm or move to the host composition` — this is the shape a preset's own forgotten realm takes. A name the host already supplies collides before the audit: `service \"\" has been registered at `. Both name the offending service.\n\nIt returns normally when the composition mounts. Run it as the final check on a finished edit rather than after every line: a successful mount installs a standing generation that lives until the process exits, while a failed one disposes its subtree and leaves nothing behind.\n\n**Do not treat the roster's `broken` field as validation.** `list()` reports `broken` from a shape check — the file parses in the loader's YAML dialect and holds named rows — which every failure above passes. It catches a damaged file, not an unusable composition.\n\n`cordis_inspect` reports THIS session's composition, so it confirms what a row does in the runtime you are already in, never what your new preset will do.\n\nAfter a clean mount-validation, ask the user to start a session on the new preset and confirm the tool list; the preset decides tool schemas and prompt sections, and only a real session shows the agent that composition produces.\n\n`cordis_mount` evaluates JavaScript against the live runtime and disappears on restart. It is for probing, not for shipping a capability: a capability belongs in a composition file.\n\n## Native product subagents\n\nCodex and Claude Code providers are independent optional Profile Bundles. Install only the products a Profile needs, then restart the Profile so its Host registers those providers:\n\n```sh\ndsh plugin --profile add @deepseek-ai/dsh-subagent-codex\ndsh plugin --profile add @deepseek-ai/dsh-subagent-claude-code\ndsh plugin --profile remove @deepseek-ai/dsh-subagent-codex\ndsh plugin --profile remove @deepseek-ai/dsh-subagent-claude-code\n```\n\nEach Bundle owns its Host availability; the preset separately grants one Agent its ordinary delegation tool. Never move a product provider into the preset and never add a product-specific settings field. Removing one package withdraws only that provider on the next Profile start.\n\nCopy these disabled templates from a shipped full preset and remove `disabled` only for the products the user requested:\n\n```yaml\n- id: tool-subagent-codex\n name: '@deepseek-ai/dsh-tool-subagent'\n disabled: true\n config:\n provider: codex\n toolName: subagent_codex\n backgroundMode: one-shot\n maxDepth: provider-managed\n\n- id: tool-subagent-claude-code\n name: '@deepseek-ai/dsh-tool-subagent'\n disabled: true\n config:\n provider: claude-code\n toolName: subagent_claude_code\n backgroundMode: one-shot\n maxDepth: provider-managed\n```\n\nFor additional named Codex or Claude Code instances, mount a separate host-plane provider row for each instance with a unique `providerName`, then add a separate preset tool row whose `provider` exactly matches that name and whose `toolName` is also unique. Keep the shipped rows for the default `codex` and `claude-code` names; do not reuse one tool row for several providers or derive either name from permission or environment settings.\n\nThe two rows are independent. Leaving both disabled preserves the copied preset, enabling one exposes only that product tool, and enabling both exposes both. Production `dsh` does not install either optional provider: before enabling a row, install the matching `@deepseek-ai/dsh-subagent-codex` or `@deepseek-ai/dsh-subagent-claude-code` Bundle in the Profile and restart it. Each Bundle registers its dormant default provider and exclusively uses its pinned package-local platform CLI; additional named instances use extra host-plane rows from the same installed package. A preset cannot provide that host dependency. `backgroundMode: one-shot` keeps omitted or `false` calls in the foreground and lets explicit `run_in_background: true` return a generic Job id. Full presets already carry `tool-jobs`, while the base host carries the job registry; retain both so `job_output`, `job_list`, `job_kill`, cancellation, and completion notices stay available. Installing a Bundle or composing a preset row does not start a product, authenticate an account, select a model, probe credentials, or manage native product settings.\n\n## What not to move into a preset\n\n`agent-loop` registers the one agent factory and throws on a second. The registries own the per-session layering and cannot themselves be per-session. Session persistence must stay host-side or the session list fragments. The sandbox, approval, and permission rows are a deliberate boundary: a preset is exactly as privileged as the plugins it names, so letting one relax its own confinement would defeat the confinement.\n\n"}],"isError":false}],"role":"user","id":"{{message:5}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} diff --git a/snapshots/web/code-mode-round/snapshot.yml b/snapshots/web/code-mode-round/snapshot.yml deleted file mode 100644 index ac7b3c2285..0000000000 --- a/snapshots/web/code-mode-round/snapshot.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: 1 -scenario: code-mode-round -profile: web -composition: web-code -recording: live -header: - class: web-code - pin: true diff --git a/snapshots/web/code-mode-round/session.jsonl b/snapshots/web/ptc-round/session.jsonl similarity index 100% rename from snapshots/web/code-mode-round/session.jsonl rename to snapshots/web/ptc-round/session.jsonl diff --git a/snapshots/web/ptc-round/snapshot.yml b/snapshots/web/ptc-round/snapshot.yml new file mode 100644 index 0000000000..01875305c8 --- /dev/null +++ b/snapshots/web/ptc-round/snapshot.yml @@ -0,0 +1,8 @@ +version: 1 +scenario: ptc-round +profile: web +composition: web-ptc +recording: live +header: + class: web-ptc + pin: true diff --git a/snapshots/web/code-mode-round/system-prompt.expected.md b/snapshots/web/ptc-round/system-prompt.expected.md similarity index 100% rename from snapshots/web/code-mode-round/system-prompt.expected.md rename to snapshots/web/ptc-round/system-prompt.expected.md diff --git a/snapshots/web/code-mode-round/tool-schemas.expected.json b/snapshots/web/ptc-round/tool-schemas.expected.json similarity index 100% rename from snapshots/web/code-mode-round/tool-schemas.expected.json rename to snapshots/web/ptc-round/tool-schemas.expected.json diff --git a/snapshots/web/code-mode-round/ui.expected.md b/snapshots/web/ptc-round/ui.expected.md similarity index 100% rename from snapshots/web/code-mode-round/ui.expected.md rename to snapshots/web/ptc-round/ui.expected.md diff --git a/snapshots/web/skill-tool-row/ui.expected.md b/snapshots/web/skill-tool-row/ui.expected.md index ce5851b7e9..51350c74e6 100644 --- a/snapshots/web/skill-tool-row/ui.expected.md +++ b/snapshots/web/skill-tool-row/ui.expected.md @@ -32,7 +32,7 @@ - button "Skill editing-cordis-compositions" [expanded]: - img - text: Skill editing-cordis-compositions -- region "Instructions": "Instructions Base directory for this skill: {{cwd}}/.dsh/skills/editing-cordis-compositions Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed. # Editing Cordis compositions Every capability in this harness is a plugin row in a `cordis.yml`. There is no separate configuration language: changing what an agent can do means changing which rows are composed for it. ## Off-limits **Never edit, delete, or overwrite a preset that ships with the deployment** — the `agent-presets` directory beside the deployment's own config, which supplies `standard`, `code`, `minimal`, and `cordis`. Never escalate the sandbox to reach it, even when a change there looks quicker. An upgrade overwrites that install, and corrupting `cordis` disables preset authoring itself. Reading a shipped composition is the intended way to start; writing to one is not, and neither is editing the host composition to work around a preset limitation. To change what a shipped preset does, copy it and edit the copy. Locally authored presets under the user root are yours to create, edit, and delete. ## Decide the plane first Two planes, and the choice is not about how \"agent-related\" something feels — it is about whether the thing must be shared. **Host composition.** The registries themselves (`tools`, `systemPrompt`, `agents`, `agent-loop`, `sessions`), anything crossing sessions (persistence, session query, storage, settings, credentials, telemetry), the sandbox and approval stack, the model route, and the subagent registry with its spawn/fork backends. One instance for the process. **Agent preset.** What one session contributes to those registries: its tool plugins, its persona and prompt sections, its compaction policy. One instance per session, mounted under that session's scope and unwound with it. **A service with a consumer outside the agent plane cannot move into a preset.** `subagents` is the worked example: the registry answers cross-session queries for the host api-proxy, so a per-session copy both starves that host row — it waits forever for a service nothing provides — and collides on the second session, since a provider name registers once. The preset contributes the delegation *tools*; the registry and its backends stay host-side. A preset is a directory holding one `agent.cordis.yml`, optionally beside a `preset.yml` carrying display metadata — `name` and `description` (and, for shipped presets, a roster `order`). Write the metadata too: a preset without it shows up in every picker as its bare directory name. Locally authored presets live one directory per preset under `${DSH_HOME:-$HOME/.dsh}/.agent-presets/`, and the shipped set sits beside the deployment's own config. Use those when the user asks where to look. A deployment can configure other roots, so the path you read or edit comes from `list()` or `resolve()` — which is also where `copy()` reports what it just created. ## The roster service `ctx.agentPresets` owns discovery, authoring, and mounting. You reach it by mounting a temporary plugin that injects it and registers a tool for yourself — `cordis_mount` returns only the mount acknowledgement, so a registered tool is how a service answer gets back to you, and it becomes callable on your next step. Read `cordis_inspect what:\"api\" name:\"agentPresets\"` for the current signatures before writing the code. What this skill relies on: - `list()` — every preset with its `id`, `trust` (`system` for the shipped set, `user` for authored ones), and the absolute `path` of its composition file. This is how you locate any composition without knowing the install layout; the directory is that path's parent. - `read(id)` — one preset's composition text, without a file tool or a path. - `copy(from, id, name?)` — the only authoring write (see below). - `standingKeyFor(id)` — mount-validate one preset (see below). ```js return { name: 'preset-tools', inject: ['agentPresets', 'tools'], apply(ctx) { harness.registerTool(ctx, harness.defineTool({ name: 'preset_check', description: 'Mount-validate one preset by id.', parameters: { id: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_a, v) { return [{ type: 'text', text: v }] } }, async execute(args) { try { await ctx.agentPresets.standingKeyFor(args.id) return 'mounted OK' } catch (error) { return error.message } }, })) }, } ``` Unmount the plugin with `cordis_unmount` when you are done; it is a probe, not a capability to leave behind. ## Authoring a preset 1. **Start from a copy.** `copy(from, id, name)` copies a whole preset directory into the user root — composition, metadata, skill directories, assets. It validates the id against `[a-z0-9][a-z0-9-]*` (it becomes the directory name, so no leading hyphen), refuses an id any root already supplies, rolls a failed copy back, and rewrites the copy's `preset.yml` to keep the source's description while dropping its name and roster `order`. Prefer it over a shell copy: it needs no sandbox escalation, it lands the copy in whichever root this deployment made writable, and the copy is exactly as loadable as its source. `resolve(id)` then names the file it created — that path, not a guessed one, is what the following edits target. `standard` is the full coding agent and the usual source. 2. **Expect the file sandbox on every edit after the copy.** The user preset root lies outside the session workspace, so under the default `workspace-write` policy the first write there is denied. Only writes are: reading any composition by absolute path needs no escalation. Retry that exact command once with `sandbox_permissions` escalation and a short justification — the user sees and approves it. Batch your writes (one heredoc per file) rather than escalating many small commands. `copy()` itself runs host-side and needs none of this; the edits do. 3. **Write the copy's `description`** in `preset.yml`, and its `name` if you passed none to `copy()`. 4. **Edit `agent.cordis.yml`** row by row, keeping the plane rule and the realm rule. 5. **Mount-validate the result**, then hand off to the user for a real session — both under *Verifying a change*. A composition written from scratch usually forgets a group realm or a consumer row; a copy starts loadable. ## The rule that catches people **A row that publishes a service may not sit loose in a preset.** Registering a service without an isolate realm puts it in the process-global realm, so the second session mounting that preset collides with the first. The mount rejects it rather than letting the collision surface later. Whether a row publishes a service is not visible from its name, and package READMEs are absent from an installed deployment. Read it off the live runtime instead: `cordis_inspect what:\"services\"` lists every service with the fiber that owns it, so a service attributed to a fiber other than the row you are adding is one that row consumes rather than provides. For a row not in your current composition, mount-validate and read the rejection — it names the offending service. When a preset genuinely owns a service, wrap the provider **and every consumer that reaches it** in one group carrying an `isolate` realm. The shipped `standard` composition does this for `workflows`, which nothing outside an agent reads — its `delegation` group, with the delegation tools omitted here: ```yaml - id: delegation name: cordis:group group: true isolate: workflows: true config: - id: workflow-worker-thread name: '@deepseek-ai/dsh-workflow-worker-thread' config: provider: spawn - id: tool-workflow name: '@deepseek-ai/dsh-tool-workflow' ``` `true` means a realm private to each mounting session. A string label instead joins subtrees into one shared realm; `provide()` still throws on the second registration under that symbol, so a label does not pool instances and is not what a preset needs. A consumer left outside the group resolves the host's registry, which the preset did not populate, and then contributes nothing. Mount-validation catches that as a row that never activated. Realms are for services a preset owns, not for every group. A host capability the preset only consumes must stay outside a realm, or the row cannot resolve it: `tool-bash`, `tool-jobs`, and `tool-goal` publish nothing and sit loose in `standard`, which explains in comments which host instance each one resolves and why a realm would break it. Wrapping a consumer row in a realm of its own is the same error as leaving one outside its provider's realm. ## Verifying a change **`standingKeyFor(id)` is the check.** It composes the preset's plugin subtree for real — the same mount a session start performs, minus the agent — and rejects the four ways a composition fails: - a row whose package does not resolve (`Cannot find package …`); - a row whose config is invalid (`invalid config: $. missing required value`); - a row that never activated (`N row(s) did not activate: : waiting for `); - a service published into the root realm, which arrives as one of two messages. A name the host does not supply lands in the root realm and the mount audit rejects it: `row(s) published process-global service(s) []; a preset service must sit behind an isolate realm or move to the host composition` — this is the shape a preset's own forgotten realm takes. A name the host already supplies collides before the audit: `service \"\" has been registered at `. Both name the offending service. It returns normally when the composition mounts. Run it as the final check on a finished edit rather than after every line: a successful mount installs a standing generation that lives until the process exits, while a failed one disposes its subtree and leaves nothing behind. **Do not treat the roster's `broken` field as validation.** `list()` reports `broken` from a shape check — the file parses in the loader's YAML dialect and holds named rows — which every failure above passes. It catches a damaged file, not an unusable composition. `cordis_inspect` reports THIS session's composition, so it confirms what a row does in the runtime you are already in, never what your new preset will do. After a clean mount-validation, ask the user to start a session on the new preset and confirm the tool list; the preset decides tool schemas and prompt sections, and only a real session shows the agent that composition produces. `cordis_mount` evaluates JavaScript against the live runtime and disappears on restart. It is for probing, not for shipping a capability: a capability belongs in a composition file. ## Native product subagents Codex and Claude Code providers are independent optional Profile Bundles. Install only the products a Profile needs, then restart the Profile so its Host registers those providers: ```sh dsh plugin --profile add @deepseek-ai/dsh-subagent-codex dsh plugin --profile add @deepseek-ai/dsh-subagent-claude-code dsh plugin --profile remove @deepseek-ai/dsh-subagent-codex dsh plugin --profile remove @deepseek-ai/dsh-subagent-claude-code ``` Each Bundle owns its Host availability; the preset separately grants one Agent its ordinary delegation tool. Never move a product provider into the preset and never add a product-specific settings field. Removing one package withdraws only that provider on the next Profile start. Copy these disabled templates from a shipped full preset and remove `disabled` only for the products the user requested: ```yaml - id: tool-subagent-codex name: '@deepseek-ai/dsh-tool-subagent' disabled: true config: provider: codex toolName: subagent_codex backgroundMode: one-shot maxDepth: provider-managed - id: tool-subagent-claude-code name: '@deepseek-ai/dsh-tool-subagent' disabled: true config: provider: claude-code toolName: subagent_claude_code backgroundMode: one-shot maxDepth: provider-managed ``` For additional named Codex or Claude Code instances, mount a separate host-plane provider row for each instance with a unique `providerName`, then add a separate preset tool row whose `provider` exactly matches that name and whose `toolName` is also unique. Keep the shipped rows for the default `codex` and `claude-code` names; do not reuse one tool row for several providers or derive either name from permission or environment settings. The two rows are independent. Leaving both disabled preserves the copied preset, enabling one exposes only that product tool, and enabling both exposes both. Production `dsh` does not install either optional provider: before enabling a row, install the matching `@deepseek-ai/dsh-subagent-codex` or `@deepseek-ai/dsh-subagent-claude-code` Bundle in the Profile and restart it. Each Bundle registers its dormant default provider and exclusively uses its pinned package-local platform CLI; additional named instances use extra host-plane rows from the same installed package. A preset cannot provide that host dependency. `backgroundMode: one-shot` keeps omitted or `false` calls in the foreground and lets explicit `run_in_background: true` return a generic Job id. Full presets already carry `tool-jobs`, while the base host carries the job registry; retain both so `job_output`, `job_list`, `job_kill`, cancellation, and completion notices stay available. Installing a Bundle or composing a preset row does not start a product, authenticate an account, select a model, probe credentials, or manage native product settings. ## What not to move into a preset `agent-loop` registers the one agent factory and throws on a second. The registries own the per-session layering and cannot themselves be per-session. Session persistence must stay host-side or the session list fragments. The sandbox, approval, and permission rows are a deliberate boundary: a preset is exactly as privileged as the plugins it names, so letting one relax its own confinement would defeat the confinement. " +- region "Instructions": "Instructions Base directory for this skill: {{cwd}}/.dsh/skills/editing-cordis-compositions Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed. # Editing Cordis compositions Every capability in this harness is a plugin row in a `cordis.yml`. There is no separate configuration language: changing what an agent can do means changing which rows are composed for it. ## Off-limits **Never edit, delete, or overwrite a preset that ships with the deployment** — the `agent-presets` directory beside the deployment's own config, which supplies `standard`, `ptc`, `minimal`, and `cordis`. Never escalate the sandbox to reach it, even when a change there looks quicker. An upgrade overwrites that install, and corrupting `cordis` disables preset authoring itself. Reading a shipped composition is the intended way to start; writing to one is not, and neither is editing the host composition to work around a preset limitation. To change what a shipped preset does, copy it and edit the copy. Locally authored presets under the user root are yours to create, edit, and delete. ## Decide the plane first Two planes, and the choice is not about how \"agent-related\" something feels — it is about whether the thing must be shared. **Host composition.** The registries themselves (`tools`, `systemPrompt`, `agents`, `agent-loop`, `sessions`), anything crossing sessions (persistence, session query, storage, settings, credentials, telemetry), the sandbox and approval stack, the model route, and the subagent registry with its spawn/fork backends. One instance for the process. **Agent preset.** What one session contributes to those registries: its tool plugins, its persona and prompt sections, its compaction policy. One instance per session, mounted under that session's scope and unwound with it. **A service with a consumer outside the agent plane cannot move into a preset.** `subagents` is the worked example: the registry answers cross-session queries for the host api-proxy, so a per-session copy both starves that host row — it waits forever for a service nothing provides — and collides on the second session, since a provider name registers once. The preset contributes the delegation *tools*; the registry and its backends stay host-side. A preset is a directory holding one `agent.cordis.yml`, optionally beside a `preset.yml` carrying display metadata — `name` and `description` (and, for shipped presets, a roster `order`). Write the metadata too: a preset without it shows up in every picker as its bare directory name. Locally authored presets live one directory per preset under `${DSH_HOME:-$HOME/.dsh}/.agent-presets/`, and the shipped set sits beside the deployment's own config. Use those when the user asks where to look. A deployment can configure other roots, so the path you read or edit comes from `list()` or `resolve()` — which is also where `copy()` reports what it just created. ## The roster service `ctx.agentPresets` owns discovery, authoring, and mounting. You reach it by mounting a temporary plugin that injects it and registers a tool for yourself — `cordis_mount` returns only the mount acknowledgement, so a registered tool is how a service answer gets back to you, and it becomes callable on your next step. Read `cordis_inspect what:\"api\" name:\"agentPresets\"` for the current signatures before writing the code. What this skill relies on: - `list()` — every preset with its `id`, `trust` (`system` for the shipped set, `user` for authored ones), and the absolute `path` of its composition file. This is how you locate any composition without knowing the install layout; the directory is that path's parent. - `read(id)` — one preset's composition text, without a file tool or a path. - `copy(from, id, name?)` — the only authoring write (see below). - `standingKeyFor(id)` — mount-validate one preset (see below). ```js return { name: 'preset-tools', inject: ['agentPresets', 'tools'], apply(ctx) { harness.registerTool(ctx, harness.defineTool({ name: 'preset_check', description: 'Mount-validate one preset by id.', parameters: { id: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_a, v) { return [{ type: 'text', text: v }] } }, async execute(args) { try { await ctx.agentPresets.standingKeyFor(args.id) return 'mounted OK' } catch (error) { return error.message } }, })) }, } ``` Unmount the plugin with `cordis_unmount` when you are done; it is a probe, not a capability to leave behind. ## Authoring a preset 1. **Start from a copy.** `copy(from, id, name)` copies a whole preset directory into the user root — composition, metadata, skill directories, assets. It validates the id against `[a-z0-9][a-z0-9-]*` (it becomes the directory name, so no leading hyphen), refuses an id any root already supplies, rolls a failed copy back, and rewrites the copy's `preset.yml` to keep the source's description while dropping its name and roster `order`. Prefer it over a shell copy: it needs no sandbox escalation, it lands the copy in whichever root this deployment made writable, and the copy is exactly as loadable as its source. `resolve(id)` then names the file it created — that path, not a guessed one, is what the following edits target. `standard` is the full coding agent and the usual source. 2. **Expect the file sandbox on every edit after the copy.** The user preset root lies outside the session workspace, so under the default `workspace-write` policy the first write there is denied. Only writes are: reading any composition by absolute path needs no escalation. Retry that exact command once with `sandbox_permissions` escalation and a short justification — the user sees and approves it. Batch your writes (one heredoc per file) rather than escalating many small commands. `copy()` itself runs host-side and needs none of this; the edits do. 3. **Write the copy's `description`** in `preset.yml`, and its `name` if you passed none to `copy()`. 4. **Edit `agent.cordis.yml`** row by row, keeping the plane rule and the realm rule. 5. **Mount-validate the result**, then hand off to the user for a real session — both under *Verifying a change*. A composition written from scratch usually forgets a group realm or a consumer row; a copy starts loadable. ## The rule that catches people **A row that publishes a service may not sit loose in a preset.** Registering a service without an isolate realm puts it in the process-global realm, so the second session mounting that preset collides with the first. The mount rejects it rather than letting the collision surface later. Whether a row publishes a service is not visible from its name, and package READMEs are absent from an installed deployment. Read it off the live runtime instead: `cordis_inspect what:\"services\"` lists every service with the fiber that owns it, so a service attributed to a fiber other than the row you are adding is one that row consumes rather than provides. For a row not in your current composition, mount-validate and read the rejection — it names the offending service. When a preset genuinely owns a service, wrap the provider **and every consumer that reaches it** in one group carrying an `isolate` realm. The shipped `standard` composition does this for `workflows`, which nothing outside an agent reads — its `delegation` group, with the delegation tools omitted here: ```yaml - id: delegation name: cordis:group group: true isolate: workflows: true config: - id: workflow-worker-thread name: '@deepseek-ai/dsh-workflow-worker-thread' config: provider: spawn - id: tool-workflow name: '@deepseek-ai/dsh-tool-workflow' ``` `true` means a realm private to each mounting session. A string label instead joins subtrees into one shared realm; `provide()` still throws on the second registration under that symbol, so a label does not pool instances and is not what a preset needs. A consumer left outside the group resolves the host's registry, which the preset did not populate, and then contributes nothing. Mount-validation catches that as a row that never activated. Realms are for services a preset owns, not for every group. A host capability the preset only consumes must stay outside a realm, or the row cannot resolve it: `tool-bash`, `tool-jobs`, and `tool-goal` publish nothing and sit loose in `standard`, which explains in comments which host instance each one resolves and why a realm would break it. Wrapping a consumer row in a realm of its own is the same error as leaving one outside its provider's realm. ## Verifying a change **`standingKeyFor(id)` is the check.** It composes the preset's plugin subtree for real — the same mount a session start performs, minus the agent — and rejects the four ways a composition fails: - a row whose package does not resolve (`Cannot find package …`); - a row whose config is invalid (`invalid config: $. missing required value`); - a row that never activated (`N row(s) did not activate: : waiting for `); - a service published into the root realm, which arrives as one of two messages. A name the host does not supply lands in the root realm and the mount audit rejects it: `row(s) published process-global service(s) []; a preset service must sit behind an isolate realm or move to the host composition` — this is the shape a preset's own forgotten realm takes. A name the host already supplies collides before the audit: `service \"\" has been registered at `. Both name the offending service. It returns normally when the composition mounts. Run it as the final check on a finished edit rather than after every line: a successful mount installs a standing generation that lives until the process exits, while a failed one disposes its subtree and leaves nothing behind. **Do not treat the roster's `broken` field as validation.** `list()` reports `broken` from a shape check — the file parses in the loader's YAML dialect and holds named rows — which every failure above passes. It catches a damaged file, not an unusable composition. `cordis_inspect` reports THIS session's composition, so it confirms what a row does in the runtime you are already in, never what your new preset will do. After a clean mount-validation, ask the user to start a session on the new preset and confirm the tool list; the preset decides tool schemas and prompt sections, and only a real session shows the agent that composition produces. `cordis_mount` evaluates JavaScript against the live runtime and disappears on restart. It is for probing, not for shipping a capability: a capability belongs in a composition file. ## Native product subagents Codex and Claude Code providers are independent optional Profile Bundles. Install only the products a Profile needs, then restart the Profile so its Host registers those providers: ```sh dsh plugin --profile add @deepseek-ai/dsh-subagent-codex dsh plugin --profile add @deepseek-ai/dsh-subagent-claude-code dsh plugin --profile remove @deepseek-ai/dsh-subagent-codex dsh plugin --profile remove @deepseek-ai/dsh-subagent-claude-code ``` Each Bundle owns its Host availability; the preset separately grants one Agent its ordinary delegation tool. Never move a product provider into the preset and never add a product-specific settings field. Removing one package withdraws only that provider on the next Profile start. Copy these disabled templates from a shipped full preset and remove `disabled` only for the products the user requested: ```yaml - id: tool-subagent-codex name: '@deepseek-ai/dsh-tool-subagent' disabled: true config: provider: codex toolName: subagent_codex backgroundMode: one-shot maxDepth: provider-managed - id: tool-subagent-claude-code name: '@deepseek-ai/dsh-tool-subagent' disabled: true config: provider: claude-code toolName: subagent_claude_code backgroundMode: one-shot maxDepth: provider-managed ``` For additional named Codex or Claude Code instances, mount a separate host-plane provider row for each instance with a unique `providerName`, then add a separate preset tool row whose `provider` exactly matches that name and whose `toolName` is also unique. Keep the shipped rows for the default `codex` and `claude-code` names; do not reuse one tool row for several providers or derive either name from permission or environment settings. The two rows are independent. Leaving both disabled preserves the copied preset, enabling one exposes only that product tool, and enabling both exposes both. Production `dsh` does not install either optional provider: before enabling a row, install the matching `@deepseek-ai/dsh-subagent-codex` or `@deepseek-ai/dsh-subagent-claude-code` Bundle in the Profile and restart it. Each Bundle registers its dormant default provider and exclusively uses its pinned package-local platform CLI; additional named instances use extra host-plane rows from the same installed package. A preset cannot provide that host dependency. `backgroundMode: one-shot` keeps omitted or `false` calls in the foreground and lets explicit `run_in_background: true` return a generic Job id. Full presets already carry `tool-jobs`, while the base host carries the job registry; retain both so `job_output`, `job_list`, `job_kill`, cancellation, and completion notices stay available. Installing a Bundle or composing a preset row does not start a product, authenticate an account, select a model, probe credentials, or manage native product settings. ## What not to move into a preset `agent-loop` registers the one agent factory and throws on a second. The registries own the per-session layering and cannot themselves be per-session. Session persistence must stay host-side or the session list fragments. The sandbox, approval, and permission rows are a deliberate boundary: a preset is exactly as privileged as the plugins it names, so letting one relax its own confinement would defeat the confinement. " - button "Inspect" - button "Think The skill is loaded.": - img diff --git a/tsconfig.host.json b/tsconfig.host.json index 14bfe0822f..561cee9b17 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -46,7 +46,7 @@ "apps/web/tests/sidebar-scrollbar.e2e.ts", "apps/web/tests/rail-search-expand.e2e.ts", "apps/web/tests/conversation-column-overflow.e2e.ts", - "apps/web/tests/code-mode-round.e2e.ts", + "apps/web/tests/ptc-round.e2e.ts", "apps/web/tests/composer-draft-scroll.e2e.ts", "apps/web/tests/cordis-tool-round.e2e.ts", "apps/web/tests/web-search-round.e2e.ts",