Merge pull request #2047 from deepseek-harness/worktree-cordisweb

feat(cordis-web): model-mounted dynamic dual-half packages for the web client
This commit is contained in:
imccyu
2026-08-13 04:17:39 +08:00
committed by GitHub
240 files changed
+26892 -6239

No files matched your search

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-08-08-cordis-web-dynamic-packages.md
2026-08-08-cordis-web-dynamic-packages.md: c8ba3940bbcf7eeaa7b5a882f91ca8165bdd8cbf
2026-08-08-cordis-web-dynamic-packages.zh.md: a6d961aef69e8820023ce3d749f0affd83a871c6
@@ -0,0 +1,270 @@
# Agent Note: Cordis Host/Client Dynamic Plugin Runtime
Status: proposed
English | [中文](2026-08-08-cordis-web-dynamic-packages.zh.md)
## Problem
The model needs to extend the current DSH process temporarily without modifying repository source, rebuilding the application, or refreshing the browser. An extension may run in the Host Node.js process, in a Client browser page, or as one plugin whose Host half retrieves data and whose Client half presents it.
This capability cannot be limited to “execute some code.” Before writing code, the model needs to discover the Services, Events, Builtins, Slots, and theme tokens available on both platforms. The user needs to preview the code before deciding whether Client code may enter the page. A single plugin needs immutable versions, retries after failure, and rollback. Asynchronous runtime errors need to return to the model instead of remaining only in server logs or the browser console.
Combining definition, approval, execution, version switching, capability discovery, and UI state into one action creates states that cannot be explained consistently: whether a successful definition also means a successful run; which version remains successful after a failed update; how long a Tool should wait when no page responds; which historical card owns the business UI after the same Package runs multiple times; and whether page-local Client load state can represent process-wide Host state.
## Proposal
### Core principles
- The Host is the sole process-wide authority for Plugins, Packages, Runs, approvals, and version pointers.
- The Client stores only the current page's approval interaction, load results, Slot contributions, business views, and page-local errors.
- Define creates only immutable code versions; Run activates only a defined version.
- A version switch commits `currentPackageId` only after the target Package completes its required Host/Client activation.
- Before writing code, the model queries capabilities through Inspect Providers. Inspect results assist coding and are not plugin runtime business data.
- Dynamic Host and Client code both use restricted plain JavaScript contexts and attach reversible side effects to the Cordis lifecycle.
- Client code requires user authorization before entering a page. Authorization may cover one Package or future versions of the same Plugin.
- Tool calls do not wait for approval or browser operations that may occur only after the current turn ends. State stores and model steering report asynchronous outcomes.
### Package responsibilities and dependency direction
Four packages under `packages/self-modification/` implement the dynamic runtime:
| Package | npm package | Responsibility |
| --- | --- | --- |
| `tool-cordis` | `@deepseek-ai/dsh-tool-cordis` | Registers the System Prompt, seven model-facing Tools, Host Inspect Providers, `@pluginId` context injection, and Tool presentation metadata |
| `cordis-host-runner` | `@deepseek-ai/dsh-cordis-host-runner` | Stores the authoritative Registry, allocates IDs, executes Host code, and manages versions, approvals, Runs, private handlers, Inspect routing, and model feedback |
| `cordis-client-runner` | `@deepseek-ai/dsh-cordis-client-runner` | Synchronizes Inspect manifests in the browser, orchestrates approved Host→Client activation, evaluates Client code, and manages the Guard, Loader/Fiber, timer, styles, and teardown |
| `ui-cordis` | `@deepseek-ai/dsh-client-ui-cordis` | Renders Define/Run Tool cards, the global Cordis panel, approval controls, version selection, runtime status, and Package-specific business views |
`tool-cordis` depends only on the Host Runner's in-process service and does not import the Client implementation. `ui-cordis` consumes only the Client Runner face and Client-safe wire types and does not import the Host implementation. Existing generated Remote APIs and forwarded events connect Host and Client runtime control; the gateway owns no dynamic Plugin domain logic.
### Domain objects
#### Plugin
A Plugin is a dynamic plugin instance that can be modified over time. It is identified by the branded type `CordisDynamicPluginId`, for example `clock-1`. When creating a Plugin, the model submits only a semantic prefix of 3 to 6 lowercase English letters; the Host appends a process-unique numeric suffix. The model cannot specify the complete `pluginId`.
A Plugin belongs to the Session that defined it. Model-facing Tools can read and operate only Plugins from the current Session. The global Client panel can list Plugins from all Sessions, but each action still executes under the owner Session carried by that row.
#### Package
A Package is an immutable code version under a Plugin. It is identified by `CordisDynamicPackageId`, for example `pkg-2`. It contains a name, a purpose, optional Host code, and optional Client code, with at least one code half present. Every `cordis_define` creates a new Package; an existing Package cannot be modified in place.
One Plugin may own multiple Packages, but at most one physical Run may exist at a time. Whether a Package contains a Host or Client half affects only its activation steps, not its version identity.
#### Plugin Run
A Plugin Run is one concrete activation attempt. It is identified by `CordisDynamicPluginRunId`, for example `run-3`. Every new activation attempt receives a new ID, including an attempt that fails after approval, a retry of the same Package, and a version update. `pluginRunId` associates approval, Host activation, Client loading, private RPC, Tool cards, and errors with the same attempt.
The Host stores the current physical Run separately from `latestRun`. The physical Run is the activation that can currently receive calls and be torn down. `latestRun` records the approval, phase, status of both halves, and diagnostics for the most recent attempt. A failed attempt may leave no live physical Run while remaining available for inspection.
#### Version pointers
- `currentPackageId` is the most recent Package to complete its required activation flow. Stopping the plugin, beginning an update, or failing an update does not clear it.
- `nextPackageId` is the target Package that is awaiting approval, activating, awaiting a Client, or most recently failed. It is cleared after the target succeeds and is committed as current.
A Host-only Package commits current after the Host successfully establishes its Fiber. A Client-bearing Package commits current after Host activation succeeds and at least one Client successfully establishes the corresponding load. A Fiber that Cordis parks as waiting because a hard dependency is absent is still a successfully established lifecycle object; it is not equivalent to a parse or `apply` failure.
If an update target fails, the old physical Run is not restarted automatically. The previous `currentPackageId` continues to identify the last successful version, and the failed target remains `nextPackageId`. The user or model can retry next, or reactivate current with `mode: "run"` to roll back.
### Host authority and persistence
`DynamicCordisRunnerService` and its internal Registry are the sole authority in the current DSH process. They store:
- each Plugin's Session ownership and immutable Package set;
- `currentPackageId`, `nextPackageId`, the physical Run, and `latestRun`;
- per-Package authorization and cross-version Plugin authorization;
- pending Client activation requests;
- Host Fibers, Package-private handlers, waiting Services, and recent diagnostics;
- Host and Client Inspect Registry directories and query routing.
These objects are not written to configuration or disk and are not restored after process restart. The Session Log may retain Tool calls, results, and metadata needed by cards, but it does not replay dynamic code to restore the Registry. Historical cards remain in the conversation after a restart, but their original `pluginId` and `packageId` are no longer runnable.
Runtime state is not written to Session projection as recoverable state. Refreshing a page or opening a new page does not automatically restore Client halves; automatic restoration would reintroduce connection identity, startup baselines, and cross-page consistency protocols, which are outside this design.
### Define, Run, and version switching
`cordis_define` has two modes: creating a Plugin submits `idPrefix`, while modifying an existing Plugin submits its exact `pluginId`. Code always uses `code: { host?, client? }`. Define validates arguments and plain JavaScript syntax, records immutable source, and returns the final IDs. It does not execute `apply`, create approval, change version pointers, or run implicitly.
There is no separate `cordis_update`. `cordis_run` expresses activation intent through `mode`:
| Version relationship | `mode` |
| --- | --- |
| No `currentPackageId` exists | `run` |
| Target equals current, including restart, retry, or rollback | `run` |
| Target differs from an existing current | `update` |
| Retry `nextPackageId` after a failed update | `update` |
Run first validates Plugin/Package ownership, the version relationship, and whether another transition is in progress. It then creates `pluginRunId` and writes `latestRun` and `nextPackageId`.
A Host-only Package completes Host activation within the Tool call and returns `running` or a failure synchronously. A Client-bearing Package does not wait for a browser outcome within the Tool call: without authorization it registers approval and returns `awaiting-approval`; with authorization it registers automatic Client activation and returns `starting`. Both results mean that the request exists, not that full activation succeeded.
When target activation actually starts, the Host stops the old physical Run before executing the target Host half. Only after Host success may the Client fetch and load source for the exact `pluginRunId`. Client success commits the version pointers. Any failure is recorded against that attempt, without restarting the old version and presenting it as target success.
`cordis_stop` tears down the current Host/Client Run and pending approval request while retaining the Plugin, Packages, authorization, and version pointers. `cordis_undefine` stops first and then deletes the Plugin, Packages, authorization, and version pointers; historical cards then show only that the Plugin was removed.
### Client approval and authorization
A Package containing Client code requires user authorization before its first activation because model-generated code will run in the user's page. The approval panel offers three actions:
- A single check authorizes the current Package. Later runs of the same Package do not need approval, but a new Package does.
- A double check authorizes future versions of the current Plugin. New Packages, updates, retries, and rollbacks no longer require per-version approval.
- Reject ends the current request without executing Host or Client code. The model must not immediately request approval again unless the user asks for it.
Authorization is written to the Host Registry when the user allows it and remains even if a later technical step fails. When the panel runs a Package directly, the user's click itself authorizes that Package.
A row awaiting approval shows only per-Package allow, cross-version allow, and reject; it does not simultaneously offer run, stop, or delete. The panel expands automatically when new approval appears. If auto-expansion fails or the panel is collapsed, the fixed entry and row status still show the pending approval count and state.
### Client activation orchestration
The Host sends Client activation requests through `cordis/request-run`. A request includes only request identity, Session, Plugin, Package, mode, name, purpose, and whether approval is required; it does not broadcast source code.
An authorized page executes the following fixed sequence:
1. Call `runHostHalf` to start the target Host half or bind to the same attempt's already-started Host Run.
2. After Host success, call `getClientCode` with `pluginId + pluginRunId` to retrieve only the exact current Run's Client source.
3. The Client Runner evaluates the plugin in the page, establishes its Loader entry/Fiber, and installs its Guard, styles, Slots, and page-local state.
4. The page reports success, waiting, or failure through `resolveRequestRun` or `settleUserRun`.
5. The Host accepts the report only for the still-current exact Run, commits current or saves diagnostics, and broadcasts request completion so other pages clear their activities.
Host activation precedes Client activation so that the Client does not start before required Host handlers exist. A page may tear down the Host Run after Client failure only when that request created the Host Run; a page that merely bound to an existing Run does not own it.
The Client Orchestrator stores pending approvals and active orchestration by `pluginId`, so one Plugin cannot run two page activations concurrently. Host inventory can reconstruct omitted pending-approval items and approval-free automatic activation requests.
Client load state is a page-local fact. An active Host does not mean that the current page loaded the Client half. The UI has three primary states: gray “Ready” when no physical Run exists, yellow “Client ready to activate” when the Host runs but the current page has not successfully loaded the Client, and green “Running” when both halves are available in the current page. Approval and failure appear as additional states.
This version does not establish per-connection identity or multi-page quorum. The first still-valid Client success may commit process-wide current; each page's store independently records whether that page loaded the Client.
### Package-private Client-to-Host communication
A dynamic Package uses a private JSON channel for Client-to-Host calls: the Host registers methods for the current Run with `harness.handle(method, handler)`, and the Client calls them with `host.call(method, args)`. Every call is associated with `pluginId + pluginRunId`, and the Host rejects stopped or stale Runs. Arguments and return values must be lossless JSON; functions, React elements, Contexts, Service instances, and class objects are forbidden.
This channel serves only Client-to-Host calls within the same Package. It does not use public Remote Services or `ctx.remote` in dynamic code. The public Remote interface carries only the Runner's own control protocol and does not expose dynamic Packages.
### Dynamic code, Guard, and lifecycle
Host and Client both execute only plain JavaScript function bodies, without TypeScript, JSX, or bundler transformation. The Host executes in `node:vm`; the Client evaluates in a restricted closure. These contexts reduce misuse and provide instructional errors, but they are not security boundaries against malicious code.
By default, the model reads an optional Service through `ctx.get('serviceName')` and checks for `undefined`. A plugin object declares `inject` only when the Service is a hard dependency whose absence must park the Package and whose later arrival must reactivate it. Direct `ctx.serviceName` access is allowed only when the same plugin declares the corresponding inject.
Host and Client `timer` are same-named Cordis Services with the same interface, not global Builtins. A plugin that needs timers must declare `inject: ['timer']`; a timer created inside a React effect returns its disposer as cleanup.
The current Fiber owns every registration and reversible side effect. Event listeners, Services, Tools, handlers, timers, Slots, styles, and theme overrides register through `ctx.effect()`, `ctx.on()`, or official APIs that return disposers. Stopping, updating, failure rollback, or undefining tears down both halves' contributions. Theme overrides are layered by source and return a disposer so unloading restores the previous theme values.
Host, DSH, Cordis, and their Service instances, Event payloads, Slot props, Session/Conversation Snapshots, Tool state, and other runtime objects are internal live data. Dynamic code must not run `JSON.stringify`, `structuredClone`, recursive enumeration, full copying, or whole-object display on these objects or their descendants. It reads only leaf fields needed by the current task and constructs minimal owned data without Host references.
### Inspect Providers and Catalogs
Capability discovery uses three Tools: `cordis_inspect_list` lists Host/Client Provider manifests; `cordis_inspect_query` executes an explicit read-only query on the selected platform; and `cordis_inspect_self` queries the current Session's Plugins, Packages, source, version pointers, and runtime diagnostics.
Host and Client each own a `CordisInspectRegistry`. A Provider registers a platform-unique ID, description, methods, input schemas, and output schemas. Provider methods are explicitly allowlisted queries, not arbitrary Service-method forwarding; the Registry has no layered target and does not automatically turn business Service methods into executable Inspect methods.
The initial Providers are:
| Platform | Provider.method | Data source |
| --- | --- | --- |
| Host / Client | `Service.listService` | Static Service Catalog for each platform |
| Host / Client | `Event.listEvents` | Static Event Catalog for each platform |
| Host / Client | `Builtin.listBuiltins` | Hand-maintained definitions beside the evaluator/Guard |
| Host | `Tool.listTools` | Tool Registry actually visible to the current Agent |
| Client | `Slots.listSubTree` | Static Slot Catalog plus the page's live subtree/occupants |
| Client | `Theme.listTokens` | Read-only inspect export from ThemeService |
When the Client Registry changes, it synchronizes the complete manifest to the Host without storing duplicate directories per Session. Host queries execute locally. For a Client query, the Host broadcasts a request ID and a page executes the local Provider and responds. The Host accepts only the first successful result that passes output-schema validation; a failed page does not settle the request. If no page succeeds, the Tool remains pending until a later success or Tool-call cancellation.
Inspect data is used only before code is written to confirm capabilities, signatures, types, and mounting protocols. A plugin that needs runtime business data calls the actual Service or listens to the actual Event; it must not cache, display, or depend on Inspect/Catalog results.
`CordisCatalogProjector` generates Host and Client Service and Event Catalogs separately through TypeRT. The Slot AST generator scans `SlotMap`, registration options, standard props, owner props, and referenced types; the Slots Provider merges the static Catalog with the live tree at query time. ThemeService exports theme tokens, Builtins are maintained manually beside the evaluator/Guard, and Tool schemas come from the Registry.
Catalog generation scans real source signatures and then applies a model-visible allowlist. The allowlist may hide Services, members, `@deprecated` APIs, Runner-owned Services, and `cordis/*` control Events, but it must not rewrite method names, parameters, or return types for the remaining APIs. Guard may reject arguments, fix sources, or hide members, but it must respect source signatures.
Model-visible owner JSDoc requires only a complete description, `@param` for every parameter, `@returns` for every non-void return, `@mode` for Events, and descriptions for Slot/props fields. Usage recommendations, counterexamples, and cross-capability choices belong in the Skill rather than duplicated Catalog example fields.
### Model guidance layers
Model guidance has four layers:
- The System Prompt carries the stable runtime model, restrictions of both platforms, lifecycle, approval, version pointers, minimum code rules, and a usage map for the seven Tools. It still supports a minimally correct implementation when the Skill is unavailable.
- The `cordis-plugin-development` Skill carries requirement navigation, capability composition, recommendations, and counterexamples without copying complete schemas.
- Each Tool description states only that action's prerequisites, parameter semantics, synchronous or asynchronous result, and next step.
- Provider/Catalog results supply current exact names, signatures, parameters, Slot props, tokens, and runtime query results.
The System Prompt requires loading the Skill first, then listing/querying capabilities, and only then defining/running code. React examples in the Skill register into a Slot instead of returning a React Element directly from `apply()`. Examples use `React.createElement`, correct `ctx.get()`/`inject`, reversible effects, and minimal JSON RPC.
### `@pluginId` and Tool UI
The input system registers an `@pluginId` mention for the current Session. Selecting it injects only Plugin identity, the default baseline Package, version pointers, the active Run, and the latest status, not source code. The default baseline is selected in order from next, current, and the most recently defined Package. The model must read source through `cordis_inspect_self` before appending a Package in existing mode; an invalid mention must not silently create a replacement Plugin.
The `cordis_define` card presents Host and Client code in two tabs. A `cordis_run` card associates with one exact attempt through `pluginRunId` and reads the Client store to show pending approval, Client ready to activate, running, failed, replaced by a later Run, or Plugin removed.
A Package may register `key: "self"` into `tool.view.cordis`. At runtime, self binds to `pluginId + packageId`; the business Slot key omits `pluginRunId`, while owner props still provide the exact Run identity. The newest Run card for a Package owns the business UI, and earlier cards show that a newer Run exists. Cards react to store changes rather than scanning later Session Log entries or notifying one another.
The global Cordis panel has one fixed entry and groups rows by current and other Sessions. Its title and collapse action remain fixed while only the list scrolls. A normal row can select a Package and run, stop, or delete it. A failed update can retry next or select current to roll back. A pending-approval row exposes only the two allow actions and reject.
### Errors and model feedback
Technical errors crossing Host and Client preserve the original `message` and preserve `stack` when the error object provides it. Structured diagnostics include `pluginId`, `packageId`, `pluginRunId`, and one phase: approval, host-load, host-apply, client-load, client-apply, or client-render.
Host and Client Guards, Host evaluation and handlers, Client evaluation and apply, Slot `onEntryError`, and React ErrorBoundary all return errors to the owning Agent. The Client console also prints the original error object through `console.error`. A rendering error belongs to the exact Run and does not contaminate the immutable Package.
After a model-initiated asynchronous Run succeeds, is rejected, or fails technically, `agent.steer` wakes the owning Agent. A technical failure requires the model to read diagnostics, correct the same Plugin, and retry autonomously. A user rejection forbids an automatic repeat request. A user's manual run, stop, or removal in the panel is supplied through context injection to the next step without waking the model proactively.
## Alternatives considered
**Combine Define and Run.** This removes the previewable “defined but not running” state and mixes syntax errors, approval, runtime errors, and retries into one action. The design therefore uses immutable Define and independent Run actions.
**Use Package ID as Plugin ID.** A single-level ID cannot append immutable versions beneath a stable instance; updates would require stop, undefine, and a new define, while historical cards and `@` references could not retain object identity. The design therefore uses separate Plugin, Package, and Run identities.
**Provide a separate `cordis_update`.** Update has the same loading, approval, UI, diagnostics, and execution semantics as Run, so a separate Tool would duplicate the protocol. It is represented by `cordis_run mode:"update"`.
**Automatically restore the old physical Run after an update failure.** Automatic restoration combines “target failed” and “old version succeeded again” into one result. The design retains the old current pointer without restarting it, so the user explicitly chooses to retry next or run current.
**Block `cordis_run` until user approval and the final Client outcome.** Approval or page interaction may only occur after the current model turn ends. Blocking would deadlock and occupy the Tool indefinitely when no page exists. The Tool returns immediately, while stores, Inspect, and steering report the final outcome.
**Broadcast source from the Host and wait for Client acknowledgements with a timeout.** Broadcasting sends source to every page before authorization. A timeout cannot distinguish no page, a slow page, and no user action, and the Host would need compensating rollback. The protocol broadcasts only metadata, and an authorized page fetches source for the exact Run.
**Automatically restore every Host-active Package when a page starts.** This requires connection identity, a startup baseline, and cross-page consistency. The design accepts page-local Client state and lets the user load again from the panel.
**Connect Package halves through public Remote Services or `ctx.remote`.** This exposes dynamic Packages through the product RPC interface. Package-private `harness.handle`/`host.call` is sufficient for Client-to-Host JSON calls and rejects stale requests by `pluginRunId`.
**Expose every Service method automatically as an Inspect query.** This turns capability discovery into a business-call proxy that bypasses plugin approval and lifecycle. Providers expose only curated read-only queries; the Service Catalog only describes business method signatures.
**Put the complete API in the System Prompt or Skill.** Static text drifts and consumes context. The System Prompt retains stable rules, the Skill provides requirement navigation, and Provider/Catalog results return exact signatures and runtime directories.
**Require Slot owners to register props schemas at runtime.** Slot props already exist in TypeScript types and JSDoc, so duplicate registration creates a second authority. The Slot AST Catalog extracts the static protocol and only merges the live tree at query time.
**Write runtime state to the Session Log and restore it during replay.** Dynamic code and Fibers are process-local objects. Restoration would require re-executing historical code and reinterpreting approval. The Session retains only model-visible records; the Registry and page Runs are not restored.
**Make historical Run cards scan later Session Log entries.** This couples Tool views to the complete log order and later message structure. The page card index/store already tells cards by Package when a later Run replaces them or their Plugin is deleted.
## Acceptance criteria
- A new Plugin can be created only from a 3-to-6-character lowercase English prefix; final Plugin, Package, and Run IDs are allocated by the Host and use branded types.
- `cordis_define` validates only parameters and plain JavaScript syntax and returns an immutable Package; one Plugin can append versions while old source remains inspectable.
- `cordis_run` strictly validates run/update; Host-only activation completes synchronously, while a Client-bearing activation returns `awaiting-approval` or `starting` without waiting for the final browser outcome.
- A single check authorizes only the current Package, and a double check authorizes future versions of the same Plugin. Authorization survives technical failure, while rejection executes neither half.
- The Host activates first and the Client then fetches source for the exact Run. A Client-bearing Package does not commit current before Client success, and current/next permit retry and rollback after failure.
- One Plugin has at most one physical Run at a time. Stop tears down both halves while retaining definitions and pointers; undefine deletes every Package, authorization, and state.
- The current page distinguishes “Ready,” “Client ready to activate,” and “Running,” and a pending-approval row shows only approval actions.
- `tool.view.cordis` self binds Plugin + Package. The newest Run card for a Package exclusively owns its business UI, while old cards and deleted Plugins have explicit fallback states.
- Host and Client Guards reject imports, JSX, undeclared Services, and unavailable globals. Services, timers, Slots, styles, Tools, handlers, and theme overrides are torn down with the Run.
- Package-private RPC permits only lossless JSON from Client to Host and rejects a stale `pluginRunId`.
- Inspect list returns Host and Client manifests together. Query calls only explicit read-only methods, and a Client query waits for the first schema-valid successful result or cancellation.
- Service/Event Catalogs are generated per Host/Client and apply allowlists. `@deprecated` APIs, Runner-owned Services, and `cordis/*` control Events are hidden from the model; Slot query merges static props with the live subtree.
- `cordis_inspect_self` returns layered Plugin lists, Package summaries, and exact source/diagnostics. `@pluginId` does not inject source and keeps updates in the same Plugin.
- Asynchronous technical failures, Host handlers, Client Guards, and React rendering errors preserve message/stack and steer the owning Agent; user panel actions only inject context into the next step.
- The System Prompt, Skill, Tool descriptions, and Provider/Catalog layers follow this Note. The Prompt remains sufficient to generate a minimally correct plugin if the Skill is unavailable.
- Relevant workspaces pass `pnpm run build`; implementation adds Host/Client lifecycle, versioning, approval, Inspect, Guard, Tool-card, and real-application snapshot coverage.
## Risks
- **A process restart loses all dynamic objects.** Historical Tool cards remain, but the Registry is not restored; the user must define again.
- **Multi-page state is not strongly consistent.** The first valid Client success may commit current while Client loading and rendering state still differs across pages. This version does not introduce connection identity, quorum, or page aggregation.
- **Client Inspect may remain pending indefinitely.** The Host stores the latest manifest, but without a page successfully executing the Provider it cannot present stale data as a live result. If every page fails, the request waits until cancellation.
- **Cross-version authorization expands trust.** A double check permits future Packages of the same Plugin without further approval. The UI must clearly distinguish per-Package and cross-version authorization.
- **A failed update can leave current pointing to an old version that is not running.** Current identifies the last successful version, not the physical Run. UI, Inspect, and prompts must show active, current, and next together.
- **Restricted contexts are not security sandboxes.** Host Services, files, commands, network access, and Client UI are real capabilities. Allowlists and approval reduce misuse but do not isolate malicious code.
- **Catalogs, Guards, and source can drift.** Generators, allowlists, and owner JSDoc must be maintained together. Guard hiding rules must not create a second signature.
- **Builtins require manual declarations.** React, harness, host, styles, and Context methods have no unified scannable source, so injection implementations and Provider definitions must share one maintenance location.
- **Provider output schemas currently permit broad JSON.** The first version prioritizes Provider ownership, input validation, and Host/Client routing; output schemas can become narrower later.
- **Host and Client Guards are parallel implementations.** Their available environments and Cordis type interfaces differ, so they remain separate. A shared specification should be extracted only if it removes code without obscuring security policy.
@@ -0,0 +1,270 @@
# Agent Note: Cordis Host/Client 动态插件运行体系
Status: proposed
[English](2026-08-08-cordis-web-dynamic-packages.md) | 中文
## Problem
模型需要在不修改仓库源码、不重新构建应用、不刷新浏览器的前提下,临时扩展当前 DSH 进程。扩展既可能运行在 Host 的 Node.js 进程,也可能运行在 Client 浏览器页面,还可能由 Host 取数、Client 展示,共同组成一个插件。
这项能力不能只是“执行一段代码”。模型需要在写代码前发现两端允许使用的 Service、Event、Builtin、Slot 和主题 token;用户需要先预览代码,再决定是否允许 Client 代码进入页面;同一个插件需要追加不可变版本、失败后重试或回退;运行后的异步错误需要回到模型,而不是只留在服务端日志或浏览器控制台。
如果把定义、审批、运行、版本切换、能力发现和 UI 状态塞进一个动作,会产生无法稳定解释的状态:定义成功是否等于运行成功,升级失败后哪个版本仍是成功版本,页面没有响应时 Tool 应等待多久,同一个 Package 多次运行时哪张历史卡片承载业务 UI,以及 Client 页面局部装载状态是否能代表 Host 的进程级状态。
## Proposal
### 核心原则
- Host 保存 Plugin、Package、Run、审批和版本指针的唯一进程级权威状态。
- Client 只保存当前页面的审批交互、装载结果、Slot 贡献、业务视图和页面局部错误。
- Define 只创建不可变代码版本;Run 只激活一个已定义版本。
- 版本切换只有在目标 Package 完成要求的 Host/Client 激活后才提交 `currentPackageId`
- 模型写代码前通过 Inspect Provider 查询能力;Inspect 结果只辅助编码,不作为插件运行时业务数据。
- Host 与 Client 动态代码都使用受限的 plain JavaScript 上下文,并把可撤销副作用挂到 Cordis 生命周期。
- Client 代码进入页面前需要用户授权;授权范围可以是单个 Package,也可以是同一 Plugin 的后续版本。
- Tool 调用不等待当前轮结束后才可能发生的审批或浏览器操作;异步结局通过状态存储和模型 steering 反馈。
### 包职责与依赖方向
动态运行体系由 `packages/self-modification/` 下四个包组成:
| 包 | npm 包名 | 职责 |
| --- | --- | --- |
| `tool-cordis` | `@deepseek-ai/dsh-tool-cordis` | 注册 System Prompt、七个模型 Tool、Host Inspect Provider、`@pluginId` 上下文注入和 Tool 展示元数据 |
| `cordis-host-runner` | `@deepseek-ai/dsh-cordis-host-runner` | 保存权威 Registry,分配 ID,执行 Host 代码,管理版本、审批、Run、私有 handler、Inspect 路由和模型反馈 |
| `cordis-client-runner` | `@deepseek-ai/dsh-cordis-client-runner` | 在浏览器同步 Inspect manifest,编排审批后的 Host→Client 激活,求值 Client 代码,管理 Guard、Loader/Fiber、timer、样式和 teardown |
| `ui-cordis` | `@deepseek-ai/dsh-client-ui-cordis` | 展示 Define/Run Tool 卡片、全局 Cordis 面板、审批控件、版本选择、运行状态和 Package 自定义业务视图 |
`tool-cordis` 只依赖 Host Runner 的进程内服务,不导入 Client 实现。`ui-cordis` 只消费 Client Runner face 和 Client-safe wire 类型,不导入 Host 实现。Host 与 Client 的运行控制通过已有生成 Remote 面和转发事件连接,网关不拥有动态 Plugin 的领域逻辑。
### 领域对象
#### Plugin
Plugin 是可持续修改的动态插件实例,由品牌类型 `CordisDynamicPluginId` 标识,例如 `clock-1`。新建 Plugin 时,模型只提交 3 至 6 位小写英文语义前缀;Host 添加进程内唯一数字后缀。完整 `pluginId` 不能由模型指定。
Plugin 属于定义它的 Session。模型 Tool 只能读取和操作当前 Session 的 Plugin;全局 Client 面板可以列出所有 Session 的 Plugin,但每个动作仍使用该行携带的 owner Session 执行。
#### Package
Package 是 Plugin 下的不可变代码版本,由 `CordisDynamicPackageId` 标识,例如 `pkg-2`。它包含名称、用途、可选 Host 代码和可选 Client 代码,且至少包含一侧。每次 `cordis_define` 都创建新 Package;已有 Package 不允许原地修改。
同一个 Plugin 可以拥有多个 Package,但同一时刻最多只有一个物理 Run。Package 是否含 Host 或 Client 半只决定激活步骤,不改变版本身份。
#### Plugin Run
Plugin Run 是一次具体激活尝试,由 `CordisDynamicPluginRunId` 标识,例如 `run-3`。每次新的激活尝试都会分配新 ID,包括审批后失败、重试同一 Package 和版本更新。`pluginRunId` 把审批、Host 激活、Client 装载、私有 RPC、Tool 卡片和错误关联到同一次尝试。
Host 分开保存当前物理 Run 与 `latestRun`。物理 Run 表示此刻仍可调用和撤销的激活;`latestRun` 表示最近一次尝试的审批、阶段、两侧状态和诊断。一次失败可以没有存活的物理 Run,但仍留下可查询的 attempt。
#### 版本指针
- `currentPackageId` 是最近一次完成要求的激活流程的 Package。停止插件、开始更新或更新失败都不清除它。
- `nextPackageId` 是正在等待审批、正在激活、等待 Client、或最近失败的目标 Package。目标成功提交为 current 后清除。
Host-only Package 在 Host 成功建立 Fiber 后提交 current。包含 Client 的 Package 在 Host 激活成功且至少一个 Client 成功建立对应装载后提交 current。因硬依赖缺失而被 Cordis park 为 waiting 的 Fiber仍是成功建立的生命周期对象,不等同于解析或 `apply` 失败。
更新目标失败时不自动重启旧物理 Run。旧 `currentPackageId` 继续表示最后成功版本,失败目标保留为 `nextPackageId`。用户或模型可以重试 next,也可以以 `mode: "run"` 重新激活 current 完成回退。
### Host 权威状态与持久性
`DynamicCordisRunnerService` 及其内部 Registry 是当前 DSH 进程内的唯一权威,保存:
- Plugin 的 Session 归属和不可变 Package 集合;
- `currentPackageId``nextPackageId`、物理 Run 和 `latestRun`
- 单 Package 授权与 Plugin 跨版本授权;
- 待处理的 Client 激活请求;
- Host Fiber、Package 私有 handler、等待中的 Service 和最近诊断;
- Host 与 Client Inspect Registry 的目录和查询路由。
这些对象不写入配置或磁盘,也不在进程重启后恢复。Session Log 可以保留 Tool 调用、结果和卡片所需元数据,但不会重放动态代码来恢复 Registry。进程重启后历史卡片仍可作为对话记录存在,原 `pluginId``packageId` 不再可运行。
运行态不作为可恢复状态写入 Session projection。页面刷新或新页面打开不会自动恢复 Client 半;自动恢复会重新引入连接身份、启动期 baseline 和跨页面一致性协议,不属于当前设计。
### Define、Run 与版本切换
`cordis_define` 有两种模式:新建 Plugin 时提交 `idPrefix`;修改现有 Plugin 时提交精确 `pluginId`。代码统一为 `code: { host?, client? }`。Define 只校验参数和 plain JavaScript 语法,记录不可变源码并返回最终 ID。它不执行 `apply`、不产生审批、不改变版本指针,也不隐式运行。
不提供独立 `cordis_update``cordis_run` 通过 `mode` 表达激活意图:
| 版本关系 | `mode` |
| --- | --- |
| 尚无 `currentPackageId` | `run` |
| 目标等于 current,包括重启、重试或回退 | `run` |
| 目标与已有 current 不同 | `update` |
| 更新失败后重试 `nextPackageId` | `update` |
Run 先验证 Plugin/Package 归属、版本关系和是否已有转换在进行,再创建 `pluginRunId`、写入 `latestRun``nextPackageId`
Host-only Package 在 Tool 调用内完成 Host 激活,并同步返回 `running` 或失败。包含 Client 的 Package不在 Tool 调用内等待浏览器终局:未授权时登记审批并返回 `awaiting-approval`;已授权时登记自动 Client 激活并返回 `starting`。这两种返回都表示请求已建立,不表示完整激活成功。
目标真正开始激活时,Host 先停止旧物理 Run,再执行目标 Host 半。Host 成功后才允许 Client 获取精确 `pluginRunId` 对应的源码并装载。Client 成功后 Host 提交版本指针;任何阶段失败都记录到该 attempt,不把旧版本重新启动伪装成目标成功。
`cordis_stop` 撤销当前 Host/Client Run 及待审批请求,但保留 Plugin、Package、授权和版本指针。`cordis_undefine` 先停止,再删除 Plugin、Package、授权和版本指针;删除后历史卡片只显示“插件已移除”。
### Client 审批与授权
包含 Client 代码的 Package 在第一次激活前需要用户授权,因为它将在用户页面中运行模型生成的代码。审批面板提供三个动作:
- 单勾允许当前 Package;同一 Package 后续重跑不再审批,新 Package 仍需审批。
- 双勾允许当前 Plugin 的后续版本;新 Package、更新、重试和回退不再逐版本审批。
- 拒绝结束当前请求,不执行 Host 或 Client 代码;模型不得在用户没有新要求时立即重复申请。
授权在用户允许时写入 Host Registry,即使随后发生技术失败也保留。面板直接运行 Package 时,用户点击本身授权该 Package。
待审批行只显示单次允许、跨版本允许和拒绝,不同时提供运行、停止或删除。发现新审批时面板自动展开;自动展开失败或被收起时,固定入口和行状态仍显示待审批数量与状态。
### Client 激活编排
Host 通过 `cordis/request-run` 发送 Client 激活请求。请求只包含请求身份、Session、Plugin、Package、mode、名称、用途和是否需要审批,不广播源码。
获得授权的页面按固定顺序执行:
1. 调用 `runHostHalf`,启动目标 Host 半或绑定同一次 attempt 已启动的 Host Run。
2. Host 成功后,以 `pluginId + pluginRunId` 调用 `getClientCode`,只取得当前精确 Run 的 Client 源码。
3. Client Runner 在页面求值插件,建立 Loader entry/Fiber,安装 Guard、样式、Slot 和页面局部状态。
4. 页面调用 `resolveRequestRun``settleUserRun` 回报成功、waiting 或失败。
5. Host 接受仍有效的精确 Run 回报,提交 current 或保存诊断,并广播请求结束,其他页面清理活动。
Host 激活先于 Client,避免 Client 在所需 Host handler 尚未存在时启动。只有本次请求实际创建的 Host Run 才能因本页 Client 失败而撤销;只是绑定既有 Run 的页面没有其所有权。
Client Orchestrator 按 `pluginId` 保存待审批和正在编排的活动,同一个 Plugin 不并发执行两次页面激活。Host inventory 可重建遗漏的待审批项和无需审批的自动激活请求。
Client 装载状态是页面局部事实。Host active 不代表当前页面已装载 Client 半。UI 使用三种主要状态:无物理 Run为灰色“待激活”,Host 已运行但当前页面 Client 未成功装载为黄色“Client 待激活”,当前页面两侧可用为绿色“运行中”。审批中和失败作为额外状态显示。
当前版本不建立 per-connection 身份或多页面法定人数。第一个仍有效的 Client 成功回报可以提交进程级 current;其他页面是否装载由各自页面 store 表示。
### Package 私有 Client→Host 通信
动态 Package 通过私有 JSON 通道从 Client 调用 HostHost 使用 `harness.handle(method, handler)` 注册当前 Run 的方法,Client 使用 `host.call(method, args)` 调用。每次调用关联 `pluginId + pluginRunId`,Host 拒绝已停止或过期 Run。参数和返回值必须是无损 JSON,不允许函数、React 元素、Context、Service 实例或类对象。
该通道只服务同一 Package 的 Client→Host 调用,不使用公开 Remote Service 或动态代码中的 `ctx.remote`。公开 Remote 面只承载 Runner 自己的控制协议,不向动态 Package 暴露。
### 动态代码、Guard 与生命周期
Host 和 Client 都只执行 plain JavaScript 函数体,不经过 TypeScript、JSX 或 bundler 转译。Host 运行在 `node:vm`,Client 在受限闭包中求值。两端上下文用于减少误用并提供教学错误,不是恶意代码安全边界。
模型默认通过 `ctx.get('serviceName')` 读取可选 Service 并判断 `undefined`。只有 Service 是硬依赖、缺失时 Package 必须 waiting 并在 Service 出现后重新激活时,才在插件对象声明 `inject`。直接访问 `ctx.serviceName` 只在同一插件声明对应 inject 时允许。
Host 与 Client 的 `timer` 都是同名 Cordis Service,使用一致接口,不是全局 Builtin。需要 timer 的插件必须声明 `inject: ['timer']`React effect 中创建的 timer 把 disposer 作为 cleanup 返回。
所有注册和可撤销副作用由当前 Fiber 拥有。Event listener、Service、Tool、handler、timer、Slot、样式和主题覆盖通过 `ctx.effect()``ctx.on()` 或返回 disposer 的官方 API 注册。停止、更新、失败回滚或 undefine 时撤销两端贡献。Theme override 必须按 source 分层并返回 disposer,使卸载后恢复此前主题值。
宿主、DSH、Cordis 及其 Service、Event payload、Slot props、Session/Conversation Snapshot、Tool 状态和其他运行时对象是内部 live data。动态代码不得对这些对象或其子对象执行 `JSON.stringify``structuredClone`、递归枚举、全量复制或整体展示;只能读取当前任务所需叶子字段,构造不含宿主引用的最小自有数据。
### Inspect Provider 与 Catalog
能力发现分为三个 Tool`cordis_inspect_list` 列 Host/Client Provider manifest`cordis_inspect_query` 执行指定平台的显式只读查询;`cordis_inspect_self` 查询当前 Session 的 Plugin、Package、源码、版本指针和运行诊断。
Host 和 Client 各自拥有 `CordisInspectRegistry`。Provider 注册平台内唯一 ID、说明、method、输入 schema 和输出 schema。Provider method 是显式白名单查询,不是任意 Service 方法透传;Registry 不维护分层 target,也不自动把业务 Service 方法变成可执行 Inspect method。
首批 Provider 为:
| Platform | Provider.method | 数据来源 |
| --- | --- | --- |
| Host / Client | `Service.listService` | 各平台 Service 静态 Catalog |
| Host / Client | `Event.listEvents` | 各平台 Event 静态 Catalog |
| Host / Client | `Builtin.listBuiltins` | evaluator/Guard 附近的手工定义 |
| Host | `Tool.listTools` | 当前 Agent 真正可见的 Tool Registry |
| Client | `Slots.listSubTree` | Slot 静态 Catalog与页面 live subtree/occupants |
| Client | `Theme.listTokens` | ThemeService 的只读 inspect export |
Client Registry 变化后向 Host 同步完整 manifest,不按 Session 保存重复目录。Host query 本地执行;Client query 由 Host 广播 request ID,页面调用本地 Provider 后回送。Host 只接受第一个通过输出 schema 校验的成功结果;失败页面不抢占请求。没有页面成功回答时 Tool 保持 pending,直到后续成功或 Tool call 取消。
Inspect 数据只用于写代码前确认能力、签名、类型和挂载协议。插件运行时需要业务数据时必须调用实际 Service 或监听实际 Event,不能缓存、展示或依赖 Inspect/Catalog 返回值。
`CordisCatalogProjector` 使用 TypeRT 分别生成 Host/Client Service 与 Event CatalogSlot AST 生成器扫描 `SlotMap`、注册选项、standard props、owner props 和引用类型;Slots Provider 查询时合并静态 Catalog 与 live tree。Theme token 由 ThemeService 导出,Builtin 在 evaluator/Guard 附近手工维护,Tool schema 来自 Registry。
Catalog 扫描真实源码签名,再应用 model-visible 白名单。白名单可以隐藏 Service、成员、`@deprecated` API、Runner 自身服务和 `cordis/*` 控制 Event,但不能改写剩余 API 的方法名、参数和返回类型。Guard 可以拒绝参数、固定来源或屏蔽成员,但必须尊重源码签名。
模型可见 owner JSDoc 只要求完整 description、每个参数的 `@param`、非 void 返回的 `@returns`、Event 的 `@mode`,以及 Slot/props 字段说明。调用推荐、反例和跨能力选择放入 Skill,不在 Catalog 增加重复 example 字段。
### 模型指导分层
模型指导分为四层:
- System Prompt 保存稳定运行模型、两端限制、生命周期、审批、版本指针、最低代码规范和七个 Tool 的使用地图。Skill 不可用时它仍须支持最低限度正确实现。
- `cordis-plugin-development` Skill 保存需求导航、能力组合、推荐和反例,不复制完整 schema。
- 每个 Tool description 只说明该动作的前置条件、参数语义、同步/异步结果和下一步。
- Provider/Catalog 返回当前精确名称、签名、参数、Slot props、token 和运行时查询结果。
System Prompt 要求先加载 Skill,再 list/query,之后 define/run。Skill 中 React 示例必须注册到 Slot,不能从 `apply()` 直接返回 React Element;示例使用 `React.createElement`、正确 `ctx.get()`/`inject`、可逆 effect 和最小 JSON RPC。
### `@pluginId` 与 Tool UI
输入系统为当前 Session 注册 `@pluginId` mention。选择后只注入 Plugin 身份、默认基准 Package、版本指针、活动 Run 和最近状态,不注入源码。默认基准依次选择 next、current、最近定义的 Package。模型必须先用 `cordis_inspect_self` 读取源码,再以 existing 模式追加 Package;引用失效时不能静默创建替代 Plugin。
`cordis_define` 卡片以 Host/Client 两个子页签展示代码。`cordis_run` 卡片由 `pluginRunId` 关联精确 attempt,并读取 Client store 显示待审批、Client 待激活、运行中、失败、已被后续 Run 替代或 Plugin 已移除。
Package 可以向 `tool.view.cordis` 注册 `key: "self"`。运行时把 self 绑定为 `pluginId + packageId`;业务 Slot key 不含 `pluginRunId`,但 owner props 仍提供精确 Run 身份。同一 Package 最新 Run 卡片承载业务 UI,更早卡片显示已有更新运行。卡片通过 store 响应变化,不扫描后续 Session Log,也不互相通知。
全局 Cordis 面板使用一个固定入口,按当前会话和其他会话分组。面板标题和收起操作固定,只有列表滚动。普通行可选择 Package并运行、停止或删除;失败更新可重试 next 或选择 current 回退;待审批行只提供两个允许动作和拒绝。
### 错误与模型反馈
跨 Host/Client 的技术错误保留原始 `message`,并在错误对象提供时保留 `stack`。结构化诊断包含 `pluginId``packageId``pluginRunId` 和阶段:approval、host-load、host-apply、client-load、client-apply 或 client-render。
Host/Client Guard、Host 求值与 handler、Client 求值与 apply、Slot `onEntryError` 和 React ErrorBoundary 都把错误回到 owning Agent。Client 控制台同时以 `console.error` 打印原始 error 对象。渲染错误属于精确 Run,不污染不可变 Package。
模型发起的异步 Run 在成功、拒绝或技术失败后使用 `agent.steer` 唤醒 owning Agent。技术失败要求读取诊断、在同一 Plugin 修正并自主重试;用户拒绝则禁止自动重复申请。用户在面板手动运行、停止或移除通过 context injection 告知下一 step,但不主动唤醒模型。
## Alternatives considered
**Define 与 Run 合并。** 这会失去“已定义但未运行”的可预览状态,把语法错误、审批、运行错误和重试混成一个动作,因此拆为不可变 Define 和独立 Run。
**Package ID 同时作为 Plugin ID。** 单层 ID 无法表达稳定实例下追加不可变版本,更新只能 stop、undefine、重新 define,历史卡片和 `@` 引用也无法保持同一对象,因此采用 Plugin、Package、Run 三层身份。
**提供独立 `cordis_update`。** Update 的装载、审批、UI、诊断和 Run 相同,独立 Tool 只复制协议,因此合并到 `cordis_run mode:"update"`
**更新失败后自动恢复旧物理 Run。** 自动恢复会把“目标失败”和“旧版本重新成功”混成一个结果。当前设计保留旧 current 指针但不自动重启,让用户明确选择重试 next 或 run current。
**让 `cordis_run` 阻塞到用户审批和 Client 终局。** 审批或页面操作可能只能在当前模型轮结束后发生,阻塞会形成死锁,并在无页面时无限占用 Tool。当前设计立即返回,通过 store、Inspect 和 steering 报告终局。
**Host 广播源码并用超时等待 Client ack。** 广播会在授权前把代码发给所有页面;超时无法区分没有页面、页面慢和用户未操作;Host 还要维护补偿式回滚。当前协议只广播元数据,由获准页面按精确 Run 拉取源码。
**页面启动时自动恢复所有 Host active Package。** 这要求连接身份、启动期 baseline 和跨页面一致性。当前设计接受页面局部 Client 状态,用户可在面板重新装载。
**通过公开 Remote Service 或 `ctx.remote` 连接 Package 两半。** 这会把动态 Package 暴露到产品级 RPC 面。Package 私有 `harness.handle`/`host.call` 足以承载 Client→Host JSON 调用,并能按 `pluginRunId` 拒绝陈旧请求。
**把所有 Service 方法自动暴露成 Inspect query。** 这会把能力发现变成业务调用代理,绕过插件审批和生命周期。Provider 只暴露策展的只读查询,Service Catalog 只描述业务方法签名。
**把完整 API 写进 System Prompt 或 Skill。** 固化文本会漂移并占用上下文。System Prompt 保留稳定规则,Skill 负责需求导航,精确签名和运行时目录由 Provider/Catalog 返回。
**要求 Slot owner 在运行时注册 props schema。** Slot props 已存在于 TypeScript 类型和 JSDoc 中,重复注册会制造第二份权威。当前设计用 Slot AST Catalog 提取静态协议,只在查询时合并 live tree。
**把运行态写入 Session Log 并在 replay 恢复。** 动态代码和 Fiber 是进程局部对象,恢复要求重新执行历史代码并重新解释审批。Session 只保留模型可见记录,Registry 和页面 Run 不恢复。
**让历史 Run 卡片扫描后续 Session Log。** 这会让 Tool view 依赖全量日志顺序和后续消息结构。页面 card index/store 已能按 Package 告知旧卡片被替代或 Plugin 被删除。
## Acceptance criteria
- 新 Plugin 只能由 3 至 6 位小写英文前缀创建,最终 Plugin、Package 和 Run ID 由 Host 分配并使用品牌类型。
- `cordis_define` 只做参数和 plain JavaScript 语法检查,返回不可变 Package;同一 Plugin 可以追加版本,旧源码保持可 inspect。
- `cordis_run` 严格校验 run/updateHost-only 同步完成,Client-bearing 返回 `awaiting-approval``starting`,不等待页面终局。
- 单勾只授权当前 Package,双勾授权同一 Plugin 后续版本;授权在技术失败后仍保留,拒绝不执行两侧代码。
- Host 先激活,Client 后取精确 Run 源码;Client 成功前不提交 Client-bearing Package 的 current,失败后 current/next 可用于重试和回退。
- 一个 Plugin 同时最多一个物理 Run;stop 撤销两端贡献但保留定义和指针,undefine 删除全部 Package、授权和状态。
- 当前页面能区分“待激活”“Client 待激活”和“运行中”,待审批时只显示审批动作。
- `tool.view.cordis` 的 self 绑定 Plugin + Package;同 Package 最新 Run 卡片独占业务 UI,旧卡片和已删除 Plugin 有明确退化状态。
- Host/Client Guard 拒绝 import、JSX、未声明 Service 和不可用全局;Service、timer、Slot、样式、Tool、handler 和主题覆盖随 Run teardown。
- Package 私有 RPC 只允许 Client→Host 无损 JSON,并拒绝陈旧 `pluginRunId`
- Inspect list 一次返回 Host/Client manifestquery 只调用显式只读方法,Client 查询等待首个 schema-valid 成功结果或取消。
- Service/Event Catalog 分 Host/Client 生成并应用白名单,`@deprecated` API、Runner 自身服务和 `cordis/*` 控制 Event 不向模型暴露;Slot query 合并静态 props 与 live subtree。
- `cordis_inspect_self` 分层返回列表、Package 摘要和精确源码/诊断;`@pluginId` 不直接注入源码且更新留在同一 Plugin。
- 异步技术失败、Host handler、Client Guard 和 React 渲染错误保留 message/stack 并 steering owning Agent;用户面板操作只注入下一 step context。
- System Prompt、Skill、Tool description 和 Provider/Catalog 按本 Note 分层,Skill 不可用时 Prompt 仍足以生成最低限度正确的插件。
- 相关工作区 `pnpm run build` 通过;实现阶段补齐 Host/Client lifecycle、版本、审批、Inspect、Guard、Tool 卡片与真实应用快照覆盖。
## Risks
- **进程重启丢失全部动态对象。** 历史 Tool 卡片仍在,但 Registry 不恢复;用户必须重新 define。
- **多页面状态不是强一致系统。** 第一个有效 Client 成功结果可以提交 current,各页面的 Client 装载和渲染状态仍可能不同;当前不引入连接身份、法定人数或页面聚合。
- **Client Inspect 可能长期 pending。** Host 保存最近 manifest,但没有页面成功执行 Provider 时不能用旧数据伪装 live 结果;多个页面都失败时请求等待到取消。
- **跨版本授权扩大信任范围。** 双勾允许同一 Plugin 后续 Package 无需再次审批;UI 必须清楚区分单次和跨版本授权。
- **失败更新可能留下 current 指向旧版本但旧版本未运行。** current 表示最后成功版本,不表示当前物理 Run;UI、Inspect 和提示必须同时展示 active、current 和 next。
- **受限上下文不是安全沙箱。** Host Service、文件、命令、网络和 Client UI 都是真实能力;白名单与审批降低误用,不隔离恶意代码。
- **Catalog、Guard 和源码可能漂移。** 生成器、白名单和 owner JSDoc 必须共同维护;Guard 的隐藏策略不能产生另一套签名。
- **Builtin 依赖手工声明。** React、harness、host、styles 和 Context 方法没有统一可扫描入口,注入实现与 Provider 定义必须放在同一维护位置。
- **Provider 输出 schema 当前允许较宽的 JSON。** 首版优先完成 Provider 所有权、输入校验和 Host/Client 路由;更窄的输出 schema 后续再收紧。
- **Host 与 Client Guard 存在平行实现。** 两侧开放环境和 Cordis 类型面不同,当前保留各自实现;公共规格只有在能减少代码且不隐藏安全策略时再提取。
@@ -0,0 +1,420 @@
---
name: cordis-plugin-development
description: Create, modify, debug, or extend dynamic Cordis Plugins, including Host Services and Events, Client Slot and theme UI, Package-private Client-to-Host calls, dynamic Tools, version updates, approval failures, and runtime diagnostics. Use this Skill to route a user request to the correct platform and Inspect Provider, then define, run, repair, or roll back the Plugin.
---
# Develop Dynamic Cordis Plugins
First determine whether a capability belongs on Host or Client, then query the real interface before writing code. Never infer a complete API from a Service name, Event payload, Slot props, theme token, or example.
## Standard workflow
1. Call `cordis_inspect_list` to obtain the Providers, methods, and schemas currently registered on Host and Client.
2. Select the smallest set of `cordis_inspect_query` calls needed to read the exact Services, Events, Builtins, Slots, Theme tokens, or Tools that the implementation will use.
3. For a new Plugin, design its first Package. To modify an existing Plugin, first use `cordis_inspect_self(pluginId, packageId)` to read the base source and diagnostics.
4. Write plain JavaScript in `code.host`, `code.client`, or both, then call `cordis_define`.
5. Call `cordis_run` with the final `pluginId` and `packageId` returned by define.
6. Handle approval, waiting, Client loading, and render failures from the Run card, steering messages, or `cordis_inspect_self`.
7. Use `cordis_stop` to disable the Plugin temporarily. Use `cordis_undefine` only when it is no longer needed.
Do not wait in the same turn for user approval or asynchronous browser results. After `cordis_run` returns `awaiting-approval` or `starting`, end the current Tool flow and wait for the system to report the final outcome through state updates and steering.
## Tool usage guidance
| Tool | Use it when | Do not |
| --- | --- | --- |
| `cordis_inspect_list` | Discover current Host/Client Providers and method schemas in one call; refresh after the runtime capability directory changes | Hard-code Provider names and skip list; treat a manifest as business data |
| `cordis_inspect_query` | Confirm exact Service methods, Event modes, Builtins, Slots, tokens, or Tool schemas before writing code | Use it instead of calling a real Service from the Plugin; assume a Client query will finish without a responding page |
| `cordis_inspect_self` | List current Plugins, inspect version pointers, or read exact Package source and runtime diagnostics | Fetch all source just to build a list; use it to modify or start a Plugin |
| `cordis_define` | Create a Plugin's first version or append an immutable Package to an existing Plugin; let the user preview the code first | Expect define to execute `apply`, request approval, or update current |
| `cordis_run` | Activate an exact Package; use `run` for first activation, restart, or rollback, and `update` to switch versions | Use `run` to switch versions implicitly; treat pending or starting as success |
| `cordis_stop` | Pause current effects while preserving Packages, grants, and version pointers for later use | Use stop to mean permanent deletion |
| `cordis_undefine` | Permanently remove a Plugin and all of its Packages and clear historical business views | Call it while rollback, inspection, or restart is still needed |
## Choose a platform
| Requirement | Preferred platform | Inspect first |
| --- | --- | --- |
| Files, commands, processes, or networking | Host | `fs`, `bash`, `subprocess`, `pty`, and `web` in `Service.listService` |
| Agents, durable Session data, or Host lifecycle | Host | The relevant Service and `Event.listEvents` |
| Register a dynamic Tool callable in the next model step | Host | `harness` in `Builtin.listBuiltins`, plus `Tool.listTools` |
| Page theme, layout, or current page state | Client | `Theme.listTokens` and Client `Service.listService` |
| Conversation Snapshot or session/workspace lists | Client | The target Slot's standard props and owner props |
| Settings pages, sidebars, input areas, overlays, or Tool cards | Client | `Slots.listSubTree` |
| Fetch on Host and display on Client | Both | Host Service + `harness.handle`; Client Slot + `host.call` |
Prefer the capability closest to the data owner. If Slot props already provide the Conversation Snapshot, do not fetch it again through Host. If only the Package's own styles need to change, do not override the global theme. If only a small entry point is needed, do not replace an entire product UI region.
## Provider navigation
Select methods from the actual `cordis_inspect_list` result. Common initial methods include:
- `Service.listService`: without `service`, returns every callable Service with its purpose and exact method signatures. Query the selected `service` again for access rules, structured method descriptions/parameters/returns, and only its referenced types.
- `Event.listEvents`: without `event`, returns every Event with its purpose, dispatch mode, and exact listener signature. Query the selected `event` again for its structured listener contract and only its referenced types; a Waterfall listener must call `next()`.
- `Builtin.listBuiltins`: returns evaluator-provided symbols and signatures that cannot be obtained through `ctx.get()`.
- `Slots.listSubTree`: without `root`, returns compact live trees with each Slot's purpose, kind, scope, registration keys, replacement risk, and children. With an exact `root`, it also returns that selected Slot's full contract, props, and current occupants while keeping descendants compact.
- `Theme.listTokens`: returns theme tokens that may currently be queried and overridden; it does not modify the theme.
- `Tool.listTools`: returns Tool schemas actually visible to the current Agent, including dynamically registered Tools.
Provider names, methods, and inputs must come from the current list result. The Service/Event Catalog describes which interfaces this version permits; it does not guarantee that a Service is currently mounted. At runtime, use real Services and Events rather than caching or displaying Catalog query results.
## Execution environment
Both `code.host` and `code.client` are plain JavaScript function bodies that return a Cordis Plugin. They are not compiled by TypeScript, JSX, or a bundler.
Do not use:
- `import`, `require`, TypeScript types, `as`, decorators, or JSX;
- globals not confirmed by `Builtin.listBuiltins`;
- guessed access to `window`, `document`, `process`, `Buffer`, `fetch`, or native timers.
Client React code must use `React.createElement(...)`.
Correct:
```js
return {
apply(ctx) {
const slots = ctx.get('slots')
if (slots === undefined) return
slots.inject('tool.view.cordis', () => slots.register(
{ name: 'tool.view.cordis', key: 'self' },
() => React.createElement('div', null, 'Hello'),
))
},
}
```
Incorrect:
```jsx
return {
apply(ctx) {
return <div>Hello</div>
},
}
```
JSX is not the only problem in this example. `apply()` registers lifecycle contributions and cannot return a React Element as the Plugin result. UI must be registered in a queried Slot.
## Access Services
Read optional capabilities with `ctx.get(name)` by default and handle their absence:
```js
return {
apply(ctx) {
const service = ctx.get('serviceName')
if (service === undefined) return
service.someMethod()
},
}
```
Declare `inject` only when a Service is a hard dependency and the Plugin must enter waiting until Cordis reactivates it after the Service appears:
```js
return {
inject: ['requiredService'],
apply(ctx) {
ctx.requiredService.someMethod()
},
}
```
Do not overuse `inject` merely to avoid an `undefined` check. Do not access `ctx.requiredService` without declaring the injection; the Guard rejects undeclared dependencies.
## Manage side effects
Every contribution must be removed after the Plugin is stopped, updated, or removed. Prefer Cordis lifecycle APIs:
- Use `ctx.on()` to register Event listeners.
- Use `ctx.effect()` to own an external subscription that returns a disposer.
- Retain disposers returned by Cordis Service, Tool, Slot, timer, and theme APIs.
- Do not create process-wide or page-wide side effects at module scope or outside `apply()`.
Recommended:
```js
return {
apply(ctx) {
const service = ctx.get('serviceName')
if (service === undefined) return
ctx.effect(() => service.subscribe((value) => {
console.log(value)
}))
},
}
```
If `subscribe()` does not return a disposer, first query whether the Service provides a supported cleanup mechanism. Do not assume unload automatically removes arbitrary third-party callbacks.
## Host and Client timers
On both platforms, the timer is a Service named `timer` with the same interface; it is not a Builtin. Query `{ "service": "timer" }` through the corresponding platform's `Service.listService` before using it. Declare `inject: ['timer']` before using the timer mixin.
One-shot delay:
```js
return {
inject: ['timer'],
apply(ctx) {
const onClick = () => {
ctx.timeout(() => console.log('done'), 300)
}
// Pass onClick to a queried Slot UI.
},
}
```
Periodic work in a React component:
```js
return {
inject: ['timer'],
apply(ctx) {
function Clock() {
React.useEffect(() => ctx.interval(() => console.log('tick'), 1000), [])
return React.createElement('div', null, 'Running')
}
// Register Clock in a queried Slot.
},
}
```
Incorrect:
```js
return {
apply(ctx) {
ctx.timeout(() => console.log('invalid'), 300)
},
}
```
```js
setTimeout(() => console.log('invalid'), 300)
```
The first example does not declare the timer hard dependency. The second uses a global timer that does not exist.
## Listen to Events
Query the Event Provider first to confirm the platform, parameter order, return value, and `mode`.
Ordinary emit Event:
```js
return {
apply(ctx) {
ctx.on('some/event', (payload) => {
console.log(payload)
})
},
}
```
The last parameter of a Waterfall Event is `next`. Unless the listener intentionally stops downstream processing, it must call and return it:
```js
return {
apply(ctx) {
ctx.on('some/waterfall', (payload, next) => {
console.log(payload)
return next()
})
},
}
```
## Register Client UI
Query `Slots.listSubTree` without `root` to choose a target from the compact purpose and topology tree, then query the exact Slot with `root` before writing its registration. The exact result determines:
- the Slot's purpose in the layout;
- whether its registration protocol is `single`, `list`, `keyed`, or `chain`;
- registration options;
- scope standard props and business owner props;
- current occupants, replacement risks, and descendant Slots.
Use `ctx.get('slots')` and handle its absence. Then use `slots.inject` to wait for the Slot declaration and call `slots.register` inside the callback:
```js
return {
apply(ctx) {
const slots = ctx.get('slots')
if (slots === undefined) return
slots.inject('target.slot', () => slots.register(
{ name: 'target.slot', id: 'my-view' },
(props) => React.createElement('div', null, String(props.someValue)),
))
},
}
```
`ctx.get('slots')` does not require an injection. Do not rewrite it as `ctx.slots` unless `inject: ['slots']` is declared:
```js
return {
apply(ctx) {
ctx.slots.register({ name: 'target.slot' }, () => null)
},
}
```
Do not guess an `id`, `key`, selector, or props before querying the Slot protocol. Do not default to root-level `root`, `sidebar`, `conversation`, or `details` Slots; replacing an entire occupant also removes the descendant Slots it declares.
### Settings pages
A full settings UI should usually register its own section through `settings.section` to obtain a complete content area. `settings.general.item` is only appropriate for one compact, general-purpose preference. Query the actual subtree, options, and props for both, then select the narrowest entry point that is still sufficient.
Dynamic Plugins are temporary and process-local, so their settings UI does not need persistent storage. Do not add durable settings or another persistence mechanism for it. Register the UI in the appropriate settings Slot and keep any transient interaction state in memory for the lifetime of the Plugin.
### Session and page data
A session-scoped Slot may provide `useSession`, `useSessions`, `useWorkspaces`, `useProjection`, input state, or actions through standard props. Follow the query result and prefer owner or standard props directly; do not add a Host RPC for data already present there.
Select only the fields that the UI actually needs. Do not copy or render an entire Conversation Snapshot, Session, Tool call, or Slot props object.
### Cordis Run-specific panel
To place interactive UI in the latest `cordis_run` card, register `tool.view.cordis` with `key: 'self'`:
When the feature needs user interaction tied to this Package's result, this region is often a good fit because it keeps the controls in the conversation flow beside the Run card. It is not the default target for every Client UI: settings, sidebars, message actions, and overlays should use their own queried Slots when those locations better match the feature.
```js
return {
apply(ctx) {
const slots = ctx.get('slots')
if (slots === undefined) return
slots.inject('tool.view.cordis', () => slots.register(
{ name: 'tool.view.cordis', key: 'self' },
(props) => React.createElement('div', null, `Package ${props.packageId}`),
))
},
}
```
At runtime, `self` binds to `pluginId + packageId`. Do not include `pluginRunId` in the key. When the same Package runs multiple times, the latest Run card hosts the UI and older cards automatically degrade.
### Ordinary Tool cards
To customize the call card for an ordinary model Tool, query `tool.call.toolview`. Its key is the Tool name; registering an existing key may replace the product's default card. When customizing only a newly added Tool, first verify its schema with `Tool.listTools`, then query the complete `ToolCallOwnerProps`.
### Overlays and local entry points
- For toasts, status notices, and frame-wide overlays, query `shell.overlay` first; observe its pointer-events and ordering rules.
- When the selected target is a global overlay Slot, decide whether the UI should be draggable, how the user shows and hides it, and which existing layers it must cover or remain below.
- For small sidebar actions, prefer additive inner Slots such as `sidebar.footer.action`; do not replace the entire sidebar.
- For supplementary content after a conversation turn, query `conversation.chat.turnTail` and register according to its returned chain selector and fallback rules.
## Themes and styles
Determine the scope of the change first:
1. Global theme: first query `Theme.listTokens`, then query `{ "service": "theme" }` through Client `Service.listService`. Supply light and dark values for each override as required by the query, and retain the returned disposer.
2. The Package's own components: use `styles.insert(css)` and prefer theme CSS variables for colors.
3. New visible content: choose a Slot first, then decide between local CSS and global tokens.
Do not manipulate `document.body`, `window`, or hard-coded product DOM selectors. The theme Service changes tokens but does not create UI. Slots create UI but do not replace the theme system.
## Call Host from Client
Host registers a Package-private method with `harness.handle(method, handler)`, and Client invokes it with `host.call(method, args)`. This is Client→Host JSON RPC.
Host:
```js
return {
apply(ctx) {
harness.handle('read-state', async (args) => {
return { value: args.key }
})
},
}
```
Client:
```js
return {
async apply(ctx) {
const result = await host.call('read-state', { key: 'demo' })
console.log(result.value)
},
}
```
Arguments and return values must be lossless JSON. Do not pass functions, React elements, class instances, Contexts, Services, or other runtime objects; return `null` when there is no response data. Do not register a public Remote Service or use `ctx.remote` for Package-private communication.
## Register a dynamic model Tool
Host can use `harness` to register a Tool callable in the next model step. First query the current `harness` signature with Host `Builtin.listBuiltins`, then inspect existing Tool names and schemas with `Tool.listTools` to avoid conflicts.
Tool arguments and return values must be JSON-compatible. `execute` owns the business result; render and presentation own only what the model and native UI see. Tool registration must belong to the current Plugin Fiber so it is automatically removed after stop or update.
## Handle internal live data
Service instances, Event payloads, Slot props, Session and Conversation Snapshots, Tool state, and other DSH/Cordis objects are internal live data.
Do not:
- call `JSON.stringify` or `structuredClone` on these objects or their descendants;
- recursively enumerate, fully copy, or display them as a whole;
- place Host objects in the Package's long-lived state or RPC return values.
Read only the leaf fields required by the current feature. Extract the minimum strings, numbers, booleans, and other scalar values before constructing owned JSON.
## Versions, approval, and repair
- A Plugin is the stable instance identified by `pluginId`.
- A Package is an immutable code version identified by `packageId`.
- Every activation attempt has its own `pluginRunId`.
- `currentPackageId` is the latest successful version; it does not imply that the Plugin is currently running.
- `nextPackageId` is the target awaiting approval, activating, awaiting Client activation, or most recently failed.
Choose the `cordis_run` mode as follows:
| Current state | Target | mode |
| --- | --- | --- |
| No current | Any Package under the Plugin | `run` |
| Has current | The same Package | `run` |
| Has current | A different Package | `update` |
| Update failed | `nextPackageId` | `update` to retry |
| Update failed | `currentPackageId` | `run` to roll back |
An unauthorized Client Package returns `awaiting-approval`. A single check mark authorizes only the current Package; double check marks authorize future versions of the same Plugin. A grant remains after a technical runtime failure. An authorized Package returns `starting` and completes asynchronously in the browser.
After a technical failure:
1. Use `cordis_inspect_self(pluginId, packageId)` to read the failed version's source and exact diagnostics.
2. If the error involves an unknown capability, list and query the corresponding Provider again.
3. Define a new Package under the same Plugin; do not overwrite the failed Package.
4. Run again with the new `packageId` and the correct mode.
Do not retry automatically after the user rejects approval. A failed update does not automatically restore the old physical Run; explicitly run current when recovery is required.
## Modify @pluginId
When the user identifies a target with `@pluginId`, do not create another Plugin. The injected context contains only identity, version pointers, and the default base Package, not source code.
Modify it as follows:
1. Read the base Package with `cordis_inspect_self(pluginId, packageId)`.
2. Preserve the Host or Client half that does not need to change and modify only the target code.
3. Call `cordis_define` with `plugin.kind: 'existing'` and the original `pluginId`.
4. Use the returned `packageId`; when current exists, activate the new version with `update` in the usual case.
If the reference is unavailable, explain that the Plugin was removed, belongs to another Session, or was lost on process restart. Do not create a same-named replacement.
## Common failure checks
| Failure | Check first |
| --- | --- |
| `service "x" is not declared` | Whether code uses `ctx.x` without declaring `inject: ['x']` on the Plugin object; switch to `ctx.get('x')` with an absence check or declare a true hard dependency |
| `cannot get property "timer" without inject` | Query the timer Service and declare `inject: ['timer']` |
| Client parse failure | Whether the code uses JSX, TypeScript, import, or an unavailable global |
| Slot registration failure | Whether the live subtree was queried, the Slot exists, and options, key, or selector satisfy the returned protocol |
| UI loads but the page reports an error | Inspect the `client-render` diagnostic and stack; the error belongs to an exact Run, so define a new Package to repair it |
| `host.call` failure | The Host handler name, current `pluginRunId`, JSON arguments, and real Service dependencies inside the handler |
| Update failure | Preserve current/next semantics; repair next and update, or run current to roll back |
+3
View File
@@ -27,6 +27,9 @@
"@deepseek-ai/dsh-agent-tool-presentation": "workspace:^",
"@deepseek-ai/dsh-app-boot": "workspace:^",
"@deepseek-ai/dsh-base": "workspace:^",
"@deepseek-ai/dsh-cordis-client-runner": "workspace:^",
"@deepseek-ai/dsh-client-ui-agent-preset": "workspace:^",
"@deepseek-ai/dsh-client-ui-cordis": "workspace:^",
"@deepseek-ai/dsh-command-compact": "workspace:^",
"@deepseek-ai/dsh-command-goal": "workspace:^",
"@deepseek-ai/dsh-compaction-basic": "workspace:^",
+5 -2
View File
@@ -260,7 +260,10 @@ describe('the shipped Web composition', () => {
try {
const tools = toolNames(ctx, handle.agent)
// The self-referential toolset is what distinguishes this preset.
expect(tools).toEqual(expect.arrayContaining(['cordis_inspect', 'cordis_mount', 'cordis_unmount']))
expect(tools).toEqual(expect.arrayContaining([
'cordis_inspect_list', 'cordis_inspect_query', 'cordis_inspect_self',
'cordis_define', 'cordis_run', 'cordis_stop', 'cordis_undefine',
]))
// And it keeps the standard agent's own tools rather than replacing them.
expect(tools).toEqual(expect.arrayContaining(['bash', 'read', 'edit', 'skill']))
expect(tools).not.toContain('str_replace_editor')
@@ -314,7 +317,7 @@ describe('the shipped Web composition', () => {
})
try {
// Editing the live runtime is opt-in per session, not ambient.
expect(toolNames(ctx, handle.agent)).not.toContain('cordis_mount')
expect(toolNames(ctx, handle.agent)).not.toContain('cordis_define')
} finally {
await handle.dispose()
}
+69 -24
View File
@@ -1,7 +1,13 @@
// Web e2e scenario for the opt-in Cordis tools. Record mode drives a real
// model through inspect, mount, and unmount; replay pins the same shipped Web
// composition, durable calls, generic rows, highlighted Plugin source, and
// conversation accessibility tree.
// model through inspect, define, run, and stop; replay pins the same shipped Web
// composition, durable calls, Cordis-owned rows, the define card's own source view,
// and conversation accessibility tree.
//
// The approval is never in the fixture. The fixture pins what the MODEL said;
// tools execute for real, and this test answers the approval before starting the
// stop turn. The package therefore carries a browser half whose only
// job is to be visible (`[data-snapshot-probe]`): its absence before the answer
// and presence after it is the v3 user gate, proven rather than described.
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import type { Browser, Page } from 'playwright'
@@ -17,12 +23,24 @@ import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './suppor
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_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_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.'
const CORDIS_TOOLS = ['cordis_inspect_self', 'cordis_define', 'cordis_run', 'cordis_stop'] as const
const PACKAGE_CODE = 'return { name: "snapshot-noop", apply(ctx) {} }'
// The browser half is the PROBE this scenario turns on: it renders a marker into
// the frame-wide overlay, so "did the plugin actually run in this page" becomes a
// DOM fact. A host-only package would sidestep the approval round trip entirely
// (the host runs those immediately), which would drop the v3 user gate out of
// coverage — the one thing this scenario exists to prove.
const CLIENT_CODE = 'return { inject: ["slots"], apply(ctx) { ctx.slots.register('
+ '{ name: "shell.overlay", id: "snapshot-probe" }, '
+ '() => React.createElement("div", { "data-snapshot-probe": "loaded" })) } }'
const PROMPT = 'Use only Cordis tools. First call cordis_inspect_self with no arguments. '
+ 'Then call cordis_define with plugin kind "new", idPrefix "snap", name "snapshot noop", '
+ 'purpose "does nothing, for the snapshot", '
+ `code.host exactly ${JSON.stringify(PACKAGE_CODE)} and code.client exactly ${JSON.stringify(CLIENT_CODE)}. `
+ 'Read its returned pluginId and packageId, then call cordis_run with those exact IDs and mode "run". '
+ 'After the run request returns, reply exactly CORDIS_UI_READY and stop.'
const STOP_PROMPT = 'Use only Cordis tools. Call cordis_stop with pluginId "snap-1". '
+ 'After it succeeds, reply exactly CORDIS_UI_DONE and stop.'
function assertCompleteCordisLifecycle(events: readonly SessionEvent[]): void {
const turnEnd = events.findLast(
@@ -46,7 +64,7 @@ function assertCompleteCordisLifecycle(events: readonly SessionEvent[]): void {
expect(results.every(event => !event.data.message.content[0].isError)).toBe(true)
}
describe('web e2e: Cordis tools use the generic row variants', () => {
describe('web e2e: Cordis tools use their owned cards', () => {
let scaffold: WebScaffold
let browser: Browser
let page: Page
@@ -75,14 +93,30 @@ describe('web e2e: Cordis tools use the generic row variants', () => {
it('drives the recorded Cordis lifecycle to a settled turn (all modes)', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-cordis-drive'))
if (MODE !== 'record') {
expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT])
expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT, STOP_PROMPT])
}
const input = page.locator('textarea').first()
await input.waitFor({ timeout: 10_000 })
const settled = scaffold.whenTurnSettled()
const runTurnSettled = scaffold.whenTurnSettled()
await input.fill(PROMPT)
await input.press('Enter')
const sessionId = await settled
// The approval is the TEST's action in every mode: the fixture pins what the
// model said, and the gate is a real round trip through the real panel.
const approve = page.locator('[data-cordis-approve]').first()
await approve.waitFor({ timeout: 90_000 })
// The one assertion this scenario cannot give up: the model asking to run is
// NOT the plugin running. Until a person answers, the browser half has not
// been fetched, evaluated, or mounted anywhere on this page.
expect(await page.locator('[data-snapshot-probe]').count()).toBe(0)
await approve.click()
await expect.poll(() => page.locator('[data-snapshot-probe]').count(), { timeout: 30_000 }).toBe(1)
const sessionId = await runTurnSettled
const stopTurnSettled = scaffold.whenTurnSettled()
await input.fill(STOP_PROMPT)
await input.press('Enter')
await stopTurnSettled
if (MODE === 'record') {
assertCompleteCordisLifecycle(sessionEvents)
await expect.poll(() => page.getByText('CORDIS_UI_DONE', { exact: true }).count(), { timeout: 15_000 })
@@ -95,25 +129,36 @@ describe('web e2e: Cordis tools use the generic row variants', () => {
assertCompleteCordisLifecycle(sessionEvents)
})
it.skipIf(MODE === 'record')('renders Cordis lifecycle titles over the generic row mechanics', async () => {
it.skipIf(MODE === 'record')('renders localized Cordis lifecycle cards', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-cordis-rows'))
await expect.poll(() => page.getByText('CORDIS_UI_DONE', { exact: true }).count(), { timeout: 15_000 })
.toBeGreaterThanOrEqual(1)
const inspectRow = page.locator('[data-tool="cordis_inspect"]').filter({ hasText: 'Inspect' }).first()
const inspectRow = page.locator('[data-tool="cordis_inspect_self"]').filter({ hasText: 'Inspect' }).first()
await inspectRow.waitFor({ timeout: 10_000 })
const mountRow = page.locator('[data-tool="cordis_mount"]').filter({ hasText: 'Mount temporary Plugin' }).first()
await mountRow.waitFor({ timeout: 10_000 })
// cordis_define does NOT go through the generic row: ui-cordis registers a
// keyed toolview for it, and a keyed hit replaces the generic card. So the
// title here is the CARD's ("Cordis Plugin"), and the expanded body is the
// card's own two code sections rather than a generic args dump.
const defineRow = page.locator('[data-tool="cordis_define"]').filter({ hasText: 'Cordis Plugin' }).first()
await defineRow.waitFor({ timeout: 10_000 })
// The whole summary row is the expand toggle (unified tool-row interaction).
await mountRow.locator('[aria-expanded]').first().click()
await expect.poll(() => mountRow.locator('pre.shiki').textContent(), { timeout: 10_000 })
.toContain(MOUNT_CODE)
await defineRow.locator('[aria-expanded]').first().click()
await expect.poll(() => defineRow.textContent(), { timeout: 10_000 }).toContain('data-snapshot-probe')
await defineRow.getByRole('tab', { name: 'Host' }).click()
await expect.poll(() => defineRow.textContent()).toContain(PACKAGE_CODE)
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')
const runRow = page.locator('[data-tool="cordis_run"]').filter({ hasText: 'Run Cordis Plugin' }).first()
await runRow.waitFor({ timeout: 10_000 })
await expect.poll(() => runRow.textContent()).toContain('snap-')
const stopRow = page.locator('[data-tool="cordis_stop"]').filter({ hasText: 'Stop Cordis Plugin' }).first()
await stopRow.waitFor({ timeout: 10_000 })
await expect.poll(() => stopRow.textContent()).toContain('snap-')
await expect(stopRow.getAttribute('data-state')).resolves.toBe('ok')
// Stopping withdraws the browser half from every page, probe included.
await expect.poll(() => page.locator('[data-snapshot-probe]').count(), { timeout: 15_000 }).toBe(0)
})
it.skipIf(MODE === 'record')('matches the conversation aria golden', async () => {
+6 -6
View File
@@ -70,7 +70,6 @@ import SessionStore, {
type SessionHeader,
} from '@deepseek-ai/dsh-session'
import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl'
import * as ToolCordis from '@deepseek-ai/dsh-tool-cordis'
// Empty type imports carry the webServer/agents/sessionPersistence Context merges.
import type {} from '@deepseek-ai/dsh-host-webserver'
import type {} from '@deepseek-ai/dsh-agent'
@@ -217,7 +216,7 @@ export interface LaunchOptions {
*/
toolsMode?: 'native' | 'code' | 'both'
/**
* Insert the opt-in self-referential Cordis tools into the shipped tree.
* Insert the opt-in model-facing Cordis tool provider into the shipped tree.
* Record and replay use the same tool surface, so captured request headers
* remain reconstructable without making the tools a product default.
*/
@@ -460,8 +459,12 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
// be able to change a golden, whatever roots a scenario asks for.
: [{ id: 'agent-presets', config: { ...options.agentPresets, includeUserRoot: false } }],
...options.toolsMode === undefined ? [] : [{ id: 'tools', config: { mode: options.toolsMode } }],
// The shipped Web bundle already owns both runners and the Cordis UI. This
// scenario adds only the model-facing tools that exercise those services.
...options.cordisTools === true
? [{ insert: [{ id: 'tool-cordis', name: 'cordis:tool-cordis' }] }]
? [{ insert: [
{ id: 'tool-cordis', name: '@deepseek-ai/dsh-tool-cordis' },
] }]
: [],
...options.deepSeekSearch === undefined
? []
@@ -513,9 +516,6 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
// and a preset resolving package names from its own directory cannot reach
// `@deepseek-ai/cordis-plugin-group` by name.
ctx.loader.builtins.group = Group
// The shipped CLI deliberately has no dependency on this opt-in package.
// Keep the Loader row real without broadening the product installation.
if (options.cordisTools === true) ctx.loader.builtins['tool-cordis'] = ToolCordis
await ctx.loader.create({
name: 'cordis:include',
config: { path: pathToFileURL(rootConfig).href, patches },
@@ -1,15 +1,13 @@
{"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":"user/message","seq":1,"time":1785157562882,"data":{"content":[{"type":"text","text":"Use only Cordis tools. First call cordis_inspect_self with no arguments. Then call cordis_define with plugin kind \"new\", idPrefix \"snap\", name \"snapshot noop\", purpose \"does nothing, for the snapshot\", code.host exactly \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\" and code.client exactly \"return { inject: [\\\"slots\\\"], apply(ctx) { ctx.slots.register({ name: \\\"shell.overlay\\\", id: \\\"snapshot-probe\\\" }, () => React.createElement(\\\"div\\\", { \\\"data-snapshot-probe\\\": \\\"loaded\\\" })) } }\". Read its returned pluginId and packageId, then call cordis_run with those exact IDs and mode \"run\". After the run request returns, reply exactly CORDIS_UI_READY 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-official","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":99,"time":1785157565490,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"I will inspect the current Session's dynamic Cordis Plugins before defining the snapshot Package."}}}}
{"type":"assistant/chunk","seq":100,"time":1785157565491,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cordis-inspect-self","name":"cordis_inspect_self","arguments":"{}"}}}}
{"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-official","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"}
@@ -18,11 +16,9 @@
{"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":156,"time":1785157567041,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"No dynamic Plugins are present, so I will define the requested Host and Client Package."}}}}
{"type":"assistant/chunk","seq":157,"time":1785157567041,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cordis-define","name":"cordis_define","arguments":"{\"plugin\":{\"kind\":\"new\",\"idPrefix\":\"snap\"},\"name\":\"snapshot noop\",\"purpose\":\"does nothing, for the snapshot\",\"code\":{\"host\":\"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\",\"client\":\"return { inject: [\\\"slots\\\"], apply(ctx) { ctx.slots.register({ name: \\\"shell.overlay\\\", id: \\\"snapshot-probe\\\" }, () => React.createElement(\\\"div\\\", { \\\"data-snapshot-probe\\\": \\\"loaded\\\" })) } }\"}}"}}}}
{"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-official","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"}
@@ -31,11 +27,9 @@
{"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":203,"time":1785157568278,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The Host returned snap-1/pkg-1, so I will request its first activation."}}}}
{"type":"assistant/chunk","seq":204,"time":1785157568279,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cordis-run","name":"cordis_run","arguments":"{\"pluginId\":\"snap-1\",\"packageId\":\"pkg-1\",\"mode\":\"run\"}"}}}}
{"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-official","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"}
@@ -44,13 +38,20 @@
{"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":243,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The activation request has been submitted, so I will return the requested readiness marker."}}}}
{"type":"assistant/chunk","seq":244,"time":1785157569551,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"CORDIS_UI_READY"}}}}
{"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-official","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"}}}
{"type":"user/message","seq":250,"time":1785157569555,"data":{"content":[{"type":"text","text":"Use only Cordis tools. Call cordis_stop with pluginId \"snap-1\". After it succeeds, reply exactly CORDIS_UI_DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpcId}}"}},"surfaceOp":"append"}
{"type":"assistant/chunk","seq":251,"time":1785157569556,"data":{"turn":2,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":252,"time":1785157569557,"data":{"turn":2,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"cordis-stop","name":"cordis_stop","arguments":"{\"pluginId\":\"snap-1\"}"}}}}
{"type":"assistant/chunk","seq":253,"time":1785157569558,"data":{"turn":2,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":254,"time":1785157569559,"data":{"turn":2,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/chunk","seq":255,"time":1785157569560,"data":{"turn":2,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":256,"time":1785157569561,"data":{"turn":2,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"CORDIS_UI_DONE"}}}}
{"type":"assistant/chunk","seq":257,"time":1785157569562,"data":{"turn":2,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":258,"time":1785157569563,"data":{"turn":2,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
@@ -9,45 +9,67 @@
- tablist:
- tab "Chat" [selected]
- tab "Trajectory"
- 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. {{clock}}"
- text: "Use only Cordis tools. First call cordis_inspect_self with no arguments. Then call cordis_define with plugin kind \"new\", idPrefix \"snap\", name \"snapshot noop\", purpose \"does nothing, for the snapshot\", code.host exactly \"return { name: \\\"snapshot-noop\\\", apply(ctx) {} }\" and code.client exactly \"return { inject: [\\\"slots\\\"], apply(ctx) { ctx.slots.register({ name: \\\"shell.overlay\\\", id: \\\"snapshot-probe\\\" }, () => React.createElement(\\\"div\\\", { \\\"data-snapshot-probe\\\": \\\"loaded\\\" })) } }\". Read its returned pluginId and packageId, then call cordis_run with those exact IDs and mode \"run\". After the run request returns, reply exactly CORDIS_UI_READY and stop. {{clock}}"
- button "Copy":
- img
- button "Context injection @deepseek-ai/dsh-system-prompt":
- img
- img
- text: Context injection @deepseek-ai/dsh-system-prompt
- button "Think The user wants me to:":
- button "Think I will inspect the current Session's dynamic Cordis Plugins before defining the snapshot Package.":
- img
- img
- text: "Think The user wants me to:"
- button "Inspect temporary":
- text: Think I will inspect the current Session's dynamic Cordis Plugins before defining the snapshot Package.
- 'button "Tool call cordis_inspect_self · {}"':
- img
- img
- text: Inspect temporary
- 'button "Think Good, no temporary plugins running. Now step 2: call cordis_mount with the exact code."':
- text: "Tool call cordis_inspect_self · {}"
- button "Think No dynamic Plugins are present, so I will define the requested Host and Client Package.":
- img
- img
- text: "Think Good, no temporary plugins running. Now step 2: call cordis_mount with the exact code."
- 'button "Mount temporary Plugin return { name: \"snapshot-noop\", apply(ctx) {} }" [expanded]':
- text: Think No dynamic Plugins are present, so I will define the requested Host and Client Package.
- button "Register Cordis Plugin snapshot noop does nothing, for the snapshot Ready" [expanded]:
- img
- text: "Mount temporary Plugin return { name: \"snapshot-noop\", apply(ctx) {} }"
- text: typescript
- button "Copy"
- code: "return { name: \"snapshot-noop\", apply(ctx) {} }"
- text: OUT Temporary Plugin dyn-1 is running (plugin "snapshot-noop"; available until unmounted or DSH restarts).
- text: Register Cordis Plugin snapshot noop does nothing, for the snapshot Ready
- tablist "Plugin source":
- tab "Client"
- tab "Host" [selected]
- tabpanel "Host":
- text: javascript
- button "Copy"
- code: "return { name: \"snapshot-noop\", apply(ctx) {} }"
- text: Result Defined snap-1/pkg-1 (snapshot noop); it is not running yet. Use cordis_run to activate this Package. Run controls live in the Cordis panel above Settings
- button "Inspect"
- 'button "Think The id is \"dyn-1\". Now step 3: call cordis_unmount with that id."':
- button "Think The Host returned snap-1/pkg-1, so I will request its first activation.":
- img
- img
- text: "Think The id is \"dyn-1\". Now step 3: call cordis_unmount with that id."
- button "Unmount temporary Plugin dyn-1":
- text: Think The Host returned snap-1/pkg-1, so I will request its first activation.
- img
- text: Run Cordis Plugin snap-1 · pkg-1 Ready
- button "Inspect"
- text: snap-1/pkg-1 is awaiting user approval (run-1).
- button "Think The activation request has been submitted, so I will return the requested readiness marker.":
- img
- img
- text: Unmount temporary Plugin dyn-1
- button "Think All three calls succeeded. I should now reply exactly \"CORDIS_UI_DONE\" and stop.":
- text: Think The activation request has been submitted, so I will return the requested readiness marker.
- paragraph: CORDIS_UI_READY
- button "Copy":
- img
- button "Good response":
- img
- button "Bad response":
- img
- button "Branch into a new conversation":
- img
- text: {{clock}} Ran for {{duration}}
- button "Context injection cordis-host-runner":
- img
- img
- text: Think All three calls succeeded. I should now reply exactly "CORDIS_UI_DONE" and stop.
- text: Context injection cordis-host-runner
- img
- text: Stop Cordis Plugin snap-1
- button "Inspect"
- text: Dynamic Plugin snap-1 is stopped; its definition and versions remain.
- paragraph: CORDIS_UI_DONE
- button "Copy":
- img
@@ -57,7 +79,14 @@
- img
- button "Branch into a new conversation":
- img
- text: {{clock}} Ran for {{duration}} TTFT {{duration}} {{throughput}} tok/s
- text: {{clock}} Ran for {{duration}} Use only Cordis tools. Call cordis_stop with pluginId "snap-1". After it succeeds, reply exactly CORDIS_UI_DONE and stop. {{clock}}
- button "Copy":
- img
- status:
- text: "This turn failedllm-replay: script exhausted — session requested model call #7 but its script has only 6; re-record the scenario"
- code: UNKNOWN
- button "Back to bottom":
- img
- textbox "Message the agent"
- button "Commands":
- img
@@ -65,6 +94,6 @@
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "13% of context used"
- button "0% of context used"
- button "Send message" [disabled]
- text: 1 turns · 4 steps LLM {{duration}} · Tool call {{duration}} TTFT avg {{duration}} · {{throughput}} tok/s Cache hit 77% Input 66.5K tok · Output 312 tok
- text: 3 turns · 7 steps LLM {{duration}} · Tool call {{duration}} Cache hit 77% Input 66.5K tok · Output 318 tok
+1 -1
View File
@@ -380,7 +380,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
await expect.poll(() => page.getByText('Sessions', { exact: true }).count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(1)
await expect.poll(() => page.getByText('Ungrouped', { exact: true }).count(), { timeout: 5_000 }).toBe(0)
await expect.poll(() => page.locator('[role="treeitem"]').count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(1)
expect(await page.evaluate(() => localStorage.getItem('dsh.workspace.view.v4'))).toContain('flat')
expect(await page.evaluate(() => localStorage.getItem('dsh.workspace.view.v5'))).toContain('flat')
// Persisted across reload; then restore grouped for inter-spec hygiene.
const warningStart = tripwire.warnings.length
await page.reload({ waitUntil: 'load' })
+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 docs/capability-seams.md
capability-seams.md: 5302b42525b0394993b06f474b5f6b1237795372
capability-seams.zh.md: 0271bdb9b4020e4cc1b6c1ef3bb8e73850085740
capability-seams.md: a990a9dd4d92d10e37b82e6a63caa4a5a469c441
capability-seams.zh.md: 441d9222835e67c4a9f657d2335860005934094b
+21
View File
@@ -185,12 +185,21 @@ flowchart LR
svc_workflowEngine["ctx.workflowEngine<br/>Workflow script engine"]
pkg_workflow_worker_thread["workflow-worker-thread"]
pkg_tool_workflow["tool-workflow"]
pkg_lsp["lsp"]
svc_lsp["ctx.lsp<br/>Language-server navigation seam"]
pkg_lsp_local["lsp-local"]
pkg_tool_lsp["tool-lsp"]
svc_apiProxy["ctx.apiProxy<br/>Host API dispatch"]
pkg_cordis_host_runner["cordis-host-runner"]
svc_dynamicCordisRunner["ctx.dynamicCordisRunner<br/>Dynamic Cordis package host runner"]
svc_cordisInspect["ctx.cordisInspect<br/>Dynamic Cordis inspect registry"]
pkg_acp --> svc_approval
pkg_agent --> svc_agents
pkg_agent_default_model --> svc_agentDefaultModel
pkg_agent_loop --> svc_agentLoop
pkg_agent_presets --> svc_agentPresets
pkg_api_gateway --> svc_typertGateway
pkg_apiproxy --> svc_apiProxy
pkg_approval --> svc_approval
pkg_attachment --> svc_attachments
pkg_attachment_local --> svc_attachments
@@ -202,6 +211,8 @@ flowchart LR
pkg_compaction --> svc_compaction
pkg_compaction_basic --> svc_compaction
pkg_compaction_tool_result_pruner --> svc_toolResultPruner
pkg_cordis_host_runner --> svc_cordisInspect
pkg_cordis_host_runner --> svc_dynamicCordisRunner
pkg_credentials --> svc_credentials
pkg_credentials_local --> svc_credentials
pkg_directory_picker --> svc_directoryPicker
@@ -220,6 +231,8 @@ flowchart LR
pkg_llm_deepseek --> svc_llm
pkg_llm_pi_ai --> svc_llm
pkg_llm_replay --> svc_llm
pkg_lsp --> svc_lsp
pkg_lsp_local --> svc_lsp
pkg_message_feedback --> svc_messageFeedback
pkg_modules --> svc_clientModules
pkg_permission_presets --> svc_permissionPresets
@@ -287,6 +300,7 @@ flowchart LR
svc_agents --> pkg_acp
svc_agents --> pkg_agent_loop
svc_agents --> pkg_subagent_inprocess
svc_apiProxy --> pkg_connection
svc_approval --> pkg_tool_bash
svc_approval --> pkg_tools
svc_attachments --> pkg_host_runtime
@@ -294,10 +308,12 @@ flowchart LR
svc_clientModules --> pkg_hmr
svc_codeRuntime --> pkg_tools
svc_compaction --> pkg_compaction_basic
svc_cordisInspect --> pkg_tool_cordis
svc_credentials --> pkg_apiproxy
svc_credentials --> pkg_llm_deepseek
svc_credentials --> pkg_llm_pi_ai
svc_directoryPicker --> pkg_apiproxy
svc_dynamicCordisRunner --> pkg_tool_cordis
svc_e2b --> pkg_fs_e2b
svc_e2b --> pkg_subprocess_e2b
svc_fs --> pkg_tool_fs
@@ -311,6 +327,7 @@ flowchart LR
svc_jobs --> pkg_tool_terminal
svc_llm --> pkg_agent_loop
svc_llm --> pkg_compaction_basic
svc_lsp --> pkg_tool_lsp
svc_sandbox --> pkg_bash_sandbox
svc_sandbox --> pkg_terminal_bash
svc_sandboxPolicy --> pkg_bash_sandbox
@@ -446,5 +463,9 @@ flowchart LR
| `ctx.webServer` | `core` | `webserver` | - | `connection`, `modules`, `hmr` | - | Plain node:http carrier: named-route registry, index transform taps, and the static dist fallback; web-transport plugins register their own routes. |
| `ctx.clientModules` | `core` | `modules` | - | `hmr` | - | Composes the __DSH_BOOT__ entry graph from an incremental dsh.client scan, serves plugin bundles, and notifies rebuilt/graph-changed subscribers. |
| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | One engine per context, as in bash, with no named-provider registry; the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents. |
| `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | `lsp-local` | [`tool-lsp`](../packages/lsp/tool-lsp) | - | Provider registration and selection plus normalized query execution over exactly four operations; the seam offers no protocol escape hatch, so a backend translates into the normalized request and result. |
| `ctx.apiProxy` | `core` | `apiproxy` | - | `connection` | - | The transport-agnostic host gateway face: it dispatches browser API calls, and each open host stream subscribes to the events it forwards rather than being pushed to through a broadcast verb. |
| `ctx.dynamicCordisRunner` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | Owns the in-memory definition registry, the vm sandbox for host halves, and the request-run round trip; browser pages reach the same service over the wire through its remote namespace. |
| `ctx.cordisInspect` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | Registers host inspect providers, mirrors the client provider manifest, and routes client queries through the dynamic Cordis transport. |
Maintenance mode: hybrid: services are discovered from Cordis declarations; interface/implementation/consumer roles are classified in `scripts/gen-doc-graphs.ts` with a completeness guard.
+21
View File
@@ -187,12 +187,21 @@ flowchart LR
svc_workflowEngine["ctx.workflowEngine<br/>Workflow script engine"]
pkg_workflow_worker_thread["workflow-worker-thread"]
pkg_tool_workflow["tool-workflow"]
pkg_lsp["lsp"]
svc_lsp["ctx.lsp<br/>Language-server navigation seam"]
pkg_lsp_local["lsp-local"]
pkg_tool_lsp["tool-lsp"]
svc_apiProxy["ctx.apiProxy<br/>Host API dispatch"]
pkg_cordis_host_runner["cordis-host-runner"]
svc_dynamicCordisRunner["ctx.dynamicCordisRunner<br/>Dynamic Cordis package host runner"]
svc_cordisInspect["ctx.cordisInspect<br/>Dynamic Cordis inspect registry"]
pkg_acp --> svc_approval
pkg_agent --> svc_agents
pkg_agent_default_model --> svc_agentDefaultModel
pkg_agent_loop --> svc_agentLoop
pkg_agent_presets --> svc_agentPresets
pkg_api_gateway --> svc_typertGateway
pkg_apiproxy --> svc_apiProxy
pkg_approval --> svc_approval
pkg_attachment --> svc_attachments
pkg_attachment_local --> svc_attachments
@@ -204,6 +213,8 @@ flowchart LR
pkg_compaction --> svc_compaction
pkg_compaction_basic --> svc_compaction
pkg_compaction_tool_result_pruner --> svc_toolResultPruner
pkg_cordis_host_runner --> svc_cordisInspect
pkg_cordis_host_runner --> svc_dynamicCordisRunner
pkg_credentials --> svc_credentials
pkg_credentials_local --> svc_credentials
pkg_directory_picker --> svc_directoryPicker
@@ -222,6 +233,8 @@ flowchart LR
pkg_llm_deepseek --> svc_llm
pkg_llm_pi_ai --> svc_llm
pkg_llm_replay --> svc_llm
pkg_lsp --> svc_lsp
pkg_lsp_local --> svc_lsp
pkg_message_feedback --> svc_messageFeedback
pkg_modules --> svc_clientModules
pkg_permission_presets --> svc_permissionPresets
@@ -289,6 +302,7 @@ flowchart LR
svc_agents --> pkg_acp
svc_agents --> pkg_agent_loop
svc_agents --> pkg_subagent_inprocess
svc_apiProxy --> pkg_connection
svc_approval --> pkg_tool_bash
svc_approval --> pkg_tools
svc_attachments --> pkg_host_runtime
@@ -296,10 +310,12 @@ flowchart LR
svc_clientModules --> pkg_hmr
svc_codeRuntime --> pkg_tools
svc_compaction --> pkg_compaction_basic
svc_cordisInspect --> pkg_tool_cordis
svc_credentials --> pkg_apiproxy
svc_credentials --> pkg_llm_deepseek
svc_credentials --> pkg_llm_pi_ai
svc_directoryPicker --> pkg_apiproxy
svc_dynamicCordisRunner --> pkg_tool_cordis
svc_e2b --> pkg_fs_e2b
svc_e2b --> pkg_subprocess_e2b
svc_fs --> pkg_tool_fs
@@ -313,6 +329,7 @@ flowchart LR
svc_jobs --> pkg_tool_terminal
svc_llm --> pkg_agent_loop
svc_llm --> pkg_compaction_basic
svc_lsp --> pkg_tool_lsp
svc_sandbox --> pkg_bash_sandbox
svc_sandbox --> pkg_terminal_bash
svc_sandboxPolicy --> pkg_bash_sandbox
@@ -448,5 +465,9 @@ flowchart LR
| `ctx.webServer` | `core` | `webserver` | - | `connection`, `modules`, `hmr` | - | 普通的 node:http 载体:具名路由注册表、索引转换 tap,以及静态 dist 回退;Web 传输插件注册自己的路由。 |
| `ctx.clientModules` | `core` | `modules` | - | `hmr` | - | 通过增量 `dsh.client` 扫描组合 __DSH_BOOT__ 入口图,提供插件组合包,并通知重建/图变更订阅方。 |
| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 每个上下文使用一个引擎,与 bash 相同,且没有具名提供方注册表;通用工作流与固定 Ralph 消费方启动运行,其中的 agent() 调用通过 ctx.subagents 扇出。 |
| `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | `lsp-local` | [`tool-lsp`](../packages/lsp/tool-lsp) | - | 提供方注册与选择,加上恰好四种操作的标准化查询执行;该 seam 不提供协议逃生口,后端必须转换为标准化请求和结果。 |
| `ctx.apiProxy` | `core` | `apiproxy` | - | `connection` | - | 与传输无关的 Host 网关接口:它分派浏览器 API 调用,每条打开的 Host 流自行订阅转发事件,而不是由广播方法向其推送。 |
| `ctx.dynamicCordisRunner` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | 拥有内存定义注册表、Host 半的 vm 沙箱和 request-run 往返流程;浏览器页面通过其 Remote 命名空间在线访问同一服务。 |
| `ctx.cordisInspect` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | 注册 Host inspect 提供方、镜像 Client 提供方 manifest,并通过动态 Cordis 传输路由 Client 查询。 |
维护模式:混合模式。服务从 Cordis 声明中发现;接口、实现和消费方角色在 `scripts/gen-doc-graphs.ts` 中分类,并设有完整性守卫。
+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 docs/config-catalog.md
config-catalog.md: f7cfea66097cd8e2a9022a32d6459bd27ad9adcf
config-catalog.zh.md: ef1e19dc704fe14a6b1a41215573a22c9f7590a1
config-catalog.md: 25a9b02f91bd7c5a3fef49e6e61dd34dd7afea57
config-catalog.zh.md: 674c60e74aaa1a65838da78bb290c4159b6fe63b
+17 -18
View File
@@ -496,6 +496,20 @@ export interface ToolResultPruneConfig {
Source: [`packages/compaction/compaction-tool-result-pruner/src/types.ts:4`](../packages/compaction/compaction-tool-result-pruner/src/types.ts)
## `@deepseek-ai/dsh-cordis-host-runner`
Requires: `tools`
```ts config-catalog
/** Runner configuration. */
export interface Config {
/** Maximum synchronous VM evaluation time in milliseconds. */
vmTimeoutMs?: number
}
```
Source: [`packages/extensions/cordis-host-runner/src/index.ts:88`](../packages/extensions/cordis-host-runner/src/index.ts)
## `@deepseek-ai/dsh-credentials-local`
```ts config-catalog
@@ -2181,24 +2195,6 @@ export interface Config {
Source: [`packages/shell/tool-bash-persistent/src/index.ts:405`](../packages/shell/tool-bash-persistent/src/index.ts)
## `@deepseek-ai/dsh-tool-cordis`
Requires: `tools`
```ts config-catalog
/** Config for the tool-cordis plugin: the sandbox evaluation bound. */
export interface Config {
/**
* Milliseconds the SYNCHRONOUS portion of mount code may run in the vm
* before evaluation is aborted (default 5000). An async body escapes this
* bound — see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md for the trust stance.
*/
vmTimeoutMs?: number
}
```
Source: [`packages/extensions/tool-cordis/src/index.ts:25`](../packages/extensions/tool-cordis/src/index.ts)
## `@deepseek-ai/dsh-tool-fs`
Requires: `tools` · `fs` · `systemPrompt`
@@ -2809,6 +2805,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
- `@deepseek-ai/dsh-client-ui-agent-preset` ([`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts))
- `@deepseek-ai/dsh-client-ui-commands` ([`packages/client/ui-commands/src/index.ts`](../packages/client/ui-commands/src/index.ts))
- `@deepseek-ai/dsh-client-ui-conversation` ([`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts))
- `@deepseek-ai/dsh-client-ui-cordis` ([`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts))
- `@deepseek-ai/dsh-client-ui-deliverables` — requires `systemPrompt` ([`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts))
- `@deepseek-ai/dsh-client-ui-directory-picker-browse` ([`packages/client/ui-directory-picker-browse/src/index.ts`](../packages/client/ui-directory-picker-browse/src/index.ts))
- `@deepseek-ai/dsh-client-ui-directory-picker-native` ([`packages/client/ui-directory-picker-native/src/index.ts`](../packages/client/ui-directory-picker-native/src/index.ts))
@@ -2838,6 +2835,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
- `@deepseek-ai/dsh-command-feedback` — requires `commands` ([`packages/feedback/command-feedback/src/index.ts`](../packages/feedback/command-feedback/src/index.ts))
- `@deepseek-ai/dsh-command-goal` — requires `commands` · `goals` ([`packages/goal/command-goal/src/index.ts`](../packages/goal/command-goal/src/index.ts))
- `@deepseek-ai/dsh-commands` ([`packages/interaction/commands/src/index.ts`](../packages/interaction/commands/src/index.ts))
- `@deepseek-ai/dsh-cordis-client-runner` ([`packages/extensions/cordis-client-runner/src/index.ts`](../packages/extensions/cordis-client-runner/src/index.ts))
- `@deepseek-ai/dsh-fs-e2b` — requires `e2b` ([`packages/e2b/fs-e2b/src/index.ts`](../packages/e2b/fs-e2b/src/index.ts))
- `@deepseek-ai/dsh-fs-observation-policy` ([`packages/fs/fs-observation-policy/src/index.ts`](../packages/fs/fs-observation-policy/src/index.ts))
- `@deepseek-ai/dsh-goal-round-driver` — requires `agents` · `goals` · `sessions` ([`packages/goal/goal-round-driver/src/index.ts`](../packages/goal/goal-round-driver/src/index.ts))
@@ -2859,6 +2857,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
- `@deepseek-ai/dsh-terminal` ([`packages/terminal/terminal/src/index.ts`](../packages/terminal/terminal/src/index.ts))
- `@deepseek-ai/dsh-tool-ask-user` — requires `tools` · `userQuestions` ([`packages/interaction/tool-ask-user/src/index.ts`](../packages/interaction/tool-ask-user/src/index.ts))
- `@deepseek-ai/dsh-tool-call-timeout-policy` — requires `tools` ([`packages/guard/timeout-policy/src/index.ts`](../packages/guard/timeout-policy/src/index.ts))
- `@deepseek-ai/dsh-tool-cordis` — requires `tools` · `systemPrompt` · `dynamicCordisRunner` · `cordisInspect` ([`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts))
- `@deepseek-ai/dsh-tool-subagent-control` — requires `tools` · `subagents` ([`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts))
- `@deepseek-ai/dsh-user-questions` ([`packages/interaction/user-questions/src/index.ts`](../packages/interaction/user-questions/src/index.ts))
- `@deepseek-ai/dsh-workspace` — requires `storageDomain` · `sessionPersistence` ([`packages/workspace/workspace/src/index.ts`](../packages/workspace/workspace/src/index.ts))
+17 -17
View File
@@ -498,6 +498,20 @@ export interface ToolResultPruneConfig {
来源:[`packages/compaction/compaction-tool-result-pruner/src/types.ts:4`](../packages/compaction/compaction-tool-result-pruner/src/types.ts)
## `@deepseek-ai/dsh-cordis-host-runner`
需要:`tools`
```ts config-catalog
/** Runner configuration. */
export interface Config {
/** Maximum synchronous VM evaluation time in milliseconds. */
vmTimeoutMs?: number
}
```
来源:[`packages/extensions/cordis-host-runner/src/index.ts:88`](../packages/extensions/cordis-host-runner/src/index.ts)
## `@deepseek-ai/dsh-credentials-local`
```ts config-catalog
@@ -2183,23 +2197,6 @@ export interface Config {
来源:[`packages/shell/tool-bash-persistent/src/index.ts:405`](../packages/shell/tool-bash-persistent/src/index.ts)
## `@deepseek-ai/dsh-tool-cordis`
需要:`tools`
```ts config-catalog
/** Config for the tool-cordis plugin: the sandbox evaluation bound. */
export interface Config {
/**
* Milliseconds the SYNCHRONOUS portion of mount code may run in the vm
* before evaluation is aborted (default 5000). An async body escapes this
* bound — see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md for the trust stance.
*/
vmTimeoutMs?: number
}
```
来源:[`packages/extensions/tool-cordis/src/index.ts:25`](../packages/extensions/tool-cordis/src/index.ts)
## `@deepseek-ai/dsh-tool-fs`
需要:`tools` · `fs` · `systemPrompt`
@@ -2810,6 +2807,7 @@ export interface Config {
- `@deepseek-ai/dsh-client-ui-agent-preset`[`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts)
- `@deepseek-ai/dsh-client-ui-commands`[`packages/client/ui-commands/src/index.ts`](../packages/client/ui-commands/src/index.ts)
- `@deepseek-ai/dsh-client-ui-conversation`[`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts)
- `@deepseek-ai/dsh-client-ui-cordis`[`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts)
- `@deepseek-ai/dsh-client-ui-deliverables` — 需要 `systemPrompt`[`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts)
- `@deepseek-ai/dsh-client-ui-directory-picker-browse`[`packages/client/ui-directory-picker-browse/src/index.ts`](../packages/client/ui-directory-picker-browse/src/index.ts)
- `@deepseek-ai/dsh-client-ui-directory-picker-native`[`packages/client/ui-directory-picker-native/src/index.ts`](../packages/client/ui-directory-picker-native/src/index.ts)
@@ -2839,6 +2837,7 @@ export interface Config {
- `@deepseek-ai/dsh-command-feedback` — 需要 `commands`[`packages/feedback/command-feedback/src/index.ts`](../packages/feedback/command-feedback/src/index.ts)
- `@deepseek-ai/dsh-command-goal` — 需要 `commands` · `goals`[`packages/goal/command-goal/src/index.ts`](../packages/goal/command-goal/src/index.ts)
- `@deepseek-ai/dsh-commands`[`packages/interaction/commands/src/index.ts`](../packages/interaction/commands/src/index.ts)
- `@deepseek-ai/dsh-cordis-client-runner`[`packages/extensions/cordis-client-runner/src/index.ts`](../packages/extensions/cordis-client-runner/src/index.ts)
- `@deepseek-ai/dsh-fs-e2b` — 需要 `e2b`[`packages/e2b/fs-e2b/src/index.ts`](../packages/e2b/fs-e2b/src/index.ts)
- `@deepseek-ai/dsh-fs-observation-policy`[`packages/fs/fs-observation-policy/src/index.ts`](../packages/fs/fs-observation-policy/src/index.ts)
- `@deepseek-ai/dsh-goal-round-driver` — 需要 `agents` · `goals` · `sessions`[`packages/goal/goal-round-driver/src/index.ts`](../packages/goal/goal-round-driver/src/index.ts)
@@ -2860,6 +2859,7 @@ export interface Config {
- `@deepseek-ai/dsh-terminal`[`packages/terminal/terminal/src/index.ts`](../packages/terminal/terminal/src/index.ts)
- `@deepseek-ai/dsh-tool-ask-user` — 需要 `tools` · `userInteraction`[`packages/interaction/tool-ask-user/src/index.ts`](../packages/interaction/tool-ask-user/src/index.ts)
- `@deepseek-ai/dsh-tool-call-timeout-policy` — 需要 `tools`[`packages/guard/timeout-policy/src/index.ts`](../packages/guard/timeout-policy/src/index.ts)
- `@deepseek-ai/dsh-tool-cordis` — 需要 `tools` · `systemPrompt` · `dynamicCordisRunner` · `cordisInspect`[`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
- `@deepseek-ai/dsh-tool-subagent-control` — 需要 `tools` · `subagents`[`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts)
- `@deepseek-ai/dsh-user-questions`[`packages/interaction/user-questions/src/index.ts`](../packages/interaction/user-questions/src/index.ts)
- `@deepseek-ai/dsh-workspace` — 需要 `storageDomain` · `sessionPersistence`[`packages/workspace/workspace/src/index.ts`](../packages/workspace/workspace/src/index.ts)
+1 -1
View File
@@ -16,7 +16,7 @@ This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verifie
- `ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin` — Low-level service-store access and binding. ([`vendor/cordis/src/reflect.ts:7`](../../vendor/cordis/src/reflect.ts))
- `ctx.extend / ctx.isolate / ctx.intercept` — Derive a child context (scoped services / isolation / interception). ([`vendor/cordis/src/context.ts:42`](../../vendor/cordis/src/context.ts))
- `ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger` — Ambient handles onto the running context graph. ([`vendor/cordis/src/context.ts:16`](../../vendor/cordis/src/context.ts))
- `ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)` — Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts))
- `ctx.timer (+ interval / timeout / throttle / debounce)` — Disposable timer helpers. The `timer` key is provided at runtime; the four supported helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts))
- `ctx.loader` — The config Loader that booted the app (present under the loader). ([`vendor/loader/src/index.ts:30`](../../vendor/loader/src/index.ts))
- `ctx.hmr` — The hot-module-reload watcher (present under the hmr plugin). ([`vendor/hmr/src/index.ts:15`](../../vendor/hmr/src/index.ts))
+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 docs/event-producer-consumer.md
event-producer-consumer.md: ad5366d41fa802f351b876c438cce5728a312f4c
event-producer-consumer.zh.md: b25673cbe975525fed4112910fbc98425e346eb9
event-producer-consumer.md: 6a79e6f7ce5addc64b10efa8da7a886dcfb36dc2
event-producer-consumer.zh.md: f7576a8e28e4f1db2c65c324595c05c98b8fe488
+7 -1
View File
@@ -15,7 +15,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:197`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) |
| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:205`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) |
| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:186`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) |
| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:231`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) |
| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:231`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill) |
| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:244`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) |
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:260`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:217`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
@@ -23,6 +23,12 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:278`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/index.ts:30`](../packages/interaction/user-approval/src/index.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `apiproxy` |
| `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:72`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `apiproxy` |
| `cordis/dynamic-package` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:379`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/dynamic-retract` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:385`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/inspect-query` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:391`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/inspect-query-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:397`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/request-run` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:367`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:373`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `credentials/updated` | `emit` | [`packages/credentials/credentials/src/types.ts:29`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) |
| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `apiproxy`, [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace) |
| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:66`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) |
+7 -1
View File
@@ -17,7 +17,7 @@
| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:197`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) |
| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:205`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) |
| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:186`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) |
| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:231`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) |
| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:231`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill) |
| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:244`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) |
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:260`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:217`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
@@ -25,6 +25,12 @@
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:278`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) |
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/index.ts:30`](../packages/interaction/user-approval/src/index.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `apiproxy` |
| `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:72`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `apiproxy` |
| `cordis/dynamic-package` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:379`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/dynamic-retract` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:385`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/inspect-query` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:391`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/inspect-query-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:397`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/request-run` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:367`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:373`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `credentials/updated` | `emit` | [`packages/credentials/credentials/src/types.ts:29`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) |
| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `apiproxy`, [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace) |
| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:66`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) |
+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 docs/module-graph.md
module-graph.md: dc2ef337d946b37d4d0a8600c70c26c53cc89dc0
module-graph.zh.md: 8cd2feff369752cb0c9a9afd38a77f31ddf29826
module-graph.md: d8b80d228251eddad4c91f73748a853207dce86e
module-graph.zh.md: 1085b6f7adc1226502ef599387be6f5b3ec92bda
+108 -69
View File
@@ -189,6 +189,9 @@ flowchart TD
pkg_sdk_jsonrpc_demo["sdk-jsonrpc-demo"]
end
subgraph group_extensions["packages/extensions"]
pkg_client_ui_cordis["client-ui-cordis"]
pkg_cordis_client_runner["cordis-client-runner"]
pkg_cordis_host_runner["cordis-host-runner"]
pkg_tool_cordis["tool-cordis"]
end
subgraph group_feedback["packages/feedback"]
@@ -637,20 +640,6 @@ flowchart TD
pkg_acp --> pkg_invariants
pkg_acp --> pkg_session
pkg_acp --> pkg_user_approval
pkg_api_remotes --> pkg_agent
pkg_api_remotes --> pkg_agent_presets
pkg_api_remotes --> pkg_api_gateway
pkg_api_remotes --> pkg_commands
pkg_api_remotes --> pkg_credentials
pkg_api_remotes --> pkg_goal
pkg_api_remotes --> pkg_host_plugin_inventory
pkg_api_remotes --> pkg_invariants
pkg_api_remotes --> pkg_llm
pkg_api_remotes --> pkg_message_feedback
pkg_api_remotes --> pkg_session
pkg_api_remotes --> pkg_session_persistence
pkg_api_remotes --> pkg_settings
pkg_api_remotes --> pkg_typert_registry
pkg_headless --> pkg_agent
pkg_headless --> pkg_agent_default_model
pkg_headless --> pkg_invariants
@@ -673,8 +662,6 @@ flowchart TD
pkg_command_feedback --> pkg_invariants
pkg_command_feedback --> pkg_session
pkg_command_feedback --> pkg_session_telemetry
pkg_host_apiproxy --> pkg_agent_presets
pkg_host_apiproxy --> pkg_invariants
pkg_permission_presets --> pkg_commands
pkg_permission_presets --> pkg_invariants
pkg_permission_presets --> pkg_sandbox
@@ -833,10 +820,6 @@ flowchart TD
pkg_tool_session_query --> pkg_system_prompt
pkg_tool_session_query --> pkg_timeout
pkg_tool_session_query --> pkg_tools
pkg_client_runtime --> pkg_api_remotes
pkg_client_runtime --> pkg_invariants
pkg_client_runtime --> pkg_typert_protocol
pkg_client_runtime --> pkg_typert_registry
pkg_command_compact --> pkg_commands
pkg_command_compact --> pkg_compaction
pkg_command_compact --> pkg_invariants
@@ -854,9 +837,14 @@ flowchart TD
pkg_session_reference --> pkg_output_retention
pkg_session_reference --> pkg_session
pkg_session_reference --> pkg_session_query
pkg_tool_cordis --> pkg_invariants
pkg_tool_cordis --> pkg_scope
pkg_tool_cordis --> pkg_tools
pkg_cordis_host_runner --> pkg_agent
pkg_cordis_host_runner --> pkg_brand
pkg_cordis_host_runner --> pkg_invariants
pkg_cordis_host_runner --> pkg_llm
pkg_cordis_host_runner --> pkg_scope
pkg_cordis_host_runner --> pkg_session
pkg_cordis_host_runner --> pkg_tools
pkg_cordis_host_runner --> pkg_typert_protocol
pkg_repeat_tool_reminder --> pkg_agent
pkg_repeat_tool_reminder --> pkg_invariants
pkg_repeat_tool_reminder --> pkg_tools
@@ -1005,29 +993,40 @@ flowchart TD
pkg_hooks_claude_code --> pkg_session_persistence
pkg_hooks_claude_code --> pkg_subagent
pkg_hooks_claude_code --> pkg_tools
pkg_api_remotes --> pkg_agent
pkg_api_remotes --> pkg_agent_presets
pkg_api_remotes --> pkg_api_gateway
pkg_api_remotes --> pkg_commands
pkg_api_remotes --> pkg_cordis_host_runner
pkg_api_remotes --> pkg_credentials
pkg_api_remotes --> pkg_goal
pkg_api_remotes --> pkg_host_plugin_inventory
pkg_api_remotes --> pkg_invariants
pkg_api_remotes --> pkg_llm
pkg_api_remotes --> pkg_message_feedback
pkg_api_remotes --> pkg_session
pkg_api_remotes --> pkg_session_persistence
pkg_api_remotes --> pkg_settings
pkg_api_remotes --> pkg_typert_registry
pkg_web_app --> pkg_invariants
pkg_web_app --> pkg_shell_env
pkg_web_app --> pkg_system_prompt
pkg_client_ui_settings --> pkg_api_remotes
pkg_client_ui_settings --> pkg_client_connection
pkg_client_ui_settings --> pkg_client_runtime
pkg_client_ui_settings --> pkg_client_schema_form
pkg_client_ui_settings --> pkg_client_ui_slots
pkg_client_ui_settings --> pkg_invariants
pkg_client_ui_settings --> pkg_settings
pkg_client_ui_settings_models --> pkg_api_remotes
pkg_client_ui_settings_models --> pkg_client_connection
pkg_client_ui_settings_models --> pkg_client_runtime
pkg_client_ui_settings_models --> pkg_client_schema_form
pkg_client_ui_settings_models --> pkg_client_ui_primitives
pkg_client_ui_settings_models --> pkg_client_ui_slots
pkg_client_ui_settings_models --> pkg_client_web_react
pkg_client_ui_settings_models --> pkg_invariants
pkg_compaction_tool_result_pruner --> pkg_compaction
pkg_compaction_tool_result_pruner --> pkg_invariants
pkg_compaction_tool_result_pruner --> pkg_llm
pkg_compaction_tool_result_pruner --> pkg_session
pkg_compaction_tool_result_pruner --> pkg_token_meter
pkg_tool_cordis --> pkg_agent
pkg_tool_cordis --> pkg_cordis_host_runner
pkg_tool_cordis --> pkg_invariants
pkg_tool_cordis --> pkg_llm
pkg_tool_cordis --> pkg_scope
pkg_tool_cordis --> pkg_session
pkg_tool_cordis --> pkg_system_prompt
pkg_tool_cordis --> pkg_tools
pkg_host_apiproxy --> pkg_agent_presets
pkg_host_apiproxy --> pkg_cordis_host_runner
pkg_host_apiproxy --> pkg_invariants
pkg_sdk_protocol --> pkg_invariants
pkg_sdk_protocol --> pkg_llm
pkg_sdk_protocol --> pkg_session
@@ -1054,11 +1053,6 @@ flowchart TD
pkg_tool_pwsh --> pkg_system_prompt
pkg_tool_pwsh --> pkg_tools
pkg_tool_pwsh --> pkg_user_approval
pkg_client_test_runtime --> pkg_client_runtime
pkg_client_test_runtime --> pkg_client_ui_slots
pkg_client_test_runtime --> pkg_client_web_react
pkg_client_test_runtime --> pkg_host_apiproxy
pkg_client_test_runtime --> pkg_invariants
pkg_tool_ralph --> pkg_agent
pkg_tool_ralph --> pkg_invariants
pkg_tool_ralph --> pkg_llm
@@ -1089,13 +1083,10 @@ flowchart TD
pkg_subagent_spawn_in_process --> pkg_invariants
pkg_subagent_spawn_in_process --> pkg_subagent
pkg_subagent_spawn_in_process --> pkg_subagent_in_process_driver
pkg_client_locale --> pkg_api_remotes
pkg_client_locale --> pkg_client_connection
pkg_client_locale --> pkg_client_runtime
pkg_client_locale --> pkg_client_ui_primitives
pkg_client_locale --> pkg_client_ui_settings
pkg_client_locale --> pkg_client_ui_slots
pkg_client_locale --> pkg_invariants
pkg_client_runtime --> pkg_api_remotes
pkg_client_runtime --> pkg_invariants
pkg_client_runtime --> pkg_typert_protocol
pkg_client_runtime --> pkg_typert_registry
pkg_compaction_basic --> pkg_agent
pkg_compaction_basic --> pkg_commands
pkg_compaction_basic --> pkg_compaction
@@ -1145,6 +1136,43 @@ flowchart TD
pkg_subagent_dsh_sdk --> pkg_session
pkg_subagent_dsh_sdk --> pkg_subagent
pkg_subagent_dsh_sdk --> pkg_subprocess
pkg_client_ui_settings --> pkg_api_remotes
pkg_client_ui_settings --> pkg_client_connection
pkg_client_ui_settings --> pkg_client_runtime
pkg_client_ui_settings --> pkg_client_schema_form
pkg_client_ui_settings --> pkg_client_ui_slots
pkg_client_ui_settings --> pkg_invariants
pkg_client_ui_settings --> pkg_settings
pkg_client_ui_settings_models --> pkg_api_remotes
pkg_client_ui_settings_models --> pkg_client_connection
pkg_client_ui_settings_models --> pkg_client_runtime
pkg_client_ui_settings_models --> pkg_client_schema_form
pkg_client_ui_settings_models --> pkg_client_ui_primitives
pkg_client_ui_settings_models --> pkg_client_ui_slots
pkg_client_ui_settings_models --> pkg_client_web_react
pkg_client_ui_settings_models --> pkg_invariants
pkg_acp_demo --> pkg_acp
pkg_acp_demo --> pkg_agent_instructions
pkg_acp_demo --> pkg_agent_spine_demo
pkg_acp_demo --> pkg_app_boot
pkg_acp_demo --> pkg_invariants
pkg_acp_demo --> pkg_session_checkpoint_policy
pkg_acp_demo --> pkg_session_persistence_jsonl
pkg_acp_demo --> pkg_session_query
pkg_acp_demo --> pkg_session_query_sqlite
pkg_acp_demo --> pkg_tools
pkg_client_test_runtime --> pkg_client_runtime
pkg_client_test_runtime --> pkg_client_ui_slots
pkg_client_test_runtime --> pkg_client_web_react
pkg_client_test_runtime --> pkg_host_apiproxy
pkg_client_test_runtime --> pkg_invariants
pkg_client_locale --> pkg_api_remotes
pkg_client_locale --> pkg_client_connection
pkg_client_locale --> pkg_client_runtime
pkg_client_locale --> pkg_client_ui_primitives
pkg_client_locale --> pkg_client_ui_settings
pkg_client_locale --> pkg_client_ui_slots
pkg_client_locale --> pkg_invariants
pkg_client_ui_input_trigger --> pkg_client_locale
pkg_client_ui_input_trigger --> pkg_client_runtime
pkg_client_ui_input_trigger --> pkg_client_ui_primitives
@@ -1195,16 +1223,6 @@ flowchart TD
pkg_client_ui_workspace --> pkg_client_ui_primitives
pkg_client_ui_workspace --> pkg_client_ui_slots
pkg_client_ui_workspace --> pkg_invariants
pkg_acp_demo --> pkg_acp
pkg_acp_demo --> pkg_agent_instructions
pkg_acp_demo --> pkg_agent_spine_demo
pkg_acp_demo --> pkg_app_boot
pkg_acp_demo --> pkg_invariants
pkg_acp_demo --> pkg_session_checkpoint_policy
pkg_acp_demo --> pkg_session_persistence_jsonl
pkg_acp_demo --> pkg_session_query
pkg_acp_demo --> pkg_session_query_sqlite
pkg_acp_demo --> pkg_tools
pkg_client_ui_conversation --> pkg_agent
pkg_client_ui_conversation --> pkg_api_remotes
pkg_client_ui_conversation --> pkg_attachment
@@ -1248,6 +1266,13 @@ flowchart TD
pkg_client_ui_settings_general --> pkg_client_ui_slots
pkg_client_ui_settings_general --> pkg_client_web_react
pkg_client_ui_settings_general --> pkg_invariants
pkg_cordis_client_runner --> pkg_api_remotes
pkg_cordis_client_runner --> pkg_client_connection
pkg_cordis_client_runner --> pkg_client_modules
pkg_cordis_client_runner --> pkg_client_runtime
pkg_cordis_client_runner --> pkg_client_ui_slots
pkg_cordis_client_runner --> pkg_client_ui_theme
pkg_cordis_client_runner --> pkg_invariants
pkg_client_ui_agent_preset --> pkg_api_remotes
pkg_client_ui_agent_preset --> pkg_client_connection
pkg_client_ui_agent_preset --> pkg_client_locale
@@ -1377,6 +1402,17 @@ flowchart TD
pkg_client_ui_skill --> pkg_client_ui_slots
pkg_client_ui_skill --> pkg_client_ui_tool
pkg_client_ui_skill --> pkg_invariants
pkg_client_ui_cordis --> pkg_api_remotes
pkg_client_ui_cordis --> pkg_client_connection
pkg_client_ui_cordis --> pkg_client_locale
pkg_client_ui_cordis --> pkg_client_runtime
pkg_client_ui_cordis --> pkg_client_ui_input_trigger
pkg_client_ui_cordis --> pkg_client_ui_primitives
pkg_client_ui_cordis --> pkg_client_ui_sidebar
pkg_client_ui_cordis --> pkg_client_ui_slots
pkg_client_ui_cordis --> pkg_client_ui_tool
pkg_client_ui_cordis --> pkg_cordis_client_runner
pkg_client_ui_cordis --> pkg_invariants
```
| Package | Group | Depends on |
@@ -1487,13 +1523,11 @@ flowchart TD
| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
| [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-title`](../packages/session/session-title) |
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`user-approval`](../packages/interaction/user-approval) |
| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`credentials`](../packages/credentials/credentials), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) |
| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
| [`fs-e2b`](../packages/e2b/fs-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`user-approval`](../packages/interaction/user-approval) |
| [`jobs-local`](../packages/jobs/jobs-local) | `jobs` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`scope`](../packages/core/scope), [`timeout`](../packages/util/timeout) |
| [`lsp-stdio`](../packages/lsp/lsp-stdio) | `lsp` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
@@ -1518,11 +1552,10 @@ flowchart TD
| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
| [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) |
| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`client-runtime`](../packages/client/runtime) | `client` | [`api-remotes`](../packages/api/remotes), [`invariants`](../packages/runtime-diagnostics/invariants), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry) |
| [`command-compact`](../packages/compaction/command-compact) | `compaction` | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) |
| [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`tools`](../packages/core/tools) |
| [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | `extensions` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
| [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools) |
| [`tool-call-timeout-policy`](../packages/guard/timeout-policy) | `guard` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`tool-ask-user`](../packages/interaction/tool-ask-user) | `interaction` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) |
@@ -1549,25 +1582,30 @@ flowchart TD
| [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`tool-subagent-report`](../packages/subagent/tool-subagent-report) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) |
| [`web-app`](../packages/bundle/web-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) |
| [`client-ui-settings`](../packages/client/ui-settings) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings) |
| [`client-ui-settings-models`](../packages/client/ui-settings-models) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
| [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
| [`tool-bash`](../packages/shell/tool-bash) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
| [`tool-pwsh`](../packages/shell/tool-pwsh) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
| [`client-test-runtime`](../packages/test-support/client-runtime) | `test-support` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`host-apiproxy`](../packages/host/apiproxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
| [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
| [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
| [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) |
| [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) |
| [`client-locale`](../packages/client/locale) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-runtime`](../packages/client/runtime) | `client` | [`api-remotes`](../packages/api/remotes), [`invariants`](../packages/runtime-diagnostics/invariants), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry) |
| [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
| [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs-local`](../packages/jobs/jobs-local), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`shell-env`](../packages/shell/shell-env), [`skill`](../packages/skill/skill), [`skill-filesystem`](../packages/skill/skill-filesystem), [`system-prompt`](../packages/core/system-prompt), [`tool-bash`](../packages/shell/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-jobs`](../packages/jobs/tool-jobs), [`tool-skill`](../packages/skill/tool-skill), [`tools`](../packages/core/tools) |
| [`sdk-client`](../packages/sdk/client) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session) |
| [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
| [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) |
| [`client-ui-settings`](../packages/client/ui-settings) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings) |
| [`client-ui-settings-models`](../packages/client/ui-settings-models) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/boot/app-boot), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`tools`](../packages/core/tools) |
| [`client-test-runtime`](../packages/test-support/client-runtime) | `test-support` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`host-apiproxy`](../packages/host/apiproxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-locale`](../packages/client/locale) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-input-trigger`](../packages/client/ui-input-trigger) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-settings-plugin-inventory`](../packages/client/ui-settings-plugin-inventory) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-settings-plugins`](../packages/client/ui-settings-plugins) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) |
@@ -1576,12 +1614,12 @@ flowchart TD
| [`client-ui-trajectory`](../packages/client/ui-trajectory) | `client` | [`agent`](../packages/core/agent), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools) |
| [`client-ui-user-questions`](../packages/client/ui-user-questions) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/boot/app-boot), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`tools`](../packages/core/tools) |
| [`client-ui-conversation`](../packages/client/ui-conversation) | `client` | [`agent`](../packages/core/agent), [`api-remotes`](../packages/api/remotes), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-attachment`](../packages/client/ui-attachment), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm-retry`](../packages/llm/llm-retry), [`session-stats`](../packages/session/session-stats), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) |
| [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-layout`](../packages/client/ui-layout) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-settings-general`](../packages/client/ui-settings-general) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-sidebar`](../packages/client/ui-sidebar), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`cordis-client-runner`](../packages/extensions/cordis-client-runner) | `extensions` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-agent-preset`](../packages/client/ui-agent-preset) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-commands`](../packages/client/ui-commands) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`system-prompt`](../packages/core/system-prompt) |
@@ -1597,3 +1635,4 @@ flowchart TD
| [`client-ui-model-selection`](../packages/client/ui-model-selection) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-commands`](../packages/client/ui-commands), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-permission-presets`](../packages/client/ui-permission-presets) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-commands`](../packages/client/ui-commands), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`permission-presets`](../packages/interaction/permission-presets) |
| [`client-ui-skill`](../packages/client/ui-skill) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-tool`](../packages/client/ui-tool), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-cordis`](../packages/extensions/ui-cordis) | `extensions` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-sidebar`](../packages/client/ui-sidebar), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-tool`](../packages/client/ui-tool), [`cordis-client-runner`](../packages/extensions/cordis-client-runner), [`invariants`](../packages/runtime-diagnostics/invariants) |
+108 -69
View File
@@ -191,6 +191,9 @@ flowchart TD
pkg_sdk_jsonrpc_demo["sdk-jsonrpc-demo"]
end
subgraph group_extensions["packages/extensions"]
pkg_client_ui_cordis["client-ui-cordis"]
pkg_cordis_client_runner["cordis-client-runner"]
pkg_cordis_host_runner["cordis-host-runner"]
pkg_tool_cordis["tool-cordis"]
end
subgraph group_feedback["packages/feedback"]
@@ -639,20 +642,6 @@ flowchart TD
pkg_acp --> pkg_invariants
pkg_acp --> pkg_session
pkg_acp --> pkg_user_approval
pkg_api_remotes --> pkg_agent
pkg_api_remotes --> pkg_agent_presets
pkg_api_remotes --> pkg_api_gateway
pkg_api_remotes --> pkg_commands
pkg_api_remotes --> pkg_credentials
pkg_api_remotes --> pkg_goal
pkg_api_remotes --> pkg_host_plugin_inventory
pkg_api_remotes --> pkg_invariants
pkg_api_remotes --> pkg_llm
pkg_api_remotes --> pkg_message_feedback
pkg_api_remotes --> pkg_session
pkg_api_remotes --> pkg_session_persistence
pkg_api_remotes --> pkg_settings
pkg_api_remotes --> pkg_typert_registry
pkg_headless --> pkg_agent
pkg_headless --> pkg_agent_default_model
pkg_headless --> pkg_invariants
@@ -675,8 +664,6 @@ flowchart TD
pkg_command_feedback --> pkg_invariants
pkg_command_feedback --> pkg_session
pkg_command_feedback --> pkg_session_telemetry
pkg_host_apiproxy --> pkg_agent_presets
pkg_host_apiproxy --> pkg_invariants
pkg_permission_presets --> pkg_commands
pkg_permission_presets --> pkg_invariants
pkg_permission_presets --> pkg_sandbox
@@ -835,10 +822,6 @@ flowchart TD
pkg_tool_session_query --> pkg_system_prompt
pkg_tool_session_query --> pkg_timeout
pkg_tool_session_query --> pkg_tools
pkg_client_runtime --> pkg_api_remotes
pkg_client_runtime --> pkg_invariants
pkg_client_runtime --> pkg_typert_protocol
pkg_client_runtime --> pkg_typert_registry
pkg_command_compact --> pkg_commands
pkg_command_compact --> pkg_compaction
pkg_command_compact --> pkg_invariants
@@ -856,9 +839,14 @@ flowchart TD
pkg_session_reference --> pkg_output_retention
pkg_session_reference --> pkg_session
pkg_session_reference --> pkg_session_query
pkg_tool_cordis --> pkg_invariants
pkg_tool_cordis --> pkg_scope
pkg_tool_cordis --> pkg_tools
pkg_cordis_host_runner --> pkg_agent
pkg_cordis_host_runner --> pkg_brand
pkg_cordis_host_runner --> pkg_invariants
pkg_cordis_host_runner --> pkg_llm
pkg_cordis_host_runner --> pkg_scope
pkg_cordis_host_runner --> pkg_session
pkg_cordis_host_runner --> pkg_tools
pkg_cordis_host_runner --> pkg_typert_protocol
pkg_repeat_tool_reminder --> pkg_agent
pkg_repeat_tool_reminder --> pkg_invariants
pkg_repeat_tool_reminder --> pkg_tools
@@ -1007,29 +995,40 @@ flowchart TD
pkg_hooks_claude_code --> pkg_session_persistence
pkg_hooks_claude_code --> pkg_subagent
pkg_hooks_claude_code --> pkg_tools
pkg_api_remotes --> pkg_agent
pkg_api_remotes --> pkg_agent_presets
pkg_api_remotes --> pkg_api_gateway
pkg_api_remotes --> pkg_commands
pkg_api_remotes --> pkg_cordis_host_runner
pkg_api_remotes --> pkg_credentials
pkg_api_remotes --> pkg_goal
pkg_api_remotes --> pkg_host_plugin_inventory
pkg_api_remotes --> pkg_invariants
pkg_api_remotes --> pkg_llm
pkg_api_remotes --> pkg_message_feedback
pkg_api_remotes --> pkg_session
pkg_api_remotes --> pkg_session_persistence
pkg_api_remotes --> pkg_settings
pkg_api_remotes --> pkg_typert_registry
pkg_web_app --> pkg_invariants
pkg_web_app --> pkg_shell_env
pkg_web_app --> pkg_system_prompt
pkg_client_ui_settings --> pkg_api_remotes
pkg_client_ui_settings --> pkg_client_connection
pkg_client_ui_settings --> pkg_client_runtime
pkg_client_ui_settings --> pkg_client_schema_form
pkg_client_ui_settings --> pkg_client_ui_slots
pkg_client_ui_settings --> pkg_invariants
pkg_client_ui_settings --> pkg_settings
pkg_client_ui_settings_models --> pkg_api_remotes
pkg_client_ui_settings_models --> pkg_client_connection
pkg_client_ui_settings_models --> pkg_client_runtime
pkg_client_ui_settings_models --> pkg_client_schema_form
pkg_client_ui_settings_models --> pkg_client_ui_primitives
pkg_client_ui_settings_models --> pkg_client_ui_slots
pkg_client_ui_settings_models --> pkg_client_web_react
pkg_client_ui_settings_models --> pkg_invariants
pkg_compaction_tool_result_pruner --> pkg_compaction
pkg_compaction_tool_result_pruner --> pkg_invariants
pkg_compaction_tool_result_pruner --> pkg_llm
pkg_compaction_tool_result_pruner --> pkg_session
pkg_compaction_tool_result_pruner --> pkg_token_meter
pkg_tool_cordis --> pkg_agent
pkg_tool_cordis --> pkg_cordis_host_runner
pkg_tool_cordis --> pkg_invariants
pkg_tool_cordis --> pkg_llm
pkg_tool_cordis --> pkg_scope
pkg_tool_cordis --> pkg_session
pkg_tool_cordis --> pkg_system_prompt
pkg_tool_cordis --> pkg_tools
pkg_host_apiproxy --> pkg_agent_presets
pkg_host_apiproxy --> pkg_cordis_host_runner
pkg_host_apiproxy --> pkg_invariants
pkg_sdk_protocol --> pkg_invariants
pkg_sdk_protocol --> pkg_llm
pkg_sdk_protocol --> pkg_session
@@ -1056,11 +1055,6 @@ flowchart TD
pkg_tool_pwsh --> pkg_system_prompt
pkg_tool_pwsh --> pkg_tools
pkg_tool_pwsh --> pkg_user_approval
pkg_client_test_runtime --> pkg_client_runtime
pkg_client_test_runtime --> pkg_client_ui_slots
pkg_client_test_runtime --> pkg_client_web_react
pkg_client_test_runtime --> pkg_host_apiproxy
pkg_client_test_runtime --> pkg_invariants
pkg_tool_ralph --> pkg_agent
pkg_tool_ralph --> pkg_invariants
pkg_tool_ralph --> pkg_llm
@@ -1091,13 +1085,10 @@ flowchart TD
pkg_subagent_spawn_in_process --> pkg_invariants
pkg_subagent_spawn_in_process --> pkg_subagent
pkg_subagent_spawn_in_process --> pkg_subagent_in_process_driver
pkg_client_locale --> pkg_api_remotes
pkg_client_locale --> pkg_client_connection
pkg_client_locale --> pkg_client_runtime
pkg_client_locale --> pkg_client_ui_primitives
pkg_client_locale --> pkg_client_ui_settings
pkg_client_locale --> pkg_client_ui_slots
pkg_client_locale --> pkg_invariants
pkg_client_runtime --> pkg_api_remotes
pkg_client_runtime --> pkg_invariants
pkg_client_runtime --> pkg_typert_protocol
pkg_client_runtime --> pkg_typert_registry
pkg_compaction_basic --> pkg_agent
pkg_compaction_basic --> pkg_commands
pkg_compaction_basic --> pkg_compaction
@@ -1147,6 +1138,43 @@ flowchart TD
pkg_subagent_dsh_sdk --> pkg_session
pkg_subagent_dsh_sdk --> pkg_subagent
pkg_subagent_dsh_sdk --> pkg_subprocess
pkg_client_ui_settings --> pkg_api_remotes
pkg_client_ui_settings --> pkg_client_connection
pkg_client_ui_settings --> pkg_client_runtime
pkg_client_ui_settings --> pkg_client_schema_form
pkg_client_ui_settings --> pkg_client_ui_slots
pkg_client_ui_settings --> pkg_invariants
pkg_client_ui_settings --> pkg_settings
pkg_client_ui_settings_models --> pkg_api_remotes
pkg_client_ui_settings_models --> pkg_client_connection
pkg_client_ui_settings_models --> pkg_client_runtime
pkg_client_ui_settings_models --> pkg_client_schema_form
pkg_client_ui_settings_models --> pkg_client_ui_primitives
pkg_client_ui_settings_models --> pkg_client_ui_slots
pkg_client_ui_settings_models --> pkg_client_web_react
pkg_client_ui_settings_models --> pkg_invariants
pkg_acp_demo --> pkg_acp
pkg_acp_demo --> pkg_agent_instructions
pkg_acp_demo --> pkg_agent_spine_demo
pkg_acp_demo --> pkg_app_boot
pkg_acp_demo --> pkg_invariants
pkg_acp_demo --> pkg_session_checkpoint_policy
pkg_acp_demo --> pkg_session_persistence_jsonl
pkg_acp_demo --> pkg_session_query
pkg_acp_demo --> pkg_session_query_sqlite
pkg_acp_demo --> pkg_tools
pkg_client_test_runtime --> pkg_client_runtime
pkg_client_test_runtime --> pkg_client_ui_slots
pkg_client_test_runtime --> pkg_client_web_react
pkg_client_test_runtime --> pkg_host_apiproxy
pkg_client_test_runtime --> pkg_invariants
pkg_client_locale --> pkg_api_remotes
pkg_client_locale --> pkg_client_connection
pkg_client_locale --> pkg_client_runtime
pkg_client_locale --> pkg_client_ui_primitives
pkg_client_locale --> pkg_client_ui_settings
pkg_client_locale --> pkg_client_ui_slots
pkg_client_locale --> pkg_invariants
pkg_client_ui_input_trigger --> pkg_client_locale
pkg_client_ui_input_trigger --> pkg_client_runtime
pkg_client_ui_input_trigger --> pkg_client_ui_primitives
@@ -1197,16 +1225,6 @@ flowchart TD
pkg_client_ui_workspace --> pkg_client_ui_primitives
pkg_client_ui_workspace --> pkg_client_ui_slots
pkg_client_ui_workspace --> pkg_invariants
pkg_acp_demo --> pkg_acp
pkg_acp_demo --> pkg_agent_instructions
pkg_acp_demo --> pkg_agent_spine_demo
pkg_acp_demo --> pkg_app_boot
pkg_acp_demo --> pkg_invariants
pkg_acp_demo --> pkg_session_checkpoint_policy
pkg_acp_demo --> pkg_session_persistence_jsonl
pkg_acp_demo --> pkg_session_query
pkg_acp_demo --> pkg_session_query_sqlite
pkg_acp_demo --> pkg_tools
pkg_client_ui_conversation --> pkg_agent
pkg_client_ui_conversation --> pkg_api_remotes
pkg_client_ui_conversation --> pkg_attachment
@@ -1250,6 +1268,13 @@ flowchart TD
pkg_client_ui_settings_general --> pkg_client_ui_slots
pkg_client_ui_settings_general --> pkg_client_web_react
pkg_client_ui_settings_general --> pkg_invariants
pkg_cordis_client_runner --> pkg_api_remotes
pkg_cordis_client_runner --> pkg_client_connection
pkg_cordis_client_runner --> pkg_client_modules
pkg_cordis_client_runner --> pkg_client_runtime
pkg_cordis_client_runner --> pkg_client_ui_slots
pkg_cordis_client_runner --> pkg_client_ui_theme
pkg_cordis_client_runner --> pkg_invariants
pkg_client_ui_agent_preset --> pkg_api_remotes
pkg_client_ui_agent_preset --> pkg_client_connection
pkg_client_ui_agent_preset --> pkg_client_locale
@@ -1379,6 +1404,17 @@ flowchart TD
pkg_client_ui_skill --> pkg_client_ui_slots
pkg_client_ui_skill --> pkg_client_ui_tool
pkg_client_ui_skill --> pkg_invariants
pkg_client_ui_cordis --> pkg_api_remotes
pkg_client_ui_cordis --> pkg_client_connection
pkg_client_ui_cordis --> pkg_client_locale
pkg_client_ui_cordis --> pkg_client_runtime
pkg_client_ui_cordis --> pkg_client_ui_input_trigger
pkg_client_ui_cordis --> pkg_client_ui_primitives
pkg_client_ui_cordis --> pkg_client_ui_sidebar
pkg_client_ui_cordis --> pkg_client_ui_slots
pkg_client_ui_cordis --> pkg_client_ui_tool
pkg_client_ui_cordis --> pkg_cordis_client_runner
pkg_client_ui_cordis --> pkg_invariants
```
| Package | Group | Depends on |
@@ -1489,13 +1525,11 @@ flowchart TD
| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
| [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-title`](../packages/session/session-title) |
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`user-approval`](../packages/interaction/user-approval) |
| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`credentials`](../packages/credentials/credentials), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) |
| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
| [`fs-e2b`](../packages/e2b/fs-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`user-approval`](../packages/interaction/user-approval) |
| [`jobs-local`](../packages/jobs/jobs-local) | `jobs` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`scope`](../packages/core/scope), [`timeout`](../packages/util/timeout) |
| [`lsp-stdio`](../packages/lsp/lsp-stdio) | `lsp` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
@@ -1520,11 +1554,10 @@ flowchart TD
| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
| [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) |
| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`client-runtime`](../packages/client/runtime) | `client` | [`api-remotes`](../packages/api/remotes), [`invariants`](../packages/runtime-diagnostics/invariants), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry) |
| [`command-compact`](../packages/compaction/command-compact) | `compaction` | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) |
| [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`tools`](../packages/core/tools) |
| [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | `extensions` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
| [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools) |
| [`tool-call-timeout-policy`](../packages/guard/timeout-policy) | `guard` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`tool-ask-user`](../packages/interaction/tool-ask-user) | `interaction` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) |
@@ -1551,25 +1584,30 @@ flowchart TD
| [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`tool-subagent-report`](../packages/subagent/tool-subagent-report) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) |
| [`web-app`](../packages/bundle/web-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) |
| [`client-ui-settings`](../packages/client/ui-settings) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings) |
| [`client-ui-settings-models`](../packages/client/ui-settings-models) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
| [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
| [`tool-bash`](../packages/shell/tool-bash) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
| [`tool-pwsh`](../packages/shell/tool-pwsh) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
| [`client-test-runtime`](../packages/test-support/client-runtime) | `test-support` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`host-apiproxy`](../packages/host/apiproxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
| [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
| [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
| [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) |
| [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) |
| [`client-locale`](../packages/client/locale) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-runtime`](../packages/client/runtime) | `client` | [`api-remotes`](../packages/api/remotes), [`invariants`](../packages/runtime-diagnostics/invariants), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry) |
| [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
| [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs-local`](../packages/jobs/jobs-local), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`shell-env`](../packages/shell/shell-env), [`skill`](../packages/skill/skill), [`skill-filesystem`](../packages/skill/skill-filesystem), [`system-prompt`](../packages/core/system-prompt), [`tool-bash`](../packages/shell/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-jobs`](../packages/jobs/tool-jobs), [`tool-skill`](../packages/skill/tool-skill), [`tools`](../packages/core/tools) |
| [`sdk-client`](../packages/sdk/client) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session) |
| [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
| [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) |
| [`client-ui-settings`](../packages/client/ui-settings) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings) |
| [`client-ui-settings-models`](../packages/client/ui-settings-models) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/boot/app-boot), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`tools`](../packages/core/tools) |
| [`client-test-runtime`](../packages/test-support/client-runtime) | `test-support` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`host-apiproxy`](../packages/host/apiproxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-locale`](../packages/client/locale) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-input-trigger`](../packages/client/ui-input-trigger) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-settings-plugin-inventory`](../packages/client/ui-settings-plugin-inventory) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-settings-plugins`](../packages/client/ui-settings-plugins) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) |
@@ -1578,12 +1616,12 @@ flowchart TD
| [`client-ui-trajectory`](../packages/client/ui-trajectory) | `client` | [`agent`](../packages/core/agent), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools) |
| [`client-ui-user-questions`](../packages/client/ui-user-questions) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/boot/app-boot), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`tools`](../packages/core/tools) |
| [`client-ui-conversation`](../packages/client/ui-conversation) | `client` | [`agent`](../packages/core/agent), [`api-remotes`](../packages/api/remotes), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-attachment`](../packages/client/ui-attachment), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm-retry`](../packages/llm/llm-retry), [`session-stats`](../packages/session/session-stats), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) |
| [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-layout`](../packages/client/ui-layout) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-settings-general`](../packages/client/ui-settings-general) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-sidebar`](../packages/client/ui-sidebar), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`cordis-client-runner`](../packages/extensions/cordis-client-runner) | `extensions` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-agent-preset`](../packages/client/ui-agent-preset) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-commands`](../packages/client/ui-commands) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`system-prompt`](../packages/core/system-prompt) |
@@ -1599,3 +1637,4 @@ flowchart TD
| [`client-ui-model-selection`](../packages/client/ui-model-selection) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-commands`](../packages/client/ui-commands), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-permission-presets`](../packages/client/ui-permission-presets) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-commands`](../packages/client/ui-commands), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`permission-presets`](../packages/interaction/permission-presets) |
| [`client-ui-skill`](../packages/client/ui-skill) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-tool`](../packages/client/ui-tool), [`invariants`](../packages/runtime-diagnostics/invariants) |
| [`client-ui-cordis`](../packages/extensions/ui-cordis) | `extensions` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-sidebar`](../packages/client/ui-sidebar), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-tool`](../packages/client/ui-tool), [`cordis-client-runner`](../packages/extensions/cordis-client-runner), [`invariants`](../packages/runtime-diagnostics/invariants) |
+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 docs/subsystems/README.md
README.md: 9dafb548060f884ab98b20d64896a974a0ad6e81
README.zh.md: f5298e7c0268d53da4322aadbe960a2f622ce8f1
README.md: a1c2262f40aebdf4b0fdcdca4b0c05bae443fe2f
README.zh.md: 0b5c00e6321a67a11dc6f98caedc7e26fb98eec2
+1
View File
@@ -32,6 +32,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures
| [terminal.md](terminal.md) | persistent terminal ids, backend/session contracts, send readiness, bounded reads, and owner-visible snapshots |
| [sandbox.md](sandbox.md) | per-session policy resolution and the process-confinement seam: file-effect modes, execution/provider policies, `ConfinedArgv`, enforcement and fail-closed errors |
| [code-runtime.md](code-runtime.md) | the code-execution seam: `CodeRunRequest`/`Result`, binding namespaces, captured logs, the `CodeRunFailure` taxonomy |
| [extensions.md](extensions.md) | versioned dynamic Cordis Plugins and Packages, Host/Client activation, approval, runtime inspection, and lifecycle teardown |
| [filesystem.md](filesystem.md) | the filesystem seam: `FsTarget`, read/write/edit outcomes, observed-file state, `FsErrorCode` |
| [lsp.md](lsp.md) | the LSP navigation seam: `LspQueryRequest`/`Result`, `LspProvider`/`Service`, four operations, `LspError` |
| [skills.md](skills.md) | the skill service: discovery priority, `SkillSummary`/`SkillDefinition`, session-prefix catalog, model-facing `skill` loading |
+1
View File
@@ -32,6 +32,7 @@
| [terminal.md](terminal.md) | 持久化终端 ID、后端/会话约定、发送就绪状态、有界读取与 owner 可见快照 |
| [sandbox.md](sandbox.md) | 每会话策略解析与进程约束 seam:文件效果模式、执行/提供方策略、`ConfinedArgv`、强制执行与故障关闭错误 |
| [code-runtime.md](code-runtime.md) | 代码执行 seam`CodeRunRequest`/`Result`、绑定命名空间、捕获日志、`CodeRunFailure` 分类体系 |
| [extensions.md](extensions.md) | 带版本的动态 Cordis Plugin 与 Package、Host/Client 激活、审批、运行时检查和生命周期撤销 |
| [filesystem.md](filesystem.md) | 文件系统 seam`FsTarget`、读/写/编辑结果、观测到的文件状态、`FsErrorCode` |
| [lsp.md](lsp.md) | LSP 导航 seam`LspQueryRequest`/`Result``LspProvider`/`Service`、四种操作、`LspError` |
| [skills.md](skills.md) | skill(技能)服务:发现优先级、`SkillSummary`/`SkillDefinition`、会话前缀目录、面向模型的 `skill` 加载 |
+6
View File
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/extensions.md
extensions.md: f212f99704cb1cc871a77ee6cc10188746771958
extensions.zh.md: 717b14951dd262213cfc42a380777d233fb6eea5
+364
View File
@@ -0,0 +1,364 @@
# Extensions
English | [中文](extensions.zh.md)
The extensions subsystem lets an agent define versioned Cordis packages, run their host and browser halves, and query approved runtime metadata before writing code. Package lifecycle and sandbox behavior belong to the [`packages/extensions`](../../packages/extensions/README.md) package group.
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
<a id="cordis-surface"></a>
## Cordis API
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxcordisinspect--cordisinspectregistryservice"></a>
### `ctx.cordisInspect` — `CordisInspectRegistryService`
Registry and cross-page router behind the two model-facing inspect tools.
```ts cordis-catalog
/**
* Register one Host provider.
* @param registration - manifest and local query handler.
* @returns idempotent disposer.
*/
register(registration: HostCordisInspectProviderRegistration): () => void
/**
* Replace the mirrored Client provider directory.
* @param providers - complete Client manifest snapshot.
*/
syncClientManifest(providers: readonly CordisInspectProviderManifest[]): void
/**
* Return the complete known Host and Client provider directory.
* @returns Host providers followed by the Client providers.
*/
list(): CordisInspectProviderView[]
/**
* Execute one provider query on its owning platform.
* @param platform - Host or Client runtime.
* @param providerId - provider selected from {@link list}.
* @param methodName - declared method name.
* @param input - optional lossless JSON input.
* @param agent - requesting Agent and scope.
* @param signal - tool-call cancellation.
* @returns provider JSON data.
*/
async query( platform: CordisInspectPlatform, providerId: string, methodName: string, input: JsonValue | undefined, agent: Agent, signal: AbortSignal, ): Promise<JsonValue>
/**
* Accept the first valid Client response for a pending query.
* @param agent - Agent whose Session owns the query.
* @param requestId - Pending Client query identity.
* @param resolution - Client provider result or failure.
* @returns whether this response settled the still-pending query.
*/
resolveClientQuery( agent: Agent, requestId: CordisInspectRequestId, resolution: CordisInspectQueryResolution, ): CordisInspectResolveAck
```
Types: [Agent](core.md)
Source: [`packages/extensions/cordis-host-runner/src/inspect-registry.ts:46`](../../packages/extensions/cordis-host-runner/src/inspect-registry.ts)
<a id="ctxdynamiccordisrunner--dynamiccordisrunnerservice"></a>
### `ctx.dynamicCordisRunner` — `DynamicCordisRunnerService`
Dynamic Plugin registry and Host-half lifecycle.
```ts cordis-catalog
/**
* Define a new Plugin's first Package or append a Package to an existing Plugin.
* @param request - Session ownership, Plugin selection, metadata, and source code.
* @returns Host-minted Plugin and Package identities with declared-half metadata.
*/
define(request: DynamicCordisDefineRequest): DynamicCordisDefineReceipt
/**
* Remove a Plugin, its active run, and all immutable Packages.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to remove.
* @returns Whether removal succeeded and whether it stopped an active run.
*/
async undefine(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisUndefineReceipt>
/**
* Remove a Plugin from the user panel and queue the resulting state change for the model's next step.
* @param agent - Agent whose Session owns the Plugin and receives the context.
* @param pluginId - Stable Plugin identity to remove.
* @returns Whether removal succeeded and whether it stopped an active run.
*/
@Remote('undefineFromPanel') async undefineFromPanel(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisUndefineReceipt>
/**
* Start or update one Package for a model tool call. An unauthorized Client
* Package waits for approval; Plugin-wide authorization covers later versions.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to activate.
* @param packageId - Immutable Package version to activate.
* @param mode - Whether to run the current version or switch versions.
* @param signal - Tool-call cancellation signal while the activation request is being created.
* @returns The successful activation identity or an actionable refusal.
*/
async run( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, mode: CordisDynamicRunMode, signal?: AbortSignal, ): Promise<DynamicCordisRunResponse>
/**
* Start Host code for an approved request or a direct panel gesture.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to activate.
* @param packageId - Immutable Package version to activate.
* @param mode - Whether to run the current version or switch versions.
* @param requestId - Model-driven request identity, or null for a direct user gesture.
* @param approveFutureVersions - Whether this approval covers later Packages of the same Plugin.
* @returns The exact Host activation or a failure message.
*/
@Remote('runHostHalf') async runHostHalf( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, mode: CordisDynamicRunMode, requestId: ApprovalRequestId | null, approveFutureVersions: boolean, ): Promise<DynamicCordisHostHalfResult>
/**
* Fetch Client code for the exact active run.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to read.
* @param pluginRunId - Exact active run authorized to receive source.
* @returns Client source and its Plugin, Package, and run identities.
*/
@Remote('getClientCode') getClientCode( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, ): DynamicCordisClientSource
/**
* Resolve one model-driven Client activation request.
* @param requestId - Request identity to settle once.
* @param resolution - Browser refusal or exact Client activation result.
* @returns Whether the still-pending request accepted this resolution.
*/
@Remote('resolveRequestRun') async resolveRequestRun( requestId: ApprovalRequestId, resolution: DynamicCordisRunResolution, ): Promise<DynamicCordisResolveAck>
/**
* Settle a direct panel run after this page loaded or failed its Client half.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity being settled.
* @param resolution - Exact Client activation result from the acting page.
* @returns The committed activation or its failure.
*/
@Remote('settleUserRun') async settleUserRun( agent: Agent, pluginId: CordisDynamicPluginId, resolution: DynamicCordisRunResolution, ): Promise<DynamicCordisRunResponse>
/**
* Stop the active run while retaining every Package version.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to stop.
* @returns Success or the reason no run was stopped.
*/
async stop(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisStopResponse>
/**
* Stop a Plugin from the user panel and queue the resulting state change for the model's next step.
* @param agent - Agent whose Session owns the Plugin and receives the context.
* @param pluginId - Stable Plugin identity to stop.
* @returns Success or the reason no run was stopped.
*/
@Remote('stopFromPanel') async stopFromPanel(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisStopResponse>
/**
* Replace the Host mirror of the Client inspect provider directory.
* @param providers - complete Client provider manifest.
* @returns null after accepting the manifest.
*/
@Remote('syncInspectManifest') syncInspectManifest(providers: readonly CordisInspectProviderManifest[]): null
/**
* Claim one pending Client inspect query with its live result.
* @param agent - Session that owns the query.
* @param requestId - exact pending query identity.
* @param resolution - provider result or structured refusal.
* @returns whether this answer won the query.
*/
@Remote('resolveInspectQuery') resolveInspectQuery( agent: Agent, requestId: CordisInspectRequestId, resolution: CordisInspectQueryResolution, ): CordisInspectResolveAck
/**
* Frame-wide inventory, grouped as one row per stable Plugin.
* @returns Source-free metadata for every process-local Plugin.
*/
@Remote('inventory') inventory(): DynamicCordisInventoryRow[]
/**
* Read one Session's Host-rich state for inspection and result rendering.
* @param agent - Agent whose Session selects visible Plugins.
* @returns Plugin versions, active runs, Host fibers, and render failures.
*/
snapshot(agent: Agent): DynamicCordisSnapshotRow[]
/**
* Read source-free context for an explicit `@pluginId` user gesture.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity referenced by the user.
* @returns The preferred modification base, or undefined when unavailable.
*/
reference(agent: Agent, pluginId: CordisDynamicPluginId): DynamicCordisReference | undefined
/**
* List source-free Plugin summaries owned by one Session.
* @param agent - Agent whose Session selects visible Plugins.
* @returns one summary per Plugin in creation order.
*/
listPlugins(agent: Agent): DynamicCordisPluginInspection[]
/**
* Inspect one Plugin without returning Package source.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - stable Plugin identity.
* @returns version pointers, latest run, and all Package summaries.
*/
inspectPlugin(agent: Agent, pluginId: CordisDynamicPluginId): DynamicCordisPluginInspection
/**
* Read one exact immutable Package and its Host and Client source.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity that owns the Package.
* @param packageId - Exact immutable Package identity to inspect.
* @returns Package metadata, source, and the Plugin's lifecycle pointers.
*/
inspectPackage( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, ): DynamicCordisPackageInspection
/**
* Record a post-load render failure for the exact active run.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity that rendered.
* @param pluginRunId - Exact active run that produced the failure.
* @param failure - Slot, message, and entry-retirement result.
* @returns Null after recording or ignoring a stale report.
*/
@Remote('reportRenderFailure') async reportRenderFailure( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, failure: DynamicCordisRenderFailure, ): Promise<null>
/**
* Report a Client guard rejection that happened after the Package completed activation.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity whose Client code was rejected.
* @param pluginRunId - Exact active run that produced the rejection.
* @param failure - Original guard message and stack.
* @returns Null after reporting or ignoring a stale/startup failure.
*/
@Remote('reportClientGuardFailure') async reportClientGuardFailure( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, failure: CordisErrorDetails, ): Promise<null>
/**
* Invoke an active Host method while rejecting stale Client runs.
* @param pluginId - Stable Plugin identity that owns the method.
* @param pluginRunId - Exact active run authorizing the call.
* @param method - Registered Host handler name.
* @param args - JSON argument delivered to the handler.
* @returns The JSON result or a typed invocation failure.
*/
@Remote('invoke') async invoke( pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, method: string, args: JsonValue, ): Promise<DynamicCordisInvokeResult>
```
Types: [Agent](core.md)
Source: [`packages/extensions/cordis-host-runner/src/index.ts:124`](../../packages/extensions/cordis-host-runner/src/index.ts)
<a id="cordis-events"></a>
### `cordis/*` events
<a id="cordisdynamic-package--emit"></a>
#### `cordis/dynamic-package` — emit
One exact Plugin/Package activation is now live in the Host.
```ts cordis-catalog
/**
* One exact Plugin/Package activation is now live in the Host.
* @param pkg - stable plugin, immutable package, run identity, and label.
* @mode emit
*/
'cordis/dynamic-package'(pkg: DynamicCordisPackage): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:379`](../../packages/extensions/cordis-host-runner/src/types.ts)
<a id="cordisdynamic-retract--emit"></a>
#### `cordis/dynamic-retract` — emit
One exact activation was withdrawn.
```ts cordis-catalog
/**
* One exact activation was withdrawn.
* @param retracted - plugin, package, and run identity.
* @mode emit
*/
'cordis/dynamic-retract'(retracted: DynamicCordisRetracted): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:385`](../../packages/extensions/cordis-host-runner/src/types.ts)
<a id="cordisinspect-query--emit"></a>
#### `cordis/inspect-query` — emit
Request a live read-only query from the Client inspect registry.
```ts cordis-catalog
/**
* Request a live read-only query from the Client inspect registry.
* @param request - correlation, Session, provider, method, and JSON input.
* @mode emit
*/
'cordis/inspect-query'(request: CordisInspectQueryRequest): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:391`](../../packages/extensions/cordis-host-runner/src/types.ts)
<a id="cordisinspect-query-resolved--emit"></a>
#### `cordis/inspect-query-resolved` — emit
Notify every Client that an inspect query has settled or been cancelled.
```ts cordis-catalog
/**
* Notify every Client that an inspect query has settled or been cancelled.
* @param resolved - exact query identity that is no longer answerable.
* @mode emit
*/
'cordis/inspect-query-resolved'(resolved: CordisInspectQueryResolved): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:397`](../../packages/extensions/cordis-host-runner/src/types.ts)
<a id="cordisrequest-run--emit"></a>
#### `cordis/request-run` — emit
A Client-bearing activation needs a browser page, and may require a user decision.
```ts cordis-catalog
/**
* A Client-bearing activation needs a browser page, and may require a user decision.
* @param request - correlation identity, owner, target version, mode, and approval requirement.
* @mode emit
*/
'cordis/request-run'(request: DynamicCordisRunRequest): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:367`](../../packages/extensions/cordis-host-runner/src/types.ts)
<a id="cordisrequest-run-resolved--emit"></a>
#### `cordis/request-run-resolved` — emit
A pending Client activation request left the answerable state.
```ts cordis-catalog
/**
* A pending Client activation request left the answerable state.
* @param resolved - request identity and outcome.
* @mode emit
*/
'cordis/request-run-resolved'(resolved: DynamicCordisRequestResolved): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:373`](../../packages/extensions/cordis-host-runner/src/types.ts)
<!-- END GENERATED cordis-surface -->
+364
View File
@@ -0,0 +1,364 @@
# 扩展
[English](extensions.md) | 中文
extensions 子系统允许 agent(智能体)定义带版本的 Cordis 包、运行其 host 与浏览器两半,并在编写代码前查询获准公开的运行时元数据。包生命周期与沙箱行为由 [`packages/extensions`](../../packages/extensions/README.md) 包组说明。
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
<a id="cordis-surface"></a>
## Cordis API
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxcordisinspect--cordisinspectregistryservice"></a>
### `ctx.cordisInspect` — `CordisInspectRegistryService`
Registry and cross-page router behind the two model-facing inspect tools.
```ts cordis-catalog
/**
* Register one Host provider.
* @param registration - manifest and local query handler.
* @returns idempotent disposer.
*/
register(registration: HostCordisInspectProviderRegistration): () => void
/**
* Replace the mirrored Client provider directory.
* @param providers - complete Client manifest snapshot.
*/
syncClientManifest(providers: readonly CordisInspectProviderManifest[]): void
/**
* Return the complete known Host and Client provider directory.
* @returns Host providers followed by the Client providers.
*/
list(): CordisInspectProviderView[]
/**
* Execute one provider query on its owning platform.
* @param platform - Host or Client runtime.
* @param providerId - provider selected from {@link list}.
* @param methodName - declared method name.
* @param input - optional lossless JSON input.
* @param agent - requesting Agent and scope.
* @param signal - tool-call cancellation.
* @returns provider JSON data.
*/
async query( platform: CordisInspectPlatform, providerId: string, methodName: string, input: JsonValue | undefined, agent: Agent, signal: AbortSignal, ): Promise<JsonValue>
/**
* Accept the first valid Client response for a pending query.
* @param agent - Agent whose Session owns the query.
* @param requestId - Pending Client query identity.
* @param resolution - Client provider result or failure.
* @returns whether this response settled the still-pending query.
*/
resolveClientQuery( agent: Agent, requestId: CordisInspectRequestId, resolution: CordisInspectQueryResolution, ): CordisInspectResolveAck
```
Types: [Agent](core.md)
Source: [`packages/extensions/cordis-host-runner/src/inspect-registry.ts:46`](../../packages/extensions/cordis-host-runner/src/inspect-registry.ts)
<a id="ctxdynamiccordisrunner--dynamiccordisrunnerservice"></a>
### `ctx.dynamicCordisRunner` — `DynamicCordisRunnerService`
Dynamic Plugin registry and Host-half lifecycle.
```ts cordis-catalog
/**
* Define a new Plugin's first Package or append a Package to an existing Plugin.
* @param request - Session ownership, Plugin selection, metadata, and source code.
* @returns Host-minted Plugin and Package identities with declared-half metadata.
*/
define(request: DynamicCordisDefineRequest): DynamicCordisDefineReceipt
/**
* Remove a Plugin, its active run, and all immutable Packages.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to remove.
* @returns Whether removal succeeded and whether it stopped an active run.
*/
async undefine(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisUndefineReceipt>
/**
* Remove a Plugin from the user panel and queue the resulting state change for the model's next step.
* @param agent - Agent whose Session owns the Plugin and receives the context.
* @param pluginId - Stable Plugin identity to remove.
* @returns Whether removal succeeded and whether it stopped an active run.
*/
@Remote('undefineFromPanel') async undefineFromPanel(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisUndefineReceipt>
/**
* Start or update one Package for a model tool call. An unauthorized Client
* Package waits for approval; Plugin-wide authorization covers later versions.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to activate.
* @param packageId - Immutable Package version to activate.
* @param mode - Whether to run the current version or switch versions.
* @param signal - Tool-call cancellation signal while the activation request is being created.
* @returns The successful activation identity or an actionable refusal.
*/
async run( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, mode: CordisDynamicRunMode, signal?: AbortSignal, ): Promise<DynamicCordisRunResponse>
/**
* Start Host code for an approved request or a direct panel gesture.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to activate.
* @param packageId - Immutable Package version to activate.
* @param mode - Whether to run the current version or switch versions.
* @param requestId - Model-driven request identity, or null for a direct user gesture.
* @param approveFutureVersions - Whether this approval covers later Packages of the same Plugin.
* @returns The exact Host activation or a failure message.
*/
@Remote('runHostHalf') async runHostHalf( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, mode: CordisDynamicRunMode, requestId: ApprovalRequestId | null, approveFutureVersions: boolean, ): Promise<DynamicCordisHostHalfResult>
/**
* Fetch Client code for the exact active run.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to read.
* @param pluginRunId - Exact active run authorized to receive source.
* @returns Client source and its Plugin, Package, and run identities.
*/
@Remote('getClientCode') getClientCode( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, ): DynamicCordisClientSource
/**
* Resolve one model-driven Client activation request.
* @param requestId - Request identity to settle once.
* @param resolution - Browser refusal or exact Client activation result.
* @returns Whether the still-pending request accepted this resolution.
*/
@Remote('resolveRequestRun') async resolveRequestRun( requestId: ApprovalRequestId, resolution: DynamicCordisRunResolution, ): Promise<DynamicCordisResolveAck>
/**
* Settle a direct panel run after this page loaded or failed its Client half.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity being settled.
* @param resolution - Exact Client activation result from the acting page.
* @returns The committed activation or its failure.
*/
@Remote('settleUserRun') async settleUserRun( agent: Agent, pluginId: CordisDynamicPluginId, resolution: DynamicCordisRunResolution, ): Promise<DynamicCordisRunResponse>
/**
* Stop the active run while retaining every Package version.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity to stop.
* @returns Success or the reason no run was stopped.
*/
async stop(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisStopResponse>
/**
* Stop a Plugin from the user panel and queue the resulting state change for the model's next step.
* @param agent - Agent whose Session owns the Plugin and receives the context.
* @param pluginId - Stable Plugin identity to stop.
* @returns Success or the reason no run was stopped.
*/
@Remote('stopFromPanel') async stopFromPanel(agent: Agent, pluginId: CordisDynamicPluginId): Promise<DynamicCordisStopResponse>
/**
* Replace the Host mirror of the Client inspect provider directory.
* @param providers - complete Client provider manifest.
* @returns null after accepting the manifest.
*/
@Remote('syncInspectManifest') syncInspectManifest(providers: readonly CordisInspectProviderManifest[]): null
/**
* Claim one pending Client inspect query with its live result.
* @param agent - Session that owns the query.
* @param requestId - exact pending query identity.
* @param resolution - provider result or structured refusal.
* @returns whether this answer won the query.
*/
@Remote('resolveInspectQuery') resolveInspectQuery( agent: Agent, requestId: CordisInspectRequestId, resolution: CordisInspectQueryResolution, ): CordisInspectResolveAck
/**
* Frame-wide inventory, grouped as one row per stable Plugin.
* @returns Source-free metadata for every process-local Plugin.
*/
@Remote('inventory') inventory(): DynamicCordisInventoryRow[]
/**
* Read one Session's Host-rich state for inspection and result rendering.
* @param agent - Agent whose Session selects visible Plugins.
* @returns Plugin versions, active runs, Host fibers, and render failures.
*/
snapshot(agent: Agent): DynamicCordisSnapshotRow[]
/**
* Read source-free context for an explicit `@pluginId` user gesture.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity referenced by the user.
* @returns The preferred modification base, or undefined when unavailable.
*/
reference(agent: Agent, pluginId: CordisDynamicPluginId): DynamicCordisReference | undefined
/**
* List source-free Plugin summaries owned by one Session.
* @param agent - Agent whose Session selects visible Plugins.
* @returns one summary per Plugin in creation order.
*/
listPlugins(agent: Agent): DynamicCordisPluginInspection[]
/**
* Inspect one Plugin without returning Package source.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - stable Plugin identity.
* @returns version pointers, latest run, and all Package summaries.
*/
inspectPlugin(agent: Agent, pluginId: CordisDynamicPluginId): DynamicCordisPluginInspection
/**
* Read one exact immutable Package and its Host and Client source.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity that owns the Package.
* @param packageId - Exact immutable Package identity to inspect.
* @returns Package metadata, source, and the Plugin's lifecycle pointers.
*/
inspectPackage( agent: Agent, pluginId: CordisDynamicPluginId, packageId: CordisDynamicPackageId, ): DynamicCordisPackageInspection
/**
* Record a post-load render failure for the exact active run.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity that rendered.
* @param pluginRunId - Exact active run that produced the failure.
* @param failure - Slot, message, and entry-retirement result.
* @returns Null after recording or ignoring a stale report.
*/
@Remote('reportRenderFailure') async reportRenderFailure( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, failure: DynamicCordisRenderFailure, ): Promise<null>
/**
* Report a Client guard rejection that happened after the Package completed activation.
* @param agent - Agent whose Session must own the Plugin.
* @param pluginId - Stable Plugin identity whose Client code was rejected.
* @param pluginRunId - Exact active run that produced the rejection.
* @param failure - Original guard message and stack.
* @returns Null after reporting or ignoring a stale/startup failure.
*/
@Remote('reportClientGuardFailure') async reportClientGuardFailure( agent: Agent, pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, failure: CordisErrorDetails, ): Promise<null>
/**
* Invoke an active Host method while rejecting stale Client runs.
* @param pluginId - Stable Plugin identity that owns the method.
* @param pluginRunId - Exact active run authorizing the call.
* @param method - Registered Host handler name.
* @param args - JSON argument delivered to the handler.
* @returns The JSON result or a typed invocation failure.
*/
@Remote('invoke') async invoke( pluginId: CordisDynamicPluginId, pluginRunId: CordisDynamicPluginRunId, method: string, args: JsonValue, ): Promise<DynamicCordisInvokeResult>
```
Types: [Agent](core.md)
Source: [`packages/extensions/cordis-host-runner/src/index.ts:124`](../../packages/extensions/cordis-host-runner/src/index.ts)
<a id="cordis-events"></a>
### `cordis/*` events
<a id="cordisdynamic-package--emit"></a>
#### `cordis/dynamic-package` — emit
One exact Plugin/Package activation is now live in the Host.
```ts cordis-catalog
/**
* One exact Plugin/Package activation is now live in the Host.
* @param pkg - stable plugin, immutable package, run identity, and label.
* @mode emit
*/
'cordis/dynamic-package'(pkg: DynamicCordisPackage): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:379`](../../packages/extensions/cordis-host-runner/src/types.ts)
<a id="cordisdynamic-retract--emit"></a>
#### `cordis/dynamic-retract` — emit
One exact activation was withdrawn.
```ts cordis-catalog
/**
* One exact activation was withdrawn.
* @param retracted - plugin, package, and run identity.
* @mode emit
*/
'cordis/dynamic-retract'(retracted: DynamicCordisRetracted): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:385`](../../packages/extensions/cordis-host-runner/src/types.ts)
<a id="cordisinspect-query--emit"></a>
#### `cordis/inspect-query` — emit
Request a live read-only query from the Client inspect registry.
```ts cordis-catalog
/**
* Request a live read-only query from the Client inspect registry.
* @param request - correlation, Session, provider, method, and JSON input.
* @mode emit
*/
'cordis/inspect-query'(request: CordisInspectQueryRequest): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:391`](../../packages/extensions/cordis-host-runner/src/types.ts)
<a id="cordisinspect-query-resolved--emit"></a>
#### `cordis/inspect-query-resolved` — emit
Notify every Client that an inspect query has settled or been cancelled.
```ts cordis-catalog
/**
* Notify every Client that an inspect query has settled or been cancelled.
* @param resolved - exact query identity that is no longer answerable.
* @mode emit
*/
'cordis/inspect-query-resolved'(resolved: CordisInspectQueryResolved): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:397`](../../packages/extensions/cordis-host-runner/src/types.ts)
<a id="cordisrequest-run--emit"></a>
#### `cordis/request-run` — emit
A Client-bearing activation needs a browser page, and may require a user decision.
```ts cordis-catalog
/**
* A Client-bearing activation needs a browser page, and may require a user decision.
* @param request - correlation identity, owner, target version, mode, and approval requirement.
* @mode emit
*/
'cordis/request-run'(request: DynamicCordisRunRequest): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:367`](../../packages/extensions/cordis-host-runner/src/types.ts)
<a id="cordisrequest-run-resolved--emit"></a>
#### `cordis/request-run-resolved` — emit
A pending Client activation request left the answerable state.
```ts cordis-catalog
/**
* A pending Client activation request left the answerable state.
* @param resolved - request identity and outcome.
* @mode emit
*/
'cordis/request-run-resolved'(resolved: DynamicCordisRequestResolved): void
```
Source: [`packages/extensions/cordis-host-runner/src/types.ts:373`](../../packages/extensions/cordis-host-runner/src/types.ts)
<!-- END GENERATED cordis-surface -->
+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 docs/subsystems/lsp.md
lsp.md: fe2d30c84b3e2ccea82b9fb1edd370845267b6d9
lsp.zh.md: d821a2493b78f8637d9917241838eaedf6e0af8b
lsp.md: 66317acc25860cfeaa3d8fb35daf2947e811e18c
lsp.zh.md: 8d975e30cb5065fd89645166c657050233eb81da
+37
View File
@@ -163,3 +163,40 @@ interface LspService {
```
`LspProviderId` is the seam's branded id (`Branded<'LspProviderId'>` from [dsh-brand](../../packages/util/brand)); `LspError` extends `HarnessError` with stable codes such as `LSP_INVALID_PROVIDER`, `LSP_CONFLICT`, `LSP_UNAVAILABLE`, `LSP_DISPOSED`, `LSP_UNSUPPORTED_OPERATION`, and `LSP_MALFORMED_RESPONSE`, which callers route on instead of parsing `message`.
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
<a id="cordis-surface"></a>
## Cordis API
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxlsp--lspservice"></a>
### `ctx.lsp` — `LspService`
The LSP capability seam (`ctx.lsp`). Owns provider registration/selection and normalized query execution; exposes exactly the four operations and no protocol escape hatch.
```ts cordis-catalog
/**
* Register a provider, atomically reserving its id and every normalized extension. Any conflict
* or invalid input publishes nothing and throws `LspError`; the returned disposer releases all
* reservations. Disposed with the calling fiber.
* @param provider - the backend to register.
* @returns a synchronous disposer releasing the id and all extension reservations.
*/
registerProvider(provider: LspProvider): () => void
/**
* Select a provider by the file's extension and run one query. Selection is per-query and
* order-independent; no match throws `LspError` `LSP_UNAVAILABLE`.
* @param request - the normalized query.
* @param signal - optional cancellation forwarded to the selected provider.
* @returns the normalized, closed-union result.
*/
query(request: LspQueryRequest, signal?: AbortSignal): Promise<LspQueryResult>
```
Source: [`packages/lsp/lsp/src/types.ts:113`](../../packages/lsp/lsp/src/types.ts)
<!-- END GENERATED cordis-surface -->
+37
View File
@@ -163,3 +163,40 @@ interface LspService {
```
`LspProviderId` 是该 seam 的品牌化 id(来自 [dsh-brand](../../packages/util/brand) 的 `Branded<'LspProviderId'>`);`LspError` 扩展 `HarnessError`,提供 `LSP_INVALID_PROVIDER`、`LSP_CONFLICT`、`LSP_UNAVAILABLE`、`LSP_DISPOSED`、`LSP_UNSUPPORTED_OPERATION` 和 `LSP_MALFORMED_RESPONSE` 等稳定错误码,调用方应按错误码路由,而不是解析 `message`。
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
<a id="cordis-surface"></a>
## Cordis API
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxlsp--lspservice"></a>
### `ctx.lsp` — `LspService`
The LSP capability seam (`ctx.lsp`). Owns provider registration/selection and normalized query execution; exposes exactly the four operations and no protocol escape hatch.
```ts cordis-catalog
/**
* Register a provider, atomically reserving its id and every normalized extension. Any conflict
* or invalid input publishes nothing and throws `LspError`; the returned disposer releases all
* reservations. Disposed with the calling fiber.
* @param provider - the backend to register.
* @returns a synchronous disposer releasing the id and all extension reservations.
*/
registerProvider(provider: LspProvider): () => void
/**
* Select a provider by the file's extension and run one query. Selection is per-query and
* order-independent; no match throws `LspError` `LSP_UNAVAILABLE`.
* @param request - the normalized query.
* @param signal - optional cancellation forwarded to the selected provider.
* @returns the normalized, closed-union result.
*/
query(request: LspQueryRequest, signal?: AbortSignal): Promise<LspQueryResult>
```
Source: [`packages/lsp/lsp/src/types.ts:113`](../../packages/lsp/lsp/src/types.ts)
<!-- END GENERATED cordis-surface -->
+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 docs/subsystems/typert.md
typert.md: 19d517f85ebd9372252608124fef4f513b1462a0
typert.zh.md: ee69f6fa7d99cb44f8e7f07bd4cd82fc70c1fa18
typert.md: 863ab9821bbf3681ae43df817cc04018e275390c
typert.zh.md: 4883b4be51a09d8e63001284e42bd7e60497bf1d
+17
View File
@@ -233,6 +233,23 @@ interface TypertClientRemote extends TypertRemoteNamespaceMap {
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxapiproxy--apiproxy"></a>
### `ctx.apiProxy` — `ApiProxy`
Root interface of the unified API. New client-request domain = one new file pair + one field here + one map row.
```ts cordis-catalog
/**
* Response entry for server requests; not a domain method.
* @param message - Client response carrying the server request's rpcId.
* @returns Transport receipt for the response delivery.
*/
respond(message: ClientResponse): Promise<RpcReceipt>
```
Source: [`packages/host/apiproxy/src/api/index.ts:22`](../../packages/host/apiproxy/src/api/index.ts)
<a id="ctxtypert--typertregistry"></a>
### `ctx.typert` — `TypertRegistry`
+17
View File
@@ -233,6 +233,23 @@ interface TypertClientRemote extends TypertRemoteNamespaceMap {
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
<a id="ctxapiproxy--apiproxy"></a>
### `ctx.apiProxy` — `ApiProxy`
Root interface of the unified API. New client-request domain = one new file pair + one field here + one map row.
```ts cordis-catalog
/**
* Response entry for server requests; not a domain method.
* @param message - Client response carrying the server request's rpcId.
* @returns Transport receipt for the response delivery.
*/
respond(message: ClientResponse): Promise<RpcReceipt>
```
Source: [`packages/host/apiproxy/src/api/index.ts:22`](../../packages/host/apiproxy/src/api/index.ts)
<a id="ctxtypert--typertregistry"></a>
### `ctx.typert` — `TypertRegistry`
+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 docs/tool-catalog.md
tool-catalog.md: 1a28d560ab9fea3ca8e68856377e7912184d5311
tool-catalog.zh.md: 1572c84f3b013e9a46198e1503d5373495845d28
tool-catalog.md: ea89508500ec6e73e6e56c4e5d0ac2c35897b342
tool-catalog.zh.md: 076cc63151e3b9ee8b3828ded65df9ebf09c9430
+197 -37
View File
@@ -20,7 +20,7 @@ This table connects model-visible tool names to the plugin package and service s
| `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`, `ctx.systemPrompt`, `ctx.userQuestions (execution time, opportunistic)` | `tool/call`, `plan/mode inactive on an approved review`, `tool/result` | - | exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary. |
| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs at call time for run_in_background` | `tool/call`, `tool/result` | - | The bash tool is the model-facing consumer of the bash executor seam. A `run_in_background` run registers with the generic `ctx.jobs` runtime and is collected/stopped through the `job_*` tools from `@deepseek-ai/dsh-tool-jobs`; the `enableRunInBackground` config (default true) removes the parameter entirely when disabled. |
| `@deepseek-ai/dsh-tool-pwsh` | `pwsh` | `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs at call time for run_in_background` | `tool/call`, `tool/result` | - | The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.shell`); it mirrors the bash tool call-for-call minus sandbox controls — `run_in_background` runs register with the generic `ctx.jobs` runtime and are collected/stopped through the `job_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-shell-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\...` paths and `$env:NAME` variables. |
| `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect`, `cordis_mount`, `cordis_unmount` | `ctx.tools` | `tool/call`, `tool/result`, `process-local temporary Plugin lifecycle` | - | Not in any shipped tree (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-cordis` | `cordis_define`, `cordis_inspect_list`, `cordis_inspect_query`, `cordis_inspect_self`, `cordis_run`, `cordis_stop`, `cordis_undefine` | `ctx.tools`, `ctx.dynamicCordisRunner` | `tool/call`, `tool/result`, `process-local dynamic package lifecycle` | - | Not in any shipped tree (a deliberate opt-in — dynamic package code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). The toolset injects `ctx.dynamicCordisRunner` from `@deepseek-ai/dsh-cordis-host-runner`, which owns the definition registry and the vm sandbox; a composition missing it never activates the tools. A running package may register ADDITIONAL model-visible tools until it is stopped, undefined, or DSH restarts; a full changed request header logs those tool-set changes. |
| `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.tools`, `ctx.terminals`, `an owning Agent at execution time` | `tool/call`, `PTY shell state`, `tool/result` | - | One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description. |
| `@deepseek-ai/dsh-tool-str-replace-editor` | `str_replace_editor` | `ctx.tools`, `ctx.fs` | `tool/call`, `fs/observed after view presence/absence, edit absence, or successful mutation`, `tool/result` | - | Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal API. |
| `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `read_image`, `write` | `ctx.tools`, `ctx.fs`, `ctx.systemPrompt`, `ctx.attachments (read_image registration)`, `ctx.llm + an image-capable route (read_image execution)` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after read presence/absence or successful file operation`, `durable attachment (read_image)`, `tool/result` | - | The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-observation-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. `read_image` is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input. |
@@ -253,50 +253,81 @@ The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for W
## `@deepseek-ai/dsh-tool-cordis`
### `cordis_inspect`
### `cordis_define`
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.
Define an immutable Cordis Package. For a new Plugin, use kind:"new" and provide only a semantic prefix of 36 lowercase English letters; the Host returns the final pluginId and packageId. To modify an existing Plugin, use kind:"existing" with its exact pluginId to append a Package without overwriting older versions. Provide at least one of code.host and code.client. Each value is a plain JavaScript function body that returns a Cordis Plugin; no TypeScript, JSX, or import transformation occurs. Query Inspect before depending on a Service, Event, Builtin, Slot, or token. Define only validates parameters and syntax and records source: it does not request approval, execute apply, or change currentPackageId. On success, call cordis_run with the returned IDs.
```json
{
"type": "object",
"properties": {
"what": {
"type": "string",
"description": "Limit the report to one section. Omit for all sections.",
"enum": [
"services",
"plugins",
"tools",
"temporary",
"api",
"events"
"plugin": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "new"
},
"idPrefix": {
"type": "string",
"description": "Suggested semantic prefix of 36 lowercase English letters; the Host adds a unique numeric suffix."
}
},
"required": [
"kind",
"idPrefix"
]
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "existing"
},
"pluginId": {
"type": "string",
"description": "Exact ID of an existing Plugin; the new Package is appended to that instance."
}
},
"required": [
"kind",
"pluginId"
]
}
]
},
"name": {
"type": "string",
"description": "Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\"."
}
}
}
```
Source: [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_mount`
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 an Harness Plugin or installable profile bundle 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.shell) 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.shell 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()` SHORT-CIRCUITS 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.shell) reach the real runtime.
```json
{
"type": "object",
"properties": {
"code": {
"description": "Short, readable Package name."
},
"purpose": {
"type": "string",
"description": "JavaScript body returning a temporary Plugin; evaluated now and saved nowhere."
"description": "One-sentence, user-facing description of the Package purpose."
},
"code": {
"type": "object",
"additionalProperties": false,
"properties": {
"host": {
"type": "string",
"description": "Plain JavaScript function body that returns the Host-half Cordis Plugin."
},
"client": {
"type": "string",
"description": "Plain JavaScript function body that returns the browser Client-half Cordis Plugin."
}
}
}
},
"required": [
"plugin",
"name",
"purpose",
"code"
]
}
@@ -304,28 +335,157 @@ Mount a temporary Cordis Plugin in the current DSH process. This creates an in-m
Source: [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_unmount`
### `cordis_inspect_list`
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.
List every Cordis Inspect Provider currently known to the Host, including local Host Providers and the latest manifests synchronized from the Client. Each entry includes its platform, purpose, read-only methods, and input/output schemas. Call this Tool before creating or modifying a Package, then select the provider and method for cordis_inspect_query from its result. Do not guess names or treat an Inspect method as a business Service that Plugin code can call.
```json
{
"type": "object",
"properties": {}
}
```
Source: [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_inspect_query`
Run a read-only query explicitly declared by an Inspect Provider. platform, provider, and method must come from cordis_inspect_list, and input must satisfy that method's schema. Use this Tool before cordis_define to read exact Service methods, Event modes, Builtin signatures, Tool schemas, theme tokens, or live Slot trees and props. Host queries run locally. A Client query waits for the first valid page response and remains pending until a page answers or the Tool is cancelled. This Tool cannot invoke business Service methods or modify the runtime. For Service.listService and Event.listEvents, query without input to navigate the compact signature directory, then query the exact service or event for its structured contract and referenced types. For Slots.listSubTree, query without root to navigate the compact tree, then query the exact root for its complete registration contract and props.
```json
{
"type": "object",
"properties": {
"id": {
"platform": {
"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."
"description": "Runtime platform that owns the Provider.",
"enum": [
"host",
"client"
]
},
"provider": {
"type": "string",
"description": "Exact Provider ID returned by cordis_inspect_list."
},
"method": {
"type": "string",
"description": "Exact method name declared by the Provider manifest."
},
"input": {
"description": "Optional query input; it must satisfy the method input schema."
}
},
"required": [
"id"
"platform",
"provider",
"method"
]
}
```
Source: [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
Not in any shipped tree (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.
### `cordis_inspect_self`
Inspect dynamic Cordis objects owned by the current Session at increasing levels of detail. With no IDs, list only Plugin summaries. With pluginId alone, return version pointers, the latest Run, and every Package summary. Only pluginId plus packageId returns that immutable Package's Host/Client source and runtime diagnostics. packageId cannot be supplied alone. Query an exact Package before handling @pluginId, repairing an asynchronous failure, or defining an updated version. This Tool is read-only: it neither executes code nor changes version pointers.
```json
{
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable Plugin ID returned by cordis_define or injected by @pluginId; omit it to list every current Plugin."
},
"packageId": {
"type": "string",
"description": "Exact immutable Package ID owned by pluginId; when specified, source and diagnostics are returned."
}
}
}
```
Source: [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_run`
Activate one exact Package of a dynamic Plugin. Use mode:"run" for the first activation, restarting currentPackageId, or rollback. When current exists, use mode:"update" to switch to a different Package, even if the Plugin is currently stopped. An unauthorized Client Package creates an approval request and returns awaiting-approval; an authorized Package returns starting and continues asynchronously in the browser. Neither result waits for the final outcome inside the Tool. currentPackageId changes only after complete success; on failure, the old current and target next remain. Asynchronous success, rejection, or technical failure is reported through state and steering. After a technical failure, read diagnostics with cordis_inspect_self, correct the same Plugin, and retry autonomously. Do not request approval again after the user rejects it.
```json
{
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable Plugin ID returned by cordis_define."
},
"packageId": {
"type": "string",
"description": "Exact immutable Package ID to activate under that Plugin."
},
"mode": {
"type": "string",
"description": "Use run for the first activation, restarting current, or rollback; use update to switch from current to a different Package.",
"enum": [
"run",
"update"
]
}
},
"required": [
"pluginId",
"packageId",
"mode"
]
}
```
Source: [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_stop`
Stop the current Run of a dynamic Plugin and cancel unfinished approval or activation requests. Retain the Plugin, every immutable Package, grants, currentPackageId, and nextPackageId so it can later run or update directly. Stopping an already stopped Plugin succeeds idempotently. Use this Tool to disable effects temporarily; use cordis_undefine for permanent removal.
```json
{
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable dynamic Plugin ID to stop."
}
},
"required": [
"pluginId"
]
}
```
Source: [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_undefine`
Permanently remove a dynamic Plugin owned by the current Session. If it is running or awaiting approval, first stop it and cancel the request, then delete every Package, grant, and version pointer. After this returns, its pluginId, packageIds, @ reference, and Package business views are invalid; historical cards retain only a "Plugin removed" record. Do not call this Tool when versions must remain available for restart or rollback; use cordis_stop instead.
```json
{
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable dynamic Plugin ID to remove permanently."
}
},
"required": [
"pluginId"
]
}
```
Source: [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
Not in any shipped tree (a deliberate opt-in — dynamic package code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). The toolset injects `ctx.dynamicCordisRunner` from `@deepseek-ai/dsh-cordis-host-runner`, which owns the definition registry and the vm sandbox; a composition missing it never activates the tools. A running package may register ADDITIONAL model-visible tools until it is stopped, undefined, or DSH restarts; a full changed request header logs those tool-set changes.
## `@deepseek-ai/dsh-tool-bash-persistent`
+197 -37
View File
@@ -22,7 +22,7 @@
| `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools``ctx.systemPrompt``ctx.userQuestions (execution time, opportunistic)` | `tool/call``plan/mode inactive on an approved review``tool/result` | - | 规划未激活时,exit_plan_mode 仍保留在面向模型的 schema 中,这样状态转换不会在规划策略变更之外额外造成工具目录变动。其执行路径会拒绝规划模式之外的调用;在规划模式下,它通过用户交互 seam 提交计划(批准/根据反馈继续规划),批准后会在步骤边界记录规划模式已停用。 |
| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.tools``ctx.shell``ctx.systemPrompt``ctx.shellEnv``ctx.jobs at call time for run_in_background` | `tool/call``tool/result` | - | bash 工具是 bash 执行器 seam 面向模型的消费方。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具(来自 `@deepseek-ai/dsh-tool-jobs`)收集/停止;禁用 `enableRunInBackground` 配置(默认为 true)后,该参数会被完全移除。 |
| `@deepseek-ai/dsh-tool-pwsh` | `pwsh` | `ctx.tools``ctx.shell``ctx.systemPrompt``ctx.shellEnv``ctx.jobs at call time for run_in_background` | `tool/call``tool/result` | - | pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费方(由 `@deepseek-ai/dsh-pwsh-local` 等 PowerShell 执行器为 `ctx.shell` 提供后端);除沙箱接口外,它逐项对应 bash 工具调用。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具收集/停止;托管的 `DSH_*` 环境来自 `@deepseek-ai/dsh-shell-env`。每次调用都在新进程中运行,不使用持久 PTY 会话。路径采用原生 `C:\...` 形式,变量采用 `$env:NAME`。 |
| `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect``cordis_mount``cordis_unmount` | `ctx.tools` | `tool/call``tool/result``process-local temporary Plugin lifecycle` | - | 不在任何随产品发布的树中,需要有意选择启用;临时 Plugin 代码可以访问真实运行时,见 .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md。由 cordis_mount 创建的插件在卸载或 DSH 重启前可以注册**额外的**模型可见工具;发生这类工具集变时,系统会记录完整且有变动的请求头。 |
| `@deepseek-ai/dsh-tool-cordis` | `cordis_define``cordis_inspect_list``cordis_inspect_query``cordis_inspect_self``cordis_run``cordis_stop``cordis_undefine` | `ctx.tools``ctx.dynamicCordisRunner` | `tool/call``tool/result``process-local dynamic package lifecycle` | - | 不在任何随产品发布的树中,需要显式选择启用;动态 Package 代码可以访问真实运行时,见 .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md。该工具集注入 `@deepseek-ai/dsh-cordis-host-runner` 提供的 `ctx.dynamicCordisRunner`,后者拥有定义注册表和 vm 沙箱;组合缺少它时这些工具不会激活。运行中的 Package 在停止、undefine 或 DSH 重启前可以注册**额外的**模型可见工具;发生这类工具集变时,系统会记录完整且有变动的请求头。 |
| `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.tools``ctx.terminals``an owning Agent at execution time` | `tool/call``PTY shell state``tool/result` | - | 一个按所有者隔离的持久 bash 工具;部署组合提供 PTY 后端,并可覆盖面向模型的环境描述。 |
| `@deepseek-ai/dsh-tool-str-replace-editor` | `str_replace_editor` | `ctx.tools``ctx.fs` | `tool/call``fs/observed after view presence/absence, edit absence, or successful mutation``tool/result` | - | 基于文件系统 seam 的独立查看/创建/唯一字面量替换/按行插入工具;可与任何 shell 或终端接口组合。 |
| `@deepseek-ai/dsh-tool-fs` | `edit``read``read_image``write` | `ctx.tools``ctx.fs``ctx.systemPrompt``ctx.attachments (read_image registration)``ctx.llm + an image-capable route (read_image execution)` | `tool/call``fs/write-intent or fs/edit-intent for mutations``fs/observed after read presence/absence or successful file operation``durable attachment (read_image)``tool/result` | - | 先读后写/编辑策略由 `@deepseek-ai/dsh-fs-observation-policy` 添加;它是一个 `fs/*` 事件门禁插件,不会改变 schema。加载这些工具的部署按预期也应加载该插件。没有 `ctx.attachments``read_image` 不会注册;其 schema 与路由无关,执行时除非确切路由的模型声明图像输入,否则拒绝。 |
@@ -255,50 +255,81 @@ pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费
## `@deepseek-ai/dsh-tool-cordis`
### `cordis_inspect`
### `cordis_define`
检查当前 DSH 进程中的实时 Cordis 运行时。只读。章节包括:`services`(所有已提供的 ctx 服务及拥有它的插件 fiber)、`plugins`(所有实时插件 fiber 及其生命周期状态)、`tools`(当前注册的模型可见工具,即你可以调用的工具)、`temporary`(仅由 cordis_mount 创建的临时 Plugin:id、名称、状态、提供的服务、等待的服务和存续期)、`api`(每个**实时**服务的方法签名以及参数/返回值类型形状;编写调用服务的插件代码前请先阅读)、`events`(每个 harness 事件的分派模式和确切签名;在此选择监听目标)。临时 Plugin 仅存在于内存中,会在后续轮次中保持活动,并在 cordis_unmount、工具集卸载或 DSH 重启后消失;不会自动恢复。`temporary``plugins` 的子集。省略 `what` 可获取全部 6 个章节。使用 `what:"api"``what:"events"` 时,可传入确切的 `name`,将范围缩小到一个服务/事件,并包含其原始源码 JSDoc
定义一个不可变的 Cordis Package。新建 Plugin 时使用 kind:"new",只提供 3 至 6 位小写英文字母组成的语义前缀;Host 返回最终 pluginId 和 packageId。修改现有 Plugin 时使用 kind:"existing" 并传入精确 pluginId,以追加 Package 而不覆盖旧版本。code.host 与 code.client 至少提供一个;每个值都是返回 Cordis Plugin 的 plain JavaScript 函数体,不经过 TypeScript、JSX 或 import 转换。依赖 Service、Event、Builtin、Slot 或 token 前先查询 Inspect。Define 只校验参数和语法并记录源码,不申请审批、不执行 apply,也不改变 currentPackageId。成功后用返回的 ID 调用 cordis_run
```json
{
"type": "object",
"properties": {
"what": {
"type": "string",
"description": "Limit the report to one section. Omit for all sections.",
"enum": [
"services",
"plugins",
"tools",
"temporary",
"api",
"events"
"plugin": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "new"
},
"idPrefix": {
"type": "string",
"description": "Suggested semantic prefix of 36 lowercase English letters; the Host adds a unique numeric suffix."
}
},
"required": [
"kind",
"idPrefix"
]
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "existing"
},
"pluginId": {
"type": "string",
"description": "Exact ID of an existing Plugin; the new Package is appended to that instance."
}
},
"required": [
"kind",
"pluginId"
]
}
]
},
"name": {
"type": "string",
"description": "Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\"."
}
}
}
```
来源:[`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_mount`
在当前 DSH 进程中挂载临时 Cordis Plugin。它创建的是内存中的运行时 Plugin,而不是已安装或已配置的 Plugin。该插件会在后续轮次中保持活动,直到执行 cordis_unmount、工具集卸载或 DSH 重启。它不会创建文件、安装包、修改 cordis.yml 或个人/项目配置、在重启后保留,也不会自动转为永久插件。若要保留,请让 Agent 通过常规开发工作流实现 Harness Plugin 或可安装的 profile bundle。它可能影响同一进程中的其他会话;沙箱不是安全边界,注入的服务会访问真实运行时。`code` 会立即作为异步 JavaScript 函数的函数体在隔离沙箱中运行,并且**必须** `return` 一个插件。支持两种形式:函数形式 `return (ctx) => { … }`,它不声明 inject,因此可以注册工具、监听事件和提供服务,但访问**任何**服务(例如 ctx.shell)都会抛出异常;仅在不需要服务时使用。对象形式 `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }`,它声明依赖,Cordis 只在服务存在后激活插件;**优先使用**这种形式。你只能访问 inject 中列出的服务:即使未声明的服务存在,访问它也会抛出异常,因为如果提供方被卸载,未声明的依赖将无法清理。代码调用服务**之前**,请读取 cordis_inspect 的 what:"api";它会列出方法签名以及参数/返回值的类型形状,不要猜测字段类型,例如 bash 运行的 stdout 是对象而非字符串。在 `apply` 内,请使用标准 Cordis API:通过 `ctx.on(event, listener)` 观察事件(见 cordis_inspect 的 what:"events"),或调用 `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 } }))` 为自己提供新工具;该工具会在你的**下一步骤**可调用。工具参数:每个键**就是**一个属性,即 { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? };每个直接 DSL 对象都声明 additionalProperties: true|false,而 oneOf: [schema, schema, ...] 会取代 type,表示恰好匹配一个成员的联合。也接受原始 JSON Schema { type: 'object', properties, required?: […] } 包装层,其中对象默认开放。工具的 `execute` **必须**返回 `output.schema` 声明的无损 JSON 值;`output.render(args, value)` 单独返回 Native/模型内容块。临时 Plugin 可以**组合**:一个 Plugin 可以通过 `ctx.provide('name', value)` 提供服务,另一个则可声明 `inject: ['name']` 来消费它;消费方会在提供方出现前保持等待,提供方卸载后重新回到等待状态。在 `apply` 中注册的一切都会由 cordis_unmount 自动清理。沙箱全局对象:`console`(带 `[cordis:<id>]` 标签,写入 harness 终端)、`harness.defineTool``harness.registerTool``btoa``atob``TextEncoder``TextDecoder`。Node API 已**禁用**:文件系统/网络/定时工作必须通过 Cordis 服务完成,绝不能使用 Node 内置能力;`require``setTimeout``setInterval``fetch` 会抛出重定向错误,`process``Buffer` 未定义。应改用 inject: ['fs'] + ctx.fs 处理文件、inject: ['web'] + ctx.web 处理 HTTP、inject: ['bash'] + ctx.shell 处理进程、inject: ['timer'] + ctx.setTimeout/ctx.setInterval 处理定时(这些是 fiber effect,卸载时自动清理);cordis_inspect 的 what:"api" 会展示**当前**运行时提供的能力。请编写**纯** JavaScript,不要使用 TypeScript(不得使用 `as` 或类型注解)。注意事项:(1) waterfall(瀑布式事件)事件(例如 tools/pre-execute)会向监听器传入最后一个 `next` 回调,该回调**必须**被调用;不调用 `next()` 就返回会**短路**此次调用。除非你有意拦截,否则请优先使用普通通知事件。(2) 切勿等待只能在当前轮次之后解析的内容;你的代码运行在该轮次的工具调用**内部**,否则会死锁。(3) 你的 `ctx` 是受限门面:可以注册工具、观察事件、提供/消费服务和使用定时器,但不会提供框架内部能力(ctx.root、ctx.fiber、ctx.extend、ctx.plugin 等)。不过,它并非安全边界:你注入的服务(例如 ctx.shell)会访问真实运行时。
```json
{
"type": "object",
"properties": {
"code": {
"description": "Short, readable Package name."
},
"purpose": {
"type": "string",
"description": "JavaScript body returning a temporary Plugin; evaluated now and saved nowhere."
"description": "One-sentence, user-facing description of the Package purpose."
},
"code": {
"type": "object",
"additionalProperties": false,
"properties": {
"host": {
"type": "string",
"description": "Plain JavaScript function body that returns the Host-half Cordis Plugin."
},
"client": {
"type": "string",
"description": "Plain JavaScript function body that returns the browser Client-half Cordis Plugin."
}
}
}
},
"required": [
"plugin",
"name",
"purpose",
"code"
]
}
@@ -306,28 +337,157 @@ pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费
来源:[`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_unmount`
### `cordis_inspect_list`
卸载当前进程中由 cordis_mount 创建的临时 Plugin。它会等待该插件的工具、监听器、服务、定时器及其他自有效果全部清理完成。只接受 dyn-N 临时 id;无法移除 Loader、已配置或已安装的 Plugin
列出 Host 当前已知的全部 Cordis Inspect Provider,包括本地 Host Provider 和 Client 最近同步的 manifest。每项包含所属平台、用途、只读方法及输入/输出 schema。创建或修改 Package 前先调用本 Tool,再从结果中选择 cordis_inspect_query 的 provider 和 method。不要猜测名称,也不要把 Inspect method 当作 Plugin 代码可调用的业务 Service
```json
{
"type": "object",
"properties": {}
}
```
来源:[`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_inspect_query`
执行 Inspect Provider 显式声明的只读查询。platform、provider 和 method 必须来自 cordis_inspect_listinput 必须符合该方法的 schema。在 cordis_define 前用本 Tool 读取精确 Service 方法、Event mode、Builtin 签名、Tool schema、主题 token,或实时 Slot 树及 props。Host 查询在本地执行;Client 查询等待首个有效页面响应,在页面回答或 Tool 被取消前保持 pending。本 Tool 不能调用业务 Service 方法或修改运行时。查询 Service.listService 和 Event.listEvents 时,先不传 input 浏览紧凑签名目录,再查询精确 service 或 event 获取结构化约定和引用类型。查询 Slots.listSubTree 时,先不传 root 浏览紧凑树,再查询精确 root 获取完整注册约定和 props。
```json
{
"type": "object",
"properties": {
"id": {
"platform": {
"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."
"description": "Runtime platform that owns the Provider.",
"enum": [
"host",
"client"
]
},
"provider": {
"type": "string",
"description": "Exact Provider ID returned by cordis_inspect_list."
},
"method": {
"type": "string",
"description": "Exact method name declared by the Provider manifest."
},
"input": {
"description": "Optional query input; it must satisfy the method input schema."
}
},
"required": [
"id"
"platform",
"provider",
"method"
]
}
```
来源:[`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
不在任何随产品发布的树中,需要有意选择启用;临时 Plugin 代码可以访问真实运行时,见 .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md。由 cordis_mount 创建的插件在卸载或 DSH 重启之前可以注册**额外的**模型可见工具;发生这类工具集变更时,系统会记录完整且有变动的请求头。
### `cordis_inspect_self`
按逐层增加的详细程度检查当前 Session 拥有的动态 Cordis 对象。不传 ID 时只列 Plugin 摘要;只传 pluginId 时返回版本指针、最新 Run 和全部 Package 摘要;只有同时传 pluginId 与 packageId 才返回该不可变 Package 的 Host/Client 源码和运行诊断。packageId 不能单独传入。处理 @pluginId、修复异步失败或定义更新版本前,先查询精确 Package。本 Tool 只读,不执行代码,也不改变版本指针。
```json
{
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable Plugin ID returned by cordis_define or injected by @pluginId; omit it to list every current Plugin."
},
"packageId": {
"type": "string",
"description": "Exact immutable Package ID owned by pluginId; when specified, source and diagnostics are returned."
}
}
}
```
来源:[`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_run`
激活动态 Plugin 的一个精确 Package。首次激活、重启 currentPackageId 或回退使用 mode:"run";已有 current 时,即使 Plugin 当前已停止,切换到其他 Package 也使用 mode:"update"。未授权的 Client Package 创建审批请求并返回 awaiting-approval;已授权的 Package 返回 starting,并在浏览器中异步继续。两种结果都不会在 Tool 内等待最终结局。currentPackageId 只在完整成功后改变;失败时保留旧 current 和目标 next。异步成功、拒绝或技术失败通过状态与 steering 报告。技术失败后,用 cordis_inspect_self 读取诊断,修正同一 Plugin 并自主重试。用户拒绝后不要再次申请审批。
```json
{
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable Plugin ID returned by cordis_define."
},
"packageId": {
"type": "string",
"description": "Exact immutable Package ID to activate under that Plugin."
},
"mode": {
"type": "string",
"description": "Use run for the first activation, restarting current, or rollback; use update to switch from current to a different Package.",
"enum": [
"run",
"update"
]
}
},
"required": [
"pluginId",
"packageId",
"mode"
]
}
```
来源:[`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_stop`
停止动态 Plugin 的当前 Run,并取消尚未完成的审批或激活请求。保留 Plugin、全部不可变 Package、授权、currentPackageId 和 nextPackageId,以便之后直接运行或更新。停止已处于停止状态的 Plugin 会幂等成功。临时禁用副作用使用本 Tool;永久移除使用 cordis_undefine。
```json
{
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable dynamic Plugin ID to stop."
}
},
"required": [
"pluginId"
]
}
```
来源:[`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
### `cordis_undefine`
永久移除当前 Session 拥有的动态 Plugin。如果它正在运行或等待审批,先停止并取消请求,再删除全部 Package、授权和版本指针。返回后,其 pluginId、packageIds、@ 引用和 Package 业务视图均失效;历史卡片只保留“Plugin 已移除”记录。需要保留版本以便重启或回退时不要调用本 Tool,应改用 cordis_stop。
```json
{
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable dynamic Plugin ID to remove permanently."
}
},
"required": [
"pluginId"
]
}
```
来源:[`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts)
不在任何随产品发布的树中,需要显式选择启用;动态 Package 代码可以访问真实运行时,见 .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md。该工具集注入 `@deepseek-ai/dsh-cordis-host-runner` 提供的 `ctx.dynamicCordisRunner`,后者拥有定义注册表和 vm 沙箱;组合缺少它时这些工具不会激活。运行中的 Package 在停止、undefine 或 DSH 重启前可以注册**额外的**模型可见工具;发生这类工具集变化时,系统会记录完整且有变动的请求头。
## `@deepseek-ai/dsh-tool-bash-persistent`
@@ -25,6 +25,8 @@
- insert:
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
- id: tool-cordis
name: '@deepseek-ai/dsh-tool-cordis'
- id: llm-replay
+2
View File
@@ -23,5 +23,7 @@
- insert:
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
- id: tool-cordis
name: '@deepseek-ai/dsh-tool-cordis'
@@ -6,5 +6,7 @@
path: ./cordis.yml
patches:
- insert:
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
- id: tool-cordis
name: '@deepseek-ai/dsh-tool-cordis'
+3 -2
View File
@@ -491,8 +491,9 @@ const SCENARIOS: Scenario[] = [
// child runs as a spawn subagent under the worker-thread engine (its session is the
// child fixture), and the tool result carries the script's return value.
{ name: 'workflow-run', hasModelTurn: true, recorded: true },
// Authored counterpart to the packaged Python SDK snapshot: mount a live marker, inspect it
// through Code Mode, run direct and workflow children, then unmount it. The extra Code Mode and
// Authored counterpart to the packaged Python SDK snapshot: define a host-half marker package and
// run it, inspect this session's dynamic packages through Code Mode, run direct and workflow
// children, then undefine it. The extra Code Mode and
// Cordis plugins require their own request-header pin; the fixture tests deterministic composition.
{
name: 'advanced-toolchain',
@@ -1,7 +1,14 @@
{
"steps": [
{ "op": "initialize" },
{ "op": "newSession" },
{ "op": "prompt", "text": "Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply with exactly ADVANCED_ACP_OK." }
{
"op": "initialize"
},
{
"op": "newSession"
},
{
"op": "prompt",
"text": "Run this advanced flow exactly once: define a host-only dynamic Cordis Package named Snapshot Marker; run and inspect snap-1/pkg-1 through run_code; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; remove snap-1; then reply with exactly ADVANCED_ACP_OK."
}
]
}
@@ -6,7 +6,7 @@
{"type":"subagent/descriptor","seq":4,"time":1786357538308,"data":{"version":2,"mode":"one-shot","provider":"spawn","label":"Check direct child"}}
{"type":"step/start","seq":5,"time":1786357538310,"data":{"turn":1,"step":1}}
{"type":"user/message","seq":6,"time":1785730458555,"data":{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"ebe0cfa0-a909-47e0-8294-28ad84a8fe77"},"surfaceOp":"append"}
{"type":"user/message","seq":7,"time":1786357538310,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"511768bb-311b-4ea7-ab14-5c0524812613"},"surfaceOp":"append"}
{"type":"user/message","seq":7,"time":1786357538310,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"21c656d1-bb34-4dcd-8d27-9eac72ffcd72"},"surfaceOp":"append"}
{"type":"session/title","seq":8,"time":1786357538310,"data":{"title":"Reply with exactly DIRECT_CHILD_OK and","messageSeqs":[6],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":9,"time":1785730458555,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":10,"time":1785730458555,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
@@ -6,7 +6,7 @@
{"type":"subagent/descriptor","seq":4,"time":1786357538469,"data":{"version":2,"mode":"one-shot","provider":"spawn"}}
{"type":"step/start","seq":5,"time":1786357538470,"data":{"turn":1,"step":1}}
{"type":"user/message","seq":6,"time":1785730458703,"data":{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"2ac2cc54-9bce-4cfa-a569-a64f51bc30a7"},"surfaceOp":"append"}
{"type":"user/message","seq":7,"time":1786357538471,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"2b3e4c08-fa23-47d9-873a-599e0345fdc4"},"surfaceOp":"append"}
{"type":"user/message","seq":7,"time":1786357538471,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"1843b045-94c6-4f30-b1f0-21a3adc04fe9"},"surfaceOp":"append"}
{"type":"session/title","seq":8,"time":1786357538471,"data":{"title":"Reply with exactly WORKFLOW_CHILD_OK and","messageSeqs":[6],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":9,"time":1785730458703,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":10,"time":1785730458703,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
@@ -1,74 +1,76 @@
{"type":"session","version":0,"id":"11111111-1111-4111-8111-111111111111","createdAt":1783950000000,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"agent/inbox/spliced","seq":0,"time":1785498801734,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply with exactly ADVANCED_ACP_OK."}],"source":{"kind":"user"},"role":"user","id":"6e45782a-31be-4ba7-8c4a-7411a2027e36"}]}}
{"type":"agent/inbox/spliced","seq":0,"time":1785498801734,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Run this advanced flow exactly once: define a host-only dynamic Cordis Package named Snapshot Marker; run and inspect snap-1/pkg-1 through run_code; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; remove snap-1; then reply with exactly ADVANCED_ACP_OK."}],"source":{"kind":"user"},"role":"user","id":"6a989c18-ce01-46ce-8105-43789f710fb5"}]}}
{"type":"turn/start","seq":1,"time":1785821417918,"data":{"turn":1}}
{"type":"agent/inbox/spliced","seq":2,"time":1785821417919,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","seq":3,"time":1783957884486,"data":{"turn":1,"step":1}}
{"type":"user/message","seq":4,"time":1785498801761,"data":{"content":[{"type":"text","text":"Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply with exactly ADVANCED_ACP_OK."}],"source":{"kind":"user"},"role":"user","id":"6e45782a-31be-4ba7-8c4a-7411a2027e36"},"surfaceOp":"append"}
{"type":"user/message","seq":4,"time":1785498801761,"data":{"content":[{"type":"text","text":"Run this advanced flow exactly once: define a host-only dynamic Cordis Package named Snapshot Marker; run and inspect snap-1/pkg-1 through run_code; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; remove snap-1; then reply with exactly ADVANCED_ACP_OK."}],"source":{"kind":"user"},"role":"user","id":"6a989c18-ce01-46ce-8105-43789f710fb5"},"surfaceOp":"append"}
{"type":"user/message","seq":5,"time":1785730458430,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"f66cc92b-b90c-4aeb-9568-7463d5eeede9"},"surfaceOp":"append"}
{"type":"session/title","seq":6,"time":1785730458430,"data":{"title":"Run this advanced flow exactly","messageSeqs":[4],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":7,"time":1785498801765,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":8,"time":1785730458431,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"assistant/chunk","seq":9,"time":1783950000007,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":10,"time":1783950000008,"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":11,"time":1783950000009,"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":10,"time":1783950000008,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-define","name":"cordis_define","argumentsDelta":"{\"plugin\":{\"kind\":\"new\",\"idPrefix\":\"snap\"},\"name\":\"Snapshot Marker\",\"purpose\":\"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\"code\":{\"host\":\"return { apply() {} }\"}}"}}}
{"type":"assistant/chunk","seq":11,"time":1783950000009,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-define","name":"cordis_define","arguments":"{\"plugin\":{\"kind\":\"new\",\"idPrefix\":\"snap\"},\"name\":\"Snapshot Marker\",\"purpose\":\"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\"code\":{\"host\":\"return { apply() {} }\"}}"}}}}
{"type":"assistant/chunk","seq":12,"time":1785498801774,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":13,"time":1785730458439,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":14,"time":1785730458440,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0713b7ec-0182-4820-8ec1-39d0371b533b"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12,13],"surfaceOp":"append"}
{"type":"tool/call","seq":15,"time":1785730458440,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}
{"type":"tool/result","seq":16,"time":1785730458450,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"advanced-mount"},"content":[{"type":"tool-result","toolCallId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until unmounted or DSH restarts)."}],"isError":false}],"role":"user","id":"b10e76f0-e1a5-4c2c-b6a2-6cbdcf259cca"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
{"type":"assistant/message","seq":14,"time":1785730458440,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-define","name":"cordis_define","arguments":"{\"plugin\":{\"kind\":\"new\",\"idPrefix\":\"snap\"},\"name\":\"Snapshot Marker\",\"purpose\":\"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\"code\":{\"host\":\"return { apply() {} }\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0713b7ec-0182-4820-8ec1-39d0371b533b"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12,13],"surfaceOp":"append"}
{"type":"tool/call","seq":15,"time":1785730458440,"data":{"turn":1,"step":1,"callId":"advanced-define","name":"cordis_define","arguments":"{\"plugin\":{\"kind\":\"new\",\"idPrefix\":\"snap\"},\"name\":\"Snapshot Marker\",\"purpose\":\"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\"code\":{\"host\":\"return { apply() {} }\"}}"}}
{"type":"tool/result","seq":16,"time":1785730458450,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"advanced-define"},"content":[{"type":"tool-result","toolCallId":"advanced-define","content":[{"type":"text","text":"Defined snap-1/pkg-1 (Snapshot Marker); it is not running yet. Use cordis_run to activate this Package."}],"isError":false}],"role":"user","id":"e583400c-a37d-4f0a-ba44-f57a1ab063bd"},"meta":{"pluginId":"snap-1","packageId":"pkg-1"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
{"type":"step/end","seq":17,"time":1785730458450,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":18,"time":1785730458460,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":19,"time":1783950000017,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":20,"time":1783950000018,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-code","name":"run_code","argumentsDelta":"{\"code\": \"return await tools.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}}}
{"type":"assistant/chunk","seq":21,"time":1783950000019,"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.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}}}}
{"type":"assistant/chunk","seq":20,"time":1783950000018,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-code","name":"run_code","argumentsDelta":"{\"code\":\"const run = await tools.cordis_run({ pluginId: 'snap-1', packageId: 'pkg-1', mode: 'run' });\\nconst inspected = await tools.cordis_inspect_self({ pluginId: 'snap-1' });\\nreturn { run, inspected };\",\"description\":\"Run and inspect the dynamic Cordis Package\"}"}}}
{"type":"assistant/chunk","seq":21,"time":1783950000019,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\":\"const run = await tools.cordis_run({ pluginId: 'snap-1', packageId: 'pkg-1', mode: 'run' });\\nconst inspected = await tools.cordis_inspect_self({ pluginId: 'snap-1' });\\nreturn { run, inspected };\",\"description\":\"Run and inspect the dynamic Cordis Package\"}"}}}}
{"type":"assistant/chunk","seq":22,"time":1785498801800,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":23,"time":1785730458465,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":24,"time":1785730458465,"data":{"turn":1,"step":2,"message":{"role":"assistant","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\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"e3061430-3f2d-4dd8-a3ee-c0fde800547d"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"}
{"type":"tool/call","seq":25,"time":1785730458465,"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":26,"time":1785730458517,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_inspect","arguments":{"what":"temporary"}}}
{"type":"tool/code-dispatch","seq":27,"time":1785730458518,"data":{"rootCallId":"advanced-code","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":28,"time":1785730458520,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"advanced-code"},"content":[{"type":"tool-result","toolCallId":"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}],"role":"user","id":"4dce223d-0097-4ac2-a717-d1c430240cef"}},"sourceEventSeqs":[25],"surfaceOp":"append"}
{"type":"step/end","seq":29,"time":1785730458520,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":30,"time":1785730458527,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":31,"time":1783950000029,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":32,"time":1783950000030,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-direct-child","name":"subagent","argumentsDelta":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}
{"type":"assistant/chunk","seq":33,"time":1785036891179,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}}
{"type":"assistant/chunk","seq":34,"time":1785498801872,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":35,"time":1785730458531,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":36,"time":1785730458531,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"51fe1d59-eebc-457b-a072-fe217546ff04"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[31,32,33,34,35],"surfaceOp":"append"}
{"type":"tool/call","seq":37,"time":1785730458531,"data":{"turn":1,"step":3,"callId":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}
{"type":"tool/result","seq":38,"time":1785730458562,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"advanced-direct-child"},"content":[{"type":"tool-result","toolCallId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false}],"role":"user","id":"09028579-5ae5-4d57-955e-02504f4dfc2a"}},"sourceEventSeqs":[37],"surfaceOp":"append"}
{"type":"step/end","seq":39,"time":1785730458563,"data":{"turn":1,"step":3}}
{"type":"step/start","seq":40,"time":1785730458572,"data":{"turn":1,"step":4}}
{"type":"assistant/chunk","seq":41,"time":1783957884594,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":42,"time":1783957884594,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-workflow","name":"workflow","argumentsDelta":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-acp-snapshot\",\"description\":\"exercise one workflow child through ACP\"}}"}}}
{"type":"assistant/chunk","seq":43,"time":1785036891211,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-acp-snapshot\",\"description\":\"exercise one workflow child through ACP\"}}"}}}}
{"type":"assistant/chunk","seq":44,"time":1785498801920,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":45,"time":1785730458577,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":46,"time":1785730458577,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-acp-snapshot\",\"description\":\"exercise one workflow child through ACP\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"ebeca5c6-68ae-43b3-87c3-c48fdfe416c8"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[41,42,43,44,45],"surfaceOp":"append"}
{"type":"tool/call","seq":47,"time":1785730458577,"data":{"turn":1,"step":4,"callId":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-acp-snapshot\",\"description\":\"exercise one workflow child through ACP\"}}"}}
{"type":"tool-workflow/run-start","seq":48,"time":1786359248404,"data":{"runId":"2f6d6a6e-6d76-4a8a-8677-6671366645dc","name":"advanced-acp-snapshot"}}
{"type":"tool-workflow/agent-start","seq":49,"time":1786359248518,"data":{"runId":"2f6d6a6e-6d76-4a8a-8677-6671366645dc","seq":1,"label":"workflow-child","phase":"Delegate","childId":"33333333-3333-4333-8333-333333333333"}}
{"type":"tool-workflow/agent-end","seq":50,"time":1786359248542,"data":{"runId":"2f6d6a6e-6d76-4a8a-8677-6671366645dc","seq":1,"outcome":"completed"}}
{"type":"tool-workflow/run-end","seq":51,"time":1786359248543,"data":{"runId":"2f6d6a6e-6d76-4a8a-8677-6671366645dc","stopReason":"completed"}}
{"type":"tool/result","seq":52,"time":1786359248543,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-acp-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"f892f17e-1e93-4f4b-9e9e-15116593b6fc"}},"sourceEventSeqs":[47],"surfaceOp":"append"}
{"type":"step/end","seq":53,"time":1786359248543,"data":{"turn":1,"step":4}}
{"type":"step/start","seq":54,"time":1786359248550,"data":{"turn":1,"step":5}}
{"type":"assistant/chunk","seq":55,"time":1785730458728,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":56,"time":1786359248554,"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":57,"time":1786359248554,"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":58,"time":1786359248554,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":59,"time":1786359248554,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":60,"time":1786359248554,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"1291ce3c-e568-4f0d-a95a-5157b8b2cc75"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[55,56,57,58,59],"surfaceOp":"append"}
{"type":"tool/call","seq":61,"time":1786359248554,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}}
{"type":"tool/result","seq":62,"time":1786359248558,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-unmount"},"content":[{"type":"tool-result","toolCallId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false}],"role":"user","id":"b3634221-2358-4e82-aac5-e37f0a115023"}},"sourceEventSeqs":[61],"surfaceOp":"append"}
{"type":"step/end","seq":63,"time":1786359248558,"data":{"turn":1,"step":5}}
{"type":"step/start","seq":64,"time":1786359248564,"data":{"turn":1,"step":6}}
{"type":"assistant/chunk","seq":65,"time":1785730458751,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":66,"time":1786359248568,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_ACP_OK"}}}
{"type":"assistant/chunk","seq":67,"time":1786359248568,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_ACP_OK"}}}}
{"type":"assistant/chunk","seq":68,"time":1786359248568,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":69,"time":1786359248568,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":70,"time":1786359248568,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_ACP_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"a32b89ce-13ed-48ba-a7f9-24144b94ec56"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[65,66,67,68,69],"surfaceOp":"append"}
{"type":"step/end","seq":71,"time":1786359248568,"data":{"turn":1,"step":6}}
{"type":"turn/end","seq":72,"time":1786359248568,"data":{"turn":1,"reason":{"kind":"completed"}}}
{"type":"assistant/message","seq":24,"time":1785730458465,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\":\"const run = await tools.cordis_run({ pluginId: 'snap-1', packageId: 'pkg-1', mode: 'run' });\\nconst inspected = await tools.cordis_inspect_self({ pluginId: 'snap-1' });\\nreturn { run, inspected };\",\"description\":\"Run and inspect the dynamic Cordis Package\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"e3061430-3f2d-4dd8-a3ee-c0fde800547d"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"}
{"type":"tool/call","seq":25,"time":1785730458465,"data":{"turn":1,"step":2,"callId":"advanced-code","name":"run_code","arguments":"{\"code\":\"const run = await tools.cordis_run({ pluginId: 'snap-1', packageId: 'pkg-1', mode: 'run' });\\nconst inspected = await tools.cordis_inspect_self({ pluginId: 'snap-1' });\\nreturn { run, inspected };\",\"description\":\"Run and inspect the dynamic Cordis Package\"}"}}
{"type":"tool/code-dispatch-start","seq":26,"time":1785730458517,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_run","arguments":{"pluginId":"snap-1","packageId":"pkg-1","mode":"run"}}}
{"type":"tool/code-dispatch","seq":27,"time":1785730458518,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_run","arguments":{"pluginId":"snap-1","packageId":"pkg-1","mode":"run"},"isError":false,"content":[{"type":"text","text":"snap-1/pkg-1 is running (run-1)."}]}}
{"type":"tool/code-dispatch-start","seq":28,"time":1786553843104,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:2","name":"cordis_inspect_self","arguments":{"pluginId":"snap-1"}}}
{"type":"tool/code-dispatch","seq":29,"time":1786553843104,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:2","name":"cordis_inspect_self","arguments":{"pluginId":"snap-1"},"isError":false,"content":[{"type":"text","text":"{\n \"mode\": \"plugin\",\n \"pluginId\": \"snap-1\",\n \"name\": \"Snapshot Marker\",\n \"packageCount\": 1,\n \"state\": \"running\",\n \"currentPackageId\": \"pkg-1\",\n \"activeRun\": {\n \"pluginRunId\": \"run-1\",\n \"packageId\": \"pkg-1\"\n },\n \"packages\": [\n {\n \"packageId\": \"pkg-1\",\n \"name\": \"Snapshot Marker\",\n \"purpose\": \"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\n \"hasHostHalf\": true,\n \"hasClientHalf\": false,\n \"isCurrent\": true,\n \"isNext\": false\n }\n ]\n}"}]}}
{"type":"tool/result","seq":30,"time":1786553843106,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"advanced-code"},"content":[{"type":"tool-result","toolCallId":"advanced-code","content":[{"type":"text","text":"{\n \"run\": {\n \"status\": \"running\",\n \"pluginId\": \"snap-1\",\n \"packageId\": \"pkg-1\",\n \"pluginRunId\": \"run-1\",\n \"currentPackageId\": \"pkg-1\",\n \"host\": {\n \"status\": \"running\",\n \"provides\": [],\n \"waitingFor\": []\n },\n \"client\": {\n \"status\": \"absent\",\n \"waitingFor\": []\n }\n },\n \"inspected\": {\n \"mode\": \"plugin\",\n \"pluginId\": \"snap-1\",\n \"name\": \"Snapshot Marker\",\n \"packageCount\": 1,\n \"state\": \"running\",\n \"currentPackageId\": \"pkg-1\",\n \"activeRun\": {\n \"pluginRunId\": \"run-1\",\n \"packageId\": \"pkg-1\"\n },\n \"packages\": [\n {\n \"packageId\": \"pkg-1\",\n \"name\": \"Snapshot Marker\",\n \"purpose\": \"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\n \"hasHostHalf\": true,\n \"hasClientHalf\": false,\n \"isCurrent\": true,\n \"isNext\": false\n }\n ]\n }\n}"}],"isError":false}],"role":"user","id":"fe7613ff-5837-4493-af89-0c06f1ef1010"}},"sourceEventSeqs":[25],"surfaceOp":"append"}
{"type":"step/end","seq":31,"time":1786553843106,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":32,"time":1786553843113,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":33,"time":1785036891179,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":34,"time":1785498801872,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-direct-child","name":"subagent","argumentsDelta":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}
{"type":"assistant/chunk","seq":35,"time":1785730458531,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}}
{"type":"assistant/chunk","seq":36,"time":1786553843117,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":37,"time":1786553843117,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":38,"time":1786553843117,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"51fe1d59-eebc-457b-a072-fe217546ff04"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[33,34,35,36,37],"surfaceOp":"append"}
{"type":"tool/call","seq":39,"time":1786553843118,"data":{"turn":1,"step":3,"callId":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}
{"type":"tool/result","seq":40,"time":1786553843151,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"advanced-direct-child"},"content":[{"type":"tool-result","toolCallId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false}],"role":"user","id":"09028579-5ae5-4d57-955e-02504f4dfc2a"}},"sourceEventSeqs":[39],"surfaceOp":"append"}
{"type":"step/end","seq":41,"time":1786553843151,"data":{"turn":1,"step":3}}
{"type":"step/start","seq":42,"time":1786553843157,"data":{"turn":1,"step":4}}
{"type":"assistant/chunk","seq":43,"time":1785036891211,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":44,"time":1785498801920,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-workflow","name":"workflow","argumentsDelta":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-acp-snapshot\",\"description\":\"exercise one workflow child through ACP\"}}"}}}
{"type":"assistant/chunk","seq":45,"time":1785730458577,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-acp-snapshot\",\"description\":\"exercise one workflow child through ACP\"}}"}}}}
{"type":"assistant/chunk","seq":46,"time":1786553843162,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":47,"time":1786553843162,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":48,"time":1786553843162,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-acp-snapshot\",\"description\":\"exercise one workflow child through ACP\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"ebeca5c6-68ae-43b3-87c3-c48fdfe416c8"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[43,44,45,46,47],"surfaceOp":"append"}
{"type":"tool/call","seq":49,"time":1786553843163,"data":{"turn":1,"step":4,"callId":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-acp-snapshot\",\"description\":\"exercise one workflow child through ACP\"}}"}}
{"type":"tool-workflow/run-start","seq":50,"time":1786553843167,"data":{"runId":"8ae2383b-3e28-438d-b9fd-1823db77fdaa","name":"advanced-acp-snapshot"}}
{"type":"tool-workflow/agent-start","seq":51,"time":1786553843285,"data":{"runId":"8ae2383b-3e28-438d-b9fd-1823db77fdaa","seq":1,"label":"workflow-child","phase":"Delegate","childId":"33333333-3333-4333-8333-333333333333"}}
{"type":"tool-workflow/agent-end","seq":52,"time":1786553843313,"data":{"runId":"8ae2383b-3e28-438d-b9fd-1823db77fdaa","seq":1,"outcome":"completed"}}
{"type":"tool-workflow/run-end","seq":53,"time":1786553843314,"data":{"runId":"8ae2383b-3e28-438d-b9fd-1823db77fdaa","stopReason":"completed"}}
{"type":"tool/result","seq":54,"time":1786553843314,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-acp-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"f892f17e-1e93-4f4b-9e9e-15116593b6fc"}},"sourceEventSeqs":[49],"surfaceOp":"append"}
{"type":"step/end","seq":55,"time":1786553843314,"data":{"turn":1,"step":4}}
{"type":"step/start","seq":56,"time":1786553843321,"data":{"turn":1,"step":5}}
{"type":"assistant/chunk","seq":57,"time":1786359248554,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":58,"time":1786359248554,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-undefine","name":"cordis_undefine","argumentsDelta":"{\"pluginId\":\"snap-1\"}"}}}
{"type":"assistant/chunk","seq":59,"time":1786359248554,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-undefine","name":"cordis_undefine","arguments":"{\"pluginId\":\"snap-1\"}"}}}}
{"type":"assistant/chunk","seq":60,"time":1786553843325,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":61,"time":1786553843325,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":62,"time":1786553843325,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-undefine","name":"cordis_undefine","arguments":"{\"pluginId\":\"snap-1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"1291ce3c-e568-4f0d-a95a-5157b8b2cc75"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[57,58,59,60,61],"surfaceOp":"append"}
{"type":"tool/call","seq":63,"time":1786553843325,"data":{"turn":1,"step":5,"callId":"advanced-undefine","name":"cordis_undefine","arguments":"{\"pluginId\":\"snap-1\"}"}}
{"type":"tool/result","seq":64,"time":1786553843329,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-undefine"},"content":[{"type":"tool-result","toolCallId":"advanced-undefine","content":[{"type":"text","text":"Removed dynamic Plugin snap-1 and all of its Packages."}],"isError":false}],"role":"user","id":"dd45db06-baa0-4e48-ad52-681b511c8f80"}},"sourceEventSeqs":[63],"surfaceOp":"append"}
{"type":"step/end","seq":65,"time":1786553843330,"data":{"turn":1,"step":5}}
{"type":"step/start","seq":66,"time":1786553843337,"data":{"turn":1,"step":6}}
{"type":"assistant/chunk","seq":67,"time":1786359248568,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":68,"time":1786359248568,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_ACP_OK"}}}
{"type":"assistant/chunk","seq":69,"time":1786359248568,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_ACP_OK"}}}}
{"type":"assistant/chunk","seq":70,"time":1786553843341,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":71,"time":1786553843341,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":72,"time":1786553843341,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_ACP_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"a32b89ce-13ed-48ba-a7f9-24144b94ec56"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[67,68,69,70,71],"surfaceOp":"append"}
{"type":"step/end","seq":73,"time":1786553843342,"data":{"turn":1,"step":6}}
{"type":"turn/end","seq":74,"time":1786553843342,"data":{"turn":1,"reason":{"kind":"completed"}}}
@@ -19,6 +19,112 @@ Use goal tools for one long-running completion objective in the current session.
Use the workflow tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.
# Dynamic Cordis Plugins
Dynamic Cordis plugins temporarily extend the current DSH process. A Plugin uses apply(ctx) to consume Services, listen to Events, provide Services, register model Tools, or register browser UI in Slots.
- Plugin and Package definitions exist only in the current process. define itself does not modify repository source, configuration, or disk, and definitions do not survive a process restart.
- The restricted execution environment prevents accidental misuse; it is not a security boundary for malicious code. Services obtained by dynamic code connect to the real runtime.
## Make the user-facing plan clear first
- Dynamic Cordis Plugins are one available implementation mechanism, not the default for every request. Consider whether one could help only when the user intends to design or create something, or when a temporary interface could materially aid the current work. The presence of these instructions or Tools, and discussion of Cordis itself, do not make a request a dynamic-Plugin task.
- When Cordis is a plausible fit, infer the intended work target and lifetime from the request and conversation. Use it only when the outcome belongs to the current running harness and should be delivered as a temporary runtime extension. If that distinction is materially ambiguous, ask at most one concise question about the intended result or lifetime. Otherwise proceed with the matching workflow; do not require the user to know or choose Cordis as an implementation mechanism.
- Once a dynamic Plugin is appropriate, decide whether the task creates a new Plugin or modifies the Plugin named by the user with @pluginId. Proceed directly when the goal is clear; do not ask for repeated confirmation.
- Choose Host, Client, or both from the requested outcome. Do not propose a Client/browser UI when the task does not need visible page behavior, and do not avoid Client when the requested outcome is visual, interactive, or depends on page state. Host versus Client is an implementation choice; do not make the user choose it.
- When a design direction or a potentially useful interface would materially affect the result, ask at most one concise outcome or creative-preference question and offer a few candidate directions. Otherwise proceed directly; do not conduct a multi-round interview or a complex questionnaire.
- cordis_define only defines and presents code; it does not run it. After definition, explain the pluginId and packageId returned by the Host and whether the next step is a run or update.
- cordis_run may require user approval. When it returns awaiting-approval, explain that the user must allow or reject it in the UI. Do not wait, retry, or claim that it is running.
- When it returns starting, explain that the request has entered the asynchronous flow and the Client is still activating. starting does not mean success. Wait for the system to report the final result through steering context.
- Do not request approval again after the user rejects it. After a technical failure, fix the same Plugin from its diagnostics; do not silently create a replacement Plugin.
## Recommended workflow and Tools
Before creating, modifying, or repairing a Plugin, load the cordis-plugin-development Skill. The Skill provides requirement navigation, capability composition, complete examples, and troubleshooting. Treat Inspect Provider results as the source of truth for exact APIs.
1. cordis_inspect_list: discover the current Host and Client Providers and their read-only query methods.
2. cordis_inspect_query: use the returned platform, provider, method, and schema to query exact Service, Event, Builtin, Slot, Theme token, or Tool information.
3. cordis_inspect_self: inspect the current Session's Plugins, Packages, version pointers, source, and diagnostics. Source is returned only when both pluginId and packageId are specified.
4. cordis_define: create the first Package for a new Plugin or append an immutable Package to an existing Plugin. It defines code but does not run it.
5. cordis_run: activate an exact Package. Use run for the first activation, restarting current, or rollback; use update to switch versions.
6. cordis_stop: remove the current Run and pending approval request while retaining definitions, grants, and version pointers.
7. cordis_undefine: permanently stop and delete a Plugin and all of its Packages. Use it only after confirming that the user no longer needs them.
- Inspect and Catalog data only confirm capabilities, names, signatures, types, and registration protocols before code is written; they do not replace business APIs.
- Query Service.listService and Event.listEvents without input to choose from their compact signature directories, then query the exact service or event before using it. Exact queries return the structured contract and only its referenced types.
- At runtime, a Plugin must call real Services or listen to real Events. Do not cache, display, or depend on Inspect results as business data.
## Identity, versions, and approval
- pluginId identifies a Plugin that can be modified over time. For a new Plugin, submit only a semantic idPrefix of 36 lowercase English letters; the Host allocates the final ID.
- packageId identifies one immutable Host/Client source version under a Plugin. To change code, define a new Package; never overwrite an old version.
- pluginRunId identifies one activation attempt and connects its approval, Host/Client loading, private RPC, Run card, and errors.
- currentPackageId is the most recent fully successful Package. Stopping, starting an update, or failing an update does not clear it.
- nextPackageId is the target awaiting approval, being attempted, awaiting Client activation, or most recently failed.
- A single check mark authorizes only the current Package; double check marks authorize future versions of the same Plugin. A grant remains in effect after a technical failure.
- An update stops the old Run before starting the target Package. Failure does not automatically restart the old version; retry next with update or roll back to current with run.
When the user enters @pluginId, the system injects identity, the default base Package, version pointers, and runtime status, but not source code:
1. Call cordis_inspect_self(pluginId, packageId) to read the target source.
2. Use cordis_define in existing mode to append a Package to the same Plugin.
3. Call cordis_run in run or update mode according to the version relationship.
Never silently create another Plugin for @pluginId. If the reference is unavailable because it was removed, belongs to another Session, or was lost on process restart, tell the user directly.
## High-frequency errors that must be avoided
### Services: ctx.get and inject
- Read an optional Service with ctx.get('serviceName') by default and handle undefined.
- Declare inject: ['serviceName'] on the returned Plugin object only when the Service is a hard dependency and the Plugin must enter waiting until Cordis reactivates it after the Service appears.
- Read ctx.serviceName only after declaring that Service in inject. Never access an undeclared Service as a ctx property.
```js
return {
inject: ['requiredService'],
apply(ctx) {
ctx.requiredService.someMethod()
const optionalService = ctx.get('optionalService')
if (optionalService !== undefined) optionalService.someMethod()
},
}
```
### Code: use plain JavaScript only
- Host and Client code is not transformed by TypeScript, JSX, or a bundler.
- Do not use TypeScript types, as, decorators, import, require, or JSX.
- Client React code must use React.createElement(...); never write <Component />.
- Do not assume that process, Buffer, window, document, fetch, native timers, or any other global is available. Query the corresponding platform's Builtins and Services first.
### Data: do not serialize live data
- Services, Events, Slots, Sessions, and their derived Cordis/DSH objects are internal live data, not ordinary JSON that can be dumped.
- Do not apply JSON.stringify, structuredClone, recursive enumeration, full copying, or whole-object display to live data.
- Read only the leaf fields required by the task, then construct the smallest owned data object without Host references.
### Lifecycle: every side effect must be reversible
- Services, Events, Tools, handlers, timers, Slots, styles, and theme overrides must all belong to the current Fiber.
- Use ctx.effect(), ctx.on(), or official APIs that return a disposer so stop, update, or undefine removes every side effect.
- The cordis-plugin-development Skill contains complete timer, Waterfall, Slot, theme, Tool, RPC, and React examples and troubleshooting guidance.
## Host and Client
- Host runs in the DSH Node.js process and is appropriate for files, networking, commands, Agent/Session access, Host Events, Services, model Tools, and JSON methods callable by the Client.
- Client runs in the browser page and is appropriate for themes, layout, current page state, Tool cards, and Slot UI.
- Host and Client communicate through Package-private JSON methods: Host uses harness.handle(method, handler), and Client uses host.call(method, args). The direction is Client→Host, and only lossless JSON may cross it.
- Client UI must be registered in a queried Slot; apply() cannot directly return a React Element. Query Slots.listSubTree without root to choose from the compact purpose/topology tree, then query the exact root for its full registration contract and props before writing code.
- See the Skill and Inspect Providers for Run-specific panels and exact Slot registration patterns.
## Asynchronous results and recovery
- Do not wait inside a Tool for approval or browser work that can happen only after the current turn ends.
- Asynchronous success, rejection, and runtime errors update Run state and notify you through steering context.
- After a technical failure, use cordis_inspect_self to read the exact Package source and its message/stack. Define a corrected Package under the same Plugin and retry autonomously.
- Use the cordis-plugin-development Skill for other failure causes, repair procedures, and complete extension patterns.
Use the ralph tool ONLY when the direct human explicitly asks for a Ralph loop or fresh-agent iterative execution. Each Ralph round starts a fresh child with no conversation seed and uses the shared workspace as durable memory. Completion and blockers are worker reports, not independent evaluation. Use same-session goal tools for ordinary long-running objectives, and plain subagents or workflows for bounded delegation and fan-out.
Use subagent in the background by default. Start independent delegations together in one assistant message and continue useful work while they run. Set `run_in_background: false` only when your next action depends on that subagent's result. When a background run settles, the runtime sends you a notice containing its outcome and any final assistant message.
@@ -55,22 +161,66 @@ 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_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;
/** Define an immutable Cordis Package. For a new Plugin, use kind:"new" and provide only a semantic prefix of 36 lowercase English letters; the Host returns the final pluginId and packageId. To modify an existing Plugin, use kind:"existing" with its exact pluginId to append a Package without overwriting older versions. Provide at least one of code.host and code.client. Each value is a plain JavaScript function body that returns a Cordis Plugin; no TypeScript, JSX, or import transformation occurs. Query Inspect before depending on a Service, Event, Builtin, Slot, or token. Define only validates parameters and syntax and records source: it does not request approval, execute apply, or change currentPackageId. On success, call cordis_run with the returned IDs. */
cordis_define: {
plugin: {
kind: "new";
/** Suggested semantic prefix of 36 lowercase English letters; the Host adds a unique numeric suffix. */
idPrefix: string;
} | {
kind: "existing";
/** Exact ID of an existing Plugin; the new Package is appended to that instance. */
pluginId: string;
};
/** Short, readable Package name. */
name: string;
/** One-sentence, user-facing description of the Package purpose. */
purpose: string;
code: {
/** Plain JavaScript function body that returns the Host-half Cordis Plugin. */
host?: string;
/** Plain JavaScript function body that returns the browser Client-half Cordis Plugin. */
client?: string;
};
} & Record<string, JsonValue>;
/** 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 an Harness Plugin or installable profile bundle 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.shell) 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.shell 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()` SHORT-CIRCUITS 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.shell) reach the real runtime. */
cordis_mount: {
/** JavaScript body returning a temporary Plugin; evaluated now and saved nowhere. */
code: string;
/** List every Cordis Inspect Provider currently known to the Host, including local Host Providers and the latest manifests synchronized from the Client. Each entry includes its platform, purpose, read-only methods, and input/output schemas. Call this Tool before creating or modifying a Package, then select the provider and method for cordis_inspect_query from its result. Do not guess names or treat an Inspect method as a business Service that Plugin code can call. */
cordis_inspect_list: Record<string, JsonValue>;
/** Run a read-only query explicitly declared by an Inspect Provider. platform, provider, and method must come from cordis_inspect_list, and input must satisfy that method's schema. Use this Tool before cordis_define to read exact Service methods, Event modes, Builtin signatures, Tool schemas, theme tokens, or live Slot trees and props. Host queries run locally. A Client query waits for the first valid page response and remains pending until a page answers or the Tool is cancelled. This Tool cannot invoke business Service methods or modify the runtime. For Service.listService and Event.listEvents, query without input to navigate the compact signature directory, then query the exact service or event for its structured contract and referenced types. For Slots.listSubTree, query without root to navigate the compact tree, then query the exact root for its complete registration contract and props. */
cordis_inspect_query: {
/** Runtime platform that owns the Provider. */
platform: "host" | "client";
/** Exact Provider ID returned by cordis_inspect_list. */
provider: string;
/** Exact method name declared by the Provider manifest. */
method: string;
/** Optional query input; it must satisfy the method input schema. */
input?: JsonValue;
} & 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;
/** Inspect dynamic Cordis objects owned by the current Session at increasing levels of detail. With no IDs, list only Plugin summaries. With pluginId alone, return version pointers, the latest Run, and every Package summary. Only pluginId plus packageId returns that immutable Package's Host/Client source and runtime diagnostics. packageId cannot be supplied alone. Query an exact Package before handling @pluginId, repairing an asynchronous failure, or defining an updated version. This Tool is read-only: it neither executes code nor changes version pointers. */
cordis_inspect_self: {
/** Stable Plugin ID returned by cordis_define or injected by @pluginId; omit it to list every current Plugin. */
pluginId?: string;
/** Exact immutable Package ID owned by pluginId; when specified, source and diagnostics are returned. */
packageId?: string;
} & Record<string, JsonValue>;
/** Activate one exact Package of a dynamic Plugin. Use mode:"run" for the first activation, restarting currentPackageId, or rollback. When current exists, use mode:"update" to switch to a different Package, even if the Plugin is currently stopped. An unauthorized Client Package creates an approval request and returns awaiting-approval; an authorized Package returns starting and continues asynchronously in the browser. Neither result waits for the final outcome inside the Tool. currentPackageId changes only after complete success; on failure, the old current and target next remain. Asynchronous success, rejection, or technical failure is reported through state and steering. After a technical failure, read diagnostics with cordis_inspect_self, correct the same Plugin, and retry autonomously. Do not request approval again after the user rejects it. */
cordis_run: {
/** Stable Plugin ID returned by cordis_define. */
pluginId: string;
/** Exact immutable Package ID to activate under that Plugin. */
packageId: string;
/** Use run for the first activation, restarting current, or rollback; use update to switch from current to a different Package. */
mode: "run" | "update";
} & Record<string, JsonValue>;
/** Stop the current Run of a dynamic Plugin and cancel unfinished approval or activation requests. Retain the Plugin, every immutable Package, grants, currentPackageId, and nextPackageId so it can later run or update directly. Stopping an already stopped Plugin succeeds idempotently. Use this Tool to disable effects temporarily; use cordis_undefine for permanent removal. */
cordis_stop: {
/** Stable dynamic Plugin ID to stop. */
pluginId: string;
} & Record<string, JsonValue>;
/** Permanently remove a dynamic Plugin owned by the current Session. If it is running or awaiting approval, first stop it and cancel the request, then delete every Package, grant, and version pointer. After this returns, its pluginId, packageIds, @ reference, and Package business views are invalid; historical cards retain only a "Plugin removed" record. Do not call this Tool when versions must remain available for restart or rollback; use cordis_stop instead. */
cordis_undefine: {
/** Stable dynamic Plugin ID to remove permanently. */
pluginId: 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: {
@@ -261,17 +411,24 @@ interface ToolOutputMap {
runnerFailed?: boolean;
};
};
cordis_inspect: string;
cordis_mount: {
id: string;
pluginName: string;
state: "pending" | "loading" | "active" | "failed" | "disposed" | "unloading";
provides: string[];
waitingFor: string[];
cordis_define: {
pluginId: string;
packageId: string;
name: string;
purpose: string;
hasHostHalf: boolean;
hasClientHalf: boolean;
};
cordis_unmount: {
id: string;
pluginName: string;
cordis_inspect_list: JsonValue;
cordis_inspect_query: JsonValue;
cordis_inspect_self: JsonValue;
cordis_run: JsonValue;
cordis_stop: {
pluginId: string;
};
cordis_undefine: {
pluginId: string;
wasRunning: boolean;
};
create_goal: {
goal: null;
@@ -46,59 +46,199 @@
}
},
{
"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_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.",
"name": "cordis_define",
"description": "Define an immutable Cordis Package. For a new Plugin, use kind:\"new\" and provide only a semantic prefix of 36 lowercase English letters; the Host returns the final pluginId and packageId. To modify an existing Plugin, use kind:\"existing\" with its exact pluginId to append a Package without overwriting older versions. Provide at least one of code.host and code.client. Each value is a plain JavaScript function body that returns a Cordis Plugin; no TypeScript, JSX, or import transformation occurs. Query Inspect before depending on a Service, Event, Builtin, Slot, or token. Define only validates parameters and syntax and records source: it does not request approval, execute apply, or change currentPackageId. On success, call cordis_run with the returned IDs.",
"parameters": {
"type": "object",
"properties": {
"what": {
"type": "string",
"description": "Limit the report to one section. Omit for all sections.",
"enum": [
"services",
"plugins",
"tools",
"temporary",
"api",
"events"
"plugin": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "new"
},
"idPrefix": {
"type": "string",
"description": "Suggested semantic prefix of 36 lowercase English letters; the Host adds a unique numeric suffix."
}
},
"required": [
"kind",
"idPrefix"
]
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"kind": {
"type": "string",
"const": "existing"
},
"pluginId": {
"type": "string",
"description": "Exact ID of an existing Plugin; the new Package is appended to that instance."
}
},
"required": [
"kind",
"pluginId"
]
}
]
},
"name": {
"type": "string",
"description": "Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\"."
}
}
}
},
{
"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 an Harness Plugin or installable profile bundle 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.shell) 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.shell 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()` SHORT-CIRCUITS 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.shell) reach the real runtime.",
"parameters": {
"type": "object",
"properties": {
"code": {
"description": "Short, readable Package name."
},
"purpose": {
"type": "string",
"description": "JavaScript body returning a temporary Plugin; evaluated now and saved nowhere."
"description": "One-sentence, user-facing description of the Package purpose."
},
"code": {
"type": "object",
"additionalProperties": false,
"properties": {
"host": {
"type": "string",
"description": "Plain JavaScript function body that returns the Host-half Cordis Plugin."
},
"client": {
"type": "string",
"description": "Plain JavaScript function body that returns the browser Client-half Cordis Plugin."
}
}
}
},
"required": [
"plugin",
"name",
"purpose",
"code"
]
}
},
{
"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.",
"name": "cordis_inspect_list",
"description": "List every Cordis Inspect Provider currently known to the Host, including local Host Providers and the latest manifests synchronized from the Client. Each entry includes its platform, purpose, read-only methods, and input/output schemas. Call this Tool before creating or modifying a Package, then select the provider and method for cordis_inspect_query from its result. Do not guess names or treat an Inspect method as a business Service that Plugin code can call.",
"parameters": {
"type": "object",
"properties": {}
}
},
{
"name": "cordis_inspect_query",
"description": "Run a read-only query explicitly declared by an Inspect Provider. platform, provider, and method must come from cordis_inspect_list, and input must satisfy that method's schema. Use this Tool before cordis_define to read exact Service methods, Event modes, Builtin signatures, Tool schemas, theme tokens, or live Slot trees and props. Host queries run locally. A Client query waits for the first valid page response and remains pending until a page answers or the Tool is cancelled. This Tool cannot invoke business Service methods or modify the runtime. For Service.listService and Event.listEvents, query without input to navigate the compact signature directory, then query the exact service or event for its structured contract and referenced types. For Slots.listSubTree, query without root to navigate the compact tree, then query the exact root for its complete registration contract and props.",
"parameters": {
"type": "object",
"properties": {
"id": {
"platform": {
"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."
"description": "Runtime platform that owns the Provider.",
"enum": [
"host",
"client"
]
},
"provider": {
"type": "string",
"description": "Exact Provider ID returned by cordis_inspect_list."
},
"method": {
"type": "string",
"description": "Exact method name declared by the Provider manifest."
},
"input": {
"description": "Optional query input; it must satisfy the method input schema."
}
},
"required": [
"id"
"platform",
"provider",
"method"
]
}
},
{
"name": "cordis_inspect_self",
"description": "Inspect dynamic Cordis objects owned by the current Session at increasing levels of detail. With no IDs, list only Plugin summaries. With pluginId alone, return version pointers, the latest Run, and every Package summary. Only pluginId plus packageId returns that immutable Package's Host/Client source and runtime diagnostics. packageId cannot be supplied alone. Query an exact Package before handling @pluginId, repairing an asynchronous failure, or defining an updated version. This Tool is read-only: it neither executes code nor changes version pointers.",
"parameters": {
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable Plugin ID returned by cordis_define or injected by @pluginId; omit it to list every current Plugin."
},
"packageId": {
"type": "string",
"description": "Exact immutable Package ID owned by pluginId; when specified, source and diagnostics are returned."
}
}
}
},
{
"name": "cordis_run",
"description": "Activate one exact Package of a dynamic Plugin. Use mode:\"run\" for the first activation, restarting currentPackageId, or rollback. When current exists, use mode:\"update\" to switch to a different Package, even if the Plugin is currently stopped. An unauthorized Client Package creates an approval request and returns awaiting-approval; an authorized Package returns starting and continues asynchronously in the browser. Neither result waits for the final outcome inside the Tool. currentPackageId changes only after complete success; on failure, the old current and target next remain. Asynchronous success, rejection, or technical failure is reported through state and steering. After a technical failure, read diagnostics with cordis_inspect_self, correct the same Plugin, and retry autonomously. Do not request approval again after the user rejects it.",
"parameters": {
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable Plugin ID returned by cordis_define."
},
"packageId": {
"type": "string",
"description": "Exact immutable Package ID to activate under that Plugin."
},
"mode": {
"type": "string",
"description": "Use run for the first activation, restarting current, or rollback; use update to switch from current to a different Package.",
"enum": [
"run",
"update"
]
}
},
"required": [
"pluginId",
"packageId",
"mode"
]
}
},
{
"name": "cordis_stop",
"description": "Stop the current Run of a dynamic Plugin and cancel unfinished approval or activation requests. Retain the Plugin, every immutable Package, grants, currentPackageId, and nextPackageId so it can later run or update directly. Stopping an already stopped Plugin succeeds idempotently. Use this Tool to disable effects temporarily; use cordis_undefine for permanent removal.",
"parameters": {
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable dynamic Plugin ID to stop."
}
},
"required": [
"pluginId"
]
}
},
{
"name": "cordis_undefine",
"description": "Permanently remove a dynamic Plugin owned by the current Session. If it is running or awaiting approval, first stop it and cancel the request, then delete every Package, grant, and version pointer. After this returns, its pluginId, packageIds, @ reference, and Package business views are invalid; historical cards retain only a \"Plugin removed\" record. Do not call this Tool when versions must remain available for restart or rollback; use cordis_stop instead.",
"parameters": {
"type": "object",
"properties": {
"pluginId": {
"type": "string",
"description": "Stable dynamic Plugin ID to remove permanently."
}
},
"required": [
"pluginId"
]
}
},
@@ -2,6 +2,6 @@
"steps": [
{ "op": "initialize" },
{ "op": "newSession" },
{ "op": "prompt", "text": "Inspect the exact tools service API and tools/pre-execute event with cordis_inspect, then reply with exactly CORDIS_INSPECT_JSDOC_OK." }
{ "op": "prompt", "text": "Inspect the exact tools service API and tools/pre-execute event with cordis_inspect_query, then reply with exactly CORDIS_INSPECT_JSDOC_OK." }
]
}
File diff suppressed because one or more lines are too long.
@@ -41,6 +41,8 @@
- insert:
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
- id: tool-cordis
name: '@deepseek-ai/dsh-tool-cordis'
- id: llm-replay
@@ -28,5 +28,7 @@
- insert:
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
- id: tool-cordis
name: '@deepseek-ai/dsh-tool-cordis'
+64 -26
View File
@@ -23,6 +23,7 @@ import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
import * as WorkspaceContext from '@deepseek-ai/dsh-agent-instructions'
import LocalJobRegistry from '@deepseek-ai/dsh-jobs-local'
import * as ToolTasks from '@deepseek-ai/dsh-tool-jobs'
import CordisHostRunner from '@deepseek-ai/dsh-cordis-host-runner'
import * as ToolCordis from '@deepseek-ai/dsh-tool-cordis'
/**
@@ -85,12 +86,18 @@ let keylessCall = 0
const testToolSignal = new AbortController().signal
/** Execute one outer Code Mode call through the real registry and worker. */
function runCode(harness: Context, code: string, signal: AbortSignal = testToolSignal): Promise<ToolExecutionResult> {
function runCode(
harness: Context,
code: string,
signal: AbortSignal = testToolSignal,
agent?: Agent,
): Promise<ToolExecutionResult> {
return harness.tools.execute({
callId: CallId(`keyless-code-${++keylessCall}`),
name: RUN_CODE_NAME,
arguments: { code, description: 'Run the e2e program' },
signal,
...(agent === undefined ? {} : { agent }),
})
}
@@ -254,46 +261,77 @@ describe('Code Mode typed values: keyless real-worker contracts', () => {
expect(ctx.jobs.list()).toEqual([])
}, 15_000)
it('uses cordis_mount DTO ids directly for running and pending temporary Plugins, then confirms removal', async () => {
it('uses versioned Cordis DTO ids directly for running and pending Plugins, then confirms removal', async () => {
ctx = await typedCodeModeHarness()
await ctx.plugin(CordisHostRunner)
await ctx.plugin(ToolCordis)
const agent = {
id: SessionId('code-mode-cordis'),
session: { append: vi.fn() },
} as unknown as Agent
const value = completion(await runCode(ctx, `
const active = await tools.cordis_mount({
code: "return { name: 'active-code-mode-plugin', apply(ctx) {} }",
const activeDefinition = await tools.cordis_define({
plugin: { kind: 'new', idPrefix: 'active' },
name: 'active-code-mode-plugin',
purpose: 'prove an active Host half',
code: { host: "return { name: 'active-code-mode-plugin', apply(ctx) {} }" },
});
const pending = await tools.cordis_mount({
code: "return { name: 'pending-code-mode-plugin', inject: ['missing-code-mode-service'], apply(ctx) {} }",
const active = await tools.cordis_run({
pluginId: activeDefinition.pluginId,
packageId: activeDefinition.packageId,
mode: 'run',
});
const before = await tools.cordis_inspect({ what: 'temporary' });
const stopped = await tools.cordis_unmount({ id: active.id });
const after = await tools.cordis_inspect({ what: 'temporary' });
await tools.cordis_unmount({ id: pending.id });
const pendingDefinition = await tools.cordis_define({
plugin: { kind: 'new', idPrefix: 'queue' },
name: 'pending-code-mode-plugin',
purpose: 'prove a Host half waiting for a Service',
code: { host: "return { name: 'pending-code-mode-plugin', inject: ['missing-code-mode-service'], apply(ctx) {} }" },
});
const pending = await tools.cordis_run({
pluginId: pendingDefinition.pluginId,
packageId: pendingDefinition.packageId,
mode: 'run',
});
const before = await tools.cordis_inspect_self({});
const removed = await tools.cordis_undefine({ pluginId: active.pluginId });
const after = await tools.cordis_inspect_self({});
await tools.cordis_undefine({ pluginId: pending.pluginId });
return {
active,
pending,
stopped,
beforeContainsId: before.includes(active.id),
afterContainsId: after.includes(active.id),
active: {
pluginId: active.pluginId,
packageId: active.packageId,
pluginRunId: active.pluginRunId,
status: active.host.status,
},
pending: {
pluginId: pending.pluginId,
packageId: pending.packageId,
pluginRunId: pending.pluginRunId,
status: pending.host.status,
waitingFor: pending.host.waitingFor,
},
removed,
beforeContainsId: before.plugins.some(plugin => plugin.pluginId === active.pluginId),
afterContainsId: after.plugins.some(plugin => plugin.pluginId === active.pluginId),
};
`))
`, testToolSignal, agent))
expect(value).toEqual({
active: {
id: 'dyn-1',
pluginName: 'active-code-mode-plugin',
state: 'active',
provides: [],
waitingFor: [],
pluginId: 'active-1',
packageId: 'pkg-1',
pluginRunId: 'run-1',
status: 'running',
},
pending: {
id: 'dyn-2',
pluginName: 'pending-code-mode-plugin',
state: 'pending',
provides: [],
pluginId: 'queue-2',
packageId: 'pkg-2',
pluginRunId: 'run-2',
status: 'waiting',
waitingFor: ['missing-code-mode-service'],
},
stopped: { id: 'dyn-1', pluginName: 'active-code-mode-plugin' },
removed: { pluginId: 'active-1', wasRunning: true },
beforeContainsId: true,
afterContainsId: false,
})
@@ -1,7 +1,14 @@
{
"steps": [
{ "op": "initialize" },
{ "op": "newSession" },
{ "op": "prompt", "text": "Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply with exactly ADVANCED_HEADLESS_OK." }
{
"op": "initialize"
},
{
"op": "newSession"
},
{
"op": "prompt",
"text": "Run this advanced flow exactly once: define a host-only dynamic Cordis Package named Snapshot Marker; run and inspect snap-1/pkg-1 through run_code; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; remove snap-1; then reply with exactly ADVANCED_HEADLESS_OK."
}
]
}
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.
@@ -1,73 +1,75 @@
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":0,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply with exactly ADVANCED_HEADLESS_OK."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":0,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Run this advanced flow exactly once: define a host-only dynamic Cordis Package named Snapshot Marker; run and inspect snap-1/pkg-1 through run_code; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; remove snap-1; then reply with exactly ADVANCED_HEADLESS_OK."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":1,"time":0,"data":{"turn":1}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":2,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}}
{"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":"user/message","seq":4,"time":0,"data":{"content":[{"type":"text","text":"Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply with exactly ADVANCED_HEADLESS_OK."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":4,"time":0,"data":{"content":[{"type":"text","text":"Run this advanced flow exactly once: define a host-only dynamic Cordis Package named Snapshot Marker; run and inspect snap-1/pkg-1 through run_code; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; remove snap-1; then reply with exactly ADVANCED_HEADLESS_OK."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":5,"time":0,"data":{"title":"Run this advanced flow exactly","messageSeqs":[4],"source":{"kind":"fallback"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":6,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":7,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":8,"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":9,"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":10,"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":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-define","name":"cordis_define","argumentsDelta":"{\"plugin\":{\"kind\":\"new\",\"idPrefix\":\"snap\"},\"name\":\"Snapshot Marker\",\"purpose\":\"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\"code\":{\"host\":\"return { apply() {} }\"}}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-define","name":"cordis_define","arguments":"{\"plugin\":{\"kind\":\"new\",\"idPrefix\":\"snap\"},\"name\":\"Snapshot Marker\",\"purpose\":\"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\"code\":{\"host\":\"return { apply() {} }\"}}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":11,"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":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[8,9,10,11,12],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":14,"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":15,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"advanced-mount"},"content":[{"type":"tool-result","toolCallId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until unmounted or DSH restarts)."}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-define","name":"cordis_define","arguments":"{\"plugin\":{\"kind\":\"new\",\"idPrefix\":\"snap\"},\"name\":\"Snapshot Marker\",\"purpose\":\"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\"code\":{\"host\":\"return { apply() {} }\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[8,9,10,11,12],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-define","name":"cordis_define","arguments":"{\"plugin\":{\"kind\":\"new\",\"idPrefix\":\"snap\"},\"name\":\"Snapshot Marker\",\"purpose\":\"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\"code\":{\"host\":\"return { apply() {} }\"}}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"advanced-define"},"content":[{"type":"tool-result","toolCallId":"advanced-define","content":[{"type":"text","text":"Defined snap-1/pkg-1 (Snapshot Marker); it is not running yet. Use cordis_run to activate this Package."}],"isError":false}],"role":"user","id":"{{sessionId}}"},"meta":{"pluginId":"snap-1","packageId":"pkg-1"}},"sourceEventSeqs":[14],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":17,"time":0,"data":{"turn":1,"step":2}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":18,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":19,"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.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":20,"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.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-code","name":"run_code","argumentsDelta":"{\"code\":\"const run = await tools.cordis_run({ pluginId: 'snap-1', packageId: 'pkg-1', mode: 'run' });\\nconst inspected = await tools.cordis_inspect_self({ pluginId: 'snap-1' });\\nreturn { run, inspected };\",\"description\":\"Run and inspect the dynamic Cordis Package\"}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":20,"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\":\"const run = await tools.cordis_run({ pluginId: 'snap-1', packageId: 'pkg-1', mode: 'run' });\\nconst inspected = await tools.cordis_inspect_self({ pluginId: 'snap-1' });\\nreturn { run, inspected };\",\"description\":\"Run and inspect the dynamic Cordis Package\"}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":21,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":22,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":23,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","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\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[18,19,20,21,22],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":24,"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":25,"time":0,"data":{"rootCallId":"advanced-code","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":26,"time":0,"data":{"rootCallId":"advanced-code","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":27,"time":0,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"advanced-code"},"content":[{"type":"tool-result","toolCallId":"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}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[24],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":28,"time":0,"data":{"turn":1,"step":2}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":29,"time":0,"data":{"turn":1,"step":3}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":30,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":31,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-direct-child","name":"subagent","argumentsDelta":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":32,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":33,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":34,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":35,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[30,31,32,33,34],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":36,"time":0,"data":{"turn":1,"step":3,"callId":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":37,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"advanced-direct-child"},"content":[{"type":"tool-result","toolCallId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[36],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":38,"time":0,"data":{"turn":1,"step":3}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":39,"time":0,"data":{"turn":1,"step":4}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":40,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":41,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-workflow","name":"workflow","argumentsDelta":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":42,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":43,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":44,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":45,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[40,41,42,43,44],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":46,"time":0,"data":{"turn":1,"step":4,"callId":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool-workflow/run-start","seq":47,"time":0,"data":{"runId":"{{sessionId}}","name":"advanced-headless-snapshot"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool-workflow/agent-start","seq":48,"time":0,"data":{"runId":"{{sessionId}}","seq":1,"label":"workflow-child","phase":"Delegate","childId":"{{sessionId}}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool-workflow/agent-end","seq":49,"time":0,"data":{"runId":"{{sessionId}}","seq":1,"outcome":"completed"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool-workflow/run-end","seq":50,"time":0,"data":{"runId":"{{sessionId}}","stopReason":"completed"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":51,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-headless-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[46],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":52,"time":0,"data":{"turn":1,"step":4}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":53,"time":0,"data":{"turn":1,"step":5}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":54,"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":55,"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":56,"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":57,"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":58,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":59,"time":0,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[54,55,56,57,58],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":60,"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":61,"time":0,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-unmount"},"content":[{"type":"tool-result","toolCallId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[60],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":62,"time":0,"data":{"turn":1,"step":5}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":63,"time":0,"data":{"turn":1,"step":6}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":64,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":65,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_HEADLESS_OK"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":66,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_HEADLESS_OK"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":67,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":68,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":69,"time":0,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_HEADLESS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[64,65,66,67,68],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":70,"time":0,"data":{"turn":1,"step":6}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":71,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":23,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\":\"const run = await tools.cordis_run({ pluginId: 'snap-1', packageId: 'pkg-1', mode: 'run' });\\nconst inspected = await tools.cordis_inspect_self({ pluginId: 'snap-1' });\\nreturn { run, inspected };\",\"description\":\"Run and inspect the dynamic Cordis Package\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[18,19,20,21,22],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":24,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","name":"run_code","arguments":"{\"code\":\"const run = await tools.cordis_run({ pluginId: 'snap-1', packageId: 'pkg-1', mode: 'run' });\\nconst inspected = await tools.cordis_inspect_self({ pluginId: 'snap-1' });\\nreturn { run, inspected };\",\"description\":\"Run and inspect the dynamic Cordis Package\"}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/code-dispatch-start","seq":25,"time":0,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_run","arguments":{"pluginId":"snap-1","packageId":"pkg-1","mode":"run"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/code-dispatch","seq":26,"time":0,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_run","arguments":{"pluginId":"snap-1","packageId":"pkg-1","mode":"run"},"isError":false,"content":[{"type":"text","text":"snap-1/pkg-1 is running (run-1)."}]}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/code-dispatch-start","seq":27,"time":0,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:2","name":"cordis_inspect_self","arguments":{"pluginId":"snap-1"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/code-dispatch","seq":28,"time":0,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:2","name":"cordis_inspect_self","arguments":{"pluginId":"snap-1"},"isError":false,"content":[{"type":"text","text":"{\n \"mode\": \"plugin\",\n \"pluginId\": \"snap-1\",\n \"name\": \"Snapshot Marker\",\n \"packageCount\": 1,\n \"state\": \"running\",\n \"currentPackageId\": \"pkg-1\",\n \"activeRun\": {\n \"pluginRunId\": \"run-1\",\n \"packageId\": \"pkg-1\"\n },\n \"packages\": [\n {\n \"packageId\": \"pkg-1\",\n \"name\": \"Snapshot Marker\",\n \"purpose\": \"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\n \"hasHostHalf\": true,\n \"hasClientHalf\": false,\n \"isCurrent\": true,\n \"isNext\": false\n }\n ]\n}"}]}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":29,"time":0,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"advanced-code"},"content":[{"type":"tool-result","toolCallId":"advanced-code","content":[{"type":"text","text":"{\n \"run\": {\n \"status\": \"running\",\n \"pluginId\": \"snap-1\",\n \"packageId\": \"pkg-1\",\n \"pluginRunId\": \"run-1\",\n \"currentPackageId\": \"pkg-1\",\n \"host\": {\n \"status\": \"running\",\n \"provides\": [],\n \"waitingFor\": []\n },\n \"client\": {\n \"status\": \"absent\",\n \"waitingFor\": []\n }\n },\n \"inspected\": {\n \"mode\": \"plugin\",\n \"pluginId\": \"snap-1\",\n \"name\": \"Snapshot Marker\",\n \"packageCount\": 1,\n \"state\": \"running\",\n \"currentPackageId\": \"pkg-1\",\n \"activeRun\": {\n \"pluginRunId\": \"run-1\",\n \"packageId\": \"pkg-1\"\n },\n \"packages\": [\n {\n \"packageId\": \"pkg-1\",\n \"name\": \"Snapshot Marker\",\n \"purpose\": \"Exercise the dynamic Cordis Package lifecycle in the snapshot.\",\n \"hasHostHalf\": true,\n \"hasClientHalf\": false,\n \"isCurrent\": true,\n \"isNext\": false\n }\n ]\n }\n}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[24],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":30,"time":0,"data":{"turn":1,"step":2}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":31,"time":0,"data":{"turn":1,"step":3}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":32,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":33,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-direct-child","name":"subagent","argumentsDelta":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":34,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":35,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":36,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":37,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[32,33,34,35,36],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":38,"time":0,"data":{"turn":1,"step":3,"callId":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":39,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"advanced-direct-child"},"content":[{"type":"tool-result","toolCallId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[38],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":40,"time":0,"data":{"turn":1,"step":3}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":41,"time":0,"data":{"turn":1,"step":4}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":42,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":43,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-workflow","name":"workflow","argumentsDelta":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":44,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":45,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":46,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":47,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[42,43,44,45,46],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":48,"time":0,"data":{"turn":1,"step":4,"callId":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool-workflow/run-start","seq":49,"time":0,"data":{"runId":"{{sessionId}}","name":"advanced-headless-snapshot"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool-workflow/agent-start","seq":50,"time":0,"data":{"runId":"{{sessionId}}","seq":1,"label":"workflow-child","phase":"Delegate","childId":"{{sessionId}}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool-workflow/agent-end","seq":51,"time":0,"data":{"runId":"{{sessionId}}","seq":1,"outcome":"completed"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool-workflow/run-end","seq":52,"time":0,"data":{"runId":"{{sessionId}}","stopReason":"completed"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":53,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-headless-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[48],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":54,"time":0,"data":{"turn":1,"step":4}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":55,"time":0,"data":{"turn":1,"step":5}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":56,"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":57,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-undefine","name":"cordis_undefine","argumentsDelta":"{\"pluginId\":\"snap-1\"}"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":58,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-undefine","name":"cordis_undefine","arguments":"{\"pluginId\":\"snap-1\"}"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":59,"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":60,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":61,"time":0,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-undefine","name":"cordis_undefine","arguments":"{\"pluginId\":\"snap-1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[56,57,58,59,60],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":62,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-undefine","name":"cordis_undefine","arguments":"{\"pluginId\":\"snap-1\"}"}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":63,"time":0,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-undefine"},"content":[{"type":"tool-result","toolCallId":"advanced-undefine","content":[{"type":"text","text":"Removed dynamic Plugin snap-1 and all of its Packages."}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[62],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":64,"time":0,"data":{"turn":1,"step":5}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":65,"time":0,"data":{"turn":1,"step":6}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":66,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":67,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_HEADLESS_OK"}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":68,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_HEADLESS_OK"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":69,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":70,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":71,"time":0,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_HEADLESS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[66,67,68,69,70],"surfaceOp":"append"}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":72,"time":0,"data":{"turn":1,"step":6}}}
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":73,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}
{"type":"result","sessionId":"{{sessionId}}","output":"ADVANCED_HEADLESS_OK","usage":{"inputTokens":18,"outputTokens":18}}
+1
View File
@@ -36,6 +36,7 @@
"@deepseek-ai/dsh-goal-round-driver": "workspace:*",
"@deepseek-ai/dsh-hooks-claude-code": "workspace:*",
"@deepseek-ai/dsh-hooks-codex": "workspace:*",
"@deepseek-ai/dsh-cordis-host-runner": "workspace:*",
"@deepseek-ai/dsh-invariants": "workspace:*",
"@deepseek-ai/dsh-sdk-jsonrpc-server": "workspace:*",
"@deepseek-ai/dsh-llm": "workspace:*",
+2
View File
@@ -13,5 +13,7 @@
port: 3081
- insert:
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
- id: tool-cordis
name: '@deepseek-ai/dsh-tool-cordis'
+12
View File
@@ -97,6 +97,18 @@
"@deepseek-ai/dsh-client-ui-directory-picker-native"
]
},
"packages/extensions/cordis-host-runner": {
"entry": [
"tests/**/*.spec.ts"
],
"project": [
"src/**/*.ts",
"tests/**/*.ts"
],
"ignoreDependencies": [
"zod"
]
},
"packages/host/directory-picker-native": {
"entry": [
"tests/**/*.spec.{ts,tsx}",
+3
View File
@@ -102,6 +102,9 @@
"verify-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts --check",
"gen-cordis-api": "tsx scripts/gen-cordis-api.ts",
"verify-cordis-api": "tsx scripts/gen-cordis-api.ts --check",
"gen-client-catalog": "tsx scripts/gen-client-catalog.ts",
"gen-cordis-inspect-catalog": "tsx scripts/gen-cordis-inspect-catalog.ts",
"verify-client-catalog": "tsx scripts/gen-client-catalog.ts --check",
"verify-export-jsdoc": "tsx scripts/verify-export-jsdoc.ts",
"gen-tool-catalog": "tsx scripts/gen-tool-catalog.ts",
"verify-tool-catalog": "tsx scripts/gen-tool-catalog.ts --check",
+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/README.md
README.md: 578e8a3326a35fadc95e8420912f950a7d7427b5
README.zh.md: cf0c0e39aa87e75bed5250cdb66909d82e2bee02
README.md: 2e28b190378ed8ca25102d6311bd8a2c8d5e1c58
README.zh.md: e0e1973d146c241d4846c4c237ed0e5f6a0d7b8a
+1 -1
View File
@@ -40,7 +40,7 @@ Groups hold `packages/<group>/<pkg>/`; names stay `@deepseek-ai/dsh-<pkg>`. **Gr
| [`preset/`](preset/README.md) | Per-session agent composition from preset `cordis.yml` files | Product — stable API |
| [`guard/`](guard/README.md) | Loop-hygiene guards: advisory repeat-call reminders + the `tools/execute` deadline enforcer | Product — stable API |
| [`bundle/`](bundle/README.md) | Installable `dsh --profile` patch layers | Product — stable API |
| [`extensions/`](self-modification/README.md) | Agent runtime self-modification: live plugin/service inspection and model-written plugin mount/unmount ([design](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)) | Product — stable API |
| [`extensions/`](extensions/README.md) | Agent runtime self-modification: live plugin/service inspection and model-written plugin mount/unmount ([design](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)) | Product — stable API |
| [`hooks/`](hooks/README.md) | Hook bridges + the shared Claude Code / Codex wire-protocol library | Product — stable API |
| [`session/`](session/README.md) | Durable session data plane: persistence seam + JSONL/SQLite backends, projection seam, log-backed titles, session reporting | Product — stable API |
| [`session-query/`](session-query/README.md) | Session retrieval family: logical corpus, bounded reads, lineage, event relationships, semantic filtering, and SQLite full-text search | Product — stable API |
+1 -1
View File
@@ -40,7 +40,7 @@ npm scope 为 `@deepseek-ai/dsh-*`Cordis `Service` 子类和函数插件通
| [`preset/`](preset/README.md) | 由 preset `cordis.yml` 按会话组装 agent | 产品:稳定接口 |
| [`guard/`](guard/README.md) | 循环卫生守卫:建议性重复调用提醒 + `tools/execute` 截止时间强制执行器 | 产品:稳定接口 |
| [`bundle/`](bundle/README.md) | 可安装的 `dsh --profile` 补丁层 | 产品:稳定接口 |
| [`extensions/`](self-modification/README.md) | agent 运行时自修改:实时插件/服务检查和模型所写插件挂载/卸载([设计](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md) | 产品:稳定接口 |
| [`extensions/`](extensions/README.md) | agent 运行时自修改:实时插件/服务检查和模型所写插件挂载/卸载([设计](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md) | 产品:稳定接口 |
| [`hooks/`](hooks/README.md) | 钩子桥接 + 共享 Claude CodeCodex 协议格式库 | 产品:稳定接口 |
| [`session/`](session/README.md) | 持久会话数据平面:持久化 seam + JSONL/SQLite 后端、投影 seam、基于日志的标题、会话上报 | 产品:稳定接口 |
| [`session-query/`](session-query/README.md) | 会话检索系列:逻辑语料库、有界读取、血缘、事件关系、语义过滤和 SQLite 全文搜索 | 产品:稳定接口 |
+2
View File
@@ -64,6 +64,7 @@
"@deepseek-ai/dsh-commands": "workspace:^",
"@deepseek-ai/dsh-credentials": "workspace:^",
"@deepseek-ai/dsh-goal": "workspace:^",
"@deepseek-ai/dsh-cordis-host-runner": "workspace:^",
"@deepseek-ai/dsh-host-plugin-inventory": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-agent-presets": "workspace:^",
@@ -80,6 +81,7 @@
"@deepseek-ai/dsh-commands": "workspace:^",
"@deepseek-ai/dsh-credentials": "workspace:^",
"@deepseek-ai/dsh-goal": "workspace:^",
"@deepseek-ai/dsh-cordis-host-runner": "workspace:^",
"@deepseek-ai/dsh-host-plugin-inventory": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-agent-presets": "workspace:^",
+51 -1
View File
@@ -3,6 +3,7 @@
import type { Context } from '@deepseek-ai/cordis'
import commandsRemote from '@deepseek-ai/dsh-commands/remote'
import goalsRemote from '@deepseek-ai/dsh-goal/remote'
import dynamicRemote from '@deepseek-ai/dsh-cordis-host-runner/remote'
import pluginInventoryRemote from '@deepseek-ai/dsh-host-plugin-inventory/remote'
import messageFeedbackRemote from '@deepseek-ai/dsh-message-feedback/remote'
import type { TypertClientRemote } from '@deepseek-ai/dsh-typert-protocol'
@@ -20,6 +21,7 @@ export type { ApiRemoteForwardedEvent } from '../types.ts'
// signatures `$on` hands to a listener, so a consumer reads the very
// declaration the Host emits rather than a flattened restatement of it.
export type {} from '@deepseek-ai/dsh-commands/types'
export type {} from '@deepseek-ai/dsh-cordis-host-runner/types'
export type {} from '@deepseek-ai/dsh-credentials/types'
export type {} from '@deepseek-ai/dsh-llm/types'
export type {} from '@deepseek-ai/dsh-agent-presets/types'
@@ -40,6 +42,50 @@ export type {
SubagentAddress, SubagentCatalog, JobView, ToolCallView, ToolEventView, ToolResultView,
WorkspaceId, WorkspaceView,
} from '@deepseek-ai/dsh-client-connection/client'
export type {} from '@deepseek-ai/dsh-api-gateway/client'
export type {} from '@deepseek-ai/dsh-cordis-host-runner/remote'
// The payload vocabulary of the selected namespaces, re-exported so a Client
// contribution can name what it sends and receives without importing a Host
// package: this assembly is the one place both planes legitimately meet.
export type {
ApprovalRequestId,
CordisHalfState,
CordisDynamicPackageId,
CordisDynamicPluginId,
CordisDynamicPluginRunId,
CordisDynamicRunMode,
CordisInspectMethodManifest,
CordisInspectPlatform,
CordisInspectProviderManifest,
CordisInspectProviderView,
CordisInspectQueryRequest,
CordisInspectQueryResolution,
CordisInspectQueryResolved,
CordisInspectRequestId,
CordisInspectResolveAck,
CordisRunDiagnostic,
CordisRunStatus,
DynamicCordisClientSource,
DynamicCordisHostHalfResult,
DynamicCordisInventoryRow,
DynamicCordisInvokeResult,
DynamicCordisPackage,
DynamicCordisRequestResolved,
DynamicCordisResolveAck,
DynamicCordisRetracted,
DynamicCordisRunRequest,
DynamicCordisRunResolution,
DynamicCordisRunAttempt,
DynamicCordisRunResponse,
DynamicCordisStopResponse,
DynamicCordisUndefineReceipt,
RequestRunOutcome,
} from '@deepseek-ai/dsh-cordis-host-runner/types'
// The JSON vocabulary those payloads are built from, re-exported for the same
// reason: a Client contribution names what it sends without importing a Host
// package, and this assembly is where both planes legitimately meet.
export type { JsonValue } from '@deepseek-ai/dsh-session/types'
declare module '@deepseek-ai/cordis' {
interface Context {
@@ -59,13 +105,17 @@ export const inject = ['remote']
export async function apply(ctx: Context): Promise<() => Promise<void>> {
const disposers: Array<() => Promise<void>> = []
try {
for (const contribution of [commandsRemote, goalsRemote, pluginInventoryRemote, messageFeedbackRemote]) {
for (const contribution of [
commandsRemote, goalsRemote, dynamicRemote, pluginInventoryRemote, messageFeedbackRemote,
]) {
disposers.push(await ctx.remote.$mount(contribution))
}
} catch (error) {
for (const dispose of disposers.reverse()) await dispose()
throw error
}
// Unwound in reverse mount order, so a namespace never outlives one mounted
// after it.
return async () => {
for (const dispose of disposers.reverse()) await dispose()
}
+1
View File
@@ -8,6 +8,7 @@ import { API_REMOTE_FORWARDED_EVENTS } from './remote-events.ts'
// makes the shape assertion below judge real signatures rather than an empty
// event vocabulary.
import type {} from '@deepseek-ai/dsh-commands/types'
import type {} from '@deepseek-ai/dsh-cordis-host-runner/types'
import type {} from '@deepseek-ai/dsh-credentials/types'
import type {} from '@deepseek-ai/dsh-llm/types'
import type {} from '@deepseek-ai/dsh-agent-presets/types'
@@ -18,6 +18,12 @@ export const API_REMOTE_FORWARDED_EVENTS = [
'agent-preset/selected',
'commands/change',
'credentials/updated',
'cordis/request-run',
'cordis/request-run-resolved',
'cordis/dynamic-package',
'cordis/dynamic-retract',
'cordis/inspect-query',
'cordis/inspect-query-resolved',
'llm/adapters-updated',
'settings/document-updated',
] as const
+2 -2
View File
@@ -51,7 +51,7 @@ describe.skipIf(!requiredArtifacts)('Goal Remote built LIB chain', () => {
const { Context } = cordis
const { default: AgentRegistry } = await import(urls.agent)
const connectionHost = await import(urls.connectionHost)
const { default: TypertGatewayService } = await import(urls.apiGatewayHost)
const { default: TypertRemoteService } = await import(urls.apiGatewayHost)
const { default: GoalService } = await import(urls.goal)
const { TYPERT } = await import(urls.goalTypert)
const { default: TypertRegistry } = await import(urls.registryHost)
@@ -70,7 +70,7 @@ describe.skipIf(!requiredArtifacts)('Goal Remote built LIB chain', () => {
await host.plugin({ inject: connectionHost.inject, apply: connectionHost.apply })
await host.plugin(TypertRegistry)
await host.plugin(AgentRegistry)
await host.plugin(TypertGatewayService)
await host.plugin(TypertRemoteService)
await host.plugin(GoalService)
host.typert.register(TYPERT)
@@ -22,6 +22,10 @@
},
{
"path": "../../credentials/credentials"
},
{
"path": "../../extensions/cordis-host-runner"
},
{
"path": "../../goal/goal"
+3
View File
@@ -37,6 +37,9 @@
{
"path": "../../session/session-persistence"
},
{
"path": "../../extensions/cordis-host-runner"
},
{
"path": "../../settings/settings"
},
+9
View File
@@ -98,6 +98,9 @@
- id: api-gateway
name: '@deepseek-ai/dsh-host-apiproxy'
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
# Ordinary provider for the parsed Web flags. Its plugin-level injection
# waits for cmdlineArgs; no launcher metadata or special row kind is needed.
- id: web-startup
@@ -164,6 +167,9 @@
- id: client-runtime
name: '@deepseek-ai/dsh-client-runtime'
- id: cordis-client-runner
name: '@deepseek-ai/dsh-cordis-client-runner'
- id: ui-theme
name: '@deepseek-ai/dsh-client-ui-theme'
@@ -195,6 +201,9 @@
- id: ui-tool
name: '@deepseek-ai/dsh-client-ui-tool'
- id: ui-cordis
name: '@deepseek-ai/dsh-client-ui-cordis'
# Durable workflow lifecycle as an independent Chat node after the
# existing generic workflow tool row.
- id: ui-workflow-run
+3
View File
@@ -55,6 +55,7 @@
"@deepseek-ai/dsh-client-ui-agent-preset": "workspace:^",
"@deepseek-ai/dsh-client-ui-commands": "workspace:^",
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
"@deepseek-ai/dsh-client-ui-cordis": "workspace:^",
"@deepseek-ai/dsh-client-ui-deliverables": "workspace:^",
"@deepseek-ai/dsh-client-ui-directory-picker-browse": "workspace:^",
"@deepseek-ai/dsh-client-ui-directory-picker-native": "workspace:^",
@@ -82,6 +83,8 @@
"@deepseek-ai/dsh-client-ui-workspace": "workspace:^",
"@deepseek-ai/dsh-cmdline": "workspace:^",
"@deepseek-ai/dsh-code-runtime-worker-thread": "workspace:^",
"@deepseek-ai/dsh-cordis-client-runner": "workspace:^",
"@deepseek-ai/dsh-cordis-host-runner": "workspace:^",
"@deepseek-ai/dsh-web-frontend": "workspace:^",
"@deepseek-ai/dsh-host-frontend-static": "workspace:^",
"@deepseek-ai/dsh-host-apiproxy": "workspace:^",
+54 -2
View File
@@ -18,13 +18,26 @@ import { Service } from '@deepseek-ai/cordis'
import type { Context } from '@deepseek-ai/cordis'
import { SlotCore } from '@deepseek-ai/dsh-client-ui-slots'
import type {
LocaleFace, OwnerOf, SlotEntryDef, SlotMap, SlotRenderer, SlotRendererHost,
LiveSlotNode, LocaleFace, OwnerOf, SlotEntryDef, SlotMap, SlotRenderer, SlotRendererHost,
SlotScope, SlotSpec, StoreDecl, StoreFactory, StoredEntry, StoreInstanceLike,
} from '@deepseek-ai/dsh-client-ui-slots'
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface SlotMap {
/** The built-in render-tree root hole (seeded by SlotCore): rendered only by the shell, occupied by a layout entry. */
/**
* The built-in render-tree root hole (seeded by SlotCore): the one slot the
* shell itself renders, and the ancestor of every other seat. OCCUPIED by
* ui-layout's AppFrame, which declares the sidebar, conversation, details,
* and shell.overlay seats inside it.
*
* DO NOT register here. This is a single slot, so a second entry does not
* sit beside the frame — it shadows it, and a dynamically registered entry
* is assigned a lower priority than the shipped one, which makes it the
* winner: the page would render your component alone, with every seat the
* frame declares gone. For a surface of your own that floats over the whole
* app, register into `shell.overlay` instead (a list slot: additive, and
* click-through until your entry opts into pointer events).
*/
'root': { kind: 'single'; scope: 'root'; owner: RootOwnerProps }
}
}
@@ -274,6 +287,43 @@ export class SlotRegistry extends Service {
return this._core.entries(key)
}
/**
* Shadowing winners per cell for a key: the first live (non-abdicated)
* entry of each cell in priority order — what outlets render; chain keys
* pass through unchanged (election consumes every entry). The raw
* {@link SlotsService.entries} view stays the inspection surface. Fresh
* array per call, not a uSES getSnapshot source.
* @param key - SlotMap key.
* @returns the winning entry per occupied cell.
*/
entriesOfSlot(key: keyof SlotMap & string): readonly StoredEntry[] {
return this._core.entriesOfSlot(key)
}
/**
* Export the current JSON-safe Slot declaration tree for read-only inspection.
* @param root - exact live Slot root; omitted returns all roots.
* @returns selected Slot trees.
*/
snapshot(root?: string): LiveSlotNode[] {
return this._core.snapshot(root)
}
/**
* Observe entry boundary crashes (every render-time entry failure the
* boundaries contain, abdicating or not) — the supervision seam for
* plugins mirroring contribution health. Fires synchronously per report,
* after the registry mutated for abdicating crashes. Callers own the
* disposer (wire it through ctx.effect for fiber-lifetime cleanup, as with
* {@link SlotsService.subscribe}).
* @param fn - called with the slot key, the crashed entry, the crash
* cause, and `abdicated`: whether the crash retired the entry from its cell.
* @returns unsubscribe.
*/
onEntryError(fn: (key: string, entry: StoredEntry, error: unknown, info: { abdicated: boolean }) => void): () => void {
return this._core.onEntryError(fn)
}
/**
* Look up a declared spec (register-declared or the built-in 'root').
* @param key - SlotMap key.
@@ -353,6 +403,8 @@ export class SlotRegistry extends Service {
subscribe: (key, fn) => this._core.subscribe(key, fn),
getVersion: key => this._core.getVersion(key),
entriesOf: key => this._core.entries(key),
entriesOfSlot: key => this._core.entriesOfSlot(key),
reportEntryError: (key, entry, error, info) => { this._core.reportEntryError(key, entry, error, info) },
specOf: key => this._core.specDynamic(key),
isLive: entry => this._core.isLive(entry),
storeOf: (entry, scopeKey) =>
@@ -33,16 +33,32 @@ export interface ComposerAttachment {
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface SlotMap {
/**
* Strict-session body inside the resident conversation scrollport. It
* owns the per-session draft mirror and active view ring.
* The entire body of one session: taking this seat means rendering that
* session's conversation yourself. The occupant also owns the per-session
* draft mirror and the active view ring, so a replacement inherits both
* duties and an empty one leaves a blank session pane — nothing here
* degrades gracefully. To ADD rather than replace, take a seat inside the
* flow instead: `conversation.view` for a whole tab, the input regions for
* composer chrome.
*/
'conversation.session': { kind: 'single'; scope: 'session' }
/** Strict-session header above the resident conversation scrollport. */
/**
* The strip above the session's scrollport: title, view tabs, and the
* action row. Taking this seat means rendering all three yourself, and it
* also collapses `conversation.session.header.actions` — that additive
* seat is declared by whoever occupies this one, so replacing the header
* takes every action entry down with it.
*/
'conversation.session.header': { kind: 'single'; scope: 'session' }
/**
* Session-header actions contributed by feature plugins. Entries render
* by ascending `order`; negative values are reserved for static session
* context that precedes interactive actions.
* One button in the session header's action row — the additive way to put
* a per-session control beside the title without replacing the header.
* Entries render by ascending `order`; negative values are reserved for
* static session context that precedes interactive actions. The owner
* passes nothing: everything a control needs comes from the framework
* session kit (`sessionId`, `useSession`, `useInput`, `inputActions`) and
* from the registrant's own inject face, so an empty owner share means
* self-sufficient, not starved.
*/
'conversation.session.header.actions': { kind: 'list'; scope: 'session'; owner: ConversationHeaderActionOwnerProps }
/**
@@ -95,7 +111,16 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
scope: 'session'
owner: AssistantActionOwnerProps
}
/** Selected Tool call output inside the details panel. */
/**
* The body of the details panel for the tool call the user selected —
* one occupant, so taking it means rendering every tool's output, not just
* the ones you know. The owner passes a frozen `block` whose two lifecycle
* forms must both be handled: branch on `'kind' in block` (a settled
* `ToolResultNode` has it, a still-running call does not), and treat
* `cwd` as display-only, for shortening workspace-rooted paths.
* A per-tool renderer belongs in the keyed `tool.call.toolview` seat
* instead; this one is the whole panel.
*/
'conversation.details.tool': { kind: 'single'; scope: 'session'; owner: DetailsToolOwnerProps }
/**
* The composer takeover chain: entries are selector-routed replacements
@@ -124,15 +149,41 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
// ui-input-trigger, so the type arrives transitively). The runtime declaration
// (children table in apply.ts) stays here with the other input slots.
/**
* Stacked strip above the input (queue rows / GoalBar / attachments;
* entries coexist in fixed order).
* A full-width row of its own, stacked above the composer card — the seat
* for anything that needs a line to itself (queue rows, a todo strip, a
* goal bar). Pick this over the three seats below when your content wraps
* or carries prose; pick `conversation.composer.dock` for an ambient
* readout under the card, and `conversation.input.left` /
* `.right` for a small control INSIDE the card's tool row.
* Read only `session`/`input` off the owner share ({@link InputZone}) —
* both are point-in-time snapshots re-rendered for you, never subscribe.
*/
'conversation.input.dock': { kind: 'list'; scope: 'session'; owner: InputZone }
/** The band under the composer card (stats line family), rendered inside the bar's width column via the `footer` owner prop. */
/**
* The band under the composer card, inside the bar's width column — the
* seat for an ambient readout about the conversation (the shipped stats
* line lives here). Same {@link InputZone} owner share as the other
* regions. Anything the user must click belongs in the tool row instead
* (`conversation.input.left` / `.right`); anything needing its own line
* above the card belongs in `conversation.input.dock`.
*/
'conversation.composer.dock': { kind: 'list'; scope: 'session'; owner: InputZone }
/** Tool-row left region inside the input card (existing chrome stays in place beside entries). */
/**
* The left end of the tool row INSIDE the composer card, after the
* resident chrome (access mode, plan, attach) — the seat for a small
* always-visible control. Entries sit beside that chrome, never replace
* it. Same {@link InputZone} owner share; use `.right` for a control that
* belongs next to the send button, and the docks for anything taller than
* one row.
*/
'conversation.input.left': { kind: 'list'; scope: 'session'; owner: InputZone }
/** Tool-row right region inside the input card. */
/**
* The right end of the same tool row, before the primary send button —
* the seat for a control the user reaches on the way to sending (the
* model select sits in its own named seat just left of here). Same
* {@link InputZone} owner share and the same one-row height budget as
* `conversation.input.left`.
*/
'conversation.input.right': { kind: 'list'; scope: 'session'; owner: InputZone }
/**
* The default composer body: a single slot rendered as the composer
@@ -149,15 +200,23 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
*/
'conversation.composer.bar': { kind: 'single'; scope: 'session-maybe'; owner: ComposerBarOwnerProps }
/**
* The Plan-mode status seat in the composer tool row (left group,
* right of the access-mode control). Declared by the composer-bar
* entry; empty until a plan plugin registers (no placeholder
* fallback).
* The named plan-status seat in the composer tool row, immediately right
* of the access-mode control — one occupant, so taking it means rendering
* the plan affordance yourself. The owner passes only `locked` (see
* {@link InputControlOwnerProps}): honour it by refusing interaction, and
* take everything else from the framework session kit or your own inject.
* Unoccupied, the seat renders nothing at all — the bar paints no
* placeholder, so an absent plan plugin costs no layout.
*/
'conversation.input.plan': { kind: 'single'; scope: 'session'; owner: InputControlOwnerProps }
/**
* The model-select seat in the composer tool row (right group). Same
* empty-until-registered contract as the plan seat.
* The named model-select seat at the right end of the composer tool row,
* left of the send button — one occupant, so taking it means rendering the
* whole model affordance yourself. Same `locked`-only owner share and same
* renders-nothing-while-empty contract as the plan seat. Note the composer
* deliberately keeps this seat LIVE while it refuses text for a
* model-related block: every such block is one the user clears by picking
* a model here.
*/
'conversation.input.model': { kind: 'single'; scope: 'session'; owner: InputControlOwnerProps }
}
@@ -14,14 +14,14 @@ export interface ViewTab { id: string; label: string }
/**
* Per-session state shared by conversation, chat-view, and details slots.
* Unknown persisted view ids fall back to the first registered view.
* Unknown persisted view ids fall back to the stable Chat view.
*/
export interface ChatStoreState {
/** Details-linkage channel (conversation writes, details reads). */
selection: SelectionTarget | null
/** Composer draft (persisted; survives session switches and reloads). */
draft: string
/** Active conversation view id ('conversation.view' entry id); null falls back to the first view. */
/** Active conversation view id ('conversation.view' entry id); null falls back to Chat. */
view: string | null
/**
* One-shot inspect handoff: chat writes the call to reveal, the trajectory
@@ -280,7 +280,7 @@
overflow-y: auto;
}
.scrollBody:has([data-conversation-composer-overlay]) > .viewArea {
.scrollBody:has([data-conversation-composer-overlay]) > :global([data-slot='conversation.session']) > .viewArea {
flex: 1 1 0;
min-height: 0;
overflow: hidden;
@@ -6,6 +6,7 @@ import type { SessionId, SessionListState, SessionSummary } from '@deepseek-ai/d
import type {
ConversationSessionHeaderSlotProps, ConversationSessionSlotProps,
} from '../contract/slots.ts'
import type { ViewTab } from '../contract/views.ts'
import css from './ConversationRoot.module.css'
/** Full props composed from the strict session body contract. */
@@ -19,6 +20,15 @@ interface Breadcrumb {
readonly displayTitle: string
}
const DEFAULT_VIEW_ID = 'chat'
/** Resolve by id and keep stale persisted selections on the stable Chat fallback. */
function resolveActiveView(tabs: readonly ViewTab[], selectedId: string | null): ViewTab | undefined {
const requestedId = selectedId ?? DEFAULT_VIEW_ID
return tabs.find(view => view.id === requestedId)
?? tabs.find(view => view.id === DEFAULT_VIEW_ID)
}
function deriveAncestry(list: SessionListState, id: SessionId): readonly Breadcrumb[] {
const chain: Breadcrumb[] = []
const seen = new Set<SessionId>()
@@ -54,8 +64,8 @@ export function ConversationSessionHeader({
}: ConversationSessionHeaderProps) {
useSyncExternalStore(views.subscribe, views.version)
const tabs = views.list()
const activeId = useStore(s => s.view) ?? 'chat'
const active = tabs.find(view => view.id === activeId) ?? tabs[0]
const selectedId = useStore(s => s.view)
const active = resolveActiveView(tabs, selectedId)
const ancestry = useSessions(s => deriveAncestry(s, sessionId), equalBreadcrumbs)
const composerPhase = useSession(s => s.composerPhase)
const blank = useSession(s => s.blank)
@@ -131,8 +141,8 @@ export function ConversationSession({
}: ConversationSessionProps) {
useSyncExternalStore(views.subscribe, views.version)
const tabs = views.list()
const activeId = useStore(s => s.view) ?? 'chat'
const active = tabs.find(view => view.id === activeId) ?? tabs[0]
const selectedId = useStore(s => s.view)
const active = resolveActiveView(tabs, selectedId)
const composerPhase = useSession(s => s.composerPhase)
const blank = useSession(s => s.blank)
const inputState = useInput(s => s)
@@ -27,6 +27,7 @@ import type { InputBarProps } from '../src/client/skeleton/InputBar.tsx'
import type {
ComposerBarOwnerProps,
} from '../src/client/contract/slots.ts'
import type { ViewTab } from '../src/client/contract/views.ts'
/** Machine-backed wiring over a sink spy. */
function fakeWiring() {
@@ -96,6 +97,8 @@ function mount(
summaryOrigin?: 'subagent'
/** A composer block another plugin raised for this session. */
composerBlock?: { reason: string }
/** Mutable view ledger used by registration-order regressions. */
viewTabs?: ViewTab[]
} = {},
) {
const root = sid('root')
@@ -123,6 +126,15 @@ function mount(
const stop = vi.fn()
const open = vi.fn()
const slotCalls: string[] = []
const viewTabs = options.viewTabs ?? [
{ id: 'chat', label: 'Chat' },
{ id: 'trajectory', label: 'Trajectory' },
]
const views = {
list: () => viewTabs,
subscribe: () => () => {},
version: () => 1,
}
/** Owner share handed to the two composer tool-row seats, per render. */
const seatOwners: { key: string; owner: unknown }[] = []
let pickerOwner: unknown
@@ -146,14 +158,7 @@ function mount(
useStore={bindSnapshotSelector(chat)}
actions={chat.actions}
renderSlot={renderSlot as never}
views={{
list: () => [
{ id: 'chat', label: 'Chat' },
{ id: 'trajectory', label: 'Trajectory' },
],
subscribe: () => () => {},
version: () => 1,
}}
views={views}
open={open}
t={t}
/>
@@ -173,14 +178,7 @@ function mount(
useStore={bindSnapshotSelector(chat)}
actions={chat.actions}
renderSlot={renderSlot as never}
views={{
list: () => [
{ id: 'chat', label: 'Chat' },
{ id: 'trajectory', label: 'Trajectory' },
],
subscribe: () => () => {},
version: () => 1,
}}
views={views}
releaseSessionImages={vi.fn()}
bindDraftMirror={write => wiring.bindMirror(write)}
/>
@@ -442,6 +440,26 @@ describe('ConversationRoot resident composer', () => {
expect(b.view.getByRole('textbox')).toBeTruthy()
})
it('keeps the Chat fallback selected by id when a view is inserted before it', () => {
const viewTabs: ViewTab[] = [
{ id: 'chat', label: 'Chat' },
{ id: 'trajectory', label: 'Trajectory' },
]
const b = mount(conversationSnapshot(), undefined, undefined, { viewTabs })
// A removed dynamic view leaves its persisted id behind. The visible
// fallback is Chat and must stay Chat when another lower-order view lands.
act(() => { b.chat.actions.setView('removed-view') })
expect(b.view.getByTestId('view-chat')).toBeTruthy()
viewTabs.unshift({ id: 'new-view', label: 'New view' })
b.rerender()
expect(b.view.getByTestId('view-chat')).toBeTruthy()
expect(b.view.queryByTestId('view-new-view')).toBeNull()
expect(b.view.getByRole('tab', { name: 'Chat' }).getAttribute('aria-selected')).toBe('true')
expect(b.view.getByRole('tab', { name: 'New view' }).getAttribute('aria-selected')).toBe('false')
})
it('rolls the pending workspace label back when switching fails', async () => {
const selectWorkspace = vi.fn(async () => { throw new Error('connect failed') })
const b = mount(
@@ -10,8 +10,16 @@ import type { InputTriggerController } from './controller.ts'
/** The `ctx.inputTriggers` service face. */
export interface InputTriggerServiceContract {
/** Register one trigger source; effect disposer. Duplicate (trigger, name) throws. */
/**
* Register one trigger source; duplicate trigger/name pairs throw.
* @param src - source that discovers and resolves slash or reference candidates.
* @returns effect disposer removing this source.
*/
registerSource(src: InputTriggerSource): () => void
/** Resolve the per-session controller for one session scope (lazy; dies with the scope). */
/**
* Resolve the lazy controller owned by one session scope.
* @param actx - session-scoped Client context.
* @returns controller that dies with that scope.
*/
sessionOf(actx: ClientContext): InputTriggerController
}
@@ -106,3 +106,14 @@
background: var(--dsw-alias-button-floating-hover);
border-color: var(--dsw-alias-border-l3);
}
.overlayLayer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
}
.overlayLayer > * {
pointer-events: auto;
}
@@ -20,7 +20,7 @@ import css from './AppFrame.module.css'
/** Full composed props: runtime share + child-slot render share + store share. */
export type AppFrameProps =
& PropsRuntime<'root'>
& PropsRenderSlots<'sidebar' | 'conversation' | 'details'>
& PropsRenderSlots<'sidebar' | 'conversation' | 'details' | 'shell.overlay'>
& PropsStore<ReturnType<typeof createLayoutStore>>
/** Center column grid item (session-body building block). */
@@ -190,6 +190,9 @@ export function AppFrame({
<CenterColumn>{renderSlot('conversation', {})}</CenterColumn>
<DetailsColumn>{renderSlot('details', {})}</DetailsColumn>
</>
<div className={css.overlayLayer} data-shell-overlay>
{renderSlot('shell.overlay', {})}
</div>
{/* The collapsed rail is fixed-width: no resize handle while closed. */}
{!sidebarCollapsed && <DragHandle side="sidebar" left={cols.sidebar} onStart={onSidebarStart} onDrag={onSidebarDrag} onEnd={onDragEnd} />}
{cols.details > 0 && <DragHandle side="details" left={viewport - cols.details} onStart={onDetailsStart} onDrag={onDetailsDrag} onEnd={onDragEnd} />}
+43 -2
View File
@@ -36,11 +36,51 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
// there); these four are the frame's children, declared by the same
// register() call that contributes AppFrame. Session owners never pass
// sessionId: the framework injects it as a standard prop.
/**
* The whole left column. OCCUPIED by ui-sidebar's SidebarRoot, which
* declares the workspace and settings seats inside it — registering here
* replaces the navigation column outright rather than adding to it, and
* the seats it declares disappear with it. To add something to the
* sidebar, register into one of those inner seats instead.
*
* The occupant receives the frame's live column state (collapsed, width)
* and is expected to render the compact control rail while collapsed.
*/
'sidebar': { kind: 'single'; scope: 'root'; owner: SidebarOwnerProps }
// Current-session-optional: the occupant owns both the no-session hero
// and live conversation states without changing its React identity.
/**
* The whole center column, across both the no-session hero and a live
* conversation. OCCUPIED by ui-conversation's ConversationRoot, which
* declares the session body, composer, and input seats inside it —
* registering here replaces the entire conversation surface (and removes
* every seat it declares) rather than adding to it.
*
* Current-session-optional: the occupant owns both states without
* changing its React identity, so it keeps its own state across a session
* switch. It receives no owner props; session facts arrive through the
* framework hooks of the `session-maybe` scope.
*/
'conversation': { kind: 'single'; scope: 'session-maybe'; owner: ConvOwnerProps }
/**
* The right details column, shown when the layout opens it. OCCUPIED by
* ui-conversation's DetailsPanel, which declares the tool-details seat
* inside it — registering here replaces the column and takes that seat
* with it. Absent an occupant the column renders nothing.
*
* No owner props: the framework injects the session id and hooks for the
* `session` scope, and `ctx.layout` owns whether the column is open.
*/
'details': { kind: 'single'; scope: 'session'; owner: DetailsOwnerProps }
/**
* Frame-wide floating layer, above every column and outside their scroll
* containers. Deliberately generic and unowned by any feature: a badge, a
* toast stack or a status pill all belong here, and entries order among
* themselves. The layer itself is click-through — entries opt back into
* pointer events — so an occupant never blocks the app underneath.
*
* This is the additive seat for a frame-wide surface of your own: a fresh
* `id` is added beside the shipped entries instead of replacing them.
*/
'shell.overlay': { kind: 'list'; scope: 'root' }
}
}
@@ -83,6 +123,7 @@ export function apply(ctx: ClientContext): void {
'sidebar': { kind: 'single', scope: 'root' },
'conversation': { kind: 'single', scope: 'session-maybe' },
'details': { kind: 'single', scope: 'session' },
'shell.overlay': { kind: 'list', scope: 'root' },
},
// Exclusive store: the factory itself — the framework instantiates per
// entry and delivers useStore/actions to AppFrame as standard props.
@@ -601,6 +601,24 @@ export const IconCodeOutline16 = ({ size = 16, className }: IconProps) => (
</svg>
)
/** ic_ds_cordis_plugin_outline_14 */
export const IconCordisPluginOutline14 = ({ size = 14, className }: IconProps) => (
<svg width={size} height={size} className={className} viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1840_45990)">
<path
d="M3.03426 5.66661L1.70084 7.00003L3.0315 8.33069L2.14762 9.21457L-0.0669245 7.00003L2.15038 4.78273L3.03426 5.66661ZM7 14.067L4.77924 11.8462L5.66313 10.9623L7 12.2992L8.33342 10.9658L9.2173 11.8496L7 14.067ZM11.8489 9.21803L10.965 8.33414L12.2992 7.00003L10.9623 5.66316L11.8462 4.77927L14.0669 7.00003L11.8489 9.21803ZM8.33066 3.03153L7 1.70087L5.66589 3.03498L4.782 2.1511L7 -0.0668945L9.21454 2.14765L8.33066 3.03153Z"
fill="currentColor"
/>
<rect x="5.98535" y="5.98535" width="2.02942" height="2.02942" fill="currentColor" />
</g>
<defs>
<clipPath id="clip0_1840_45990">
<rect width="14" height="14" fill="currentColor" />
</clipPath>
</defs>
</svg>
)
/** ic_ds_api_outline (figma extract) */
export const IconApiOutline14 = ({ size = 14, className }: IconProps) => (
<svg width={size} height={size} className={className} viewBox="0 0 14 14" fill="none">
@@ -16,8 +16,8 @@ const icons = Object.fromEntries(
const iconNames = Object.keys(icons)
describe('ic_ds_ icon set', () => {
it('exports the full icon set (46 deepsuite + 20 figma extracts + three product glyphs outside those sets)', () => {
expect(iconNames.length).toBe(69)
it('exports the full icon set (46 deepsuite + 20 figma extracts + four product glyphs outside those sets)', () => {
expect(iconNames.length).toBe(70)
})
it.each(iconNames)('%s renders an svg with currentColor fills and no hardcoded palette', (name) => {
@@ -7,6 +7,6 @@
width: 100%;
}
.section > :last-child {
.section > :global([data-slot='settings.general.item']) > :last-child {
border-bottom: none;
}
@@ -74,14 +74,18 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
*/
'settings.onboarding': { kind: 'list'; scope: 'root'; owner: SettingsOnboardingOwnerProps }
/**
* One preference row inside the General section, contributed by the
* feature plugin that owns the preference (locale → Language, ui-theme →
* Appearance, ui-conversation → Composer Enter). Options: `id` (row key),
* `order` (row position). Rows draw their own internals; the section
* column only stacks them. Declared at runtime by ui-settings-general's
* General entry — the type lives here with every other settings slot type,
* because this package is the settings domain's base layer and every
* registrant already depends on it for `ctx.settingsScope`.
* One preference row inside the General section — the additive seat for a
* single setting that needs no page of its own (a whole page is
* `settings.section`), contributed by the feature plugin that owns the
* preference (locale → Language, ui-theme → Appearance, ui-conversation
* Composer Enter). Options: `id` (row key), `order` (row position). The
* section column only stacks rows, so a row draws its own internals,
* including its label: nothing projects a `label` here and the owner passes
* no props at all — copy, current value, and the write path are all yours,
* through your own inject face and `host.call`. Declared at runtime by
* ui-settings-general's General entry; the type lives here with every other
* settings slot type, because this package is the settings domain's base
* layer and every registrant already depends on it for `ctx.settingsScope`.
*/
'settings.general.item': { kind: 'list'; scope: 'root'; owner: SettingsGeneralItemOwnerProps }
}
@@ -227,11 +227,34 @@
padding-left: 0;
}
/* Foot seat: a pure layout socket pinned under the region; the ui-settings
trigger row inside owns its own geometry (38px wide row / 36px rail
circle) and hover chrome. */
/* Footer seats: additive actions stack above Settings. Each occupant owns its
button geometry and hover chrome. */
.footArea {
flex: none;
display: flex;
flex-direction: column;
}
.settingsArea,
.footerActions {
flex: none;
min-width: 0;
width: 100%;
}
.footerActions {
display: flex;
}
.collapsed .footArea {
align-items: center;
}
.collapsed .settingsArea,
.collapsed .footerActions {
display: flex;
justify-content: center;
width: auto;
}
@media (prefers-reduced-motion: reduce) {
@@ -6,7 +6,7 @@
* snap to the 56px rail (one icon each, same top-down order) fading in as the
* slide ends. The workspace/session browsing region between the New Session
* button and the foot is the `sidebar.workspaces` registrant's, and the foot
* is the `sidebar.settings` registrant's; the shell hands them the wide flag
* holds `sidebar.settings` plus `sidebar.footer.action`; the shell hands them the wide flag
* (plus an expand request callback for the browser).
*
* The column also owns whether the scroll regions nested in it draw a
@@ -177,9 +177,14 @@ export function SidebarRoot({
})}
</div>
{/* Foot seat: ui-settings registers the trigger row + panel here. */}
{/* Footer actions stack above Settings in both sidebar widths. */}
<div className={css.footArea}>
{renderSlot('sidebar.settings', { wide })}
<div className={css.footerActions}>
{renderSlot('sidebar.footer.action', { wide })}
</div>
<div className={css.settingsArea}>
{renderSlot('sidebar.settings', { wide })}
</div>
</div>
</div>
)
@@ -4,7 +4,8 @@
* owns column geometry (fold state machine, brand row, New Session);
* everything between the section header and the list bottom is the
* `sidebar.workspaces` registrant's (ui-workspace), and the foot is the
* `sidebar.settings` registrant's (ui-settings).
* `sidebar.settings` registrant's (ui-settings), followed by optional footer
* actions in `sidebar.footer.action`.
*/
import type { PropsLocale, PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
// Type-only: pulls ui-layout's SlotMap merge (the 'sidebar' entry) into every
@@ -27,6 +28,11 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
* The sidebar passes only its column state — it holds no settings state.
*/
'sidebar.settings': { kind: 'single'; scope: 'root'; owner: SidebarSettingsOwnerProps }
/**
* Optional actions beside Settings at the sidebar foot. Declared by this
* package's 'sidebar' entry; each action receives only the column state.
*/
'sidebar.footer.action': { kind: 'list'; scope: 'root'; owner: SidebarFooterActionOwnerProps }
}
}
@@ -50,6 +56,12 @@ export interface SidebarSettingsOwnerProps {
wide: boolean
}
/** Owner share of an action rendered beside Settings at the sidebar foot. */
export interface SidebarFooterActionOwnerProps {
/** Whether the sidebar renders wide content (false = 56px rail). */
wide: boolean
}
/**
* Registrant-private injected share (arrives via the register inject
* factory). The shell keeps only its own controls: starting a Session from
@@ -72,5 +84,6 @@ export type SidebarRootInjected = {
* seat. No store is registered.
*/
export type SidebarRootComponentProps =
PropsRuntime<'sidebar'> & PropsRenderSlots<'sidebar.workspaces' | 'sidebar.settings'>
PropsRuntime<'sidebar'>
& PropsRenderSlots<'sidebar.workspaces' | 'sidebar.settings' | 'sidebar.footer.action'>
& SidebarRootInjected & PropsLocale<'sidebar'>
@@ -6,7 +6,10 @@ import type { SidebarRootInjected } from './contract/slots.ts'
import { SidebarRoot } from './SidebarRoot.tsx'
import { en, zh, type SidebarKey } from './locales.ts'
export type { SidebarRootComponentProps, SidebarRootInjected, SidebarSectionOwnerProps, SidebarSettingsOwnerProps } from './contract/slots.ts'
export type {
SidebarFooterActionOwnerProps, SidebarRootComponentProps, SidebarRootInjected,
SidebarSectionOwnerProps, SidebarSettingsOwnerProps,
} from './contract/slots.ts'
export type { SidebarKey } from './locales.ts'
declare module '@deepseek-ai/dsh-client-ui-slots' {
@@ -44,6 +47,7 @@ export function apply(ctx: ClientContext): void {
children: {
'sidebar.workspaces': { kind: 'single', scope: 'root' },
'sidebar.settings': { kind: 'single', scope: 'root' },
'sidebar.footer.action': { kind: 'list', scope: 'root' },
},
inject: injectProps,
}, SidebarRoot),
@@ -3,6 +3,7 @@
exports[`sidebar shell snapshots > renders the collapsed rail after the crossfade settles, in place 1`] = `
<div
data-slot="sidebar"
style="display: contents;"
>
<div
class="root collapsed railIn quietBars"
@@ -52,10 +53,32 @@ exports[`sidebar shell snapshots > renders the collapsed rail after the crossfad
</button>
<div
class="regionArea"
/>
>
<div
data-slot="sidebar.workspaces"
style="display: contents;"
/>
</div>
<div
class="footArea"
/>
>
<div
class="footerActions"
>
<div
data-slot="sidebar.footer.action"
style="display: contents;"
/>
</div>
<div
class="settingsArea"
>
<div
data-slot="sidebar.settings"
style="display: contents;"
/>
</div>
</div>
</div>
</div>
`;
@@ -63,6 +86,7 @@ exports[`sidebar shell snapshots > renders the collapsed rail after the crossfad
exports[`sidebar shell snapshots > renders the expanded column (wordmark, capsule, empty holes) 1`] = `
<div
data-slot="sidebar"
style="display: contents;"
>
<div
class="root quietBars"
@@ -122,10 +146,32 @@ exports[`sidebar shell snapshots > renders the expanded column (wordmark, capsul
</button>
<div
class="regionArea"
/>
>
<div
data-slot="sidebar.workspaces"
style="display: contents;"
/>
</div>
<div
class="footArea"
/>
>
<div
class="footerActions"
>
<div
data-slot="sidebar.footer.action"
style="display: contents;"
/>
</div>
<div
class="settingsArea"
>
<div
data-slot="sidebar.settings"
style="display: contents;"
/>
</div>
</div>
</div>
</div>
`;
@@ -133,6 +179,7 @@ exports[`sidebar shell snapshots > renders the expanded column (wordmark, capsul
exports[`sidebar shell snapshots > renders the expanded column in the default locale (zh, no setLocale) 1`] = `
<div
data-slot="sidebar"
style="display: contents;"
>
<div
class="root quietBars"
@@ -192,10 +239,32 @@ exports[`sidebar shell snapshots > renders the expanded column in the default lo
</button>
<div
class="regionArea"
/>
>
<div
data-slot="sidebar.workspaces"
style="display: contents;"
/>
</div>
<div
class="footArea"
/>
>
<div
class="footerActions"
>
<div
data-slot="sidebar.footer.action"
style="display: contents;"
/>
</div>
<div
class="settingsArea"
>
<div
data-slot="sidebar.settings"
style="display: contents;"
/>
</div>
</div>
</div>
</div>
`;
@@ -31,11 +31,13 @@ describe('ui-sidebar apply', () => {
expect(inject).toEqual(['slots', 'layout', 'sessions', 'workspaces', 'locale'])
})
it('registers the shell and declares the browsing-region hole', async () => {
it('registers the shell and declares its child seats', async () => {
const b = await bench()
await b.ctx.plugin({ inject: [...inject], apply }).await()
expect(b.slots.entries('sidebar')).toHaveLength(1)
expect(b.slots.spec('sidebar.workspaces')).toEqual({ kind: 'single', scope: 'root' })
expect(b.slots.spec('sidebar.settings')).toEqual({ kind: 'single', scope: 'root' })
expect(b.slots.spec('sidebar.footer.action')).toEqual({ kind: 'list', scope: 'root' })
// Copy rides the standard locale seat, not the inject face.
expect(b.slots.entries('sidebar')[0]!.locale).toBe('sidebar')
const injected = (b.slots.entries('sidebar')[0]!.inject as () => SidebarRootInjected)()
@@ -61,5 +63,6 @@ describe('ui-sidebar apply', () => {
await fiber.dispose()
expect(b.slots.entries('sidebar')).toHaveLength(0)
expect(b.slots.spec('sidebar.workspaces')).toBeUndefined()
expect(b.slots.spec('sidebar.footer.action')).toBeUndefined()
})
})
@@ -1,7 +1,10 @@
// @vitest-environment jsdom
import { afterEach, describe, expect, it, vi } from 'vitest'
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import type { SidebarRootComponentProps, SidebarSectionOwnerProps, SidebarSettingsOwnerProps } from '../src/client/contract/slots.ts'
import type {
SidebarFooterActionOwnerProps, SidebarRootComponentProps, SidebarSectionOwnerProps,
SidebarSettingsOwnerProps,
} from '../src/client/contract/slots.ts'
import { SidebarRoot } from '../src/client/SidebarRoot.tsx'
import { en } from '../src/client/locales.ts'
@@ -23,17 +26,25 @@ function mountShell({ collapsed = false, width = 300 }: { collapsed?: boolean; w
const toggleSidebar = vi.fn()
let regionOwner: SidebarSectionOwnerProps | undefined
let settingsOwner: SidebarSettingsOwnerProps | undefined
let footerActionOwner: SidebarFooterActionOwnerProps | undefined
let current = { collapsed, width }
const root = () => (
<SidebarRoot
collapsed={current.collapsed} width={current.width}
useSessions={neverHook} useWorkspaces={neverHook}
startSession={startSession} toggleSidebar={toggleSidebar} t={t}
renderSlot={((key: string, owner: SidebarSectionOwnerProps | SidebarSettingsOwnerProps) => {
renderSlot={((
key: string,
owner: SidebarFooterActionOwnerProps | SidebarSectionOwnerProps | SidebarSettingsOwnerProps,
) => {
if (key === 'sidebar.settings') {
settingsOwner = owner
return <div data-testid="settings-seat" data-wide={owner.wide} />
}
if (key === 'sidebar.footer.action') {
footerActionOwner = owner
return <div data-testid="footer-action-seat" data-wide={owner.wide} />
}
regionOwner = owner as SidebarSectionOwnerProps
return <div data-testid="region" data-wide={owner.wide} />
}) as SidebarRootComponentProps['renderSlot']}
@@ -51,6 +62,10 @@ function mountShell({ collapsed = false, width = 300 }: { collapsed?: boolean; w
if (settingsOwner === undefined) throw new Error('settings owner not rendered')
return settingsOwner
},
footerActionOwner: () => {
if (footerActionOwner === undefined) throw new Error('footer action owner not rendered')
return footerActionOwner
},
rerender(next: Partial<typeof current>) {
current = { ...current, ...next }
view.rerender(root())
@@ -75,6 +90,7 @@ describe('SidebarRoot shell', () => {
expect(b.regionOwner().wide).toBe(true)
// The settings seat rides the same wide flag (ui-settings renders the row).
expect(b.settingsOwner().wide).toBe(true)
expect(b.footerActionOwner().wide).toBe(true)
// Expanded: the request is a no-op (no accidental collapse).
b.regionOwner().expandSidebar()
expect(b.toggleSidebar).not.toHaveBeenCalled()
@@ -89,6 +105,7 @@ describe('SidebarRoot shell', () => {
vi.advanceTimersByTime(200)
b.rerender({})
expect(b.regionOwner().wide).toBe(false)
expect(b.footerActionOwner().wide).toBe(false)
expect(screen.getByTestId('region')).toBeTruthy()
b.regionOwner().expandSidebar()
expect(b.toggleSidebar).toHaveBeenCalledOnce()
+227 -21
View File
@@ -473,21 +473,40 @@ export type InjectParams<K extends keyof SlotMap & string, H> =
*/
export type SlotLabel = string | (() => string)
/** Kind shape fields carried in register options (keyed dispatch key; list id/order/label; chain select/priority). */
/**
* Kind shape fields carried in register options (keyed dispatch key; list
* id/order/label; chain select/priority; non-chain priority = cell shadowing rank).
*/
export type KindOptions<
K extends keyof SlotMap & string,
EntryKey extends EntryKeyOf<K>,
M = never,
> =
SlotMap[K]['kind'] extends 'keyed' ? { key: EntryKey }
: SlotMap[K]['kind'] extends 'list' ? { id: string; order?: number; label?: SlotLabel }
SlotMap[K]['kind'] extends 'keyed' ? {
key: EntryKey
/** Cell shadowing rank (ascending, default 0, lowest renders; same key + same priority throws — see {@link SlotCore.register}). */
priority?: number
}
: SlotMap[K]['kind'] extends 'list' ? {
id: string
order?: number
label?: SlotLabel
/** Cell shadowing rank (ascending, default 0, lowest renders; same id + same priority throws — see {@link SlotCore.register}). */
priority?: number
}
: SlotMap[K]['kind'] extends 'chain' ? {
/** Routing selector, mandatory on chain entries; `M` (the component's `matched` prop) infers from its return. */
select: ChainSelect<SlotMap[K] extends { owner: infer O extends object } ? O : object, M>
/** Explicit chain position (ascending, default 0, lower tries first); ties keep registration = assembly order. */
priority?: number
}
: object
: {
/**
* Cell shadowing rank (ascending, default 0, lowest renders; a
* same-priority second registration throws — see {@link SlotCore.register}).
*/
priority?: number
}
/**
* Compile-time presence check: an entry declaring children MUST consume
@@ -596,6 +615,8 @@ interface SlotRecord {
spec: SlotSpec<SlotEntryDef> | undefined
/** Diagnostics: which slot's entry declared this key ('(built-in)' for root). */
declaredBy: string | undefined
/** Live parent declaration, absent for root slots. */
parent: string | undefined
/** Monotonic declaration lifetime, distinct from ordinary entry mutations. */
declarationEpoch: number
entries: readonly StoredEntry[]
@@ -606,6 +627,38 @@ interface SlotRecord {
const NO_ENTRIES: readonly StoredEntry[] = Object.freeze([])
/** JSON-safe live occupant returned by slot inspection. */
export interface LiveSlotOccupant {
/** Plugin or package that registered the entry, when known. */
registrant?: string
/** Keyed-slot cell. */
key?: string
/** List-slot cell. */
id?: string
/** List display order. */
order?: number
/** Shadowing or chain priority. */
priority: number
/** Whether the renderer currently selects this entry. */
active: boolean
}
/** JSON-safe live slot declaration tree. */
export interface LiveSlotNode {
/** Exact SlotMap key. */
name: string
/** Slot cardinality. */
kind: SlotKind
/** Runtime data scope. */
scope: SlotScope
/** Diagnostic owner of this declaration. */
declaredBy?: string
/** Current registrations in ledger order. */
occupants: LiveSlotOccupant[]
/** Slots declared by entries mounted in this slot. */
children: LiveSlotNode[]
}
/**
* Pure slot registry (no cordis; event emission and the renderer installation contract
* live in the runtime Service wrapper).
@@ -618,7 +671,9 @@ const NO_ENTRIES: readonly StoredEntry[] = Object.freeze([])
* fire); {@link SlotCore.subscribeDeclaration} fires synchronously for each
* declaration lifetime boundary; {@link SlotCore.subscribe} notifications
* batch per microtask, so N same-tick mutations produce one notification per
* touched key.
* touched key. Entry crash reports ({@link SlotCore.reportEntryError}) ride
* the same mutation channel when they abdicate, then notify
* {@link SlotCore.onEntryError} synchronously.
*/
export class SlotCore {
private records = new Map<string, SlotRecord>()
@@ -629,6 +684,16 @@ export class SlotCore {
// reference skips a lookup (and an unreachable missing-record branch) at flush.
private dirty = new Set<SlotRecord>()
private flushScheduled = false
/**
* Entries retired by an abdicating crash report
* ({@link SlotCore.reportEntryError}): excluded from
* {@link SlotCore.entriesOfSlot} projections for the rest of their
* registration's life, while the registration itself stays on the ledger
* (disposal authority remains with the registrant).
*/
private abdicated = new WeakSet<StoredEntry>()
private entryErrorListeners
= new Set<(key: string, entry: StoredEntry, error: unknown, info: { abdicated: boolean }) => void>()
constructor() {
// The a-priori root hole. No markDirty: nothing can observe construction.
@@ -646,11 +711,18 @@ export class SlotCore {
* re-checks nothing): registering into an undeclared slot throws; declaring
* an already-declared child key throws (one declarer per slot — the message
* names the first declarer); mounting one shared store handle under slots
* of different scopes throws. Kind constraints: single — duplicate
* registration throws; keyed — missing/duplicate `key` throws; list —
* missing/duplicate `id` throws; chain — missing `select` throws (the
* of different scopes throws. Kind constraints: keyed — missing `key`
* throws; list — missing `id` throws; chain — missing `select` throws (the
* selector is the entry's routing seat, see {@link ChainSelect}).
*
* Shadowing (single/keyed/list): entries sharing one cell (single — the
* slot itself; keyed — same `key`; list — same `id`) coexist at distinct
* priorities, sorted ascending with ties keeping registration order; the
* cell's lowest live entry renders ({@link SlotCore.entriesOfSlot}). A
* second registration at an occupied cell's exact priority (default 0)
* throws naming the occupant, so priority-less composition keeps the
* historical one-occupant-per-cell fail-loud.
*
* Lifecycle: the disposer removes the contribution AND collapses every
* declared child slot (child entries clear recursively; their stale
* disposers become no-ops) — one lifecycle axis, no dangling state.
@@ -719,23 +791,33 @@ export class SlotCore {
}
const spec = rec.spec
// Kind constraints stay runtime checks for dynamically-composed callers;
// typed callers already satisfied KindOptions statically.
// typed callers already satisfied KindOptions statically. Cell occupancy
// clashes only at the exact priority: a different priority shadows.
const priority = options.priority ?? 0
const occupantHint = (occupant: StoredEntry) =>
`at priority ${priority}${occupant.registrant !== undefined ? ` (registered by ${occupant.registrant})` : ''} — register at a different priority to shadow it (lowest renders)`
switch (spec.kind) {
case 'single':
if (rec.entries.length > 0) throw new Error(`single slot "${options.name}" already has a registration`)
case 'single': {
const occupant = rec.entries.find(e => (e.options.priority ?? 0) === priority)
if (occupant) throw new Error(`single slot "${options.name}" already has a registration ${occupantHint(occupant)}`)
break
case 'keyed':
}
case 'keyed': {
if (options.key === undefined) throw new Error(`keyed slot "${options.name}" requires options.key`)
if (rec.entries.some(e => e.options.key === options.key)) {
throw new Error(`keyed slot "${options.name}" already has an entry for key "${options.key}"`)
const occupant = rec.entries.find(e => e.options.key === options.key && (e.options.priority ?? 0) === priority)
if (occupant) {
throw new Error(`keyed slot "${options.name}" already has an entry for key "${options.key}" ${occupantHint(occupant)}`)
}
break
case 'list':
}
case 'list': {
if (options.id === undefined) throw new Error(`list slot "${options.name}" requires options.id`)
if (rec.entries.some(e => e.options.id === options.id)) {
throw new Error(`list slot "${options.name}" already has an entry with id "${options.id}"`)
const occupant = rec.entries.find(e => e.options.id === options.id && (e.options.priority ?? 0) === priority)
if (occupant) {
throw new Error(`list slot "${options.name}" already has an entry with id "${options.id}" ${occupantHint(occupant)}`)
}
break
}
case 'chain':
if (options.select === undefined) throw new Error(`chain slot "${options.name}" requires options.select`)
break
@@ -777,10 +859,13 @@ export class SlotCore {
...(options.registrant !== undefined ? { registrant: options.registrant } : {}),
}
const next = [...rec.entries, entry]
// Stable sorts: ascending, ties keep registration sequence (list rides
// `order`, chain rides `priority` — lower priority tries first).
if (spec.kind === 'list') next.sort((a, b) => (a.options.order ?? 0) - (b.options.order ?? 0))
if (spec.kind === 'chain') next.sort((a, b) => (a.options.priority ?? 0) - (b.options.priority ?? 0))
// Stable sorts: priority ascending for every kind, ties keep registration
// sequence — a cell's winner is its first occurrence, chain tries lower
// priority first. List refines equal priorities by explicit `order` so the
// raw ledger keeps its display sequence for priority-less compositions.
next.sort(spec.kind === 'list'
? (a, b) => ((a.options.priority ?? 0) - (b.options.priority ?? 0)) || ((a.options.order ?? 0) - (b.options.order ?? 0))
: (a, b) => (a.options.priority ?? 0) - (b.options.priority ?? 0))
rec.entries = next
this.markDirty(options.name, rec)
if (options.children) {
@@ -789,6 +874,7 @@ export class SlotCore {
const childRec = this.record(childKey)
childRec.spec = childSpec
childRec.declaredBy = `an entry in "${options.name}"${options.registrant ? ` (${options.registrant})` : ''}`
childRec.parent = options.name
childRec.declarationEpoch += 1
declarations.push([childKey, childRec])
}
@@ -835,6 +921,36 @@ export class SlotCore {
return this.records.get(key)?.entries ?? NO_ENTRIES
}
/**
* Project a key's entries to its shadowing winners: the first live
* (non-abdicated) entry of each cell in priority order — single: the slot
* is one cell; keyed: one cell per `key`; list: one cell per `id` (winners
* keep ledger sequence; list renderers still refine display by `order`).
* Chain keys return the raw entries unchanged: election consumes every
* entry, shadowing does not apply. The raw {@link SlotCore.entries} view
* stays the inspection surface. Builds a fresh array per call — a render
* body read, not a uSES getSnapshot source.
* @param key - slot key (dynamic: the render machinery holds keys as strings).
* @returns the winning entry per occupied cell (empty while undeclared).
*/
entriesOfSlot(key: string): readonly StoredEntry[] {
const rec = this.records.get(key)
if (!rec?.spec) return NO_ENTRIES
const kind = rec.spec.kind
if (kind === 'chain') return rec.entries
const heads: StoredEntry[] = []
const seenCells = new Set<string | undefined>()
for (const entry of rec.entries) {
if (this.abdicated.has(entry)) continue
// Single-kind entries all share the one undefined cell.
const cell = kind === 'keyed' ? entry.options.key : kind === 'list' ? entry.options.id : undefined
if (seenCells.has(cell)) continue
seenCells.add(cell)
heads.push(entry)
}
return heads
}
/**
* Look up a slot's declared spec, narrowed by the SlotMap key.
* @param key - SlotMap key.
@@ -855,6 +971,53 @@ export class SlotCore {
return this.records.get(key)?.spec
}
/**
* Export the current declaration topology without components or executable hooks.
* @param root - exact Slot key to select; omitted returns every live root.
* @returns selected live Slot trees, or an empty array when `root` is unavailable.
*/
snapshot(root?: string): LiveSlotNode[] {
const build = (name: string, seen: Set<string>): LiveSlotNode | undefined => {
const record = this.records.get(name)
if (record?.spec === undefined || seen.has(name)) return undefined
const branch = new Set(seen)
branch.add(name)
const active = new Set(this.entriesOfSlot(name))
const children = [...this.records.entries()]
.filter(([, candidate]) => candidate.spec !== undefined && candidate.parent === name)
.flatMap(([child]) => {
const node = build(child, branch)
return node === undefined ? [] : [node]
})
return {
name,
kind: record.spec.kind,
scope: record.spec.scope,
...record.declaredBy === undefined ? {} : { declaredBy: record.declaredBy },
occupants: record.entries.map(entry => ({
...entry.registrant === undefined ? {} : { registrant: entry.registrant },
...entry.options.key === undefined ? {} : { key: entry.options.key },
...entry.options.id === undefined ? {} : { id: entry.options.id },
...entry.options.order === undefined ? {} : { order: entry.options.order },
priority: entry.options.priority ?? 0,
active: active.has(entry),
})),
children,
}
}
if (root !== undefined) {
const node = build(root, new Set())
return node === undefined ? [] : [node]
}
return [...this.records.entries()]
.filter(([, record]) => record.spec !== undefined
&& (record.parent === undefined || this.records.get(record.parent)?.spec === undefined))
.flatMap(([name]) => {
const node = build(name, new Set())
return node === undefined ? [] : [node]
})
}
/**
* Read the declaration lifetime of a key. Entry additions and removals do
* not change it; declaration creation and collapse each advance it.
@@ -916,6 +1079,47 @@ export class SlotCore {
return () => { this.mutateListeners.delete(fn) }
}
/**
* Renderer crash report from an entry boundary. Always notifies
* {@link SlotCore.onEntryError} listeners; with `info.abdicate` set (the
* shadowing kinds — single/keyed/list) it first retires the entry from its
* cell, one-shot: the record's version bumps through the ordinary mutation
* channel so outlets re-project onto the cell's next survivor, and a
* repeat abdicating report no-ops entirely. Chain crashes report with
* `abdicate: false` — election alternatives resolve at select time, so the
* entry keeps its cell and only the notification fires. The registration
* itself stays on the ledger either way — raw {@link SlotCore.entries}
* still lists the entry and its disposer keeps working.
* @param key - slot key the entry rendered under.
* @param entry - the crashed entry.
* @param error - the crash cause, forwarded to listeners verbatim.
* @param info - `abdicate`: whether the crash retires the entry from its cell.
*/
reportEntryError(key: string, entry: StoredEntry, error: unknown, info: { abdicate: boolean }): void {
if (info.abdicate) {
if (this.abdicated.has(entry)) return
this.abdicated.add(entry)
const rec = this.records.get(key)
if (rec !== undefined) this.markDirty(key, rec)
}
for (const fn of [...this.entryErrorListeners]) fn(key, entry, error, { abdicated: info.abdicate })
}
/**
* Observe entry boundary crashes (every render-time entry failure the
* boundaries contain, abdicating or not) — the supervision seam for hosts
* mirroring contribution health. Fires synchronously per report, after the
* registry mutated for abdicating crashes (same listener discipline as
* {@link SlotCore.onMutate}).
* @param fn - called with the slot key, the crashed entry, the crash
* cause, and `abdicated`: whether the crash retired the entry from its cell.
* @returns unsubscribe.
*/
onEntryError(fn: (key: string, entry: StoredEntry, error: unknown, info: { abdicated: boolean }) => void): () => void {
this.entryErrorListeners.add(fn)
return () => { this.entryErrorListeners.delete(fn) }
}
/**
* Cascade for a removed entry: release its store mount and collapse every
* child slot it declared — specs clear, contributions empty (their stale
@@ -935,6 +1139,7 @@ export class SlotCore {
const doomed = childRec.entries
childRec.spec = undefined
childRec.declaredBy = undefined
childRec.parent = undefined
childRec.declarationEpoch += 1
childRec.entries = NO_ENTRIES
this.markDirty(childKey, childRec)
@@ -949,6 +1154,7 @@ export class SlotCore {
rec = {
spec: undefined,
declaredBy: undefined,
parent: undefined,
declarationEpoch: 0,
entries: NO_ENTRIES,
version: 0,
+21
View File
@@ -118,6 +118,27 @@ export interface SlotRendererHost {
* @returns entries in registration (list: order) sequence.
*/
entriesOf(key: string): readonly StoredEntry[]
/**
* Shadowing winners per cell for a key — the render read for single/keyed/
* list dispatch: the first live (non-abdicated) entry of each cell in
* priority order; chain keys pass through unchanged (election consumes
* every entry). Fresh array per call — a render-body read, not a uSES
* getSnapshot source.
* @param key - slot key.
* @returns the winning entry per occupied cell.
*/
entriesOfSlot(key: string): readonly StoredEntry[]
/**
* Report an entry boundary crash. With `info.abdicate` (shadowing kinds)
* the entry retires from its cell, one-shot, so the next survivor renders;
* chain crashes report without abdicating. The registration stays on the
* ledger either way.
* @param key - slot key the entry rendered under.
* @param entry - the crashed entry.
* @param error - the crash cause.
* @param info - `abdicate`: whether the crash retires the entry from its cell.
*/
reportEntryError(key: string, entry: StoredEntry, error: unknown, info: { abdicate: boolean }): void
/**
* Declared runtime spec from the declarations ledger.
* @param key - slot key.
+159 -4
View File
@@ -42,6 +42,21 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
/** Theme token dictionary: --dsw-alias-* overrides keyed by variable name. */
export type ThemeTokens = Record<string, string>
/**
* One override-layer token value: both palette modes are mandatory (repeat
* the same value when the token is scheme-invariant) so an override never
* goes illegible when the user switches to the other scheme.
*/
export interface ThemeTokenModes {
/** Value applied while the light base palette is active. */
light: string
/** Value applied while the dark base palette is active. */
dark: string
}
/** Override-layer dictionary: token names to per-mode value pairs. */
export type ThemeTokenOverrides = Record<string, ThemeTokenModes>
/** One selectable theme: id, dark/light semantics, and alias-token overrides. */
export interface ThemeDefinition {
/** Theme id (the setTheme argument for concrete themes). */
@@ -59,7 +74,11 @@ export interface ThemeDefinition {
export interface ThemeSnapshot {
/** The persisted preference (may be `system`). */
preference: ThemePreference
/** The resolved active theme (`system` resolved via prefers-color-scheme). */
/**
* The resolved active theme (`system` resolved via prefers-color-scheme)
* with override layers folded into its tokens (seq order, later layers win
* per-token; each value picked for the active color scheme).
*/
active: ThemeDefinition
/** Registered themes in registration order. */
themes: readonly ThemeDefinition[]
@@ -67,6 +86,20 @@ export interface ThemeSnapshot {
revision: number
}
/** One theme token exposed to pre-definition Cordis inspection. */
export interface ThemeTokenInspection {
/** Token name accepted by {@link ThemeService.overrideTokens}. */
name: string
/** Intended visual role. */
description: string
/** CSS value category. */
valueType: string
/** Whether override layers must supply both palette modes. */
requiresLightAndDark: boolean
/** CSS custom property consumed by UI styles. */
cssVariable?: string
}
declare module '@deepseek-ai/cordis' {
interface Context {
theme: ThemeRuntime
@@ -87,11 +120,29 @@ const BUILTIN_THEMES: readonly ThemeDefinition[] = Object.freeze([
Object.freeze({ id: 'dark', colorScheme: 'dark' as const, tokens: Object.freeze({}) }),
])
const BUILTIN_INSPECT_TOKENS: readonly ThemeTokenInspection[] = Object.freeze([
{ name: '--dsw-alias-bg-base', description: 'Application base background.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-bg-base' },
{ name: '--dsw-alias-bg-layer-1', description: 'Primary raised surface background.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-bg-layer-1' },
{ name: '--dsw-alias-bg-layer-2', description: 'Secondary nested surface background.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-bg-layer-2' },
{ name: '--dsw-alias-bg-overlay', description: 'Overlay and popover background.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-bg-overlay' },
{ name: '--dsw-alias-border-l1', description: 'Primary subtle border.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-border-l1' },
{ name: '--dsw-alias-border-l2', description: 'Secondary stronger border.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-border-l2' },
{ name: '--dsw-alias-brand-primary', description: 'Primary brand accent.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-brand-primary' },
{ name: '--dsw-alias-label-primary', description: 'Primary text color.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-label-primary' },
{ name: '--dsw-alias-label-secondary', description: 'Secondary text color.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-label-secondary' },
{ name: '--dsw-alias-state-error-primary', description: 'Primary error state color.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-state-error-primary' },
{ name: '--dsw-alias-state-success-primary', description: 'Primary success state color.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-state-success-primary' },
{ name: '--dsw-alias-state-warn-primary', description: 'Primary warning state color.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-alias-state-warn-primary' },
{ name: '--dsw-specific-sidebar-fill', description: 'Sidebar column and title-row background.', valueType: 'CSS color', requiresLightAndDark: true, cssVariable: '--dsw-specific-sidebar-fill' },
])
/**
* Theme registry and preference owner. `light`/`dark` are built in (the base
* stylesheets carry both palettes); third-party themes register alias-layer
* overrides. Reads go through {@link getTheme}; writes only through
* {@link setTheme}; continuous sync only through the `theme/change` event.
* overrides. Reads go through {@link getTheme}; preference writes only
* through {@link setTheme}; continuous sync only through the `theme/change`
* event. {@link overrideTokens} stacks partial token layers over the active
* theme without touching the registry.
* The service holds the `prefers-color-scheme` media query (environment
* sensing, not presentation) and re-emits when the OS scheme flips while the
* preference is `system`.
@@ -104,6 +155,9 @@ export class ThemeRuntime {
private revision = 0
private snapshot: ThemeSnapshot
private readonly media: MediaQueryList | undefined
/** Override layers by source; seq (monotonic) is the stacking order. */
private readonly overrides = new Map<string, { seq: number; tokens: ThemeTokenOverrides }>()
private overrideSeq = 0
/**
* @param ctx - owning context (change events are emitted on it; the
@@ -140,6 +194,25 @@ export class ThemeRuntime {
return this.snapshot
}
/**
* Export the current token directory without reading DOM or computed styles.
* @returns stable JSON-safe token descriptions, including registered and override-only names.
*/
exportInspectTokens(): ThemeTokenInspection[] {
const tokens = new Map(BUILTIN_INSPECT_TOKENS.map(token => [token.name, token]))
for (const theme of this.themes) {
for (const name of Object.keys(theme.tokens)) {
if (!tokens.has(name)) tokens.set(name, dynamicToken(name))
}
}
for (const layer of this.overrides.values()) {
for (const name of Object.keys(layer.tokens)) {
if (!tokens.has(name)) tokens.set(name, dynamicToken(name))
}
}
return [...tokens.values()].map(token => ({ ...token })).sort((left, right) => left.name.localeCompare(right.name))
}
/**
* Switch the theme preference — the only user preference write entry.
* Built-in preferences are written through the settings scope and every
@@ -189,6 +262,33 @@ export class ThemeRuntime {
}
}
/**
* Stack a token override layer on top of the active theme — the token-level
* analogue of slot shading: the base theme stays untouched, layers compose
* in seq order with later layers winning per-token, and removing a layer
* restores whatever it covered. Calling again with the same source replaces
* that source's whole layer and restacks it on top (effect re-registration
* semantics). Emits `theme/change` with the recomposed snapshot.
* @param source - layer identity; one layer per source (dynamic packages
* pass their package id — the façade pins it, so it also names the layer's
* origin for inspection).
* @param tokens - token-name → `{ light, dark }` value pairs. Validated at
* runtime (model-authored callers reach this boundary with untyped JS);
* a bare string value throws a teaching error.
* @returns disposer removing exactly the layer this call created; a no-op
* once the source has re-overridden (the newer layer is not torn down).
*/
overrideTokens(source: string, tokens: ThemeTokenOverrides): () => void {
const layer = { seq: this.overrideSeq++, tokens: validateOverrides(source, tokens) }
this.overrides.set(source, layer)
this.publish()
return () => {
if (this.overrides.get(source) !== layer) return
this.overrides.delete(source)
this.publish()
}
}
private buildSnapshot(): ThemeSnapshot {
const resolvedId = this.preference === 'system'
? (this.media?.matches === true ? 'dark' : 'light')
@@ -200,12 +300,29 @@ export class ThemeRuntime {
if (active === undefined) throw new Error(`theme registry lost "${resolvedId}"`)
return Object.freeze({
preference: this.preference,
active,
active: this.composeActive(active),
themes: Object.freeze([...this.themes]),
revision: this.revision,
})
}
/**
* Fold the override layers into the active definition: seq order, later
* layers win per-token, each value picked for the active color scheme (the
* presenter consumes the composed snapshot and needs no override awareness).
* Without layers the registered definition passes through by identity.
*/
private composeActive(active: ThemeDefinition): ThemeDefinition {
if (this.overrides.size === 0) return active
const tokens: ThemeTokens = { ...active.tokens }
for (const layer of [...this.overrides.values()].sort((a, b) => a.seq - b.seq)) {
for (const [name, modes] of Object.entries(layer.tokens)) {
tokens[name] = modes[active.colorScheme]
}
}
return Object.freeze({ ...active, tokens: Object.freeze(tokens) })
}
private publish(): void {
this.revision += 1
this.snapshot = this.buildSnapshot()
@@ -213,6 +330,44 @@ export class ThemeRuntime {
}
}
/**
* Runtime shape check for one override layer (model-authored callers pass
* untyped JS through the dynamic-package façade, so the static type cannot
* enforce the pair shape there). Returns a defensive per-token copy so later
* caller mutation cannot reach the stored layer.
*/
function validateOverrides(source: string, tokens: ThemeTokenOverrides): ThemeTokenOverrides {
const validated: ThemeTokenOverrides = {}
for (const [name, value] of Object.entries<unknown>(tokens)) {
if (typeof value === 'string') {
throw new TypeError(
`theme override "${name}" from "${source}" is a bare string — pass { light: ${JSON.stringify(value)}, dark: ${JSON.stringify(value)} } `
+ '(repeat the value when it is the same in both palettes); a single value goes illegible when the user switches color scheme',
)
}
if (typeof value !== 'object' || value === null
|| typeof (value as { light?: unknown }).light !== 'string'
|| typeof (value as { dark?: unknown }).dark !== 'string') {
throw new TypeError(
`theme override "${name}" from "${source}" must map to a { light, dark } pair of strings — one value per color scheme`,
)
}
const modes = value as ThemeTokenModes
validated[name] = { light: modes.light, dark: modes.dark }
}
return validated
}
function dynamicToken(name: string): ThemeTokenInspection {
return {
name,
description: 'Theme token registered by the current Client composition.',
valueType: 'CSS value',
requiresLightAndDark: true,
...(name.startsWith('--') ? { cssVariable: name } : {}),
}
}
/**
* Required services: settings transport plus slots/locale for the Appearance
* row. `remote` carries the forwarded settings invalidation that
@@ -2,7 +2,11 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import { stubSettingsScope, type StubSettingsScope } from '@deepseek-ai/dsh-client-test-runtime'
import type { ThemeSettings, ThemeSnapshot } from '@deepseek-ai/dsh-client-ui-theme/client'
import type {
ThemeSettings,
ThemeSnapshot,
ThemeTokenOverrides,
} from '@deepseek-ai/dsh-client-ui-theme/client'
import { ThemeRuntime } from '@deepseek-ai/dsh-client-ui-theme/client'
const make = (host = stubSettingsScope<ThemeSettings>()): {
@@ -103,6 +107,91 @@ describe('ThemeRuntime', () => {
expect(events.map(e => e.revision)).toEqual([1, 2, 3, 4])
})
it('stacks reversible token overrides in call order and selects the active palette value', () => {
const { theme } = make()
const firstTokens: ThemeTokenOverrides = {
'--shared': { light: 'first-light', dark: 'first-dark' },
'--first': { light: 'first-only-light', dark: 'first-only-dark' },
}
const disposeFirst = theme.overrideTokens('first', firstTokens)
firstTokens['--shared']!.light = 'mutated-after-call'
const disposeSecond = theme.overrideTokens('second', {
'--shared': { light: 'second-light', dark: 'second-dark' },
})
expect(theme.getTheme().active.tokens).toMatchObject({
'--first': 'first-only-light',
'--shared': 'second-light',
})
theme.setTheme('dark')
expect(theme.getTheme().active.tokens).toMatchObject({
'--first': 'first-only-dark',
'--shared': 'second-dark',
})
disposeSecond()
expect(theme.getTheme().active.tokens['--shared']).toBe('first-dark')
disposeFirst()
expect(theme.getTheme().active.tokens['--shared']).toBeUndefined()
})
it('replacing one source leaves its stale disposer harmless', () => {
const { theme, events } = make()
const stale = theme.overrideTokens('package', {
'--old': { light: 'old-light', dark: 'old-dark' },
})
const current = theme.overrideTokens('package', {
'--new': { light: 'new-light', dark: 'new-dark' },
})
stale()
expect(theme.getTheme().active.tokens).toEqual({ '--new': 'new-light' })
current()
current()
expect(theme.getTheme().active.tokens).toEqual({})
expect(events).toHaveLength(3)
})
it('exports sorted built-in, registered, and override-only token descriptions as copies', () => {
const { theme } = make()
theme.register({
id: 'custom',
colorScheme: 'light',
tokens: {
'--dsw-alias-bg-base': 'duplicate-built-in',
'--registered': 'registered',
},
})
theme.overrideTokens('package', {
'--registered': { light: 'duplicate-registered', dark: 'duplicate-registered' },
semanticAccent: { light: 'pink', dark: 'red' },
})
const tokens = theme.exportInspectTokens()
expect(tokens.map(token => token.name)).toEqual([...tokens.map(token => token.name)].sort())
expect(tokens.find(token => token.name === '--registered')).toMatchObject({
valueType: 'CSS value',
cssVariable: '--registered',
})
const semantic = tokens.find(token => token.name === 'semanticAccent')
expect(semantic).toMatchObject({ valueType: 'CSS value' })
expect(semantic).not.toHaveProperty('cssVariable')
expect(tokens.filter(token => token.name === '--dsw-alias-bg-base')).toHaveLength(1)
tokens[0]!.description = 'caller mutation'
expect(theme.exportInspectTokens()[0]!.description).not.toBe('caller mutation')
})
it('rejects every malformed token override value with a teaching error', () => {
const { theme } = make()
const override = (value: unknown): void => {
theme.overrideTokens('package', { '--bad': value } as unknown as ThemeTokenOverrides)
}
expect(() => { override('red') }).toThrow(/bare string.*light.*dark/)
for (const value of [1, null, {}, { light: 1, dark: 'dark' }, { light: 'light' }]) {
expect(() => { override(value) }).toThrow(/must map to a \{ light, dark \} pair/)
}
})
it('context dispose releases the scope subscription', async () => {
const { ctx, host } = make()
expect(host.listenerCount()).toBe(1)
Loaded 100 of 240 files, more files were not shown because too many files have changed in this diff. Show more