refactor: replace overloaded surface terminology
This commit is contained in:
@@ -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: eb7df95bde10dafd7afcb168d30c9dda90296687
|
||||
README.md: 257ed4647c74ef45973d717278cdaa420968b3f6
|
||||
README.zh.md: 03cd02510267d3abdd414bed6ec1f42773d0811a
|
||||
|
||||
@@ -10,48 +10,48 @@ Groups hold `packages/<group>/<pkg>/`; names stay `@deepseek-ai/dsh-<pkg>`. **Gr
|
||||
|
||||
| Group | Role | Release expectation |
|
||||
|---|---|---|
|
||||
| [`core/`](core/README.md) | Product API spine: sessions, prompts, tools, agent services, and the concrete loop | Product — stable surface |
|
||||
| [`api/`](api/README.md) | Remote BFF assembly and TypeRT RPC gateway | Product — stable surface |
|
||||
| [`typert/`](typert/README.md) | Type graph generation, artifact loading, and runtime registry | Product — stable surface |
|
||||
| [`goal/`](goal/README.md) | Same-session goal persistence and lifecycle | Product — stable surface |
|
||||
| [`feedback/`](feedback/README.md) | Human feedback | Product — stable surface |
|
||||
| [`llm/`](llm/README.md) | LLM capability family: the abstract service + provider adapters | Product — stable surface |
|
||||
| [`core/`](core/README.md) | Product API spine: sessions, prompts, tools, agent services, and the concrete loop | Product — stable API |
|
||||
| [`api/`](api/README.md) | Remote BFF assembly and TypeRT RPC gateway | Product — stable API |
|
||||
| [`typert/`](typert/README.md) | Type graph generation, artifact loading, and runtime registry | Product — stable API |
|
||||
| [`goal/`](goal/README.md) | Same-session goal persistence and lifecycle | Product — stable API |
|
||||
| [`feedback/`](feedback/README.md) | Human feedback | Product — stable API |
|
||||
| [`llm/`](llm/README.md) | LLM capability family: the abstract service + provider adapters | Product — stable API |
|
||||
| [`e2b/`](e2b/README.md) | E2B providers | POC |
|
||||
| [`subprocess/`](subprocess/README.md) | Subprocess capability family: Service Definition + local process-tree provider | Product — stable surface |
|
||||
| [`bash/`](bash/README.md) | Bash capability family: executor seam, local impl, model-facing tool | Product — stable surface |
|
||||
| [`pty/`](pty/README.md) | Persistent PTY capability family: owner-scoped sessions, local implementation, and model-facing tools | Product — stable surface |
|
||||
| [`code-runtime/`](code-runtime/README.md) | Code-execution capability family: Service Definition + worker-thread provider + Code Mode Consumer | Product — stable surface |
|
||||
| [`sandbox/`](sandbox/README.md) | Process-confinement seam; bwrap/Landlock/Seatbelt backends | Product — stable surface |
|
||||
| [`fs/`](fs/README.md) | Filesystem capability family: seam, local impl, model-facing file tools, bash-backed discovery tools | Product — stable surface |
|
||||
| [`lsp/`](lsp/README.md) | LSP capability family: seam, generic stdio provider, and the `lsp` tool | Product — stable surface |
|
||||
| [`skill/`](skill/README.md) | Skill capability family: the provider registry, local provider, and model-facing catalog/loader | Product — stable surface |
|
||||
| [`compact/`](compact/README.md) | Compaction capability family: Service Definition + basic provider + command Consumer | Product — stable surface |
|
||||
| [`context/`](context/README.md) | Model-visible request context, including workspace instructions and time context | Product — stable surface |
|
||||
| [`subagent/`](subagent/README.md) | Subagent capability family: the provider-registry contract and the model-facing delegation tool | Product — stable surface |
|
||||
| [`tasks/`](tasks/README.md) | Generic background-task runtime and model-facing `task_*` control tools | Product — stable surface |
|
||||
| [`workflow/`](workflow/README.md) | Workflow seam, worker-thread engine, and model-facing `workflow`/`ralph` tools | Product — stable surface |
|
||||
| [`web/`](web/README.md) | Web capability family: seam, search/fetch provider impls, and the model-facing web tools | Product — stable surface |
|
||||
| [`attachment/`](attachment/README.md) | Durable attachment identity, validation, local content-addressed storage | Product — stable surface |
|
||||
| [`spill/`](spill/README.md) | Spill capability family: storage seam, local impl, tool-result spill policy | Product — stable surface |
|
||||
| [`todo/`](todo/README.md) | The model-facing `todo_write` tool | Product — stable surface |
|
||||
| [`plan/`](plan/README.md) | Plan collaboration state with a direct entry command and reviewed exit | Product — stable surface |
|
||||
| [`preset/`](preset/README.md) | Per-session agent composition from preset `cordis.yml` files | Product — stable surface |
|
||||
| [`guard/`](guard/README.md) | Loop-hygiene guards: advisory repeat-call reminders + the `tools/execute` deadline enforcer | Product — stable surface |
|
||||
| [`bundle/`](bundle/README.md) | Installable `dsh --profile` patch layers | Product — stable surface |
|
||||
| [`self-modification/`](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 surface |
|
||||
| [`hooks/`](hooks/README.md) | Hook bridges + the shared Claude Code / Codex wire-protocol library | Product — stable surface |
|
||||
| [`session/`](session/README.md) | Durable session data plane: persistence seam + JSONL/SQLite backends, projection seam, log-backed titles, session reporting | Product — stable surface |
|
||||
| [`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 surface |
|
||||
| [`settings/`](settings/README.md) | User-settings seam + file-backed provider | Product — stable surface |
|
||||
| [`credentials/`](credentials/README.md) | Credential-reference seam + env-over-`.env` provider | Product — stable surface |
|
||||
| [`storage/`](storage/README.md) | Non-session storage hub + backends + domain form | Product — stable surface |
|
||||
| [`workspace/`](workspace/README.md) | Workspace entity | Product — stable surface |
|
||||
| [`scaffold/`](scaffold/README.md) | Create/launch/drive project tooling: helper, launcher, initializer, wire protocol with both ends, launcher telemetry | Product — stable surface |
|
||||
| [`acp/`](acp/README.md) | Automation-only Agent Client Protocol server | Product — stable surface |
|
||||
| [`interaction/`](interaction/README.md) | Human-collaboration plane: approval/interaction seams, permission preset, commands, ask-user tool | Product — stable surface |
|
||||
| [`boot/`](boot/README.md) | Shared app-bin boot glue | Product — stable surface |
|
||||
| [`host/`](host/README.md) | Web-GUI host half: API gateway + HTTP route server | Product — stable surface |
|
||||
| [`client/`](client/README.md) | Web-GUI browser half: shell, wire, object services, slots, `ui-*` plugins | Product — stable surface |
|
||||
| [`subprocess/`](subprocess/README.md) | Subprocess capability family: Service Definition + local process-tree provider | Product — stable API |
|
||||
| [`bash/`](bash/README.md) | Bash capability family: executor seam, local impl, model-facing tool | Product — stable API |
|
||||
| [`pty/`](pty/README.md) | Persistent PTY capability family: owner-scoped sessions, local implementation, and model-facing tools | Product — stable API |
|
||||
| [`code-runtime/`](code-runtime/README.md) | Code-execution capability family: Service Definition + worker-thread provider + Code Mode Consumer | Product — stable API |
|
||||
| [`sandbox/`](sandbox/README.md) | Process-confinement seam; bwrap/Landlock/Seatbelt backends | Product — stable API |
|
||||
| [`fs/`](fs/README.md) | Filesystem capability family: seam, local impl, model-facing file tools, bash-backed discovery tools | Product — stable API |
|
||||
| [`lsp/`](lsp/README.md) | LSP capability family: seam, generic stdio provider, and the `lsp` tool | Product — stable API |
|
||||
| [`skill/`](skill/README.md) | Skill capability family: the provider registry, local provider, and model-facing catalog/loader | Product — stable API |
|
||||
| [`compact/`](compact/README.md) | Compaction capability family: Service Definition + basic provider + command Consumer | Product — stable API |
|
||||
| [`context/`](context/README.md) | Model-visible request context, including workspace instructions and time context | Product — stable API |
|
||||
| [`subagent/`](subagent/README.md) | Subagent capability family: the provider-registry contract and the model-facing delegation tool | Product — stable API |
|
||||
| [`tasks/`](tasks/README.md) | Generic background-task runtime and model-facing `task_*` control tools | Product — stable API |
|
||||
| [`workflow/`](workflow/README.md) | Workflow seam, worker-thread engine, and model-facing `workflow`/`ralph` tools | Product — stable API |
|
||||
| [`web/`](web/README.md) | Web capability family: seam, search/fetch provider impls, and the model-facing web tools | Product — stable API |
|
||||
| [`attachment/`](attachment/README.md) | Durable attachment identity, validation, local content-addressed storage | Product — stable API |
|
||||
| [`spill/`](spill/README.md) | Spill capability family: storage seam, local impl, tool-result spill policy | Product — stable API |
|
||||
| [`todo/`](todo/README.md) | The model-facing `todo_write` tool | Product — stable API |
|
||||
| [`plan/`](plan/README.md) | Plan collaboration state with a direct entry command and reviewed exit | Product — stable API |
|
||||
| [`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 |
|
||||
| [`self-modification/`](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 |
|
||||
| [`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 |
|
||||
| [`settings/`](settings/README.md) | User-settings seam + file-backed provider | Product — stable API |
|
||||
| [`credentials/`](credentials/README.md) | Credential-reference seam + env-over-`.env` provider | Product — stable API |
|
||||
| [`storage/`](storage/README.md) | Non-session storage hub + backends + domain form | Product — stable API |
|
||||
| [`workspace/`](workspace/README.md) | Workspace entity | Product — stable API |
|
||||
| [`scaffold/`](scaffold/README.md) | Create/launch/drive project tooling: helper, launcher, initializer, wire protocol with both ends, launcher telemetry | Product — stable API |
|
||||
| [`acp/`](acp/README.md) | Automation-only Agent Client Protocol server | Product — stable API |
|
||||
| [`interaction/`](interaction/README.md) | Human-collaboration plane: approval/interaction seams, permission preset, commands, ask-user tool | Product — stable API |
|
||||
| [`boot/`](boot/README.md) | Shared app-bin boot glue | Product — stable API |
|
||||
| [`host/`](host/README.md) | Web-GUI host half: API gateway + HTTP route server | Product — stable API |
|
||||
| [`client/`](client/README.md) | Web-GUI browser half: shell, wire, object services, slots, `ui-*` plugins | Product — stable API |
|
||||
| [`examples/`](examples/README.md) | Demo bundles (agent-spine + CLI/ACP/JSON-RPC bins) leaves load | Support — example infra |
|
||||
| [`support/`](support/README.md) | Support infrastructure (testkits, invariants, replay, Loader smokes) | Support — lower compatibility expectations |
|
||||
| [`util/`](util/README.md) | Low-level zero-dependency utilities shared across groups (`Branded<B>`, Harness home/path helpers, timeout, retention) | Support — small, stable, harness-dep-free |
|
||||
|
||||
@@ -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/api/remotes/README.md
|
||||
README.md: 3d9de0955faefe37c95ff8bb792d57c4fa1f1a3a
|
||||
README.md: 567ece0fb58d4b9c0b022dd2ce4d8ee87caccc57
|
||||
README.zh.md: 12add6f8efc5b6af3e9b74b26a1abb2bb3936e0a
|
||||
|
||||
@@ -20,7 +20,7 @@ The package-local `clientBundle(..., { hostPhase: true })` makes Host tsdown bun
|
||||
|
||||
## Model Experience
|
||||
|
||||
None, as this BFF selects Remote application methods and identity policy but registers no model surface.
|
||||
None, as this BFF selects Remote application methods and identity policy but registers nothing model-facing.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
|
||||
@@ -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/bash/bash-local/README.md
|
||||
README.md: cb8e7f0ae766d9b1c5f1678e77d35992085d3d52
|
||||
README.zh.md: 20af9c18998c6f3f0403c50f3a8ac599607dc094
|
||||
README.md: 253d1c9efb518ca204956f062b2f529f4e83531b
|
||||
README.zh.md: 5a0d943a08d1da6c73ad07f469c056180b476ff3
|
||||
|
||||
@@ -25,7 +25,7 @@ The package root exports the default and named `LocalBashExecutor` plugin plus i
|
||||
- **Spawn per call, no shell state** — every call is a fresh non-login `bash -c` with no rc files.
|
||||
- **Configured budgets over managed groups** — `resolve()` fills `workdir`/`timeoutMs`/`stdoutMaxBytes` from config, and every spawn hands the service explicit byte caps, spill cap, and `graceMs`. The grace must be positive, finite, and no greater than [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.md), so Node can represent it with one timer. Process-group kills, post-exit pipe draining, tail retention, and bounded spill files are [`dsh-subprocess-local`](../../subprocess/subprocess-local/README.md) mechanics. A foreground `BashExecRequest.stdoutMaxBytes` can raise stdout's capture budget for one trusted caller; stderr and background runs still use `maxOutputBytes`.
|
||||
- **Timeout and cancel classification** — `run()` fuses its config-clamped timeout with the caller's signal through one deadline; only the executor's own timeout reports `timedOut`, an upstream cancel reports `aborted`, and a self-signaled command reports neither ([timeout-library Agent Note](../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md)).
|
||||
- **Model-friendly terminal env** — `NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat` prevents pagers and ANSI color from garbling results. These values merge as ordinary env under the service's credential scrub and `DSH_*` channel rules; an explicit caller entry still wins. See the [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) and [managed environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md).
|
||||
- **Model-friendly terminal env** — `NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat` prevents pagers and ANSI color from garbling results. These values merge as ordinary env under the service's credential scrub and `DSH_*` channel rules; an explicit caller entry still wins. See the [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md) and [managed environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md).
|
||||
- **Background processes** — `start()` returns a live `BashProcess` handle immediately with no timeout, and `readOutput()` merges offset-based stdout/stderr reads into one consuming delta, placing stderr under a `[stderr]` marker when present. A running process belongs to the subprocess service, survives executor reloads, and is killed and joined on service disposal. Task ids, ownership, polling, and notices belong to the generic [`ctx.tasks` runtime](../../tasks/tasks/README.md), which the tool layer registers the handle with.
|
||||
|
||||
## Model Experience
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
- **每次调用都 spawn,不保留 shell 状态**:每次调用都启动新的非登录 `bash -c`,且不读取 rc 文件。
|
||||
- **在受管进程组之上应用配置预算**:`resolve()` 从配置补全 `workdir`/`timeoutMs`/`stdoutMaxBytes`,每次 spawn 都向服务传入显式的字节上限、spill 上限与 `graceMs`。该宽限期须为正有限值,且不得大于 [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.md),这样 Node 就能用一个定时器表示它。进程组终止、退出后管道排空、尾部保留与有界 spill 文件是 [`dsh-subprocess-local`](../../subprocess/subprocess-local/README.md) 的机制。前台 `BashExecRequest.stdoutMaxBytes` 可为某个受信任调用方提高单次 stdout 捕获预算;stderr 和后台运行仍使用 `maxOutputBytes`。
|
||||
- **超时与取消分类**:`run()` 通过同一个 deadline 把经配置钳位的超时与调用方的信号融合;只有执行器自身的超时报告 `timedOut`,上游取消报告 `aborted`,自身因信号终止的命令两者皆不报告(见[超时库 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md))。
|
||||
- **适合模型的终端环境**:`NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat` 防止分页器与 ANSI 颜色破坏结果。这些值作为普通 env 合并,遵循服务的凭据清除与 `DSH_*` 通道规则;调用方的显式条目依旧优先。详见 [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) 与 [受管环境 Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md)。
|
||||
- **适合模型的终端环境**:`NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat` 防止分页器与 ANSI 颜色破坏结果。这些值作为普通 env 合并,遵循服务的凭据清除与 `DSH_*` 通道规则;调用方的显式条目依旧优先。详见 [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md) 与 [受管环境 Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md)。
|
||||
- **后台进程**:`start()` 会立即返回活动的 `BashProcess` 句柄且不应用超时;`readOutput()` 把基于偏移量的 stdout/stderr 读取合并为一条消费式增量,并在存在 stderr 时将其置于 `[stderr]` 标记下。运行中的进程属于 subprocess 服务,可在执行器重载后存活,并在服务 dispose 时被终止且等待退出。task id、所有权、轮询和通知属于通用 [`ctx.tasks` 运行时](../../tasks/tasks/README.md),工具层会在其中注册该句柄。
|
||||
|
||||
## 模型体验
|
||||
|
||||
@@ -307,7 +307,7 @@ describe('process lifecycle ownership (the subprocess service, not the executor)
|
||||
const bash = ctx.bash as LocalBashExecutor
|
||||
|
||||
// The child prints its own pid ($$ = the detached bash group leader) so
|
||||
// the test can probe liveness through the public read surface alone.
|
||||
// the test can probe liveness through the public read API alone.
|
||||
const proc = bash.start(bash.resolve({ command: 'echo $$; sleep 60' }))
|
||||
const pid = Number((await readUntil(proc, '\n')).trim())
|
||||
expect(Number.isInteger(pid) && pid > 0).toBe(true)
|
||||
|
||||
@@ -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/bash/bash/README.md
|
||||
README.md: 23b0acd096bb835ef57563337c91e5cf63b58677
|
||||
README.zh.md: 14ba749a0018bd6a63475bc0ab72c6fe6d26893a
|
||||
README.md: a076c6ef3150de86c251f0501b18de01c4491c67
|
||||
README.zh.md: 1be9f817a8379b2974fb4cfd63c197c320934f00
|
||||
|
||||
@@ -33,7 +33,7 @@ Implementations subclass `BashExecutor` and implement the abstract methods. Disp
|
||||
|
||||
The per-session sandbox-mode override vocabulary (the `'sandbox/mode'` event, the `effectiveSandboxMode(events)` fold, and the `setSandboxMode(session, mode)` write path) is NOT here — it is policy state shared by every enforcing family, owned by [`@deepseek-ai/dsh-sandbox-policy`](../../sandbox/sandbox-policy/). `run()` returns `BashRunResult`; `start()` returns `BashProcess`, whose incremental read and kill methods are adapted by `dsh-tool-bash` into a generic task registration. A sandboxing executor stamps `BashSandboxInfo` on foreground results and settled process handles. See `src/types.ts` and [subsystems/bash.md](../../../docs/subsystems/bash.md).
|
||||
|
||||
`stdin` and ordinary `env` are set by in-process plugins (the hooks bridges, native plugins) to feed a hook command its JSON payload and `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` values. `dshEnv` is a separate trusted overlay restricted by type to managed keys; the exported `DSH_ENV_PREFIX` is the single source for that namespace, its `DshEnvironmentKey` template type, executor scrubbing, registry validation, derived built-in names, and model guidance. Model bash uses the current snapshot collected by `ctx.bashEnv`. Implementations remove inherited managed keys, then merge `dshEnv` after ordinary `env`, so an omitted current fact cannot fall back to stale ambient state and an `env` entry cannot displace a managed value. The model-facing tool exposes none of these as parameters. All three remain optional on the resolved spec; absent means no input/overlay. See [the bash-stdin-env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) and [the session environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md).
|
||||
`stdin` and ordinary `env` are set by in-process plugins (the hooks bridges, native plugins) to feed a hook command its JSON payload and `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` values. `dshEnv` is a separate trusted overlay restricted by type to managed keys; the exported `DSH_ENV_PREFIX` is the single source for that namespace, its `DshEnvironmentKey` template type, executor scrubbing, registry validation, derived built-in names, and model guidance. Model bash uses the current snapshot collected by `ctx.bashEnv`. Implementations remove inherited managed keys, then merge `dshEnv` after ordinary `env`, so an omitted current fact cannot fall back to stale ambient state and an `env` entry cannot displace a managed value. The model-facing tool exposes none of these as parameters. All three remain optional on the resolved spec; absent means no input/overlay. See [the bash-stdin-env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md) and [the session environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md).
|
||||
|
||||
The exported `parseExitStatus` (with `ParsedExitStatus`) is the shared rendering contract half of the shell tools: the inverse of the `[exit code: N]` / `[killed by signal: X]` markers `dsh-tool-bash`'s `renderResult` and `dsh-tool-pwsh`'s `renderPwshResult` append. Both tools' `presentResult` use it to split the rendered text into the terminal card's output body and its exit-status pill; it lives with the Service Definition so the two tools never drift on the marker contract.
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
每会话沙箱模式覆盖词汇(`'sandbox/mode'` 事件、`effectiveSandboxMode(events)` fold 以及 `setSandboxMode(session, mode)` 写入路径)不位于此处。它是所有强制执行家族共享的策略状态,属于 [`@deepseek-ai/dsh-sandbox-policy`](../../sandbox/sandbox-policy/)。`run()` 返回 `BashRunResult`;`start()` 返回 `BashProcess`,其增量读取与终止方法由 `dsh-tool-bash` 适配为通用任务注册。沙箱执行器会在前台结果与已结算进程句柄上标记 `BashSandboxInfo`。详见 `src/types.ts` 与 [subsystems/bash.md](../../../docs/subsystems/bash.md)。
|
||||
|
||||
`stdin` 与普通 `env` 由同进程插件(hooks 桥接、原生插件)设置,用于向 hook 命令提供其 JSON payload 和 `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` 值。`dshEnv` 是受类型限制、仅允许受管 key 的独立受信任 overlay;导出的 `DSH_ENV_PREFIX` 是该 namespace、其 `DshEnvironmentKey` 模板类型、执行器清理、注册表验证、派生内置名称与模型指引的统一来源。模型 bash 使用 `ctx.bashEnv` 收集的当前快照。实现会移除继承的受管 key,再在普通 `env` 之后合并 `dshEnv`,因此省略的当前事实不会回退到陈旧环境状态,`env` 条目也无法顶掉受管值。面向模型的工具不将这三者中的任何一个公开为参数。这三者在已解析 spec 上仍然可选;缺失表示没有输入/overlay。详见 [bash-stdin-env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) 与 [会话环境 Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md)。
|
||||
`stdin` 与普通 `env` 由同进程插件(hooks 桥接、原生插件)设置,用于向 hook 命令提供其 JSON payload 和 `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` 值。`dshEnv` 是受类型限制、仅允许受管 key 的独立受信任 overlay;导出的 `DSH_ENV_PREFIX` 是该 namespace、其 `DshEnvironmentKey` 模板类型、执行器清理、注册表验证、派生内置名称与模型指引的统一来源。模型 bash 使用 `ctx.bashEnv` 收集的当前快照。实现会移除继承的受管 key,再在普通 `env` 之后合并 `dshEnv`,因此省略的当前事实不会回退到陈旧环境状态,`env` 条目也无法顶掉受管值。面向模型的工具不将这三者中的任何一个公开为参数。这三者在已解析 spec 上仍然可选;缺失表示没有输入/overlay。详见 [bash-stdin-env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md) 与 [会话环境 Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md)。
|
||||
|
||||
导出的 `parseExitStatus`(连同 `ParsedExitStatus`)是 shell 工具共享渲染约定的另一半:`dsh-tool-bash` 的 `renderResult` 与 `dsh-tool-pwsh` 的 `renderPwshResult` 追加的 `[exit code: N]`/`[killed by signal: X]` marker 的逆解析。两个工具的 `presentResult` 都用它把渲染文本拆成 terminal 卡的输出正文与其退出状态 pill;它放在 Service Definition 中,两个工具便永远不会在 marker 约定上漂移。
|
||||
|
||||
|
||||
@@ -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/bash/tool-bash/README.md
|
||||
README.md: 9e2cd0c2ed999a11dcbffcd99a1d0cb672367905
|
||||
README.zh.md: 8a1e7ba0af36ffd891b38491075ee75283fdbed5
|
||||
README.md: d5483e5e266a5362d9a8d324d71046591fe146bc
|
||||
README.zh.md: 527543ea7130cb89e7e9776cb71adec842527e85
|
||||
|
||||
@@ -42,7 +42,7 @@ The tool owns its `presentCall`/`presentResult` render intent. A foreground call
|
||||
|
||||
## The tool builds its request from named args only
|
||||
|
||||
`BashExecRequest` carries optional `stdoutMaxBytes`, `stdin`, ordinary `env`, and managed `dshEnv`, used by trusted in-process plugins and this tool's environment registry. The model-facing tool exposes none of `stdoutMaxBytes`, `stdin`, or `env`: it builds requests from named command/workdir/timeout/signal/sandbox fields plus the registry-collected `dshEnv`. Extra model keys are ignored and cannot replace managed values. Shell syntax provides equivalent command-level behavior, while the local executor scrubs ambient credentials and stale `DSH_*` values. See the [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md).
|
||||
`BashExecRequest` carries optional `stdoutMaxBytes`, `stdin`, ordinary `env`, and managed `dshEnv`, used by trusted in-process plugins and this tool's environment registry. The model-facing tool exposes none of `stdoutMaxBytes`, `stdin`, or `env`: it builds requests from named command/workdir/timeout/signal/sandbox fields plus the registry-collected `dshEnv`. Extra model keys are ignored and cannot replace managed values. Shell syntax provides equivalent command-level behavior, while the local executor scrubs ambient credentials and stale `DSH_*` values. See the [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md).
|
||||
|
||||
## Permissions and escalation
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
## 工具仅使用具名参数构建请求
|
||||
|
||||
`BashExecRequest` 携带可选的 `stdoutMaxBytes`、`stdin`、普通 `env` 和托管 `dshEnv`,供可信进程内插件及此工具的环境注册表使用。模型侧工具不公开 `stdoutMaxBytes`、`stdin` 或 `env`:它使用具名的命令/工作目录/超时/信号/沙箱字段,加上从注册表收集的 `dshEnv` 来构建请求。额外模型键会被忽略,无法替换托管值。Shell 语法可以提供等价的命令级行为,而本地执行器会清除环境中的凭据和陈旧 `DSH_*` 值。参见 [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md)。
|
||||
`BashExecRequest` 携带可选的 `stdoutMaxBytes`、`stdin`、普通 `env` 和托管 `dshEnv`,供可信进程内插件及此工具的环境注册表使用。模型侧工具不公开 `stdoutMaxBytes`、`stdin` 或 `env`:它使用具名的命令/工作目录/超时/信号/沙箱字段,加上从注册表收集的 `dshEnv` 来构建请求。额外模型键会被忽略,无法替换托管值。Shell 语法可以提供等价的命令级行为,而本地执行器会清除环境中的凭据和陈旧 `DSH_*` 值。参见 [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md)。
|
||||
|
||||
## 权限与升权
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ export function renderResult(
|
||||
* Shape one background-process read into the `task_output` delta the model
|
||||
* sees: the incremental delta, plus the lossy-read notice (with full-stream
|
||||
* spill paths) when in-memory truncation dropped unread bytes. Empty-delta
|
||||
* rendering (`(no new output)`) is the generic control surface's job.
|
||||
* rendering (`(no new output)`) is the generic task controller's job.
|
||||
* @param read - one incremental read from the process handle.
|
||||
* @param sandbox - settled sandbox facts, when this was a confined process.
|
||||
* @param escalationModes - escalation targets advertised by this composition.
|
||||
|
||||
@@ -42,7 +42,7 @@ async function setup() {
|
||||
return ctx
|
||||
}
|
||||
|
||||
/** Full harness: the generic task runtime + its control surface, then the bash tool. */
|
||||
/** Full harness: the generic task runtime + its controller, then the bash tool. */
|
||||
async function setupWithTasks() {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
@@ -524,7 +524,7 @@ describe('background execution through the task runtime', () => {
|
||||
})
|
||||
|
||||
it('never spawns the process when tasks.start preflight throws (no orphan, by construction)', async () => {
|
||||
// With no control surface, task preflight fails before the executor can spawn.
|
||||
// With no task controller, preflight fails before the executor can spawn.
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
@@ -536,7 +536,7 @@ describe('background execution through the task runtime', () => {
|
||||
|
||||
const result = await call(ctx, 'bash', { command: 'sleep 60', description: 'test command', run_in_background: true })
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain('no control surface serves this agent')
|
||||
expect(text(result)).toContain('no task controller serves this agent')
|
||||
// Declare-then-execute: the failed preflight means no process ever ran.
|
||||
expect((ctx.bash as CountingStartExecutor).starts).toBe(0)
|
||||
})
|
||||
|
||||
@@ -139,7 +139,7 @@ async function setup(toolConfig: Partial<ToolPwsh.Config> = {}, dshHome?: string
|
||||
return { ctx, bash }
|
||||
}
|
||||
|
||||
/** Full harness: the generic task runtime + its control surface, then the pwsh tool. */
|
||||
/** Full harness: the generic task runtime + its controller, then the pwsh tool. */
|
||||
async function setupWithTasks(toolConfig: Partial<ToolPwsh.Config> = {}, dshHome?: string) {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
@@ -766,7 +766,7 @@ describe('background execution through the task runtime', () => {
|
||||
})
|
||||
|
||||
it('never spawns the process when tasks.start preflight throws (no orphan, by construction)', async () => {
|
||||
// With no control surface, task preflight fails before the executor can spawn.
|
||||
// With no task controller, preflight fails before the executor can spawn.
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
@@ -778,7 +778,7 @@ describe('background execution through the task runtime', () => {
|
||||
|
||||
const result = await call(ctx, 'pwsh', { command: 'Start-Sleep -Seconds 60', description: 'test command', run_in_background: true })
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain('no control surface serves this agent')
|
||||
expect(text(result)).toContain('no task controller serves this agent')
|
||||
// Declare-then-execute: the failed preflight means no process ever ran.
|
||||
expect(bash.startCalls).toBe(0)
|
||||
})
|
||||
|
||||
@@ -190,7 +190,7 @@ function ensureSymlink(link: string, target: string): void {
|
||||
// Concurrent launches heal the same fallback; losing the race to a
|
||||
// process writing the identical link is success, anything else is not.
|
||||
// The window between the lstat miss above and this write cannot be
|
||||
// staged deterministically from the public surface.
|
||||
// staged deterministically from the public API.
|
||||
/* v8 ignore next 4 */
|
||||
if ((error as NodeJS.ErrnoException).code !== 'EEXIST'
|
||||
|| !lstatSync(link).isSymbolicLink() || readlinkSync(link) !== target) {
|
||||
|
||||
@@ -387,7 +387,7 @@
|
||||
thresholds: [3, 5, 8]
|
||||
argumentsPreviewChars: 500
|
||||
|
||||
# Every mode enables the stable web_search model surface. DeepSeek search
|
||||
# Every mode enables the stable model-facing web_search tool. DeepSeek search
|
||||
# resolves the same DEEPSEEK_API_KEY credential the Models page manages for
|
||||
# chat, at each search; its Messages endpoint is separate from the
|
||||
# chat-completions endpoint, so it takes its own base-URL override. Fetch stays
|
||||
|
||||
@@ -13,7 +13,7 @@ The [slot system standard](../../.agents/notes/implemented/architecture/2026-07-
|
||||
3. **Component props are the four shares, all derived**: `PropsRuntime<K>` (SlotMap: owner params + `useSession`/`sessionId` on session scope + global `useSessions`/`useWorkspaces`) & `PropsRenderSlots<S>` (children keys) & `PropsStore<H>` (store factory) & the inject face. Never hand-write a member a share already derives; never re-type a share locally.
|
||||
4. **Hooks are framework-made only**: `useSession`, `useSessions`, `useWorkspaces`, `useStore`, `renderSlot` are the five standing seats, plus the `use<Name>` hooks the renderer binds from provide contributions and inject `hooks` compartments. Business code never creates a hook or selector as a prop value — pass plain data and callbacks. (Component-internal behavioral hooks that subscribe to nothing external are fine.)
|
||||
5. **Live data has exactly three channels**: parent knows it → owner props at the renderSlot site; only the component knows it → local state; shared across entries or survives remounts → a store declared at register. Derived data is a pure function over framework-hook data (`useMemo`), never its own subscription.
|
||||
6. **Stores: read `props.useStore`, write `props.actions.*`** — the declared actions are the complete mutation surface. Write the store as an exported `createXXXStore()` factory (module-level handles are forbidden — de-facto singletons); share by passing one handle to several registers inside `apply`. Production code never calls the factory or `.create()` outside `apply`; tests do (that is the sanctioned zero-machinery path).
|
||||
6. **Stores: read `props.useStore`, write `props.actions.*`** — the declared actions are the complete mutation API. Write the store as an exported `createXXXStore()` factory (module-level handles are forbidden — de-facto singletons); share by passing one handle to several registers inside `apply`. Production code never calls the factory or `.create()` outside `apply`; tests do (that is the sanctioned zero-machinery path).
|
||||
7. **inject returns plain data and callbacks** from the apply closure's own ctx — no hand-made hooks, no ReactNode producers, no whole-service objects. A registrant-private reactive fact uses the reserved `hooks` compartment (bare observables the renderer binds to `use<Name>`; components never see the sources). The plugin may use only the dependencies named by its `inject` declaration; there is no wider ctx to reach for.
|
||||
|
||||
## Reactive read and contract-currency discipline
|
||||
@@ -32,7 +32,7 @@ How live data reaches render code, and what UI domains may share:
|
||||
The `/client` entrypoint of a UI plugin package is its public browser API, not a convenience barrel. Three rules apply package-wide (do not restate them as per-file comments):
|
||||
|
||||
1. **A UI plugin exports no values beyond what cordis loading needs** — `apply` / `inject` (and `Config` where present), plus store factories consumed type-only by components (`ReturnType<typeof createXXXStore>`). Shared types (owner data, injected values, composed prop aliases) may also be exported. Implementation components, pure helpers, constants, and store handles stay internal. Adding any new value export requires user sign-off, not a matching consumer.
|
||||
2. **Same-package tests import internals directly** — relative `../src/client/xxx.ts` from package tests, or the `./src/*` subpath where a spec lives outside the package. Never widen the public surface to make a test compile.
|
||||
2. **Same-package tests import internals directly** — relative `../src/client/xxx.ts` from package tests, or the `./src/*` subpath where a spec lives outside the package. Never widen the public API to make a test compile.
|
||||
3. **Cross-package imports of another plugin's symbols are in principle forbidden.** The sanctioned routes are the slot system (register/renderSlot) and ctx services. If neither fits, stop and escalate — do not add an export to unblock yourself.
|
||||
|
||||
## ctx discipline (components never see ctx)
|
||||
|
||||
@@ -71,7 +71,7 @@ const MARKDOWN_FIXTURE = [
|
||||
'- first item',
|
||||
' - nested item',
|
||||
'',
|
||||
'| Surface | State |',
|
||||
'| Area | State |',
|
||||
'| --- | --- |',
|
||||
'| history | rendered |',
|
||||
'| streaming | stable |',
|
||||
|
||||
@@ -46,7 +46,7 @@ export type { ClientConnectionRpc } from '../rpc.ts'
|
||||
export const inject: string[] = []
|
||||
|
||||
/**
|
||||
* The ctx.connection service surface: the api client plus a one-shot
|
||||
* The ctx.connection service API: the API client plus a one-shot
|
||||
* controller starter (the runtime plugin supplies sinks when its object layer
|
||||
* is ready — connection stays consumer-agnostic).
|
||||
*/
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('connection client apply', () => {
|
||||
it('start() hands out one loop, rejects a second consumer, and stop() aborts the streams', async () => {
|
||||
;(globalThis as Win).location = { hostname: 'localhost', search: '?fixture' }
|
||||
const handle = await mount()
|
||||
// config omitted: the `config ?? {}` default arm is part of the surface.
|
||||
// config omitted: the `config ?? {}` default arm is part of the API.
|
||||
const loop = handle.start({})
|
||||
expect(() => handle.start({})).toThrow(/already owned by another consumer/)
|
||||
loop.stop() // teardown must not throw; the fixture streams abort quietly
|
||||
|
||||
@@ -16,7 +16,7 @@ export const inject = ['invariants']
|
||||
|
||||
/**
|
||||
* No runtime invariant: ns-by-locale dictionary registry with a stable
|
||||
* bind(ns) surface — it emits no cordis events and owns no cross-plugin
|
||||
* bind(ns) API — it emits no cordis events and owns no cross-plugin
|
||||
* mutable relation; fallback-chain resolution and locale-store behavior are
|
||||
* asserted directly by this package's behavior specs.
|
||||
*/
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/modules/README.md
|
||||
README.md: a1d578850c2518a85dc32f048768b78caf5ffec4
|
||||
README.md: efaff699839b977cc45f89f3c164402241b90dc2
|
||||
README.zh.md: 772a4870f7ef6730d9d3d4db434ed771d97984f0
|
||||
|
||||
@@ -4,7 +4,7 @@ English | [中文](README.zh.md)
|
||||
|
||||
Client module system: the browser peer of Node's internal ESM loader, built as a lazy CJS table. The web shell mounts the vendored cordis Loader for entry governance (fiber lifecycle, inject waiting, update/refresh) and injects this package's `ClientModuleLoader` through its `internal` contract — the vendored side's only consumption point is `EntryTree.import`, so replacing `internal` replaces exactly "how plugin code arrives" and nothing else.
|
||||
|
||||
Lazy CJS model (web2): executing a plugin bundle only REGISTERS its factory (`window.__ModuleLoader__.load({id, factory})`); every module body side effect — CSS injection included — lives in the factory closure and runs at materialization (`factory(require)` → export surface, memoized in `loadCache`), not at script execution. A factory that requires another registered-but-unmaterialized module materializes it recursively, so load order needs no external sequencing; require cycles throw (factory-form CJS cannot deliver partial exports). `<id>/client` and the bare id name the same surface (a plugin bundle IS its package's client half).
|
||||
Lazy CJS model (web2): executing a plugin bundle only REGISTERS its factory (`window.__ModuleLoader__.load({id, factory})`); every module body side effect — CSS injection included — lives in the factory closure and runs at materialization (`factory(require)` → exports, memoized in `loadCache`), not at script execution. A factory that requires another registered-but-unmaterialized module materializes it recursively, so load order needs no external sequencing; require cycles throw (factory-form CJS cannot deliver partial exports). `<id>/client` and the bare id resolve to the same exports (a plugin bundle IS its package's client half).
|
||||
|
||||
Resolution branch order (`import(specifier)`): platform seed word → shell instance; memoized record → surface; shell-own static registry (`registerStatic`, app-shell) → module; registered factory → materialize; graph row (`window.__DSH_BOOT__`) → load its external classic script + materialize; anything else throws — the runtime mirror of the build-time bundle purity gate. The synchronous `require` handed to factories walks the same order minus the asynchronous load branch and records observed edges into the module record. `prefetch` is the stage-one arrival hook (script load and factory registration only; concurrent calls share one in-flight task); `invalidate` drops the factory and materialized record so the next prefetch/import reloads the script (the HMR hook).
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
* factory (`window.__ModuleLoader__.load({id, factory})`); every module body
|
||||
* side effect — including CSS injection — lives inside the factory closure
|
||||
* and runs at materialization, not at script execution. Materialization
|
||||
* (factory(require) → export surface) happens on first import/require and is
|
||||
* (factory(require) → exports) happens on first import/require and is
|
||||
* memoized in {@link ClientModuleLoader.loadCache}; a factory that requires
|
||||
* another registered-but-unmaterialized module materializes it recursively,
|
||||
* so load order needs no external sequencing.
|
||||
*
|
||||
* Resolution branch order (import): seed word → shell instance; memoized
|
||||
* record → surface; static registry (shell-own modules, e.g. app-shell) →
|
||||
* record → exports; static registry (shell-own modules, e.g. app-shell) →
|
||||
* module; registered factory → materialize; graph row → load + materialize;
|
||||
* anything else → throw (loud — the runtime mirror of the
|
||||
* build-time bundle purity gate). The synchronous `require` handed to
|
||||
@@ -149,13 +149,13 @@ export interface ClientPluginHandoff {
|
||||
id: string
|
||||
/**
|
||||
* Closure factory holding the whole bundle body: receives the synchronous
|
||||
* require bound to the module table and returns the bundle's export
|
||||
* surface. Runs once, at materialization.
|
||||
* require bound to the module table and returns the bundle's exports. Runs
|
||||
* once, at materialization.
|
||||
*/
|
||||
factory: (require: (spec: string) => unknown) => Record<string, unknown>
|
||||
}
|
||||
|
||||
/** Window surface of the web boot protocol: the host-injected graph, the registration sink, and the kernel handoff slot. */
|
||||
/** Window API of the web boot protocol: the host-injected graph, registration sink, and kernel handoff slot. */
|
||||
export interface DshWindow {
|
||||
/** Host-composed entry graph, injected before the shell bundle runs; wire-boundary raw until {@link parseBootManifest}. */
|
||||
__DSH_BOOT__?: unknown
|
||||
@@ -174,8 +174,8 @@ export interface DshWindow {
|
||||
export interface ClientModuleRecord {
|
||||
/** Module id (entry name / package name). */
|
||||
id: string
|
||||
/** The materialized export surface (factory `module.exports`, or the shell module for static registrations). */
|
||||
surface: unknown
|
||||
/** Materialized exports (`module.exports` from a factory, or a statically registered shell module). */
|
||||
exports: unknown
|
||||
/** Owned `<style data-plugin>` tag ids (`data-plugin-css` values) injected during materialization. */
|
||||
styles: string[]
|
||||
/** Observed `require()` edges (module-graph boundary; only table words can appear today). */
|
||||
@@ -190,7 +190,7 @@ export interface ClientModuleRecord {
|
||||
export interface ClientModuleLoader {
|
||||
/** Discriminant against Node's internal loader shapes ('v1'/'v2'). */
|
||||
version: 'client'
|
||||
/** Materialized-module registry: id → record. The governance-side read face for entry export surfaces. */
|
||||
/** Materialized-module registry: id → record. The governance-side read API for entry exports. */
|
||||
loadCache: Map<string, ClientModuleRecord>
|
||||
/**
|
||||
* Internal contract consumed by the vendored Loader's `tree.import`. Resolves
|
||||
@@ -199,7 +199,7 @@ export interface ClientModuleLoader {
|
||||
* @param specifier - module specifier (entry name or table word).
|
||||
* @param parentURL - importer URL (unused — the client module graph is flat).
|
||||
* @param attrs - Import attributes (unused; interface parity with Node's loader contract).
|
||||
* @returns the module's export surface.
|
||||
* @returns the module's exports.
|
||||
*/
|
||||
import(specifier: string, parentURL: string, attrs: Record<string, unknown>): Promise<unknown>
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,7 @@ const defaultLoadBundle = (url: string): Promise<void> => new Promise((resolve,
|
||||
/**
|
||||
* A plugin bundle IS its package's client half: `<id>/client` (the exports
|
||||
* subpath external bundles emit) and the bare graph id name the same
|
||||
* surface, so table lookups normalize the suffix away.
|
||||
* exports, so table lookups normalize the suffix away.
|
||||
*/
|
||||
const stripClientSuffix = (spec: string): string =>
|
||||
spec.endsWith('/client') ? spec.slice(0, -'/client'.length) : spec
|
||||
@@ -123,8 +123,8 @@ export class ClientModuleSystem implements ClientModuleLoader {
|
||||
this.materializing.add(id)
|
||||
try {
|
||||
const edges = new Set<string>()
|
||||
const surface = registered(this.makeRequire(edges))
|
||||
const record: ClientModuleRecord = { id, surface, styles: claimStyles(id), edges }
|
||||
const exports = registered(this.makeRequire(edges))
|
||||
const record: ClientModuleRecord = { id, exports, styles: claimStyles(id), edges }
|
||||
this.loadCache.set(id, record)
|
||||
return record
|
||||
} finally {
|
||||
@@ -146,8 +146,8 @@ export class ClientModuleSystem implements ClientModuleLoader {
|
||||
if (this.statics.has(spec)) return this.statics.get(spec)
|
||||
const id = stripClientSuffix(spec)
|
||||
const record = this.loadCache.get(id)
|
||||
if (record !== undefined) return record.surface
|
||||
if (this.factories.has(id)) return this.materialize(id).surface
|
||||
if (record !== undefined) return record.exports
|
||||
if (this.factories.has(id)) return this.materialize(id).exports
|
||||
throw new Error(
|
||||
`client-modules: require("${spec}") missed the module table — not a platform seed word, not a shell-own module, `
|
||||
+ 'and no registered factory (a build-time externals drift, or a forbidden cross-plugin value import)',
|
||||
@@ -158,11 +158,11 @@ export class ClientModuleSystem implements ClientModuleLoader {
|
||||
async import(specifier: string): Promise<unknown> {
|
||||
if (this.seed.has(specifier)) return this.seed.get(specifier)
|
||||
const existing = this.loadCache.get(specifier)
|
||||
if (existing !== undefined) return existing.surface
|
||||
if (existing !== undefined) return existing.exports
|
||||
if (this.statics.has(specifier)) {
|
||||
const surface = this.statics.get(specifier)
|
||||
this.loadCache.set(specifier, { id: specifier, surface, styles: [], edges: new Set() })
|
||||
return surface
|
||||
const exports = this.statics.get(specifier)
|
||||
this.loadCache.set(specifier, { id: specifier, exports, styles: [], edges: new Set() })
|
||||
return exports
|
||||
}
|
||||
if (!this.factories.has(specifier)) {
|
||||
const row = this.graphRows.get(specifier)
|
||||
@@ -174,7 +174,7 @@ export class ClientModuleSystem implements ClientModuleLoader {
|
||||
}
|
||||
await this.arrive(row)
|
||||
}
|
||||
return this.materialize(specifier).surface
|
||||
return this.materialize(specifier).exports
|
||||
}
|
||||
|
||||
registerStatic(id: string, module: unknown): void {
|
||||
|
||||
@@ -70,7 +70,7 @@ describe('lazy CJS arrival', () => {
|
||||
expect(b.loader.loadCache.size).toBe(0)
|
||||
})
|
||||
|
||||
it('import materializes once and memoizes the export surface', async () => {
|
||||
it('import materializes once and memoizes the exports', async () => {
|
||||
const ran: string[] = []
|
||||
const b = bench([row('a')], { a: () => { ran.push('a'); return { marker: 'a' } } })
|
||||
const first = await b.loader.import('a', '', {})
|
||||
@@ -83,8 +83,8 @@ describe('lazy CJS arrival', () => {
|
||||
|
||||
it('import without prefetch loads, registers, and materializes in one call', async () => {
|
||||
const b = bench([row('a')], { a: () => ({ marker: 'direct' }) })
|
||||
const surface = await b.loader.import('a', '', {})
|
||||
expect((surface as { marker: string }).marker).toBe('direct')
|
||||
const exports = await b.loader.import('a', '', {})
|
||||
expect((exports as { marker: string }).marker).toBe('direct')
|
||||
expect(b.fetched).toHaveLength(1)
|
||||
})
|
||||
|
||||
@@ -123,8 +123,8 @@ describe('require resolution', () => {
|
||||
})
|
||||
await b.loader.prefetch('a')
|
||||
await b.loader.prefetch('b')
|
||||
const surface = await b.loader.import('a', '', {})
|
||||
expect((surface as { got: string }).got).toBe('from-b')
|
||||
const exports = await b.loader.import('a', '', {})
|
||||
expect((exports as { got: string }).got).toBe('from-b')
|
||||
expect(order).toEqual(['a', 'b'])
|
||||
expect(b.loader.loadCache.get('a')?.edges.has('b/client')).toBe(true)
|
||||
expect(b.loader.loadCache.has('b')).toBe(true)
|
||||
@@ -135,8 +135,8 @@ describe('require resolution', () => {
|
||||
const b = bench([row('a')], {
|
||||
a: req => ({ dep: req('react') }),
|
||||
}, { seed: { react } })
|
||||
const surface = await b.loader.import('a', '', {})
|
||||
expect((surface as { dep: unknown }).dep).toBe(react)
|
||||
const exports = await b.loader.import('a', '', {})
|
||||
expect((exports as { dep: unknown }).dep).toBe(react)
|
||||
expect(await b.loader.import('react', '', {})).toBe(react)
|
||||
expect(b.loader.loadCache.has('react')).toBe(false)
|
||||
})
|
||||
@@ -284,8 +284,8 @@ describe('default transport seam', () => {
|
||||
})
|
||||
})
|
||||
const loader: ClientModuleLoader = new ClientModuleSystem({ modules: [row('dee')], staticModules: {} })
|
||||
const surface = await loader.import('dee', '', {})
|
||||
expect((surface as { marker: string }).marker).toBe('via-script')
|
||||
const exports = await loader.import('dee', '', {})
|
||||
expect((exports as { marker: string }).marker).toBe('via-script')
|
||||
expect(append).toHaveBeenCalledOnce()
|
||||
expect([...document.querySelectorAll('script')]).toEqual([])
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ import type {
|
||||
} from '@deepseek-ai/dsh-client-ui-slots'
|
||||
|
||||
// Store contract types are ui-slots authority; re-exported beside the engine
|
||||
// so store consumers get one import surface.
|
||||
// so store consumers get one import path.
|
||||
export type {
|
||||
ActionsDecl, BakedActions, BoundActions, StoreFactory, StoreHandle, StoreInstance, StoreSpec,
|
||||
} from '@deepseek-ai/dsh-client-ui-slots'
|
||||
@@ -165,7 +165,7 @@ function deepFreeze(value: unknown): void {
|
||||
|
||||
/** A live engine instance: the contract instance plus the raw engine store. */
|
||||
export interface EngineStoreInstance<T, A extends ActionsDecl<T>> extends StoreInstance<T, A> {
|
||||
/** The underlying engine store (framework/test surface; components never see it). */
|
||||
/** The underlying engine store (framework/test API; components never see it). */
|
||||
readonly store: SnapshotStore<T>
|
||||
}
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@ export function apply(ctx: Context): void {
|
||||
workspaces.handleHostEnvelope(envelope)
|
||||
// Typed-event bridge: the session layer ignores registry frames (no
|
||||
// session routing); consumers (command directory caches, the settings
|
||||
// and model surfaces) subscribe on ctx.
|
||||
// and model services) subscribe on ctx.
|
||||
const frame = envelope.payload
|
||||
if (frame.type === 'host/commands-changed') ctx.emit('commands/changed')
|
||||
else if (frame.type === 'host/session-preset-changed') {
|
||||
|
||||
@@ -430,7 +430,7 @@ export class SessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- List surface ----
|
||||
// ---- List API ----
|
||||
|
||||
/** Full refresh via session.list (single-flight: an in-flight call is reused). */
|
||||
refreshList(): Promise<void> {
|
||||
@@ -629,7 +629,7 @@ export class SessionManager {
|
||||
this.notifier.markDirty()
|
||||
}
|
||||
|
||||
// ---- Subscription surface (for useSessionList) ----
|
||||
// ---- Subscription API (for useSessionList) ----
|
||||
|
||||
/**
|
||||
* uSES subscription entry for useSessionList.
|
||||
|
||||
@@ -60,7 +60,7 @@ export interface SessionOptions {
|
||||
* remaining public members are manager/runtime entry points.
|
||||
*/
|
||||
export class Session implements SessionFace {
|
||||
// ---- Window and derived state (all private; the snapshot is the only read surface) ----
|
||||
// ---- Window and derived state (all private; the snapshot is the only read API) ----
|
||||
private events: SessionEvent[] = []
|
||||
/** Wire views aligned with `events` by index (envelope-level annotations; undefined = no view).
|
||||
* Kept parallel rather than merged so `events` stays the raw log slice (model-visible ⟺ logged). */
|
||||
@@ -428,7 +428,7 @@ export class Session implements SessionFace {
|
||||
await this.open()
|
||||
}
|
||||
|
||||
// ---- Subscription surface (useSyncExternalStore direct wiring) ----
|
||||
// ---- Subscription API (useSyncExternalStore direct wiring) ----
|
||||
|
||||
/**
|
||||
* uSES subscription entry.
|
||||
|
||||
@@ -25,7 +25,7 @@ const CSS_VIRTUAL_PREFIX = '\0dsh-css:'
|
||||
const CSS_VIRTUAL_SUFFIX = '.mjs'
|
||||
|
||||
/**
|
||||
* Wire/type layers a client bundle may inline: browser-safe contract surfaces
|
||||
* Wire/type layers a client bundle may inline: browser-safe contracts
|
||||
* with no runtime identity to share (no Symbol/instanceof/singleton state).
|
||||
* Everything else under @deepseek-ai/* is either a module-table entry
|
||||
* (external) or a leak the purity gate rejects.
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-conversation/README.md
|
||||
README.md: 605bba15d704c0c6e9f28abb3cddeb68bdd7e0d8
|
||||
README.md: ed8f888d35693ecaa2667ea432b462f4bb3369cf
|
||||
README.zh.md: e6a2dd0b545b66ab01b213b5ebc937e22af8ac1a
|
||||
|
||||
@@ -40,7 +40,7 @@ The composer bar declares session-scoped single seats for `'conversation.input.p
|
||||
|
||||
The chat stats line takes its token accounting from the generic token-meter `tokenUsage` projection read through the standard-kit `useProjection`: billed input is uncached input plus cache reads and writes; cache hit divides cache reads by that total. Visible nodes supply only the turn and step counts plus the LLM and tool wall times, which are window-scoped facts about what is on screen rather than accounting; durable token and context groups remain visible when compaction leaves no assistant node in the loaded window. The same window fold averages each recorded step's TTFT and divides sampled output tokens by their summed decode spans into a latency/throughput group localized through the `conversation` locale namespace (`TTFT avg … · … tok/s` in English); a step missing a timing boundary or a usage sample drops out of those figures instead of skewing them. The turn-count, step-count, duration, cache, and token labels use the same namespace. Each settled turn additionally appends hover-revealed `TTFT {s}s · {tps} tok/s` labels to its assistant footer after the `Ran for` duration — the turn's first-step TTFT and its turn-aggregate decode throughput — gated on the turn's timing being in the loaded window (a contiguous log suffix, so an in-window turn carries every one of its steps) and omitting whichever figure is unrecorded. A deployment without token-meter drops the token groups; when the line overflows, it elides with an ellipsis and a delayed hover tooltip carries the full text only while actually clipped. Context occupancy renders as the composer's trailing ContextMeter: a 14px occupancy ring after the model seat, fed by `contextPressure` and rendered only once both a numerator and a route capacity are known, that click-opens a panel pairing the `percent used` header and `~used / capacity` figures with a color-segmented bar and `~`-prefixed heuristic composition rows (system prompt, tools, messages) from the `contextBreakdown` projection. The ring and header read `projectedTokens` — the provider sample carried forward over the surface's movement since — so a compaction registers immediately instead of after a further turn; the composition rows stay wholly heuristic and therefore still do not sum to the header ([rationale](../../llm/token-meter/README.md)). Occupancy is deliberately an approximation: numerator and capacity are independent last-wins projection fields, not one atomic request observation.
|
||||
|
||||
`src/client/` is organized by domain. `contract/` is the shared face for slot declarations, composed props, and cross-domain types; `skeleton/`, `chat/`, `input/`, `queue/`, and `settings/` keep their implementations internal, while `apply.ts` is their assembly point. The `/client` export surface contains only loader entries, service classes, and contract types; components and store factories reach the page through slot registrations.
|
||||
`src/client/` is organized by domain. `contract/` is the shared face for slot declarations, composed props, and cross-domain types; `skeleton/`, `chat/`, `input/`, `queue/`, and `settings/` keep their implementations internal, while `apply.ts` is their assembly point. The `/client` exports contain only loader entries, service classes, and contract types; components and store factories reach the page through slot registrations.
|
||||
|
||||
A finished turn materializes one ordered `turn-tail` Conversation Node. Its engine-owned `TurnLocation` supplies the closing Assistant and Turn data; the renderer places the `conversation.chat.turnTail` chain before that node's IconActions and dispatches `TurnTailOwnerProps` containing the Turn, closing seq, and `openFile`. This package owns only the hole; `@deepseek-ai/dsh-client-ui-deliverables` accumulates mutation-tool `locations` into Turn data and owns the produced-files row, chip cap, and copy, so composing that plugin out of cordis.yml turns the surface off while the hole renders empty at zero cost. The closing prose participates through the same off switch: the chat view asks the optional `chatFileMentions` service (ctx.get; provided by the same plugin) for a closing message's inline-code vocabulary and threads the result into MarkdownText's `fileMentions` seam — an absent service leaves the prose inert.
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ export class ConversationService extends Service implements IConversation {
|
||||
|
||||
/**
|
||||
* Send a prompt into the scoped session. Business failures also land in the
|
||||
* session snapshot's promptError (object-layer surface); the rejection here
|
||||
* session snapshot's promptError (object-layer state); the rejection here
|
||||
* exists for caller choreography (the composer restores the draft on it).
|
||||
* @param text - prompt text, sent verbatim as one text block.
|
||||
*/
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// @vitest-environment jsdom
|
||||
// apply inject factories exercised end to end against the terminal thin
|
||||
// shape: the strict session surface (views triple, draft mirror), the
|
||||
// API: the strict session API (views triple, draft mirror), the
|
||||
// provide-channel input face (machine-sink submit choreography incl.
|
||||
// optimistic clear + failure restore), the resident surface (selectWorkspace
|
||||
// optimistic clear + failure restore), the resident API (selectWorkspace
|
||||
// draft carrying), the composer-bar stop face, openDetails = select action +
|
||||
// layout orchestration, and the closeDetails details surface. Complements
|
||||
// layout orchestration, and the closeDetails details API. Complements
|
||||
// chat-apply.spec.tsx (registration) and selection-survival.spec.tsx (store
|
||||
// axis). History opening is NOT an inject concern — the runtime sessions
|
||||
// service opens on watch (sessions-service.spec.ts owns that behavior).
|
||||
//
|
||||
// The inject surfaces are read off the ledger entries deliberately (typed at
|
||||
// The inject APIs are read off the ledger entries deliberately (typed at
|
||||
// this spec's own contract): these cases pin factory choreography the UI
|
||||
// guards would mask. Rendering-path acceptance lives in
|
||||
// chat-toolview-slot.spec.tsx.
|
||||
@@ -75,30 +75,30 @@ async function bench() {
|
||||
const entryOf = (key: 'conversation' | 'conversation.session' | 'conversation.session.header' | 'conversation.composer.bar' | 'conversation.view' | 'details') =>
|
||||
runtime.slots.entries(key)[0]!
|
||||
/** Resolve store instance + call the inject the way the outlet would. */
|
||||
const conversationSurface = (id: SessionId) => {
|
||||
const conversationApi = (id: SessionId) => {
|
||||
const entry = entryOf('conversation.session')
|
||||
const instance = runtime.storeOf('conversation.session', id) as ChatInstance
|
||||
const injected = (entry.inject as unknown as (sessionId: SessionId, actions: ChatActions) => ConversationSessionInjected)(
|
||||
id, instance.actions)
|
||||
return { instance, injected }
|
||||
}
|
||||
const conversationHeaderSurface = (id: SessionId) => {
|
||||
const conversationHeaderApi = (id: SessionId) => {
|
||||
const entry = entryOf('conversation.session.header')
|
||||
const instance = runtime.storeOf('conversation.session.header', id) as ChatInstance
|
||||
const injected = (entry.inject as unknown as (sessionId: SessionId, actions: ChatActions) => ConversationSessionHeaderInjected)(
|
||||
id, instance.actions)
|
||||
return { instance, injected }
|
||||
}
|
||||
const residentSurface = (id: SessionId | undefined) => {
|
||||
const residentApi = (id: SessionId | undefined) => {
|
||||
const entry = entryOf('conversation')
|
||||
return (entry.inject as unknown as (sessionId: SessionId | undefined) => ConversationInjected)(id)
|
||||
}
|
||||
const composerSurface = (id: SessionId | undefined) => {
|
||||
const composerApi = (id: SessionId | undefined) => {
|
||||
const entry = entryOf('conversation.composer.bar')
|
||||
return (entry.inject as unknown as (sessionId: SessionId | undefined) => ComposerBarInjected)(id)
|
||||
}
|
||||
/** Same resolution for the chat entry riding the view ring. */
|
||||
const chatViewSurface = (id: SessionId) => {
|
||||
const chatViewApi = (id: SessionId) => {
|
||||
const entry = entryOf('conversation.view')
|
||||
const instance = runtime.storeOf('conversation.view', id) as ChatInstance
|
||||
const injected = (entry.inject as unknown as (sessionId: SessionId, actions: ChatActions) => ChatViewInjected)(
|
||||
@@ -106,7 +106,7 @@ async function bench() {
|
||||
return { instance, injected }
|
||||
}
|
||||
/** Materialize the input provide contribution the way the runtime does. */
|
||||
const inputSurface = (id: SessionId) => {
|
||||
const inputApi = (id: SessionId) => {
|
||||
const info = runtime.sessions.provideInfo(id)!
|
||||
const state = info.hooks['input'] as {
|
||||
getSnapshot: () => { draft: string }
|
||||
@@ -120,21 +120,21 @@ async function bench() {
|
||||
}
|
||||
return {
|
||||
runtime, feature, slots: runtime.slots, entryOf,
|
||||
conversationSurface, conversationHeaderSurface, residentSurface, composerSurface, chatViewSurface, inputSurface,
|
||||
conversationApi, conversationHeaderApi, residentApi, composerApi, chatViewApi, inputApi,
|
||||
sessionFake, layoutFake,
|
||||
}
|
||||
}
|
||||
|
||||
describe('conversation slot inject surface', () => {
|
||||
it('assembles the thin surface side-effect-free', async () => {
|
||||
describe('conversation slot inject API', () => {
|
||||
it('assembles the thin API side-effect-free', async () => {
|
||||
const b = await bench()
|
||||
const { injected } = b.conversationSurface(ROOT)
|
||||
const { injected } = b.conversationApi(ROOT)
|
||||
// Assembly has no session side effects: opening the event window belongs
|
||||
// to the runtime watch path, not the inject factory.
|
||||
expect(b.sessionFake.open).not.toHaveBeenCalled()
|
||||
expect(injected.views.list().map(v => v.id)).toEqual(['chat'])
|
||||
|
||||
const chatView = b.chatViewSurface(ROOT)
|
||||
const chatView = b.chatViewApi(ROOT)
|
||||
chatView.injected.loadOlder()
|
||||
expect(b.sessionFake.loadOlder).toHaveBeenCalledTimes(1)
|
||||
chatView.injected.forkAt(17)
|
||||
@@ -149,8 +149,8 @@ describe('conversation slot inject surface', () => {
|
||||
|
||||
it('the provide-channel input face submits through the machine sink: trim, optimistic clear, failure restore without clobber', async () => {
|
||||
const b = await bench()
|
||||
const { injected } = b.conversationSurface(ROOT)
|
||||
const { state, actions } = b.inputSurface(ROOT)
|
||||
const { injected } = b.conversationApi(ROOT)
|
||||
const { state, actions } = b.inputApi(ROOT)
|
||||
// Whitespace-only: the machine treats it as empty — no prompt, draft kept.
|
||||
actions.setDraft(' ')
|
||||
actions.submit()
|
||||
@@ -176,16 +176,16 @@ describe('conversation slot inject surface', () => {
|
||||
await new Promise(r => setTimeout(r, 0))
|
||||
expect(state.getSnapshot().draft).toBe('typed during flight')
|
||||
// The provide contribution is idempotent per session: one shell identity.
|
||||
expect(b.inputSurface(ROOT).state).toBe(state)
|
||||
expect(b.inputApi(ROOT).state).toBe(state)
|
||||
// The draft mirror rides the conversation inject face.
|
||||
const mirrored: string[] = []
|
||||
const unbind = injected.bindDraftMirror(text => mirrored.push(text))
|
||||
actions.setDraft('mirrored text')
|
||||
expect(mirrored).toEqual(['mirrored text'])
|
||||
unbind()
|
||||
// Stop failure is swallowed (promptError owns the surface).
|
||||
// Stop failure is swallowed (promptError owns the display).
|
||||
b.sessionFake.cancel.mockResolvedValueOnce({ ok: false, error: { code: 'internal', message: 'x', details: {} } })
|
||||
b.composerSurface(ROOT).stop!()
|
||||
b.composerApi(ROOT).stop!()
|
||||
await new Promise(r => setTimeout(r, 0))
|
||||
expect(b.sessionFake.cancel).toHaveBeenCalledTimes(1)
|
||||
await b.runtime.dispose()
|
||||
@@ -216,20 +216,20 @@ describe('conversation slot inject surface', () => {
|
||||
|
||||
it('openDetails (chat view face) writes the selection through the store actions and opens the panel', async () => {
|
||||
const b = await bench()
|
||||
const { instance, injected } = b.chatViewSurface(ROOT)
|
||||
const { instance, injected } = b.chatViewApi(ROOT)
|
||||
injected.openDetails({ turnSeq: 2, callId: 'c1' })
|
||||
expect(instance.store.getSnapshot().selection).toEqual({ turnSeq: 2, callId: 'c1' })
|
||||
expect(b.layoutFake.openDetails).toHaveBeenCalledTimes(1)
|
||||
// The chat view shares the conversation entry's store instance: selection
|
||||
// writes land where the skeleton and details read.
|
||||
const conv = b.conversationSurface(ROOT)
|
||||
const conv = b.conversationApi(ROOT)
|
||||
expect(conv.instance).toBe(instance)
|
||||
await b.runtime.dispose()
|
||||
})
|
||||
|
||||
it('openFile (chat view face) resolves against session cwd and calls workspaces.openPath', async () => {
|
||||
const b = await bench()
|
||||
const { injected } = b.chatViewSurface(ROOT)
|
||||
const { injected } = b.chatViewApi(ROOT)
|
||||
injected.openFile('src/a.ts')
|
||||
await vi.waitFor(() => {
|
||||
expect(b.runtime.workspaces.calls).toContainEqual({ method: 'openPath', args: ['/proj/src/a.ts'] })
|
||||
@@ -239,11 +239,11 @@ describe('conversation slot inject surface', () => {
|
||||
|
||||
it('routes workspace switching through the runtime owner, carrying the draft', async () => {
|
||||
const b = await bench()
|
||||
const resident = b.residentSurface(ROOT)
|
||||
const resident = b.residentApi(ROOT)
|
||||
// Same-session connect (the picked workspace resolves to this session):
|
||||
// no draft movement, plain re-open.
|
||||
b.runtime.workspaces.stub('connectWorkspace', () => Promise.resolve(ROOT))
|
||||
const { state, actions } = b.inputSurface(ROOT)
|
||||
const { state, actions } = b.inputApi(ROOT)
|
||||
actions.setDraft('carry me')
|
||||
void resident.selectWorkspace('workspace-1' as never)
|
||||
await vi.waitFor(() => {
|
||||
@@ -261,7 +261,7 @@ describe('conversation slot inject surface', () => {
|
||||
expect(b.runtime.sessions.calls).toContainEqual({ method: 'open', args: [OTHER] })
|
||||
})
|
||||
expect(state.getSnapshot().draft).toBe('')
|
||||
expect(b.inputSurface(OTHER).state.getSnapshot().draft).toBe('carry me')
|
||||
expect(b.inputApi(OTHER).state.getSnapshot().draft).toBe('carry me')
|
||||
await b.runtime.dispose()
|
||||
})
|
||||
|
||||
@@ -269,7 +269,7 @@ describe('conversation slot inject surface', () => {
|
||||
const b = await bench()
|
||||
// No-session resident (hero before any session): connect resolves and
|
||||
// navigation proceeds without any draft choreography.
|
||||
const noSession = b.residentSurface(undefined)
|
||||
const noSession = b.residentApi(undefined)
|
||||
b.runtime.workspaces.stub('connectWorkspace', () => Promise.resolve(ROOT))
|
||||
void noSession.selectWorkspace('workspace-0' as never)
|
||||
await vi.waitFor(() => {
|
||||
@@ -279,15 +279,15 @@ describe('conversation slot inject surface', () => {
|
||||
// Cross-session connect with an EMPTY draft: no move, no clearing.
|
||||
const OTHER = 'b9-other' as SessionId
|
||||
await b.runtime.sessions.add({ id: OTHER }, { current: false })
|
||||
const resident = b.residentSurface(ROOT)
|
||||
const { state } = b.inputSurface(ROOT)
|
||||
const resident = b.residentApi(ROOT)
|
||||
const { state } = b.inputApi(ROOT)
|
||||
expect(state.getSnapshot().draft).toBe('')
|
||||
b.runtime.workspaces.stub('connectWorkspace', () => Promise.resolve(OTHER))
|
||||
void resident.selectWorkspace('workspace-3' as never)
|
||||
await vi.waitFor(() => {
|
||||
expect(b.runtime.sessions.calls).toContainEqual({ method: 'open', args: [OTHER] })
|
||||
})
|
||||
expect(b.inputSurface(OTHER).state.getSnapshot().draft).toBe('')
|
||||
expect(b.inputApi(OTHER).state.getSnapshot().draft).toBe('')
|
||||
|
||||
// Connect failure: the rejection propagates to the caller (the view owns
|
||||
// the rollback) and no further navigation happens.
|
||||
@@ -310,7 +310,7 @@ describe('conversation slot inject surface', () => {
|
||||
|
||||
it('views read face projects the ring ledger (subscribe/version through ctx.slots)', async () => {
|
||||
const b = await bench()
|
||||
const { injected } = b.conversationSurface(ROOT)
|
||||
const { injected } = b.conversationApi(ROOT)
|
||||
const before = injected.views.version()
|
||||
const listener = vi.fn()
|
||||
const unsub = injected.views.subscribe(listener)
|
||||
@@ -332,7 +332,7 @@ describe('conversation slot inject surface', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('details inject surface', () => {
|
||||
describe('details inject API', () => {
|
||||
it('details injects the one layout callback; selection rides the shared store instead', async () => {
|
||||
const b = await bench()
|
||||
const entry = b.entryOf('details')
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-goal/README.md
|
||||
README.md: a53fb3a89eaee364cb025ca728ca42ce934887b0
|
||||
README.md: f0446aa0637bc181f7fdc22e5d0d3192e0ac20cf
|
||||
README.zh.md: 1ad9f50aee5b103f6455e4d4b7d29fa9eb29a108
|
||||
|
||||
@@ -4,7 +4,7 @@ English | [中文](README.zh.md)
|
||||
|
||||
Goal surface plugin, browser half: the `GoalBar` strip is the second standalone card in the `conversation.input.dock` composer-context stack (order 10, after Todo and before Queue). The live goal arrives through `useProjection('goal')` — the host-computed whole value seeded by the history tail page and updated by `session/projection` frames — so the plugin owns no domain store, refresh chain, or event listener. The slot inject face carries only the four mutation verbs (edit / pause / resume / clear through `ctx.remote.goals` — an active goal offers the pause action, a paused one resume); each reads the CAS ref from the session's current projected value at call time and surfaces the rejected Remote error inline. The strip single-flights mutations synchronously because React's pending render cannot fence same-frame clicks; after a successful clear it immediately suppresses that exact goal id while the authoritative null projection catches up. Goal creation stays on the `/goal` host command; loading, absent, completed, and successfully cleared goals render nothing.
|
||||
|
||||
The `/client` export surface is the plugin body (`apply`/`inject`), the `GoalBar`/`GoalDock` components, and the injected verb face types.
|
||||
The `/client` exports are the plugin body (`apply`/`inject`), the `GoalBar`/`GoalDock` components, and the injected verb face types.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-layout/README.md
|
||||
README.md: fa60520a20ac8a7f25d494879c68efb06a28998f
|
||||
README.md: c58b9a26ac794131aaa197722d14bd04de9f0cae
|
||||
README.zh.md: 2ce3d1972d39013d84216596dc57d48ab2d245d9
|
||||
|
||||
@@ -6,7 +6,7 @@ Shell plugin: three-column AppFrame (drag handles and concession chain) plus the
|
||||
|
||||
AppFrame always mounts the conversation and details columns; a connected Session renders through `SessionProvider`. The transient layout store starts the sidebar at its default width and details closed, and it never reads or writes `localStorage`. Hero and other unselected states also derive a zero rendered details width without changing that stored preference. AppFrame retains the last non-blank Session id across those states: the first Session remains closed, an explicit details action opens the contract default width, returning to the same Session restores its unchanged width, and selecting a different Session closes details before paint. The conversation owner share is empty, while the sidebar owner share contains only `collapsed` and `width`; registrants obtain business data from standard hooks and actions from their own inject faces.
|
||||
|
||||
The `/client` export surface is the plugin body (`apply`/`inject`), `LayoutService`, and the four owner-share interfaces. AppFrame, the panel store, and the concession solver remain package-internal.
|
||||
The `/client` exports are the plugin body (`apply`/`inject`), `LayoutService`, and the four owner-share interfaces. AppFrame, the panel store, and the concession solver remain package-internal.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { createLayoutStore } from './stores.ts'
|
||||
import { LayoutService } from './service.ts'
|
||||
import { ThemePresenter } from './theme-presenter.ts'
|
||||
|
||||
// Contract surface only (export-convergence rule: cross-package consumers
|
||||
// Contract exports only (export-convergence rule: cross-package consumers
|
||||
// keep a symbol exported; test-only/package-internal symbols live off /src).
|
||||
// ILayout: the ctx.layout face consumers and test fakes type against.
|
||||
// OwnerShare contracts below are the render-side halves registrants compose
|
||||
@@ -64,7 +64,7 @@ export interface ConvOwnerProps {}
|
||||
/** Details owner share: empty — sessionId arrives as a framework-standard prop. */
|
||||
export interface DetailsOwnerProps {}
|
||||
|
||||
/** Required services (cordis fiber inject — the loader passes the whole export surface as an object plugin). */
|
||||
/** Required services (cordis fiber inject — the loader passes all module exports as an object plugin). */
|
||||
export const inject = ['slots', 'theme']
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// ONE register() call declares the three child slots + seats the store factory
|
||||
// + wires the panel actions through the inject hook; teardown cascades
|
||||
// (service unprovided + declarations gone + registration cleared). Node half
|
||||
// and the invariant companion ride along — one-line surfaces the aggregate
|
||||
// and the invariant companion ride along — one line exposes the aggregate
|
||||
// coverage gate still requires exercised.
|
||||
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
@@ -111,7 +111,7 @@ describe('node half + invariant companion', () => {
|
||||
const register = vi.fn().mockReturnValue(() => {})
|
||||
const ctx = { invariants: { register } } as never
|
||||
// The /invariant subpath types live in lib/types (build product); assert
|
||||
// the surface so the call stays typed where lint runs without a build.
|
||||
// the API so the call stays typed where lint runs without a build.
|
||||
const dispose = await (invariant as { apply: (ctx: never) => Promise<() => void> }).apply(ctx)
|
||||
expect(register).toHaveBeenCalledWith('@deepseek-ai/dsh-client-ui-layout', expect.any(Function))
|
||||
// The installer is the declared no-op — calling it must not throw.
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-model/README.md
|
||||
README.md: 519429834f214fcb82eeb692378fb79770fa30be
|
||||
README.md: fdc3258eb37b45f4773648d2d796c275f8657d47
|
||||
README.zh.md: 116e151d1afeaaa22618c408eed2c7542d1357e7
|
||||
|
||||
@@ -10,7 +10,7 @@ When the Host reports that no adapter serves the session's route (`session.model
|
||||
|
||||
Directories are per-session, resolved lazily through `ctx.models.directoryFor(sessionId)`, and disposed with the session scope. Addressed subagent sessions expose neither entry, and their directory rejects loads, selections, and reconnect refreshes, because ordinary Agent-bound model RPCs would activate persisted child history outside the direct-parent continuation path.
|
||||
|
||||
The `/client` export surface is the plugin body (`apply`/`inject`), `ModelService`, `ModelDirectory` with its state shape, and the seat's injected face type.
|
||||
The `/client` exports are the plugin body (`apply`/`inject`), `ModelService`, `ModelDirectory` with its state fields, and the seat's injected face type.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-permission/README.md
|
||||
README.md: 742e82d767152073ab963dc74c0565d6e8f8e5c4
|
||||
README.md: cf3981d66745bd5a65c83e09daeb8463023f447b
|
||||
README.zh.md: 70bbbb2d14358cbe52a6fc27deb7ce01d5f3679b
|
||||
|
||||
@@ -6,7 +6,7 @@ Permission browser surfaces for two different lifetimes. The General-settings ro
|
||||
|
||||
The current-session surface remains a popupSelect DECORATION hung on the host `/permission` command (`ctx.command.decorate`). A decoration is not a second command — the host command keeps its slash-menu row, the argued path (`/permission <preset>` switches directly), and the durable lifecycle logging; the decoration replaces only the bare invocation with the picker: one flat preset list with the current value marked active and kebab-case preset names rendered as title-case labels (`workspace-write` → `Workspace Write`, the composer chip's display transform twin), where a pick submits the `/permission <preset>` command line. Options and the active mark read the session's `permissions` projection (the same host-computed select the composer chip renders), so both current-session surfaces share one read source and one write path, and the pushed projection frame is the single confirmation both follow. The decoration is available exactly while the projection key is present; a permission-less composition shows neither picker nor Settings row.
|
||||
|
||||
The `/client` export surface is the plugin body (`apply`/`inject`).
|
||||
The `/client` exports are the plugin body (`apply`/`inject`).
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export const name = 'client-ui-primitives-invariant'
|
||||
export const inject = ['invariants']
|
||||
|
||||
/**
|
||||
* No runtime invariant: pure props-in React atoms with zero cordis surface —
|
||||
* No runtime invariant: pure props-in React atoms with no Cordis API —
|
||||
* no events, no services, no mutable cross-plugin state; rendering contracts
|
||||
* are asserted directly by this package's component specs.
|
||||
*/
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-sidebar/README.md
|
||||
README.md: 45ae267d98b17bbc612cf932f5b95b42ba6ff4bf
|
||||
README.md: 4eb9eeb73f1f8398eb9d16434996840182ba79a9
|
||||
README.zh.md: a9fb927305d0bab5fb4d27adbfdbec90dfa1dd6d
|
||||
|
||||
@@ -12,7 +12,7 @@ Scrollbars in the column are a pointer affordance: the shell rebinds ui-theme's
|
||||
|
||||
The foot is the `sidebar.settings` seat: the sidebar renders only the bottom-pinned layout slot and shares its column state (`wide`); ui-settings registers the trigger row and settings panel there.
|
||||
|
||||
The `/client` export surface is the plugin body (`apply`/`inject`) plus the contract types only; SidebarRoot, the row components, and the tree derivation remain package-internal behind the slot registration.
|
||||
The `/client` exports are the plugin body (`apply`/`inject`) plus the contract types only; SidebarRoot, the row components, and the tree derivation remain package-internal behind the slot registration.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-skill/README.md
|
||||
README.md: 36b4cf4181d74ca1ea05fd8ed2db5e42fa36c7f2
|
||||
README.md: 0456db4de9453e5060e39b5f061422486e44dfc9
|
||||
README.zh.md: 336f43117e7bc4de41a31e636ee0966e5d1a2cd6
|
||||
|
||||
@@ -8,7 +8,7 @@ A pick lands the literal `/name ` text and the prompt ships the same literal ([s
|
||||
|
||||
A failed `skill.list` throws from `candidates`, which the slash shell logs and folds into a silent menu-group drop — the menu shows only pending/ready states.
|
||||
|
||||
The `/client` export surface is the plugin body (`apply`/`inject`) only; the source object is internal to the registration effect.
|
||||
The `/client` exports are the plugin body (`apply`/`inject`) only; the source object is internal to the registration effect.
|
||||
|
||||
## Skill tool row
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-slash/README.md
|
||||
README.md: 3f97351bea04dbc268e6acc105be10e1e4e0a4b5
|
||||
README.md: e6c9c3240d03a0cde57eda0bce2a995f91786f4e
|
||||
README.zh.md: cf9e42d419cb472509a74ba227990b1502b38a75
|
||||
|
||||
@@ -8,7 +8,7 @@ Layering: `src/core/` is the pure core — `detectTrigger`, `menuReduce`/`seedGr
|
||||
|
||||
MenuView renders the menu store into the `conversation.input.overlay` slot (list kind, session scope) and renders null while closed. Typed triggers seed every source registered for that trigger; a programmatic launcher seeds only its requested source and publishes the source name through the controller's `launcher` snapshot store until the menu closes or typed tracking resumes. Groups sort by the optional `SlashSource.order` (lower first, default 0, ties keep registration order) under title rows localized through the `slash.menu` locale namespace (an unknown source shows its raw name); the list height clamps to the space above the composer, and a pointer down outside both the menu and the surrounding composer card dismisses it. The slot is owned by ui-conversation's composer entry (anchor, children declaration, lifecycle); its SlotMap type merge lives in this package's `src/client/slots.ts` because the dependency direction (ui-conversation → ui-slash) admits no reverse type import. Combobox pattern: focus stays in the textarea, rows pick on mousedown, the highlight rides `aria-activedescendant`.
|
||||
|
||||
The `/client` export surface is the plugin body (`apply`/`inject`), `SlashService`, `MenuViewInjected`, and the contract types. MenuView itself is internal — the slot registration closes over it.
|
||||
The `/client` exports are the plugin body (`apply`/`inject`), `SlashService`, `MenuViewInjected`, and the contract types. MenuView itself is internal — the slot registration closes over it.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface LocaleFace extends HostObservable<{ revision: number }> {
|
||||
bind(ns: string): Translate
|
||||
}
|
||||
|
||||
/** Minimal observable surface for host-provided standard-kit data sources. */
|
||||
/** Minimal observable API for host-provided standard-kit data sources. */
|
||||
export interface HostObservable<T> {
|
||||
getSnapshot(): T
|
||||
subscribe(fn: () => void): () => void
|
||||
@@ -97,7 +97,7 @@ export interface RenderOpts {
|
||||
hookContext?: unknown
|
||||
}
|
||||
|
||||
/** Host surface the runtime SlotsService presents to the installed renderer. */
|
||||
/** Host API the runtime SlotsService presents to the installed renderer. */
|
||||
export interface SlotRendererHost {
|
||||
/**
|
||||
* Subscribe to a key's registration changes (microtask-batched).
|
||||
@@ -167,8 +167,8 @@ export interface SlotRendererHost {
|
||||
/** The installation contract: runtime owns install()/renderSlot(); web-react implements rendering. */
|
||||
export interface SlotRenderer {
|
||||
/**
|
||||
* Render the root slot tree over the host surface (the only ctx-level entry).
|
||||
* @param host - the installing service's host surface.
|
||||
* Render the root slot tree over the host API (the only ctx-level entry).
|
||||
* @param host - the installing service's host API.
|
||||
* @param ownerProps - owner props from the shell's renderSlot('root', ...) call.
|
||||
* @returns the rendered tree.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SlotCore terminal-design behavior: the single register composition API —
|
||||
// a-priori 'root', children declaration/authorization, load-time validation,
|
||||
// one-axis lifecycle cascade, store scope pinning, subscription surface.
|
||||
// one-axis lifecycle cascade, store scope pinning, subscription API.
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { SlotComponent, StoreHandle } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { SlotCore } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
@@ -230,7 +230,7 @@ describe('store scope pinning', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('subscription surface', () => {
|
||||
describe('subscription API', () => {
|
||||
it('tracks declaration epochs separately from ordinary entry mutations', () => {
|
||||
const core = new SlotCore()
|
||||
expect(core.declarationEpoch('root')).toBe(1)
|
||||
|
||||
@@ -5,8 +5,8 @@ import { SlotCore } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
|
||||
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
interface SlotMap {
|
||||
'surface.a': { kind: 'single'; scope: 'root' }
|
||||
'surface.b': { kind: 'single'; scope: 'root' }
|
||||
'dynamic.a': { kind: 'single'; scope: 'root' }
|
||||
'dynamic.b': { kind: 'single'; scope: 'root' }
|
||||
'surface.injected': { kind: 'single'; scope: 'root'; inject: { token: string } }
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,17 @@ const Comp: SlotComponent<object> = () => null
|
||||
describe('dynamic-key escape hatch', () => {
|
||||
it('specDynamic reads wide-typed specs for string keys; undefined while undeclared', () => {
|
||||
const core = new SlotCore()
|
||||
expect(core.specDynamic('surface.a')).toBeUndefined()
|
||||
core.register({ name: 'root', children: { 'surface.a': { kind: 'single', scope: 'root' } } }, Comp as never)
|
||||
expect(core.specDynamic('surface.a')).toEqual({ kind: 'single', scope: 'root' })
|
||||
expect(core.specDynamic('dynamic.a')).toBeUndefined()
|
||||
core.register({ name: 'root', children: { 'dynamic.a': { kind: 'single', scope: 'root' } } }, Comp as never)
|
||||
expect(core.specDynamic('dynamic.a')).toEqual({ kind: 'single', scope: 'root' })
|
||||
expect(core.specDynamic('never.declared')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('spec() narrows by SlotMap key', () => {
|
||||
const core = new SlotCore()
|
||||
core.register({ name: 'root', children: { 'surface.a': { kind: 'single', scope: 'root' } } }, Comp as never)
|
||||
expect(core.spec('surface.a')).toEqual({ kind: 'single', scope: 'root' })
|
||||
expect(core.spec('surface.b')).toBeUndefined()
|
||||
core.register({ name: 'root', children: { 'dynamic.a': { kind: 'single', scope: 'root' } } }, Comp as never)
|
||||
expect(core.spec('dynamic.a')).toEqual({ kind: 'single', scope: 'root' })
|
||||
expect(core.spec('dynamic.b')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('records the parent-declared Slot inject on the runtime spec', () => {
|
||||
@@ -41,9 +41,9 @@ describe('dynamic-key escape hatch', () => {
|
||||
|
||||
it('entries/getVersion on an untouched key return the frozen empty array and 0', () => {
|
||||
const core = new SlotCore()
|
||||
expect(core.entries('surface.b')).toHaveLength(0)
|
||||
expect(core.entries('surface.b')).toBe(core.entries('surface.b'))
|
||||
expect(core.getVersion('surface.b')).toBe(0)
|
||||
expect(core.entries('dynamic.b')).toHaveLength(0)
|
||||
expect(core.entries('dynamic.b')).toBe(core.entries('dynamic.b'))
|
||||
expect(core.getVersion('dynamic.b')).toBe(0)
|
||||
})
|
||||
|
||||
it('isLive is false for entries the core never held', () => {
|
||||
@@ -103,7 +103,7 @@ declare function NarrowTakeover(props: PropsRuntime<'chain.takeover'> & { matche
|
||||
|
||||
describe('terminal-design type chain', () => {
|
||||
it('holds the positive chain and the compile-time negatives', () => {
|
||||
// Everything below is compile-surface only.
|
||||
// Everything below is compile-time only.
|
||||
const samples = (core: SlotCore, chat: ChatHandle, fp: FrameProps, cp: ConvProps, acts: BoundActions<ChatHandle>) => {
|
||||
// ── positive chain ─────────────────────────────────────────────
|
||||
// Frame: children + factory store + inject; actions arrive baked.
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-theme/README.md
|
||||
README.md: 7b81728b02c0dadcbd134a4bca20c49721a3dd52
|
||||
README.md: cab9961a6d703d600a856e71339cda7062d20d62
|
||||
README.zh.md: 81b64c356749b6ffe12694b218e92eaa483ff739
|
||||
|
||||
@@ -20,5 +20,5 @@ None; this package neither assembles nor sends a provider request.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **Third-party themes are a surface, not a product** — registering one means overriding same-named alias variables; no validation exists that an override set is complete.
|
||||
- **Third-party themes are an extension point, not a product** — registering one means overriding same-named alias variables; no validation exists that an override set is complete.
|
||||
- **The token sheets are the sole color authority** — values absent from cssdesign (for example the design's #4176E6 tab blue) are deliberately not appended; the nearest semantic token wins. Design-owner-approved additions are the exception and enter as a static step plus a semantic alias in the same change (`--dsw-static-blue-900` / `--dsw-alias-label-primary-bluish`).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* Real tsdown artifact shape: lib/client.js hands off through
|
||||
* window.__ModuleLoader__.load, resolves externals through the injected
|
||||
* require, returns the export surface (apply + inject), and a mounted apply
|
||||
* require, returns the exports (apply + inject), and a mounted apply
|
||||
* registers the view tab into a real SlotsService ring. Skips when dist/ is
|
||||
* not built (`pnpm --filter @deepseek-ai/dsh-client-ui-trajectory bundle`).
|
||||
*/
|
||||
@@ -52,24 +52,24 @@ describe('tsdown client artifact', () => {
|
||||
['@deepseek-ai/dsh-client-runtime/client', await import('@deepseek-ai/dsh-client-runtime/client')],
|
||||
['@deepseek-ai/dsh-client-ui-primitives', await import('@deepseek-ai/dsh-client-ui-primitives')],
|
||||
])
|
||||
const surface = handoff!.factory((spec) => {
|
||||
const exports = handoff!.factory((spec) => {
|
||||
if (!modules.has(spec)) throw new Error(`unexpected require: ${spec}`)
|
||||
return modules.get(spec)
|
||||
})
|
||||
return { handoff: handoff!, surface }
|
||||
return { handoff: handoff!, exports }
|
||||
}
|
||||
|
||||
it.skipIf(code === undefined)('hands off with the manifest id and a DI-require factory', async () => {
|
||||
const { handoff, surface } = await loadArtifact()
|
||||
const { handoff, exports } = await loadArtifact()
|
||||
expect(handoff.id).toBe(PLUGIN_ID)
|
||||
expect(surface.apply).toBeTypeOf('function')
|
||||
expect(surface.inject).toEqual([
|
||||
expect(exports.apply).toBeTypeOf('function')
|
||||
expect(exports.inject).toEqual([
|
||||
'slots', 'conversationEvents', 'conversationViews', 'sessions', 'locale',
|
||||
])
|
||||
})
|
||||
|
||||
it.skipIf(code === undefined)('mounted as an object plugin, apply registers the view tab on the real ring', async () => {
|
||||
const { surface } = await loadArtifact()
|
||||
const { exports } = await loadArtifact()
|
||||
const ctx = new Context()
|
||||
const slots = new SlotsService(ctx)
|
||||
await ctx.plugin(ConversationEventRegistry).await()
|
||||
@@ -87,7 +87,7 @@ describe('tsdown client artifact', () => {
|
||||
ctx.provide('connection', { api: { settings: {} }, isLoopback: false } as never)
|
||||
const locale = await import('@deepseek-ai/dsh-client-locale/client')
|
||||
ctx.plugin({ inject: [...locale.inject], apply: locale.apply })
|
||||
const fiber = ctx.plugin(surface as { apply: (ctx: Context) => void })
|
||||
const fiber = ctx.plugin(exports as { apply: (ctx: Context) => void })
|
||||
await fiber.await()
|
||||
const events = ctx.get('conversationEvents') as ConversationEventRegistry
|
||||
const views = ctx.get('conversationViews') as ConversationViewRegistry
|
||||
|
||||
@@ -6,7 +6,7 @@ export { bindSnapshotSelector } from './bind.ts'
|
||||
/**
|
||||
* Selector hook over a session's conversation snapshot. Wide (`object`) by
|
||||
* default inside this dependency-inverted package; runtime narrows it once at
|
||||
* its export surface (`UseSession<ConversationSnapshot>`) — the snapshot type
|
||||
* its exports (`UseSession<ConversationSnapshot>`) — the snapshot type
|
||||
* never flows back into web-react.
|
||||
*/
|
||||
export type UseSession<Snap extends object = object> = SnapshotSelectorHook<Snap>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const HostContext = createContext<SlotRendererHost | null>(null)
|
||||
/**
|
||||
* Read the installed renderer host; throws outside the rendered root tree
|
||||
* (framework components must not render detached from the renderer).
|
||||
* @returns the host surface.
|
||||
* @returns the host API.
|
||||
*/
|
||||
export function useHost(): SlotRendererHost {
|
||||
const host = useContext(HostContext)
|
||||
@@ -132,7 +132,7 @@ export function SessionMaybeProvider({ children }: { children: ReactNode }) {
|
||||
)
|
||||
}
|
||||
|
||||
/** SessionProvider surface: render-prop body plus the no-session branch. */
|
||||
/** SessionProvider API: render-prop body plus the no-session branch. */
|
||||
export interface SessionProviderProps {
|
||||
/** No-session body (also covers a current id whose session cannot be resolved). */
|
||||
empty?: (() => ReactNode) | undefined
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Integration against the real ui-slots SlotCore through a passthrough host:
|
||||
* registrations go through the real register() (options form, children
|
||||
* declaration), and the outlets ride the real subscribe/getVersion/entries/
|
||||
* isLive surfaces — microtask-batched notifications, mutation-stable entry
|
||||
* isLive APIs — microtask-batched notifications, mutation-stable entry
|
||||
* references (the cache axis), and ledger-fed stale bindings are the
|
||||
* real-core semantics the fake-host suite cannot vouch for.
|
||||
*/
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/web/README.md
|
||||
README.md: 48355a046910bd5b78249af7b7781dab6a7c3a60
|
||||
README.md: 4b44e0e2c2e192363ce32ac2decc218f5ea17bd2
|
||||
README.zh.md: f0b567569849c698e8a1a29323e7088b49996522
|
||||
|
||||
@@ -6,7 +6,7 @@ Web shell kernel: `new AppWebEntry(el, seams?).run()` mounts the whole client th
|
||||
|
||||
Shell self-sufficiency (web2 hard rule): the kernel value-imports no plugin package — the boot status store and signals are hand-rolled here (`loader-status.ts`), so the loading page works while (and especially when) plugins fail. The app-shell assembly (`@deepseek-ai/dsh-client-app-shell`, a shell-owned pseudo entry with no npm package behind it) is the only module registered through `registerStatic`; it inject-waits on slots/sessions/layout like any plugin.
|
||||
|
||||
`PLATFORM_MODULES` (src/platform.ts) is the single source of truth for the shared module surface: seed-table keys, tsdown client externals, and the vite alias set are its projections.
|
||||
`PLATFORM_MODULES` (src/platform.ts) is the single source of truth for shared modules: seed-table keys, tsdown client externals, and the Vite alias set are its projections.
|
||||
|
||||
The optional override parameter `seams` forwards the module system's `loadBundle` transport override (`BootSeams`) for environments where external `<script>` execution cannot reach the page context; ordinary browser callers omit it.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { PlatformModule } from './platform.ts'
|
||||
|
||||
/**
|
||||
* Build the static table handed to the module loader at boot.
|
||||
* @returns module specifier → export-surface entity (one entry per platform word).
|
||||
* @returns module specifier → exported entity (one entry per platform word).
|
||||
*/
|
||||
export function getStaticModules(): Record<string, unknown> {
|
||||
// The satisfies pin is the projection contract: a word added to
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/code-runtime/code-runtime-worker/README.md
|
||||
README.md: 590b79dcd1bc322350060b55767b09c6305edacc
|
||||
README.md: 44ba6a98169057f24d55f41cf3ca7b9f68bb329c
|
||||
README.zh.md: e45aa484c0935d5b4b1626f5c4c7c72482cd5b5d
|
||||
|
||||
@@ -34,7 +34,7 @@ Every field is validated and defaulted; `maxOutputBytes` is a safe integer of at
|
||||
|
||||
Source mode loads erasable-only `src/worker.ts` through Node's native type stripping. Its transitive runtime closure contains only Node built-ins and relative source modules, so a fresh checkout never requires a sibling workspace package's unbuilt `lib/` export. The worker-local and session-owned JSON boundaries both flatten and rebuild validated values around the message port so application nesting never reaches structured clone. Built mode passes the sibling `lib/worker.cjs` as a filesystem path because pkg's VFS Worker hook expects CommonJS; the same path works under ordinary Node. The repository-wide requirement to exercise this published entry path belongs to the [testing policy](../../../docs/testing.md).
|
||||
|
||||
The SDK surface is the default/named `WorkerCodeRuntime` class plus `Config`. The operational `./worker` subpath exists only as the packaged spawn entry; the wire protocol and bootstrap helpers are source-private implementation details.
|
||||
The SDK API is the default/named `WorkerCodeRuntime` class plus `Config`. The operational `./worker` subpath exists only as the packaged spawn entry; the wire protocol and bootstrap helpers are source-private implementation details.
|
||||
|
||||
## Model Experience
|
||||
|
||||
@@ -49,6 +49,6 @@ No direct invalidation; the named consumer owns any request-prefix changes.
|
||||
- **OS processes a program spawns survive termination** — `worker.terminate()` ends the thread only, weaker than bash-local's process-group kill; orphan cleanup is a deployment concern until a container backend exists.
|
||||
- **Type-strip rides Node's experimental `stripTypeScriptTypes` API** — amaro or sucrase are the named drop-in replacements if the relied-on behavior shifts.
|
||||
- **`computeMs` expiry can overshoot by up to one poll interval** — busy time is sampled every 25 ms (an internal constant, deliberately not config).
|
||||
- **Programs get a five-method `console` shim** (`log`/`info`/`warn`/`error`/`debug`) — deliberately not Node's full console surface.
|
||||
- **Programs get a five-method `console` shim** (`log`/`info`/`warn`/`error`/`debug`) — deliberately not Node's full console API.
|
||||
- **Intermediate binding values have no byte cap** — a program can exhaust process or worker memory with a value that never becomes outer output.
|
||||
- **The 64 MiB default is a rejection boundary, not recoverable storage** — outer spill can save only the bounded logs and diagnostic returned after `output-limit`; bytes rejected beyond the runtime cap never reach the spill layer.
|
||||
|
||||
@@ -22,7 +22,7 @@ function defineBindingErrorField(error: Error, key: string, value: string): void
|
||||
capturedObjectDefineProperty(error, key, attributes)
|
||||
}
|
||||
|
||||
/** The port surface the bootstrap needs — satisfied by `parentPort` and by the tests' fake. */
|
||||
/** The port API the bootstrap needs — satisfied by `parentPort` and by the tests' fake. */
|
||||
export interface BootstrapPort {
|
||||
postMessage(message: WorkerToHost): void
|
||||
on(event: 'message', listener: (message: ReplyMessage) => void): void
|
||||
@@ -105,7 +105,7 @@ const CONSOLE_LEVELS = ['log', 'info', 'warn', 'error', 'debug'] as const
|
||||
* `util.inspect`-style (matching real console formatting closely enough for
|
||||
* a model to recognize its own output) into the buffer. Only these five
|
||||
* exist — the program gets a deliberately small console, not Node's full
|
||||
* surface.
|
||||
* console API.
|
||||
* @param logs - the buffer every rendered line is pushed into.
|
||||
* @returns the five-method console object handed to the program.
|
||||
*/
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/code-runtime/code-runtime/README.md
|
||||
README.md: 1d3cee958c186b7d0b2be1f69400977d009abc40
|
||||
README.md: ee64f7a16295bcf31e40eb305ca35aa774dac8d2
|
||||
README.zh.md: 9aac1196085d12dfe240af7667e98787f445c534
|
||||
|
||||
@@ -32,7 +32,7 @@ No direct invalidation; the named consumer owns any request-prefix changes.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **`run()` is one-shot** — `logs` arrive only on the resolved `CodeRunResult`; the seam exposes no streaming-log or progress surface for a live program's output.
|
||||
- **`run()` is one-shot** — `logs` arrive only on the resolved `CodeRunResult`; the seam exposes no streaming-log or progress API for a live program's output.
|
||||
- **A persistent REPL-style kernel is recorded future work** — the no-state-between-runs contract stands until a persistent-kernel backend brings its own logging story ([Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md)).
|
||||
- **Only the worker-thread backend ships** — `'process'`/`'container'` are declared well-known `isolation` values with no implementation; a hard security boundary awaits a container backend.
|
||||
- **Intermediate binding values have no byte cap** — implementations remain subject to structured-clone cost and process memory, while a provider or executor may already have imposed its own acquisition bound.
|
||||
|
||||
@@ -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/core/agent-loop/README.md
|
||||
README.md: 6092363fae2853d6c5d92aaf8cd01e41e18e0b52
|
||||
README.md: 889fd74671eddc202b814cdf2749069ec3cea02c
|
||||
README.zh.md: b65b5334d735a1e0b51fa517ce41c0c953f87cf7
|
||||
|
||||
@@ -12,7 +12,7 @@ This is the only package in the harness that contains concrete loop logic. Every
|
||||
|
||||
Creation and resume are one rollback-covered transaction: construct a private session, concrete agent, and scoped context; await optional setup; enter both registries; announce `session/created` then `agent/created`; emit `agent/session-start`; and only then start the driver. Setup receives the full scoped `Context` as trusted same-process composition code and must not drive the unpublished agent. Ordinary typed identity and option inputs are borrowed under their readonly contract, while seed events and session metadata are validated and snapshotted because they cross the durable session boundary. An optional `AbortSignal` cancels only load/setup/publication and is detached before the returned handle becomes visible.
|
||||
|
||||
The caller fiber and the AgentLoop provider are co-owners. `AgentFactory.createAgent(ownerCtx, options)` and `resume(ownerCtx, options)` receive caller ownership explicitly, while the factory keeps its own dependency context for `sessions`/`llm`/`tools`/`systemPrompt`; this lets a caller inject only `agents` without shrinking the new agent's service surface. Caller unload, handle disposal, or provider unload converge on one memoized quiescence boundary. Provider shutdown waits both resource teardown and the public create/resume wrapper that observed deactivation, so no continuation can publish after dependencies disappear.
|
||||
The caller fiber and the AgentLoop provider are co-owners. `AgentFactory.createAgent(ownerCtx, options)` and `resume(ownerCtx, options)` receive caller ownership explicitly, while the factory keeps its own dependency context for `sessions`/`llm`/`tools`/`systemPrompt`; this lets a caller inject only `agents` without shrinking the new agent's service set. Caller unload, handle disposal, or provider unload converge on one memoized quiescence boundary. Provider shutdown waits both resource teardown and the public create/resume wrapper that observed deactivation, so no continuation can publish after dependencies disappear.
|
||||
|
||||
Each agent and its session share one caller-chosen `SessionId`, assumed globally unique; accidental UUID collisions are outside the supported model. Two concurrent operations with the same id may both prepare, but the final `enter()` calls arbitrate publication and every loser rolls its private resources back. Each detach is bound to the exact entered object, so a stale disposer cannot remove a later same-id replacement. A detach requested during a synchronous creation notification waits for that dispatch to unwind, preserving created/disposed pairing. Teardown runs stop and drain → unwind scope → detach agent → detach session; the id becomes reusable after private scope cleanup. Ordinary non-vetoing `agent/*` notifications go through `agentEvents(ctx, agent)`, and per-step assembly goes through `assembleContextFor(agent)`.
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { MockAdapter, textResponse, toolCallResponse } from './mock-adapter.ts'
|
||||
* `agent/session-start`, `agent/turn-stopping`, and the
|
||||
* `tools/pre-execute` / `tools/post-execute`
|
||||
* split with `additionalContexts` buffering. These verify the canonical event
|
||||
* surface a hook bridge (or a native plugin) programs against, WITHOUT any
|
||||
* API a hook bridge (or a native plugin) programs against, WITHOUT any
|
||||
* external protocol — a native plugin uses the typed decisions directly.
|
||||
*/
|
||||
|
||||
|
||||
@@ -605,7 +605,7 @@ describe('agent scope lifecycle', () => {
|
||||
agentCtx.systemPrompt.section({
|
||||
name: 'dependency-origin-section',
|
||||
order: 1,
|
||||
text: 'factory dependency surface',
|
||||
text: 'factory dependency API',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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/core/agent/README.md
|
||||
README.md: 61294df5cca1b03f9f158678f2992a6e4fbaaffd
|
||||
README.zh.md: 5576a07330926cfd8fd5bd42c8a0351a375f035c
|
||||
README.md: f6b698e93b254c97786155e7d2c7e81f07c0d981
|
||||
README.zh.md: 8b108ca93b11ce5e4af57f9d9c823a4b10eac5ab
|
||||
|
||||
@@ -116,6 +116,6 @@ Prefix-stable while an agent's scoped registrations are unchanged. Setup or relo
|
||||
- **Ambient identity may outlive liveness** — consumers still check `agent.status`, cancellation, and the owning capability contract before lifecycle-sensitive work.
|
||||
- **Inter-agent channels beyond delegation** — shared state, streaming child output, and background/poll semantics remain outside the current synchronous `ctx.subagents` seam.
|
||||
- **`agent/session-start` cannot gate startup** — it remains a synchronous, veto-less notification; async composition that must finish before publication belongs in the factory's `setup(agentCtx)` transaction instead.
|
||||
- **`cancel()` clears the inbox by default** — it aborts the in-flight turn plus queued and steering work; `cancel(cause, { keepInbox: true })` aborts only the turn and preserves pending items. There is still no step-only abort that keeps the in-flight turn running ([stop-surface Agent Note](../../../.agents/notes/implemented/simplification/2026-06-20-public-agent-stop-surface.md)).
|
||||
- **`cancel()` clears the inbox by default** — it aborts the in-flight turn plus queued and steering work; `cancel(cause, { keepInbox: true })` aborts only the turn and preserves pending items. There is still no step-only abort that keeps the in-flight turn running ([stop API Agent Note](../../../.agents/notes/implemented/simplification/2026-06-20-public-agent-stop-api.md)).
|
||||
- **Each additional `UserMessage` carries exactly one `MessageSource`** — contributions from several plugins merged onto one tool call collapse under one source, so the message cannot name several producers.
|
||||
- **`SessionStartSource` reserves `'clear'`/`'compact'` with no emitter yet** — only `'startup'`/`'resume'` occur until the driving subsystems land (`TODO(compaction)`).
|
||||
|
||||
@@ -116,6 +116,6 @@ inbox 的实时通知刻意采用逐消息的最小载荷:`agent/inbox/inserte
|
||||
- **环境身份可能比存活状态更久**:消费方在生命周期敏感工作前,仍要检查 `agent.status`、取消状态和所属能力约定。
|
||||
- **委派以外的 agent 间通道**:共享状态、流式子输出和后台/轮询语义仍在当前同步 `ctx.subagents` seam 之外。
|
||||
- **`agent/session-start` 不能为启动设置门禁**:它仍是同步且不可 veto 的通知;必须在发布前完成的异步组合属于工厂的 `setup(agentCtx)` 事务。
|
||||
- **`cancel()` 默认清空 inbox**:它会中止正在处理的轮次以及排队和 steering 工作;`cancel(cause, { keepInbox: true })` 只中止轮次并保留待处理项。仍不存在只中止步骤、同时让正在处理的轮次继续运行的操作([关于停止操作接口的 Agent Note](../../../.agents/notes/implemented/simplification/2026-06-20-public-agent-stop-surface.md))。
|
||||
- **`cancel()` 默认清空 inbox**:它会中止正在处理的轮次以及排队和 steering 工作;`cancel(cause, { keepInbox: true })` 只中止轮次并保留待处理项。仍不存在只中止步骤、同时让正在处理的轮次继续运行的操作([关于停止操作接口的 Agent Note](../../../.agents/notes/implemented/simplification/2026-06-20-public-agent-stop-api.md))。
|
||||
- **每条附加 `UserMessage` 恰好携带一个 `MessageSource`**:多个插件合并到一次工具调用上的贡献会归入同一来源,因此该消息无法列出多个生产者。
|
||||
- **`SessionStartSource` 预留 `'clear'`/`'compact'`,但还没有发出方**:在驱动子系统落地前,只会出现 `'startup'`/`'resume'`(`TODO(compaction)`)。
|
||||
|
||||
@@ -160,7 +160,7 @@ export interface ResumeAgentOptions {
|
||||
* {@link AgentRegistry.resume}. The disposer is a CAPABILITY: among consumers,
|
||||
* only the holder can tear this agent down. The registered factory provider is
|
||||
* also a structural owner because the scoped agent depends on that provider's
|
||||
* service surface; provider unload stops and drains every live handle it made.
|
||||
* service API; provider unload stops and drains every live handle it made.
|
||||
* `dispose()` stops the loop, awaits its exit, unregisters the agent, removes
|
||||
* its session from the store, and finally unwinds its scoped world.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Negative-path tests for the export-surface JSDoc gate (`scripts/verify-export-jsdoc.ts`).
|
||||
* Negative-path tests for the exported-API JSDoc gate (`scripts/verify-export-jsdoc.ts`).
|
||||
*/
|
||||
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
@@ -46,7 +46,7 @@ export function hiddenFn(value: string): string { return value }
|
||||
expect(violations.every(violation => violation.includes('publicFn'))).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts a fully documented surface', () => {
|
||||
it('accepts a fully documented API', () => {
|
||||
expect(collectExportJsdocViolations(make(`
|
||||
/**
|
||||
* Add one to a count.
|
||||
@@ -106,7 +106,7 @@ export const halve = (n: number): number => n / 2
|
||||
'/**\n * Docs.\n * @param ghost - not real.\n */\nexport function f({ a }: { a: number }): void {}\n',
|
||||
))
|
||||
expect(violations).toEqual([
|
||||
expect.stringMatching(/parameter '\{ a \}' is a binding pattern; the export surface needs simple identifier parameters/),
|
||||
expect.stringMatching(/parameter '\{ a \}' is a binding pattern; the exported API needs simple identifier parameters/),
|
||||
expect.stringMatching(/@param ghost does not match any parameter \(stale tag\?\)/),
|
||||
])
|
||||
})
|
||||
@@ -166,9 +166,9 @@ describe('verify-export-jsdoc export forms', () => {
|
||||
))).toEqual([expect.stringMatching(/exported function 'f' .* has no JSDoc\./)])
|
||||
})
|
||||
|
||||
it('does not treat a never-exported sibling declarator as surface', () => {
|
||||
it('does not treat a never-exported sibling declarator as API', () => {
|
||||
// `export { publicValue }` resolves to the whole variable statement; only
|
||||
// the named declarator is surface — the gate must not demand JSDoc for
|
||||
// the named declarator is exported — the gate must not demand JSDoc for
|
||||
// the private sibling sharing the statement.
|
||||
expect(collectExportJsdocViolations(make(
|
||||
'/** The public knob. */\nconst publicValue = 1, privateHelper = 2\nexport { publicValue }\nvoid privateHelper\n',
|
||||
@@ -177,7 +177,7 @@ describe('verify-export-jsdoc export forms', () => {
|
||||
|
||||
it('unions declarators across multiple export lists over one statement', () => {
|
||||
// Two lists each name one declarator of the same undocumented statement:
|
||||
// both are surface (deduplicating on first resolution would drop `b`),
|
||||
// both are exported (deduplicating on first resolution would drop `b`),
|
||||
// while the never-exported `c` stays out.
|
||||
const violations = collectExportJsdocViolations(make(
|
||||
'const a = 1, b = 2, c = 3\nexport { a }\nexport { b }\nvoid c\n',
|
||||
@@ -344,7 +344,7 @@ describe('verify-export-jsdoc fail-closed forms', () => {
|
||||
))).toEqual([])
|
||||
})
|
||||
|
||||
it('treats an inline function-type annotation as the surface signature', () => {
|
||||
it('treats an inline function-type annotation as the API signature', () => {
|
||||
expect(collectExportJsdocViolations(make(
|
||||
'/** Maps a number. */\nexport declare const f: (x: number) => number\n',
|
||||
))).toEqual([
|
||||
@@ -402,7 +402,7 @@ describe('verify-export-jsdoc fail-closed forms', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('treats a single-call-signature type literal as the surface signature', () => {
|
||||
it('treats a single-call-signature type literal as the API signature', () => {
|
||||
expect(collectExportJsdocViolations(make(
|
||||
'/** Maps. */\nexport declare const f: { (x: number): number }\n',
|
||||
))).toEqual([
|
||||
|
||||
@@ -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/core/scope/README.md
|
||||
README.md: a8fbe97ae3b59f223bb52e44860439803fda420c
|
||||
README.md: 1ed09acd7ccb864b16fb5d2ac4390264085d1c9e
|
||||
README.zh.md: af238232987c74e89cdc4e009d3d0c40f71b02d8
|
||||
|
||||
@@ -28,10 +28,10 @@ The registration context determines both visibility and ownership, preventing a
|
||||
|
||||
Scope-aware services define a concrete `ScopeLayer` that aggregates their heterogeneous tables and domain helpers. `ScopedLayers.effect()` accepts one synchronous action returning one synchronous undo, installs that undo before optional notification, and reclaims an exact-scope layer only when the complete aggregate is empty. `notify` defaults to `true`; the supplied callback owns whether observer failures throw or are contained. `EntryValues` remains internal, the storage classes are imported from the package root rather than a `/store` subpath, and the shared storage does not define registry-specific filtering or iteration policy. See the [shared scoped-layer storage Agent Note](../../../.agents/notes/implemented/architecture/2026-07-12-scoped-layers-store.md).
|
||||
|
||||
Handing out a scoped context hands out the minting plugin's service-resolution surface (resolution walks the minting fiber's dependency chain, not the holder's) — mint it from the plugin whose dependencies the scoped registrations need to resolve.
|
||||
Handing out a scoped context hands out the minting plugin's service-resolution API (resolution walks the minting fiber's dependency chain, not the holder's) — mint it from the plugin whose dependencies the scoped registrations need to resolve.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **Only scope-aware surfaces isolate state** — registries must file by `scopeOf()` and events must dispatch through `scopeTarget()`; an arbitrary Cordis service remains context-global merely because it is called through a scoped context.
|
||||
- **Only scope-aware APIs isolate state** — registries must file by `scopeOf()` and events must dispatch through `scopeTarget()`; an arbitrary Cordis service remains context-global merely because it is called through a scoped context.
|
||||
- **A context carries one nearest scope key** — the hierarchy lives in the key-level parent relation, not in context tags; nested scope CONTEXTS still shadow to a single tag, and multi-membership policy sets remain unsupported.
|
||||
- **Service reachability comes from the scope minter** — handing out `Scope.ctx` also hands out the minting plugin's injected service surface, so a broader minter cannot later be narrowed by the holder.
|
||||
- **Service reachability comes from the scope minter** — handing out `Scope.ctx` also hands out the minting plugin's injected services, so a broader minter cannot later be narrowed by the holder.
|
||||
|
||||
@@ -128,8 +128,8 @@ export interface CreateScopeOptions {
|
||||
|
||||
/**
|
||||
* Mint a scope under `ctx`. The scoped context inherits the minting plugin's
|
||||
* dependency surface and owns every registration made through it.
|
||||
* @param ctx - active context whose dependency surface the scope inherits.
|
||||
* dependency API and owns every registration made through it.
|
||||
* @param ctx - active context whose dependency API the scope inherits.
|
||||
* @param key - opaque identity used for listener routing.
|
||||
* @param options - optional scope-chain placement.
|
||||
* @returns the scoped context and exact/shared disposal boundaries.
|
||||
|
||||
@@ -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/core/system-prompt/README.md
|
||||
README.md: cedda783d549633f5be9765a9a074e968d99500d
|
||||
README.md: e7757cda02b75f1a7a24f8f68b3c25c2f442d3d3
|
||||
README.zh.md: 41729cdd1cfe6ebbd86f38c15bab5c50bd6ff7d2
|
||||
|
||||
@@ -83,5 +83,5 @@ Prefix-stable while the visible schema set, rendering, and order are unchanged.
|
||||
|
||||
- **Deployment-authored prompt text is config/composition only** — this plugin owns the global persona default, creator plugins may register agent-scoped shadows, and other sections come from the plugin that owns the fact; there is no end-user prompt-editing API.
|
||||
- **No escape syntax for literal `{{…}}` braces** — every complete group is interpolated against registered variables; an escape is deferred until a real prompt needs one.
|
||||
- **`toolOrder` misconfiguration surfaces at prompt assembly (the first turn), not at boot** — only shape violations throw at config load.
|
||||
- **`toolOrder` misconfiguration APIs at prompt assembly (the first turn), not at boot** — only shape violations throw at config load.
|
||||
- **Sections sharing an `order` value tie-break by registration order** — a plugin-load artifact; determinism relies on the distinct-order band convention, unlike the canonicalized tool order.
|
||||
|
||||
@@ -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/core/tools/README.md
|
||||
README.md: cc7b323ae1de917e93e243e97bd5cf5937ecdca4
|
||||
README.md: 75d18712a02ecc72c2ea3a7203d2a7377cef87c7
|
||||
README.zh.md: 8d4ae42596483f77aa82b23a0b41168465e2b165
|
||||
|
||||
@@ -111,7 +111,7 @@ Tools optionally own pure `presentCall()` and `presentResult()` render intents,
|
||||
- Call views are `{ card: 'generic', title, kind?, rawInput?, content?, locations? }`, `{ card: 'terminal', title, description?, cwd? }`, or `{ card: 'diff', title, diffs, locations? }`.
|
||||
- Result views are `{ card: 'generic', title?, content? }`, `{ card: 'terminal', title?, output?, exitCode?, signal? }`, `{ card: 'diff', title?, diffs }`, `{ card: 'search', shape, title?, truncated, total, … }` (a completed discovery search — grouped-by-file matches for `shape: 'matches'` (grep) or a flat path list for `shape: 'paths'` (glob), with `truncated`/`total` so a UI never presents a capped result as complete; the view carries no result text and a search has no `card: 'search'` call-time analogue), `{ card: 'read', title?, path, offset, lines, totalLines, lang?, content? }` (a completed file read → a line-numbered, optionally syntax-highlighted code view; `offset` is the 1-based first line the window requested, kept even when `lines` is empty; `lines` is `{ number, text }[]` keeping each file line number, and `content` is the envelope-stripped text a UI without read support falls back to), or `{ card: 'web', kind: 'search' | 'fetch', title?, … }` (a completed web retrieval; the `kind` arms carry the structured search sources or the fetch summary, and a UI without the `web` capability falls back to the raw result content).
|
||||
|
||||
Returning `undefined` selects generic fallback. Presenters depend only on their arguments and the durable result because UIs call them during live streaming and log replay. `output.presentationMeta(args, value)` derives JSON metadata for direct surface calls; that metadata persists with `tool/result` and returns to `presentResult`, while the canonical value itself remains execution-local and is never replayed. Nested Code dispatches do not compute metadata. `defineTool` soft-validates older logged arguments and falls back instead of crashing replay. `dsh-tool-bash` and `dsh-tool-fs` are the reference implementations; the [canonical-output Agent Note](../../../.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md) owns the value/presentation split and the [render-intent Agent Note](../../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md) owns card vocabulary.
|
||||
Returning `undefined` selects generic fallback. Presenters depend only on their arguments and the durable result because UIs call them during live streaming and log replay. `output.presentationMeta(args, value)` derives JSON metadata for direct top-level calls; that metadata persists with `tool/result` and returns to `presentResult`, while the canonical value itself remains execution-local and is never replayed. Nested Code dispatches do not compute metadata. `defineTool` soft-validates older logged arguments and falls back instead of crashing replay. `dsh-tool-bash` and `dsh-tool-fs` are the reference implementations; the [canonical-output Agent Note](../../../.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md) owns the value/presentation split and the [render-intent Agent Note](../../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md) owns card vocabulary.
|
||||
|
||||
### Code Mode
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ type RunCodeOutput = { logs: string[]; result?: JsonValue }
|
||||
/**
|
||||
* Registry-private capabilities the bridge receives at construction — the
|
||||
* `requireRuntime` idiom: operations only the owning registry can mint stay
|
||||
* off its public service surface and flow here as closures instead.
|
||||
* off its public service API and flow here as closures instead.
|
||||
*/
|
||||
export interface RunCodeBridgeOptions {
|
||||
/** Resolves `ctx.codeRuntime` or throws the loud misconfiguration error (shared with the registry's assembly-time checks). */
|
||||
@@ -646,7 +646,7 @@ export function createRunCodeTool(registry: ToolRegistry, options: RunCodeBridge
|
||||
kind: 'execute',
|
||||
rawInput: args.code,
|
||||
}),
|
||||
// Deliberately no presentResult: the generic surface fallback keeps this
|
||||
// Deliberately no presentResult: the generic card fallback keeps this
|
||||
// title and reads durable result content without duplicating a large raw
|
||||
// result into the host view payload.
|
||||
})
|
||||
|
||||
@@ -94,7 +94,7 @@ export { defineContentToolFixture, type ContentToolFixtureOptions } from './test
|
||||
|
||||
// The render-intent vocabulary a tool declares via `presentCall`/`presentResult`
|
||||
// lives in its own UI-facing module; re-export it so `@deepseek-ai/dsh-tools`
|
||||
// stays the single public surface for tool producers and UI adapters.
|
||||
// stays the single public API for tool producers and UI adapters.
|
||||
export type {
|
||||
ToolCallKind,
|
||||
FileLocation,
|
||||
@@ -200,7 +200,7 @@ export interface ToolOutputDefinition {
|
||||
readonly schema: JsonSchemaNode
|
||||
/** Pure projection from validated arguments and value to Native/model content. */
|
||||
render(args: unknown, value: JsonValue): ContentBlock[]
|
||||
/** Pure replayable presentation projection, computed only for surface calls. */
|
||||
/** Pure replayable presentation projection, computed only for top-level calls. */
|
||||
presentationMeta?(args: unknown, value: JsonValue): JsonValue
|
||||
}
|
||||
|
||||
@@ -1009,7 +1009,7 @@ export class ToolRegistry extends Service {
|
||||
* Restrict global tools for the calling agent scope. Empty filters, unknown
|
||||
* names, scope-local names, and reserved transport names fail. Restrictions
|
||||
* intersect; scoped registrations remain visible.
|
||||
* @param filter - global-surface mask: `allow` (keep only) and/or `deny` (remove).
|
||||
* @param filter - global-tool mask: `allow` (keep only) and/or `deny` (remove).
|
||||
* @returns the exact disposer that lifts this restriction.
|
||||
*/
|
||||
restrict(filter: ToolRestriction): () => void {
|
||||
|
||||
@@ -60,7 +60,7 @@ export interface GenericCallView {
|
||||
/** Category for icon/treatment; defaults to `other` when omitted. */
|
||||
kind?: ToolCallKind
|
||||
/**
|
||||
* The salient input to surface in a detail/expanded view (e.g. a background
|
||||
* The salient input to show in a detail/expanded view (e.g. a background
|
||||
* task id). Omit to show nothing; a string renders as-is, an object as pretty
|
||||
* JSON. NOT the full raw args object unless that is genuinely what a reader wants.
|
||||
*/
|
||||
|
||||
@@ -493,7 +493,7 @@ export interface DefineToolOptions<S extends ParameterSchemaSpec, O extends Valu
|
||||
readonly schema: O
|
||||
/** Pure Native/model rendering of one validated canonical value. */
|
||||
render(args: InferArgs<S>, value: InferValue<NoInfer<O>>): ContentBlock[]
|
||||
/** Pure replayable presentation metadata for direct surface calls. */
|
||||
/** Pure replayable presentation metadata for direct top-level calls. */
|
||||
presentationMeta?(args: InferArgs<S>, value: InferValue<NoInfer<O>>): JsonValue
|
||||
}
|
||||
/** Optional positive cooperative timeout budget in milliseconds. */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Code Mode codegen: the pure projection from registered tool schemas to the TypeScript SDK
|
||||
* text the model programs against (the `tools:sdk` prompt section). Sibling of
|
||||
* `json-schema.ts` — `schemas()` (native function calling) and this module (the generated
|
||||
* `declare const tools` surface) are two projections of the same store.
|
||||
* `declare const tools` API) are two projections of the same store.
|
||||
* @module @deepseek-ai/dsh-tools/src/ts-types
|
||||
*/
|
||||
|
||||
|
||||
@@ -1229,7 +1229,7 @@ describe('the run_code dispatch bridge', () => {
|
||||
const tool = ctx.tools.get(RUN_CODE_NAME)!
|
||||
|
||||
expect(result.content).toEqual([{ type: 'text', text }])
|
||||
// Surfaces keep the pending program title and render this durable content
|
||||
// Presenters keep the pending program title and render this durable content
|
||||
// through their generic fallback. Omitting a result view also prevents the
|
||||
// host frame from carrying the same raw content a second time.
|
||||
expect('presentResult' in tool).toBe(false)
|
||||
|
||||
@@ -2593,7 +2593,7 @@ describe('defineTool validation (the runtime-validation Agent Note, part 1)', ()
|
||||
expect(err.message).toBe('invalid arguments: missing required property "a"; "b" must be a number')
|
||||
})
|
||||
|
||||
it('a schema-invalid call surfaces the structured error on the result', async () => {
|
||||
it('a schema-invalid call APIs the structured error on the result', async () => {
|
||||
const ctx = await setup()
|
||||
ctx.tools.register(defineContentToolFixture({
|
||||
name: 'reader',
|
||||
|
||||
@@ -65,7 +65,7 @@ export interface Config {
|
||||
skills?: agentCore.SkillConfig
|
||||
/** Model-facing bash tool config forwarded through agent-core. */
|
||||
toolBash?: NonNullable<agentCore.Config['toolBash']>
|
||||
/** Generic background-task controls forwarded through agent-core; set false to omit their tool surface. */
|
||||
/** Generic background-task controls forwarded through agent-core; set false to omit their tools. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/** Persisted same-session goals; owner defaults enable them, or false disables the stack and tools. */
|
||||
goals?: agentCore.GoalConfig | false
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/feedback/README.md
|
||||
README.md: 152db65bd7ac179bb8d475d446535734b9f8238e
|
||||
README.zh.md: 64202a3c4a0258f9b41a6cd96e7bbbf011d0338d
|
||||
README.md: b4dcafd0987b0045b4c966bb218f70811b2bfd2e
|
||||
README.zh.md: 7b1a4b6335e4c06e1833834daed663c1111cd49b
|
||||
|
||||
@@ -9,6 +9,6 @@ The feedback family exposes two deliberately separate contracts: an immutable re
|
||||
| `command-feedback/` | Trigger-independent `feedback/record` event plus the human-facing `/feedback` producer | — |
|
||||
| `message-feedback/` | Lifecycle-bound per-message rating/note sidecar plus Host `messageFeedback.list/put/delete` Remote contract | `messageFeedback` |
|
||||
|
||||
A command feedback remark is log-only: it never enters the model surface or derived history. When mounted, [`dsh-session-telemetry-otel`](../session/session-telemetry-otel) observes `feedback/record` to release a pending telemetry prefix or warn that disabled telemetry leaves the feedback local; capture itself remains independent of that policy.
|
||||
A command feedback remark is log-only: it never enters model context or derived history. When mounted, [`dsh-session-telemetry-otel`](../session/session-telemetry-otel) observes `feedback/record` to release a pending telemetry prefix or warn that disabled telemetry leaves the feedback local; capture itself remains independent of that policy.
|
||||
|
||||
Message feedback is not a Session event or projection. It remains in the storage-domain sidecar and causes no telemetry handoff. The Host Remote contract ships with the service; the client Remote aggregate mount and UI consumer are separately owned and deferred.
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
| `command-feedback/` | 与触发方式无关的 `feedback/record` 事件,以及面向用户的 `/feedback` 生产方 | 无 |
|
||||
| `message-feedback/` | 绑定生命周期的逐消息评分/备注伴随记录,以及 Host `messageFeedback.list/put/delete` Remote 契约 | `messageFeedback` |
|
||||
|
||||
command feedback 评价仅写入日志:它绝不会进入模型接口或派生历史。挂载后,[`dsh-session-telemetry-otel`](../session/session-telemetry-otel) 会观察 `feedback/record`,以释放待处理的遥测前缀,或在遥测已禁用时警告反馈将留在本地;采集本身与该策略相互独立。
|
||||
command feedback 评价仅写入日志:它绝不会进入模型上下文或派生历史。挂载后,[`dsh-session-telemetry-otel`](../session/session-telemetry-otel) 会观察 `feedback/record`,以释放待处理的遥测前缀,或在遥测已禁用时警告反馈将留在本地;采集本身与该策略相互独立。
|
||||
|
||||
message feedback 不是 Session 事件或投影。它只保留在 storage-domain 伴随记录中,不触发任何遥测交接。服务随附 Host Remote 契约;客户端 Remote 聚合挂载与 UI 消费方由各自边界负责,并保持延后。
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user