Restore Cordis mount and unmount names

Keep temporary lifecycle descriptions and UI labels explicit.
This commit is contained in:
NI0317
2026-07-27 21:20:06 +08:00
parent 826c6fd243
commit 1ef285d038
80 changed files with 578 additions and 567 deletions
@@ -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: 822fd0fae02be62d83aa0402cdf6d7d4322b89fb
2026-07-20-canonical-tool-output-contract.zh.md: 3d32114107732c48cb0cb236faa8ec60ad887698
2026-07-20-canonical-tool-output-contract.md: b2de9480d2659153dfb8a76ee07438d12e5b07c3
2026-07-20-canonical-tool-output-contract.zh.md: 1ae2654fb1e1d6913bc91c4aeb380dc533a6b258
@@ -56,7 +56,7 @@ The first-party tools preserve their existing Native text while returning domain
| `todo_write` | `{ todos, counts }` |
| `ask_user_question` | `{ answers: [{ id, selected, custom? }] }` |
| `exit_plan_mode` | `{ approved: true }` |
| `cordis_inspect` / `cordis_try` / `cordis_stop` | Inspection text or typed temporary-Plugin handles |
| `cordis_inspect` / `cordis_mount` / `cordis_unmount` | Inspection text or typed temporary-Plugin handles |
| `structured_output` | `{ recorded: true }` |
| `run_code` | `{ logs: string[], result?: JsonValue }` |
@@ -56,7 +56,7 @@ type ToolExecutionResult =
| `todo_write` | `{ todos, counts }` |
| `ask_user_question` | `{ answers: [{ id, selected, custom? }] }` |
| `exit_plan_mode` | `{ approved: true }` |
| `cordis_inspect` `cordis_try` `cordis_stop` | 检查文本或类型化的临时 Plugin 句柄 |
| `cordis_inspect` `cordis_mount` `cordis_unmount` | 检查文本或类型化的临时 Plugin 句柄 |
| `structured_output` | `{ recorded: true }` |
| `run_code` | `{ logs: string[], result?: JsonValue }` |
@@ -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-08-self-referential-cordis-toolset.md
2026-07-08-self-referential-cordis-toolset.md: c531b8b7da988b9381f7a1c78f4017977dd321d8
2026-07-08-self-referential-cordis-toolset.zh.md: ae3ef1170d29cf896d4a5c54a4b23289b981b88f
2026-07-08-self-referential-cordis-toolset.md: 40934fe0e2975c4e068df6ef8f31ed7921df3230
2026-07-08-self-referential-cordis-toolset.zh.md: 13662b9359aa85895ce85391ebd5a5902cc451cc
@@ -12,7 +12,7 @@ First, model-written registration must be validated where it happens: a malforme
## Decision
The toolset ships as [`@deepseek-ai/dsh-tool-cordis`](../../../../packages/cordis/tool-cordis/README.md) and is demoed by [`examples/cordis-agent`](../../../../examples/cordis-agent/README.md). It gives the model three tools over the live Cordis runtime in the current DSH process: inspect it, try an in-memory temporary Plugin, and stop that Plugin to quiescence.
The toolset ships as [`@deepseek-ai/dsh-tool-cordis`](../../../../packages/cordis/tool-cordis/README.md) and is demoed by [`examples/cordis-agent`](../../../../examples/cordis-agent/README.md). It gives the model three tools over the live Cordis runtime in the current DSH process: inspect it, mount an in-memory temporary Plugin, and unmount that Plugin to quiescence.
The vm isolates accidental global pollution, and the context façade hides framework internals. Neither restricts the authority of exposed services: a temporary Plugin can call `ctx.bash` with the host executor's privileges and reach the real filesystem and web services. It runs in the shared DSH runtime and may affect other sessions in that process. This is an opt-in development tool with bash-equivalent trust, not a security boundary or product default.
@@ -20,11 +20,11 @@ The vm isolates accidental global pollution, and the context façade hides frame
| Tool | Contract |
|---|---|
| `cordis_inspect` | Read-only report over the live current-process runtime, one Markdown section per `what` value (omit `what` for all sections). `plugins` lists every live fiber; `temporary` lists only the temporary Plugins created by `cordis_try`. An exact `name` with `what: "api"` or `what: "events"` narrows to one source-documented target. |
| `cordis_try` | Evaluates `code` now as an async JavaScript-function body in a `node:vm` sandbox and saves it nowhere. The returned Plugin is mounted under the internal `cordis-dynamic` group and tracked under a fresh process-local id (`dyn-1`, `dyn-2`, …). |
| `cordis_stop` | Stops one `cordis_try` temporary Plugin by id and returns only after every owned tool, listener, service, timer, and effect reaches quiescence. It cannot remove Loader, configured, or installed Plugins. |
| `cordis_inspect` | Read-only report over the live current-process runtime, one Markdown section per `what` value (omit `what` for all sections). `plugins` lists every live fiber; `temporary` lists only the temporary Plugins created by `cordis_mount`. An exact `name` with `what: "api"` or `what: "events"` narrows to one source-documented target. |
| `cordis_mount` | Evaluates `code` now as an async JavaScript-function body in a `node:vm` sandbox and saves it nowhere. The returned Plugin is mounted under the internal `cordis-dynamic` group and tracked under a fresh process-local id (`dyn-1`, `dyn-2`, …). |
| `cordis_unmount` | Unmounts one `cordis_mount` temporary Plugin by id and returns only after every owned tool, listener, service, timer, and effect reaches quiescence. It cannot remove Loader, configured, or installed Plugins. |
`cordis_inspect` sections are `services` (every provided ctx service and owning fiber), `plugins` (every live plugin fiber), `tools` (what the model can call), `temporary` (the `cordis_try` subset with id, running/pending state, provided and awaited services, and lifetime), `api` (live service signatures and referenced types), and `events` (harness events with dispatch mode and signature). Temporary Plugins remain active across later turns and disappear after `cordis_stop`, toolset unload, or DSH restart; they are never restored automatically. Broad `api` and `events` reports omit full JSDoc to stay compact; an exact `name` returns one service or event with its original method/declaration JSDoc. A name is invalid with other sections, unknown targets fail, and an API target must be live. The model-facing tool descriptions carry the operational rules needed at call time; [the generated tool catalog](../../../../docs/tool-catalog.md) is their exhaustive rendering.
`cordis_inspect` sections are `services` (every provided ctx service and owning fiber), `plugins` (every live plugin fiber), `tools` (what the model can call), `temporary` (the `cordis_mount` subset with id, running/pending state, provided and awaited services, and lifetime), `api` (live service signatures and referenced types), and `events` (harness events with dispatch mode and signature). Temporary Plugins remain active across later turns and disappear after `cordis_unmount`, toolset unload, or DSH restart; they are never restored automatically. Broad `api` and `events` reports omit full JSDoc to stay compact; an exact `name` returns one service or event with its original method/declaration JSDoc. A name is invalid with other sections, unknown targets fail, and an API target must be live. The model-facing tool descriptions carry the operational rules needed at call time; [the generated tool catalog](../../../../docs/tool-catalog.md) is their exhaustive rendering.
### Sandbox semantics
@@ -38,7 +38,7 @@ The boundary normalizes unambiguous JSON-Schema forms into `ParameterSchemaSpec`
### The internal group and temporary-Plugin lifecycle
Every temporary Plugin is a child of one internal `cordis-dynamic` group beneath the tool plugin, so ordinary fiber disposal handles toolset reload and unload. `cordis_try` awaits settlement; startup failure disposes the fiber before returning an error. A settled pending Plugin remains visible with its missing injections. `cordis_stop` awaits the Plugin fiber's disposal.
Every temporary Plugin is a child of one internal `cordis-dynamic` group beneath the tool plugin, so ordinary fiber disposal handles toolset reload and unload. `cordis_mount` awaits settlement; startup failure disposes the fiber before returning an error. A settled pending Plugin remains visible with its missing injections. `cordis_unmount` awaits the Plugin fiber's disposal.
Temporary Plugins exist only in process memory. They create no Plugin file, install no package, change no `cordis.yml` or personal/project configuration, do not survive restart, and have no automatic save, promote, or install path. Keeping an experiment means asking the Agent to implement a normal local, project, or repository Plugin through the regular development workflow.
@@ -54,15 +54,15 @@ Freshness is gated like every generated artifact: `pnpm run verify-cordis-api` (
### Configuration, rendering, and observability
The plugin exposes one config field, validated by schemastery and documented in [the config catalog](../../../../docs/config-catalog.md): `vmTimeoutMs` (default 5000), the millisecond bound on the synchronous portion of code evaluation. The current model-facing names are `cordis_inspect`, `cordis_try`, and `cordis_stop`; the internal `cordis-dynamic` group name and `dyn-` id prefix remain structural vocabulary. All three tools render as `generic` cards per [the tool cookbook](../../../../docs/cookbook/adding-a-tool.md): inspect is `read`, try is `execute` carrying code as `rawInput`, and stop is `delete`. Web conversation rows preserve those generic mechanics while giving the tools the action titles `Inspect`, `Try temporary Plugin`, and `Stop temporary Plugin` plus one shared Cordis accent; the try row retains the shared JavaScript expansion and syntax highlighting.
The plugin exposes one config field, validated by schemastery and documented in [the config catalog](../../../../docs/config-catalog.md): `vmTimeoutMs` (default 5000), the millisecond bound on the synchronous portion of code evaluation. The current model-facing names are `cordis_inspect`, `cordis_mount`, and `cordis_unmount`; the internal `cordis-dynamic` group name and `dyn-` id prefix remain structural vocabulary. All three tools render as `generic` cards per [the tool cookbook](../../../../docs/cookbook/adding-a-tool.md): inspect is `read`, mount is `execute` carrying code as `rawInput`, and unmount is `delete`. Web conversation rows preserve those generic mechanics while giving the tools the action titles `Inspect`, `Mount temporary Plugin`, and `Unmount temporary Plugin` plus one shared Cordis accent; the mount row retains the shared JavaScript expansion and syntax highlighting.
Model-visible ⟺ logged holds with no new session event type: try and stop are visible through their logged `tool/call` / `tool/result` pairs, and any changed tool set is logged by the full changed request header emitted when schemas change between steps. Temporary Plugins are process memory, not session state: session resume rehydrates conversation history but never recreates them.
Model-visible ⟺ logged holds with no new session event type: mount and unmount are visible through their logged `tool/call` / `tool/result` pairs, and any changed tool set is logged by the full changed request header emitted when schemas change between steps. Temporary Plugins are process memory, not session state: session resume rehydrates conversation history but never recreates them.
## Alternatives considered
**A structured per-capability registration tool instead of `cordis_try`.** The most tempting alternative is a `cordis_register_tool` with explicit `name` / `description` / `parameters` / `code` fields (and siblings `cordis_register_listener`, `cordis_register_service`, …) rather than a single "mount a plugin" primitive. It was rejected because its one real win — no plugin boilerplate for the single commonest case — does not pay for its costs, while a single mount primitive answers every capability at once.
**A structured per-capability registration tool instead of `cordis_mount`.** The most tempting alternative is a `cordis_register_tool` with explicit `name` / `description` / `parameters` / `code` fields (and siblings `cordis_register_listener`, `cordis_register_service`, …) rather than a single "mount a plugin" primitive. It was rejected because its one real win — no plugin boilerplate for the single commonest case — does not pay for its costs, while a single mount primitive answers every capability at once.
| Dimension | Structured per-capability tools | Single `cordis_try` |
| Dimension | Structured per-capability tools | Single `cordis_mount` |
|---|---|---|
| Schema correctness | `parameters` is still model-written JSON needing unified-schema validation, merely one step earlier | The same validation runs at the sandbox boundary, with the same instructive errors |
| The code field | An `execute` body is still model-written JS in a vm; the realm and service-call correctness problems are unchanged | One sandbox, one normalization path, one guarded registration |
@@ -12,7 +12,7 @@ Status: implemented
## 决策
该工具集以 [`@deepseek-ai/dsh-tool-cordis`](../../../../packages/cordis/tool-cordis/README.md) 发布,并由 [`examples/cordis-agent`](../../../../examples/cordis-agent/README.md) 演示。它为模型提供三个工具,操作当前 DSH 进程中的活跃 Cordis 运行时:审视它、尝试一个仅存于内存的临时 Plugin,再该 Plugin 完全停稳。
该工具集以 [`@deepseek-ai/dsh-tool-cordis`](../../../../packages/cordis/tool-cordis/README.md) 发布,并由 [`examples/cordis-agent`](../../../../examples/cordis-agent/README.md) 演示。它为模型提供三个工具,操作当前 DSH 进程中的活跃 Cordis 运行时:审视它、挂载一个仅存于内存的临时 Plugin,再该 Plugin 卸载至完全停稳。
vm 隔离了意外的全局污染,上下文门面隐藏了框架内部细节。但二者都不限制已暴露服务的权限:临时 Plugin 可以调用 `ctx.bash` 以宿主执行器的权限运行命令,也能访问真实的文件系统和网络服务。它运行在共享 DSH runtime 中,可能影响同一进程的其他 session。这是一个需要显式启用的开发工具,信任等级与 bash 相当,不是安全边界,也不是产品默认配置。
@@ -20,11 +20,11 @@ vm 隔离了意外的全局污染,上下文门面隐藏了框架内部细节
| 工具 | 契约 |
|---|---|
| `cordis_inspect` | 当前进程活跃运行时的只读报告,每个 `what` 值对应一个 Markdown 段落(省略 `what` 则输出全部段落)。`plugins` 列出全部存活 fiber`temporary` 只列 `cordis_try` 创建的临时 Plugin。精确 `name` 搭配 `what: "api"``what: "events"` 可收窄到一个带源码文档的目标。 |
| `cordis_try` | 立即在 `node:vm` 沙箱中把 `code` 作为异步 JavaScript 函数体求值,且不保存到任何位置。返回的 Plugin 挂在内部 `cordis-dynamic` 分组下,并用新的进程内 id`dyn-1``dyn-2`……)跟踪。 |
| `cordis_stop` | 按 id 停止一个 `cordis_try` 临时 Plugin,并只在其自有工具、监听器、服务、定时器和其他 effect 完全停稳后返回。它不能删除 Loader、配置或已安装的 Plugin。 |
| `cordis_inspect` | 当前进程活跃运行时的只读报告,每个 `what` 值对应一个 Markdown 段落(省略 `what` 则输出全部段落)。`plugins` 列出全部存活 fiber`temporary` 只列 `cordis_mount` 创建的临时 Plugin。精确 `name` 搭配 `what: "api"``what: "events"` 可收窄到一个带源码文档的目标。 |
| `cordis_mount` | 立即在 `node:vm` 沙箱中把 `code` 作为异步 JavaScript 函数体求值,且不保存到任何位置。返回的 Plugin 挂在内部 `cordis-dynamic` 分组下,并用新的进程内 id`dyn-1``dyn-2`……)跟踪。 |
| `cordis_unmount` | 按 id 卸载一个 `cordis_mount` 临时 Plugin,并只在其自有工具、监听器、服务、定时器和其他 effect 完全停稳后返回。它不能删除 Loader、配置或已安装的 Plugin。 |
`cordis_inspect` 的段落是 `services`(每个已提供的 ctx 服务及所属 fiber)、`plugins`(全部存活 Plugin fiber)、`tools`(模型可调用的工具)、`temporary``cordis_try` 子集,包含 id、running/pending 状态、提供与等待的服务和生命周期)、`api`(活跃服务签名及其引用类型)和 `events`(harness 事件及分发模式和签名)。临时 Plugin 可跨后续 turn 保持活跃,并在 `cordis_stop`、工具集卸载或 DSH 重启后消失;系统绝不会自动恢复它们。宽泛的 `api``events` 报告省略完整 JSDoc;精确 `name` 返回一个服务或事件及其原始 JSDoc。其他段落不能搭配 name,未知目标会失败,而 API 目标必须处于活跃状态。[生成的工具目录](../../../../docs/tool-catalog.md)完整呈现面向模型的调用契约。
`cordis_inspect` 的段落是 `services`(每个已提供的 ctx 服务及所属 fiber)、`plugins`(全部存活 Plugin fiber)、`tools`(模型可调用的工具)、`temporary``cordis_mount` 子集,包含 id、running/pending 状态、提供与等待的服务和生命周期)、`api`(活跃服务签名及其引用类型)和 `events`(harness 事件及分发模式和签名)。临时 Plugin 可跨后续 turn 保持活跃,并在 `cordis_unmount`、工具集卸载或 DSH 重启后消失;系统绝不会自动恢复它们。宽泛的 `api``events` 报告省略完整 JSDoc;精确 `name` 返回一个服务或事件及其原始 JSDoc。其他段落不能搭配 name,未知目标会失败,而 API 目标必须处于活跃状态。[生成的工具目录](../../../../docs/tool-catalog.md)完整呈现面向模型的调用契约。
### 沙箱语义
@@ -38,7 +38,7 @@ vm 隔离了意外的全局污染,上下文门面隐藏了框架内部细节
### 内部分组与临时 Plugin 生命周期
每个临时 Plugin 都是工具插件下方内部 `cordis-dynamic` 分组的子节点,因此普通的 fiber 释放即可处理工具集重载和卸载。`cordis_try` 会等待 settlement;启动失败时在返回错误前释放 fiber。已 settle 但处于 pending 状态的 Plugin 仍然可见,并列出其缺失的注入。`cordis_stop` 等待 Plugin fiber 的释放完成。
每个临时 Plugin 都是工具插件下方内部 `cordis-dynamic` 分组的子节点,因此普通的 fiber 释放即可处理工具集重载和卸载。`cordis_mount` 会等待 settlement;启动失败时在返回错误前释放 fiber。已 settle 但处于 pending 状态的 Plugin 仍然可见,并列出其缺失的注入。`cordis_unmount` 等待 Plugin fiber 的释放完成。
临时 Plugin 只存在于进程内存中。它不会创建 Plugin 文件、安装 package、修改 `cordis.yml` 或个人/项目配置、跨重启存续,也不存在自动保存、转正式或安装路径。若要保留实验结果,应让 Agent 通过常规开发流程实现普通的本地、项目或仓库 Plugin。
@@ -54,15 +54,15 @@ vm 隔离了意外的全局污染,上下文门面隐藏了框架内部细节
### 配置、渲染与可观测性
该插件暴露一个配置字段,由 schemastery 校验并记录在[配置目录](../../../../docs/config-catalog.md)中:`vmTimeoutMs`(默认 5000),代码同步求值部分的毫秒上限。当前面向模型的名称是 `cordis_inspect``cordis_try``cordis_stop`;内部 `cordis-dynamic` 分组名和 `dyn-` id 前缀仍是结构性词汇。三个工具均按[工具实操手册](../../../../docs/cookbook/adding-a-tool.md)渲染为 `generic` 卡片:inspect 为 `read`try 为携带代码 `rawInput``execute`stop`delete`。Web 对话行保留这些通用机制,同时为各工具设置操作标题 `Inspect``Try temporary Plugin``Stop temporary Plugin` 以及统一的 Cordis 强调色;try 行仍使用共用的 JavaScript 展开视图和语法高亮。
该插件暴露一个配置字段,由 schemastery 校验并记录在[配置目录](../../../../docs/config-catalog.md)中:`vmTimeoutMs`(默认 5000),代码同步求值部分的毫秒上限。当前面向模型的名称是 `cordis_inspect``cordis_mount``cordis_unmount`;内部 `cordis-dynamic` 分组名和 `dyn-` id 前缀仍是结构性词汇。三个工具均按[工具实操手册](../../../../docs/cookbook/adding-a-tool.md)渲染为 `generic` 卡片:inspect 为 `read`mount 为携带代码 `rawInput``execute`unmount`delete`。Web 对话行保留这些通用机制,同时为各工具设置操作标题 `Inspect``Mount temporary Plugin``Unmount temporary Plugin` 以及统一的 Cordis 强调色;mount 行仍使用共用的 JavaScript 展开视图和语法高亮。
「模型可见 ⟺ 已记录」成立,且无需新的会话事件类型:try 与 stop 通过已记录的 `tool/call` / `tool/result` 对可见,工具集变化由 schema 在 step 间变化时发出的完整 request header 记录。临时 Plugin 属于进程内存,而非 session 状态:恢复持久化 session 只会重建对话历史,绝不会重新创建它们。
「模型可见 ⟺ 已记录」成立,且无需新的会话事件类型:mount 与 unmount 通过已记录的 `tool/call` / `tool/result` 对可见,工具集变化由 schema 在 step 间变化时发出的完整 request header 记录。临时 Plugin 属于进程内存,而非 session 状态:恢复持久化 session 只会重建对话历史,绝不会重新创建它们。
## 曾考虑的替代方案
**用结构化的逐能力注册工具替代 `cordis_try`。** 最具吸引力的替代方案是一个带有显式 `name` / `description` / `parameters` / `code` 字段的 `cordis_register_tool`(以及兄弟工具 `cordis_register_listener``cordis_register_service`……),而非单一的「挂载一个插件」原语。否决原因:它唯一的真正优势——对最常见的单一场景免去插件样板代码——不足以抵偿其代价,而单一的 mount 原语能一次性覆盖所有能力。
**用结构化的逐能力注册工具替代 `cordis_mount`。** 最具吸引力的替代方案是一个带有显式 `name` / `description` / `parameters` / `code` 字段的 `cordis_register_tool`(以及兄弟工具 `cordis_register_listener``cordis_register_service`……),而非单一的「挂载一个插件」原语。否决原因:它唯一的真正优势——对最常见的单一场景免去插件样板代码——不足以抵偿其代价,而单一的 mount 原语能一次性覆盖所有能力。
| 维度 | 结构化逐能力工具 | 单一 `cordis_try` |
| 维度 | 结构化逐能力工具 | 单一 `cordis_mount` |
|---|---|---|
| Schema 正确性 | `parameters` 仍然是模型编写的 JSON,需要统一 schema 校验,只是提前了一步 | 同样的校验在沙箱边界运行,同样的指导性错误信息 |
| 代码字段 | `execute` 函数体仍然是 vm 中模型编写的 JS;realm 和服务调用的正确性问题不变 | 一个沙箱、一条规范化路径、一处受保护的注册 |
@@ -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-code-mode-typed-tool-returns.md
2026-07-20-code-mode-typed-tool-returns.md: 4d60954b372253f51a2be61a12df153789239d14
2026-07-20-code-mode-typed-tool-returns.zh.md: 562937b88ccce30d7101f8807144ec03f7ad67b4
2026-07-20-code-mode-typed-tool-returns.md: 2081d8161f0ee14493a09762b18ec7d9d07ea3c4
2026-07-20-code-mode-typed-tool-returns.zh.md: 0fa5eec7a96ebba6796dda6221b83e91f14ebf7d
@@ -69,7 +69,7 @@ Compute time, wall time, worker heap, cancellation, and fresh-worker isolation r
Background producers return a typed canonical handle such as `{ kind: 'background', taskId }` while retaining their established Native sentence. A pre-aborted background call remains a failure because successful output promises an id and no task was created. After `ctx.tasks.start()` publishes the id, task-owned cancellation governs the work: settlement or later cancellation of the enclosing `run_code` call does not kill it. A later program can pass the returned id to `task_output`, and `task_kill`, owner disposal, or service teardown owns cancellation. Foreground execution remains coupled to the call signal. The task lifetime contract is owned by the [background task runtime note](../architecture/2026-06-20-generic-long-running-tool-runtime.md).
Temporary Cordis Plugins follow the same rule: `cordis_try` returns `{ id, pluginName, state, provides, waitingFor }`, so a program can read `temporary.id`, inspect active or pending state, and pass that id to `cordis_stop` without parsing the stable Native sentence.
Temporary Cordis Plugins follow the same rule: `cordis_mount` returns `{ id, pluginName, state, provides, waitingFor }`, so a program can read `mounted.id`, inspect active or pending state, and pass that id to `cordis_unmount` without parsing the stable Native sentence.
### Persistence, metadata, and spill
@@ -69,7 +69,7 @@ Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProper
后台 producer 返回类型化的规范句柄,例如 `{ kind: 'background', taskId }`,同时保留既有的 Native 语句。已预先中止的后台调用仍是失败,因为成功输出承诺返回 id,而此时并未创建任务。`ctx.tasks.start()` 发布 id 后,工作由任务自有的取消机制控制:外围 `run_code` 调用完成,或随后被取消,都不会终止该任务。后续程序可以把返回的 id 传给 `task_output`;取消则由 `task_kill`、owner dispose 或服务 teardown 负责。前台执行仍与本次调用的信号耦合。任务生命周期契约由[后台任务运行时 Agent Note](../architecture/2026-06-20-generic-long-running-tool-runtime.md)定义。
临时 Cordis Plugin 遵循同一规则:`cordis_try` 返回 `{ id, pluginName, state, provides, waitingFor }`,因此程序可以直接读取 `temporary.id`,检查 active 或 pending 状态,并把该 id 传给 `cordis_stop`,无需解析稳定的 Native 语句。
临时 Cordis Plugin 遵循同一规则:`cordis_mount` 返回 `{ id, pluginName, state, provides, waitingFor }`,因此程序可以直接读取 `mounted.id`,检查 active 或 pending 状态,并把该 id 传给 `cordis_unmount`,无需解析稳定的 Native 语句。
### 持久化、元数据与输出落盘
@@ -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-surface.md
2026-07-04-prune-dead-core-spine-surface.md: 10433886d6e8763ca498cb42e0f6b8f35f8c0beb
2026-07-04-prune-dead-core-spine-surface.zh.md: 4502aaebf8b023ca2e43581d25c28eb43e0e73f9
2026-07-04-prune-dead-core-spine-surface.md: a6c608617415f3af07de5c95fd20b0bde40bdef3
2026-07-04-prune-dead-core-spine-surface.zh.md: 83603d8a8432b99d8f42222442b38005e196ac4e
@@ -8,7 +8,7 @@ English | [中文](2026-07-04-prune-dead-core-spine-surface.zh.md)
Several package-root exports, result fields, and convenience methods have no production consumer. They survive because tests import internals through public entry points or because a type anticipated a caller that never arrived. Each item is small in isolation, but together they enlarge the SDK contract, generated catalogs, documentation, and regression matrix without enabling a shipped path.
The production corpus is `packages/*/*/src`, example sources/config, and runtime scripts. Tests, package READMEs, and Agent Note prose are evidence of publication but not fixed callers. `cordis_inspect` makes `packages/cordis/tool-cordis/src/api-catalog.ts` model-visible, and `cordis_try` can invoke injected services through guarded real-service proxies, so catalogued service methods and returned shapes are a genuine dynamic product surface. The table therefore distinguishes absence of a fixed repository caller from unreachability: rows touching catalogued vocabulary intentionally contract what model-written mounts can discover and call, while package-root implementation helpers are not reached through that service façade. Exact-symbol searches produce the following inventory:
The production corpus is `packages/*/*/src`, example sources/config, and runtime scripts. Tests, package READMEs, and Agent Note prose are evidence of publication but not fixed callers. `cordis_inspect` makes `packages/cordis/tool-cordis/src/api-catalog.ts` model-visible, and `cordis_mount` can invoke injected services through guarded real-service proxies, so catalogued service methods and returned shapes are a genuine dynamic product surface. The table therefore distinguishes absence of a fixed repository caller from unreachability: rows touching catalogued vocabulary intentionally contract what model-written mounts can discover and call, while package-root implementation helpers are not reached through that service façade. Exact-symbol searches produce the following inventory:
| Surface | Production evidence | Simplification |
| --- | --- | --- |
@@ -8,7 +8,7 @@ Status: proposed
若干包(package)根导出、结果字段和便利方法没有生产消费方。它们之所以存活,要么是因为测试通过公开入口导入了内部实现,要么是因为某个类型预期了一个从未出现的调用者。每一项单独看都很小,但合在一起,它们扩大了 SDK 契约、生成的 catalog、文档和回归矩阵,却没有支撑任何已交付的路径。
生产语料库是 `packages/*/*/src`、示例源码/配置和运行时脚本。测试、包 README 和 Agent Noteagent 决策记录)行文是发布的证据,但不是固定调用者。`cordis_inspect` 使 `packages/cordis/tool-cordis/src/api-catalog.ts` 对模型可见,`cordis_try` 可以通过受保护的真实服务代理调用注入的服务,因此 catalog 中的服务方法和返回形状是真正的动态产品接口。下表因此区分「没有固定的仓库调用者」与「不可达」:涉及 catalog 词汇的行有意收缩模型编写的 mount 能发现和调用的内容,而包根实现辅助函数并不通过该服务门面可达。精确符号搜索得出以下清单:
生产语料库是 `packages/*/*/src`、示例源码/配置和运行时脚本。测试、包 README 和 Agent Noteagent 决策记录)行文是发布的证据,但不是固定调用者。`cordis_inspect` 使 `packages/cordis/tool-cordis/src/api-catalog.ts` 对模型可见,`cordis_mount` 可以通过受保护的真实服务代理调用注入的服务,因此 catalog 中的服务方法和返回形状是真正的动态产品接口。下表因此区分「没有固定的仓库调用者」与「不可达」:涉及 catalog 词汇的行有意收缩模型编写的 mount 能发现和调用的内容,而包根实现辅助函数并不通过该服务门面可达。精确符号搜索得出以下清单:
| 接口 | 生产证据 | 简化方式 |
| --- | --- | --- |
+14 -14
View File
@@ -1,5 +1,5 @@
// Web e2e scenario for the opt-in Cordis tools. Record mode drives a real
// model through inspect, try, and stop; replay pins the same shipped Web
// model through inspect, mount, and unmount; replay pins the same shipped Web
// composition, durable calls, generic rows, highlighted Plugin source, and
// conversation accessibility tree.
import { readFile } from 'node:fs/promises'
@@ -17,11 +17,11 @@ import { connectFreshWorkspace, saveFailureShot } from './support.ts'
const FIXTURE = fileURLToPath(new URL('./snapshots/cordis-tool-round/session.jsonl', import.meta.url))
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/cordis-tool-round/ui.expected.md', import.meta.url))
const MODE = webSnapshotMode()
const CORDIS_TOOLS = ['cordis_inspect', 'cordis_try', 'cordis_stop'] as const
const TRY_CODE = 'return { name: "snapshot-noop", apply(ctx) {} }'
const CORDIS_TOOLS = ['cordis_inspect', 'cordis_mount', 'cordis_unmount'] as const
const MOUNT_CODE = 'return { name: "snapshot-noop", apply(ctx) {} }'
const PROMPT = 'Use only Cordis tools. First call cordis_inspect with what "temporary". '
+ `Then call cordis_try with this exact code: ${JSON.stringify(TRY_CODE)}. `
+ 'Read its returned id and call cordis_stop with that exact id. '
+ `Then call cordis_mount with this exact code: ${JSON.stringify(MOUNT_CODE)}. `
+ 'Read its returned id and call cordis_unmount with that exact id. '
+ 'After all three calls succeed, reply exactly CORDIS_UI_DONE and stop.'
function assertCompleteCordisLifecycle(events: readonly SessionEvent[]): void {
@@ -105,16 +105,16 @@ describe('web e2e: Cordis tools use the generic row variants', () => {
const inspectRow = page.locator('[data-tool="cordis_inspect"]').filter({ hasText: 'Inspect' }).first()
await inspectRow.waitFor({ timeout: 10_000 })
const tryRow = page.locator('[data-tool="cordis_try"]').filter({ hasText: 'Try temporary Plugin' }).first()
await tryRow.waitFor({ timeout: 10_000 })
await tryRow.locator('button[aria-expanded]').click()
await expect.poll(() => tryRow.locator('pre.shiki').textContent(), { timeout: 10_000 })
.toContain(TRY_CODE)
const mountRow = page.locator('[data-tool="cordis_mount"]').filter({ hasText: 'Mount temporary Plugin' }).first()
await mountRow.waitFor({ timeout: 10_000 })
await mountRow.locator('button[aria-expanded]').click()
await expect.poll(() => mountRow.locator('pre.shiki').textContent(), { timeout: 10_000 })
.toContain(MOUNT_CODE)
const stopRow = page.locator('[data-tool="cordis_stop"]').filter({ hasText: 'Stop temporary Plugin' }).first()
await stopRow.waitFor({ timeout: 10_000 })
await expect.poll(() => stopRow.textContent()).toContain('dyn-')
await expect(stopRow.getAttribute('data-state')).resolves.toBe('ok')
const unmountRow = page.locator('[data-tool="cordis_unmount"]').filter({ hasText: 'Unmount temporary Plugin' }).first()
await unmountRow.waitFor({ timeout: 10_000 })
await expect.poll(() => unmountRow.textContent()).toContain('dyn-')
await expect(unmountRow.getAttribute('data-state')).resolves.toBe('ok')
})
it.skipIf(MODE === 'record')('matches the conversation aria golden', async () => {
@@ -1,48 +1,56 @@
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":1785146149001,"cwd":"{{cwd}}/workspace"}
{"type":"turn/start","seq":0,"time":1785146149060,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"{{rpcId}}"}}}}
{"type":"user/message","seq":1,"time":1785146149060,"data":{"content":[{"type":"text","text":"Use only Cordis tools. First call cordis_inspect with what \"temporary\". Then call cordis_try with this exact code: \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\". Read its returned id and call cordis_stop with that exact id. After all three calls succeed, reply exactly CORDIS_UI_DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpcId}}"}},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":1785146149062,"data":{"title":"Use only Cordis tools. First","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":1785146149128,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":1785146149129,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash","reasoningEffort":"high"},"system":"{{system}}","tools":"{{tools}}","messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":1785146150219,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":6,"time0":1785146150219,"data":{"turn":1,"step":1,"index":0,"dt":[142,8,1,0,0,1,0,19,1,30,0,0,1,0,0,21,1,0,0,0,0,47,0,0,0,0,1,26,0,0,7,0,0,0,0,1,28,1,0,0,0,0,27,0,0,1,0,0,25,0,0,53,3,0,0,0,1,0,1,0,1,0,21,1,0,0,0,28,26,0,0,0,0,0,27,0,0,0,0,0,27,1,0,0,0,28,0,0,29,1,0,0],"texts":["The"," user"," wants"," me"," to",":\n","1","."," Call"," cord","is","_in","spect"," with"," what"," \"","t","emporary","\"\n","2","."," Call"," cord","is","_t","ry"," with"," the"," exact"," code"," \"","return"," {"," name",":"," \\\"","sn","apshot","-no","op","\\\","," apply","(ctx",")"," {}"," }","\"\n","3","."," Read"," the"," returned"," id"," and"," call"," cord","is","_st","op"," with"," that"," exact"," id","\n","4","."," Reply"," exactly"," C","ORD","IS","_","UI","_D","ONE"," and"," stop","\n\n","Let"," me"," start"," with"," steps"," ","1"," and"," ","2"," since"," they","'re"," independent","."]}}
{"type":"assistant/chunk","seq":99,"time":1785146150940,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
{"type":"tool-call-chunks","seq0":100,"time0":1785146150940,"data":{"turn":1,"step":1,"index":1,"dt":[19,1,0,26,2,0,1,24,1,28],"id":"call_00_6xalzzYPi0G7a0hsGnuo3216","name":"cordis_inspect","args":["","{","\"","what","\"",": ","\"","t","emporary","\"","}"]}}
{"type":"assistant/chunk","seq":111,"time":1785146151097,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":2,"blockType":"tool-call"}}}
{"type":"tool-call-chunks","seq0":112,"time0":1785146151098,"data":{"turn":1,"step":1,"index":2,"dt":[26,1,0,0,0,24,2,0,0,0,25,1,0,0,0,2,25,0,1,0,0,25,0],"id":"call_01_SY6OIGsbAyt3IG3BIqa12688","name":"cordis_try","args":["","{","\"","code","\"",": ","\"","return"," {"," name",":"," \\\"","sn","apshot","-no","op","\\\","," apply","(ctx",")"," {}"," }","\"","}"]}}
{"type":"assistant/chunk","seq":136,"time":1785146151291,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to:\n1. Call cordis_inspect with what \"temporary\"\n2. Call cordis_try with the exact code \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\"\n3. Read the returned id and call cordis_stop with that exact id\n4. Reply exactly CORDIS_UI_DONE and stop\n\nLet me start with steps 1 and 2 since they're independent."}}}}
{"type":"assistant/chunk","seq":137,"time":1785146151292,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_6xalzzYPi0G7a0hsGnuo3216","name":"cordis_inspect","arguments":"{\"what\": \"temporary\"}"}}}}
{"type":"assistant/chunk","seq":138,"time":1785146151292,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":2,"block":{"type":"tool-call","id":"call_01_SY6OIGsbAyt3IG3BIqa12688","name":"cordis_try","arguments":"{\"code\": \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\"}"}}}}
{"type":"assistant/chunk","seq":139,"time":1785146151292,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":14106,"outputTokens":186,"cacheReadTokens":2304,"reasoningTokens":93}}}}
{"type":"assistant/chunk","seq":140,"time":1785146151292,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":141,"time":1785146151296,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to:\n1. Call cordis_inspect with what \"temporary\"\n2. Call cordis_try with the exact code \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\"\n3. Read the returned id and call cordis_stop with that exact id\n4. Reply exactly CORDIS_UI_DONE and stop\n\nLet me start with steps 1 and 2 since they're independent."},{"type":"tool-call","id":"call_00_6xalzzYPi0G7a0hsGnuo3216","name":"cordis_inspect","arguments":"{\"what\": \"temporary\"}"},{"type":"tool-call","id":"call_01_SY6OIGsbAyt3IG3BIqa12688","name":"cordis_try","arguments":"{\"code\": \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":14106,"outputTokens":186,"cacheReadTokens":2304,"reasoningTokens":93}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140],"surfaceOp":"append"}
{"type":"tool/call","seq":142,"time":1785146151296,"data":{"turn":1,"step":1,"callId":"call_00_6xalzzYPi0G7a0hsGnuo3216","name":"cordis_inspect","arguments":"{\"what\": \"temporary\"}"}}
{"type":"tool/result","seq":143,"time":1785146151299,"data":{"turn":1,"step":1,"callId":"call_00_6xalzzYPi0G7a0hsGnuo3216","content":[{"type":"text","text":"## Temporary Plugins\nNo temporary Plugins are running. Temporary Plugins created with cordis_try disappear when DSH restarts."}],"isError":false},"sourceEventSeqs":[142],"surfaceOp":"append"}
{"type":"tool/call","seq":144,"time":1785146151300,"data":{"turn":1,"step":1,"callId":"call_01_SY6OIGsbAyt3IG3BIqa12688","name":"cordis_try","arguments":"{\"code\": \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\"}"}}
{"type":"tool/result","seq":145,"time":1785146151302,"data":{"turn":1,"step":1,"callId":"call_01_SY6OIGsbAyt3IG3BIqa12688","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-noop\"; available until stopped or DSH restarts)."}],"isError":false},"sourceEventSeqs":[144],"surfaceOp":"append"}
{"type":"step/end","seq":146,"time":1785146151304,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":147,"time":1785146151305,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":148,"time":1785146152097,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":149,"time0":1785146152097,"data":{"turn":1,"step":2,"index":0,"dt":[119,33,2,0,0,0,24,2,0,0,1,24,2,0,31,21,1,0,1,0,0,26,1,0,1,0,0,23,2,0,0],"texts":["The"," cord","is","_t","ry"," returned"," \"","d","yn","-","1","\""," as"," the"," temporary"," plugin"," id","."," Now"," I"," need"," to"," call"," cord","is","_st","op"," with"," that"," exact"," id","."]}}
{"type":"assistant/chunk","seq":181,"time":1785146152498,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
{"type":"tool-call-chunks","seq0":182,"time0":1785146152498,"data":{"turn":1,"step":2,"index":1,"dt":[19,2,0,0,28,2,0,0,22,2,0,25],"id":"call_00_NJsTqzhejSfRE4hdAwCe4952","name":"cordis_stop","args":["","{","\"","id","\"",": ","\"","d","yn","-","1","\"","}"]}}
{"type":"assistant/chunk","seq":195,"time":1785146152633,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The cordis_try returned \"dyn-1\" as the temporary plugin id. Now I need to call cordis_stop with that exact id."}}}}
{"type":"assistant/chunk","seq":196,"time":1785146152633,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_NJsTqzhejSfRE4hdAwCe4952","name":"cordis_stop","arguments":"{\"id\": \"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":197,"time":1785146152633,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":155,"outputTokens":81,"cacheReadTokens":16512,"reasoningTokens":32}}}}
{"type":"assistant/chunk","seq":198,"time":1785146152634,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":199,"time":1785146152635,"data":{"turn":1,"step":2,"content":[{"type":"reasoning","text":"The cordis_try returned \"dyn-1\" as the temporary plugin id. Now I need to call cordis_stop with that exact id."},{"type":"tool-call","id":"call_00_NJsTqzhejSfRE4hdAwCe4952","name":"cordis_stop","arguments":"{\"id\": \"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":155,"outputTokens":81,"cacheReadTokens":16512,"reasoningTokens":32}},"sourceEventSeqs":[148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198],"surfaceOp":"append"}
{"type":"tool/call","seq":200,"time":1785146152635,"data":{"turn":1,"step":2,"callId":"call_00_NJsTqzhejSfRE4hdAwCe4952","name":"cordis_stop","arguments":"{\"id\": \"dyn-1\"}"}}
{"type":"tool/result","seq":201,"time":1785146152636,"data":{"turn":1,"step":2,"callId":"call_00_NJsTqzhejSfRE4hdAwCe4952","content":[{"type":"text","text":"Temporary Plugin dyn-1 was stopped and removed."}],"isError":false},"sourceEventSeqs":[200],"surfaceOp":"append"}
{"type":"step/end","seq":202,"time":1785146152638,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":203,"time":1785146152638,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":204,"time":1785146153286,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":205,"time0":1785146153286,"data":{"turn":1,"step":3,"index":0,"dt":[90,55,12,1,13,1,0,33,0,0,22,1,1,0,0,1,27,1],"texts":["All"," three"," calls"," succeeded","."," I"," should"," now"," reply"," exactly"," \"","C","ORD","IS","_","UI","_D","ONE","\"."]}}
{"type":"assistant/chunk","seq":224,"time":1785146153544,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
{"type":"text-chunks","seq0":225,"time0":1785146153544,"data":{"turn":1,"step":3,"index":1,"dt":[25,0,0,0,0,3],"texts":["C","ORD","IS","_","UI","_D","ONE"]}}
{"type":"assistant/chunk","seq":232,"time":1785146153601,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"All three calls succeeded. I should now reply exactly \"CORDIS_UI_DONE\"."}}}}
{"type":"assistant/chunk","seq":233,"time":1785146153601,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"CORDIS_UI_DONE"}}}}
{"type":"assistant/chunk","seq":234,"time":1785146153601,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":129,"outputTokens":27,"cacheReadTokens":16640,"reasoningTokens":19}}}}
{"type":"assistant/chunk","seq":235,"time":1785146153601,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":236,"time":1785146153602,"data":{"turn":1,"step":3,"content":[{"type":"reasoning","text":"All three calls succeeded. I should now reply exactly \"CORDIS_UI_DONE\"."},{"type":"text","text":"CORDIS_UI_DONE"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":129,"outputTokens":27,"cacheReadTokens":16640,"reasoningTokens":19}},"sourceEventSeqs":[204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235],"surfaceOp":"append"}
{"type":"step/end","seq":237,"time":1785146153602,"data":{"turn":1,"step":3}}
{"type":"turn/end","seq":238,"time":1785146153603,"data":{"turn":1,"reason":{"kind":"completed"}}}
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":1785157562825,"cwd":"{{cwd}}/workspace"}
{"type":"turn/start","seq":0,"time":1785157562881,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"{{rpcId}}"}}}}
{"type":"user/message","seq":1,"time":1785157562882,"data":{"content":[{"type":"text","text":"Use only Cordis tools. First call cordis_inspect with what \"temporary\". Then call cordis_mount with this exact code: \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\". Read its returned id and call cordis_unmount with that exact id. After all three calls succeed, reply exactly CORDIS_UI_DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpcId}}"}},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":1785157562883,"data":{"title":"Use only Cordis tools. First","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":1785157562937,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":1785157562938,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash","reasoningEffort":"high"},"system":"{{system}}","tools":"{{tools}}","messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":1785157564667,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":6,"time0":1785157564667,"data":{"turn":1,"step":1,"index":0,"dt":[115,31,3,0,1,0,1,21,1,0,0,0,1,23,0,1,0,0,0,24,2,1,0,0,0,28,2,0,0,0,1,23,2,22,2,1,25,2,0,0,25,27,1,1,0,0,28,2,0,0,0,0,32,1,31,1,0,0,0,9,29,3,0,0,0,1,23,1,0,0,0,27,4,0,0,0,23,2,0,0],"texts":["The"," user"," wants"," me"," to",":\n","1","."," Call"," `","cord","is","_in","spect","`"," with"," `","what",":"," \"","t","emporary","\"`\n","2","."," Call"," `","cord","is","_m","ount","`"," with"," the"," exact"," code"," provided","\n","3","."," Read"," the"," returned"," id"," and"," call"," `","cord","is","_un","mount","`"," with"," that"," exact"," id","\n","4","."," Reply"," exactly"," \"","C","ORD","IS","_","UI","_D","ONE","\""," and"," stop","\n\n","Let"," me"," start"," with"," step"," ","1","."]}}
{"type":"assistant/chunk","seq":87,"time":1785157565360,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
{"type":"tool-call-chunks","seq0":88,"time0":1785157565360,"data":{"turn":1,"step":1,"index":1,"dt":[15,2,0,0,25,2,0,0,27,1],"id":"call_00_KZk918WtlKan9pHMULIT8794","name":"cordis_inspect","args":["","{","\"","what","\"",": ","\"","t","emporary","\"","}"]}}
{"type":"assistant/chunk","seq":99,"time":1785157565490,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to:\n1. Call `cordis_inspect` with `what: \"temporary\"`\n2. Call `cordis_mount` with the exact code provided\n3. Read the returned id and call `cordis_unmount` with that exact id\n4. Reply exactly \"CORDIS_UI_DONE\" and stop\n\nLet me start with step 1."}}}}
{"type":"assistant/chunk","seq":100,"time":1785157565491,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_KZk918WtlKan9pHMULIT8794","name":"cordis_inspect","arguments":"{\"what\": \"temporary\"}"}}}}
{"type":"assistant/chunk","seq":101,"time":1785157565491,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":15137,"outputTokens":128,"cacheReadTokens":1280,"reasoningTokens":81}}}}
{"type":"assistant/chunk","seq":102,"time":1785157565491,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":103,"time":1785157565495,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to:\n1. Call `cordis_inspect` with `what: \"temporary\"`\n2. Call `cordis_mount` with the exact code provided\n3. Read the returned id and call `cordis_unmount` with that exact id\n4. Reply exactly \"CORDIS_UI_DONE\" and stop\n\nLet me start with step 1."},{"type":"tool-call","id":"call_00_KZk918WtlKan9pHMULIT8794","name":"cordis_inspect","arguments":"{\"what\": \"temporary\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":15137,"outputTokens":128,"cacheReadTokens":1280,"reasoningTokens":81}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102],"surfaceOp":"append"}
{"type":"tool/call","seq":104,"time":1785157565496,"data":{"turn":1,"step":1,"callId":"call_00_KZk918WtlKan9pHMULIT8794","name":"cordis_inspect","arguments":"{\"what\": \"temporary\"}"}}
{"type":"tool/result","seq":105,"time":1785157565500,"data":{"turn":1,"step":1,"callId":"call_00_KZk918WtlKan9pHMULIT8794","content":[{"type":"text","text":"## Temporary Plugins\nNo temporary Plugins are running. Temporary Plugins created with cordis_mount disappear when DSH restarts."}],"isError":false},"sourceEventSeqs":[104],"surfaceOp":"append"}
{"type":"step/end","seq":106,"time":1785157565503,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":107,"time":1785157565503,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":108,"time":1785157566524,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":109,"time0":1785157566525,"data":{"turn":1,"step":2,"index":0,"dt":[105,30,2,0,0,24,2,0,0,27,2,1,0,25,0,0,0,1,0,39,1],"texts":["Good",","," no"," temporary"," plugins"," running","."," Now"," step"," ","2",":"," call"," cord","is","_m","ount"," with"," the"," exact"," code","."]}}
{"type":"assistant/chunk","seq":131,"time":1785157566845,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
{"type":"tool-call-chunks","seq0":132,"time0":1785157566845,"data":{"turn":1,"step":2,"index":1,"dt":[42,4,1,0,0,15,2,0,0,0,16,0,0,0,0,1,23,2,0,0,25,7,18],"id":"call_00_OyrlxqqpnvzTe11sN5Oy2361","name":"cordis_mount","args":["","{","\"","code","\"",": ","\"","return"," {"," name",":"," \\\"","sn","apshot","-no","op","\\\","," apply","(ctx",")"," {}"," }","\"","}"]}}
{"type":"assistant/chunk","seq":156,"time":1785157567041,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Good, no temporary plugins running. Now step 2: call cordis_mount with the exact code."}}}}
{"type":"assistant/chunk","seq":157,"time":1785157567041,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_OyrlxqqpnvzTe11sN5Oy2361","name":"cordis_mount","arguments":"{\"code\": \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\"}"}}}}
{"type":"assistant/chunk","seq":158,"time":1785157567042,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":72,"outputTokens":82,"cacheReadTokens":16512,"reasoningTokens":22}}}}
{"type":"assistant/chunk","seq":159,"time":1785157567042,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":160,"time":1785157567043,"data":{"turn":1,"step":2,"content":[{"type":"reasoning","text":"Good, no temporary plugins running. Now step 2: call cordis_mount with the exact code."},{"type":"tool-call","id":"call_00_OyrlxqqpnvzTe11sN5Oy2361","name":"cordis_mount","arguments":"{\"code\": \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":72,"outputTokens":82,"cacheReadTokens":16512,"reasoningTokens":22}},"sourceEventSeqs":[108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159],"surfaceOp":"append"}
{"type":"tool/call","seq":161,"time":1785157567043,"data":{"turn":1,"step":2,"callId":"call_00_OyrlxqqpnvzTe11sN5Oy2361","name":"cordis_mount","arguments":"{\"code\": \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\"}"}}
{"type":"tool/result","seq":162,"time":1785157567049,"data":{"turn":1,"step":2,"callId":"call_00_OyrlxqqpnvzTe11sN5Oy2361","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-noop\"; available until unmounted or DSH restarts)."}],"isError":false},"sourceEventSeqs":[161],"surfaceOp":"append"}
{"type":"step/end","seq":163,"time":1785157567050,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":164,"time":1785157567050,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":165,"time":1785157567835,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":166,"time0":1785157567836,"data":{"turn":1,"step":3,"index":0,"dt":[83,25,2,0,0,26,1,1,0,0,0,41,2,0,0,0,0,8,2,0,0,0],"texts":["The"," id"," is"," \"","d","yn","-","1","\"."," Now"," step"," ","3",":"," call"," cord","is","_un","mount"," with"," that"," id","."]}}
{"type":"assistant/chunk","seq":189,"time":1785157568117,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
{"type":"tool-call-chunks","seq0":190,"time0":1785157568118,"data":{"turn":1,"step":3,"index":1,"dt":[17,2,1,24,2,0,1,25,0,0,0,29],"id":"call_00_e38S6zeYdZGvbhecUCil6659","name":"cordis_unmount","args":["","{","\"","id","\"",": ","\"","d","yn","-","1","\"","}"]}}
{"type":"assistant/chunk","seq":203,"time":1785157568278,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The id is \"dyn-1\". Now step 3: call cordis_unmount with that id."}}}}
{"type":"assistant/chunk","seq":204,"time":1785157568279,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_e38S6zeYdZGvbhecUCil6659","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":205,"time":1785157568279,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":63,"outputTokens":72,"cacheReadTokens":16640,"reasoningTokens":23}}}}
{"type":"assistant/chunk","seq":206,"time":1785157568279,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":207,"time":1785157568280,"data":{"turn":1,"step":3,"content":[{"type":"reasoning","text":"The id is \"dyn-1\". Now step 3: call cordis_unmount with that id."},{"type":"tool-call","id":"call_00_e38S6zeYdZGvbhecUCil6659","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":63,"outputTokens":72,"cacheReadTokens":16640,"reasoningTokens":23}},"sourceEventSeqs":[165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206],"surfaceOp":"append"}
{"type":"tool/call","seq":208,"time":1785157568280,"data":{"turn":1,"step":3,"callId":"call_00_e38S6zeYdZGvbhecUCil6659","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}
{"type":"tool/result","seq":209,"time":1785157568281,"data":{"turn":1,"step":3,"callId":"call_00_e38S6zeYdZGvbhecUCil6659","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false},"sourceEventSeqs":[208],"surfaceOp":"append"}
{"type":"step/end","seq":210,"time":1785157568282,"data":{"turn":1,"step":3}}
{"type":"step/start","seq":211,"time":1785157568282,"data":{"turn":1,"step":4}}
{"type":"assistant/chunk","seq":212,"time":1785157569185,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":213,"time0":1785157569186,"data":{"turn":1,"step":4,"index":0,"dt":[165,26,26,1,0,27,9,17,2,1,0,0,0,44,1,0,0,1,0,6,3],"texts":["All"," three"," calls"," succeeded","."," I"," should"," now"," reply"," exactly"," \"","C","ORD","IS","_","UI","_D","ONE","\""," and"," stop","."]}}
{"type":"assistant/chunk","seq":235,"time":1785157569515,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
{"type":"text-chunks","seq0":236,"time0":1785157569515,"data":{"turn":1,"step":4,"index":1,"dt":[0,0,35,1,0,0],"texts":["C","ORD","IS","_","UI","_D","ONE"]}}
{"type":"assistant/chunk","seq":243,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"All three calls succeeded. I should now reply exactly \"CORDIS_UI_DONE\" and stop."}}}}
{"type":"assistant/chunk","seq":244,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"CORDIS_UI_DONE"}}}}
{"type":"assistant/chunk","seq":245,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":29,"outputTokens":30,"cacheReadTokens":16768,"reasoningTokens":22}}}}
{"type":"assistant/chunk","seq":246,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":247,"time":1785157569553,"data":{"turn":1,"step":4,"content":[{"type":"reasoning","text":"All three calls succeeded. I should now reply exactly \"CORDIS_UI_DONE\" and stop."},{"type":"text","text":"CORDIS_UI_DONE"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":29,"outputTokens":30,"cacheReadTokens":16768,"reasoningTokens":22}},"sourceEventSeqs":[212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246],"surfaceOp":"append"}
{"type":"step/end","seq":248,"time":1785157569554,"data":{"turn":1,"step":4}}
{"type":"turn/end","seq":249,"time":1785157569554,"data":{"turn":1,"reason":{"kind":"completed"}}}
@@ -6,7 +6,7 @@
- tab "Chat" [selected]
- tab "Trajectory"
- tab "Waterfall"
- text: "Use only Cordis tools. First call cordis_inspect with what \"temporary\". Then call cordis_try with this exact code: \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\". Read its returned id and call cordis_stop with that exact id. After all three calls succeed, reply exactly CORDIS_UI_DONE and stop."
- text: "Use only Cordis tools. First call cordis_inspect with what \"temporary\". Then call cordis_mount with this exact code: \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\". Read its returned id and call cordis_unmount with that exact id. After all three calls succeed, reply exactly CORDIS_UI_DONE and stop."
- button "复制":
- img
- button "在新对话中分支":
@@ -19,22 +19,25 @@
- button:
- img
- text: Inspect temporary
- 'button "Think Good, no temporary plugins running. Now step 2: call cordis_mount with the exact code."':
- img
- text: "Think Good, no temporary plugins running. Now step 2: call cordis_mount with the exact code."
- button [expanded]:
- img
- text: Try temporary Plugin typescript
- text: Mount temporary Plugin typescript
- button "复制"
- code: "return { name: \"snapshot-noop\", apply(ctx) {} }"
- button "Think The cordis_try returned \"dyn-1\" as the temporary plugin id. Now I need to call cordis_stop with that exact id.":
- 'button "Think The id is \"dyn-1\". Now step 3: call cordis_unmount with that id."':
- img
- text: Think The cordis_try returned "dyn-1" as the temporary plugin id. Now I need to call cordis_stop with that exact id.
- text: "Think The id is \"dyn-1\". Now step 3: call cordis_unmount with that id."
- button:
- img
- text: Stop temporary Plugin dyn-1
- button "Think All three calls succeeded. I should now reply exactly \"CORDIS_UI_DONE\".":
- text: Unmount temporary Plugin dyn-1
- button "Think All three calls succeeded. I should now reply exactly \"CORDIS_UI_DONE\" and stop.":
- img
- text: Think All three calls succeeded. I should now reply exactly "CORDIS_UI_DONE".
- text: Think All three calls succeeded. I should now reply exactly "CORDIS_UI_DONE" and stop.
- paragraph: CORDIS_UI_DONE
- text: cache hit 71% · 50,140 tokens · 1 turns · 3 steps
- text: cache hit 77% · 66,813 tokens · 1 turns · 4 steps
- textbox "Message the agent"
- button "Add attachment":
- img
+26 -26
View File
@@ -19,7 +19,7 @@ This table connects model-visible tool names to the plugin package and service s
| `@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 TypeScript SDK section, 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.userInteraction (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-interaction 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.bash`, `ctx.tasks 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.tasks` runtime and is collected/stopped through the `task_*` tools from `@deepseek-ai/dsh-tool-tasks`; the `enableRunInBackground` config (default true) removes the parameter entirely when disabled. |
| `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect`, `cordis_stop`, `cordis_try` | `ctx.tools` | `tool/call`, `tool/result`, `process-local temporary Plugin lifecycle` | - | Ships in examples/cordis-agent only (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_try may register ADDITIONAL model-visible tools until stopped or DSH restarts; a full changed request header logs those tool-set changes. |
| `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect`, `cordis_mount`, `cordis_unmount` | `ctx.tools` | `tool/call`, `tool/result`, `process-local temporary Plugin lifecycle` | - | Ships in examples/cordis-agent only (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes. |
| `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `write` | `ctx.tools`, `ctx.fs`, `ctx.systemPrompt` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after successful file operations`, `tool/result` | - | The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The tool schemas above are identical with or without the policy plugin. |
| `@deepseek-ai/dsh-tool-fs-search` | `glob`, `grep` | `ctx.tools`, `ctx.bash`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | glob and grep are conditional bash-backed discovery tools: they register only when ctx.bash can find `rg`, then run fixed ripgrep commands through ctx.bash as ordinary foreground calls (never background tasks). Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments. |
| `@deepseek-ai/dsh-tool-pty` | `terminal_close`, `terminal_list`, `terminal_open`, `terminal_read`, `terminal_send`, `terminal_signal` | `ctx.tools`, `ctx.pty`, `ctx.systemPrompt`, `ctx.tasks at call time for run_in_background` | `tool/call`, `tool/result` | - | The six terminal tools are opt-in and complement one-shot bash/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.tasks`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema. |
@@ -207,7 +207,7 @@ The bash tool is the model-facing consumer of the bash executor seam. A `run_in_
### `cordis_inspect`
Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_try: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_stop, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:"api"` or `what:"events"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc.
Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:"api"` or `what:"events"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc.
```json
{
@@ -235,30 +235,9 @@ Inspect the live Cordis runtime in the current DSH process. Read-only. Sections:
Source: [`packages/cordis/tool-cordis/src/index.ts`](../packages/cordis/tool-cordis/src/index.ts)
### `cordis_stop`
### `cordis_mount`
Stop a current-process temporary Plugin created by cordis_try. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.
```json
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The temporary Plugin id returned by cordis_try (for example \"dyn-1\"); valid only in this process and invalid after stop or restart."
}
},
"required": [
"id"
]
}
```
Source: [`packages/cordis/tool-cordis/src/index.ts`](../packages/cordis/tool-cordis/src/index.ts)
### `cordis_try`
Try a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_stop, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider stops. BEFORE calling a service from your code, read cordis_inspect what:"api" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:"events"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider stops. Everything registered inside `apply` is cleaned up automatically by cordis_stop. Sandbox globals: `console` (tagged `[cordis:<id>]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when stopped) — cordis_inspect what:"api" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` VETOES the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.
Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:"api" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:"events"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:<id>]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:"api" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` VETOES the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.
```json
{
@@ -277,7 +256,28 @@ Try a temporary Cordis Plugin in the current DSH process. This creates an in-mem
Source: [`packages/cordis/tool-cordis/src/index.ts`](../packages/cordis/tool-cordis/src/index.ts)
Ships in examples/cordis-agent only (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_try may register ADDITIONAL model-visible tools until stopped or DSH restarts; a full changed request header logs those tool-set changes.
### `cordis_unmount`
Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.
```json
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart."
}
},
"required": [
"id"
]
}
```
Source: [`packages/cordis/tool-cordis/src/index.ts`](../packages/cordis/tool-cordis/src/index.ts)
Ships in examples/cordis-agent only (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes.
## `@deepseek-ai/dsh-tool-fs`
+2 -2
View File
@@ -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 examples/README.md
README.md: 8c9bf449bc0ec4f857fae3c7775ed2cb8febbad8
README.zh.md: 2baba679de10a5e52b719e1bbe1900a792610777
README.md: 7f12178d1b67f1ebfac6f4f0e31403c54106e98f
README.zh.md: 72ab92602d0a53cabdbfa8bc34838061df25d1c7
+1 -1
View File
@@ -22,7 +22,7 @@ An unattended coding agent driven through the Python SDK: JSON-RPC stdio, foregr
## cordis-agent
The **self-referential** demo: the coding spine plus [`@deepseek-ai/dsh-tool-cordis`](../packages/cordis/tool-cordis), whose three tools (`cordis_inspect` / `cordis_try` / `cordis_stop`) let the agent inspect the current DSH process, try model-written temporary Plugins (an event listener, a brand-new tool, or a service another temporary Plugin injects), and stop them again. These Plugins exist only in memory and share one internal `cordis-dynamic` fiber subtree; `ctx.fs`/`ctx.web` ride along provider-only as capabilities they can use.
The **self-referential** demo: the coding spine plus [`@deepseek-ai/dsh-tool-cordis`](../packages/cordis/tool-cordis), whose three tools (`cordis_inspect` / `cordis_mount` / `cordis_unmount`) let the agent inspect the current DSH process, mount model-written temporary Plugins (an event listener, a brand-new tool, or a service another temporary Plugin injects), and unmount them again. These Plugins exist only in memory and share one internal `cordis-dynamic` fiber subtree; `ctx.fs`/`ctx.web` ride along provider-only as capabilities they can use.
Run the TUI with `pnpm run demo:cordis`, the browser UI at `http://127.0.0.1:3081` with `pnpm run demo:cordis web`, or the ACP server with `pnpm run demo:cordis acp` (all need `DEEPSEEK_API_KEY`). See [cordis-agent/README.md](cordis-agent/README.md) for the staged demo script and [the toolset Agent Note](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md) for the design and sandbox caveats.
+1 -1
View File
@@ -22,7 +22,7 @@
## cordis-agent
**自指** 演示:编码主干加 [`@deepseek-ai/dsh-tool-cordis`](../packages/cordis/tool-cordis),其三个工具(`cordis_inspect`/`cordis_try`/`cordis_stop`)使 agent 可以检查当前 DSH 进程、尝试模型编写的临时 Plugin(事件监听器、一个全新工具,或一个供另一临时 Plugin 注入的服务),并再次停止它们。这些 Plugin 只存在于内存中,共享一个内部 `cordis-dynamic` fiber 子树;`ctx.fs`/`ctx.web` 仅作为它们可用的能力提供方。
**自指** 演示:编码主干加 [`@deepseek-ai/dsh-tool-cordis`](../packages/cordis/tool-cordis),其三个工具(`cordis_inspect`/`cordis_mount`/`cordis_unmount`)使 agent 可以检查当前 DSH 进程、挂载模型编写的临时 Plugin(事件监听器、一个全新工具,或一个供另一临时 Plugin 注入的服务),并再次卸载它们。这些 Plugin 只存在于内存中,共享一个内部 `cordis-dynamic` fiber 子树;`ctx.fs`/`ctx.web` 仅作为它们可用的能力提供方。
使用 `pnpm run demo:cordis` 运行 TUI,使用 `pnpm run demo:cordis web``http://127.0.0.1:3081` 启动浏览器 UI,或使用 `pnpm run demo:cordis acp` 启动 ACP 服务器(三者均需 `DEEPSEEK_API_KEY`)。分阶段演示脚本详见 [cordis-agent/README.md](cordis-agent/README.md),设计与沙箱注意事项详见[工具集 Agent Note](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。
@@ -5,13 +5,13 @@
{"type":"step/start","seq":3,"time":1783957884486,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":1783957884486,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":1783950000005,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":6,"time":1783950000006,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_try","argumentsDelta":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}
{"type":"assistant/chunk","seq":7,"time":1783950000007,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_try","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}}
{"type":"assistant/chunk","seq":6,"time":1783950000006,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_mount","argumentsDelta":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}
{"type":"assistant/chunk","seq":7,"time":1783950000007,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}}
{"type":"assistant/chunk","seq":8,"time":1783950000008,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":9,"time":1783950000009,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":10,"time":1783957884487,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_try","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"tool/call","seq":11,"time":1783957884487,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_try","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}
{"type":"tool/result","seq":12,"time":1783957884488,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until stopped or DSH restarts)."}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}
{"type":"assistant/message","seq":10,"time":1783957884487,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"tool/call","seq":11,"time":1783957884487,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}
{"type":"tool/result","seq":12,"time":1783957884488,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until unmounted or DSH restarts)."}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}
{"type":"step/end","seq":13,"time":1783957884489,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":14,"time":1783957884489,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":15,"time":1783950000015,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
@@ -22,8 +22,8 @@
{"type":"assistant/message","seq":20,"time":1783957884490,"data":{"turn":1,"step":2,"content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[15,16,17,18,19],"surfaceOp":"append"}
{"type":"tool/call","seq":21,"time":1783957884490,"data":{"turn":1,"step":2,"callId":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}}
{"type":"tool/code-dispatch-start","seq":22,"time":1785036891166,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_inspect","arguments":{"what":"temporary"}}}
{"type":"tool/code-dispatch","seq":23,"time":1785036891167,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_inspect","arguments":{"what":"temporary"},"isError":false,"content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until stopped or DSH restarts"}]}}
{"type":"tool/result","seq":24,"time":1785036891170,"data":{"turn":1,"step":2,"callId":"advanced-code","content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until stopped or DSH restarts"}],"isError":false},"sourceEventSeqs":[21],"surfaceOp":"append"}
{"type":"tool/code-dispatch","seq":23,"time":1785036891167,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_inspect","arguments":{"what":"temporary"},"isError":false,"content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until unmounted or DSH restarts"}]}}
{"type":"tool/result","seq":24,"time":1785036891170,"data":{"turn":1,"step":2,"callId":"advanced-code","content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until unmounted or DSH restarts"}],"isError":false},"sourceEventSeqs":[21],"surfaceOp":"append"}
{"type":"step/end","seq":25,"time":1785036891171,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":26,"time":1785036891175,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":27,"time":1783950000027,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
@@ -47,13 +47,13 @@
{"type":"step/end","seq":45,"time":1785036891786,"data":{"turn":1,"step":4}}
{"type":"step/start","seq":46,"time":1785036891789,"data":{"turn":1,"step":5}}
{"type":"assistant/chunk","seq":47,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":48,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-unmount","name":"cordis_stop","argumentsDelta":"{\"id\":\"dyn-1\"}"}}}
{"type":"assistant/chunk","seq":49,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\":\"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":48,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-unmount","name":"cordis_unmount","argumentsDelta":"{\"id\":\"dyn-1\"}"}}}
{"type":"assistant/chunk","seq":49,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":50,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":51,"time":1785036891795,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":52,"time":1785036891796,"data":{"turn":1,"step":5,"content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\":\"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[47,48,49,50,51],"surfaceOp":"append"}
{"type":"tool/call","seq":53,"time":1785036891796,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\":\"dyn-1\"}"}}
{"type":"tool/result","seq":54,"time":1785036891798,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was stopped and removed."}],"isError":false},"sourceEventSeqs":[53],"surfaceOp":"append"}
{"type":"assistant/message","seq":52,"time":1785036891796,"data":{"turn":1,"step":5,"content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[47,48,49,50,51],"surfaceOp":"append"}
{"type":"tool/call","seq":53,"time":1785036891796,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}}
{"type":"tool/result","seq":54,"time":1785036891798,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false},"sourceEventSeqs":[53],"surfaceOp":"append"}
{"type":"step/end","seq":55,"time":1785036891799,"data":{"turn":1,"step":5}}
{"type":"step/start","seq":56,"time":1785036891801,"data":{"turn":1,"step":6}}
{"type":"assistant/chunk","seq":57,"time":1783957884720,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
@@ -56,23 +56,23 @@ interface ToolArgsMap {
/** Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access. */
justification?: string;
} & Record<string, JsonValue>;
/** Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_try: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_stop, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:"api"` or `what:"events"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc. */
/** Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:"api"` or `what:"events"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc. */
cordis_inspect: {
/** Limit the report to one section. Omit for all sections. */
what?: "services" | "plugins" | "tools" | "temporary" | "api" | "events";
/** Exact service key or event name whose original JSDoc to include; valid only with what:"api" or what:"events". */
name?: string;
} & Record<string, JsonValue>;
/** Stop a current-process temporary Plugin created by cordis_try. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins. */
cordis_stop: {
/** The temporary Plugin id returned by cordis_try (for example "dyn-1"); valid only in this process and invalid after stop or restart. */
id: string;
} & Record<string, JsonValue>;
/** Try a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_stop, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider stops. BEFORE calling a service from your code, read cordis_inspect what:"api" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:"events"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider stops. Everything registered inside `apply` is cleaned up automatically by cordis_stop. Sandbox globals: `console` (tagged `[cordis:<id>]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when stopped) — cordis_inspect what:"api" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` VETOES the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime. */
cordis_try: {
/** Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:"api" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:"events"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:<id>]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:"api" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` VETOES the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime. */
cordis_mount: {
/** JavaScript body returning a temporary Plugin; evaluated now and saved nowhere. */
code: string;
} & Record<string, JsonValue>;
/** Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins. */
cordis_unmount: {
/** The temporary Plugin id returned by cordis_mount (for example "dyn-1"); valid only in this process and invalid after unmount or restart. */
id: string;
} & Record<string, JsonValue>;
/** Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say "create a goal". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority. */
create_goal: {
/** The concrete completion objective inferred from the direct human request. */
@@ -248,17 +248,17 @@ interface ToolOutputMap {
};
};
cordis_inspect: string;
cordis_stop: {
id: string;
pluginName: string;
};
cordis_try: {
cordis_mount: {
id: string;
pluginName: string;
state: "pending" | "loading" | "active" | "failed" | "disposed" | "unloading";
provides: string[];
waitingFor: string[];
};
cordis_unmount: {
id: string;
pluginName: string;
};
create_goal: {
goal: null;
} | {
@@ -47,7 +47,7 @@
},
{
"name": "cordis_inspect",
"description": "Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_try: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_stop, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc.",
"description": "Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc.",
"parameters": {
"type": "object",
"properties": {
@@ -71,24 +71,8 @@
}
},
{
"name": "cordis_stop",
"description": "Stop a current-process temporary Plugin created by cordis_try. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.",
"parameters": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The temporary Plugin id returned by cordis_try (for example \"dyn-1\"); valid only in this process and invalid after stop or restart."
}
},
"required": [
"id"
]
}
},
{
"name": "cordis_try",
"description": "Try a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_stop, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider stops. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider stops. Everything registered inside `apply` is cleaned up automatically by cordis_stop. Sandbox globals: `console` (tagged `[cordis:<id>]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when stopped) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` VETOES the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.",
"name": "cordis_mount",
"description": "Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:<id>]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` VETOES the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.",
"parameters": {
"type": "object",
"properties": {
@@ -102,6 +86,22 @@
]
}
},
{
"name": "cordis_unmount",
"description": "Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.",
"parameters": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart."
}
},
"required": [
"id"
]
}
},
{
"name": "create_goal",
"description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.",
@@ -11,7 +11,7 @@
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"call_spill","name":"bash","arguments":"{\"command\":\"node -e \\\"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\\\"\",\"description\":\"Print large deterministic output\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"tool/call","seq":11,"time":0,"data":{"turn":1,"step":1,"callId":"call_spill","name":"bash","arguments":"{\"command\":\"node -e \\\"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\\\"\",\"description\":\"Print large deterministic output\"}"}}
{"type":"tool/result","seq":12,"time":0,"data":{"turn":1,"step":1,"callId":"call_spill","content":[{"type":"text","text":"SPILL_START-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-SPILL_END\n\n(Omitted 1417 bytes. Full formatted result stored at: /tmp/dsh-acp-snap-ee77dff02/session-8398dc5565aa/cad9e2509e5d-bash.txt. Use read with offset/limit, or grep this path to search within it.)"}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}
{"type":"tool/result","seq":12,"time":0,"data":{"turn":1,"step":1,"callId":"call_spill","content":[{"type":"text","text":"SPILL_START-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-SPILL_END\n\n(Omitted 1417 bytes. Full formatted result stored at: /tmp/dsh-acp-snap-ee77dff02/session-5e53dc8acfe4/2ce9d7a31a38-bash.txt. Use read with offset/limit, or grep this path to search within it.)"}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}
{"type":"step/end","seq":13,"time":0,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":14,"time":0,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
@@ -14,8 +14,8 @@
{"type":"assistant/chunk","seq":127,"time":1783860677493,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":128,"time":1784821261753,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to run a command with sandbox_permissions set to danger-full-access, no prior run needed, justified as instructed."},{"type":"tool-call","id":"call_00_d0sAHpJ9mYOJi0z7KNy30441","name":"bash","arguments":"{\"command\": \"printf 'escalated\\\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt\", \"description\": \"Write file outside workspace and verify\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"the user asked to write a file outside the workspace\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":1501,"outputTokens":174,"cacheReadTokens":0,"reasoningTokens":28}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127],"surfaceOp":"append"}
{"type":"tool/call","seq":129,"time":1784821261754,"data":{"turn":1,"step":1,"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","name":"bash","arguments":"{\"command\": \"printf 'escalated\\\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt\", \"description\": \"Write file outside workspace and verify\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"the user asked to write a file outside the workspace\"}"}}
{"type":"approval/asked","seq":130,"time":1784821261758,"data":{"id":"d7e97e44-02ae-486e-8fb9-17a1b080a3ed","toolName":"bash","callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","reason":"escalate sandbox to danger-full-access: the user asked to write a file outside the workspace"}}
{"type":"approval/decided","seq":131,"time":1784821261759,"data":{"id":"d7e97e44-02ae-486e-8fb9-17a1b080a3ed","outcome":"allowed-once"}}
{"type":"approval/asked","seq":130,"time":1784821261758,"data":{"id":"71b8e33b-4358-4688-a626-cae0f73cd9cb","toolName":"bash","callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","reason":"escalate sandbox to danger-full-access: the user asked to write a file outside the workspace"}}
{"type":"approval/decided","seq":131,"time":1784821261759,"data":{"id":"71b8e33b-4358-4688-a626-cae0f73cd9cb","outcome":"allowed-once"}}
{"type":"tool/result","seq":132,"time":1784821261775,"data":{"turn":1,"step":1,"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","content":[{"type":"text","text":"escalated\n"}],"isError":false},"sourceEventSeqs":[129],"surfaceOp":"append"}
{"type":"step/end","seq":133,"time":1784821261781,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":134,"time":1784821261782,"data":{"turn":1,"step":2}}
@@ -14,8 +14,8 @@
{"type":"assistant/chunk","seq":151,"time":1783860681967,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":152,"time":1784821263293,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to run a specific command with `sandbox_permissions` set to `danger-full-access` and a specific justification. They explicitly said NOT to run it without sandbox_permissions first. Let me do exactly that."},{"type":"tool-call","id":"call_00_WB1vnPomi8yr6MlcFKTj7912","name":"bash","arguments":"{\"command\": \"printf 'escalated\\\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt\", \"description\": \"Write to /tmp and verify, then clean up\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"the user asked to write a file outside the workspace\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":1509,"outputTokens":198,"cacheReadTokens":0,"reasoningTokens":48}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151],"surfaceOp":"append"}
{"type":"tool/call","seq":153,"time":1784821263294,"data":{"turn":1,"step":1,"callId":"call_00_WB1vnPomi8yr6MlcFKTj7912","name":"bash","arguments":"{\"command\": \"printf 'escalated\\\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt\", \"description\": \"Write to /tmp and verify, then clean up\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"the user asked to write a file outside the workspace\"}"}}
{"type":"approval/asked","seq":154,"time":1784821263300,"data":{"id":"f9e0a1a7-7864-4397-a1df-c1ce37f41426","toolName":"bash","callId":"call_00_WB1vnPomi8yr6MlcFKTj7912","reason":"escalate sandbox to danger-full-access: the user asked to write a file outside the workspace"}}
{"type":"approval/decided","seq":155,"time":1784821263301,"data":{"id":"f9e0a1a7-7864-4397-a1df-c1ce37f41426","outcome":"rejected"}}
{"type":"approval/asked","seq":154,"time":1784821263300,"data":{"id":"20a27d8b-d8c6-4620-b314-3d68747f68b2","toolName":"bash","callId":"call_00_WB1vnPomi8yr6MlcFKTj7912","reason":"escalate sandbox to danger-full-access: the user asked to write a file outside the workspace"}}
{"type":"approval/decided","seq":155,"time":1784821263301,"data":{"id":"20a27d8b-d8c6-4620-b314-3d68747f68b2","outcome":"rejected"}}
{"type":"tool/result","seq":156,"time":1784821263302,"data":{"turn":1,"step":1,"callId":"call_00_WB1vnPomi8yr6MlcFKTj7912","content":[{"type":"text","text":"Error: the user rejected escalating this command to \"danger-full-access\""}],"isError":true},"sourceEventSeqs":[153],"surfaceOp":"append"}
{"type":"step/end","seq":157,"time":1784821263307,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":158,"time":1784821263307,"data":{"turn":1,"step":2}}
@@ -14,8 +14,8 @@
{"type":"assistant/chunk","seq":85,"time":1784045703749,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":86,"time":1784821264893,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to create a file using the write tool with sandbox_permissions. Let me do that."},{"type":"tool-call","id":"call_00_Fnymmavpr4klMDy4Fdej3227","name":"write","arguments":"{\"file_path\": \"escalated.md\", \"content\": \"escalated\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"the user asked to escalate this write\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3871,"outputTokens":132,"cacheReadTokens":0,"reasoningTokens":23}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85],"surfaceOp":"append"}
{"type":"tool/call","seq":87,"time":1784821264893,"data":{"turn":1,"step":1,"callId":"call_00_Fnymmavpr4klMDy4Fdej3227","name":"write","arguments":"{\"file_path\": \"escalated.md\", \"content\": \"escalated\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"the user asked to escalate this write\"}"}}
{"type":"approval/asked","seq":88,"time":1784821264898,"data":{"id":"9439e616-fcb2-498e-a7ee-0488c378bccf","toolName":"write","callId":"call_00_Fnymmavpr4klMDy4Fdej3227","reason":"escalate sandbox to danger-full-access: the user asked to escalate this write"}}
{"type":"approval/decided","seq":89,"time":1784821264898,"data":{"id":"9439e616-fcb2-498e-a7ee-0488c378bccf","outcome":"allowed-once"}}
{"type":"approval/asked","seq":88,"time":1784821264898,"data":{"id":"ffef80ac-8819-4d23-943f-71cea3fdf01e","toolName":"write","callId":"call_00_Fnymmavpr4klMDy4Fdej3227","reason":"escalate sandbox to danger-full-access: the user asked to escalate this write"}}
{"type":"approval/decided","seq":89,"time":1784821264898,"data":{"id":"ffef80ac-8819-4d23-943f-71cea3fdf01e","outcome":"allowed-once"}}
{"type":"tool/result","seq":90,"time":1784821264906,"data":{"turn":1,"step":1,"callId":"call_00_Fnymmavpr4klMDy4Fdej3227","content":[{"type":"text","text":"<path>/private/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-vmEGzd/escalated.md</path>\n<type>file</type>\n<content>\nCreated file\n</content>"}],"isError":false,"meta":{"diffs":[]}},"sourceEventSeqs":[87],"surfaceOp":"append"}
{"type":"step/end","seq":91,"time":1784821264911,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":92,"time":1784821264912,"data":{"turn":1,"step":2}}
@@ -16,8 +16,8 @@
{"type":"tool/call","seq":54,"time":1783352172557,"data":{"turn":1,"step":1,"callId":"call_00_6k0oGSliVHxGSgqBmMEO4311","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Echo HELLO\"}"}}
{"type":"hook/invoked","seq":55,"time":1783352172558,"data":{"turn":1,"point":"PreToolUse","dialect":"claude","handlerId":"claude:PreToolUse:1","matcher":"bash"}}
{"type":"hook/result","seq":56,"time":1783352172573,"data":{"turn":1,"point":"PreToolUse","handlerId":"claude:PreToolUse:1","decision":"ask","exitCode":0,"durationMs":14.113374999999905}}
{"type":"approval/asked","seq":57,"time":1783962235813,"data":{"id":"5583b42d-96b8-4a43-9064-12fe05f62a24","toolName":"bash","callId":"call_00_6k0oGSliVHxGSgqBmMEO4311","reason":"bash requires manual approval in this session"}}
{"type":"approval/decided","seq":58,"time":1783962235813,"data":{"id":"5583b42d-96b8-4a43-9064-12fe05f62a24","outcome":"rejected"}}
{"type":"approval/asked","seq":57,"time":1783962235813,"data":{"id":"3c30c6d5-3b49-4e3a-9ed1-fc94f9fb39bb","toolName":"bash","callId":"call_00_6k0oGSliVHxGSgqBmMEO4311","reason":"bash requires manual approval in this session"}}
{"type":"approval/decided","seq":58,"time":1783962235813,"data":{"id":"3c30c6d5-3b49-4e3a-9ed1-fc94f9fb39bb","outcome":"rejected"}}
{"type":"tool/result","seq":59,"time":1783962235814,"data":{"turn":1,"step":1,"callId":"call_00_6k0oGSliVHxGSgqBmMEO4311","content":[{"type":"text","text":"Error: the user rejected tool \"bash\""}],"isError":true},"sourceEventSeqs":[54],"surfaceOp":"append"}
{"type":"step/end","seq":60,"time":1783962235814,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":61,"time":1783962235814,"data":{"turn":1,"step":2}}
@@ -11,7 +11,7 @@
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"call_session_query_spill","name":"session_event_read","arguments":"{\"seq\":4}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"tool/call","seq":11,"time":0,"data":{"turn":1,"step":1,"callId":"call_session_query_spill","name":"session_event_read","arguments":"{\"seq\":4}"}}
{"type":"tool/result","seq":12,"time":0,"data":{"turn":1,"step":1,"callId":"call_session_query_spill","content":[{"type":"text","text":"Session {{sessionId}} — Read request event 4 with\nTarget event seq 4:\n```json\n{\n \"type\": \"request/header\",\n \"seq\": 4,\n \"time\": 1785140680525,\n \"data\": {\n \"header\": {\n \"config\": {\n \"provider\": \"deepseek\",\n \"model\": \"deepseek-v4-flash\"\n },\n rmissions: one sentence for the user explaining why this exact file operation needs the wider access.\"\n }\n },\n \"required\": [\n \"file_path\",\n \"content\"\n ]\n }\n }\n ]\n },\n \"reason\": \"initial\"\n }\n}\n```\n\n(Omitted 36007 bytes. Full formatted result stored at: /tmp/dsh-acp-snap-035d1d054/session-5ca5c1bc368d/3127fda07f8f-session_event_read.txt. Use read with offset/limit, or grep this path to search within it.)"}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}
{"type":"tool/result","seq":12,"time":0,"data":{"turn":1,"step":1,"callId":"call_session_query_spill","content":[{"type":"text","text":"Session {{sessionId}} — Read request event 4 with\nTarget event seq 4:\n```json\n{\n \"type\": \"request/header\",\n \"seq\": 4,\n \"time\": 1785157642983,\n \"data\": {\n \"header\": {\n \"config\": {\n \"provider\": \"deepseek\",\n \"model\": \"deepseek-v4-flash\"\n },\n rmissions: one sentence for the user explaining why this exact file operation needs the wider access.\"\n }\n },\n \"required\": [\n \"file_path\",\n \"content\"\n ]\n }\n }\n ]\n },\n \"reason\": \"initial\"\n }\n}\n```\n\n(Omitted 36007 bytes. Full formatted result stored at: /tmp/dsh-acp-snap-035d1d054/session-0a508d3a5c8b/adedf3ca051a-session_event_read.txt. Use read with offset/limit, or grep this path to search within it.)"}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}
{"type":"step/end","seq":13,"time":0,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":14,"time":0,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
+2 -2
View File
@@ -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 examples/cordis-agent/README.md
README.md: 45c7d96f672a6dc7703d750f46e2fee08966adfa
README.zh.md: c9991dea8840e1c046f3e0861e65c0d84a81634a
README.md: 55970e932bc16d8361932daa9ea55af83ef73d33
README.zh.md: c2873b6de96a8b47ad8ea4fb2cf03a7501406300
+9 -9
View File
@@ -2,7 +2,7 @@
English | [中文](README.zh.md)
The self-referential harness demo: the DeepSeek V4 coding spine on the full-screen TUI plus [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md), which lets the model inspect the current DSH process, try in-memory temporary Plugins, and stop them. Temporary Plugins remain active across turns but disappear on stop, toolset unload, or DSH restart; they create no files or configuration and may affect other sessions in the process. The `ctx.fs` and `ctx.web` services are provider-only capabilities available to those Plugins. The design lives in [the toolset Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md).
The self-referential harness demo: the DeepSeek V4 coding spine on the full-screen TUI plus [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md), which lets the model inspect the current DSH process, mount in-memory temporary Plugins, and unmount them. Temporary Plugins remain active across turns but disappear on unmount, toolset unload, or DSH restart; they create no files or configuration and may affect other sessions in the process. The `ctx.fs` and `ctx.web` services are provider-only capabilities available to those Plugins. The design lives in [the toolset Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md).
## Run it
@@ -18,20 +18,20 @@ pnpm run demo:cordis acp # ACP server
The intended demo is staged — verify the listener link first, then let the agent extend itself:
```
> Try a temporary Plugin that listens to the 'agent/status' event and logs every status change, then run `echo hi` with bash.
[tool call] cordis_try({"code": "return { name: 'status-logger', apply(ctx) { ctx.on('agent/status', (agent, status) => console.log('status →', status)) } }"})
[tool result] Temporary Plugin dyn-1 is running (plugin "status-logger"; available until stopped or DSH restarts).
> Mount a temporary Plugin that listens to the 'agent/status' event and logs every status change, then run `echo hi` with bash.
[tool call] cordis_mount({"code": "return { name: 'status-logger', apply(ctx) { ctx.on('agent/status', (agent, status) => console.log('status →', status)) } }"})
[tool result] Temporary Plugin dyn-1 is running (plugin "status-logger"; available until unmounted or DSH restarts).
[tool call] bash({"command": "echo hi"})
[cordis:dyn-1] status → … ← the temporary listener firing, live
> Now give yourself a reverse_text tool and use it on "harness".
[tool call] cordis_try({"code": "return { name: 'reverse-text', inject: ['tools'], apply(ctx) { ctx.tools.register(harness.defineTool({ name: 'reverse_text', … })) } }"})
[tool call] cordis_mount({"code": "return { name: 'reverse-text', inject: ['tools'], apply(ctx) { ctx.tools.register(harness.defineTool({ name: 'reverse_text', … })) } }"})
[tool call] reverse_text({"text": "harness"}) ← a tool the agent built for itself, one step earlier
> Stop both temporary Plugins.
[tool call] cordis_stop({"id": "dyn-1"})
> Unmount both temporary Plugins.
[tool call] cordis_unmount({"id": "dyn-1"})
```
Ask for `cordis_inspect` with `what: "api"` or `what: "events"` to see the generated service/event reference used to write Plugin code, and try two cooperating temporary Plugins (`ctx.provide` in one, `inject` in the other) to watch Cordis park and revive the consumer.
Ask for `cordis_inspect` with `what: "api"` or `what: "events"` to see the generated service/event reference used to write Plugin code, and mount two cooperating temporary Plugins (`ctx.provide` in one, `inject` in the other) to watch Cordis park and revive the consumer.
## End-to-end tests
`tests/keyless-smoke.e2e.ts` boots the real `cordis.yml` through the Loader with a dummy key and asserts the banner, package-name resolution, and clean EOF exit. `tests/cordis-tools.e2e.ts` is the with-key smoke: a real model tries a temporary status listener and the test verifies its tagged console line, creates and uses a `reverse_text` tool, and composes two temporary Plugins through provide/inject. [`packages/cordis/tool-cordis`](../../packages/cordis/tool-cordis) carries the unit coverage under the per-file 100% gate.
`tests/keyless-smoke.e2e.ts` boots the real `cordis.yml` through the Loader with a dummy key and asserts the banner, package-name resolution, and clean EOF exit. `tests/cordis-tools.e2e.ts` is the with-key smoke: a real model mounts a temporary status listener and the test verifies its tagged console line, creates and uses a `reverse_text` tool, and composes two temporary Plugins through provide/inject. [`packages/cordis/tool-cordis`](../../packages/cordis/tool-cordis) carries the unit coverage under the per-file 100% gate.
+9 -9
View File
@@ -2,7 +2,7 @@
[English](README.md) | 中文
自指 harness 演示:在全屏 TUI 上运行 DeepSeek V4 编码主干,并加载 [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md)。后者让模型检查当前 DSH 进程、尝试仅存于内存的临时 Plugin,并再次停止它们。临时 Plugin 可跨 turn 保持活跃,但会在 stop、工具集卸载或 DSH 重启后消失;它们不创建文件或配置,也可能影响同一进程中的其他 session。`ctx.fs``ctx.web` 是这些 Plugin 可用的 provider-only 能力。设计详见[工具集 Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。
自指 harness 演示:在全屏 TUI 上运行 DeepSeek V4 编码主干,并加载 [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md)。后者让模型检查当前 DSH 进程、挂载仅存于内存的临时 Plugin,并再次卸载它们。临时 Plugin 可跨 turn 保持活跃,但会在卸载、工具集卸载或 DSH 重启后消失;它们不创建文件或配置,也可能影响同一进程中的其他 session。`ctx.fs``ctx.web` 是这些 Plugin 可用的 provider-only 能力。设计详见[工具集 Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。
## 运行
@@ -18,20 +18,20 @@ pnpm run demo:cordis acp # ACP server
预期演示分阶段进行:先验证监听器链接,再让 agent 扩展自身:
```
> Try a temporary Plugin that listens to the 'agent/status' event and logs every status change, then run `echo hi` with bash.
[tool call] cordis_try({"code": "return { name: 'status-logger', apply(ctx) { ctx.on('agent/status', (agent, status) => console.log('status →', status)) } }"})
[tool result] Temporary Plugin dyn-1 is running (plugin "status-logger"; available until stopped or DSH restarts).
> Mount a temporary Plugin that listens to the 'agent/status' event and logs every status change, then run `echo hi` with bash.
[tool call] cordis_mount({"code": "return { name: 'status-logger', apply(ctx) { ctx.on('agent/status', (agent, status) => console.log('status →', status)) } }"})
[tool result] Temporary Plugin dyn-1 is running (plugin "status-logger"; available until unmounted or DSH restarts).
[tool call] bash({"command": "echo hi"})
[cordis:dyn-1] status → … ← the temporary listener firing, live
> Now give yourself a reverse_text tool and use it on "harness".
[tool call] cordis_try({"code": "return { name: 'reverse-text', inject: ['tools'], apply(ctx) { ctx.tools.register(harness.defineTool({ name: 'reverse_text', … })) } }"})
[tool call] cordis_mount({"code": "return { name: 'reverse-text', inject: ['tools'], apply(ctx) { ctx.tools.register(harness.defineTool({ name: 'reverse_text', … })) } }"})
[tool call] reverse_text({"text": "harness"}) ← a tool the agent built for itself, one step earlier
> Stop both temporary Plugins.
[tool call] cordis_stop({"id": "dyn-1"})
> Unmount both temporary Plugins.
[tool call] cordis_unmount({"id": "dyn-1"})
```
请求 `cordis_inspect` 并使用 `what: "api"``what: "events"`,即可查看编写 Plugin 代码所用的生成服务/事件资料。还可尝试两个协作临时 Plugin(一个中调用 `ctx.provide`,另一个中使用 `inject`),观察 Cordis 如何暂停并恢复消费方。
请求 `cordis_inspect` 并使用 `what: "api"``what: "events"`,即可查看编写 Plugin 代码所用的生成服务/事件资料。还可挂载两个协作临时 Plugin(一个中调用 `ctx.provide`,另一个中使用 `inject`),观察 Cordis 如何暂停并恢复消费方。
## 端到端测试
`tests/keyless-smoke.e2e.ts` 使用虚拟密钥通过 Loader 启动真实 `cordis.yml`,并断言横幅、包名解析和 EOF 后干净退出。`tests/cordis-tools.e2e.ts` 是带密钥的冒烟测试:真实模型尝试一个临时状态 listener,测试验证其带标记的 console 行;然后创建并使用 `reverse_text` 工具,再通过 provide/inject 组合两个临时 Plugin。[`packages/cordis/tool-cordis`](../../packages/cordis/tool-cordis) 在每文件 100% 覆盖率门禁下承载单元覆盖。
`tests/keyless-smoke.e2e.ts` 使用虚拟密钥通过 Loader 启动真实 `cordis.yml`,并断言横幅、包名解析和 EOF 后干净退出。`tests/cordis-tools.e2e.ts` 是带密钥的冒烟测试:真实模型挂载一个临时状态 listener,测试验证其带标记的 console 行;然后创建并使用 `reverse_text` 工具,再通过 provide/inject 组合两个临时 Plugin。[`packages/cordis/tool-cordis`](../../packages/cordis/tool-cordis) 在每文件 100% 覆盖率门禁下承载单元覆盖。
+1 -1
View File
@@ -3,7 +3,7 @@
# Cordis Agent App Composition
The self-referential demo puts @deepseek-ai/dsh-tool-cordis on the coding spine, letting the agent inspect its current-process runtime and try or stop in-memory temporary Plugins.
The self-referential demo puts @deepseek-ai/dsh-tool-cordis on the coding spine, letting the agent inspect its current-process runtime and mount or unmount in-memory temporary Plugins.
```mermaid
flowchart LR
+7 -7
View File
@@ -1,6 +1,6 @@
# Self-referential TUI demo: the coding spine plus tools to inspect the live
# service/plugin/tool/temporary/API/event state, try a model-written temporary
# Plugin, and quiescently stop it. The app bin loads the gitignored
# service/plugin/tool/temporary/API/event state, mount a model-written temporary
# Plugin, and quiescently unmount it. The app bin loads the gitignored
# root `.env` before reading the required DeepSeek key and optional base URL.
# Trust stance: the vm and context façade limit accidental global/framework
# access but are not a security boundary; temporary Plugin code reaches live capabilities
@@ -60,7 +60,7 @@
persistenceRoot: './.sessions'
workspaceContext:
maxBytes: 65536
welcome: 'cordis-agent ready. Ask it to inspect its runtime, try a temporary listener, or invent a temporary tool for itself.'
welcome: 'cordis-agent ready. Ask it to inspect its runtime, mount a temporary listener, or invent a temporary tool for itself.'
persona: |
You are cordis-agent, a self-referential harness demo powered by the
{{model}} model.
@@ -68,15 +68,15 @@
You run INSIDE a cordis plugin runtime, and your cordis_* tools operate
on that live runtime: cordis_inspect to look around (its `api` and
`events` sections document the service methods, type shapes, and events
your Plugin code can use), cordis_try to try an in-memory temporary
your Plugin code can use), cordis_mount to mount an in-memory temporary
Plugin (an event listener, a brand-new tool for yourself, or a service
another temporary Plugin injects), cordis_stop to clean one up. These
Plugins remain across turns but disappear on stop, toolset unload, or
another temporary Plugin injects), cordis_unmount to clean one up. These
Plugins remain across turns but disappear on unmount, toolset unload, or
DSH restart and may affect other sessions in this process. In Plugin code, NEVER use Node
built-ins (require/setTimeout/fetch) — use the runtime's cordis services
via inject: fs, web, bash, and timer (ctx.setTimeout). Prefer small
single-purpose plugins, prefer plain notification events over waterfall
events unless you intend to intercept, and stop what you no longer
events unless you intend to intercept, and unmount what you no longer
need. Report results briefly.
# The self-referential cordis toolset (loaded after the app so ctx.tools exists).
+10 -10
View File
@@ -8,7 +8,7 @@ const testToolSignal = new AbortController().signal
/**
* With-key smoke for the self-referential cordis tools: a REAL model drives
* cordis_try/cordis_stop against the live context the test observes.
* cordis_mount/cordis_unmount against the live context the test observes.
* World-verified, not self-reported: the mounted listener must actually WRITE
* its tagged console line, the self-made tool must actually EXIST in the
* registry and appear as a real `tool/call`, the cross-mount service must
@@ -37,14 +37,14 @@ function resultText(result: { content: { type: string; text?: string }[] }): str
}
describe.skipIf(!process.env.DEEPSEEK_API_KEY)('cordis tools: a real model modifies its own runtime', () => {
it('tries a temporary status listener whose tagged output actually fires, then stops it', async () => {
it('mounts a temporary status listener whose tagged output actually fires, then unmounts it', async () => {
ctx = await cordisHarness()
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
const agent = ctx.agentLoop.create(SessionId('cordis-e2e-listener'), { provider: 'deepseek', model: 'deepseek-v4-flash' })
agent.followup([{
type: 'text',
text: 'Use cordis_try to create a temporary Plugin that listens to the \'agent/status\' '
text: 'Use cordis_mount to create a temporary Plugin that listens to the \'agent/status\' '
+ 'Cordis event and logs every change with console.log. Reply "running" once done.',
}])
await waitForIdle(ctx, agent)
@@ -58,7 +58,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('cordis tools: a real model modif
})
expect(resultText(mid)).toContain('dyn-')
agent.followup([{ type: 'text', text: 'Now stop the temporary Plugin you just tried.' }])
agent.followup([{ type: 'text', text: 'Now unmount the temporary Plugin you just mounted.' }])
await waitForIdle(ctx, agent)
const after = await ctx.tools.execute({
@@ -74,7 +74,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('cordis tools: a real model modif
agent.followup([{
type: 'text',
text: 'Give yourself a new tool: use cordis_try to create a temporary Plugin with '
text: 'Give yourself a new tool: use cordis_mount to create a temporary Plugin with '
+ 'inject ["tools"] that calls harness.registerTool(ctx, harness.defineTool({...})) '
+ 'to register a tool named reverse_text with one required string parameter '
+ '"text", returning the text reversed. Then CALL reverse_text with the '
@@ -88,7 +88,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('cordis tools: a real model modif
expect(ctx.tools.get('reverse_text')).toBeDefined()
const events = [...agent.session.events]
const calls = events.filter(event => event.type === 'tool/call')
expect(calls.some(event => event.data.name === 'cordis_try')).toBe(true)
expect(calls.some(event => event.data.name === 'cordis_mount')).toBe(true)
const reverseCalls = calls.filter(event => event.data.name === 'reverse_text')
expect(reverseCalls.length).toBeGreaterThan(0)
const reverseResults = events
@@ -98,7 +98,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('cordis tools: a real model modif
// On failure, surface what the model actually mounted and what the tool
// returned — an e2e failing at a distance is undebuggable without it.
const mountCode = calls
.filter(event => event.data.name === 'cordis_try')
.filter(event => event.data.name === 'cordis_mount')
.map(event => event.data.arguments)
.join('\n---\n')
const trace = events.map((event) => {
@@ -115,13 +115,13 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('cordis tools: a real model modif
).toBe(true)
}, 120_000)
it('composes two temporary Plugins through provide/inject, and stopping the provider parks the consumer', async () => {
it('composes two temporary Plugins through provide/inject, and unmounting the provider parks the consumer', async () => {
ctx = await cordisHarness()
const agent = ctx.agentLoop.create(SessionId('cordis-e2e-compose'), { provider: 'deepseek', model: 'deepseek-v4-flash' })
agent.followup([{
type: 'text',
text: 'Try TWO separate temporary Plugins with cordis_try. First a provider: apply calls '
text: 'Mount TWO separate temporary Plugins with cordis_mount. First a provider: apply calls '
+ 'ctx.provide(\'shouter\', { shout: (s) => s.toUpperCase() }). Second a consumer with '
+ 'inject ["shouter", "tools"] that registers (via harness.registerTool + harness.defineTool) '
+ 'a tool named shout_text with one required string parameter "text" whose execute returns '
@@ -144,7 +144,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('cordis tools: a real model modif
.flatMap(event => event.data.content.filter(block => block.type === 'text').map(block => block.text))
expect(shoutResults.some(text => text.includes('QUIET'))).toBe(true)
agent.followup([{ type: 'text', text: 'Now stop ONLY the provider temporary Plugin (the one that provided shouter).' }])
agent.followup([{ type: 'text', text: 'Now unmount ONLY the provider temporary Plugin (the one that provided shouter).' }])
await waitForIdle(ctx, agent)
// The consumer must have been parked by cordis itself: service gone,
+2 -2
View File
@@ -15,8 +15,8 @@ import * as ToolCordis from '@deepseek-ai/dsh-tool-cordis'
const PERSONA = 'You are cordis-agent, a self-referential harness demo. '
+ 'Your cordis_* tools operate on the live cordis runtime you run inside: '
+ 'cordis_inspect to look around, cordis_try to try a temporary Plugin, cordis_stop '
+ 'to stop one. Follow the tool descriptions exactly and report results briefly.'
+ 'cordis_inspect to look around, cordis_mount to mount a temporary Plugin, cordis_unmount '
+ 'to unmount one. Follow the tool descriptions exactly and report results briefly.'
export async function cordisHarness(): Promise<Context> {
const ctx = new Context()
@@ -248,21 +248,21 @@ describe('Code Mode typed values: keyless real-worker contracts', () => {
expect(ctx.tasks.list()).toEqual([])
}, 15_000)
it('uses cordis_try DTO ids directly for running and pending temporary Plugins, then confirms removal', async () => {
it('uses cordis_mount DTO ids directly for running and pending temporary Plugins, then confirms removal', async () => {
ctx = await typedCodeModeHarness()
await ctx.plugin(ToolCordis)
const value = completion(await runCode(ctx, `
const active = await tools.cordis_try({
const active = await tools.cordis_mount({
code: "return { name: 'active-code-mode-plugin', apply(ctx) {} }",
});
const pending = await tools.cordis_try({
const pending = await tools.cordis_mount({
code: "return { name: 'pending-code-mode-plugin', inject: ['missing-code-mode-service'], apply(ctx) {} }",
});
const before = await tools.cordis_inspect({ what: 'temporary' });
const stopped = await tools.cordis_stop({ id: active.id });
const stopped = await tools.cordis_unmount({ id: active.id });
const after = await tools.cordis_inspect({ what: 'temporary' });
await tools.cordis_stop({ id: pending.id });
await tools.cordis_unmount({ id: pending.id });
return {
active,
pending,
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,13 +4,13 @@
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_try","argumentsDelta":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_try","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_mount","argumentsDelta":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_try","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":11,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_try","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":12,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until stopped or DSH restarts)."}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":11,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":12,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until unmounted or DSH restarts)."}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":13,"time":0,"data":{"turn":1,"step":1}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":14,"time":0,"data":{"turn":1,"step":2}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}
@@ -21,8 +21,8 @@
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":20,"time":0,"data":{"turn":1,"step":2,"content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[15,16,17,18,19],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":21,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/code-dispatch-start","seq":22,"time":0,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_inspect","arguments":{"what":"temporary"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/code-dispatch","seq":23,"time":0,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_inspect","arguments":{"what":"temporary"},"isError":false,"content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until stopped or DSH restarts"}]}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":24,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until stopped or DSH restarts"}],"isError":false},"sourceEventSeqs":[21],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/code-dispatch","seq":23,"time":0,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_inspect","arguments":{"what":"temporary"},"isError":false,"content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until unmounted or DSH restarts"}]}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":24,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until unmounted or DSH restarts"}],"isError":false},"sourceEventSeqs":[21],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":25,"time":0,"data":{"turn":1,"step":2}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":26,"time":0,"data":{"turn":1,"step":3}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":27,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}
@@ -46,13 +46,13 @@
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":45,"time":0,"data":{"turn":1,"step":4}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":46,"time":0,"data":{"turn":1,"step":5}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":47,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":48,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-unmount","name":"cordis_stop","argumentsDelta":"{\"id\":\"dyn-1\"}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":49,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\":\"dyn-1\"}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":48,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-unmount","name":"cordis_unmount","argumentsDelta":"{\"id\":\"dyn-1\"}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":49,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":50,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":51,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":52,"time":0,"data":{"turn":1,"step":5,"content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\":\"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[47,48,49,50,51],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":53,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\":\"dyn-1\"}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":54,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was stopped and removed."}],"isError":false},"sourceEventSeqs":[53],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":52,"time":0,"data":{"turn":1,"step":5,"content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[47,48,49,50,51],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":53,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":54,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false},"sourceEventSeqs":[53],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":55,"time":0,"data":{"turn":1,"step":5}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":56,"time":0,"data":{"turn":1,"step":6}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":57,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}}
@@ -4,13 +4,13 @@
{"type":"step/start","seq":2,"time":1783957884486,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":3,"time":1783957884486,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"assistant/chunk","seq":4,"time":1783950000005,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":5,"time":1783950000006,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_try","argumentsDelta":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}
{"type":"assistant/chunk","seq":6,"time":1783950000007,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_try","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}}
{"type":"assistant/chunk","seq":5,"time":1783950000006,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_mount","argumentsDelta":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}
{"type":"assistant/chunk","seq":6,"time":1783950000007,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}}
{"type":"assistant/chunk","seq":7,"time":1783950000008,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":8,"time":1783950000009,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":9,"time":1783957884487,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_try","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[4,5,6,7,8],"surfaceOp":"append"}
{"type":"tool/call","seq":10,"time":1783957884487,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_try","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}
{"type":"tool/result","seq":11,"time":1783957884488,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until stopped or DSH restarts)."}],"isError":false},"sourceEventSeqs":[10],"surfaceOp":"append"}
{"type":"assistant/message","seq":9,"time":1783957884487,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[4,5,6,7,8],"surfaceOp":"append"}
{"type":"tool/call","seq":10,"time":1783957884487,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}
{"type":"tool/result","seq":11,"time":1783957884488,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until unmounted or DSH restarts)."}],"isError":false},"sourceEventSeqs":[10],"surfaceOp":"append"}
{"type":"step/end","seq":12,"time":1783957884489,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":13,"time":1783957884489,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":14,"time":1783950000015,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
@@ -45,13 +45,13 @@
{"type":"step/end","seq":43,"time":1783957884718,"data":{"turn":1,"step":4}}
{"type":"step/start","seq":44,"time":1783957884718,"data":{"turn":1,"step":5}}
{"type":"assistant/chunk","seq":45,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":46,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-unmount","name":"cordis_stop","argumentsDelta":"{\"id\":\"dyn-1\"}"}}}
{"type":"assistant/chunk","seq":47,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\":\"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":46,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-unmount","name":"cordis_unmount","argumentsDelta":"{\"id\":\"dyn-1\"}"}}}
{"type":"assistant/chunk","seq":47,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":48,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":49,"time":1783957884719,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":50,"time":1783957884719,"data":{"turn":1,"step":5,"content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\":\"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[45,46,47,48,49],"surfaceOp":"append"}
{"type":"tool/call","seq":51,"time":1783957884719,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\":\"dyn-1\"}"}}
{"type":"tool/result","seq":52,"time":1783957884719,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was stopped and removed."}],"isError":false},"sourceEventSeqs":[51],"surfaceOp":"append"}
{"type":"assistant/message","seq":50,"time":1783957884719,"data":{"turn":1,"step":5,"content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[45,46,47,48,49],"surfaceOp":"append"}
{"type":"tool/call","seq":51,"time":1783957884719,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}}
{"type":"tool/result","seq":52,"time":1783957884719,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false},"sourceEventSeqs":[51],"surfaceOp":"append"}
{"type":"step/end","seq":53,"time":1783957884719,"data":{"turn":1,"step":5}}
{"type":"step/start","seq":54,"time":1783957884720,"data":{"turn":1,"step":6}}
{"type":"assistant/chunk","seq":55,"time":1783957884720,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
@@ -29,11 +29,11 @@ buffer
11| <blank>
12| "▌ "
style 0-0 fg=green
13| "▌ ✓ Try temporary Cordis Plugin "
13| "▌ ✓ Mount temporary Cordis Plugin "
style 0-0 fg=green
style 2-2 fg=green bold
style 3-30 bold
14| "▌ Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until stopped or DSH "
style 3-32 bold
14| "▌ Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until unmounted or DSH "
style 0-0 fg=green
15| "▌ restarts). "
style 0-0 fg=green
@@ -50,7 +50,7 @@ buffer
style 0-0 fg=green
21| "▌ - Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime:"
style 0-0 fg=green
22| "▌ until stopped or DSH restarts "
22| "▌ until unmounted or DSH restarts "
style 0-0 fg=green
23| "▌ "
style 0-0 fg=green
@@ -87,11 +87,11 @@ buffer
38| <blank>
39| "▌ "
style 0-0 fg=green
40| "▌ ✓ Stop temporary Cordis Plugin dyn-1 "
40| "▌ ✓ Unmount temporary Cordis Plugin dyn-1 "
style 0-0 fg=green
style 2-2 fg=green bold
style 3-37 bold
41| "▌ Temporary Plugin dyn-1 was stopped and removed. "
style 3-40 bold
41| "▌ Temporary Plugin dyn-1 was unmounted and removed. "
style 0-0 fg=green
42| "▌ "
style 0-0 fg=green
+1 -1
View File
@@ -122,7 +122,7 @@ const SCENARIOS: Scenario[] = [
{
name: 'cordis-dynamic-toolchain',
composition: 'advanced',
expectedTools: ['cordis_try', 'run_code', 'subagent', 'workflow', 'cordis_stop'],
expectedTools: ['cordis_mount', 'run_code', 'subagent', 'workflow', 'cordis_unmount'],
expectedEventCounts: { 'tool/code-dispatch': 1 },
childSessions: 2,
recorded: false,
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/ui-conversation/README.md
README.md: 4e12b82070bd27a0724cc3e43e952a4221339f49
README.zh.md: b4936501b68e110ff538fec28bb52a3a72d7b849
README.md: 32651291253077098bc43a930cf4ce11d29b1ed8
README.zh.md: ea8f398541d7af6136b29c3365a78c4ea3a1e85d
+1 -1
View File
@@ -8,7 +8,7 @@ The no-session hero renders the frontend Session Intent from the Session list pr
The view ring IS a slot: the conversation registration declares the `'conversation.view'` list slot (session scope) in its `children` table, ConversationRoot renders the active entry through its renderSlot share (`only: <active id>`), and view tabs project from the ring ledger's registration options (`id`/`order`/`label`). The chat view is this package's own ring entry; other plugins (ui-trajectory) contribute tabs through plain `ctx.slots.register` — the former package-local view registry (`registerView`/`ViewEntry`/`ConversationViewMap` and the chrome attachment table) is retired, with per-view chrome dissolved into the view components themselves.
Generic tool rows classify the built-in bash, read, search, write, edit, and run_code names into dedicated visual variants. The filesystem variants render the edit icon and `Write · <path>` or `Edit · <path>` summary while retaining the shared row-to-details interaction. The code variant summarizes with the model-authored `description` and expands to the program itself; its logged sub-dispatches render as always-visible nested rows through the SAME keyed toolview hole (custom registrations and the GenericToolCard fallback apply to sub-rows unchanged), and the details panel resolves a selected sub-call id to its full logged args and complete output. Cordis lifecycle tools reuse those generic variants while presenting `Inspect`, `Try temporary Plugin`, and `Stop temporary Plugin` with a shared Cordis accent; try keeps the code variant's expandable source rendering.
Generic tool rows classify the built-in bash, read, search, write, edit, and run_code names into dedicated visual variants. The filesystem variants render the edit icon and `Write · <path>` or `Edit · <path>` summary while retaining the shared row-to-details interaction. The code variant summarizes with the model-authored `description` and expands to the program itself; its logged sub-dispatches render as always-visible nested rows through the SAME keyed toolview hole (custom registrations and the GenericToolCard fallback apply to sub-rows unchanged), and the details panel resolves a selected sub-call id to its full logged args and complete output. Cordis lifecycle tools reuse those generic variants while presenting `Inspect`, `Mount temporary Plugin`, and `Unmount temporary Plugin` with a shared Cordis accent; mount keeps the code variant's expandable source rendering.
Tool rows are slots too — the standalone tool ring (`ToolViewRegistry`/`ctx.toolviews`/outlet) is retired. The chat entry declares the keyed `'conversation.chat.toolview'` hole (session scope; the key space is runtime-open); its render site dispatches per row via `entryKey: toolName` with `GenericToolCard` as the call-site `fallback`. The owner payload is the uniform `ToolRowOwnerProps` (`callId`/`toolName`/`block`/`openDetails`) and `ToolRowProps` pre-composes it with the session standard kit. A registrant is a plain plugin: `ctx.slots.register({ name: 'conversation.chat.toolview', key: '<tool>', inject? }, Row)` with `inject: ['slots', 'conversation']` as the load-order seam (apply mounts ConversationService after the chat registration, so the service being present guarantees the slot is declared); session differentiation happens inside the component (`useSessions` reading `parentId` — the bash sample is the third-party-posture exemplar). Trajectory/waterfall toolview slots share this shape and land with their own render sites (RendersCheck rejects a declaration nobody renders).
+1 -1
View File
@@ -8,7 +8,7 @@
视图环本身就是 slot:会话注册声明 `'conversation.view'` 列表 slotSession scope),并将其列在 `children` 表中;ConversationRoot 通过 renderSlot share 渲染活跃配置项(`only: <active id>`);视图标签页从环账本的注册选项(`id``order``label`)投影而来。聊天视图是该包自身的环配置项;其他插件(ui-trajectory)通过普通的 `ctx.slots.register` 贡献标签页。先前包内的视图注册表(`registerView``ViewEntry``ConversationViewMap` 及 chrome 附加表)已退役,逐视图 chrome 则被拆入视图组件自身。
通用工具行把内置的 bash、read、search、write、edit 和 run_code 名称归入专用视觉变体。文件系统变体会渲染 edit 图标和 `Write · <path>``Edit · <path>` 摘要,同时保留共享的行到详情交互。code 变体以模型撰写的 `description` 作摘要,展开后显示程序本身;其已记录的子调用经由同一个键控 toolview 空位渲染为始终可见的嵌套行(自定义注册和 GenericToolCard fallback 原样适用于子行),details 面板则会根据选中的子调用 id 解析出其完整记录的参数与完整输出。Cordis 生命周期工具复用这些通用变体,同时以统一的 Cordis 强调色呈现 `Inspect``Try temporary Plugin``Stop temporary Plugin`try 行保留 code 变体的可展开源码渲染。
通用工具行把内置的 bash、read、search、write、edit 和 run_code 名称归入专用视觉变体。文件系统变体会渲染 edit 图标和 `Write · <path>``Edit · <path>` 摘要,同时保留共享的行到详情交互。code 变体以模型撰写的 `description` 作摘要,展开后显示程序本身;其已记录的子调用经由同一个键控 toolview 空位渲染为始终可见的嵌套行(自定义注册和 GenericToolCard fallback 原样适用于子行),details 面板则会根据选中的子调用 id 解析出其完整记录的参数与完整输出。Cordis 生命周期工具复用这些通用变体,同时以统一的 Cordis 强调色呈现 `Inspect``Mount temporary Plugin``Unmount temporary Plugin`mount 行保留 code 变体的可展开源码渲染。
工具行同样是 slot:独立工具环(`ToolViewRegistry``ctx.toolviews`/outlet)已经退役。聊天配置项声明键控的 `'conversation.chat.toolview'` 空位(Session scope;key 空间在运行时开放);其渲染点逐行通过 `entryKey: toolName` 分发,并以 `GenericToolCard` 作为调用点 `fallback`。owner 载荷是统一的 `ToolRowOwnerProps``callId``toolName``block``openDetails`),`ToolRowProps` 则预先将其与 Session 标准工具包组合。注册方只是普通插件:`ctx.slots.register({ name: 'conversation.chat.toolview', key: '<tool>', inject? }, Row)`,以 `inject: ['slots', 'conversation']` 作为加载顺序 seam(apply 在聊天注册后挂载 ConversationService,因此服务存在即可保证 slot 已声明);Session 区分在组件内部完成(`useSessions` 读取 `parentId`,bash 示例是第三方姿态的范例)。Trajectory/waterfall 工具视图 slot 共享此形状,并随各自的渲染点落地(RendersCheck 会拒绝没有任何渲染方的声明)。
@@ -37,15 +37,15 @@ const TOOL_VARIANTS: Record<string, ToolRowVariant> = {
edit: 'edit',
run_code: 'code',
cordis_inspect: 'read',
cordis_try: 'code',
cordis_stop: 'others',
cordis_mount: 'code',
cordis_unmount: 'others',
}
/** Tool-owned titles that refine a generic row variant without replacing it. */
const TOOL_TITLES: Record<string, string> = {
cordis_inspect: 'Inspect',
cordis_try: 'Try temporary Plugin',
cordis_stop: 'Stop temporary Plugin',
cordis_mount: 'Mount temporary Plugin',
cordis_unmount: 'Unmount temporary Plugin',
}
/**
@@ -172,21 +172,21 @@ describe('run_code sub-calls through the real chat machinery', () => {
const code = 'return { name: "audit", apply(ctx) {} }'
const dispatches = new Map([[parent, [
subCall(11, parent, 1, 'cordis_inspect', { what: 'temporary' }, '## Temporary Plugins'),
subCall(12, parent, 2, 'cordis_try', { code }, 'Temporary Plugin dyn-2 is running'),
subCall(13, parent, 3, 'cordis_stop', { id: 'dyn-2' }, 'Temporary Plugin dyn-2 was stopped and removed.'),
subCall(12, parent, 2, 'cordis_mount', { code }, 'Temporary Plugin dyn-2 is running'),
subCall(13, parent, 3, 'cordis_unmount', { id: 'dyn-2' }, 'Temporary Plugin dyn-2 was unmounted and removed.'),
]]])
const b = await bench(snapshotWith([codeResult(10, parent)], dispatches))
const view = mountApp(b.slots)
const nest = view.container.querySelector('[data-subcalls]')!
expect(nest.querySelector('[data-tool="cordis_inspect"]')?.textContent).toContain('Inspect')
const tried = nest.querySelector('[data-variant="code"]')
expect(tried?.textContent).toContain(`Try temporary Plugin${code}`)
expect(nest.querySelector('[data-tool="cordis_stop"]')?.textContent)
.toContain('Stop temporary Plugindyn-2')
const mounted = nest.querySelector('[data-variant="code"]')
expect(mounted?.textContent).toContain(`Mount temporary Plugin${code}`)
expect(nest.querySelector('[data-tool="cordis_unmount"]')?.textContent)
.toContain('Unmount temporary Plugindyn-2')
fireEvent.click(tried!.querySelector('button[aria-expanded]')!)
expect(tried!.querySelector('pre.shiki')?.textContent).toBe(code)
fireEvent.click(mounted!.querySelector('button[aria-expanded]')!)
expect(mounted!.querySelector('pre.shiki')?.textContent).toBe(code)
})
it('expanding the code row reveals the program body verbatim (shiki-tokenized)', async () => {
@@ -32,8 +32,8 @@ describe('tool-call-model', () => {
expect(classifyTool('write')).toBe('write')
expect(classifyTool('edit')).toBe('edit')
expect(classifyTool('cordis_inspect')).toBe('read')
expect(classifyTool('cordis_try')).toBe('code')
expect(classifyTool('cordis_stop')).toBe('others')
expect(classifyTool('cordis_mount')).toBe('code')
expect(classifyTool('cordis_unmount')).toBe('others')
expect(classifyTool('todo_write')).toBe('others')
})
@@ -80,20 +80,20 @@ describe('tool-call-model', () => {
title: 'Inspect',
summary: 'api',
})
expect(toolRowModel('cordis_try', running({
name: 'cordis_try',
expect(toolRowModel('cordis_mount', running({
name: 'cordis_mount',
argsRaw: '{"code":"return { name: \\"audit\\", apply(ctx) {} }"}',
}))).toMatchObject({
variant: 'code',
title: 'Try temporary Plugin',
title: 'Mount temporary Plugin',
summary: 'return { name: "audit", apply(ctx) {} }',
body: 'return { name: "audit", apply(ctx) {} }',
})
expect(toolRowModel('cordis_stop', result({
call: { name: 'cordis_stop', argsRaw: '{"id":"dyn-2"}' },
expect(toolRowModel('cordis_unmount', result({
call: { name: 'cordis_unmount', argsRaw: '{"id":"dyn-2"}' },
}))).toMatchObject({
variant: 'others',
title: 'Stop temporary Plugin',
title: 'Unmount temporary Plugin',
summary: 'dyn-2',
})
})
@@ -166,19 +166,19 @@ describe('keyed toolview hole through the real machinery', () => {
const code = 'return { name: "audit", apply(ctx) {} }'
const b = await bench([
toolResult(3, 'cordis-1', 'cordis_inspect', '{"what":"api","name":"tools"}'),
toolResult(4, 'cordis-2', 'cordis_try', JSON.stringify({ code })),
toolResult(5, 'cordis-3', 'cordis_stop', '{"id":"dyn-2"}'),
toolResult(4, 'cordis-2', 'cordis_mount', JSON.stringify({ code })),
toolResult(5, 'cordis-3', 'cordis_unmount', '{"id":"dyn-2"}'),
])
const view = mountApp(b.slots)
expect(view.container.querySelector('[data-tool="cordis_inspect"]')?.textContent).toContain('Inspect')
const tried = view.container.querySelector('[data-variant="code"]')
expect(tried?.textContent).toContain(`Try temporary Plugin${code}`)
expect(view.container.querySelector('[data-tool="cordis_stop"]')?.textContent)
.toContain('Stop temporary Plugindyn-2')
const mounted = view.container.querySelector('[data-variant="code"]')
expect(mounted?.textContent).toContain(`Mount temporary Plugin${code}`)
expect(view.container.querySelector('[data-tool="cordis_unmount"]')?.textContent)
.toContain('Unmount temporary Plugindyn-2')
fireEvent.click(tried!.querySelector('button[aria-expanded]')!)
expect(tried!.querySelector('pre.shiki')?.textContent).toBe(code)
fireEvent.click(mounted!.querySelector('button[aria-expanded]')!)
expect(mounted!.querySelector('pre.shiki')?.textContent).toBe(code)
})
it('row clicks travel owner openDetails → chat inject → layout orchestration', async () => {
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/cordis/README.md
README.md: bcd11230cdaaaf2893bcc64eadfe689855bc3553
README.zh.md: c26424f08dced26aa60d0a21202449e1b3b16860
README.md: a47b9ba20789bb6b9a36b1af9b3942b90e61b365
README.zh.md: cc91e68f0dfa9fb343c332eba2579c2a077beb64
+1 -1
View File
@@ -6,4 +6,4 @@ Model-facing tools over the live cordis runtime the agent itself runs inside: in
| Package | Role | ctx key |
|---|---|---|
| [`tool-cordis/`](tool-cordis/README.md) | The `cordis_inspect` / `cordis_try` / `cordis_stop` tools: read the current-process runtime and manage in-memory temporary Plugins under one owned group fiber | registers on `ctx.tools` |
| [`tool-cordis/`](tool-cordis/README.md) | The `cordis_inspect` / `cordis_mount` / `cordis_unmount` tools: read the current-process runtime and manage in-memory temporary Plugins under one owned group fiber | registers on `ctx.tools` |
+2 -2
View File
@@ -2,8 +2,8 @@
[English](README.md) | 中文
面向模型、作用于 agent(智能体)所在实时 Cordis 运行时的工具:检查当前 DSH 进程,并尝试或停止仅存于内存的临时 Plugin。设计归档见[工具集 Agent Noteagent 决策记录)](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。
面向模型、作用于 agent(智能体)所在实时 Cordis 运行时的工具:检查当前 DSH 进程,并挂载或卸载仅存于内存的临时 Plugin。设计归档见[工具集 Agent Noteagent 决策记录)](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。
| 包(package | 角色 | ctx 键 |
|---|---|---|
| [`tool-cordis/`](tool-cordis/README.md) | `cordis_inspect``cordis_try``cordis_stop` 工具:读取当前进程运行时,并在一个自有分组 fiber 下管理临时 Plugin | 注册到 `ctx.tools` |
| [`tool-cordis/`](tool-cordis/README.md) | `cordis_inspect``cordis_mount``cordis_unmount` 工具:读取当前进程运行时,并在一个自有分组 fiber 下管理临时 Plugin | 注册到 `ctx.tools` |
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/cordis/tool-cordis/README.md
README.md: fda296817026556f235d42626e87fe1f361f2c36
README.zh.md: b8e3c02ba43a7c366664f5964168798ee7356186
README.md: 5b58e665dae95aea0d0ad094238fef5d3dc0fb97
README.zh.md: 11e5be11dca84247b19888fefa7d70e5d74da174
+11 -11
View File
@@ -6,15 +6,15 @@ The self-referential Cordis toolset: three model-facing tools over the live runt
## What it does
- `cordis_inspect` — read-only report over the current process: services, all live plugin fibers, registered tools, the `cordis_try` temporary-Plugin subset, and the catalog-backed `api` / `events` references. An exact `name` with `what: "api"` or `what: "events"` narrows the report and adds the original source JSDoc.
- `cordis_try` — evaluates model-written JavaScript now and saves it nowhere; the code must return an in-memory temporary Plugin tracked as `dyn-<n>`.
- `cordis_stop` — stops one `dyn-<n>` temporary Plugin and returns only after its owned effects reach quiescence. It cannot remove Loader, configured, or installed Plugins.
- `cordis_inspect` — read-only report over the current process: services, all live plugin fibers, registered tools, the `cordis_mount` temporary-Plugin subset, and the catalog-backed `api` / `events` references. An exact `name` with `what: "api"` or `what: "events"` narrows the report and adds the original source JSDoc.
- `cordis_mount` — evaluates model-written JavaScript now and saves it nowhere; the code must return an in-memory temporary Plugin tracked as `dyn-<n>`.
- `cordis_unmount` — unmounts one `dyn-<n>` temporary Plugin and returns only after its owned effects reach quiescence. It cannot remove Loader, configured, or installed Plugins.
Exact model-facing schemas: [the generated tool catalog](../../../docs/tool-catalog.md).
Canonical successes are the inspection string, try `{ id, pluginName, state, provides, waitingFor }`, and stop `{ id, pluginName }`. Native rendering says whether the temporary Plugin is running or pending and that it remains available until stopped or DSH restarts; stop confirms that it was stopped and removed.
Canonical successes are the inspection string, mount `{ id, pluginName, state, provides, waitingFor }`, and unmount `{ id, pluginName }`. Native rendering says whether the temporary Plugin is running or pending and that it remains available until unmounted or DSH restarts; unmount confirms that it was removed.
Temporary Plugins live only in the shared DSH process memory. They remain active across later turns and may affect other sessions in that process, but disappear after `cordis_stop`, toolset unload, or DSH restart. They create no Plugin file, install no package, change no `cordis.yml` or personal/project configuration, do not survive restart, and cannot be promoted automatically. To keep an experiment, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow.
Temporary Plugins live only in the shared DSH process memory. They remain active across later turns and may affect other sessions in that process, but disappear after `cordis_unmount`, toolset unload, or DSH restart. They create no Plugin file, install no package, change no `cordis.yml` or personal/project configuration, do not survive restart, and cannot be promoted automatically. To keep an experiment, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow.
## Trust stance
@@ -32,7 +32,7 @@ The sandbox isolates globals but is not a security boundary. Node globals are ab
## Rendering
All three tools render `generic` cards (`read` / `execute` / `delete`); `cordis_try` carries the temporary-Plugin code as `rawInput`. Presenters are pure functions of the args; results keep the default text rendering.
All three tools render `generic` cards (`read` / `execute` / `delete`); `cordis_mount` carries the temporary-Plugin code as `rawInput`. Presenters are pure functions of the args; results keep the default text rendering.
## Export shape
@@ -44,7 +44,7 @@ Namespace plugin: named exports `name` / `inject` / `Config` / `apply`, no defau
#### What the model sees
The conversation model sees the generated [`cordis_inspect`, `cordis_try`, and `cordis_stop` schemas](../../../docs/tool-catalog.md#deepseek-aidsh-tool-cordis) whenever this plugin is visible.
The conversation model sees the generated [`cordis_inspect`, `cordis_mount`, and `cordis_unmount` schemas](../../../docs/tool-catalog.md#deepseek-aidsh-tool-cordis) whenever this plugin is visible.
#### Token effect
@@ -58,7 +58,7 @@ Prefix-stable while this tool view is unchanged. Scoping or plugin lifecycle cha
#### What the model sees
Inspect joins selected sections exactly as `## <section>` then a newline and the data-dependent body, with one blank line between sections; `what: "temporary"` uses the `## Temporary Plugins` heading. Each temporary-Plugin row reports running/pending state, provided and awaited services, and its lifetime until stopped or DSH restart. The empty state explains that `cordis_try` Plugins disappear on restart. Broad API/event reports omit JSDoc; `name` with `what: "api"` or `what: "events"` returns one exact target with its original JSDoc. Try returns `Temporary Plugin <id> is running (...)` or `Temporary Plugin <id> is pending (...)`; stop returns `Temporary Plugin <id> was stopped and removed.` The submitted program remains in assistant tool-call history.
Inspect joins selected sections exactly as `## <section>` then a newline and the data-dependent body, with one blank line between sections; `what: "temporary"` uses the `## Temporary Plugins` heading. Each temporary-Plugin row reports running/pending state, provided and awaited services, and its lifetime until unmounted or DSH restart. The empty state explains that `cordis_mount` Plugins disappear on restart. Broad API/event reports omit JSDoc; `name` with `what: "api"` or `what: "events"` returns one exact target with its original JSDoc. Mount returns `Temporary Plugin <id> is running (...)` or `Temporary Plugin <id> is pending (...)`; unmount returns `Temporary Plugin <id> was unmounted and removed.` The submitted program remains in assistant tool-call history.
#### Token effect
@@ -68,11 +68,11 @@ Inspect output and mount code are data-dependent and resent until compaction; li
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
### Later requests after cordis_try
### Later requests after cordis_mount
#### What the model sees
A temporary Plugin may register tools, prompt contributions, or listeners that change later requests for the scopes it targets; `cordis_stop` removes those contributions after quiescence.
A temporary Plugin may register tools, prompt contributions, or listeners that change later requests for the scopes it targets; `cordis_unmount` removes those contributions after quiescence.
#### Token effect
@@ -80,7 +80,7 @@ Indirect token impact equals the temporary Plugin's contributions and lasts only
#### KV Cache effect
Trying or stopping a prompt or tool contribution changes later request prefixes and may invalidate reuse from the first changed contribution; an unchanged temporary-Plugin set remains prefix-stable.
Mounting or unmounting a prompt or tool contribution changes later request prefixes and may invalidate reuse from the first changed contribution; an unchanged temporary-Plugin set remains prefix-stable.
## Known Limitations and Deferred Work
+11 -11
View File
@@ -6,15 +6,15 @@
## 功能
- `cordis_inspect`:当前进程运行时的只读报告,包括服务、全部存活 Plugin fiber、已注册工具、`cordis_try` 临时 Plugin 子集,以及目录支持的 `api``events` 参考。精确的 `name` 配合 `what: "api"``what: "events"` 可缩窄报告,并附上原始源代码 JSDoc。
- `cordis_try`:立即求值模型编写的 JavaScript 且不保存到任何位置;代码必须返回一个以 `dyn-<n>` 跟踪、仅存于内存的临时 Plugin。
- `cordis_stop`停止一个 `dyn-<n>` 临时 Plugin,并只在其自有效果完全停稳后返回;它不能删除 Loader、配置或已安装的 Plugin。
- `cordis_inspect`:当前进程运行时的只读报告,包括服务、全部存活 Plugin fiber、已注册工具、`cordis_mount` 临时 Plugin 子集,以及目录支持的 `api``events` 参考。精确的 `name` 配合 `what: "api"``what: "events"` 可缩窄报告,并附上原始源代码 JSDoc。
- `cordis_mount`:立即求值模型编写的 JavaScript 且不保存到任何位置;代码必须返回一个以 `dyn-<n>` 跟踪、仅存于内存的临时 Plugin。
- `cordis_unmount`卸载一个 `dyn-<n>` 临时 Plugin,并只在其自有效果完全停稳后返回;它不能删除 Loader、配置或已安装的 Plugin。
精确的面向模型 schema 见[生成的工具目录](../../../docs/tool-catalog.md)。
规范成功值分别为检查字符串、尝试 `{ id, pluginName, state, provides, waitingFor }`,以及停止 `{ id, pluginName }`。原生 renderer 会说明临时 Plugin 正在运行还是等待中,并说明它可用至被停止或 DSH 重启;停止结果确认它已停止并移除。
规范成功值分别为检查字符串、挂载 `{ id, pluginName, state, provides, waitingFor }`,以及卸载 `{ id, pluginName }`。原生 renderer 会说明临时 Plugin 正在运行还是等待中,并说明它可用至被卸载或 DSH 重启;卸载结果确认它已移除。
临时 Plugin 只存在于共享 DSH 进程内存中。它可跨后续 turn 保持活跃,也可能影响同一进程中的其他 session,但会在 `cordis_stop`、工具集卸载或 DSH 重启后消失。它不会创建 Plugin 文件、安装 package、修改 `cordis.yml` 或个人/项目配置、跨重启存续,也不能自动转为正式 Plugin。若要保留实验结果,应让 Agent 通过常规开发流程实现普通的本地、项目或仓库 Plugin。
临时 Plugin 只存在于共享 DSH 进程内存中。它可跨后续 turn 保持活跃,也可能影响同一进程中的其他 session,但会在 `cordis_unmount`、工具集卸载或 DSH 重启后消失。它不会创建 Plugin 文件、安装 package、修改 `cordis.yml` 或个人/项目配置、跨重启存续,也不能自动转为正式 Plugin。若要保留实验结果,应让 Agent 通过常规开发流程实现普通的本地、项目或仓库 Plugin。
## 信任立场
@@ -32,7 +32,7 @@
## 渲染
三个工具都渲染 `generic` 卡片(`read``execute``delete`);`cordis_try``rawInput` 携带临时 Plugin 代码。presenter 是 args 的纯函数;结果保留默认文本渲染。
三个工具都渲染 `generic` 卡片(`read``execute``delete`);`cordis_mount``rawInput` 携带临时 Plugin 代码。presenter 是 args 的纯函数;结果保留默认文本渲染。
## 导出形状
@@ -44,7 +44,7 @@ Namespace 插件:命名导出 `name``inject``Config``apply`,无默
#### 模型看到的内容
该插件可见时,会话模型会看到生成的 [`cordis_inspect`、`cordis_try` 和 `cordis_stop` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-cordis)。
该插件可见时,会话模型会看到生成的 [`cordis_inspect`、`cordis_mount` 和 `cordis_unmount` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-cordis)。
#### Token 影响
@@ -58,7 +58,7 @@ Namespace 插件:命名导出 `name``inject``Config``apply`,无默
#### 模型看到的内容
检查会精确地用 `## <section>` 加换行及数据相关主体来拼接选中区段,各区段之间留一个空行;`what: "temporary"` 使用 `## Temporary Plugins` 标题。每个临时 Plugin 行都会报告 runningpending 状态、提供与等待的服务,以及持续至停止或 DSH 重启的生命周期;空状态说明 `cordis_try` Plugin 会在重启时消失。宽泛的 API/事件报告省略 JSDoc;`name` 配合 `what: "api"``what: "events"` 返回一个精确目标及其原始 JSDoc。尝试结果为 `Temporary Plugin <id> is running (...)``Temporary Plugin <id> is pending (...)`停止结果为 `Temporary Plugin <id> was stopped and removed.`。提交的程序保留在 assistant 工具调用历史中。
检查会精确地用 `## <section>` 加换行及数据相关主体来拼接选中区段,各区段之间留一个空行;`what: "temporary"` 使用 `## Temporary Plugins` 标题。每个临时 Plugin 行都会报告 runningpending 状态、提供与等待的服务,以及持续至卸载或 DSH 重启的生命周期;空状态说明 `cordis_mount` Plugin 会在重启时消失。宽泛的 API/事件报告省略 JSDoc;`name` 配合 `what: "api"``what: "events"` 返回一个精确目标及其原始 JSDoc。挂载返回 `Temporary Plugin <id> is running (...)``Temporary Plugin <id> is pending (...)`卸载返回 `Temporary Plugin <id> was unmounted and removed.`。提交的程序保留在 assistant 工具调用历史中。
#### Token 影响
@@ -68,11 +68,11 @@ Namespace 插件:命名导出 `name``inject``Config``apply`,无默
仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV-cache 配置项失效。
### cordis_try 后的后续请求
### cordis_mount 后的后续请求
#### 模型看到的内容
临时 Plugin 可以注册工具、提示词贡献或监听器,改变其目标 scope 的后续请求;`cordis_stop` 会在完全停稳后移除这些贡献。
临时 Plugin 可以注册工具、提示词贡献或监听器,改变其目标 scope 的后续请求;`cordis_unmount` 会在完全停稳后移除这些贡献。
#### Token 影响
@@ -80,7 +80,7 @@ Namespace 插件:命名导出 `name``inject``Config``apply`,无默
#### KV Cache 影响
尝试或停止提示词/工具贡献会改变后续请求前缀,并可能使从第一个变化的贡献起的复用失效;临时 Plugin 集合不变时,前缀保持稳定。
挂载或卸载提示词/工具贡献会改变后续请求前缀,并可能使从第一个变化的贡献起的复用失效;临时 Plugin 集合不变时,前缀保持稳定。
## 已知限制与暂缓事项
+1 -1
View File
@@ -684,7 +684,7 @@ function sandboxContext(ctx: Context): Context {
if (ctx.get(prop) !== undefined) {
throw new Error(
`service "${prop}" is not injected. Declare it: inject: ['${prop}', …] on your plugin, `
+ 'so cordis parks this temporary Plugin if the provider later stops.',
+ 'so cordis parks this temporary Plugin if the provider is later unmounted.',
)
}
throw new Error(
+19 -19
View File
@@ -1,6 +1,6 @@
/**
* Self-referential runtime tools: inspect live services/plugins/tools, try a returned temporary
* plugin under an owned dynamic fiber, and stop it to quiescence. Registrations are fiber effects,
* Self-referential runtime tools: inspect live services/plugins/tools, mount a returned temporary
* plugin under an owned dynamic fiber, and unmount it to quiescence. Registrations are fiber effects,
* so plugin disposal removes the entire dynamic subtree. The VM and context façade prevent
* accidental misuse, not hostile code: an allowed service such as `ctx.bash` reaches the real
* runtime. Named exports preserve loader injection metadata.
@@ -15,7 +15,7 @@ import { isPlugin, pluginName } from './guard.ts'
import { EVENT_API, INHERITED_CTX_API, SERVICE_API, TYPE_API } from './api-catalog.ts'
import { describeApi, describeDynamic, describeEvents, describePlugins, describeServices, describeTools, providedServices } from './inspect.ts'
import { missingServices, mountDynamic, type DynamicMount } from './mount.ts'
import { presentInspectCall, presentStopCall, presentTryCall } from './present.ts'
import { presentInspectCall, presentMountCall, presentUnmountCall } from './present.ts'
import { createSandbox, evaluateMountCode } from './sandbox.ts'
export const name = 'tool-cordis'
@@ -60,10 +60,10 @@ export function apply(ctx: Context, config: Config): void {
+ 'Sections: `services` (every provided ctx service and the plugin fiber that owns it), '
+ '`plugins` (all live plugin fibers with their lifecycle states), '
+ '`tools` (the model-facing tools currently registered, i.e. what you can call), '
+ '`temporary` (only temporary Plugins created by cordis_try: id, name, state, provided services, awaited services, and lifetime), '
+ '`temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), '
+ '`api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), '
+ '`events` (every harness event with its dispatch mode and exact signature — pick listener targets here). '
+ 'Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_stop, toolset unload, or DSH restart; they are not restored automatically. '
+ 'Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. '
+ 'The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. '
+ 'With `what:"api"` or `what:"events"`, pass an exact `name` '
+ 'to narrow to one service/event and include its original source JSDoc.',
@@ -106,11 +106,11 @@ export function apply(ctx: Context, config: Config): void {
}))
ctx.tools.register(defineTool({
name: 'cordis_try',
name: 'cordis_mount',
description:
'Try a temporary Cordis Plugin in the current DSH process. '
'Mount a temporary Cordis Plugin in the current DSH process. '
+ 'This creates an in-memory runtime Plugin, not an installed or configured Plugin. '
+ 'It remains active across later turns until cordis_stop, toolset unload, or DSH restart. '
+ 'It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. '
+ 'It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. '
+ 'To keep it, ask the Agent to implement a normal local, project, or repository Plugin through the regular development workflow. '
+ 'It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. '
@@ -123,7 +123,7 @@ export function apply(ctx: Context, config: Config): void {
+ '— declares dependencies, and cordis activates the plugin only after the '
+ 'services exist; PREFER this form. You may reach ONLY the services you list in '
+ 'inject: an undeclared service throws even if it exists, because an undeclared '
+ 'dependency would not be cleaned up if its provider stops. '
+ 'dependency would not be cleaned up if its provider is unmounted. '
+ 'BEFORE calling a service from your code, read cordis_inspect what:"api" — it lists '
+ 'method signatures AND the type shapes of their arguments/returns (do not guess a '
+ 'field\'s type; e.g. a bash run\'s stdout is an object, not a string). '
@@ -140,8 +140,8 @@ export function apply(ctx: Context, config: Config): void {
+ '`output.render(args, value)` separately returns Native/model content blocks. '
+ 'Temporary Plugins can COMPOSE: one Plugin may `ctx.provide(\'name\', value)` a service and '
+ 'another may declare `inject: [\'name\']` to consume it — the consumer stays pending '
+ 'until the provider exists and returns to pending when the provider stops. '
+ 'Everything registered inside `apply` is cleaned up automatically by cordis_stop. '
+ 'until the provider exists and returns to pending when the provider is unmounted. '
+ 'Everything registered inside `apply` is cleaned up automatically by cordis_unmount. '
+ 'Sandbox globals: `console` (tagged `[cordis:<id>]`, writes through to the harness '
+ 'terminal), `harness.defineTool`, `harness.registerTool`, '
+ '`btoa`, `atob`, `TextEncoder`, `TextDecoder`. '
@@ -150,7 +150,7 @@ export function apply(ctx: Context, config: Config): void {
+ 'errors; `process` and `Buffer` are undefined. Instead use inject: [\'fs\'] + ctx.fs for '
+ 'files, inject: [\'web\'] + ctx.web for HTTP, inject: [\'bash\'] + ctx.bash for processes, '
+ 'and inject: [\'timer\'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, '
+ 'auto-cleaned when stopped) — cordis_inspect what:"api" shows what THIS runtime provides. '
+ 'auto-cleaned when unmounted) — cordis_inspect what:"api" shows what THIS runtime provides. '
+ 'Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). '
+ 'Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a '
+ 'trailing `next` callback which MUST be called — returning without `next()` '
@@ -191,7 +191,7 @@ export function apply(ctx: Context, config: Config): void {
: `is running (plugin "${value.pluginName}"`
return [{
type: 'text',
text: `Temporary Plugin ${value.id} ${status}; available until stopped or DSH restarts).`,
text: `Temporary Plugin ${value.id} ${status}; available until unmounted or DSH restarts).`,
}]
},
},
@@ -226,19 +226,19 @@ export function apply(ctx: Context, config: Config): void {
waitingFor: missing,
}
},
presentCall: presentTryCall,
presentCall: presentMountCall,
}))
ctx.tools.register(defineTool({
name: 'cordis_stop',
name: 'cordis_unmount',
description:
'Stop a current-process temporary Plugin created by cordis_try. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. '
'Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. '
+ 'Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.',
parameters: {
id: {
type: 'string',
required: true,
description: 'The temporary Plugin id returned by cordis_try (for example "dyn-1"); valid only in this process and invalid after stop or restart.',
description: 'The temporary Plugin id returned by cordis_mount (for example "dyn-1"); valid only in this process and invalid after unmount or restart.',
},
},
output: {
@@ -250,7 +250,7 @@ export function apply(ctx: Context, config: Config): void {
pluginName: { type: 'string', required: true },
},
},
render: (_args, value) => [{ type: 'text', text: `Temporary Plugin ${value.id} was stopped and removed.` }],
render: (_args, value) => [{ type: 'text', text: `Temporary Plugin ${value.id} was unmounted and removed.` }],
},
async execute(args) {
const mount = mounts.get(args.id)
@@ -261,6 +261,6 @@ export function apply(ctx: Context, config: Config): void {
mounts.delete(args.id)
return { id: args.id, pluginName: mount.pluginName }
},
presentCall: presentStopCall,
presentCall: presentUnmountCall,
}))
}
+2 -2
View File
@@ -100,13 +100,13 @@ export function describeTools(ctx: Context, scope?: ScopeKey): string[] {
*/
export function describeDynamic(ctx: Context, mounts: ReadonlyMap<string, DynamicMount>): string[] {
if (mounts.size === 0) {
return ['No temporary Plugins are running. Temporary Plugins created with cordis_try disappear when DSH restarts.']
return ['No temporary Plugins are running. Temporary Plugins created with cordis_mount disappear when DSH restarts.']
}
return [...mounts].map(([id, mount]) => {
const provides = providedServices(ctx, mount.fiber)
const waiting = missingServices(ctx, mount.fiber)
const state = mount.fiber.state === FiberState.ACTIVE ? 'running' : STATE_LABELS[mount.fiber.state]
return `- Temporary Plugin ${id}: ${mount.pluginName} [${state}] — provides: ${provides.join(', ') || 'none'}; waiting for: ${waiting.join(', ') || 'none'}; lifetime: until stopped or DSH restarts`
return `- Temporary Plugin ${id}: ${mount.pluginName} [${state}] — provides: ${provides.join(', ') || 'none'}; waiting for: ${waiting.join(', ') || 'none'}; lifetime: until unmounted or DSH restarts`
})
}
+2 -2
View File
@@ -39,8 +39,8 @@ export async function mountDynamic(group: Fiber, plugin: Plugin): Promise<Fiber>
// while the old mount still holds the name — teach the replace recipe.
if (message.includes('already registered')) {
throw new Error(
`${message} — to REPLACE something an earlier temporary Plugin registered, first cordis_stop that Plugin's id `
+ '(find it with cordis_inspect what:"temporary"), then try the new version.',
`${message} — to REPLACE something an earlier temporary Plugin registered, first cordis_unmount that Plugin's id `
+ '(find it with cordis_inspect what:"temporary"), then mount the new version.',
)
}
throw error instanceof Error ? error : new Error(message)
+6 -6
View File
@@ -25,28 +25,28 @@ export function presentInspectCall(args: { what?: string; name?: string }): Gene
}
/**
* The `cordis_try` call card: an execute carrying the temporary-plugin code as raw input.
* The `cordis_mount` call card: an execute carrying the temporary-plugin code as raw input.
* @param args - the validated call arguments.
* @returns the generic call card.
*/
export function presentTryCall(args: { code: string }): GenericCallView {
export function presentMountCall(args: { code: string }): GenericCallView {
return {
card: 'generic',
kind: 'execute',
title: 'Try temporary Cordis Plugin',
title: 'Mount temporary Cordis Plugin',
rawInput: { code: args.code },
}
}
/**
* The `cordis_stop` call card: a delete, titled with the temporary-plugin id.
* The `cordis_unmount` call card: a delete, titled with the temporary-plugin id.
* @param args - the validated call arguments.
* @returns the generic call card.
*/
export function presentStopCall(args: { id: string }): GenericCallView {
export function presentUnmountCall(args: { id: string }): GenericCallView {
return {
card: 'generic',
kind: 'delete',
title: `Stop temporary Cordis Plugin ${args.id}`,
title: `Unmount temporary Cordis Plugin ${args.id}`,
}
}
+3 -3
View File
@@ -1,5 +1,5 @@
/**
* The `node:vm` sandbox `cordis_try` code evaluates in: a fresh realm whose globals are a
* The `node:vm` sandbox `cordis_mount` code evaluates in: a fresh realm whose globals are a
* tagged write-through console, the `harness` registration helpers, the encoding primitives a
* bare vm context lacks, and callable traps over the Node APIs the sandbox deliberately
* withholds. Traps steer filesystem, network, process, and timer work to `ctx.fs`, `ctx.web`,
@@ -52,7 +52,7 @@ function patchDualRealmInstanceof(sandbox: object): void {
const TIMER_REDIRECT
= 'Node timers are unavailable. Use the cordis timer service instead: declare inject: [\'timer\'] on your plugin '
+ 'and call ctx.setTimeout / ctx.setInterval — those are fiber effects, cleaned up automatically when stopped.'
+ 'and call ctx.setTimeout / ctx.setInterval — those are fiber effects, cleaned up automatically when unmounted.'
/**
* The callable Node APIs the sandbox deliberately disables, each mapped to the
@@ -87,7 +87,7 @@ function nodeApiTraps(): Record<string, () => never> {
}
/**
* Build the vm context one `cordis_try` call evaluates in: the tagged
* Build the vm context one `cordis_mount` call evaluates in: the tagged
* console, the `harness` registration helpers, the encoding primitives, the
* Node-API traps, and the dual-realm `instanceof` patch, already
* `createContext`-ed.
@@ -11,10 +11,10 @@ import { call, CONSUMER_CODE, CONTENT_OUTPUT_CODE, PROVIDER_CODE, setup, text }
describe('cross-mount provide/inject', () => {
it('provider first: the consumer activates immediately and its tool reaches the provided service', async () => {
const ctx = await setup()
const provider = await call(ctx, 'cordis_try', { code: PROVIDER_CODE })
const provider = await call(ctx, 'cordis_mount', { code: PROVIDER_CODE })
expect(text(provider)).toContain('is running')
const consumer = await call(ctx, 'cordis_try', { code: CONSUMER_CODE })
const consumer = await call(ctx, 'cordis_mount', { code: CONSUMER_CODE })
expect(consumer.isError).toBe(false)
expect(text(consumer)).toContain('is running')
@@ -27,39 +27,39 @@ describe('cross-mount provide/inject', () => {
it('consumer first: stays pending naming the missing service, then activates when the provider mounts', async () => {
const ctx = await setup()
const consumer = await call(ctx, 'cordis_try', { code: CONSUMER_CODE })
const consumer = await call(ctx, 'cordis_mount', { code: CONSUMER_CODE })
expect(consumer.isError).toBe(false)
expect(text(consumer)).toContain('is pending')
expect(text(consumer)).toContain('missing services: greeter')
expect(text(await call(ctx, 'cordis_inspect', { what: 'temporary' }))).toContain('waiting for: greeter')
expect(ctx.tools.get('greet')).toBeUndefined()
await call(ctx, 'cordis_try', { code: PROVIDER_CODE })
await call(ctx, 'cordis_mount', { code: PROVIDER_CODE })
expect(ctx.tools.get('greet')).toBeDefined()
expect(text(await call(ctx, 'greet', { name: 'late' }))).toBe('hi late')
})
it('unmounting the provider sends the consumer back to pending and unwinds its registrations', async () => {
const ctx = await setup()
await call(ctx, 'cordis_try', { code: PROVIDER_CODE }) // dyn-1
await call(ctx, 'cordis_try', { code: CONSUMER_CODE }) // dyn-2
await call(ctx, 'cordis_mount', { code: PROVIDER_CODE }) // dyn-1
await call(ctx, 'cordis_mount', { code: CONSUMER_CODE }) // dyn-2
expect(ctx.tools.get('greet')).toBeDefined()
const unmounted = await call(ctx, 'cordis_stop', { id: 'dyn-1' })
const unmounted = await call(ctx, 'cordis_unmount', { id: 'dyn-1' })
expect(unmounted.isError).toBe(false)
expect(ctx.tools.get('greet')).toBeUndefined()
const report = text(await call(ctx, 'cordis_inspect', { what: 'temporary' }))
expect(report).toContain('Temporary Plugin dyn-2: greeter-consumer [pending] — provides: none; waiting for: greeter; lifetime: until stopped or DSH restarts')
expect(report).toContain('Temporary Plugin dyn-2: greeter-consumer [pending] — provides: none; waiting for: greeter; lifetime: until unmounted or DSH restarts')
})
it('re-providing the service re-runs the consumer through the same guard (active again, tool back)', async () => {
const ctx = await setup()
await call(ctx, 'cordis_try', { code: PROVIDER_CODE }) // dyn-1
await call(ctx, 'cordis_try', { code: CONSUMER_CODE }) // dyn-2
await call(ctx, 'cordis_stop', { id: 'dyn-1' })
await call(ctx, 'cordis_mount', { code: PROVIDER_CODE }) // dyn-1
await call(ctx, 'cordis_mount', { code: CONSUMER_CODE }) // dyn-2
await call(ctx, 'cordis_unmount', { id: 'dyn-1' })
expect(ctx.tools.get('greet')).toBeUndefined()
await call(ctx, 'cordis_try', { code: PROVIDER_CODE }) // dyn-3
await call(ctx, 'cordis_mount', { code: PROVIDER_CODE }) // dyn-3
expect(ctx.tools.get('greet')).toBeDefined()
expect(text(await call(ctx, 'greet', { name: 'again' }))).toBe('hi again')
expect(text(await call(ctx, 'cordis_inspect', { what: 'temporary' }))).toContain('Temporary Plugin dyn-2: greeter-consumer [running]')
@@ -67,8 +67,8 @@ describe('cross-mount provide/inject', () => {
it('a duplicate provide fails loud with the owning fiber named, and the failed mount is disposed', async () => {
const ctx = await setup()
await call(ctx, 'cordis_try', { code: PROVIDER_CODE })
const duplicate = await call(ctx, 'cordis_try', { code: PROVIDER_CODE })
await call(ctx, 'cordis_mount', { code: PROVIDER_CODE })
const duplicate = await call(ctx, 'cordis_mount', { code: PROVIDER_CODE })
expect(duplicate.isError).toBe(true)
expect(text(duplicate)).toContain('has been registered')
const report = text(await call(ctx, 'cordis_inspect', { what: 'temporary' }))
@@ -78,11 +78,11 @@ describe('cross-mount provide/inject', () => {
it('inspect surfaces the linkage: provides on the provider row, the service in services and api sections', async () => {
const ctx = await setup()
await call(ctx, 'cordis_try', { code: PROVIDER_CODE })
await call(ctx, 'cordis_try', { code: CONSUMER_CODE })
await call(ctx, 'cordis_mount', { code: PROVIDER_CODE })
await call(ctx, 'cordis_mount', { code: CONSUMER_CODE })
const dynamic = text(await call(ctx, 'cordis_inspect', { what: 'temporary' }))
expect(dynamic).toContain('Temporary Plugin dyn-1: greeter-provider [running] — provides: greeter; waiting for: none; lifetime: until stopped or DSH restarts')
expect(dynamic).toContain('Temporary Plugin dyn-1: greeter-provider [running] — provides: greeter; waiting for: none; lifetime: until unmounted or DSH restarts')
const services = text(await call(ctx, 'cordis_inspect', { what: 'services' }))
expect(services).toContain('- greeter (provided by greeter-provider)')
@@ -93,7 +93,7 @@ describe('cross-mount provide/inject', () => {
it('a primitive (or null) provided value passes through the façade unwrapped, on both read paths', async () => {
const ctx = await setup()
const provider = await call(ctx, 'cordis_try', {
const provider = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'answer-provider',
@@ -106,7 +106,7 @@ describe('cross-mount provide/inject', () => {
})
expect(provider.isError).toBe(false)
const consumer = await call(ctx, 'cordis_try', {
const consumer = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'answer-consumer',
@@ -132,9 +132,9 @@ describe('cross-mount provide/inject', () => {
it('unmounting the consumer leaves the provider and its service intact', async () => {
const ctx = await setup()
await call(ctx, 'cordis_try', { code: PROVIDER_CODE }) // dyn-1
await call(ctx, 'cordis_try', { code: CONSUMER_CODE }) // dyn-2
await call(ctx, 'cordis_stop', { id: 'dyn-2' })
await call(ctx, 'cordis_mount', { code: PROVIDER_CODE }) // dyn-1
await call(ctx, 'cordis_mount', { code: CONSUMER_CODE }) // dyn-2
await call(ctx, 'cordis_unmount', { id: 'dyn-2' })
expect(ctx.tools.get('greet')).toBeUndefined()
const services = text(await call(ctx, 'cordis_inspect', { what: 'services' }))
@@ -27,8 +27,8 @@ describe('cordis_inspect', () => {
expect(report).toContain('- tools (provided by ToolRegistry)')
expect(report).toContain('- tool-cordis [active]')
expect(report).toContain('- cordis-dynamic [active]')
expect(report).toContain('- cordis_try')
expect(report).toContain('No temporary Plugins are running. Temporary Plugins created with cordis_try disappear when DSH restarts.')
expect(report).toContain('- cordis_mount')
expect(report).toContain('No temporary Plugins are running. Temporary Plugins created with cordis_mount disappear when DSH restarts.')
})
it('limits the report to one section via `what`', async () => {
@@ -42,10 +42,10 @@ describe('cordis_inspect', () => {
it('shows a temporary Plugin in its exact section and in the flat plugins list', async () => {
const ctx = await setup()
await call(ctx, 'cordis_try', { code: LISTENER_CODE })
await call(ctx, 'cordis_mount', { code: LISTENER_CODE })
const report = text(await call(ctx, 'cordis_inspect', {}))
expect(report).toContain('## Temporary Plugins')
expect(report).toContain('- Temporary Plugin dyn-1: change-logger [running] — provides: none; waiting for: none; lifetime: until stopped or DSH restarts')
expect(report).toContain('- Temporary Plugin dyn-1: change-logger [running] — provides: none; waiting for: none; lifetime: until unmounted or DSH restarts')
expect(report).toContain('- change-logger [active]')
})
@@ -40,9 +40,9 @@ function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
describe('cordis tools through the agent loop', () => {
it('mounts a tool, calls it on the next step, and unmounts it — all as real tool/call events', async () => {
const adapter = new MockAdapter([
toolCallResponse('call-1', 'cordis_try', { code: REVERSE_TOOL_CODE }, 'Extending myself.'),
toolCallResponse('call-1', 'cordis_mount', { code: REVERSE_TOOL_CODE }, 'Extending myself.'),
toolCallResponse('call-2', 'reverse_text', { text: 'harness' }),
toolCallResponse('call-3', 'cordis_stop', { id: 'dyn-1' }),
toolCallResponse('call-3', 'cordis_unmount', { id: 'dyn-1' }),
textResponse('Done.'),
])
const ctx = await harness(adapter)
@@ -53,7 +53,7 @@ describe('cordis tools through the agent loop', () => {
const log = agent.session.events
const calls = log.filter(event => event.type === 'tool/call').map(event => event.data.name)
expect(calls).toEqual(['cordis_try', 'reverse_text', 'cordis_stop'])
expect(calls).toEqual(['cordis_mount', 'reverse_text', 'cordis_unmount'])
const results = log.filter(event => event.type === 'tool/result')
expect(results.map(event => event.data.isError)).toEqual([false, false, false])
@@ -67,22 +67,22 @@ describe('cordis tools through the agent loop', () => {
expect(ctx.tools.get('reverse_text')).toBeUndefined()
})
it('keeps a temporary Plugin across turns, stops it, and does not restore it in a new runtime', async () => {
it('keeps a temporary Plugin across turns, unmounts it, and does not restore it in a new runtime', async () => {
const adapter = new MockAdapter([
toolCallResponse('try-1', 'cordis_try', { code: 'return { name: \'turn-marker\', apply() {} }' }),
toolCallResponse('mount-1', 'cordis_mount', { code: 'return { name: \'turn-marker\', apply() {} }' }),
toolCallResponse('inspect-1', 'cordis_inspect', { what: 'temporary' }),
textResponse('Turn one complete.'),
toolCallResponse('inspect-2', 'cordis_inspect', { what: 'temporary' }),
toolCallResponse('stop-1', 'cordis_stop', { id: 'dyn-1' }),
toolCallResponse('unmount-1', 'cordis_unmount', { id: 'dyn-1' }),
toolCallResponse('inspect-3', 'cordis_inspect', { what: 'temporary' }),
textResponse('Turn two complete.'),
])
const ctx = await harness(adapter)
const agent = ctx.agentLoop.create(SessionId('it-cordis-turn-lifetime'), { provider: 'mock', model: 'mock' })
agent.followup([{ type: 'text', text: 'Try the marker and inspect it.' }])
agent.followup([{ type: 'text', text: 'Mount the marker and inspect it.' }])
await waitForIdle(ctx, agent)
agent.followup([{ type: 'text', text: 'On this later turn, inspect the marker, stop it, then inspect again.' }])
agent.followup([{ type: 'text', text: 'On this later turn, inspect the marker, unmount it, then inspect again.' }])
await waitForIdle(ctx, agent)
const resultText = new Map(
@@ -92,7 +92,7 @@ describe('cordis tools through the agent loop', () => {
)
expect(resultText.get(CallId('inspect-1'))).toContain('Temporary Plugin dyn-1: turn-marker [running]')
expect(resultText.get(CallId('inspect-2'))).toContain('Temporary Plugin dyn-1: turn-marker [running]')
expect(resultText.get(CallId('stop-1'))).toBe('Temporary Plugin dyn-1 was stopped and removed.')
expect(resultText.get(CallId('unmount-1'))).toBe('Temporary Plugin dyn-1 was unmounted and removed.')
expect(resultText.get(CallId('inspect-3'))).toContain('No temporary Plugins are running.')
const restarted = await setup()
+49 -49
View File
@@ -5,7 +5,7 @@ import { syntaxErrorContext } from '../src/sandbox.ts'
import { call, CONTENT_OUTPUT_CODE, dummyTool, LISTENER_CODE, REVERSE_TOOL_CODE, setup, text } from './helpers.ts'
/**
* The `cordis_try` success/failure family: real plugins land on a genuine
* The `cordis_mount` success/failure family: real plugins land on a genuine
* cordis fiber tree, their registrations are observable through the real
* registry/event bus, and every rejection path teaches the fix.
*/
@@ -14,7 +14,7 @@ afterEach(() => {
vi.restoreAllMocks()
})
describe('cordis_try', () => {
describe('cordis_mount', () => {
it.each([
[42, 'options must be an object'],
[{ parameters: {} }, 'output must declare { schema, render, presentationMeta? }'],
@@ -47,9 +47,9 @@ describe('cordis_try', () => {
const ctx = await setup()
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
const result = await call(ctx, 'cordis_try', { code: LISTENER_CODE })
const result = await call(ctx, 'cordis_mount', { code: LISTENER_CODE })
expect(result.isError).toBe(false)
if (result.isError) throw new Error('expected cordis_try success')
if (result.isError) throw new Error('expected cordis_mount success')
expect(result.value).toEqual({
id: 'dyn-1',
pluginName: 'change-logger',
@@ -57,7 +57,7 @@ describe('cordis_try', () => {
provides: [],
waitingFor: [],
})
expect(text(result)).toBe('Temporary Plugin dyn-1 is running (plugin "change-logger"; available until stopped or DSH restarts).')
expect(text(result)).toBe('Temporary Plugin dyn-1 is running (plugin "change-logger"; available until unmounted or DSH restarts).')
// Fire a REAL tools/change by registering a tool; the mounted listener logs.
ctx.tools.register(dummyTool('trigger_a'))
@@ -66,16 +66,16 @@ describe('cordis_try', () => {
it('mounts a bare-function plugin as <anonymous>, and a named function under its name', async () => {
const ctx = await setup()
const anonymous = await call(ctx, 'cordis_try', { code: 'return (ctx) => { ctx.on(\'tools/change\', () => {}) }' })
const anonymous = await call(ctx, 'cordis_mount', { code: 'return (ctx) => { ctx.on(\'tools/change\', () => {}) }' })
expect(anonymous.isError).toBe(false)
expect(text(anonymous)).toContain('plugin "<anonymous>"')
const named = await call(ctx, 'cordis_try', { code: 'return function watcher(ctx) {}' })
const named = await call(ctx, 'cordis_mount', { code: 'return function watcher(ctx) {}' })
expect(text(named)).toContain('plugin "watcher"')
})
it('lets the agent give ITSELF a new tool, immediately callable through the registry', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', { code: REVERSE_TOOL_CODE })
const result = await call(ctx, 'cordis_mount', { code: REVERSE_TOOL_CODE })
expect(result.isError).toBe(false)
expect(ctx.tools.schemas().map(schema => schema.name)).toContain('reverse_text')
@@ -89,14 +89,14 @@ describe('cordis_try', () => {
it('normalizes a self-made tool\'s result into the host realm, so the session log accepts it', async () => {
// VM-realm objects fail the session prototype-identity check; normalize them into host JSON.
const ctx = await setup()
await call(ctx, 'cordis_try', { code: REVERSE_TOOL_CODE })
await call(ctx, 'cordis_mount', { code: REVERSE_TOOL_CODE })
const reversed = await call(ctx, 'reverse_text', { text: 'harness' })
expect(isJsonValue({ content: reversed.content, isError: reversed.isError })).toBe(true)
})
it('projects presentation metadata from a dynamic canonical value', async () => {
const ctx = await setup()
await call(ctx, 'cordis_try', {
await call(ctx, 'cordis_mount', {
code: `
return {
name: 'meta-return',
@@ -136,7 +136,7 @@ describe('cordis_try', () => {
['undefined — a forgotten return', 'return undefined', 'execute result must be lossless JSON data'],
])('rejects an execute return of %s against its declared output', async (_label, returnStatement, diagnostic) => {
const ctx = await setup()
await call(ctx, 'cordis_try', {
await call(ctx, 'cordis_mount', {
code: `
return {
name: 'bad-return',
@@ -162,7 +162,7 @@ describe('cordis_try', () => {
it('does not echo a huge schema-invalid canonical value in the diagnostic', async () => {
const ctx = await setup()
await call(ctx, 'cordis_try', {
await call(ctx, 'cordis_mount', {
code: `
return {
name: 'huge-return',
@@ -189,7 +189,7 @@ describe('cordis_try', () => {
// These common JSON-Schema spellings each have one DSL meaning, so normalize rather than
// consume another model turn with a rejection.
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'json-schema-tool',
@@ -245,7 +245,7 @@ describe('cordis_try', () => {
// On an object PROPERTY, a JSON-Schema-style `required` array names the
// required children — the nested unwrap converts it just like the top level.
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'nested-json-schema',
@@ -276,7 +276,7 @@ describe('cordis_try', () => {
it('normalizes every unified DSL node and lossless annotation shape across the sandbox realm', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'unified-schema',
@@ -324,7 +324,7 @@ describe('cordis_try', () => {
it('normalizes and snapshots deeply nested sandbox schemas and annotations stack-safely', async () => {
const ctx = await setup()
const depth = 5_000
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'deep-unified-schema',
@@ -377,7 +377,7 @@ describe('cordis_try', () => {
it('normalizes unconstrained and closed nested nodes from a raw JSON Schema wrapper', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'raw-unified-schema',
@@ -467,7 +467,7 @@ describe('cordis_try', () => {
['parameters: Object.create(Object.create(null))', 'must be a ParameterSchemaSpec object'],
])('rejects a malformed ParameterSchemaSpec (%s) with a teaching error', async (parameters, message) => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'bad-schema',
@@ -508,7 +508,7 @@ describe('cordis_try', () => {
],
])('rejects circular sandbox schemas without exhausting the call stack', async (declaration, message) => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'circular-schema',
@@ -531,7 +531,7 @@ describe('cordis_try', () => {
it('preserves literal __proto__ keys in sandbox schemas and annotations', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'proto-schema',
@@ -566,7 +566,7 @@ describe('cordis_try', () => {
it('accepts a nested object/array ParameterSchemaSpec (the DSL recursion)', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'nested-schema',
@@ -593,7 +593,7 @@ describe('cordis_try', () => {
it('rejects raw dynamic ctx.tools.register calls that bypass harness helpers', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'raw-register',
@@ -618,7 +618,7 @@ describe('cordis_try', () => {
it('guards the registry reached through ctx.get(\'tools\') identically', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'raw-register-get',
@@ -636,13 +636,13 @@ describe('cordis_try', () => {
it('passes non-register registry members through the guard with correct binding', async () => {
const ctx = await setup()
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'schema-reader',
inject: ['tools'],
apply(ctx) {
console.log('sees', ctx.tools.schemas().length, 'tools; mount is', typeof ctx.tools.get('cordis_try'))
console.log('sees', ctx.tools.schemas().length, 'tools; mount is', typeof ctx.tools.get('cordis_mount'))
},
}
`,
@@ -653,11 +653,11 @@ describe('cordis_try', () => {
it('keeps a plugin with unsatisfied inject mounted as pending and names what it waits for', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: 'return { name: \'waiter\', inject: [\'no-such-service\'], apply(ctx) {} }',
})
expect(result.isError).toBe(false)
if (result.isError) throw new Error('expected pending cordis_try success')
if (result.isError) throw new Error('expected pending cordis_mount success')
expect(result.value).toEqual({
id: 'dyn-1',
pluginName: 'waiter',
@@ -665,15 +665,15 @@ describe('cordis_try', () => {
provides: [],
waitingFor: ['no-such-service'],
})
expect(text(result)).toBe('Temporary Plugin dyn-1 is pending (plugin "waiter"; missing services: no-such-service; available until stopped or DSH restarts).')
expect(text(result)).toBe('Temporary Plugin dyn-1 is pending (plugin "waiter"; missing services: no-such-service; available until unmounted or DSH restarts).')
// Unmounting a pending mount works like any other.
const unmounted = await call(ctx, 'cordis_stop', { id: 'dyn-1' })
const unmounted = await call(ctx, 'cordis_unmount', { id: 'dyn-1' })
expect(unmounted.isError).toBe(false)
})
it('rejects code that throws, leaving nothing mounted', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', { code: 'throw new Error(\'boom in sandbox\')' })
const result = await call(ctx, 'cordis_mount', { code: 'throw new Error(\'boom in sandbox\')' })
expect(result.isError).toBe(true)
expect(text(result)).toContain('boom in sandbox')
expect(text(await call(ctx, 'cordis_inspect', { what: 'temporary' }))).toContain('No temporary Plugins are running.')
@@ -681,30 +681,30 @@ describe('cordis_try', () => {
it('passes non-Error and null throws through untouched (no SyntaxError misclassification)', async () => {
const ctx = await setup()
const primitive = await call(ctx, 'cordis_try', { code: 'throw \'plain-string-throw\'' })
const primitive = await call(ctx, 'cordis_mount', { code: 'throw \'plain-string-throw\'' })
expect(primitive.isError).toBe(true)
expect(text(primitive)).toContain('plain-string-throw')
const nullish = await call(ctx, 'cordis_try', { code: 'throw null' })
const nullish = await call(ctx, 'cordis_mount', { code: 'throw null' })
expect(nullish.isError).toBe(true)
})
it('rejects code that does not return a plugin', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', { code: 'return 42' })
const result = await call(ctx, 'cordis_mount', { code: 'return 42' })
expect(result.isError).toBe(true)
expect(text(result)).toContain('must `return` a Plugin')
})
it('answers a missing return with the two valid plugin forms', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', { code: 'const plugin = (ctx) => {}' })
const result = await call(ctx, 'cordis_mount', { code: 'const plugin = (ctx) => {}' })
expect(result.isError).toBe(true)
expect(text(result)).toContain('did you forget `return`?')
})
it('disposes a plugin whose apply throws, and reports the error', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: 'return { name: \'broken\', apply(ctx) { throw new Error(\'apply exploded\') } }',
})
expect(result.isError).toBe(true)
@@ -714,14 +714,14 @@ describe('cordis_try', () => {
it('rolls back a plugin that collides with an existing tool name, keeping the original tool intact', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'usurper',
inject: ['tools'],
apply(ctx) {
harness.registerTool(ctx, harness.defineTool({
name: 'cordis_try',
name: 'cordis_mount',
description: 'dup',
parameters: {},
${CONTENT_OUTPUT_CODE}
@@ -733,15 +733,15 @@ describe('cordis_try', () => {
})
expect(result.isError).toBe(true)
expect(text(result)).toContain('already registered')
expect(text(result)).toContain('first cordis_stop')
// The original cordis_try still dispatches — the failed fiber is gone.
const retry = await call(ctx, 'cordis_try', { code: LISTENER_CODE })
expect(text(result)).toContain('first cordis_unmount')
// The original cordis_mount still dispatches — the failed fiber is gone.
const retry = await call(ctx, 'cordis_mount', { code: LISTENER_CODE })
expect(retry.isError).toBe(false)
})
it('isolates sandbox globals: no process/Buffer, and globalThis writes do not leak to the host', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
globalThis.__cordis_tool_leak = 'leaked'
return { name: 'probe-' + typeof process + '-' + typeof Buffer, apply(ctx) {} }
@@ -758,7 +758,7 @@ describe('cordis_try', () => {
['fetch(\'https://example.com\')', 'fetch is not available in the temporary Plugin sandbox', 'ctx.web'],
])('traps the Node API call %s with a redirect to the cordis alternative', async (invocation, trapMessage, redirect) => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', { code: `${invocation}\nreturn (ctx) => {}` })
const result = await call(ctx, 'cordis_mount', { code: `${invocation}\nreturn (ctx) => {}` })
expect(result.isError).toBe(true)
expect(text(result)).toContain(trapMessage)
expect(text(result)).toContain(redirect)
@@ -768,7 +768,7 @@ describe('cordis_try', () => {
it('lets a mounted plugin schedule through the cordis timer service (inject: [\'timer\'])', async () => {
const ctx = await setup()
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'ticker',
@@ -789,7 +789,7 @@ describe('cordis_try', () => {
const ctx = await setup()
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
const error = vi.spyOn(console, 'error').mockImplementation(() => {})
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
console.warn('warned')
console.error('errored')
@@ -807,7 +807,7 @@ describe('cordis_try', () => {
it('answers TypeScript syntax in the plain-JS sandbox with the fix', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: 'return { name: \'ts\' as const, apply(ctx) {} }',
})
expect(result.isError).toBe(true)
@@ -819,7 +819,7 @@ describe('cordis_try', () => {
// The canonical model mistake: closing the returned object with `});` as
// if it were a callback argument. The word "as" in a STRING elsewhere must
// not trigger the TypeScript hint — the heuristic reads the failing line.
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: 'const note = \'treat pattern as regex\'\nreturn {\n name: \'oops\',\n apply(ctx) {}\n});',
})
expect(result.isError).toBe(true)
@@ -842,7 +842,7 @@ describe('cordis_try', () => {
it('handles a runtime-thrown SyntaxError (no source-line prelude) with the generic hint', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', { code: 'throw new SyntaxError(\'user-crafted\')' })
const result = await call(ctx, 'cordis_mount', { code: 'throw new SyntaxError(\'user-crafted\')' })
expect(result.isError).toBe(true)
expect(text(result)).toContain('failed to parse')
expect(text(result)).toContain('user-crafted')
@@ -850,7 +850,7 @@ describe('cordis_try', () => {
it('honors the configured vmTimeoutMs for the synchronous portion', async () => {
const ctx = await setup({ vmTimeoutMs: 50 })
const result = await call(ctx, 'cordis_try', { code: 'while (true) {}' })
const result = await call(ctx, 'cordis_mount', { code: 'while (true) {}' })
expect(result.isError).toBe(true)
expect(text(result)).toMatch(/timed? ?out/i)
expect(text(await call(ctx, 'cordis_inspect', { what: 'temporary' }))).toContain('No temporary Plugins are running.')
@@ -861,7 +861,7 @@ describe('cordis_try', () => {
// Symbol.hasInstance prelude, `args.items instanceof Array` in sandbox code is silently
// false.
const ctx = await setup()
await call(ctx, 'cordis_try', {
await call(ctx, 'cordis_mount', {
code: `
return {
name: 'probe-instanceof',
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { presentInspectCall, presentTryCall, presentStopCall } from '../src/present.ts'
import { presentInspectCall, presentMountCall, presentUnmountCall } from '../src/present.ts'
import { setup } from './helpers.ts'
/**
@@ -18,17 +18,17 @@ describe('presenters', () => {
})
})
it('cordis_try renders a generic execute card carrying the code as raw input', () => {
expect(presentTryCall({ code: 'return (ctx) => {}' })).toEqual({
it('cordis_mount renders a generic execute card carrying the code as raw input', () => {
expect(presentMountCall({ code: 'return (ctx) => {}' })).toEqual({
card: 'generic',
kind: 'execute',
title: 'Try temporary Cordis Plugin',
title: 'Mount temporary Cordis Plugin',
rawInput: { code: 'return (ctx) => {}' },
})
})
it('cordis_stop renders a generic delete card titled with the id', () => {
expect(presentStopCall({ id: 'dyn-1' })).toEqual({ card: 'generic', kind: 'delete', title: 'Stop temporary Cordis Plugin dyn-1' })
it('cordis_unmount renders a generic delete card titled with the id', () => {
expect(presentUnmountCall({ id: 'dyn-1' })).toEqual({ card: 'generic', kind: 'delete', title: 'Unmount temporary Cordis Plugin dyn-1' })
})
it('is wired onto the registered definitions through the defineTool soft-validation path', async () => {
@@ -41,9 +41,9 @@ describe('presenters', () => {
expect(ctx.tools.get('cordis_inspect')!.presentCall!({ what: 'api', name: 'tools' })).toMatchObject({
title: 'Inspect cordis runtime: api: tools',
})
expect(ctx.tools.get('cordis_try')!.presentCall!({ code: 'return 1' })).toMatchObject({ kind: 'execute' })
expect(ctx.tools.get('cordis_stop')!.presentCall!({ id: 'dyn-2' })).toMatchObject({ title: 'Stop temporary Cordis Plugin dyn-2' })
expect(ctx.tools.get('cordis_mount')!.presentCall!({ code: 'return 1' })).toMatchObject({ kind: 'execute' })
expect(ctx.tools.get('cordis_unmount')!.presentCall!({ id: 'dyn-2' })).toMatchObject({ title: 'Unmount temporary Cordis Plugin dyn-2' })
// Soft validation: presenter args that fail the schema render as no card, never a throw.
expect(ctx.tools.get('cordis_stop')!.presentCall!({ id: 42 })).toBeUndefined()
expect(ctx.tools.get('cordis_unmount')!.presentCall!({ id: 42 })).toBeUndefined()
})
})
@@ -10,7 +10,7 @@ import { call, CONTENT_OUTPUT_CODE, setup, text } from './helpers.ts'
/** Mount a plugin whose `apply` touches one framework member, and report the error text. */
async function mountTouching(ctx: Awaited<ReturnType<typeof setup>>, expr: string): Promise<string> {
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `return { name: 'probe', inject: ['tools'], apply(ctx) { ${expr} } }`,
})
expect(result.isError).toBe(true)
@@ -41,7 +41,7 @@ describe('sandbox context façade — escape surface is closed', () => {
it('the classic ctx.root.tools.register bypass registers nothing and fails loud', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'root-bypass',
@@ -66,7 +66,7 @@ describe('sandbox context façade — escape surface is closed', () => {
it('rejects assignment to the façade rather than silently dropping it', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: 'return { name: \'writer\', apply(ctx) { ctx.stash = 1 } }',
})
expect(result.isError).toBe(true)
@@ -78,7 +78,7 @@ describe('sandbox context façade — escape surface is closed', () => {
// `ctx.systemPrompt.ctx.root.tools.register(…)` would escape the façade; service-return
// guards reject that Context before the registration lands.
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'svc-ctx-escape',
@@ -108,7 +108,7 @@ describe('sandbox context façade — escape surface is closed', () => {
name: 'host-async-svc',
apply(c) { c.provide('hostAsync', { grab: async () => 'host-fetched' }) },
})
await call(ctx, 'cordis_try', {
await call(ctx, 'cordis_mount', {
code: `
return {
name: 'async-consumer',
@@ -135,7 +135,7 @@ describe('sandbox context façade — escape surface is closed', () => {
it('reads a symbol property as undefined and answers the `in` operator without throwing', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'introspector',
@@ -157,7 +157,7 @@ describe('sandbox context façade — inject gate on services', () => {
// mount does not declare it — reaching it would let the mount depend on a
// provider cordis does not know about, so it is refused.
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: 'return { name: \'undeclared\', inject: [\'tools\'], apply(ctx) { const s = ctx.systemPrompt } }',
})
expect(result.isError).toBe(true)
@@ -167,7 +167,7 @@ describe('sandbox context façade — inject gate on services', () => {
it('denies an undeclared live service reached through ctx.get too', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: 'return { name: \'undeclared-get\', inject: [\'tools\'], apply(ctx) { ctx.get(\'systemPrompt\') } }',
})
expect(result.isError).toBe(true)
@@ -176,7 +176,7 @@ describe('sandbox context façade — inject gate on services', () => {
it('allows a service the mount DID declare in inject', async () => {
const ctx = await setup()
const result = await call(ctx, 'cordis_try', {
const result = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'declared',
@@ -193,10 +193,10 @@ describe('sandbox context façade — inject gate on services', () => {
// Without declared inject, Cordis cannot park the consumer when its provider unmounts. The
// façade refuses access up front instead of leaving a zombie tool.
const ctx = await setup()
await call(ctx, 'cordis_try', {
await call(ctx, 'cordis_mount', {
code: 'return { name: \'greeter-provider\', apply(ctx) { ctx.provide(\'greeter\', { greet: (n) => \'hi \' + n }) } }',
})
const undeclared = await call(ctx, 'cordis_try', {
const undeclared = await call(ctx, 'cordis_mount', {
code: `
return {
name: 'sloppy-consumer',
@@ -229,7 +229,7 @@ describe('sandbox tools façade — get is a read-only schema view', () => {
// function, letting it bypass ToolRegistry.execute (and its pre/post hooks). get now
// returns the same name/description/parameters view as schemas(), with no execute.
const ctx = await setup()
await call(ctx, 'cordis_try', {
await call(ctx, 'cordis_mount', {
code: `
return {
name: 'reporter',
@@ -241,7 +241,7 @@ describe('sandbox tools façade — get is a read-only schema view', () => {
parameters: {},
${CONTENT_OUTPUT_CODE}
async execute() {
const view = ctx.tools.get('cordis_try')
const view = ctx.tools.get('cordis_mount')
return [{ type: 'text', text: JSON.stringify({
hasExecute: 'execute' in view,
hasPresentCall: 'presentCall' in view,
@@ -259,13 +259,13 @@ describe('sandbox tools façade — get is a read-only schema view', () => {
const shape = JSON.parse(text(reported)) as { hasExecute: boolean; hasPresentCall: boolean; name: string; keys: string[] }
expect(shape.hasExecute).toBe(false)
expect(shape.hasPresentCall).toBe(false)
expect(shape.name).toBe('cordis_try')
expect(shape.name).toBe('cordis_mount')
expect(shape.keys).toEqual(['description', 'name', 'parameters'])
})
it('ctx.tools.get returns undefined for an unknown tool', async () => {
const ctx = await setup()
await call(ctx, 'cordis_try', {
await call(ctx, 'cordis_mount', {
code: `
return {
name: 'unknown-probe',
@@ -30,8 +30,8 @@ describe('tool registration', () => {
it('registers the three cordis tools with the documented schemas', async () => {
const ctx = await setup()
const names = ctx.tools.schemas().map(schema => schema.name)
expect(names).toEqual(expect.arrayContaining(['cordis_inspect', 'cordis_try', 'cordis_stop']))
expect(names).not.toEqual(expect.arrayContaining(['cordis_mount', 'cordis_unmount']))
expect(names).toEqual(expect.arrayContaining(['cordis_inspect', 'cordis_mount', 'cordis_unmount']))
expect(names).not.toEqual(expect.arrayContaining(['cordis_try', 'cordis_stop']))
const inspect = ctx.tools.schemas().find(schema => schema.name === 'cordis_inspect')!
const props = (inspect.parameters as { properties: Record<string, { enum?: string[]; type?: string }> }).properties
expect(props.what?.enum).toEqual(['services', 'plugins', 'tools', 'temporary', 'api', 'events'])
@@ -6,7 +6,7 @@ import * as tool from '../src/index.ts'
import { call, dummyTool, LISTENER_CODE, REVERSE_TOOL_CODE, setup, text } from './helpers.ts'
/**
* Disposal semantics: `cordis_stop` reaches quiescence before returning,
* Disposal semantics: `cordis_unmount` reaches quiescence before returning,
* and disposing the tool-cordis fiber itself (the HMR path) cascades over the
* whole dynamic subtree through the ordinary parentchild fiber lifecycle.
*/
@@ -15,20 +15,20 @@ afterEach(() => {
vi.restoreAllMocks()
})
describe('cordis_stop', () => {
describe('cordis_unmount', () => {
it('disposes the mount and its registrations have stopped by the time it returns (quiescence)', async () => {
const ctx = await setup()
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
await call(ctx, 'cordis_try', { code: LISTENER_CODE })
await call(ctx, 'cordis_mount', { code: LISTENER_CODE })
ctx.tools.register(dummyTool('trigger_before'))
expect(log).toHaveBeenCalledTimes(1)
const result = await call(ctx, 'cordis_stop', { id: 'dyn-1' })
const result = await call(ctx, 'cordis_unmount', { id: 'dyn-1' })
expect(result.isError).toBe(false)
if (result.isError) throw new Error('expected cordis_stop success')
if (result.isError) throw new Error('expected cordis_unmount success')
expect(result.value).toEqual({ id: 'dyn-1', pluginName: 'change-logger' })
expect(text(result)).toBe('Temporary Plugin dyn-1 was stopped and removed.')
expect(text(result)).toBe('Temporary Plugin dyn-1 was unmounted and removed.')
// Immediately after the awaited unmount, the listener must be gone — no
// grace period, no eventual consistency.
@@ -39,22 +39,22 @@ describe('cordis_stop', () => {
it('unregisters a self-made tool on unmount', async () => {
const ctx = await setup()
await call(ctx, 'cordis_try', { code: REVERSE_TOOL_CODE })
await call(ctx, 'cordis_mount', { code: REVERSE_TOOL_CODE })
expect(ctx.tools.get('reverse_text')).toBeDefined()
await call(ctx, 'cordis_stop', { id: 'dyn-1' })
await call(ctx, 'cordis_unmount', { id: 'dyn-1' })
expect(ctx.tools.get('reverse_text')).toBeUndefined()
})
it('rejects an unknown id, and a second unmount of the same id', async () => {
const ctx = await setup()
const unknown = await call(ctx, 'cordis_stop', { id: 'dyn-99' })
const unknown = await call(ctx, 'cordis_unmount', { id: 'dyn-99' })
expect(unknown.isError).toBe(true)
expect(text(unknown)).toContain('no temporary Plugin with id "dyn-99"')
await call(ctx, 'cordis_try', { code: LISTENER_CODE })
await call(ctx, 'cordis_stop', { id: 'dyn-1' })
const again = await call(ctx, 'cordis_stop', { id: 'dyn-1' })
await call(ctx, 'cordis_mount', { code: LISTENER_CODE })
await call(ctx, 'cordis_unmount', { id: 'dyn-1' })
const again = await call(ctx, 'cordis_unmount', { id: 'dyn-1' })
expect(again.isError).toBe(true)
})
})
@@ -67,8 +67,8 @@ describe('HMR safety', () => {
const fiber = await ctx.plugin(tool)
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
await call(ctx, 'cordis_try', { code: LISTENER_CODE })
await call(ctx, 'cordis_try', { code: REVERSE_TOOL_CODE })
await call(ctx, 'cordis_mount', { code: LISTENER_CODE })
await call(ctx, 'cordis_mount', { code: REVERSE_TOOL_CODE })
expect(ctx.tools.get('reverse_text')).toBeDefined()
await fiber.dispose()
@@ -76,7 +76,7 @@ describe('HMR safety', () => {
// The whole subtree is gone: the self-made tool, the cordis tools, and the
// mounted listener (no log on a fresh tools/change).
expect(ctx.tools.get('reverse_text')).toBeUndefined()
expect(ctx.tools.get('cordis_try')).toBeUndefined()
expect(ctx.tools.get('cordis_mount')).toBeUndefined()
const calls = log.mock.calls.length
ctx.tools.register(dummyTool('trigger_post_dispose'))
expect(log).toHaveBeenCalledTimes(calls)
@@ -23,7 +23,7 @@ describe('gen-tool-catalog collectToolCatalog', () => {
it('boots every shipped tool package and harvests its model-facing schemas', async () => {
const catalog = await collectToolCatalog()
const names = catalog.flatMap(entry => entry.schemas.map(s => s.name)).sort()
expect(names).toEqual(['ask_user_question', 'bash', 'cordis_inspect', 'cordis_stop', 'cordis_try', 'create_goal', 'edit', 'exit_plan_mode', 'get_goal', 'glob', 'grep', 'lsp', 'ralph', 'read', 'run_code', 'session_event_read', 'session_event_search', 'session_event_trace', 'session_search', 'session_trace', 'skill', 'subagent', 'task_kill', 'task_list', 'task_output', 'terminal_close', 'terminal_list', 'terminal_open', 'terminal_read', 'terminal_send', 'terminal_signal', 'todo_write', 'update_goal', 'web_fetch', 'web_search', 'workflow', 'write'])
expect(names).toEqual(['ask_user_question', 'bash', 'cordis_inspect', 'cordis_mount', 'cordis_unmount', 'create_goal', 'edit', 'exit_plan_mode', 'get_goal', 'glob', 'grep', 'lsp', 'ralph', 'read', 'run_code', 'session_event_read', 'session_event_search', 'session_event_trace', 'session_search', 'session_trace', 'skill', 'subagent', 'task_kill', 'task_list', 'task_output', 'terminal_close', 'terminal_list', 'terminal_open', 'terminal_read', 'terminal_send', 'terminal_signal', 'todo_write', 'update_goal', 'web_fetch', 'web_search', 'workflow', 'write'])
// Every tool carries a JSON-Schema `parameters` object (what the model sees).
for (const entry of catalog) {
for (const schema of entry.schemas) {
@@ -18,10 +18,10 @@ buffer
5| <blank>
6| "▌ "
style 0-0 fg=yellow
7| "▌ ◌ Try temporary Cordis Plugin "
7| "▌ ◌ Mount temporary Cordis Plugin "
style 0-0 fg=yellow
style 2-2 fg=yellow bold
style 3-30 bold
style 3-32 bold
8| "▌ { "
style 0-0 fg=yellow
9| "▌ \"code\": \"return { name: 'snapshot-marker', apply(ctx) { ctx.provide('snapshotMarker', { "
@@ -33,10 +33,10 @@ buffer
12| "▌ "
style 0-0 fg=yellow
13| <blank>
14| "▌ ◌ Stop temporary Cordis Plugin dyn-1 "
14| "▌ ◌ Unmount temporary Cordis Plugin dyn-1 "
style 0-0 fg=yellow
style 2-2 fg=yellow bold
style 3-37 bold
style 3-40 bold
15| "────────────────────────────────────────────────────────────────────────────────────────────────"
style 0-95 dim
16| " "
+2 -2
View File
@@ -414,10 +414,10 @@ describe('TUI terminal-state snapshots', () => {
{ id: 'cordis-1', name: 'cordis_inspect', arguments: { what: 'tools' } },
{
id: 'cordis-2',
name: 'cordis_try',
name: 'cordis_mount',
arguments: { code: "return { name: 'snapshot-marker', apply(ctx) { ctx.provide('snapshotMarker', { ready: true }) } }" },
},
{ id: 'cordis-3', name: 'cordis_stop', arguments: { id: 'dyn-1' } },
{ id: 'cordis-3', name: 'cordis_unmount', arguments: { id: 'dyn-1' } },
]
await renderAfter(harness, () => { appendToolCalls(harness.session, calls) })
await checkpoint('cordis-tools-pending', harness.terminal, { includeScrollback: true })
+1 -1
View File
@@ -569,7 +569,7 @@ const APP_EXAMPLES = [
title: 'Cordis Agent App Composition',
label: 'examples/cordis-agent',
config: 'examples/cordis-agent/cordis.yml',
summary: 'The self-referential demo puts @deepseek-ai/dsh-tool-cordis on the coding spine, letting the agent inspect its current-process runtime and try or stop in-memory temporary Plugins.',
summary: 'The self-referential demo puts @deepseek-ai/dsh-tool-cordis on the coding spine, letting the agent inspect its current-process runtime and mount or unmount in-memory temporary Plugins.',
},
{
id: 'acp',
+1 -1
View File
@@ -213,7 +213,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(ToolCordis)
},
note:
'Ships in examples/cordis-agent only (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_try may register ADDITIONAL model-visible tools until stopped or DSH restarts; a full changed request header logs those tool-set changes.',
'Ships in examples/cordis-agent only (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes.',
},
{
pkg: '@deepseek-ai/dsh-tool-fs',
+10 -10
View File
@@ -149,10 +149,10 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
if prompt == SNAPSHOT_WORKFLOW_CHILD_PROMPT:
return text_chunks("WORKFLOW_CHILD_OK")
if prompt == SNAPSHOT_PROMPT:
assert_advertised_tool(body, "cordis_try")
assert_advertised_tool(body, "cordis_mount")
return tool_call_chunks(
"advanced-mount",
"cordis_try",
"cordis_mount",
{"code": SNAPSHOT_MOUNT_CODE},
)
if prompt == CODE_PROMPT:
@@ -187,9 +187,9 @@ def advanced_tool_followup(
"""Advance the executable snapshot's deterministic parent tool chain."""
if not call_id.startswith("advanced-"):
return None
if call_id == "advanced-mount" and tool_name == "cordis_try":
if call_id == "advanced-mount" and tool_name == "cordis_mount":
if "Temporary Plugin dyn-1 is running" not in tool_text:
raise AssertionError(f"cordis_try returned no temporary Plugin id: {tool_text}")
raise AssertionError(f"cordis_mount returned no temporary Plugin id: {tool_text}")
assert_advertised_tool(body, "run_code")
assert_advertised_tool(body, "snapshot_double")
return tool_call_chunks(
@@ -230,17 +230,17 @@ def advanced_tool_followup(
if call_id == "advanced-workflow" and tool_name == "workflow":
if "WORKFLOW_CHILD_OK" not in tool_text:
raise AssertionError(f"workflow returned no expected child value: {tool_text}")
assert_advertised_tool(body, "cordis_stop")
assert_advertised_tool(body, "cordis_unmount")
return tool_call_chunks(
"advanced-unmount",
"cordis_stop",
"cordis_unmount",
{"id": "dyn-1"},
)
if call_id == "advanced-unmount" and tool_name == "cordis_stop":
if "Temporary Plugin dyn-1 was stopped and removed." not in tool_text:
raise AssertionError(f"cordis_stop returned no stop result: {tool_text}")
if call_id == "advanced-unmount" and tool_name == "cordis_unmount":
if "Temporary Plugin dyn-1 was unmounted and removed." not in tool_text:
raise AssertionError(f"cordis_unmount returned no unmount result: {tool_text}")
if "snapshot_double" in advertised_tool_names(body):
raise AssertionError("snapshot_double remained advertised after cordis_stop")
raise AssertionError("snapshot_double remained advertised after cordis_unmount")
return text_chunks(SNAPSHOT_FINAL_TEXT)
raise AssertionError(f"unexpected advanced tool follow-up: {call_id} {tool_name}: {tool_text}")
@@ -72,8 +72,8 @@
"tools": [
"bash",
"cordis_inspect",
"cordis_stop",
"cordis_try",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"subagent",
@@ -114,7 +114,7 @@
"type": "tool-call-delta",
"index": 0,
"id": "advanced-mount",
"name": "cordis_try",
"name": "cordis_mount",
"argumentsDelta": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
}
@@ -132,7 +132,7 @@
"block": {
"type": "tool-call",
"id": "advanced-mount",
"name": "cordis_try",
"name": "cordis_mount",
"arguments": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
}
@@ -180,7 +180,7 @@
{
"type": "tool-call",
"id": "advanced-mount",
"name": "cordis_try",
"name": "cordis_mount",
"arguments": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
],
@@ -210,7 +210,7 @@
"turn": 1,
"step": 1,
"callId": "advanced-mount",
"name": "cordis_try",
"name": "cordis_mount",
"arguments": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
},
@@ -225,7 +225,7 @@
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until stopped or DSH restarts)."
"text": "Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until unmounted or DSH restarts)."
}
],
"isError": false
@@ -268,8 +268,8 @@
"tools": [
"bash",
"cordis_inspect",
"cordis_stop",
"cordis_try",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"snapshot_double",
@@ -836,7 +836,7 @@
"type": "tool-call-delta",
"index": 0,
"id": "advanced-unmount",
"name": "cordis_stop",
"name": "cordis_unmount",
"argumentsDelta": "{\"id\": \"dyn-1\"}"
}
}
@@ -854,7 +854,7 @@
"block": {
"type": "tool-call",
"id": "advanced-unmount",
"name": "cordis_stop",
"name": "cordis_unmount",
"arguments": "{\"id\": \"dyn-1\"}"
}
}
@@ -902,7 +902,7 @@
{
"type": "tool-call",
"id": "advanced-unmount",
"name": "cordis_stop",
"name": "cordis_unmount",
"arguments": "{\"id\": \"dyn-1\"}"
}
],
@@ -932,7 +932,7 @@
"turn": 1,
"step": 5,
"callId": "advanced-unmount",
"name": "cordis_stop",
"name": "cordis_unmount",
"arguments": "{\"id\": \"dyn-1\"}"
}
},
@@ -947,7 +947,7 @@
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 was stopped and removed."
"text": "Temporary Plugin dyn-1 was unmounted and removed."
}
],
"isError": false
@@ -990,8 +990,8 @@
"tools": [
"bash",
"cordis_inspect",
"cordis_stop",
"cordis_try",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"subagent",
@@ -1233,8 +1233,8 @@
"tools": [
"bash",
"cordis_inspect",
"cordis_stop",
"cordis_try",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"subagent",
@@ -1287,7 +1287,7 @@
"type": "tool-call-delta",
"index": 0,
"id": "advanced-mount",
"name": "cordis_try",
"name": "cordis_mount",
"argumentsDelta": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
}
@@ -1311,7 +1311,7 @@
"block": {
"type": "tool-call",
"id": "advanced-mount",
"name": "cordis_try",
"name": "cordis_mount",
"arguments": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
}
@@ -1377,7 +1377,7 @@
{
"type": "tool-call",
"id": "advanced-mount",
"name": "cordis_try",
"name": "cordis_mount",
"arguments": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
],
@@ -1413,7 +1413,7 @@
"turn": 1,
"step": 1,
"callId": "advanced-mount",
"name": "cordis_try",
"name": "cordis_mount",
"arguments": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
}
@@ -1434,7 +1434,7 @@
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until stopped or DSH restarts)."
"text": "Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until unmounted or DSH restarts)."
}
],
"isError": false
@@ -1495,8 +1495,8 @@
"tools": [
"bash",
"cordis_inspect",
"cordis_stop",
"cordis_try",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"snapshot_double",
@@ -2055,8 +2055,8 @@
"tools": [
"bash",
"cordis_inspect",
"cordis_stop",
"cordis_try",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"snapshot_double",
@@ -2595,8 +2595,8 @@
"tools": [
"bash",
"cordis_inspect",
"cordis_stop",
"cordis_try",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"snapshot_double",
@@ -2899,7 +2899,7 @@
"type": "tool-call-delta",
"index": 0,
"id": "advanced-unmount",
"name": "cordis_stop",
"name": "cordis_unmount",
"argumentsDelta": "{\"id\": \"dyn-1\"}"
}
}
@@ -2923,7 +2923,7 @@
"block": {
"type": "tool-call",
"id": "advanced-unmount",
"name": "cordis_stop",
"name": "cordis_unmount",
"arguments": "{\"id\": \"dyn-1\"}"
}
}
@@ -2989,7 +2989,7 @@
{
"type": "tool-call",
"id": "advanced-unmount",
"name": "cordis_stop",
"name": "cordis_unmount",
"arguments": "{\"id\": \"dyn-1\"}"
}
],
@@ -3025,7 +3025,7 @@
"turn": 1,
"step": 5,
"callId": "advanced-unmount",
"name": "cordis_stop",
"name": "cordis_unmount",
"arguments": "{\"id\": \"dyn-1\"}"
}
}
@@ -3046,7 +3046,7 @@
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 was stopped and removed."
"text": "Temporary Plugin dyn-1 was unmounted and removed."
}
],
"isError": false
@@ -3107,8 +3107,8 @@
"tools": [
"bash",
"cordis_inspect",
"cordis_stop",
"cordis_try",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"subagent",
@@ -3,7 +3,7 @@
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"}},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":0,"data":{"title":"Reply with exactly DIRECT_CHILD_OK and","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_stop","cordis_try","run_code","skill","snapshot_double","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"DIRECT_CHILD_OK"}}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DIRECT_CHILD_OK"}}}}
@@ -3,7 +3,7 @@
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"}},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":0,"data":{"title":"Reply with exactly WORKFLOW_CHILD_OK and","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_stop","cordis_try","run_code","skill","snapshot_double","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"WORKFLOW_CHILD_OK"}}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"WORKFLOW_CHILD_OK"}}}}
@@ -3,18 +3,18 @@
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Run the advanced packaged-runtime snapshot scenario."}],"source":{"kind":"user"}},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":0,"data":{"title":"Run the advanced packaged-runtime snapsh","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_stop","cordis_try","run_code","skill","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_try","argumentsDelta":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_try","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}}}
{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_mount","argumentsDelta":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}}}
{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_try","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"tool/call","seq":11,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_try","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}
{"type":"tool/result","seq":12,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until stopped or DSH restarts)."}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}
{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"tool/call","seq":11,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}
{"type":"tool/result","seq":12,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until unmounted or DSH restarts)."}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}
{"type":"step/end","seq":13,"time":0,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":14,"time":0,"data":{"turn":1,"step":2}}
{"type":"request/header","seq":15,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_stop","cordis_try","run_code","skill","snapshot_double","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"change"}}
{"type":"request/header","seq":15,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"change"}}
{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-code","name":"run_code","argumentsDelta":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}}
{"type":"assistant/chunk","seq":18,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}}}
@@ -48,16 +48,16 @@
{"type":"step/end","seq":46,"time":0,"data":{"turn":1,"step":4}}
{"type":"step/start","seq":47,"time":0,"data":{"turn":1,"step":5}}
{"type":"assistant/chunk","seq":48,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":49,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-unmount","name":"cordis_stop","argumentsDelta":"{\"id\": \"dyn-1\"}"}}}
{"type":"assistant/chunk","seq":50,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\": \"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":49,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-unmount","name":"cordis_unmount","argumentsDelta":"{\"id\": \"dyn-1\"}"}}}
{"type":"assistant/chunk","seq":50,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":51,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":52,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":53,"time":0,"data":{"turn":1,"step":5,"content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\": \"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[48,49,50,51,52],"surfaceOp":"append"}
{"type":"tool/call","seq":54,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_stop","arguments":"{\"id\": \"dyn-1\"}"}}
{"type":"tool/result","seq":55,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was stopped and removed."}],"isError":false},"sourceEventSeqs":[54],"surfaceOp":"append"}
{"type":"assistant/message","seq":53,"time":0,"data":{"turn":1,"step":5,"content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[48,49,50,51,52],"surfaceOp":"append"}
{"type":"tool/call","seq":54,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}
{"type":"tool/result","seq":55,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false},"sourceEventSeqs":[54],"surfaceOp":"append"}
{"type":"step/end","seq":56,"time":0,"data":{"turn":1,"step":5}}
{"type":"step/start","seq":57,"time":0,"data":{"turn":1,"step":6}}
{"type":"request/header","seq":58,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_stop","cordis_try","run_code","skill","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"change"}}
{"type":"request/header","seq":58,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"change"}}
{"type":"assistant/chunk","seq":59,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":60,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_EXECUTABLE_OK"}}}
{"type":"assistant/chunk","seq":61,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}}}}