diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml index 7f14684d59..1939ff2f17 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-18-session-surface.md -2026-06-18-session-surface.md: 93ea55883dedd943fe1ffac67a9842c962ca6dac -2026-06-18-session-surface.zh.md: 54ecb1162bc46007dfcbb7d8cb39075d52171567 +2026-06-18-session-surface.md: 26780d54a1ea12283900572c6fd0e8f740de5524 +2026-06-18-session-surface.zh.md: 328a9ee9f1bed9c52996cfd307549a7745e16d4b diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md index 93ea55883d..26780d54a1 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md @@ -37,7 +37,7 @@ Delta processing is O(1) when no new events and O(new events) when new events ar ### Persistence -The fields are serialized as top-level JSON properties. JSONL preserves placement and provenance without a separate column mapping. The [V3 canonical-envelope decision](2026-09-06-v3-canonical-session-envelopes.md) owns exact replacement keys and strict-acceptance rationale; the [V2-to-V3 specification](../../../../packages/session/session-format-v2-to-v3/README.md#canonical-envelopes) owns historical conversion. This note retains ordered-projection ownership and replacement rationale. +The fields are serialized as top-level JSON properties. JSONL preserves placement and source-event references without a separate column mapping. The [V3 canonical-envelope decision](2026-09-06-v3-canonical-session-envelopes.md) owns exact replacement keys and strict-acceptance rationale; the [V2-to-V3 specification](../../../../packages/session/session-format-v2-to-v3/README.md#canonical-envelopes) owns historical conversion. This note retains ordered-projection ownership and replacement rationale. ### Crash recovery diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md index 54ecb1162b..328a9ee9f1 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md @@ -37,7 +37,7 @@ surface 元数据仅属于四种 surface 事件类型(`system/message`、`user ### 持久化 -这些字段作为顶层 JSON 属性序列化。JSONL 无需单独列映射即可保留位置与来源。[V3 规范信封决策](2026-09-06-v3-canonical-session-envelopes.zh.md)负责精确替换键与严格准入依据;[V2 到 V3 规范](../../../../packages/session/session-format-v2-to-v3/README.zh.md#canonical-envelopes)负责历史转换。本文继续负责有序投影的所有权与替换依据。 +这些字段作为顶层 JSON 属性序列化。JSONL 无需单独列映射即可保留位置与源事件引用。[V3 规范信封决策](2026-09-06-v3-canonical-session-envelopes.zh.md)负责精确替换键与严格准入依据;[V2 到 V3 规范](../../../../packages/session/session-format-v2-to-v3/README.zh.md#canonical-envelopes)负责历史转换。本文继续负责有序投影的所有权与替换依据。 ### 崩溃恢复 diff --git a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml index 9f08a92826..ac7f2acd4e 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md -2026-07-05-reconstructable-requests.md: 2f88675a75a72e7fbf105dfbf4f337a4dd80948a -2026-07-05-reconstructable-requests.zh.md: 90008502a6651e38c142b7fb88052c05d46dea76 +2026-07-05-reconstructable-requests.md: f47d0938b91edf6c9b544223ac44925f02dd8c1f +2026-07-05-reconstructable-requests.zh.md: d2499a6264a3e6bebf31944d8eb056265e082c68 diff --git a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md index 2f88675a75..f47d0938b9 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md +++ b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md @@ -22,9 +22,9 @@ Prefix-cache stability is corollary #1, not the headline: an append-only log pro **Messages.** `Session.deriveMessages()` is cached: each surface entry is projected exactly once, when first seen, through the public per-event function `deriveEventMessage(event)`; a surface rewrite (a compaction `replace` — `SurfaceManager.replaceGeneration`) rebuilds. Callers get a fresh array per call over shared, deep-frozen messages: mutating logged history through a projection is unrepresentable (it throws), replacing the old clone-per-call isolation. External reconstructors fold the same public function over a log prefix, so no two paths can disagree. -`EpochHeader` records the request's non-history state: call config and tool schemas. Writers omit `tools: []` and `adapterDefaults: {}`; current acceptance rejects those fields and any `header.system`, rather than repairing them. Whitespace-only system-message content, `config.stop: []`, and nested extensions remain intact. The [V3 canonical-envelope decision](2026-09-06-v3-canonical-session-envelopes.md) owns historical conversion. The rendered system prompt is derived history — the `system/message` event at surface node 0, per the [surface-node Agent Note](2026-09-02-system-prompt-as-surface-node.md) — so a prompt change is a surface replacement rather than a header change. Adapter-supplied effort and token defaults retain their `adapterDefaults` provenance; a Web model selection restored from the log omits an adapter-owned effort so the next resolution cannot reclassify the same effective config as an explicit selection and a false change. `request/header` always writes a full snapshot: the first loop instance uses reason `initial`, later instances use `resume`, an in-instance change uses `change`, and an unchanged envelope beginning an explicitly declared message series or following a surface replacement uses `series`. A `change` snapshot carries `startsSeries: true` when the changed request also starts a series, preserving the two independent facts without a duplicate header. Ordinary append-only later Turns, further same-series Steps, and retries inherit the latest snapshot. `foldRequestHeader` selects the latest snapshot. Legacy `request/header-delta` events and the removed `fallback` reason are rejected when appended or loaded. +`EpochHeader` records the request's non-history state: call config and tool schemas. Writers omit `tools: []` and `adapterDefaults: {}`; current acceptance rejects those fields and any `header.system`, rather than repairing them. Whitespace-only system-message content, `config.stop: []`, and nested extensions remain intact. The [V3 canonical-envelope decision](2026-09-06-v3-canonical-session-envelopes.md) owns historical conversion. The rendered system prompt is derived history — the `system/message` event at surface node 0, per the [surface-node Agent Note](2026-09-02-system-prompt-as-surface-node.md) — so a prompt change is a surface replacement rather than a header change. Adapter-supplied effort and token defaults retain their `adapterDefaults` ownership; a Web model selection restored from the log omits an adapter-owned effort so the next resolution cannot reclassify the same effective config as an explicit selection and a false change. `request/header` always writes a full snapshot: the first loop instance uses reason `initial`, later instances use `resume`, an in-instance change uses `change`, and an unchanged envelope beginning an explicitly declared message series or following a surface replacement uses `series`. A `change` snapshot carries `startsSeries: true` when the changed request also starts a series, preserving the two independent facts without a duplicate header. Ordinary append-only later Turns, further same-series Steps, and retries inherit the latest snapshot. `foldRequestHeader` selects the latest snapshot. Legacy `request/header-delta` events and the removed `fallback` reason are rejected when appended or loaded. -Each proposed step first claims its inbox batch, assembles the system prompt and tools, projects the rendered prompt against the surviving `system/message` node, and runs `agent/pre-step`. Rejection opens no step; enter opens `step/start`, commits a changed prompt as the `system/message` append or node-0 replacement, records the final message batch as `user/message` events, and may use `startsRequestSeries: true` to declare a distinct series. `agent/request` may replace only the frozen call-config seed. The loop records the owed initial, resume, change, or series full snapshot, builds `GenerateOptions` from derived messages (system message first) and that header with no `system` field, and freezes it while leaving `AbortSignal` live. The [request-freeze provenance decision](../simplification/2026-09-06-agent-request-freeze-provenance.md) owns reuse of completed message freezes and per-request local header freezing. The first call config starts from explicit `AgentOptions`, preserving fork overrides and resume reconfiguration; later calls start from the folded header. +Each proposed step first claims its inbox batch, assembles the system prompt and tools, projects the rendered prompt against the surviving `system/message` node, and runs `agent/pre-step`. Rejection opens no step; enter opens `step/start`, commits a changed prompt as the `system/message` append or node-0 replacement, records the final message batch as `user/message` events, and may use `startsRequestSeries: true` to declare a distinct series. `agent/request` may replace only the frozen call-config seed. The loop records the owed initial, resume, change, or series full snapshot, builds `GenerateOptions` from derived messages (system message first) and that header with no `system` field, and freezes it while leaving `AbortSignal` live. The [request-freeze evidence decision](../simplification/2026-09-06-agent-request-freeze-evidence.md) owns reuse of completed message freezes and per-request local header freezing. The first call config starts from explicit `AgentOptions`, preserving fork overrides and resume reconfiguration; later calls start from the folded header. **The open step is the reconstruction boundary.** Its entered `user/message` batch and any newly written `request/header` precede request dispatch. Injection after the atomic claim joins a later request, while a listener that must affect this request returns messages through `agent/pre-step`. Header reconstruction selects the step's `request/header`, or carries the prior snapshot when no new header is written. diff --git a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md index 90008502a6..d2499a6264 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md @@ -24,7 +24,7 @@ Status: implemented `EpochHeader` 记录请求的非历史状态:调用配置和工具 schema。写入方省略 `tools: []` 与 `adapterDefaults: {}`;当前接纳拒绝这些字段以及任何 `header.system`,而不修复它们。仅含空白的系统消息内容、`config.stop: []` 与嵌套扩展保持原样。[V3 规范信封决策](2026-09-06-v3-canonical-session-envelopes.zh.md)负责历史转换。渲染后的系统提示词是派生历史——surface 第 0 号节点上的 `system/message` 事件,见[surface 节点 Agent Note](2026-09-02-system-prompt-as-surface-node.zh.md)——因此提示词变更是 surface 替换而不是 header 变更。适配器提供的推理强度与 token 默认值会保留其 `adapterDefaults` 来源信息;Web 从日志恢复模型选择时会省略适配器持有的推理强度,因此下一次解析不会把相同的有效配置重新归类为显式选择并产生虚假变更。`request/header` 始终写入完整快照:首个循环实例使用 reason `initial`,后续实例使用 `resume`,实例内变更使用 `change`,内容未变的封装显式开启消息序列或跟随表层替换时使用 `series`。如果发生变化的请求同时开启序列,`change` 快照会携带 `startsSeries: true`,无需重复 header 即可保留这两个独立事实。普通的仅追加后续 Turn、同一序列内后续的 Step 与重试沿用最新快照。`foldRequestHeader` 选择最新快照。旧的 `request/header-delta` 事件和已移除的 `fallback` reason 在追加或加载时都会被拒绝。 -每个拟议步骤先领取其 inbox 批次,组装系统提示词与工具,把渲染后的提示词与存活的 `system/message` 节点比对投影,再运行 `agent/pre-step`。reject 不打开步骤;enter 打开 `step/start`,把变化的提示词作为 `system/message` 追加或第 0 号节点替换提交,把最终消息批次记录为 `user/message` 事件,并可使用 `startsRequestSeries: true` 声明独立序列。`agent/request` 只能替换冻结的调用配置种子。循环记录所需的 initial、resume、change 或 series 完整快照,从派生消息(系统消息在先)与该不含 `system` 字段的 header 构建 `GenerateOptions`,冻结请求但保持 `AbortSignal` 活跃。[请求冻结来源证明决策](../simplification/2026-09-06-agent-request-freeze-provenance.zh.md)拥有消息完整冻结的复用规则和每次请求的本地 header 冻结规则。首次调用配置从显式的 `AgentOptions` 出发,保留 fork 覆盖和恢复重配置;后续调用从折叠后的 header 出发。 +每个拟议步骤先领取其 inbox 批次,组装系统提示词与工具,把渲染后的提示词与存活的 `system/message` 节点比对投影,再运行 `agent/pre-step`。reject 不打开步骤;enter 打开 `step/start`,把变化的提示词作为 `system/message` 追加或第 0 号节点替换提交,把最终消息批次记录为 `user/message` 事件,并可使用 `startsRequestSeries: true` 声明独立序列。`agent/request` 只能替换冻结的调用配置种子。循环记录所需的 initial、resume、change 或 series 完整快照,从派生消息(系统消息在先)与该不含 `system` 字段的 header 构建 `GenerateOptions`,冻结请求但保持 `AbortSignal` 活跃。[请求冻结证据决策](../simplification/2026-09-06-agent-request-freeze-evidence.zh.md)拥有消息完整冻结的复用规则和每次请求的本地 header 冻结规则。首次调用配置从显式的 `AgentOptions` 出发,保留 fork 覆盖和恢复重配置;后续调用从折叠后的 header 出发。 **已打开步骤是重建边界。** 进入步骤的 `user/message` 批次与任何新写入的 `request/header` 都位于请求分派之前。原子领取后发生的注入加入后续请求;必须影响本次请求的监听器则通过 `agent/pre-step` 返回消息。header 重建选择该步骤的 `request/header`,或在无新 header 写入时沿用前一个快照。 diff --git a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml index 2522b59d89..072caec450 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md -2026-07-08-tool-output-spill-files.md: a80b6cdcb0e731a687d511d38ea288a68a298173 +2026-07-08-tool-output-spill-files.md: fa6697aad63c97b0ef580fe1ca46e50541e3d682 2026-07-08-tool-output-spill-files.zh.md: a995eafc7878b342a2164c5116da1f43ada791a5 diff --git a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md index a80b6cdcb0..fa6697aad6 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md +++ b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md @@ -22,7 +22,7 @@ A thin spill storage seam plus a default spill policy plugin, in a new `packages | `@deepseek-ai/dsh-spill-local` | Local backend: private, session-scoped file storage on the host filesystem. | | `@deepseek-ai/dsh-spill-policy` | Tool-result policy plugin: wraps final text results after dispatch and replaces oversized results with a retained preview plus a spill locator. | -The tool-result Consumer is `dsh-spill-policy`, which consumes final tool results through the `tools/post-execute` waterfall. The model follows the backend-supplied retrieval hint for the returned locator. [Session-reference spill reuse](../bug-fix/2026-09-05-session-reference-spill-reuse.md) adds a direct storage consumer with separate preview, provenance, and failure semantics; it does not change the tool-result policy. +The tool-result Consumer is `dsh-spill-policy`, which consumes final tool results through the `tools/post-execute` waterfall. The model follows the backend-supplied retrieval hint for the returned locator. [Session-reference spill reuse](../bug-fix/2026-09-05-session-reference-spill-reuse.md) adds a direct storage consumer with separate preview, source-description, and failure semantics; it does not change the tool-result policy. ### Spill seam diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml index f7987d80d7..003214c467 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md -2026-07-10-single-file-executable-sdk-runtime-distribution.md: 8c55c142137f0ab24869bd57ffed3835b7b0516a +2026-07-10-single-file-executable-sdk-runtime-distribution.md: e8c38a7b96cbff9feae2271fcd9a5c41087c62c4 2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 52ca2e13901d9469e2f9663936acb766a934e8f7 diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md index 8c55c14213..e8c38a7b96 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md @@ -46,7 +46,7 @@ The deploy root includes `@deepseek-ai/dsh-mcp-client` as an explicitly supporte [`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts): runtime closure verification → `pnpm run build` → (after clearing) `pnpm --filter dsh-python-runtime-closure deploy --legacy --prod --config.allow-unused-patches=true --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **directly into** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/`, including [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) as the carrier-root `runtime-bootstrap.mjs` → restore direct workspace packages omitted by legacy deploy and reject any remaining manifest gap → replace staged dependency symlinks with their target bytes, remove package-manager `.bin` links, and fail if any symlink remains → verify the deployed bootstrap and inject pkg configuration with that bin plus assets covering dynamic profile, bundle, frontend, preset, native-library, and configuration reads → stage the target `node-pty` addon → invoke `pkg --sea` once per target → write `deepseek-harness-sdk-runtime--` under `dist-exe/` and copy it into the runtime directory. The Python runtime owns that bootstrap. It calls the public CLI export for ordinary launches and dispatches a provider-private selection to the same `@deepseek-ai/dsh-subprocess-local/runner` core without changing CLI grammar or adding another executable; the [native-containment decision](2026-08-28-subprocess-native-containment.md) owns that private path. Linux CI rebuilds `pty.node` inside the matching manylinux 2.28 container because legacy deploy omits that install side effect. Every target copies its native `@vscode/ripgrep` binary beside the executable as the required `-rg` sidecar; pkg runtimes select that sidecar through `process.pkg`, while ordinary Node execution uses `@vscode/ripgrep` directly. macOS uses its target prebuild and also emits the required `-spawn-helper`. The deploy flags preserve the measured runtime layout: `--legacy` is the mandatory path with inject-workspace-packages off; hoisted gives pkg a stable single-instance layout that the explicit materialization pass makes symlink-free; disabling automatic peer installation prevents undeclared peers from expanding the closure; link-workspace-packages selects direct workspace dependencies. [`pnpm-workspace.yaml`](../../../../pnpm-workspace.yaml) overrides the transitive `@deepseek-ai/cosmokit` and `@deepseek-ai/schemastery` semver requests to the pinned vendor sources so legacy deploy never resolves those unpublished names from a registry. Production deployment permits unused workspace patches because development-only tools can be absent from its closure; included-package patch failures still fail deployment. Workspace installs retain strict unused-patch validation. -CI: [`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml) runs [installed-wheel validation](../testing/2026-08-23-installed-python-wheel-black-box-ci.md) on Linux/Windows x64 for pull requests and Linux ARM64 plus both macOS architectures for master pushes. The [public publication workflow](../../archived/process/2026-08-11-python-publication-workflow.md) calls it for all five targets; `workflow_dispatch` can still select a subset. Native builds run on linux-x64 / linux-arm64 (`ubuntu-24.04-arm`) / macos-arm64 / macos-x64 (`macos-15-intel`) / win-x64 (`windows-2025`), with `~/.pkg-cache` cached where applicable, and pkg handles macOS ad-hoc signing. Each leg installs the release-shaped SDK and runtime wheels into a clean venv outside the checkout, proves their package and executable provenance, then drives the complete keyless scenario set through the public SDK and direct NDJSON JSON-RPC. Trusted pull requests and master pushes additionally run a real DeepSeek two-turn tool smoke on their selected targets; fork and Dependabot heads receive no key. Linux inspects the executable and native addon's GLIBC requirements and runs an additional manylinux 2.28 smoke, while macOS checks the runtime, ripgrep, and PTY helper architectures and verifies that all three deployment targets fit the wheel tag. A full five-target run retains six artifacts, each containing one release file: the platform-independent SDK wheel and five native runtime wheels; a subset dispatch retains the SDK wheel and selected runtime wheels. Bare executables and source bundles remain intermediate test inputs. [`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) accepts `python-v` tag pipelines whose version matches the root `package.json`, builds one SDK wheel and five native runtime wheels, then a single serialized job checks and publishes all six to the project PyPI registry. The [`python/sdk-runtime` README](../../../../python/sdk-runtime/README.md) owns the Windows target and the explicit exclusion of Windows arm64. +CI: [`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml) runs [installed-wheel validation](../testing/2026-08-23-installed-python-wheel-black-box-ci.md) on Linux/Windows x64 for pull requests and Linux ARM64 plus both macOS architectures for master pushes. The [public publication workflow](../../archived/process/2026-08-11-python-publication-workflow.md) calls it for all five targets; `workflow_dispatch` can still select a subset. Native builds run on linux-x64 / linux-arm64 (`ubuntu-24.04-arm`) / macos-arm64 / macos-x64 (`macos-15-intel`) / win-x64 (`windows-2025`), with `~/.pkg-cache` cached where applicable, and pkg handles macOS ad-hoc signing. Each leg installs the release-shaped SDK and runtime wheels into a clean venv outside the checkout, verifies their package and executable origin, then drives the complete keyless scenario set through the public SDK and direct NDJSON JSON-RPC. Trusted pull requests and master pushes additionally run a real DeepSeek two-turn tool smoke on their selected targets; fork and Dependabot heads receive no key. Linux inspects the executable and native addon's GLIBC requirements and runs an additional manylinux 2.28 smoke, while macOS checks the runtime, ripgrep, and PTY helper architectures and verifies that all three deployment targets fit the wheel tag. A full five-target run retains six artifacts, each containing one release file: the platform-independent SDK wheel and five native runtime wheels; a subset dispatch retains the SDK wheel and selected runtime wheels. Bare executables and source bundles remain intermediate test inputs. [`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) accepts `python-v` tag pipelines whose version matches the root `package.json`, builds one SDK wheel and five native runtime wheels, then a single serialized job checks and publishes all six to the project PyPI registry. The [`python/sdk-runtime` README](../../../../python/sdk-runtime/README.md) owns the Windows target and the explicit exclusion of Windows arm64. ### Python SDK distribution: two carriers, exe for production, node for development diff --git a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.i18n.yaml index 441e67f823..088a268a62 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md -2026-07-28-portable-execution-world-consumers.md: 787fe341e58cc212c99e0f35f07eea8e83daf000 -2026-07-28-portable-execution-world-consumers.zh.md: a558a5af64437b8743e741ace4ccf27079501721 +2026-07-28-portable-execution-world-consumers.md: 3375a38d41724aee9b3f7591b014d6eea6979857 +2026-07-28-portable-execution-world-consumers.zh.md: 62d7338d237358b05c0c52f97720312474b74717 diff --git a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md index 787fe341e5..3375a38d41 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md +++ b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md @@ -26,19 +26,15 @@ Generic consumers use that execution world: - `dsh-lsp-stdio` reads and contains source through `ctx.fs`, resolves and launches language servers through `ctx.subprocess`, and carries provider-owned file URIs through initialization and result rendering. One provider-lifetime signal aborts filesystem and protocol work during disposal, including workspace lookup before queue ownership; its JSON-RPC, pooling, synchronization, and normalization stay unchanged. - `dsh-terminal-bash` maps persistent-shell semantics onto `ctx.subprocess.spawnTerminal()`. The local `node-pty` and process-inspection implementation moves into `dsh-subprocess-local`; another subprocess provider supplies the same primitive. `danger-full-access` needs no `ctx.sandbox`; a confined mode requires a same-world sandbox provider and fails before spawn when none is mounted. Prompt and silence evidence collected during asynchronous pre-write inspection is discarded when the provider write begins. Cancellation retains the send reservation while an in-flight write settles and then signals the foreground group, so late bytes or the signal cannot target a successor; an in-flight readiness poll cannot release that reservation, and a rejected write sends no signal. The absolute deadline remains armed throughout cancellation. A signal failure becomes terminal transport failure. Completion of a stale inspection resumes polling for the current send. Startup cancellation begins terminal rollback without waiting for a stalled readiness or signalling call. Close rejects new public signals and delegates provider-managed session quiescence to the handle's awaited termination operation. -## E2B POC boundary +## Remote provider ownership -The opt-in E2B realization has exactly three provider-specific packages under `packages/e2b/`: `dsh-e2b` creates one sandbox and deletes it on timeout or disposal, `dsh-fs-e2b` implements `ctx.fs`, and `dsh-subprocess-e2b` implements `ctx.subprocess` over E2B Commands, PTYs, and remote Linux process groups. The two adapters obtain the sole SDK handle from the owner and never create private sandboxes. +The [E2B provider removal](../simplification/2026-09-11-remove-e2b-providers.md) supersedes the E2B realization of this decision. The filesystem/subprocess agreement and asynchronous terminal contracts remain in force for remote implementations. -E2B owns the mutable filesystem, managed command and Bash processes, terminal allocation and terminal-session groups, language-server processes and source reads, and adapter-private files under `.dsh-e2b`. The host owns Cordis and plugin objects, the agent loop, agent/session/goal state, session logs and persistence, LLM calls, prompts and tools, authority, skills, subagent orchestration, PTY buffers and readiness, LSP protocol state, and E2B SDK/network buffers. The overlay neither uploads nor synchronizes the host workspace. - -The adapters retain only substrate mechanics. Filesystem canonicalization crosses the SDK's decoded command transport as strict base64-encoded NUL framing; streamed reads leave byte ceilings with consumers. Subprocess command output and environment snapshots use ASCII/base64 where SDK chunk decoding would otherwise lose bytes, while private control shells isolate profiles and later launches blank discovered credential-shaped names. Process and terminal cleanup uses remote groups and proves quiescence before settlement. - -Sandbox state is deliberately ephemeral: timeout and disposal delete the remote files and unmanaged state. The POC adds no reconnect or pause/leave retention, session-persistence backend, template builder, volume, snapshot, network-policy layer, sandbox catalog, workspace synchronization, durable remote handles, or whole-harness execution. +A remote provider owns mutable files, command and terminal processes, language-server processes, and provider-private runtime files. The host owns Cordis and plugin objects, the agent loop, agent/session/goal state, session logs and persistence, model transport, authority, skills, subagent orchestration, terminal readiness and LSP protocol state. Moving execution does not imply workspace synchronization or durable remote handles. ## Verification -Focused package suites pin sandbox lifecycle, canonical path framing, filesystem metadata and atomic versions, subprocess publication/rollback, terminal text I/O and session cleanup, output limits, cancellation, disposal, and invariant registration. A credential-gated Loader composition exercises the same three-package provider through source imports and built exports, including FS/Bash visibility, hostile login profiles, byte-split UTF-8 output, process and terminal cleanup, LSP queries, host-workspace isolation, and final sandbox deletion. +The local filesystem, subprocess, terminal and LSP suites cover path identity, executable lookup, managed cleanup, output limits, cancellation and disposal. Terminal consumer tests retain delayed provider writes, foreground inspection and signalling to verify asynchronous ownership without requiring a remote service. ## Alternatives considered @@ -60,7 +56,7 @@ Focused package suites pin sandbox lifecycle, canonical path framing, filesystem **Implement remote filesystem operations only through shell commands.** Rejected because that discards structured filesystem identity, errors, streaming, version guards, and atomic mutation semantics already consumed by the file tools. -**Add a generic distributed-runtime abstraction or reconnect live handles.** Rejected because the existing capability seams carry the demonstrated contracts, while remote identity alone cannot reconstruct callbacks, pending promises, authority, protocol state, or output cursors. A new layer would speculate about persistence and synchronization beyond the POC. +**Add a generic distributed-runtime abstraction or reconnect live handles.** Rejected because the existing capability seams carry the demonstrated contracts, while remote identity alone cannot reconstruct callbacks, pending promises, authority, protocol state, or output cursors. A new layer would speculate about persistence and synchronization beyond the demonstrated consumer contracts. ## Consequences @@ -70,4 +66,4 @@ The fundamental interfaces are wider, and a filesystem/subprocess pair must agre The local implementation absorbs `node-pty` and platform process inspection because it owns local terminal mechanics. On supported Linux hosts, the user-systemd scope retains descendants that call `setsid` or reparent, while process inspection continues to own foreground attribution and synchronous fallback evidence. Other hosts use the observational teardown: disposal sweeps descendants before and after terminating the top-level shell, waits for exact PID-identity-fenced descendants retained during foreground inspection, and retains Linux session members that survive top-level exit. macOS cannot enumerate a POSIX session after its leader exits, so a child that reparents between inspection snapshots remains an explicit local-provider limitation rather than a reason to move process mechanics back into the PTY consumer. -The E2B composition demonstrates that a shared sandbox owner plus filesystem and subprocess adapters are sufficient to move the mutable coding world off-host while leaving higher capabilities provider-neutral. Its POC limits remain explicit: the SDK retains complete command transport in host memory, remote startup cannot publish a PID synchronously, exact terminal stdin-wait and independent signal facts are unavailable, numeric PID/PGID operations are not identity-fenced, the initial environment probe cannot hide unknown sandbox-default secrets from already-running same-UID processes, and adapter artifacts remain until sandbox deletion. These are provider constraints, not justification for compatibility shims or more E2B packages. +Remote implementations must preserve the existing consumer contracts or reject unsupported operations explicitly. Transport-specific buffering, identity and disconnect limits belong to the provider; they do not justify duplicate Bash, PTY or LSP implementations. diff --git a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md index a558a5af64..62d7338d23 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md @@ -26,19 +26,15 @@ Status: implemented - `dsh-lsp-stdio` 通过 `ctx.fs` 读取源文件并验证包含关系,通过 `ctx.subprocess` 解析和启动语言服务器,并让由提供方负责的文件 URI 贯穿初始化与结果渲染。一个提供方生命周期信号会在资源释放期间中止文件系统与协议操作,包括取得队列所有权之前的工作区查找;其 JSON-RPC、池化、同步和规范化保持不变。 - `dsh-terminal-bash` 把持久 shell 语义映射到 `ctx.subprocess.spawnTerminal()`。本地 `node-pty` 与进程检查实现移入 `dsh-subprocess-local`;其他进程管理提供方则提供相同原语。`danger-full-access` 不需要 `ctx.sandbox`;受限模式要求同一执行世界中存在沙箱提供方,未挂载时会在 spawn 前失败。提供方开始写入时,系统会丢弃异步写入前检查期间收集的提示符与静默证据。取消会在在途写入结算期间保留发送预留,随后向前台进程组发送信号,因此延迟字节和该信号都无法落到后续发送;在途就绪检查无法释放该预留,写入被拒绝时也不会发送信号。绝对截止时间会在整个取消期间保持启用。信号发送失败会成为终结性传输失败。陈旧检查完成后,会针对当前发送恢复轮询。启动取消会立即开始终端回滚,而不等待停滞的就绪检查或信号发送调用。关闭操作会拒绝新的公开信号,并把由提供方管理的会话完全停稳委托给句柄上须等待的终止操作。 -## E2B POC 边界 +## 远程提供方的职责 -可选启用的 E2B 实现在 `packages/e2b/` 下恰好只有三个提供方专用包:`dsh-e2b` 创建一个沙箱,并在超时或资源释放时将其删除;`dsh-fs-e2b` 实现 `ctx.fs`;`dsh-subprocess-e2b` 基于 E2B Commands、PTY 和远程 Linux 进程组实现 `ctx.subprocess`。两个适配器都从所有者取得唯一的 SDK 句柄,绝不创建私有沙箱。 +[E2B 提供方移除决策](../simplification/2026-09-11-remove-e2b-providers.zh.md)取代本决策中的 E2B 实现。文件系统/子进程约定与异步终端约定继续适用于远程实现。 -E2B 负责可变文件系统、受管命令与 Bash 进程、终端分配与终端会话组、语言服务器进程与源文件读取,以及 `.dsh-e2b` 下的适配器私有文件。宿主负责 Cordis 与插件对象、agent loop(智能体循环)、agent 状态、会话状态与目标状态、会话日志与持久化、LLM(大语言模型)调用、提示词与工具、权限、skill(技能)、subagent 编排、PTY 缓冲区与就绪状态、LSP 协议状态,以及 E2B SDK/网络缓冲区。该叠加层既不上传,也不同步宿主工作区。 - -适配器只保留执行基底机制。文件系统规范化以严格的 base64 加 NUL 分帧穿过 SDK 已解码的命令传输;流式读取把字节上限留给消费方执行。进程管理命令输出与环境快照采用 ASCII/base64,避免 SDK 分片解码丢失字节;私有控制 shell 隔离 profile,后续启动会把已发现且名称呈凭据特征的环境变量置空。进程与终端清理使用远程进程组,并在结算前证明完全停稳。 - -沙箱状态有意保持短暂:超时与资源释放会删除远程文件和非托管状态。该 POC 不提供重新连接、pause/leave 保留、会话持久化后端、模板构建器、卷、快照、网络策略层、沙箱目录、工作区同步、持久远程句柄,也不会在其中运行整个 harness。 +远程提供方负责可变文件、命令与终端进程、语言服务器进程,以及提供方私有运行时文件。宿主负责 Cordis 与插件对象、智能体循环、智能体/会话/目标状态、会话日志与持久化、模型传输、权限、技能、子智能体编排、终端就绪判断和 LSP 协议状态。移动执行位置不意味着工作区同步或持久远程句柄。 ## 验证 -聚焦的包测试套件锁定了沙箱生命周期、规范化路径分帧、文件系统元数据与原子版本、进程管理发布/回滚、终端文本 I/O 与会话清理、输出上限、取消、资源释放和不变式注册。一项受凭据门控的 Loader 组合通过源代码导入与构建后导出运行同一套三包提供方组合,其中包括 FS/Bash 可见性、恶意登录 profile、跨字节边界拆分的 UTF-8 输出、进程与终端清理、LSP 查询、宿主工作区隔离,以及最终沙箱删除。 +本地文件系统、子进程、终端与 LSP 测试覆盖路径身份、可执行文件查找、受管清理、输出上限、取消与资源释放。终端消费方测试保留延迟的提供方写入、前台检查和信号发送,以便在无需远程服务时验证异步所有权。 ## 考虑过的替代方案 @@ -60,7 +56,7 @@ E2B 负责可变文件系统、受管命令与 Bash 进程、终端分配与终 **只通过 shell 命令实现远程文件系统操作。** 不予采纳,因为这会丢弃现有文件工具已消费的结构化文件系统身份、错误、流式输出、版本保护和原子变更语义。 -**新增通用分布式运行时抽象,或重新连接活跃句柄。** 不予采纳,因为现有能力 seam 已承载经证实的约定,而仅凭远程身份无法重建回调、待处理 promise、权限、协议状态或输出游标。新增一层只会推测 POC 边界之外的持久化与同步问题。 +**新增通用分布式运行时抽象,或重新连接活跃句柄。** 不予采纳,因为现有能力 seam 已承载经证实的约定,而仅凭远程身份无法重建回调、待处理 promise、权限、协议状态或输出游标。新增一层只会推测 已验证消费方约定之外的持久化与同步问题。 ## 后果 @@ -70,4 +66,4 @@ E2B 负责可变文件系统、受管命令与 Bash 进程、终端分配与终 本地实现承接 `node-pty` 和平台进程检查,因为它负责本地终端机制。在受支持的 Linux 宿主上,user-systemd scope 会保留调用 `setsid` 或发生 reparent 的后代,进程检查则继续负责前台归属与同步 fallback 证据。其他宿主使用观察型拆卸:dispose(资源释放)会在终止顶层 shell 前后清理后代进程,等待前台检查期间保留下来且受精确 PID 身份围栏保护的后代进程,并继续追踪在顶层进程退出后仍存活的 Linux 会话成员。macOS 无法在 POSIX 会话 leader 退出后枚举该会话,因此在两次检查快照之间重新设定父进程的子进程仍是明确的本地提供方限制,而不是把进程机制移回 PTY 消费方的理由。 -E2B 组合证明,共享沙箱所有者加上文件系统与进程管理适配器,就足以在保持上层能力与提供方无关的同时,把可变编码世界移出宿主。其 POC 限制仍明确在案:SDK 会把完整命令传输内容保留在宿主内存中;远程启动无法同步发布 PID;无法获得精确的终端 stdin 等待状态与独立信号事实;基于数值 PID/PGID 的操作没有身份围栏;初始环境探测无法向已在运行的同 UID 进程隐藏未知的沙箱默认 secret;适配器产物会一直保留到沙箱删除。这些是提供方限制,不是引入兼容性 shim 或更多 E2B 包的理由。 +远程实现必须保留现有消费方约定,或明确拒绝不支持的操作。传输特定的缓冲、身份与断线限制归提供方负责;这些限制不是复制 Bash、PTY 或 LSP 实现的理由。 diff --git a/.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.i18n.yaml index 0642d7aaad..4830f124d8 100644 --- a/.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md -2026-08-19-session-projection-mandatory-seam.md: 371faa254809649e93685a8f3ada3e640d5f391e +2026-08-19-session-projection-mandatory-seam.md: e1c6835311228fcaf7a22140f1db8da748603186 2026-08-19-session-projection-mandatory-seam.zh.md: 5d6a5169a556febae3f5c960b2d519c324d99863 diff --git a/.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md b/.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md index 371faa2548..e1c6835311 100644 --- a/.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md +++ b/.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md @@ -25,7 +25,7 @@ The registry provides `stateOf(session, key)` for one typed host state and keeps - **Default missing projected state.** This preserves more partial compositions but makes missing host state indistinguishable from a valid empty value. Rejected because official profiles mount the registry and configuration errors must fail explicitly. - **Require every contributor at activation.** This makes the key set uniform but unnecessarily couples contribution lifecycle to service activation. Explicit first-access failure preserves the optional registration form without allowing silent degradation. - **Use `snapshot()` for every read.** This keeps one method but computes unrelated wire views and encourages consumers to depend on batch transport data for host logic. Rejected in favor of typed single-key state reads. -- **Send full host values to clients.** This avoids separate view types but exposes provenance and policy knobs that no client consumes. Rejected in favor of explicit cropped views. +- **Send full host values to clients.** This avoids separate view types but exposes producer identities and policy knobs that no client consumes. Rejected in favor of explicit cropped views. - **Broadcast registry additions and removals across Host and mux streams.** The streams have no shared ordering, so clients need tombstones, buffered frames, and baseline retries to reconcile them. Rejected because plugin-key churn does not justify a second synchronization protocol. ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml index 13c4e2fb9b..2be979e55d 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md -2026-08-21-deepseek-llm-api-request-extensions.md: eadbe2a5f17de446c345120f9a6aeeeb531c43b4 -2026-08-21-deepseek-llm-api-request-extensions.zh.md: f45210adbc075c30484754ce7f8c2b65b515be6c +2026-08-21-deepseek-llm-api-request-extensions.md: a0e38c1da5af32b0ba5e07df632ac4e25148419e +2026-08-21-deepseek-llm-api-request-extensions.zh.md: 763f418c7bd6029c08027cd5f4705bffd984b9ee diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md index eadbe2a5f1..a0e38c1da5 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md @@ -30,7 +30,7 @@ The `events` array contains complete canonical `SessionEvent` objects directly. `@deepseek-ai/dsh-plugin-package-inventory-deepseek` owns the default-on `dsh_plugin_packages` field from the `llm` package family. It reads active non-group entries from the host Loader tree and, for a live requesting Agent, its standing preset tree. Node package resolution locates the owning manifest without requiring a `./package.json` export. Ordinary entries resolve from their owning tree, while a standing preset root mirrors its Loader's intentional harness-base override and nested includes retain their own bases. An anonymous nearest manifest marks a loose module; a named manifest must carry a version. Exact name/version pairs are deduplicated with deterministic ordering; simultaneously active versions remain separate. -Disabled, pending, failed, unloading, disposed, structural, loose non-package, ordinary dependency, programmatic child-fiber, and in-memory dynamic-plugin entries are outside this package inventory. This definition reports package-backed composition facts the runtime can prove instead of inventing provenance for arbitrary callbacks. +Disabled, pending, failed, unloading, disposed, structural, loose non-package, ordinary dependency, programmatic child-fiber, and in-memory dynamic-plugin entries are outside this package inventory. This definition reports package-backed composition facts the runtime can prove instead of inventing package identity for arbitrary callbacks. ## Deferred inventory caching @@ -73,11 +73,11 @@ The receiver would also need to traverse the tagged tree, resolve paths into the ### Why not omit assistant chunks or overlapping event data? -About 98% of the measured v1 real-session events were `assistant/chunk`. Omitting them after reference encoding reduced the complete identity JSON by another 84.79% for late enable and 6.49% for steady state, but prevented lossless reconstruction and left message provenance dangling. V2 embeds compact streams in attempt settlements; `dsh_session_log` still sends every current canonical event whole and does not omit those embedded records. Fuzzy or normalized substitutions have the same reconstruction defect. +About 98% of the measured v1 real-session events were `assistant/chunk`. Omitting them after reference encoding reduced the complete identity JSON by another 84.79% for late enable and 6.49% for steady state, but prevented lossless reconstruction and left message source-event references dangling. V2 embeds compact streams in attempt settlements; `dsh_session_log` still sends every current canonical event whole and does not omit those embedded records. Fuzzy or normalized substitutions have the same reconstruction defect. **Keep the upload cursor only in memory.** Rejected because a normal process restart would resend the entire Session. A canonical acceptance event makes restart recovery best-effort durable without another storage backend; the remaining crash window produces allowed duplicates. -**Inventory every live Cordis fiber.** Rejected because programmatic and in-memory fibers have no authoritative npm package provenance. Loader-backed host and preset entries provide exact resolvable package identity. +**Inventory every live Cordis fiber.** Rejected because programmatic and in-memory fibers have no authoritative npm package identity. Loader-backed host and preset entries provide exact resolvable package identity. **Cache one process-global list or expire it on a TTL.** Rejected because one immutable list is incorrect for Loader lifecycle and per-Session presets, while a TTL permits stale metadata between expiry boundaries. The deferred epoch design invalidates on the authoritative active-state transition instead. diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md index f45210adbc..763f418c7b 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md @@ -73,7 +73,7 @@ Status: implemented ### 为什么不省略 assistant 分片或重叠事件数据? -实测 v1 真实 Session event 中约 98% 为 `assistant/chunk`。在引用编码后省略它们,会让完整 identity JSON 在延迟启用场景进一步减少 84.79%,在稳态场景进一步减少 6.49%,但会阻止无损重建并让 message provenance 悬空。V2 把紧凑 stream 嵌入 attempt settlement;`dsh_session_log` 仍会完整发送每个当前规范 event,且不会省略这些嵌入式 record。模糊或规范化替换也有相同重建缺陷。 +实测 v1 真实 Session event 中约 98% 为 `assistant/chunk`。在引用编码后省略它们,会让完整 identity JSON 在延迟启用场景进一步减少 84.79%,在稳态场景进一步减少 6.49%,但会阻止无损重建并让 message source-event reference 悬空。V2 把紧凑 stream 嵌入 attempt settlement;`dsh_session_log` 仍会完整发送每个当前规范 event,且不会省略这些嵌入式 record。模糊或规范化替换也有相同重建缺陷。 **只在内存中保留上传游标。** 已否决,因为普通进程重启会重发完整会话。权威接受事件让重启恢复获得尽力而为的持久性,无需另一存储后端;剩余崩溃窗口只会产生允许的重复。 diff --git a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml index a75186206d..bc7d37ce4c 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md -2026-08-27-adjacent-agent-steer-messaging.md: 7bd97d5c992acac240a28d61d27e35e1f1058ae3 +2026-08-27-adjacent-agent-steer-messaging.md: 47d9bd05c24871a14f78ebfa0afb8c13a6c2754d 2026-08-27-adjacent-agent-steer-messaging.zh.md: 1fd0fc39b937287ac34a2862c7cb9cde90256c09 diff --git a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md index 7bd97d5c99..47d9bd05c2 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md +++ b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md @@ -6,7 +6,7 @@ English | [中文](2026-08-27-adjacent-agent-steer-messaging.zh.md) ## Problem -Continuable Agents originally used direction-specific model controls. A parent called `send_message({ subagent_id, message })`, which delegated to a FIFO `followup` service operation. A child instead received a child-scoped `report({ output })` tool, a `tool:report` system-prompt section, and deployment-selected quiet or waking delivery. The tools described one adjacent-Agent operation through different schemas, service paths, provenance, and scheduling. +Continuable Agents originally used direction-specific model controls. A parent called `send_message({ subagent_id, message })`, which delegated to a FIFO `followup` service operation. A child instead received a child-scoped `report({ output })` tool, a `tool:report` system-prompt section, and deployment-selected quiet or waking delivery. The tools described one adjacent-Agent operation through different schemas, service paths, source attribution, and scheduling. A continuable child owns its own Session, so its parent does not automatically receive the child's transcript, tool output, or reasoning. The return path must therefore remain explicit and repeatable: a child may send progress before it finishes, remain available after sending, or fail before it can cooperate. Turning every final assistant message into an implicit result would conflate turn completion with model-selected communication and would not cover abnormal endings. diff --git a/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.i18n.yaml index 7676532850..e2b8151a62 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md -2026-08-27-outbound-proxy-policy.md: 67927a9d1404e0b14c6e420cc5bea462be5c87ad -2026-08-27-outbound-proxy-policy.zh.md: cf0b203a061522450a2a1b0c337a060b0c387684 +2026-08-27-outbound-proxy-policy.md: 015b6edd3f4153f29db4084c92e8e8da8cad70e2 +2026-08-27-outbound-proxy-policy.zh.md: c7bdcaee138ac41a55733d76da1063b25f9ba633 diff --git a/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md b/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md index 67927a9d14..015b6edd3f 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md +++ b/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md @@ -6,7 +6,7 @@ English | [中文](2026-08-27-outbound-proxy-policy.zh.md) ## Problem -Node's built-in `fetch` ignores `HTTP_PROXY` and `HTTPS_PROXY`. Every other tool a developer runs — curl, git, npm, pip — honours them, so a user behind a proxy exports the variables once and expects everything to follow. The harness did not: `setGlobalDispatcher`, `ProxyAgent`, and `EnvHttpProxyAgent` appeared zero times across `packages/` and `apps/`, so the model request, every web search, `web_fetch`, MCP over HTTP, the OTLP exporter, and the E2B SDK all connected directly, silently, with no diagnostic anywhere. +Node's built-in `fetch` ignores `HTTP_PROXY` and `HTTPS_PROXY`. Every other tool a developer runs — curl, git, npm, pip — honours them, so a user behind a proxy exports the variables once and expects everything to follow. The harness did not: `setGlobalDispatcher`, `ProxyAgent`, and `EnvHttpProxyAgent` appeared zero times across `packages/` and `apps/`, so the model request, every web search, `web_fetch`, MCP over HTTP, and the OTLP exporter all connected directly, silently, with no diagnostic anywhere. The repository had briefly had an answer and lost it without noticing. PR #971 set `NODE_USE_ENV_PROXY=1` in `bin/dsh`; eleven days later `bbb1b1cc38 cleanup: remove managed source installer` deleted that launcher wholesale, taking the flag with it. What survived was one sentence in `apps/cli/reference/README.md` telling the reader to set a variable that nothing consumed any more. @@ -24,9 +24,7 @@ An earlier revision put it in a new `net/` package group, reasoning that dependi The plugin that revision shipped is gone with it. It let a composition declare the policy in `cordis.yml`, but no shipped bundle mounted it, so the launcher's path was the only reachable one — and its `Config` was the sole supplier of a configuration branch nothing else could reach. -**Four functions, because the call sites converged rather than the package growing an export each.** An earlier revision exported six: a dispatcher factory, a `node:http` agent factory, a proxy-URL lookup, a policy accessor, an installer, and a child-environment builder. Each existed for one SDK's transport, which is how a transport-policy package turns into a catalogue of other packages' constraints. Review asked whether the call sites could converge instead; they could, and each removal took a whole shape with it. Telemetry stopped being routed at all, retiring the `node:http` factory. `web-fetch-http` builds its own pinning agent under an annotated exemption, retiring the dispatcher factory. E2B reads `route.proxy`, retiring the proxy-URL lookup. - -What remains is `installProxyFromEnvironment`, `proxyRouteFor`, `proxyEnvironmentForChild`, and `clearedProxyEnv` — one per way a caller can need the policy, none per SDK. Installation absorbed resolution and diagnostic reporting, which no caller needed apart: a resolved policy that is not installed routes nothing. +**One operation per caller need.** `installProxyFromEnvironment`, `proxyRouteFor`, `proxyEnvironmentForChild`, and `clearedProxyEnv` cover installation, per-request routing, child inheritance and fixture isolation. SDK-specific factories would expose individual transport constraints through the shared API. `web-fetch-http` consumes the resolved route when constructing its pinning transport; the OTLP exporter remains direct. Installation includes resolution and diagnostic reporting because callers need one operation that resolves and installs routing. `proxyRouteFor` also closes a defect the old accessor made expressible. `web-fetch-http` read the policy to decide whether to pin, then read it again to build a transport; an unmount between the two returned a direct, unpinned agent for a URL the first read had cleared as proxied. A route carries both, so the branch and the request cannot disagree. Its dispatcher is the process-wide one, closed rather than destroyed on disposal, so a request already in flight when a policy is unmounted still finishes. @@ -48,13 +46,13 @@ The child keeps the user's own values, and that is what once broke it. Node pars This accepts a documented seam. Such a context matches bypass entries by Node's rules, which differ from this package's in separators and IPv4-range support, and the flag exists only on Node 22.21+ and 24+. -**Two SDKs do not reach `globalThis.fetch`, and reading their code said otherwise.** The audit first classified the OTLP exporter and the E2B SDK as covered, on a grep that found `globalThis.fetch` in `@opentelemetry/otlp-exporter-base`. That match is the *browser* transport; on Node the delegate selects `http-exporter-transport`, which posts through `node:http` — where a global dispatcher does not reach. E2B is a second shape again: it builds its own undici `Agent`/`ProxyAgent` and takes a `proxy` URL that it never reads from the environment. Both were measured direct. E2B is handed `route.proxy` from `proxyRouteFor`, the same call `web-fetch-http` makes. Telemetry is deliberately left direct, and that exclusion is the more interesting half. +**SDK transports need independent verification.** The OTLP exporter selects `http-exporter-transport` on Node, which posts through `node:http` and bypasses the global fetch dispatcher; a `globalThis.fetch` reference in its browser implementation does not prove Node routing. The [E2B removal](../simplification/2026-09-11-remove-e2b-providers.md) retires a second SDK transport integration without changing this requirement. **Telemetry stays direct on purpose.** Routing it needs one of two things, and both cost more than the channel is worth. An `http.Agent` reads the environment through `proxyEnv`, which arrived in Node 22.21 and 24.5 — inside the engines range, so 22.19, 22.20, and 24.0–24.4 would stay direct regardless, and the proxy package would have to keep a `createNodeHttpAgent` export for a path that works on some runtimes. Replacing the transport with the SDK's `fetch` delegate covers every runtime, but that delegate has no compression, and the shipped `base` bundle enables gzip: a realistic OTLP batch measures 6.4x smaller with it. An attempt that refused `exporter.compression` instead broke every test that boots the shipped bundle, and one that gzipped at the serializer worked but put transport code in a telemetry plugin to keep it working. Weighed against that, telemetry is the one outbound channel whose loss costs the user nothing: no tool, no model request, and no session depends on it, and an export that cannot connect is already dropped silently. A user behind a mandatory proxy is left exactly where they were before this change rather than regressed. `egress.spec.ts` now asserts the exclusion — an SDK upgrade that moved the exporter onto `fetch` would start routing telemetry through a proxy silently, and that case is what makes it visible. -**Every call site carries an egress test, because reading the code was not enough.** `egress.spec.ts` in each owning package drives that site's real code path at an unresolvable `.invalid` host through a fake proxy and asserts the proxy saw the request. Nine of them cover the search backends, pi-ai discovery, MCP over HTTP, E2B, a spawned child Node, a worker thread, and telemetry's exclusion. The gate below cannot see inside a dependency; these can, and they are what turns "an SDK changed its transport" from a silent regression into a failing test. +**Every call site carries an egress test.** Each owning package's `egress.spec.ts` drives its actual transport through a fake proxy and checks the observed route. These tests cover search backends, pi-ai discovery, MCP over HTTP, child Node processes, worker threads and telemetry's direct-route exception. They detect dependency transport changes that a static call-site check cannot observe. **A gate keeps the defect from returning.** `verify-no-bare-dispatcher` parses the TypeScript AST — `scripts/AGENTS.md` requires syntax-aware discovery, and a line-wise regex missed both the `{ dispatcher }` shorthand this repository already uses and a `new Alias(...)` behind a renamed import. It rejects an undici agent construction and an explicit `dispatcher` option outside the owning package. `proxyRouteFor(url)` is the sanctioned replacement, and the one call site that genuinely owns its transport — `web-fetch-http`, pinning a request to addresses it validated — says so with a `proxy-exempt:` comment. The rule exists because `web-fetch-http`'s original `new Agent` was entirely reasonable when it was written — proxying simply did not exist yet, and nothing would have caught it. diff --git a/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.zh.md b/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.zh.md index cf0b203a06..c7bdcaee13 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.zh.md @@ -6,7 +6,7 @@ Status: implemented ## Problem -Node 内置的 `fetch` 会忽略 `HTTP_PROXY` 与 `HTTPS_PROXY`。开发者运行的其他工具——curl、git、npm、pip——都遵循它们,所以代理后面的用户导出一次变量就期待一切随之生效。Harness 并没有:`setGlobalDispatcher`、`ProxyAgent` 与 `EnvHttpProxyAgent` 在 `packages/` 与 `apps/` 中出现次数为零,因此模型请求、每次 web 搜索、`web_fetch`、走 HTTP 的 MCP、OTLP 导出器与 E2B SDK 全部直连,且是静默的,任何地方都没有诊断。 +Node 内置的 `fetch` 会忽略 `HTTP_PROXY` 与 `HTTPS_PROXY`。开发者运行的其他工具——curl、git、npm、pip——都遵循它们,所以代理后面的用户导出一次变量就期待一切随之生效。Harness 并没有:`setGlobalDispatcher`、`ProxyAgent` 与 `EnvHttpProxyAgent` 在 `packages/` 与 `apps/` 中出现次数为零,因此模型请求、每次 web 搜索、`web_fetch`、走 HTTP 的 MCP 与 OTLP 导出器全部直连,且是静默的,任何地方都没有诊断。 仓库曾短暂拥有过答案,又在无人察觉时弄丢了。PR #971 在 `bin/dsh` 里设置了 `NODE_USE_ENV_PROXY=1`;十一天后 `bbb1b1cc38 cleanup: remove managed source installer` 整体删除了那个启动器,把该标志一并带走。留下的只有 `apps/cli/reference/README.md` 里的一句话,让读者去设置一个已经无人消费的变量。 @@ -24,9 +24,7 @@ Node 内置的 `fetch` 会忽略 `HTTP_PROXY` 与 `HTTPS_PROXY`。开发者运 那次修订一并引入的插件也随之删除。它让某个组合可以把策略写进 `cordis.yml`,但没有任何随附 bundle 挂载它,因此启动器那条路径是唯一可达的——而它的 `Config` 是那条配置分支唯一的供给方,别处无从到达。 -**四个函数——收敛的是调用方,而不是让本包为每个 SDK 各加一个导出。** 早先一版导出六个:dispatcher 工厂、`node:http` agent 工厂、代理 URL 查询、策略访问器、安装器与子进程环境构造器。每一个都为某个 SDK 的传输而存在,而这正是一个传输策略包退化成「别的包的约束目录」的过程。Review 问能不能反过来让调用方收敛;能,而且每删掉一个导出都带走了一整种写法。遥测不再被路由,`node:http` agent 工厂随之退场。`web-fetch-http` 在带注释的豁免下自建 pin agent,dispatcher 工厂随之退场。E2B 读 `route.proxy`,代理 URL 查询随之退场。 - -剩下的是 `installProxyFromEnvironment`、`proxyRouteFor`、`proxyEnvironmentForChild` 与 `clearedProxyEnv`——按「调用方需要策略的方式」各一个,而不是按 SDK 各一个。安装吸收了解析与诊断上报,因为没有调用方需要把它们分开:解析出来却不安装的策略什么也路由不了。 +**每种调用需求对应一项操作。** `installProxyFromEnvironment`、`proxyRouteFor`、`proxyEnvironmentForChild` 与 `clearedProxyEnv` 分别负责安装、逐请求路由、子进程继承和 fixture 隔离。特定于 SDK 的工厂会通过共享 API 暴露各自的传输约束。`web-fetch-http` 在构造地址固定传输时使用已解析路由;OTLP 导出器保持直连。安装包含解析与诊断上报,因为调用方需要一项同时解析并安装路由的操作。 `proxyRouteFor` 还堵掉了旧访问器让人写得出来的一个缺陷。`web-fetch-http` 先读策略决定是否 pin,再读一次去构造传输;两次读取之间发生卸载,就会为第一次读取已判定走代理的 URL 返回一个直连且未 pin 的 agent。路由把两者一起交出,分支与请求便无从分歧。它携带的是进程级 dispatcher,dispose 时是 close 而非 destroy,因此策略被卸载时已经发出的请求仍会跑完。 @@ -48,13 +46,13 @@ URL 层策略未受影响:仅 `http(s)`、禁止内嵌凭据、长度上限与 这接受了一处已记录的接缝。此类上下文按 Node 自己的规则匹配绕过条目,其分隔符与 IPv4 区间支持与本包不同,且该标志仅存在于 Node 22.21+ 与 24+。 -**有两个 SDK 并不落到 `globalThis.fetch`,而读代码给出的答案是相反的。** 审计最初把 OTLP 导出器与 E2B SDK 判为已覆盖,依据是在 `@opentelemetry/otlp-exporter-base` 里 grep 到了 `globalThis.fetch`。那处命中属于**浏览器**传输;在 Node 上 delegate 选择的是 `http-exporter-transport`,它通过 `node:http` 投递——那里全局 dispatcher 触及不到。E2B 又是另一种形态:它自建 undici `Agent`/`ProxyAgent`,并接受一个自己从不从环境读取的 `proxy` URL。两者都实测为直连。E2B 接收 `proxyRouteFor` 给出的 `route.proxy`,与 `web-fetch-http` 调的是同一个函数。遥测则被有意保留为直连,而这个排除项才是更值得说的一半。 +**SDK 传输需要独立验证。** OTLP 导出器在 Node 上选择 `http-exporter-transport`,通过 `node:http` 投递并绕过全局 fetch dispatcher;其浏览器实现中的 `globalThis.fetch` 引用不能证明 Node 路由。[E2B 移除决策](../simplification/2026-09-11-remove-e2b-providers.zh.md)撤下另一项 SDK 传输集成,但不改变这一要求。 **遥测的直连是有意为之。** 要让它走代理只有两条路,代价都超过这条通道本身的价值。`http.Agent` 通过 `proxyEnv` 读取环境,而该选项自 Node 22.21 与 24.5 才有——落在 engines 范围之内,因此 22.19、22.20 与 24.0–24.4 无论如何仍是直连,而代理包还得为一条只在部分运行时生效的路径保留 `createNodeHttpAgent` 导出。改用 SDK 的 `fetch` delegate 替换传输可以覆盖所有运行时,但该 delegate 没有压缩能力,而随附的 `base` bundle 启用了 gzip:实测一批真实规模的 OTLP 数据启用后体积只有 1/6.4。曾有一版转而在加载期拒绝 `exporter.compression`,结果凡是启动随附 bundle 的测试全部失败;另一版在 serializer 处 gzip 确实能跑通,但代价是把传输层代码塞进了遥测插件。 与之相比,遥测是唯一一条丢失了对用户毫无代价的出网通道:没有任何工具、模型请求或会话依赖它,而连不上的导出本就被静默丢弃。处在强制代理后的用户,只是停留在本次改动之前的状态,而不是被弄坏。`egress.spec.ts` 现在断言这一排除——若某次 SDK 升级把导出器挪到 `fetch` 上,遥测就会开始静默走代理,而该用例正是让这件事暴露出来的东西。 -**每个出网点都配一份出网测试,因为读代码不够。** 各所属包中的 `egress.spec.ts` 驱动该点的真实代码路径,目标是无法解析的 `.invalid` 主机,穿过一个假代理,并断言代理确实收到了请求。九份测试覆盖搜索后端、pi-ai 发现、走 HTTP 的 MCP、E2B、派生的子 Node、worker 线程,以及遥测的排除。下面那条门禁看不进依赖内部;这些能,它们把「某个 SDK 换了传输」从静默回归变成失败的测试。 +**每个出网点都配有出网测试。** 各所属包中的 `egress.spec.ts` 通过假代理驱动实际传输,并检查观察到的路由。这些测试覆盖搜索后端、pi-ai 发现、走 HTTP 的 MCP、子 Node 进程、worker 线程和遥测的直连例外。它们可发现静态调用点检查无法观察到的依赖传输变化。 **用门禁防止该缺陷复现。** `verify-no-bare-dispatcher` 解析 TypeScript AST——`scripts/AGENTS.md` 要求 source-ownership 门禁使用语法感知发现,而逐行正则漏掉了本仓库已在使用的 `{ dispatcher }` 简写,以及重命名导入后的 `new Alias(...)`。它在所属包之外拒绝 undici agent 构造与显式 `dispatcher` 选项。`proxyRouteFor(url)` 是受支持的替代;唯一一处确实自有传输的调用点——`web-fetch-http`,它把请求钉在已校验的地址上——用 `proxy-exempt:` 注释说明。这条规则之所以存在,是因为 `web-fetch-http` 里原本那行 `new Agent` 在写下时完全合理——那时根本还没有代理这回事,也没有任何机制会拦下它。 diff --git a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.i18n.yaml index 2f925cf952..bb8c3f29df 100644 --- a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md -2026-08-28-subprocess-native-containment.md: 7523f9d66e7a302f6ce9c77d93671a5b1303a5aa -2026-08-28-subprocess-native-containment.zh.md: 252a8e9fd058cf37a1a7a70f09394a6811d58580 +2026-08-28-subprocess-native-containment.md: 1b0fd162be77356001bcd9224bb2ee2889e6d6c0 +2026-08-28-subprocess-native-containment.zh.md: f33643f98a6ea6a90c0780432cbe11a35a868562 diff --git a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md b/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md index 7523f9d66e..1b0fd162be 100644 --- a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md +++ b/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md @@ -22,7 +22,7 @@ The first eligible Linux ordinary or PTY call in one runtime deeply checks the e The parent creates one 0700 directory with a complete 0600 `launch-request.json` containing the final target cwd and environment. The private `DSH_SUBPROCESS_RUNNER` value locates that request while the runner starts from the provider cwd and a bootstrap-safe environment. `systemd-run --user --scope --quiet --collect --expand-environment=no` registers its process in the scope, then the one-shot bootstrap removes and validates the request, changes to the target cwd, restores the complete target environment, resolves a bare executable with the target PATH rules, clears `FD_CLOEXEC` on fd 0 through fd 2, and calls libc `execve()` with the original argv. The bootstrap becomes the target in place and preserves its inherited stdio; it does not remain as a supervisor. -Request consumption or a manager observation of a loaded unit establishes scope ownership. Unit absence before either fact remains unresolved while the direct launcher is running. If that launcher exits while the request remains unconsumed, the direct result rejects with startup failure unless its observed signal matches a termination requested while the launcher was running. A matching signal preserves the actual exit outcome for both ordinary and PTY launches; a recorded startup error always takes precedence. Range observation independently resolves an empty range when the launcher has exited and the unit is absent. The parent checks this unresolved interval every 50 milliseconds; after establishment, state queries back off exponentially to the existing 5-second systemctl bound. Each query reads `LoadState`, `ActiveState`, and `TasksCurrent`: loaded `inactive` or `failed`, or an established unit becoming `not-found`/`inactive` or otherwise collected away, proves the range empty. `active`, `activating`, `reloading`, and `deactivating` remain nonterminal, except that once termination was requested and the launcher has exited, a still-active unit reporting no processes is an empty range: the manager ends a scope only on the populated-to-empty transition, so a payload killed before it entered the cgroup never triggers one. That conclusion stops the leftover unit so transient units cannot accumulate, while an unreported or `[not set]` process count stays unknown and keeps waiting. Unknown or malformed combinations and unreadable manager results reject `waitForExit()` instead of claiming quiescence. `terminate()` wakes a sleeping observer for an immediate recheck, and settlement cancels the losing backoff sleep. A strict sibling `startup-error.json` carries only request/bootstrap or target pre-exec failure, and the parent removes this spawn's private paths at observable lifecycle completion. +Request consumption or a manager observation of a loaded unit establishes scope ownership. Unit absence before either fact remains unresolved while the direct launcher is running. If that launcher exits while the request remains unconsumed, the direct result rejects with startup failure unless its observed signal matches a termination requested while the launcher was running. A matching signal preserves the actual exit outcome for both ordinary and PTY launches; a recorded startup error always takes precedence. Range observation independently resolves an empty range when the launcher has exited and the unit is absent. The parent checks this unresolved interval every 50 milliseconds; after establishment, state queries back off exponentially to the existing 5-second systemctl bound. Each query reads `LoadState`, `ActiveState`, and `TasksCurrent`: loaded `inactive` or `failed`, or an established unit becoming `not-found`/`inactive` or otherwise collected away, proves the range empty. `active`, `activating`, `reloading`, and `deactivating` remain nonterminal, except that once termination was requested and either the launcher has exited or the bootstrap has consumed its request, a still-active unit reporting no processes is an empty range. Request consumption proves the payload entered the scope, so zero members establishes quiescence even before Node receives the direct-process exit notification. Without request consumption, launcher exit remains necessary: the manager ends a scope only on the populated-to-empty transition, so a payload killed before it entered the cgroup never triggers one. That conclusion stops the leftover unit so transient units cannot accumulate, while an unreported or `[not set]` process count stays unknown and keeps waiting. Unknown or malformed combinations and unreadable manager results reject `waitForExit()` instead of claiming quiescence. `terminate()` wakes a sleeping observer for an immediate recheck, and settlement cancels the losing backoff sleep. A strict sibling `startup-error.json` carries only request/bootstrap or target pre-exec failure, and the parent removes this spawn's private paths at observable lifecycle completion. The ordinary target result still comes from the same child process. The PTY path uses the same request and bootstrap without a resident runner, so the `node-pty` PID, process group, session leader, controlling terminal, foreground `inputWaiting`, `/dev/tty`, readiness, and direct terminal outcome retain their existing meanings while scope membership covers `setsid` and reparented descendants. @@ -59,7 +59,7 @@ This note owns the current native-containment mechanism. It partially updates th - A keyless [`bash-startup-timeout`](../../../../snapshots/session/bash-startup-timeout/snapshot.yml) Session snapshot pins the model-facing timeout result. A Linux user-systemd fixture holds the launch request unconsumed at an input barrier and verifies cancellation plus range settlement. - Real Linux user-systemd tests run one ordinary and one `node-pty` `setsid`/reparent scenario through the production entry. They prove scope signalling and collection, bare executable lookup, escaped-descendant termination, range settlement, and unchanged PTY PID, session, controlling-terminal, foreground-input, `/dev/tty`, readiness, and startup-failure semantics. - Native Windows tests prove suspended creation, Job assignment before resume, inherited stdio, default descendant inheritance, direct result, termination, active-process zero, abnormal/disconnected runner cleanup, kill-on-close, and synchronous host-exit termination. Source, built, and Python packaged smokes enter the same runner core. -- Public seam types, local and E2B providers, LSP and subagent consumers, shell fixtures, READMEs, the Cordis catalog, and the keyless subprocess API snapshot contain no ordinary PID; terminal PID remains. +- Public seam types, local provider, LSP and subagent consumers, shell fixtures, READMEs, the Cordis catalog, and the keyless subprocess API snapshot contain no ordinary PID; terminal PID remains. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.zh.md b/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.zh.md index 252a8e9fd0..f33643f98a 100644 --- a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.zh.md @@ -22,7 +22,7 @@ detached POSIX 进程组、Windows direct-parent 遍历与 PTY 后代扫描只 parent 创建一个 0700 目录,其中的完整 0600 `launch-request.json` 保存最终 target cwd 与环境。私有 `DSH_SUBPROCESS_RUNNER` 值负责定位该 request,runner 则从 provider cwd 与 bootstrap-safe 环境启动。`systemd-run --user --scope --quiet --collect --expand-environment=no` 先把自身进程注册到 scope,再由 one-shot bootstrap 删除并校验 request、切换到 target cwd、恢复完整 target 环境、按 target PATH 规则解析裸可执行文件、清除 fd 0 至 fd 2 的 `FD_CLOEXEC`,并使用原始 argv 调用 libc `execve()`。bootstrap 会原地成为 target 并保留继承的 stdio,不作为常驻 supervisor。 -request 被消费或 manager 已观察到 loaded unit 都能建立 scope ownership。在这两项事实出现前,只要 direct launcher 仍在运行,unit absence 就保持未决。如果 launcher 退出时 request 仍未消费,direct result 会以 startup failure reject,除非实际观察到的信号匹配 launcher 仍在运行时请求的终止信号。普通进程与 PTY 进程遇到匹配信号时都会保留实际退出结果;已记录的 startup error 始终优先。launcher 已退出且 unit 不存在时,range observation 会独立结算 empty-range wait。parent 每 50 毫秒检查一次这段未决区间;建立后,状态查询按指数增长间隔退避,最多达到既有的 5 秒 systemctl 上限。每次查询读取 `LoadState`、`ActiveState` 与 `TasksCurrent`:loaded `inactive` 或 `failed`,以及已经建立的 unit 变为 `not-found`/`inactive` 或被 collect 卸载,都能证明 range 为空。`active`、`activating`、`reloading` 与 `deactivating` 仍是非终态;例外是:一旦请求过终止且 launcher 已经退出,报告没有任何进程却仍 active 的 unit 就是空 range——manager 只在观测到 populated→empty 转变时才结束 scope,因此进入 cgroup 前就被杀死的 payload 永远不会触发该转变。该判定会 stop 掉这个遗留 unit,使 transient unit 不会累积;未上报或为 `[not set]` 的进程数仍视为未知并继续等待。未知或 malformed 组合以及不可读的 manager 结果会使 `waitForExit()` reject,而不是宣称完全停稳。`terminate()` 会唤醒正在休眠的 observer 立即复查,结算时会取消未胜出的退避 sleep。严格的同目录 `startup-error.json` 只承载 request/bootstrap 或 target pre-exec failure,parent 会在可观察生命周期完成时移除本次 spawn 的私有路径。 +request 被消费或 manager 已观察到 loaded unit 都能建立 scope ownership。在这两项事实出现前,只要 direct launcher 仍在运行,unit absence 就保持未决。如果 launcher 退出时 request 仍未消费,direct result 会以 startup failure reject,除非实际观察到的信号匹配 launcher 仍在运行时请求的终止信号。普通进程与 PTY 进程遇到匹配信号时都会保留实际退出结果;已记录的 startup error 始终优先。launcher 已退出且 unit 不存在时,range observation 会独立结算 empty-range wait。parent 每 50 毫秒检查一次这段未决区间;建立后,状态查询按指数增长间隔退避,最多达到既有的 5 秒 systemctl 上限。每次查询读取 `LoadState`、`ActiveState` 与 `TasksCurrent`:loaded `inactive` 或 `failed`,以及已经建立的 unit 变为 `not-found`/`inactive` 或被 collect 卸载,都能证明 range 为空。`active`、`activating`、`reloading` 与 `deactivating` 仍是非终态;例外是:一旦请求过终止,且 launcher 已经退出或 bootstrap 已消费其 request,报告没有任何进程却仍 active 的 unit 就是空 range。request 消费证明 payload 已进入 scope,因此即使 Node 尚未收到直接进程的退出通知,零成员也能证明完全停稳。request 未消费时仍须等待 launcher 退出——manager 只在观测到 populated→empty 转变时才结束 scope,因此进入 cgroup 前就被杀死的 payload 永远不会触发该转变。该判定会 stop 掉这个遗留 unit,使 transient unit 不会累积;未上报或为 `[not set]` 的进程数仍视为未知并继续等待。未知或 malformed 组合以及不可读的 manager 结果会使 `waitForExit()` reject,而不是宣称完全停稳。`terminate()` 会唤醒正在休眠的 observer 立即复查,结算时会取消未胜出的退避 sleep。严格的同目录 `startup-error.json` 只承载 request/bootstrap 或 target pre-exec failure,parent 会在可观察生命周期完成时移除本次 spawn 的私有路径。 普通 target result 仍来自同一个 child process。PTY 路径复用同一 request 与 bootstrap,但不增加常驻 runner,因此 `node-pty` PID、进程组、session leader、控制终端、前台 `inputWaiting`、`/dev/tty`、readiness 与 direct terminal outcome 保留既有含义,同时 scope membership 覆盖 `setsid` 与 reparent 后代。 @@ -59,7 +59,7 @@ selector 是 per-spawn locator 或 sentinel,不是凭据或持久格式。Linu - 无需密钥的 [`bash-startup-timeout`](../../../../snapshots/session/bash-startup-timeout/snapshot.yml) Session 快照固定模型可见的超时结果。Linux user-systemd fixture 通过输入屏障保持启动请求未消费,并验证取消与 range settlement。 - 真实 Linux user-systemd 测试会分别通过生产入口运行一条普通命令与一条 `node-pty` `setsid`/reparent 场景。它们证明 scope signalling 与 collection、裸可执行文件查找、逃逸后代终止、range settlement,以及不变的 PTY PID、session、控制终端、前台输入、`/dev/tty`、readiness 与 startup-failure 语义。 - native Windows 测试证明 suspended creation、resume 前 Job assignment、继承 stdio、默认后代继承、direct result、termination、active-process zero、异常/disconnected runner cleanup、kill-on-close 与同步 host-exit termination。source、built 与 Python packaged 冒烟测试进入同一 runner core。 -- 公共 seam 类型、local 与 E2B provider、LSP 与 subagent 消费方、shell fixture、README、Cordis catalog 与 keyless subprocess API snapshot 都不包含普通 PID;terminal PID 保留。 +- 公共 seam 类型、local provider、LSP 与 subagent 消费方、shell fixture、README、Cordis catalog 与 keyless subprocess API snapshot 都不包含普通 PID;terminal PID 保留。 ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.i18n.yaml index 9fbfab3698..7fd3cc3676 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md -2026-09-01-v2-embedded-assistant-streams.md: 98207749028e2182c5e60073fc07985688ecfa30 -2026-09-01-v2-embedded-assistant-streams.zh.md: 90dff5a385cf83071ec52a2fc2b57a893107b861 +2026-09-01-v2-embedded-assistant-streams.md: 219879600f8435a6ecc9593661dd3f463ed1158c +2026-09-01-v2-embedded-assistant-streams.zh.md: d38c180b1f1de689620f2e0533d2b02f4a71e466 diff --git a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md index 9820774902..219879600f 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md +++ b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md @@ -23,7 +23,7 @@ Session format v2 has no top-level `assistant/chunk` event. Each model attempt c `AssistantStreamAccumulator` snapshots each chunk once. Consecutive text, reasoning, or tool-argument deltas for the same block become one compact run with its first timestamp, exact timestamp gaps, and one array member per original delta. Every other chunk remains a timestamped raw record. `expandAssistantStream()` strictly validates and reconstructs the exact timed sequence; compaction never joins delta boundaries. -The migration publication verifier and frozen v2 fixture validator require the embedded stream to reproduce a non-empty `assistant/message`'s content, usage, and replay state. An empty stream remains valid for a migrated legacy message that had no source chunks. Ordinary Session restoration validates the settlement fields needed by the runtime without expanding every historical stream; consumers that expand a compact stream validate its records when they read it. `assistant/message` cannot carry obsolete chunk `sourceEventSeqs`; ordinary user and tool surface provenance remains available. +The migration publication verifier and frozen v2 fixture validator require the embedded stream to reproduce a non-empty `assistant/message`'s content, usage, and replay state. An empty stream remains valid for a migrated legacy message that had no source chunks. Ordinary Session restoration validates the settlement fields needed by the runtime without expanding every historical stream; consumers that expand a compact stream validate its records when they read it. `assistant/message` cannot carry obsolete chunk `sourceEventSeqs`; ordinary user and tool source-event references remain available. ### Live presentation and durable replay @@ -35,9 +35,9 @@ The Client event source passes durable settlements through unchanged. The Chat a ### Released v1 to v2 migration -The adjacent migration validates the complete frozen v1 artifact, groups chunks by turn, step, terminal boundary, and exact message provenance, and then substitutes one settlement per attempt. A successful group's chunks move into its message. An unclaimed group becomes `assistant/attempt` at the last consumed chunk's position. Unrelated interleaved events retain their relative order, and survivors receive dense v2 sequence numbers. The edge compacts embedded streams through the runtime `AssistantStreamAccumulator` from `dsh-llm` instead of a frozen copy, because that package owns the v2 stream encoding. The isolated publication verifier expands and re-assembles the written stream through `expandAssistantStream()` and `BlockAssembler`, then checks each migrated `assistant/message` against it before publication. A later format that changes the stream encoding must freeze copies of these helpers into this edge. +The adjacent migration validates the complete frozen v1 artifact, groups chunks by turn, step, terminal boundary, and exact message chunk references, and then substitutes one settlement per attempt. A successful group's chunks move into its message. An unclaimed group becomes `assistant/attempt` at the last consumed chunk's position. Unrelated interleaved events retain their relative order, and survivors receive dense v2 sequence numbers. The edge compacts embedded streams through the runtime `AssistantStreamAccumulator` from `dsh-llm` instead of a frozen copy, because that package owns the v2 stream encoding. The isolated publication verifier expands and re-assembles the written stream through `expandAssistantStream()` and `BlockAssembler`, then checks each migrated `assistant/message` against it before publication. A later format that changes the stream encoding must freeze copies of these helpers into this edge. -The edge remaps the finite declared reference inventory: envelope provenance, surface replacement endpoints, command source events, compaction ranges and shadowed lists, and title message lists. The model-visible text of a validated `session/title-llm-request` remains byte-identical in the source sequence namespace while its `messageSeqs` field moves to the v2 namespace; target validation therefore does not reconstruct that text from remapped sequences. A reference to a consumed chunk refuses migration; it is never redirected to a settlement with different meaning. The edge also refuses an inherited cut that splits an attempt. +The edge remaps the finite declared reference inventory: envelope source-event references, surface replacement endpoints, command source events, compaction ranges and shadowed lists, and title message lists. The model-visible text of a validated `session/title-llm-request` remains byte-identical in the source sequence namespace while its `messageSeqs` field moves to the v2 namespace; target validation therefore does not reconstruct that text from remapped sequences. A reference to a consumed chunk refuses migration; it is never redirected to a settlement with different meaning. The edge also refuses an inherited cut that splits an attempt. The v2 physical header requires `isSeeded` and stores no numeric cut. A seeded artifact marks its exact cut with `session/end-seed { inherited: true }`; decoding derives the cut from the last tagged marker. The v2 codec writes one durable event per physical row, range-encodes only `sourceEventSeqs`, and validates physical envelopes without freezing ordinary event vocabulary or payload additions. The v1-to-v2 target validator separately freezes the released-v2 inventory, while current restoration uses the installed Session vocabulary. Frozen v0 and v1 codecs retain packed-row decoding for their immutable historical generations. @@ -49,7 +49,7 @@ Generation selection and publication follow the [released Session migration deci ## Verification -The compact-stream tests pin exact accumulation and expansion for text, reasoning, tool arguments, raw chunks, timestamp gaps, malformed records, and detached snapshots. The v1-to-v2 tests cover successful and failed attempts, interleaving, dense sequence and reference remapping, source-sequence title framing, seed-cut insertion and split refusal, strict source and target validation, one-row v2 encoding, backend-compatible provenance ranges, raw and Zstandard publication, and no-write current reads. +The compact-stream tests pin exact accumulation and expansion for text, reasoning, tool arguments, raw chunks, timestamp gaps, malformed records, and detached snapshots. The v1-to-v2 tests cover successful and failed attempts, interleaving, dense sequence and reference remapping, source-sequence title framing, seed-cut insertion and split refusal, strict source and target validation, one-row v2 encoding, backend-compatible source-event ranges, raw and Zstandard publication, and no-write current reads. The pre-merge performance acceptance measured static catalog-routing overhead against direct released-v2 restoration of the same already parsed physical rows across three runs, 100 warmup pairs, and 600 measured pairs; it did not compare v1 with v2 or time backend I/O. Every pooled median and p95 regression stayed within the 5% budget, with a worst p95 regression of 3.150%. diff --git a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md index 90dff5a385..d38c180b1f 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md @@ -23,7 +23,7 @@ Session format v2 没有顶层 `assistant/chunk` 事件。每个模型 attempt `AssistantStreamAccumulator` 对每个 chunk 只快照一次。同一 block 的连续 text、reasoning 或 tool argument delta 会变成一个紧凑 run,包含首个时间戳、精确时间戳间隔和每个原始 delta 对应的一个数组成员。其他 chunk 保留为带时间戳的 raw record。`expandAssistantStream()` 会严格校验并重建精确的带时间序列;压缩绝不会合并 delta 边界。 -Migration publication verifier 与冻结的 v2 fixture validator 要求嵌入式 stream 能复现非空 `assistant/message` 的 content、usage 与 replay state。对于没有源 chunk 的已迁移旧 message,空 stream 仍然有效。普通 Session restore 只校验 runtime 直接依赖的 settlement 字段,不展开全部历史 stream;需要展开 compact stream 的 consumer 会在读取时校验 record。`assistant/message` 不能携带已停用的 chunk `sourceEventSeqs`;普通 user 与 tool surface provenance 保持可用。 +Migration publication verifier 与冻结的 v2 fixture validator 要求嵌入式 stream 能复现非空 `assistant/message` 的 content、usage 与 replay state。对于没有源 chunk 的已迁移旧 message,空 stream 仍然有效。普通 Session restore 只校验 runtime 直接依赖的 settlement 字段,不展开全部历史 stream;需要展开 compact stream 的 consumer 会在读取时校验 record。`assistant/message` 不能携带已停用的 chunk `sourceEventSeqs`;普通 user 与 tool source-event reference 保持可用。 ### 实时呈现与持久回放 @@ -35,9 +35,9 @@ Client event source 原样传递持久 settlement。Chat 与 Trajectory 的 Assi ### 已发布 v1 到 v2 迁移 -相邻迁移会校验完整的冻结 v1 产物,按 turn、step、terminal boundary 与精确 message provenance 对 chunk 分组,再为每个 attempt 替换一个 settlement。成功分组的 chunk 移入其 message。未被认领的分组会在最后一个被消费 chunk 的位置变成 `assistant/attempt`。无关的交错事件保持相对顺序,存活事件获得密集 v2 序号。该迁移边通过 `dsh-llm` 运行时的 `AssistantStreamAccumulator` 压缩嵌入 stream,而不持有冻结副本,因为该包拥有 v2 stream 编码。隔离的 publication verifier 通过 `expandAssistantStream()` 与 `BlockAssembler` 展开并重组写入后的 stream,并在发布前检查每个迁移后的 `assistant/message` 是否与其一致。日后若某个格式改变 stream 编码,必须把这些 helper 的冻结副本纳入本迁移边。 +相邻迁移会校验完整的冻结 v1 产物,按 turn、step、terminal boundary 与精确 message chunk reference 对 chunk 分组,再为每个 attempt 替换一个 settlement。成功分组的 chunk 移入其 message。未被认领的分组会在最后一个被消费 chunk 的位置变成 `assistant/attempt`。无关的交错事件保持相对顺序,存活事件获得密集 v2 序号。该迁移边通过 `dsh-llm` 运行时的 `AssistantStreamAccumulator` 压缩嵌入 stream,而不持有冻结副本,因为该包拥有 v2 stream 编码。隔离的 publication verifier 通过 `expandAssistantStream()` 与 `BlockAssembler` 展开并重组写入后的 stream,并在发布前检查每个迁移后的 `assistant/message` 是否与其一致。日后若某个格式改变 stream 编码,必须把这些 helper 的冻结副本纳入本迁移边。 -该迁移边会重映射有限的已声明引用清单:信封 provenance、surface replacement 端点、command source event、compaction range 与 shadowed list,以及 title message list。经过校验的 `session/title-llm-request` 模型可见文本会在源序号命名空间中保持逐字节不变,而它的 `messageSeqs` 字段会迁移到 v2 命名空间;因此目标校验不会根据重映射后的序号重建该文本。指向被消费 chunk 的引用会使迁移失败;它绝不会被重定向到含义不同的 settlement。该迁移边也会拒绝切开 attempt 的继承切点。 +该迁移边会重映射有限的已声明引用清单:信封 source-event reference、surface replacement 端点、command source event、compaction range 与 shadowed list,以及 title message list。经过校验的 `session/title-llm-request` 模型可见文本会在源序号命名空间中保持逐字节不变,而它的 `messageSeqs` 字段会迁移到 v2 命名空间;因此目标校验不会根据重映射后的序号重建该文本。指向被消费 chunk 的引用会使迁移失败;它绝不会被重定向到含义不同的 settlement。该迁移边也会拒绝切开 attempt 的继承切点。 v2 物理 header 要求 `isSeeded`,且不存储数值切点。带 seed 的产物用 `session/end-seed { inherited: true }` 标记其精确切点;解码从最后一个 tagged marker 推导切点。v2 编解码器为每个持久事件写一条物理行,只对 `sourceEventSeqs` 做范围编码,并在不冻结普通事件词汇或 payload 新增项的前提下校验物理 envelope。v1-to-v2 target validator 会另行冻结 released-v2 清单,current restoration 则使用 installed Session 词汇。冻结的 v0 与 v1 编解码器继续为不可变历史 generation 解码 packed row。 @@ -49,7 +49,7 @@ Generation 选择与发布遵循[已发布 Session 迁移决策](2026-08-31-rele ## 验证 -紧凑 stream 测试固定 text、reasoning、tool argument、raw chunk、时间戳间隔、格式错误 record 与分离 snapshot 的精确累积和展开。v1 到 v2 测试覆盖成功与失败 attempt、交错、密集序号与引用重映射、源序号 title framing、seed 切点插入与切分拒绝、严格源与目标校验、每行一个事件的 v2 编码、与 backend 兼容的 provenance range、原始与 Zstandard 发布,以及无写入的当前读取。 +紧凑 stream 测试固定 text、reasoning、tool argument、raw chunk、时间戳间隔、格式错误 record 与分离 snapshot 的精确累积和展开。v1 到 v2 测试覆盖成功与失败 attempt、交错、密集序号与引用重映射、源序号 title framing、seed 切点插入与切分拒绝、严格源与目标校验、每行一个事件的 v2 编码、与 backend 兼容的 source-event range、原始与 Zstandard 发布,以及无写入的当前读取。 合并前的 performance acceptance 在三轮、100 组 warmup pair 与 600 组 measured pair 下,针对同一批已经解析的物理 row,把静态 catalog routing 与直接 released-v2 restoration 比较;它不比较 v1 与 v2,也不计入 backend I/O。每个 pooled median 与 p95 regression 都保持在 5% 预算以内,最差 p95 regression 为 3.150%。 diff --git a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.i18n.yaml index 47eb403310..050ce7d48f 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md -2026-09-05-workspace-files-service.md: b2dbdfc99388d8c2f18991a7704599d2d95f070b -2026-09-05-workspace-files-service.zh.md: d860d220af50a24a6e0f40b640d0a8fb534c4741 +2026-09-05-workspace-files-service.md: 40157508a83abc61245845a1dbe4f1749e175563 +2026-09-05-workspace-files-service.zh.md: 51de0ad5b644f27e30557ea6b11ba62b8ef9a715 diff --git a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md index b2dbdfc993..40157508a8 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md +++ b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md @@ -95,7 +95,7 @@ abstract readByteRange(target: FsTarget, range: { offset: number; length: number It returns the bytes at `[offset, offset + length)`, shorter when the file ends inside the window and empty when `offset` lies at or past the end. The window is the bound: a backend transfers at most `length` bytes beyond the prefix it skips to reach `offset` and never buffers the whole file, so the caller's cap on `length` is the guard against unbounded buffering, sitting beside `readBytes`'s bound rather than replacing it. The parameter order follows `readText`, `streamText`, and `listDir` — target, then the operation's own arguments, then an optional signal — rather than `readBytes`'s signal-in-the-middle form, which is the one exception in the class. Both `offset` and `length` are non-negative integers by precondition; the seam is a typed same-process boundary and validates nothing, and the Remote method validates at the wire. -`fs-local` opens `createReadStream(targetKey, { start: offset, end: offset + length - 1 })` after the same regular-file stat as its other reads, returning an empty array for `length` 0 without opening a stream; `fs-sandbox` extends `LocalFileSystem` and inherits it. `fs-e2b` has an SDK that streams only from a file's start, so it skips `offset` bytes, copies `length` into the window, and cancels the stream the moment the window is full, transferring no more than the window beyond the skipped prefix; a stream that ends first is left to close. The four test doubles that extend `FileSystem` implement the method too. +`fs-local` opens `createReadStream(targetKey, { start: offset, end: offset + length - 1 })` after the same regular-file stat as its other reads, returning an empty array for `length` 0 without opening a stream; `fs-sandbox` extends `LocalFileSystem` and inherits it. The four test doubles that extend `FileSystem` implement the method too. ### The Client `file` provider @@ -133,7 +133,7 @@ The [resource model](2026-09-05-client-resource-model.md) owns `ctx.resources`, - Workspace file access belongs to the Host/Client faces of `api/workspace-files`; the Session Controller carries neither implementation, and compiler and runtime entries stay separate. Header-only Session scope lets ordinary, subagent, live, and cold Sessions resolve their own relative paths without an Agent lifecycle or parent fallback. - A file of any size opens: text by line page, anything by byte window, each costing one page or window of memory on the Host; complete reads instead enforce `maxFileBytes`; the cost is that a consumer assembles pages itself and that a single line above `maxBytes` has no page at all, because pages are cut by lines. -- Every filesystem provider now offers a windowed raw read. `fs-e2b` pays for it by transferring the skipped prefix, since its SDK cannot seek; `fs-local` seeks. +- Every filesystem provider offers windowed raw reads; `fs-local` seeks directly to the requested offset. - Paths on the wire are canonical: `absolutePath` and change frames spell a file with symlinks resolved. A follower binds to successful `stat.absolutePath`, so another spelling of the same file — a workspace root reached through a symlink — uses that canonical change key. - Change frames report the agent's own operations only. A file edited by the user's editor, a shell, or a subprocess raises no frame; an agent merely reading a file that something else changed does raise one, because the read observes a new version. - File-kind inspection precedes backend reads, and `list` reports kind before an outside position. A page's `version` may be one write behind its content, and a stalled `changes` consumer grows Host memory because a generation's queue is unbounded; each is a known trade-off recorded in the package README. @@ -142,7 +142,7 @@ The [resource model](2026-09-05-client-resource-model.md) owns `ctx.resources`, ## Testing -Host specs in `packages/api/workspace-files/tests` exercise header-only scope resolution for live and cold subagent Sessions, the deployment fallback, missing identities, and lookup disposal; the paged read (whole file, nested path, empty file, multi-byte UTF-8, the line window's edges, defaults and refused limits, carriage returns kept); the byte window (defaults, a middle window with more following, tail windows exact and short, past-end and empty files, NUL and invalid UTF-8 round-tripping through base64, version parity with `stat`, the cap as `too-large`, bad ranges, a window of a file far above the cap, and `eof` inferred without a size); `stat`; outside-workspace reads and backend refusals; `list` with containment, truncation, symlink children, and `not-directory`; and the `changes` stream driven by `fs/observed` and filtered by root. Client specs cover the provider's frames, the change feed, unsupported addresses, and registration and disposal. `fs/fs`, `fs-local`, and `fs-e2b` specs pin `readByteRange`; `dsh-util-workspace-path` specs pin the file-address grammar. The connection fixture serves `stat`, paged `read`, `list`, and an opt-in `changes` frame for the web e2e suite. +Host specs in `packages/api/workspace-files/tests` exercise header-only scope resolution for live and cold subagent Sessions, the deployment fallback, missing identities, and lookup disposal; the paged read (whole file, nested path, empty file, multi-byte UTF-8, the line window's edges, defaults and refused limits, carriage returns kept); the byte window (defaults, a middle window with more following, tail windows exact and short, past-end and empty files, NUL and invalid UTF-8 round-tripping through base64, version parity with `stat`, the cap as `too-large`, bad ranges, a window of a file far above the cap, and `eof` inferred without a size); `stat`; outside-workspace reads and backend refusals; `list` with containment, truncation, symlink children, and `not-directory`; and the `changes` stream driven by `fs/observed` and filtered by root. Client specs cover the provider's frames, the change feed, unsupported addresses, and registration and disposal. `fs/fs` and `fs-local` specs pin `readByteRange`; `dsh-util-workspace-path` specs pin the file-address grammar. The connection fixture serves `stat`, paged `read`, `list`, and an opt-in `changes` frame for the web e2e suite. ## Deferred diff --git a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.zh.md b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.zh.md index d860d220af..51de0ad5b6 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.zh.md @@ -95,7 +95,7 @@ abstract readByteRange(target: FsTarget, range: { offset: number; length: number 它返回 `[offset, offset + length)` 处的字节,文件在窗内结束则变短,`offset` 位于或越过末尾则为空。窗口即界:后端最多传输为到达 `offset` 而跳过的前缀之外的 `length` 字节,从不缓冲整个文件,因此调用方对 `length` 的上限就是防无界缓冲的守卫,与 `readBytes` 的界并列而非取代它。参数顺序遵循 `readText`、`streamText` 与 `listDir`——先目标,再操作自己的参数,最后可选 signal——而不是 `readBytes` 把 signal 放中间的形式,那是该类中唯一的例外。`offset` 与 `length` 按前置条件都是非负整数;seam 是类型化的同进程边界,不做任何校验,由 Remote 方法在线路处校验。 -`fs-local` 在与其他读取相同的普通文件 stat 之后打开 `createReadStream(targetKey, { start: offset, end: offset + length - 1 })`,对 `length` 为 0 直接返回空数组而不开流;`fs-sandbox` 继承 `LocalFileSystem`,随之继承该方法。`fs-e2b` 的 SDK 只能从文件开头开始流式读取,于是它跳过 `offset` 字节、把 `length` 字节拷入窗口,并在窗口填满的那一刻取消流,除跳过的前缀外传输量不超过窗口;先行结束的流则任其关闭。继承 `FileSystem` 的四个测试替身也实现了该方法。 +`fs-local` 在与其他读取相同的普通文件 stat 之后打开 `createReadStream(targetKey, { start: offset, end: offset + length - 1 })`,对 `length` 为 0 直接返回空数组而不开流;`fs-sandbox` 继承 `LocalFileSystem`,随之继承该方法。继承 `FileSystem` 的四个测试替身也实现了该方法。 ### Client `file` 提供者 @@ -133,7 +133,7 @@ Client 导出向 `ctx.resources` 注册一个 `ResourceProvider<'file'>`,存 - 工作区文件访问由 `api/workspace-files` 的 Host/Client 两面共同承担;Session Controller 不携带其中任何实现,两面的编译与运行时入口保持独立。header-only Session scope 让普通、subagent、live 与 cold Session 都能解析自己的相对路径,不需要 Agent 生命周期,也不回退父 Session。 - 任意大小的文件都能打开:文本按行页、任何文件按字节窗口,在 Host 上各自只花一页或一窗内存;全文读取则受 `maxFileBytes` 约束;代价是消费者自己拼装页面,且单行超过 `maxBytes` 的行没有任何页,因为页按行切。 -- 每个文件系统提供者现在都提供开窗的原始读取。`fs-e2b` 为此付出传输被跳过前缀的代价,因为其 SDK 不能 seek;`fs-local` 能 seek。 +- 每个文件系统提供者都提供开窗的原始读取;`fs-local` 直接定位到所请求的偏移量。 - 线路上的路径是规范的:`absolutePath` 与变更帧以符号链接已解析的拼法命名文件。跟随者绑定到成功的 `stat.absolutePath`,因此同一文件的另一种拼法——经符号链接到达的工作区根——也使用该规范变更键。 - 变更帧只报告 agent 自己的操作。用户编辑器、shell 或子进程改动的文件不产生帧;agent 仅仅读取一个被别处改动的文件却会产生帧,因为读取观察到了新版本。 - 文件类型检查先于后端读取,`list` 也先报种类再报根外位置。页的 `version` 可能落后内容一次写入,停滞的 `changes` 消费者会让 Host 内存增长,因为一代流的队列无界;每一条都是包 README 记录在册的已知取舍。 @@ -142,7 +142,7 @@ Client 导出向 `ctx.resources` 注册一个 `ResourceProvider<'file'>`,存 ## Testing -`packages/api/workspace-files/tests` 中的 Host spec 覆盖 live 与 cold subagent Session 的 header-only scope 解析、部署 fallback、缺失身份与 lookup 释放;分页读取(整文件、嵌套路径、空文件、多字节 UTF-8、行窗口边界、缺省与拒绝的 limit、保留回车);字节窗口(缺省、中段与尾窗、越界与空文件、base64 往返、版本、上限、坏范围以及无大小时的 `eof`);`stat`;工作区外读取及后端拒绝;`list` 的包含、截断、符号链接与 `not-directory`;以及由 `fs/observed` 驱动并按根过滤的 `changes`。Client spec 覆盖提供者帧、变更流、不支持地址及注册与释放。`fs/fs`、`fs-local` 与 `fs-e2b` spec 钉住 `readByteRange`;`dsh-util-workspace-path` spec 钉住文件地址语法。connection fixture 为 web e2e 套件提供 `stat`、分页 `read`、`list` 与一帧可选启用的 `changes`。 +`packages/api/workspace-files/tests` 中的 Host spec 覆盖 live 与 cold subagent Session 的 header-only scope 解析、部署 fallback、缺失身份与 lookup 释放;分页读取(整文件、嵌套路径、空文件、多字节 UTF-8、行窗口边界、缺省与拒绝的 limit、保留回车);字节窗口(缺省、中段与尾窗、越界与空文件、base64 往返、版本、上限、坏范围以及无大小时的 `eof`);`stat`;工作区外读取及后端拒绝;`list` 的包含、截断、符号链接与 `not-directory`;以及由 `fs/observed` 驱动并按根过滤的 `changes`。Client spec 覆盖提供者帧、变更流、不支持地址及注册与释放。`fs/fs` 与 `fs-local` spec 钉住 `readByteRange`;`dsh-util-workspace-path` spec 钉住文件地址语法。connection fixture 为 web e2e 套件提供 `stat`、分页 `read`、`list` 与一帧可选启用的 `changes`。 ## Deferred diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml index 6333d7947a..caf0d668d6 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md -2026-09-05-nested-terminal-cards.md: b369aef20b0b5fbb338e40affd5fd2800b525bcb +2026-09-05-nested-terminal-cards.md: 03b80382a00c2302d25d2b572c3b06aedff0e1b3 2026-09-05-nested-terminal-cards.zh.md: bf97fffa2a8d92562e982d498d17b830e1e25f48 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md index b369aef20b..03b80382a0 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md +++ b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md @@ -28,7 +28,7 @@ Shell output ending in a recognized spill-policy notice uses generic output: exp ## Consequences -Rows and Details share terminal derivation for nested calls without a second renderer or presentation hint. Generic fallback and settled-persistent behavior remain separate from terminal-card eligibility. The parent-child relationship still controls tree placement, not terminal rendering. Text recognition cannot authenticate output: a tool can print the same notice. A match selects conservative generic presentation, not proof of spill provenance or process status. +Rows and Details share terminal derivation for nested calls without a second renderer or presentation hint. Generic fallback and settled-persistent behavior remain separate from terminal-card eligibility. The parent-child relationship still controls tree placement, not terminal rendering. Text recognition cannot authenticate output: a tool can print the same notice. A match selects conservative generic presentation, not proof of spill source or process status. ## Verification diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml index 27ac99b5da..2653e7d928 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md -2026-09-05-patch-plugin-file-urls.md: 48112483b4a3eef1fefcb774c2d19a2c901ccbdb +2026-09-05-patch-plugin-file-urls.md: bad63355ce74b520b5e49b94deeefb2d648b71f4 2026-09-05-patch-plugin-file-urls.zh.md: 775a576324dd2856f490cf3ce3ba907965737fc2 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md b/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md index 48112483b4..bad63355ce 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md +++ b/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md @@ -18,7 +18,7 @@ The optional `HostResolvedRootInclude` import override separately converts absol **Convert only the Python fixture with `Path.as_uri()`.** This avoids one failure but leaves user-authored profile and overlay patches exposed. -**Change the shared Loader base.** This loses per-patch provenance and changes bare-package resolution. File URLs preserve the selected local file without changing the resolver base. +**Change the shared Loader base.** This loses each patch's file source and changes bare-package resolution. File URLs preserve the selected local file without changing the resolver base. ## Consequences diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml index caf13e2fa5..e188205736 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md -2026-09-05-pi-ai-upgrade-compatibility.md: ed400ab62d221e1b58025617f05500590bfed5e7 +2026-09-05-pi-ai-upgrade-compatibility.md: e28d911facae33fcf278a6824376f889e3416d77 2026-09-05-pi-ai-upgrade-compatibility.zh.md: 408669ddc963a505254529acaab4c19e9774c6b1 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md b/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md index ed400ab62d..e28d911fac 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md +++ b/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md @@ -12,7 +12,7 @@ The pi-ai adapter classifies upstream compatibility fields explicitly and persis The adapter follows [pi-ai 0.85.1](https://github.com/earendil-works/pi/blob/v0.85.1/packages/ai/CHANGELOG.md). `thinkingTokenBudgetField`, `vllmPriority`, and `supportsMaxOutputTokens` are opt-in gateway controls; `thinking.budget` joins the existing template placeholders. The SDK owns budget resolution and serialization. `supportsMidConvoEffort` and `allowedFallbackModels` remain catalog-owned because their correctness depends on exact Anthropic transports, model capabilities, and fallback pricing. -Optional `providerThinkingLevel` remains in the adapter replay-v2 response metadata so Anthropic history retains its provider-native effort. Absence remains valid; neither the replay version nor the released Session format changes. Replay provenance retains the requested model while `responseModel` retains an Anthropic alias resolution or fallback. Reconstruction restores that native model so pi-ai still applies its cross-model signature rules. The provider-neutral LLM API stays unchanged; the [provider-routed replay ownership rules](../architecture/2026-07-14-provider-routed-llm-adapters.md) still apply. The 0.84.2 Anthropic adapter [initializes `model` from the request](https://github.com/earendil-works/pi/blob/v0.84.2/packages/ai/src/api/anthropic-messages.ts#L510-L515) and [records only response id and usage at message start](https://github.com/earendil-works/pi/blob/v0.84.2/packages/ai/src/api/anthropic-messages.ts#L589-L605). It never writes `responseModel`, so its replay records retain the requested model without native-model metadata. +Optional `providerThinkingLevel` remains in the adapter replay-v2 response metadata so Anthropic history retains its provider-native effort. Absence remains valid; neither the replay version nor the released Session format changes. Replay metadata retains the requested model while `responseModel` retains an Anthropic alias resolution or fallback. Reconstruction restores that native model so pi-ai still applies its cross-model signature rules. The provider-neutral LLM API stays unchanged; the [provider-routed replay ownership rules](../architecture/2026-07-14-provider-routed-llm-adapters.md) still apply. The 0.84.2 Anthropic adapter [initializes `model` from the request](https://github.com/earendil-works/pi/blob/v0.84.2/packages/ai/src/api/anthropic-messages.ts#L510-L515) and [records only response id and usage at message start](https://github.com/earendil-works/pi/blob/v0.84.2/packages/ai/src/api/anthropic-messages.ts#L589-L605). It never writes `responseModel`, so its replay records retain the requested model without native-model metadata. ## Alternatives considered diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml index a97e6873d0..577e94fdc8 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md -2026-09-05-session-reference-spill-reuse.md: d9f9e3702075417a9b5c742831737e412e75bd00 +2026-09-05-session-reference-spill-reuse.md: 2a24e3adf28c70df8feb726700376df6f06dee87 2026-09-05-session-reference-spill-reuse.zh.md: 346ed1a39ee65a560cf9e74e519ae45c2ce133e1 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md b/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md index d9f9e37020..2a24e3adf2 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md +++ b/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md @@ -26,7 +26,7 @@ Cancellation after an asynchronous save prevents context publication, even if st **Put omission and retrieval data inside the bounded preview JSON.** Rejected because that spends the conversation budget on metadata and can hide the notice precisely when the budget is smallest. Separate durable model-visible text preserves both obligations. -**Use tool provenance for every spill.** Rejected because a session reference has no model-issued tool call. Invented tool ids would misattribute the artifact rather than describe its producer. +**Use a tool source for every spill.** Rejected because a session reference has no model-issued tool call. Invented tool ids would misattribute the artifact rather than describe its producer. ## Consequences diff --git a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml similarity index 55% rename from .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.i18n.yaml rename to .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml index 70e7a16248..79ba95b677 100644 --- a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.md -2026-09-06-agent-request-freeze-provenance.md: 1235a87ea549c6bbd9c53620017cb1d96f8e7cf7 -2026-09-06-agent-request-freeze-provenance.zh.md: 357b25b0f252a9423c485b2cc119f1226ec16687 +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md +2026-09-11-session-controller-fork-turn-cut.md: 0c8d45c861df964a62586827e80c613704ed17f0 +2026-09-11-session-controller-fork-turn-cut.zh.md: 729a42a596264ac618608d26cd52a1ad836b58db diff --git a/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md b/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md new file mode 100644 index 0000000000..0c8d45c861 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md @@ -0,0 +1,29 @@ +# Agent Note: Session Controller forks stop at the selected turn end + +Status: implemented + +English | [中文](2026-09-11-session-controller-fork-turn-cut.zh.md) + +## Problem + +A user input enters the durable inbox before its `turn/start`. Extending a completed-turn fork through the following between-turn events can copy the next input's insertion without its later removal. Continuing the child then executes an input from beyond the selected turn. + +## Decision + +The [Session Controller](../../../../packages/api/session-controller/README.md) copies the contiguous prefix through the selected `turn/end`, inclusive. Explicit anchors select the first closing event at or after the anchor; omitted and past-end anchors select the last closing event. No event after that closing event belongs to the seed, including queued input, titles, and model settings. + +The lower-level `SessionStore.fork()` retains its explicit stable-event semantics from the [log-only event decision](../simplification/2026-07-28-remove-synthetic-log-only-turns.md). Selecting a completed turn in the controller does not request a later stable event. + +## Alternatives considered + +**Stop only at the next inbox event.** Event-type exceptions still copy unrelated state changes after the selected turn and require the controller to classify plugin-owned events. + +**Copy the tail and clear the child's inbox.** Clearing adds child events to cancel input that lies outside the requested prefix, while still inheriting other state from after the selected turn. + +## Consequences + +A fork inherits the configuration recorded through its selected turn. Later configuration and title events are excluded. The client may independently assign the child's fork title. Events already inside the selected prefix retain their ordinary replay semantics; this decision does not redefine pending input inserted before the selected closing event. + +## Verification + +Controller tests execute the production loop and check that sending C after forking A excludes the parent's later B from child history and produces one model request. Message, closing-event, omitted, and past-end anchors share this assertion. Model-routing coverage excludes a later configuration change. The Web message-actions snapshot seeds a queued input after the completed turn and verifies the branch action creates a child without that input or its inbox insertion. diff --git a/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md b/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md new file mode 100644 index 0000000000..729a42a596 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md @@ -0,0 +1,29 @@ +# Agent Note: Session Controller 分叉截到选中轮次的结束事件 + +Status: implemented + +English | [中文](2026-09-11-session-controller-fork-turn-cut.md) + +## Problem + +用户输入会先进入持久化 inbox,再出现对应的 `turn/start`。如果已结束轮次的分叉继续复制后面的轮次间事件,就可能复制下一条输入的入队事件,却没有复制其后的出队事件。继续子会话时,就会执行选中轮次之后的输入。 + +## Decision + +[Session Controller](../../../../packages/api/session-controller/README.zh.md) 复制截至选中 `turn/end` 的连续前缀,并包含该事件。显式锚点选择位于锚点或其后的第一条结束事件;省略锚点或锚点超出日志末尾时,选择最后一条结束事件。结束事件之后的所有事件均不属于种子,包括排队输入、标题和模型设置。 + +底层 `SessionStore.fork()` 保留[纯日志事件决策](../simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)规定的显式稳定事件语义。在控制器中选择已结束轮次,不代表请求复制到更晚的稳定事件。 + +## Alternatives considered + +**仅在下一条 inbox 事件处停止。** 按事件类型添加例外仍会复制选中轮次之后的无关状态变化,并要求控制器分类插件所属的事件。 + +**复制尾部后清空子会话 inbox。** 清空操作会添加子会话事件,以取消本来就在所请求前缀之外的输入,同时仍然继承选中轮次之后的其他状态。 + +## Consequences + +分叉继承截至选中轮次记录的配置。之后的配置和标题事件被排除。客户端可以独立设置子会话的分叉标题。已经位于选中前缀内的事件保留通常的回放语义;本决策不重新定义在选中结束事件之前入队的待处理输入。 + +## Verification + +控制器测试执行生产循环,检查从 A 分叉后发送 C 时,子会话历史不包含父会话后续的 B,并且只产生一次模型请求。消息、结束事件、省略及越界锚点共用此断言。模型路由覆盖排除了后续配置变更。Web 消息操作快照在已结束轮次后植入排队输入,验证分叉操作创建的子会话不包含该输入或其入队事件。 diff --git a/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.i18n.yaml b/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.i18n.yaml index fc2a00aa26..332515c11d 100644 --- a/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md -2026-07-28-continuable-subagent-conversations.md: 8c3f2e1da593157f17528f13fc8842012aad0284 +2026-07-28-continuable-subagent-conversations.md: af4382a764a59a2d7c02f3cd5feffb32022441f8 2026-07-28-continuable-subagent-conversations.zh.md: 886e31fa3d88b78f875d51058bb2ec8c525837fe diff --git a/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md b/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md index 8c3f2e1da5..af4382a764 100644 --- a/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md +++ b/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md @@ -44,7 +44,7 @@ Cold resume does not dispatch through a subagent provider. The continuation mana `SubagentProvider.start()` and `SubagentRun` remain exclusively on the unchanged one-shot path. A continuable Activation directly owns its `AgentHandle` and never creates, wraps, or retains a `SubagentRun`; `SubagentRun.steer?()` is therefore absent. -`ctx.subagents.sendMessage(sender, targetId, content, { signal })` is the sole model-authored continuation-message operation. The exact live sender authorizes delivery to its direct parent or direct continuable child; cold resume checks direct-child authority before reconstruction and every path checks again in the final no-await inbox-admission span, so an Agent unregistered or replaced during materialization cannot authorize delivery. The service derives durable `agent-message` provenance from that sender. The model-facing `send_message` tool keeps only `agent_id` and `message` and uses fixed Steer scheduling. Both start and send return the accepted `MessageId`, and neither reports how the manager materialized the Activation. +`ctx.subagents.sendMessage(sender, targetId, content, { signal })` is the sole model-authored continuation-message operation. The exact live sender authorizes delivery to its direct parent or direct continuable child; cold resume checks direct-child authority before reconstruction and every path checks again in the final no-await inbox-admission span, so an Agent unregistered or replaced during materialization cannot authorize delivery. The service derives a durable `agent-message` source from that sender. The model-facing `send_message` tool keeps only `agent_id` and `message` and uses fixed Steer scheduling. Both start and send return the accepted `MessageId`, and neither reports how the manager materialized the Activation. For start and follow-up, the caller signal owns lookup, materialization, and admission only until inbox acceptance. After the operation returns its `MessageId`, the manager owns the Activation independently; later caller cancellation does not cancel the accepted turn or dispose the child. @@ -117,7 +117,7 @@ The shared `sendMessage(sender, targetId, content, options)` service operation a ### Agent and human scheduling -Every accepted Agent message uses `Agent.steer()`. A running target claims it at the nearest step boundary; an idle or cold-resumed target starts a turn. Browser-authored human input separately carries `delivery: 'queue' | 'steer'` through `subagent.prompt`: Queue opens a later FIFO turn, while Steer uses the same best-effort nearest-step scheduling without changing the message's human provenance. The public service exposes no caller-selectable scheduling mode for Agent messages. +Every accepted Agent message uses `Agent.steer()`. A running target claims it at the nearest step boundary; an idle or cold-resumed target starts a turn. Browser-authored human input separately carries `delivery: 'queue' | 'steer'` through `subagent.prompt`: Queue opens a later FIFO turn, while Steer uses the same best-effort nearest-step scheduling without changing the message's human source. The public service exposes no caller-selectable scheduling mode for Agent messages. ### Authority and recorded sender identity diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml b/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml index ccf2075105..ab88dedfc2 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-05-agent-teams.md -2026-08-05-agent-teams.md: 1fcbe9c586b78db3d5638074e51745c8c189c80c -2026-08-05-agent-teams.zh.md: 5520b9c1e7fa039be677881f4d2df504f835ae81 +2026-08-05-agent-teams.md: bdf646db9b1617ded7f50f4145887f34a52ae83c +2026-08-05-agent-teams.zh.md: 5386044edb57dad9fabf114d565808c735846464 diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.md b/.agents/notes/implemented/feature/2026-08-05-agent-teams.md index 1fcbe9c586..bdf646db9b 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.md +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.md @@ -20,6 +20,14 @@ The implementation is split into `@deepseek-ai/dsh-experimental-agent-team`, whi The Lead must wait for required work before its final answer. Process teardown remains the final lifecycle owner and drains continuation Activations; a Team task owner is durable state and is not automatically released by idle, interruption, or process exit. +## Profile delegation + +The [Team profile](../../../../packages/experimental/agent-team-profile/README.md) disables `subagent` and `subagent_fork` together with the overlapping global controls. Direct model delegation uses `spawn_teammate` with fresh or fork context, keeping those children in the durable roster. Workflow remains available through the base profile’s fresh `spawn` provider for scripted orchestration; it cannot inherit a teammate’s conversation identity through the model tool. The Subagent services and providers remain shared infrastructure. Ordinary Session forks retain their history without identity correction. Provider-owned child tool visibility remains a [documented limitation](../../../../packages/experimental/tool-agent-team/README.md#known-limitations-and-deferred-work). + +## Team identity + +The `spawn_teammate` tool prefixes the initial task with a user-role `` stating `You are teammate "".`. Identity and task enter the same durable inbox message. Shared system policy and all tool schemas stay uniform across members; execution owns role restrictions. Team tools resolve the caller’s Team and accept member names, so the model needs no Team id. Identity follows ordinary history through cold recovery and compaction; the plugin does not inspect reminder retention or add replacement messages. Forks inherit the recorded text without a Lead identity correction. Putting identity in the system prompt changes the prefix before inherited history; keeping it in the initial task preserves that prefix without per-step identity bookkeeping. Existing system-embedded identities may require a one-time prompt reconciliation; retained event generations are unchanged. + ## Provisioning and recovery Creation first appends and flushes a `team/member` provisioning snapshot in the Lead Session, then starts the reserved continuable child through the selected fresh or fork provider. Failure before initial inbox acceptance appends a failed snapshot. Success flushes the child's accepted inbox item before appending active. Recovery recognizes that initial message while it is still pending or after it enters user-message history. Names are reserved by the first provisioning record and never reused, including after failure. Disposal closes admission, aborts and awaits admitted creation and mailbox-dispatch transactions, then stops every live child recorded by the roster; a failed child remains cleanup-owned until its Activation exits, and cleanup rejection fails disposal. diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md b/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md index 5520b9c1e7..5386044edb 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md @@ -20,6 +20,14 @@ Agent Teams 的公开约定仍处于实验阶段,因此需要显式启用的 Lead 必须等待所需工作后才能给出最终答案。进程 teardown 仍是最终生命周期 owner,并会 drain continuation Activation;Team task owner 是持久状态,不会因 idle、interrupt 或进程退出自动释放。 +## Profile delegation + +[Team profile](../../../../packages/experimental/agent-team-profile/README.zh.md) 禁用 `subagent`、`subagent_fork` 以及名称重叠的全局控件。模型直接委派使用支持 fresh 或 fork 上下文的 `spawn_teammate`,使这些子代理进入持久 roster。Workflow 仍通过 base profile 的 fresh `spawn` 提供方执行脚本编排;经模型工具创建的 workflow 子代理不会继承 teammate 的对话身份。Subagent 服务和提供方仍是共享基础设施。普通 Session fork 保留历史,不纠正身份。提供方所拥有的子代理工具可见性仍是[已记录的限制](../../../../packages/experimental/tool-agent-team/README.zh.md#known-limitations-and-deferred-work)。 + +## Team identity + +工具 `spawn_teammate` 在初始任务前加上 user-role ``,声明 `You are teammate "".`。身份和任务进入同一条持久化收件箱消息。共享 system 策略和全部工具 schema 在成员间保持一致;执行时检查角色权限。Team 工具根据调用者确定 Team,并接受成员名字,因此模型不需要 Team id。身份随普通历史经历冷恢复和压缩;插件不检查提醒是否保留,也不添加替代消息。fork 继承已记录文本,不补发 Lead 身份修正。把身份放进 system prompt 会在继承历史之前改变前缀;把它留在初始任务中既保留此前缀,也无需每步维护身份提醒。已有的 system 内嵌身份可能需要一次提示词协调;保留的事件格式代际保持不变。 + ## Provisioning and recovery 创建操作先在 Lead Session 中追加并 flush `team/member` provisioning 快照,再通过选定 fresh 或 fork provider 启动预留的 continuable child。初始 inbox 获准前的失败会追加 failed 快照;成功会先 flush child 中已接受的 inbox 条目,再追加 active。恢复会在初始消息仍处于 pending 或已进入用户消息历史时识别它。名字由第一条 provisioning 记录永久保留,包括失败后也不能复用。dispose 会关闭准入,中止并等待已获准的创建与 mailbox dispatch 事务,再停止 roster 记录的所有 live child;failed child 在 Activation 退出前仍由 cleanup 拥有,cleanup 拒绝会让 dispose 失败。 diff --git a/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml b/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml index ae2bffad1a..96dfe450a7 100644 --- a/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md -2026-08-06-manager-owned-subagent-settlement-delivery.md: f223571dc91300d085b7fcf0a9e3196daa48b760 +2026-08-06-manager-owned-subagent-settlement-delivery.md: 9d14d4af775809a974349c753a7b4b593a145489 2026-08-06-manager-owned-subagent-settlement-delivery.zh.md: 4bbee373aa2b50902af5319f9398a89da9cc3143 diff --git a/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md b/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md index f223571dc9..9d14d4af77 100644 --- a/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md +++ b/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md @@ -18,7 +18,7 @@ The continuation manager delivers the account itself, from inside the disposal t When a resident Activation settles, `notifySettlement()` resolves the child's durable direct parent and sends it one user-role message: the epoch's outcome as a sentence the parent can act on, then the child's final assistant content, or a statement that it produced none. Delivery is unconditional for every child whose id a caller actually received. It does not consult whether the child reported, and it keeps no bookkeeping that could make the promise conditional — that unconditionality is what lets `tool-subagent` promise a runtime notice containing the outcome and any final assistant message. A materialization rolled back before its first accepted message stays silent, because the caller was told that child was not established. -### Provenance +### Runtime source The notice carries `{ kind: 'subagent-settled', form: 'notice', summary, senderSessionId }`. It is deliberately not the `agent-message` kind used by `send_message`. An Agent message is content the child chose; this is the runtime stating what became of the child. Merging them would credit the child with words it never wrote, and would make a durable log unable to distinguish "the child said it was done" from "the harness observed that it stopped". The `notice` form also gives a UI the collapsed one-line presentation this message wants, where `relay` presents Agent correspondence. diff --git a/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml b/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml index 03918f3585..3877584618 100644 --- a/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md -2026-08-22-fire-and-forget-webhook-sessions.md: 976bccd8b460de7cb696ee45ea8963710cc4c738 +2026-08-22-fire-and-forget-webhook-sessions.md: 08f718b8063c7ca0d78e93f547c0e0c33c183d07 2026-08-22-fire-and-forget-webhook-sessions.zh.md: f015d993e61864bbc21d9d55fc331c25118fbde3 diff --git a/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md b/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md index 976bccd8b4..08f718b806 100644 --- a/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md +++ b/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md @@ -28,7 +28,7 @@ Patch loading anchors relative plugin names in inserted rows to the patch file. A rule result names a local Workspace path, title, text prompt, agent preset, permission preset, and optional explicit provider/model route with an output cap. Without that route, the runtime snapshots the complete live default, including reasoning effort, until the first request records its durable header. It validates presets before mutation, resolves or creates the canonical Workspace, creates the Agent with that path as Session cwd, mounts the preset before publication, and attaches the Session before admitting the prompt. -The initial follow-up is an ordinary durable user-role message with webhook provider, source, delivery, and rule provenance. Its inbox insertion is the webhook operation's last boundary. Ordinary Session persistence and Agent lifecycle own later work; the runtime neither flushes specially nor waits for a turn. +The initial follow-up is an ordinary durable user-role message whose source records the webhook provider, source, delivery, and rule identifiers. Its inbox insertion is the webhook operation's last boundary. Ordinary Session persistence and Agent lifecycle own later work; the runtime neither flushes specially nor waits for a turn. ## Alternatives considered @@ -40,13 +40,13 @@ The initial follow-up is an ordinary durable user-role message with webhook prov **Restrict rules to a declarative predicate language.** Rejected because programmatic rules explicitly need arbitrary external calls. Trusted Cordis plugins already provide the required authority and lifecycle. -**Let each adapter create Sessions directly.** Rejected because Workspace, preset, permission, title, rollback, and provenance logic would spread across provider packages. +**Let each adapter create Sessions directly.** Rejected because Workspace, preset, permission, title, rollback, and message-source logic would spread across provider packages. ## Verification Package tests pin independent callback execution, fire-and-forget HTTP timing, cancellation and quiescent disposal, request validation, Workspace attachment before prompt admission, rollback, GitHub HMAC and body limits, credential rotation, and exact Loader composition. The assembled Web example sends a signed ready-for-review delivery to an isolated second listener and records the resulting ordinary Workspace conversation. -A real-API e2e test starts the built `dsh web` CLI with the webhook overlay and isolated listener, synthesizes only the signed inbound GitHub delivery, observes Workspace attachment and durable provenance through the public Web API, and waits for the real DeepSeek response. No DSH service, model adapter, or provider call is replaced by a test double. +A real-API e2e test starts the built `dsh web` CLI with the webhook overlay and isolated listener, synthesizes only the signed inbound GitHub delivery, observes Workspace attachment and the durable message source through the public Web API, and waits for the real DeepSeek response. No DSH service, model adapter, or provider call is replaced by a test double. Source audits keep execution records, retry timers, dedupe maps, completion events, and Agent-status listeners absent. diff --git a/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml b/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml index e8f658dfe7..25749576f7 100644 --- a/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md -2026-08-27-continuable-subagent-human-inbox-control.md: cf5dfd070dfd600fb64bc529b4a1476a258c1181 +2026-08-27-continuable-subagent-human-inbox-control.md: 6bf8ceb2b8eab7138a1e617bb97e475d5cfa8172 2026-08-27-continuable-subagent-human-inbox-control.zh.md: 081fb84f75afcc94339ae3bf627480081bc56d89 diff --git a/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md b/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md index cf5dfd070d..6bf8ceb2b8 100644 --- a/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md +++ b/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md @@ -20,7 +20,7 @@ The browser gives a continuable child the ordinary busy Enter/Cmd+Enter Queue/St The existing `session.updateQueue(itemId, action)` resolves the exact live Agent and admits a subagent-owned Session only when its current projected identity is continuable and the descriptor sequence belongs to the child's own non-seed suffix. A live one-shot Agent and a missing, inherited-only, or invalid identity retain the ownership failure. An absent Agent returns `queue-item-not-found` and does not cold-resume the child. The target Session id is sufficient human authority for a live inbox occurrence mutation; a parent address is not required. Edit and Remove retain their complete existing `nextTurn` and `nextStep` semantics, including plugin-injected context, while Steer requires a queued occurrence and an Agent that reports running when the command begins. -The continuation manager keeps no second message-reservation state. One private `SubagentInbox` delegates Queue and Steer to the Agent inbox and owns the Activation's existing closing promise. Natural settlement waits for `Agent.whenIdle()`, an empty child Inbox, and disposal of every owned child. The manager confirms the Inbox, owned-child set, and wake generation under the child lock, then flushes final Session state while admission remains open. The final child-lock decision revalidates the Session sequence and the same residency facts, then synchronously starts an `Agent.runMaintenance()` task whose entry claims the idle phase and closes the wrapper in the same JavaScript turn. Every pending Inbox occurrence retains the Activation regardless of its delivery mode or provenance. Manager-owned deliveries, Inbox claims or discards, and owned-child release renew the wake generation. Direct Agent work accepted during the flush either changes the final Session or residency observation, remains active and prevents the final maintenance task from starting, or completes before revalidation. +The continuation manager keeps no second message-reservation state. One private `SubagentInbox` delegates Queue and Steer to the Agent inbox and owns the Activation's existing closing promise. Natural settlement waits for `Agent.whenIdle()`, an empty child Inbox, and disposal of every owned child. The manager confirms the Inbox, owned-child set, and wake generation under the child lock, then flushes final Session state while admission remains open. The final child-lock decision revalidates the Session sequence and the same residency facts, then synchronously starts an `Agent.runMaintenance()` task whose entry claims the idle phase and closes the wrapper in the same JavaScript turn. Every pending Inbox occurrence retains the Activation regardless of its delivery mode or source. Manager-owned deliveries, Inbox claims or discards, and owned-child release renew the wake generation. Direct Agent work accepted during the flush either changes the final Session or residency observation, remains active and prevents the final maintenance task from starting, or completes before revalidation. QueueDock Steer uses the Agent's best-effort delivery after the command admits a running queued occurrence. If the queued occurrence was claimed first, `queue-item-not-found` leaves its ordinary Queue delivery underway. If active cancellation wins during the synchronous transfer, Agent steering appends the message to `nextTurn`, latches a wake, and the Session command still succeeds. The selected message moves behind the remaining Queue in that fallback case. Newly composed Steer uses the same fallback and remains deliverable when it misses the nearest step. @@ -46,6 +46,6 @@ Continuable child conversations and ordinary Sessions share one human inbox inte The generic Session command has one narrow ownership-fence exception for a live subagent-owned Agent with a valid own-suffix continuable identity. Because the operation addresses either inbox destination, a caller that knows a pending `MessageId` can edit or remove plugin-supplied next-step input, exactly as on an ordinary Session. QueueDock renders only `queued`-placement rows, so no browser gesture reaches that input; an edit there also keeps the original producer's `MessageSource`, which would attribute human text to that producer. -Inbox notifications retain their occurrence semantics and do not carry continuation residency. Claim and discard notifications only wake settlement after pending work changes; `whenIdle()`, the final idle-phase maintenance task, `Inbox.hasPending`, the owned-child set, the Activation generation, and the Session sequence decide whether disposal is safe without depending on scheduler ordering, message identity, or provenance. The final flush precedes the closing cutoff, so a detached hook, job completion, or direct Agent delivery accepted during that await invalidates the observation instead of being stopped by the resulting disposal. Maintenance that remains active prevents the final task from claiming the idle phase; maintenance that starts and finishes during the flush has completed before disposal. A child left holding only injected context remains resident even though no driver is obliged to claim it; without a later waking delivery, queue removal, or manager teardown, that child and its live ancestors can remain resident for the process lifetime. A replayed Inbox follows the same conservative rule without reconstructing how each pending message was delivered. +Inbox notifications retain their occurrence semantics and do not carry continuation residency. Claim and discard notifications only wake settlement after pending work changes; `whenIdle()`, the final idle-phase maintenance task, `Inbox.hasPending`, the owned-child set, the Activation generation, and the Session sequence decide whether disposal is safe without depending on scheduler ordering, message identity, or source. The final flush precedes the closing cutoff, so a detached hook, job completion, or direct Agent delivery accepted during that await invalidates the observation instead of being stopped by the resulting disposal. Maintenance that remains active prevents the final task from claiming the idle phase; maintenance that starts and finishes during the flush has completed before disposal. A child left holding only injected context remains resident even though no driver is obliged to claim it; without a later waking delivery, queue removal, or manager teardown, that child and its live ancestors can remain resident for the process lifetime. A replayed Inbox follows the same conservative rule without reconstructing how each pending message was delivered. Model-side scheduling remains fixed rather than caller-selectable. The adjacent-Agent `send_message` tool always uses Steer, while only the browser human path chooses Queue or Steer. diff --git a/.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.i18n.yaml b/.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.i18n.yaml index ffb1560ce8..0c14aed1d7 100644 --- a/.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md -2026-09-02-in-history-system-prompt-replacement.md: 8e5fa65ec4649537889cefc75459d2a4958165e5 +2026-09-02-in-history-system-prompt-replacement.md: aaaf44d6388e6daf98acf8b1a93a61f1d7c55703 2026-09-02-in-history-system-prompt-replacement.zh.md: 73003b04de82f45154f9c673654cc492eb62a4fd diff --git a/.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md b/.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md index 8e5fa65ec4..aaaf44d638 100644 --- a/.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md +++ b/.agents/notes/implemented/feature/2026-09-02-in-history-system-prompt-replacement.md @@ -47,7 +47,7 @@ Web presents an appended in-history node at its own position. `SystemPromptNode` Trajectory chooses the newer of the preceding real header and the preceding synthetic system header as the comparison state. A real header owns configuration and tools; an appended prompt can advance that state without another real header. Comparing only real headers would report A rather than B as the previous prompt for an A → B → C sequence. -Chat and Trajectory interpret the effective prompt through the pure `uiConversation.inspectSystemPrompt` operation. Each target keeps immutable prefix states for system events and positional replacements, with only surviving system nodes and a map of surviving replacement sequences to inherited surface positions. Each replacement copies that map and removes shadowed entries; historical prefix maps remain immutable. Ordinary appends and streaming updates require no prompt fold. Surface order, rather than event order or provenance citations, determines which nodes survive: a compaction can restore an older prompt without another system event, and a head rewrite can have a greater sequence than a later active prompt. Empty nodes remain addressable but do not override a nonempty prompt. An endpoint older than the earliest relevant loaded event has unknown order unless its replacement position is indexed. The interpreter withholds all subsequent prompt text after such an endpoint until prepend replay resolves the missing prefix; numeric event order cannot establish surface order. Historical cards keep their own prefix state rather than reading the final surface. +Chat and Trajectory interpret the effective prompt through the pure `uiConversation.inspectSystemPrompt` operation. Each target keeps immutable prefix states for system events and positional replacements, with only surviving system nodes and a map of surviving replacement sequences to inherited surface positions. Each replacement copies that map and removes shadowed entries; historical prefix maps remain immutable. Ordinary appends and streaming updates require no prompt fold. Surface order, rather than event order or source-event citations, determines which nodes survive: a compaction can restore an older prompt without another system event, and a head rewrite can have a greater sequence than a later active prompt. Empty nodes remain addressable but do not override a nonempty prompt. An endpoint older than the earliest relevant loaded event has unknown order unless its replacement position is indexed. The interpreter withholds all subsequent prompt text after such an endpoint until prepend replay resolves the missing prefix; numeric event order cannot establish surface order. Historical cards keep their own prefix state rather than reading the final surface. ### Compaction @@ -91,7 +91,7 @@ Lifecycle verification requires no event for an unchanged prompt and an appended - `packages/core/agent-loop/tests/system-prompt-projection.spec.ts` pins the append on a continuing series, the re-baseline at a series start with or without surviving later nodes and with changed or unchanged effective text, the empty-prompt clearing of all active versions, and the replace-only behaviour without the capability. - `packages/core/agent-loop/tests/request-reconstruction.spec.ts` pins the appended node under an inherited header with `request/context` carrying `systemPromptUpdate`, the series-start fold into node 0, the compaction-driven re-baseline, and the tool-schema change re-baseline under a `change` header that starts a series. - `packages/llm/llm/tests/service.spec.ts`, `packages/llm/llm-deepseek/tests/adapter.spec.ts`, and `packages/test-support/llm-replay/tests/llm-replay.spec.ts` pin the declared mode on resolved model info and the load-time rejection of any other value. -- `packages/llm/token-meter/tests/context-breakdown-projection.spec.ts` pins newest/middle prompt removal, exact heuristic totals, surface ordering after head rewrites, extra provenance citations, dormant empties and fallback clears, immutable transitions, compact retained checkpoints, late registration, replay, and version invalidation. +- `packages/llm/token-meter/tests/context-breakdown-projection.spec.ts` pins newest/middle prompt removal, exact heuristic totals, surface ordering after head rewrites, extra source-event citations, dormant empties and fallback clears, immutable transitions, compact retained checkpoints, late registration, replay, and version invalidation. - `packages/client/ui-conversation`, `ui-chat`, and `ui-trajectory` client specs pin the update card, the same-step header dedupe, the absent system change after an update, and the synthetic trajectory header. - The keyless authored snapshot `snapshots/session/system-prompt-in-history/` declares the capability on the replay route, changes the prompt after the first tool call through a fixture section, and pins the appended `system/message`, the untouched node 0, the single `request/header`, and the `request/context` mode. - `packages/llm/llm-deepseek/tests/adapter.e2e.ts` runs a two-step prompt change against the model named by `DEEPSEEK_IN_HISTORY_MODEL`, asserts that the reply follows the appended prompt, and asserts that the appended request reads more cached tokens than the same conversation with a rewritten leading prompt; it skips when the variable is unset. diff --git a/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.i18n.yaml b/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.i18n.yaml new file mode 100644 index 0000000000..d704c77ad2 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.md +2026-09-09-headless-machine-readable-run-surface.md: 317f213095f86e94104c013139390cdf988f95fe +2026-09-09-headless-machine-readable-run-surface.zh.md: 0114f87aef4f25db0887663464973bb2f762b2d5 diff --git a/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.md b/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.md new file mode 100644 index 0000000000..317f213095 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.md @@ -0,0 +1,100 @@ +# Agent Note: Headless machine-readable run surface + +Status: implemented + +English | [中文](2026-09-09-headless-machine-readable-run-surface.zh.md) + +## Problem + +`dsh --profile headless` serves a human terminal: the task arrives only through argv, stdout carries one final assistant message, provider reasoning streams to stderr, and every run creates a fresh random session. [Headless is a direct core entry point](../../archived/architecture/2026-08-09-headless-direct-core-entry-point.md) owns that transport and completion contract; [headless reasoning progress](../../archived/feature/2026-08-21-headless-reasoning-progress.md) owns the stderr projection. + +A supervising process that drives one headless process per wake, such as an external agent runtime, needs three things that contract does not provide. It needs the task over a private pipe rather than argv, because a long prompt exceeds the argument limit and argv is visible to other processes. It needs a machine-readable stream that separates assistant text, reasoning, tool calls and results, turn boundaries, and usage, because scraping stderr yields only reasoning and the final stdout line yields no tool activity. It needs an exact session identity it can pass back on the next wake, because a fresh random session per process makes continuity impossible. + +## Decision + +The `dsh-headless` bundle owns an opt-in machine-readable run surface. The default invocation keeps the previous contract unchanged: one final assistant message on stdout, reasoning on stderr, exit 0 exactly when the terminal `turn/end` reason is `completed`. + +Three additions extend the app-owned command line that [Apps own their command lines](../../archived/architecture/2026-08-06-app-owned-command-line.md) established: + +- `--json` replaces the stdout payload with newline-delimited JSON run events. Reasoning becomes an event instead of stderr output, so stderr carries only `dsh:` diagnostics. +- `--session-id ` selects the exact session identity: adopt the persisted session with that id, and fail when no such log exists. Without the flag the run mints `session-` as before. +- The task text also arrives on stdin when no positional task is present, or when the positional is `-`. + +A per-run `--model` override is deliberately out of scope; the composition default stays authoritative. + +The product change is confined to `packages/bundle/headless`: `src/startup.ts`, `src/index.ts`, the new `src/json-stream.ts`, the package manifest and `tsconfig.json`, and its tests. Around it, the product-profile expectation test in `apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts` covers both output modes end to end and adds one optional caller-owned `cwd` to the `packages/test-support/loader-smoke` harness so two wakes can share a world. `scripts/check-workspace-constraints.ts` and the package manifest publish the shared `lib/json-stream-*.js` chunk both entries import, and `pnpm-lock.yaml` records the new `@deepseek-ai/dsh-session-query` workspace link. No core session, persistence, session-controller, base composition, or launcher file changes. + +### Command-line contract + +```text +dsh --profile headless [--json] [--session-id ] [... | -] +``` + +Task resolution order: joined positionals, then `-`, then piped stdin. A whitespace-only positional is a usage error on its own, even when stdin is not a terminal, so an accidental blank argument never consumes a pipe; a task that is absent entirely is a usage error only when stdin is a terminal. A lone `-` is the only stdin marker: mixing it with other task words is a usage error instead of a task that starts with a dash. A piped task is sent verbatim, trailing newline included. In `--json` mode every usage error — including commander's own grammar rejections such as an unknown option or a missing option value — writes the `error` event before the process exits, because the runner never mounts to write it; the event message omits commander's `error: ` prefix so one event type carries one message shape. + +`--json` changes the stdout payload and the destination of the reasoning projection only. Exit status, shutdown ordering, session flush, and the durable session log are unchanged, so a supervisor classifies a run exactly as it does today. + +### Event stream + +`--json` writes one JSON object per line to stdout and nothing else. The vocabulary is a projection of the session event log, not the log itself. + +| `type` | Fields | Emitted | +|---|---|---| +| `session` | `sessionId`, `cwd` | first line, before any model output | +| `status` | `phase` (`turn_start`, `step_start`, `step_end`, `turn_end`), `turn`, `step`, `usage`, `reason` | one per boundary | +| `text` | `text` | one committed assistant text block | +| `thinking` | `text` | one committed reasoning block | +| `tool_call` | `callId`, `tool`, `input` | once per call | +| `tool_result` | `callId`, `status`, `result` | once per appended result | +| `error` | `message` | a failure the runner raises outside a turn | +| `final` | `text` | last line | + +Projection rules: + +- Text and reasoning are projected only from a committed `assistant/message`, never from live attempt deltas. A retried or discarded attempt appends `assistant/attempt`, which the projection ignores, so the stream never carries content the durable log does not contain ([publish state only at its commit point](../../../../packages/AGENTS.md)). +- Each committed content block becomes exactly one `text` or `thinking` event in content order; `tool-call` blocks are not projected because the `tool/call` event owns them. `user/message` echoes and internal session events (title, model selection, projection, checkpoint, goal, subagent) are not projected. +- A `tool/result` is projected only when its `surfaceOp` is `append`. A compaction replacement of an older result is history, and projecting it would emit a call id with no matching `tool_call`. +- Every projected string and object key is bounded at 8 KiB, an event with a cut value carries `truncated: true`, and one serialized event line, newline included, is bounded at 32 KiB — an over-long event keeps its scalar fields, drops structured ones, and at the extreme reduces to `type` and `truncated`, while a payload nested 64 levels or deeper is cut at that depth so no legal input can overflow the bounding recursion. This includes the process-level `error` event; a literal `__proto__` argument key is copied as data rather than through the inherited setter, an empty tool-argument string projects as `{}` to match the executor, and arguments that JSON cannot round-trip (an overflowing number such as `1e400`) keep their raw text rather than the `null` that `JSON.stringify` would report. The terminal `final` event is deliberately unbounded: it carries the same lossless answer the default mode prints. +- Text and reasoning arrive when the step commits, not per token; default-mode stderr reasoning remains the only live text channel. A turn that fails in-turn still ends the stream with `final` and no `error` event, so a supervisor classifies that run from the exit code and the `turn_end` reason even when the stream is well formed. +- `usage` appears on `step_end` only when every attempt in the step reported a sample, summed across them — including a retried attempt whose only usage sample sits in its discarded `assistant/attempt` stream — so a partial total is never presented as exact. +- Raw session events stay out of scope. A debug escape hatch can be added later without changing this vocabulary. + +### Session identity + +The runtime owns identity. A run without `--session-id` mints `session-` and reports it in the first event. A supervisor persists that value and passes it back on the next wake. + +`--session-id ` is adopt-only: observe the persisted session and resume it, failing when the log does not exist. The first wake omits the flag, so the runtime mints the identity and reports it in the `session` event; every later wake names that value and continues the history. A requested id with no stored log is an error rather than a fresh conversation, so a mistyped or stale id cannot silently open an empty history the caller believes it is resuming, and the JSONL store's refusal of an existing log id ([session persistence](../../implemented/architecture/2026-06-14-session-persistence.md)) is never on this path. The id is opaque, so the runner validates non-emptiness on the trimmed value and passes the caller's exact string through, whitespace included. + +Adoption compares the persisted session's recorded cwd with the process cwd, since sessions are organized per project directory ([project session directories](../../implemented/architecture/2026-07-24-project-session-directories.md)). A mismatch exits 1 with a `dsh:` diagnostic instead of silently continuing a conversation rooted elsewhere, and a session that recorded no cwd is rejected for the same reason. A session running under an agent preset is rejected because this bundle composes no preset roster: resuming it here would run it under the headless tools and prompts instead of the composition its log records. The check reads the preset the log currently records — the creation header advanced by any `agent-preset/selected` event — because a blank session may switch preset after creation while the header stays a creation fact, and a malformed selection record fails closed rather than reading as no preset. A session linked to a parent or subagent — including a user fork — is rejected. A live Agent already holding the requested id is refused outright: its owner may still drive it, and `whenIdle` is not a single-message signal, so the runner cannot own an exclusive interval over it. The resumed log is checked again after the runner's idle wait, so a preset selected in that window is still rejected. A whitespace-only `sessionId` is rejected on both the CLI and the direct-config path. Two live processes cannot write one id; the store's write lease already rejects the second writer. The runner reads the observation through the composed `sessionQuery` service and fails loudly when `--session-id` is requested without it — the observation is how it finds the id it must resume — or when the requested identity would lack the `sessionPersistence` service that makes it durable. + +## Consequences + +What landed: `src/startup.ts` parses `--json` and `--session-id `, treats an absent or `-` task as "read stdin", and raises the usage error only when stdin is a terminal. `src/index.ts` resolves the task, adopts the named session — or mints a fresh identity when the flag is absent — and wires either the stderr reasoning projection or the new `src/json-stream.ts` projection. `cordis.patch.yml` forwards the two new settings. `package.json` publishes the shared `lib/json-stream-*.js` chunk both entries import, so the installed tarball loads. + +- Default mode is unchanged: a text-only run writes one final assistant line to stdout and nothing to stderr, and exit status still follows the terminal reason. +- `--json` stdout parses line by line as JSON, starts with `session`, ends with `final`, and contains no plain text. Stderr carries no reasoning in this mode. +- A step that retries publishes `text` and `thinking` only for the attempt that commits, so a discarded attempt leaves no trace in the stream. +- Two consecutive runs with the same `--session-id` share history, and a requested id with no stored log exits 1 before the task runs. A run whose cwd differs from the persisted session, that recorded no cwd, that is a subagent or forked session, that runs under an agent preset, that carries a malformed preset record, or whose identity is already live in the process, exits 1 with a diagnostic. +- A piped task with no positional task is honored, a whitespace-only positional is rejected instead of consuming the pipe, and an interactive invocation without a task still fails with the usage error. +- Unit coverage lands in `packages/bundle/headless/tests/startup.spec.ts`, `tests/headless.spec.ts`, and `tests/json-stream.spec.ts`. The product headless profile expectation test in `apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts` covers both output modes end to end. + +Deferred and open: + +- The per-run `--model` override is unimplemented. A later change must respect the session-local selection precedence owned by the Session Controller rather than overriding a stored selection. +- Cold start plus log replay grows with session length, so a long-lived conversation pays more per wake than a fresh one. +- `--json` moves reasoning from stderr to stdout, so a log collector that watches stderr sees nothing on a reasoned run in that mode. +- Bounded `tool_result` payloads hide full output from the supervisor; the 8 KiB string/key cap and the 32 KiB line cap are owned by `src/json-stream.ts` and should stay constants, with the terminal `final` event the only exemption. + +## Alternatives considered + +**`--verbose` human text on stderr.** A supervisor parses stdout, so a stderr-only projection is invisible to it. Default-mode stderr reasoning already is the human verbose surface. + +**Dump raw session events.** They repeat the assembled message beside its deltas, echo `user/message`, and include internal events. Measured on one prompt, pi's delta stream produced 84 lines and 11.7 KB against opencode's 3 lines and 962 B, with roughly a quarter of pi's bytes spent repeating one message across `message_end`, `turn_end`, and `agent_end`. + +**A long-lived SDK process instead of one process per wake.** The SDK already speaks structured events and an explicit session identity, but it replaces the one-process-per-wake model the supervisor is built on. Measured cold start for the headless profile is about 0.45 s warm and 1.2 s cold, small against a real turn. + +**Let the supervisor mint the session id.** Identity belongs to the runtime that owns the log. The supervisor records what the first event reports. + +**Adopt-or-create `--session-id`.** Creating the requested id when no log exists would let a mistyped or stale id silently open an empty history that the supervisor believes it is continuing; the first wake already omits the flag and reads the minted id from the `session` event, so no caller needs `--session-id` to create. + +**Task from argv only.** Long prompts exceed `ARG_MAX` and expose the prompt in the process list. diff --git a/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.zh.md b/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.zh.md new file mode 100644 index 0000000000..0114f87aef --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.zh.md @@ -0,0 +1,100 @@ +# Agent Note: Headless 的机器可读运行接口 + +Status: implemented + +[English](2026-09-09-headless-machine-readable-run-surface.md) | 中文 + +## 问题 + +`dsh --profile headless` 面向的是人类终端:任务只能通过 argv 传入,stdout 只输出最终一条助手消息,provider 的推理过程流式写到 stderr,而且每次运行都新建一个随机会话。[Headless is a direct core entry point](../../archived/architecture/2026-08-09-headless-direct-core-entry-point.md) 拥有那套传输与完成契约;[headless reasoning progress](../../archived/feature/2026-08-21-headless-reasoning-progress.md) 拥有 stderr 投影。 + +一个"每次唤醒起一个 headless 进程"的监督进程(例如外部 agent 运行时)需要三样该契约没有提供的东西。它需要通过私有管道而不是 argv 传入任务,因为长提示词会超出参数上限,而 argv 对其他进程可见。它需要一条机器可读的流,把助手文本、推理、工具调用与结果、轮次边界和用量区分开,因为抓取 stderr 只能拿到推理,而 stdout 最后一行拿不到任何工具活动。它需要一个精确的会话身份,以便在下一次唤醒时传回去,因为每次进程都新建随机会话意味着无法连续。 + +## 决策 + +`dsh-headless` bundle 拥有一个可选的机器可读运行接口。默认调用保持原有契约不变:stdout 输出一条最终助手消息,推理走 stderr,当且仅当终端 `turn/end` 原因为 `completed` 时退出码为 0。 + +三项新增扩展 [Apps own their command lines](../../archived/architecture/2026-08-06-app-owned-command-line.md) 确立的 app 自有命令行: + +- `--json` 把 stdout 负载换成逐行 JSON 运行事件。推理变成一条事件而不再写 stderr,因此该模式下 stderr 只承载 `dsh:` 诊断。 +- `--session-id ` 选定精确的会话身份:采用具有该 id 的持久化会话,不存在时就失败。不带该 flag 时,运行仍像以前一样生成 `session-`。 +- 没有位置参数、或者位置参数为 `-` 时,任务文本改从 stdin 读取。 + +每次运行的 `--model` 覆盖被明确排除在范围之外;组合默认模型仍然权威。 + +产品改动限于 `packages/bundle/headless`:`src/startup.ts`、`src/index.ts`、新增的 `src/json-stream.ts`、包清单与 `tsconfig.json`,以及测试。围绕它,产品 profile 的期望测试位于 `apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts`,端到端覆盖两种输出模式,并给 `packages/test-support/loader-smoke` harness 增加了一个可选的调用方自有 `cwd`,让两次唤醒共享同一个世界;`scripts/check-workspace-constraints.ts` 与包清单负责发布两个入口共同引用的共享 chunk `lib/json-stream-*.js`,`pnpm-lock.yaml` 则记录新增的 `@deepseek-ai/dsh-session-query` workspace 链接。不修改任何 core session、持久化、session-controller、base 组合或 launcher 文件。 + +### 命令行契约 + +```text +dsh --profile headless [--json] [--session-id ] [... | -] +``` + +任务解析顺序:拼接后的位置参数,其次是 `-`,其次是管道 stdin。只有空白的位置参数本身就属于用法错误,即使 stdin 不是终端也一样,因此误传的空白参数绝不会消费管道内容;完全缺失任务时,仅在 stdin 是终端时属于用法错误。单独的 `-` 是唯一的 stdin 标记:把它与其他任务词混用属于用法错误,而不是以连字符开头的任务。管道任务会原样发送,包括结尾换行。在 `--json` 模式下,所有用法错误——包括 commander 自身的语法拒绝,例如未知选项或选项缺少取值——都会在进程退出前写出 `error` 事件,因为 runner 从未挂载来写它;事件 message 省略 commander 的 `error: ` 前缀,使同一事件类型只承载一种消息形态。 + +`--json` 只改变 stdout 负载和推理投影的去向。退出码、关闭顺序、会话 flush 和持久化会话日志都不变,因此监督进程对一次运行的分类方式与现在完全一致。 + +### 事件流 + +`--json` 向 stdout 每行写一个 JSON 对象,不写其他内容。词汇表是会话事件日志的投影,而不是日志本身。 + +| `type` | 字段 | 发出时机 | +|---|---|---| +| `session` | `sessionId`、`cwd` | 第一行,先于任何模型输出 | +| `status` | `phase`(`turn_start`、`step_start`、`step_end`、`turn_end`)、`turn`、`step`、`usage`、`reason` | 每个边界一条 | +| `text` | `text` | 一个已提交的助手文本块 | +| `thinking` | `text` | 一个已提交的推理块 | +| `tool_call` | `callId`、`tool`、`input` | 每次调用一条 | +| `tool_result` | `callId`、`status`、`result` | 每次追加的结果一条 | +| `error` | `message` | runner 在轮次之外抛出的失败 | +| `final` | `text` | 最后一行 | + +投影规则: + +- 文本与推理只从已提交的 `assistant/message` 投影,绝不来自实时的 attempt 增量。被重试或丢弃的 attempt 会追加 `assistant/attempt`,投影直接忽略,因此事件流永远不会承载持久化日志中不存在的内容([只在提交点发布状态](../../../../packages/AGENTS.md))。 +- 每个已提交的内容块按内容顺序变成恰好一条 `text` 或 `thinking` 事件;`tool-call` 块不投影,因为 `tool/call` 事件已经拥有它。`user/message` 回显和内部会话事件(标题、模型选择、投影、检查点、目标、子 agent)都不投影。 +- `tool/result` 仅在其 `surfaceOp` 为 `append` 时投影。压缩对旧结果的替换属于历史,投影它会产生没有对应 `tool_call` 的 call id。 +- 每个被投影的字符串与对象键都限制在 8 KiB;被截断的事件带 `truncated: true`,单条序列化事件行(含换行)限制在 32 KiB——超长事件保留标量字段、丢弃结构化字段,极端情况下只剩 `type` 与 `truncated`,嵌套达到 64 层及以上的负载会在该深度被截断,因此任何合法输入都不会让限界递归溢出。进程级 `error` 事件同样受限;字面量 `__proto__` 参数键会作为数据复制,而不经过继承的 setter;空工具参数字符串会投影为 `{}`,与执行器保持一致;JSON 无法往返的参数(例如溢出为 `Infinity` 的 `1e400`)保留原始文本,而不是 `JSON.stringify` 会报告的 `null`。终止 `final` 事件刻意不做限长:它承载与默认模式相同的无损答案。 +- 文本与推理在步骤提交时到达,而不是逐 token 到达;默认模式的 stderr 推理仍是唯一的实时文本通道。轮次内失败的运行仍以 `final` 结束且没有 `error` 事件,因此即使事件流格式良好,监督进程也要用退出码与 `turn_end` 原因来分类该次运行。 +- `usage` 仅在该步每一次 attempt 都上报了样本时出现在 `step_end` 上,并累计这些样本——包括仅在被丢弃的 `assistant/attempt` 流中留下用量样本的重试——因此部分汇总不会被当作精确总量发布。 +- 原始会话事件不在范围内。调试用的逃生口可以以后再加,不必改动这套词汇表。 + +### 会话身份 + +身份由运行时拥有。不带 `--session-id` 的运行生成 `session-`,并在第一条事件里报告它。监督进程保存该值,并在下一次唤醒时传回。 + +`--session-id ` 是只采用:先观察持久化会话并 resume,日志不存在时失败。首轮不传该 flag,由运行时生成身份并在 `session` 事件里报告;后续每一轮都用该值指名并续接历史。请求的 id 没有持久化日志时报错,而不是开一段新会话,因此写错或过期的 id 不会静默开出一段调用方自以为在续接的空历史,JSONL 存储拒绝已存在日志 id 的问题(见 [session persistence](../../implemented/architecture/2026-06-14-session-persistence.zh.md))也不会出现在这条路径上。标识是不透明的,因此 runner 只在 trim 后的值上校验非空,并把调用方的原始字符串(含空白字符)原样传下去。 + +采用时会比较持久化会话记录的 cwd 与进程 cwd,因为会话按项目目录组织(见 [project session directories](../../implemented/architecture/2026-07-24-project-session-directories.zh.md))。不一致时以 `dsh:` 诊断退出 1,而不是静默续接一个根目录在别处的会话;未记录 cwd 的会话出于同样理由被拒绝。运行在 agent preset 下的会话被拒绝,因为本 bundle 不组合任何 preset roster:在这里 resume 它,会用 headless 的工具与提示词运行它,而不是它日志当前记录的组合。该检查读取日志当前记录的 preset——创建 header 再叠加任何 `agent-preset/selected` 事件——因为空白会话可能在创建后切换 preset,而 header 始终只是创建事实;畸形的选择记录会失败关闭,而不会读成「无 preset」。带父会话或子 agent 关联的会话——包括用户 fork 出的会话——被拒绝。本进程已存在持有请求 id 的存活 Agent 时直接拒绝:它的 owner 可能仍在驱动它,而 `whenIdle` 不是单条消息的完成信号,runner 无法对它取得独占区间。resume 后的日志会在 runner 等待 idle 后再次检查,因此该窗口内选中的 preset 仍会被拒绝。纯空白的 `sessionId` 在 CLI 与直接配置两条路径上都会被拒绝。两个存活进程不能写同一个 id;存储的写租约已经会拒绝第二个写入者。runner 通过已组合的 `sessionQuery` 服务读取观察结果,并在请求 `--session-id` 却没有该服务时显式失败——观察结果正是它找到待 resume id 的途径;若所请求的身份缺少让它持久化的 `sessionPersistence` 服务,同样显式失败。 + +## 后果 + +实际落地:`src/startup.ts` 解析 `--json` 与 `--session-id `,把缺失或为 `-` 的任务视为"从 stdin 读取",并且只在 stdin 是终端时抛出用法错误。`src/index.ts` 解析任务、采用指名的会话——未传 flag 时生成新身份——并接上 stderr 推理投影或新的 `src/json-stream.ts` 投影。`cordis.patch.yml` 转发这两个新设置。`package.json` 发布两个入口共同引用的共享 chunk `lib/json-stream-*.js`,因此安装后的 tarball 可以加载。 + +- 默认模式不变:纯文本运行向 stdout 写一行最终助手消息、stderr 无输出,退出码仍跟随终端原因。 +- `--json` 的 stdout 逐行可解析为 JSON,以 `session` 开头、以 `final` 结尾,不含纯文本。该模式下 stderr 不承载推理。 +- 发生重试的步骤只为最终提交的 attempt 发布 `text` 与 `thinking`,因此被丢弃的 attempt 不会在事件流中留下任何痕迹。 +- 两次连续的相同 `--session-id` 运行共享历史,而请求一个没有持久化日志的 id 会在任务运行前退出 1。cwd 不一致、未记录 cwd、属于子 agent 或 fork 会话、运行在 agent preset 下、preset 记录畸形,或本进程已存在同 id 存活 Agent 的运行都以诊断退出 1。 +- 无位置参数但 stdin 有管道输入时任务被采纳,只有空白的位置参数会被拒绝而不会消费管道,交互式无任务调用仍以用法错误失败。 +- 单元覆盖落在 `packages/bundle/headless/tests/startup.spec.ts`、`tests/headless.spec.ts` 与 `tests/json-stream.spec.ts`。`apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts` 的产品 headless profile 期望测试端到端覆盖两种输出模式。 + +延期与未决: + +- 每次运行的 `--model` 覆盖尚未实现。后续改动必须尊重 Session Controller 拥有的会话局部选择优先级,而不是覆盖已保存的选择。 +- 冷启动加上日志重放会随会话变长而增长,因此长会话每次唤醒的代价高于新会话。 +- `--json` 把推理从 stderr 移到 stdout,因此只监听 stderr 的日志收集器在该模式的有推理运行上什么都看不到。 +- 有界的 `tool_result` 负载会让监督进程看不到完整输出;8 KiB 字符串/键上限与 32 KiB 行上限由 `src/json-stream.ts` 拥有,应保持为常量,终止 `final` 事件是唯一的例外。 + +## 备选方案 + +**`--verbose` 人类可读文本写到 stderr。** 监督进程解析的是 stdout,只落在 stderr 的投影对它不可见。默认模式的 stderr 推理已经是人类可读的 verbose 面。 + +**直接倾倒原始会话事件。** 它们在增量之外重复整条已组装消息,回显 `user/message`,还夹带内部事件。在同一条提示词上实测,pi 的增量流产生 84 行、11.7 KB,而 opencode 是 3 行、962 B;pi 大约四分之一的字节花在把同一条消息在 `message_end`、`turn_end`、`agent_end` 里重复三遍。 + +**用长驻 SDK 进程代替每次唤醒一个进程。** SDK 已经有结构化事件和显式的会话身份语义,但它会替换掉监督进程所依赖的"一次唤醒一个进程"模型。实测 headless profile 的冷启动约为热态 0.45 s、冷态 1.2 s,相对一个真实轮次很小。 + +**让监督进程生成会话 id。** 身份属于拥有日志的运行时。监督进程记录第一条事件报告的值即可。 + +**采用或创建语义的 `--session-id`。** 在日志不存在时创建所请求的 id,会让写错或过期的 id 静默开出一段监督进程自以为在续接的空历史;首轮本就不传该 flag 并从 `session` 事件读到生成的 id,因此没有任何调用方需要用 `--session-id` 来创建。 + +**任务只走 argv。** 长提示词会超出 `ARG_MAX`,并且把提示词暴露在进程列表里。 diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml index 5892d118ec..4326e893cf 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md -2026-08-09-concrete-prose-names-actors-and-recorded-facts.md: 887a3666501a97e0930fe7eb4282603f537c17e8 -2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md: 0202be5c22c75ff4feec225cdff12b772d64d1cf +2026-08-09-concrete-prose-names-actors-and-recorded-facts.md: 91f7913ff0cabafdd1c089e618d7864b9a085147 +2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md: 315850711adb234fa2bd9a92d9bd69fea5077230 diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md index 887a366650..91f7913ff0 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md +++ b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md @@ -24,6 +24,8 @@ Before using `contract`, `boundary`, or `shape`, writers check whether the sente This decision complements the [documentation tiers and budgets](2026-07-04-doc-tiers-and-budgets.md) decision, which continues to own placement, document form, and word budgets. +The [blocked ambiguous origin label](2026-08-26-ban-ambiguous-origin-label.md) decision partially supersedes the sentence-level policy for one term after that term spread across unrelated contracts. + ## Alternatives considered **Ban a fixed list of words.** Rejected because a word may be an exact identifier or the clearest term in another contract. For example, caller/callee invariants are real contracts, and process or wire boundaries identify real divisions. Sentence-level review catches ambiguity without rejecting valid names. diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md index 0202be5c22..315850711a 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md +++ b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md @@ -24,6 +24,8 @@ Status: implemented 该决策补充了[文档层级与字数预算](2026-07-04-doc-tiers-and-budgets.zh.md)决策;后者继续规定内容位置、文档形式和字数预算。 +[禁止有歧义来源名称](2026-08-26-ban-ambiguous-origin-label.zh.md)决策对其中一个术语部分取代了逐句判断规则,因为该术语已经扩散到互不相关的约定中。 + ## 曾考虑的替代方案 **禁止一份固定词表中的所有词。** 不予采纳:某个词可能是确切的标识符,也可能是另一项约定中最清楚的用词。例如,调用方与被调用方依赖的不变量属于真实的约定,进程边界或协议边界也表示真实分界。逐句审查可以找出歧义,且不会拒绝有效名称。 diff --git a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.i18n.yaml b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.i18n.yaml index 82e3d79cac..4d9e3fafc1 100644 --- a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md -2026-08-10-event-directed-pr-review-status.md: de4dc0700f2083772321fdf5f26c05fdf39928be -2026-08-10-event-directed-pr-review-status.zh.md: b8a8fbaa25673a376542965700e864f82ab0d739 +2026-08-10-event-directed-pr-review-status.md: 37b8086a081145e8c16ceb29d489fbc38a7f0d43 +2026-08-10-event-directed-pr-review-status.zh.md: cc0dad8554f869d51965c30d2648937c54dc2b75 diff --git a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md index de4dc0700f..37b8086a08 100644 --- a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md +++ b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md @@ -12,7 +12,7 @@ A monotonic projection also cannot return an automation-owned Issue from `In rev ## Decision -The Issue lifecycle workflow treats review webhooks as commands. `pull_request.review_requested`, including a repeated request, targets `In review`. `pull_request_review.submitted` targets `In progress` only when `review.state` is `changes_requested`; the submitted event remains necessary because a reviewer can request changes without an earlier review-request event. Approved and commented submissions run their lifecycle job but no-op (they never reach the Project token step), while dismissed reviews are not subscribed. +The Issue lifecycle workflow treats review webhooks as commands. `pull_request.review_requested`, including a repeated request, targets `In review`. `pull_request_review.submitted` targets `In progress` only when `review.state` is `changes_requested`; the submitted event remains necessary because a reviewer can request changes without an earlier review-request event. Approved and commented submissions do not allocate a lifecycle runner, while dismissed reviews are not subscribed. [Selective policy evaluation](2026-09-07-selective-issue-policy-evaluation.md) owns lifecycle scheduling. Ordinary subscribed pull-request events remain forward-only implementation signals: they can move `Inbox`, `Backlog`, or `Ready` to `In progress`, but they cannot move `In review` backward. Review-request commands can move any earlier active status to `In review`. Changes-requested commands can move earlier active statuses forward to `In progress` and can move `In review` back only when the latest status event for the target Project was written by the configured lifecycle actor. A human or unknown latest actor preserves the current status. @@ -22,7 +22,7 @@ The status projection resolves only exact same-repository `Fixes`, `Closes`, or ## Verification -[Issue-management tests](../../../../.github/issue-management/policy.test.mjs) pin the event-to-command mapping, the repeated-review-request transition after a changes-requested command, the changes-requested regression, terminal protection, and human override preservation. [Workflow tests](../../../../scripts/ci-workflow.spec.ts) pin the subscribed events, the job-level absence of `if` plus the step-level gate on the token/board steps (so approved/commented reviews pass without minting a token), and the separate `ready_for_review` policy trigger. +[Issue-management tests](../../../../.github/issue-management/policy.test.mjs) pin the event-to-command mapping, the repeated-review-request transition after a changes-requested command, the changes-requested regression, terminal protection, and human override preservation. [Workflow tests](../../../../scripts/ci-workflow.spec.ts) pin the subscribed events, the job-level condition that excludes approved/commented reviews before runner allocation, and the separate `ready_for_review` policy trigger. ## Alternatives considered diff --git a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.zh.md b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.zh.md index b8a8fbaa25..cc0dad8554 100644 --- a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.zh.md +++ b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.zh.md @@ -12,7 +12,7 @@ Issue 所在 Project 中的状态记录了解决工作的下一步由谁负责 ## 决策 -Issue 生命周期工作流把评审 webhook 视为命令。`pull_request.review_requested`(包括重复请求)将目标状态指定为 `In review`。`pull_request_review.submitted` 将目标状态指定为 `In progress`,但仅在 `review.state` 为 `changes_requested` 时生效;submitted 事件仍不可省略,因为评审人即使没有先触发 review-request 事件,也可以直接提出修改要求。对于 approved 和 commented 提交,生命周期作业会运行但空操作(不会走到创建 Project token 一步);dismissed 评审则不在订阅范围内。 +Issue 生命周期工作流把评审 webhook 视为命令。`pull_request.review_requested`(包括重复请求)将目标状态指定为 `In review`。`pull_request_review.submitted` 将目标状态指定为 `In progress`,但仅在 `review.state` 为 `changes_requested` 时生效;submitted 事件仍不可省略,因为评审人即使没有先触发 review-request 事件,也可以直接提出修改要求。对于 approved 和 commented 提交,工作流不分配生命周期 runner;dismissed 评审则不在订阅范围内。[选择性策略求值](2026-09-07-selective-issue-policy-evaluation.zh.md)拥有生命周期调度规则。 工作流订阅的普通 PR 事件仍是只向前推进的实现信号:它们可以将 `Inbox`、`Backlog` 或 `Ready` 推进至 `In progress`,但不能让 `In review` 倒退。请求评审命令可将任意较早的活跃状态推进至 `In review`。请求修改命令可将较早的活跃状态推进至 `In progress`;它也可以让 `In review` 状态回退,但仅在目标 Project 的最新状态事件由配置的生命周期执行主体写入时进行。若最新状态事件的执行主体是人工用户或未知主体,则保留当前状态。 @@ -22,7 +22,7 @@ Issue 生命周期工作流把评审 webhook 视为命令。`pull_request.review ## 验证 -[Issue 管理测试](../../../../.github/issue-management/policy.test.mjs)锁定事件到命令的映射、请求修改命令后重复请求评审所触发的状态转换、请求修改后的状态回退、终态保护,以及保留人工覆盖状态。[工作流测试](../../../../scripts/ci-workflow.spec.ts)锁定订阅事件、job 级无 `if` 且 token/看板步骤带 step 级门控(使 approved/commented 评审以 pass 呈现且不铸 token),以及独立的 `ready_for_review` 策略触发器。 +[Issue 管理测试](../../../../.github/issue-management/policy.test.mjs)锁定事件到命令的映射、请求修改命令后重复请求评审所触发的状态转换、请求修改后的状态回退、终态保护,以及保留人工覆盖状态。[工作流测试](../../../../scripts/ci-workflow.spec.ts)锁定订阅事件、在 runner 分配前排除 approved/commented 评审的 job 级条件,以及独立的 `ready_for_review` 策略触发器。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml index aec7f94a6a..ae1929ecc7 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-10-npm-release-sequences.md -2026-08-10-npm-release-sequences.md: 19e8d8db8550816f542111a9b831a3694a080de5 -2026-08-10-npm-release-sequences.zh.md: d057d77adbf35c20fc260203d3ef0db92e30f6b7 +2026-08-10-npm-release-sequences.md: 3a52c01494921cacd5df3053fb515461831d6007 +2026-08-10-npm-release-sequences.zh.md: f973b442f2bbf6400ef0b75650392744a489ace3 diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md index 19e8d8db85..3a52c01494 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md @@ -178,7 +178,7 @@ What this costs: - **The change judgement depends on visible tags.** A shallow clone, or a checkout without tags, degrades the vendored judgement to "publish everything for the first time". `fetch-depth: 0` is a precondition, not an optimization. - **The protocol rewrite touched 1504 dependency declarations.** It does not change local resolution — pnpm already resolves from the workspace — but it changes the ranges that go out. - **Private packages need credentials to install.** Every consumer — CI, sandbox e2e, outside users — needs scope credentials, including for the Landlock packages, which have never been published and so cut off no existing anonymous path. -- **`repository` names a different organization than the one running the workflows.** Token-based publication is unaffected; npm provenance (OIDC) requires the two to agree, so adopting it means either repointing `repository` or publishing from the organization it names. +- **`repository` names a different organization than the one running the workflows.** Token-based publication is unaffected; npm's OIDC attestation requires the two to agree, so adopting it means either repointing `repository` or publishing from the organization it names. - **Byte reproducibility is assumed, not measured.** The skip-on-identical-integrity state rests on packing the same commit twice producing the same bytes. Nothing measures that yet: if the build embeds absolute paths or timestamps, a re-run reports a false failure. Measure it before the first publication a re-run might follow, and fall back to comparing per-file content hashes if it does not hold. - **Re-running publish over an older artifact can move `latest` backwards.** Publication is decided per version, so an older set republished after a newer one takes the stable dist-tag again. The rehearsals run from a prerelease version, which never takes `latest`. - **The first publication is one large step.** Nine vendored packages and the whole dsh set publish at once, so any payload defect surfaces in a single release, which is why a prerelease version drives the complete path first. diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md index d057d77adb..f973b442f2 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md @@ -178,7 +178,7 @@ dsh 的验证会一并安装 vendored 族的 pack 产物。harness 的包把 ven - **变更判据依赖 tag 可见。** shallow clone 或未拉 tag 会把 vendored 族的判据退化成「全部首发」。`fetch-depth: 0` 是前提,不是优化。 - **协议改写触及 1504 处依赖声明。** 它不改变本机解析(pnpm 本来就从 workspace 解析),但改变了发布出去的范围写法。 - **私有包需要凭据才能安装。** 任何消费方——CI、沙箱 e2e、外部使用者——都要持有 scope 凭据,Landlock 三包也在其中;它们从未发布过,所以没有切断既有的匿名安装路径。 -- **`repository` 指向的组织与运行 workflow 的组织不同。** 用 token 发布不受影响;npm provenance(OIDC)要求二者一致,届时要么把 `repository` 改指过去,要么从它指向的组织发布。 +- **`repository` 指向的组织与运行 workflow 的组织不同。** 用 token 发布不受影响;npm 的 OIDC attestation 要求二者一致,届时要么把 `repository` 改指过去,要么从它指向的组织发布。 - **字节可复现性是假定的,没有实测。** 「integrity 相同则跳过」这一态建立在「同一 commit 两次 pack 得到相同字节」之上。目前没有任何东西测量过它:若构建嵌入了绝对路径或时间,重跑会误报失败。在第一次可能被重跑的发布之前实测,若不成立就退到比对 tarball 内逐文件内容哈希。 - **用较旧的 artifact 重跑 publish 会把 `latest` 拉回旧版。** 发布是按版本决定的,所以在较新版本之后重发较旧的一批,会让稳定 dist-tag 再次指向旧版。排练用的是预发布版本,它永远不占 `latest`。 - **首发是一次大步。** 九个 vendored 包与整个 dsh 集一次发出,任何 payload 缺陷都会集中在同一次发布里暴露——这正是先用预发布版本把完整链路走一遍的理由。 diff --git a/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.i18n.yaml b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.i18n.yaml new file mode 100644 index 0000000000..5221fa234d --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md +2026-08-26-ban-ambiguous-origin-label.md: fe06cf5d2dd43a09a2699a4dc48ff5791cffa65e +2026-08-26-ban-ambiguous-origin-label.zh.md: 1389d13992f5566d5b6091740e20433b06dd6e31 diff --git a/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md new file mode 100644 index 0000000000..fe06cf5d2d --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md @@ -0,0 +1,37 @@ +# Agent Note: Ban an ambiguous origin label + +Status: implemented + +English | [中文](2026-08-26-ban-ambiguous-origin-label.zh.md) + +## Problem + +The case-insensitive ten-letter ASCII token formed by `prove` followed by `nance` had accumulated unrelated meanings across the repository. It named source-event references, provider and model metadata, context producers, installed artifact identity, configuration origins, browser-recording evidence, and release attestations. A reader could not determine the recorded fact from the label alone. + +The existing [concrete prose decision](2026-08-09-concrete-prose-names-actors-and-recorded-facts.md) required sentence-level classification and normally preserved identifiers. That policy improved individual sentences but allowed the same ambiguous label to remain in APIs, durable compatibility fixtures, filenames, generated catalogs, and new prose. + +## Decision + +Maintained tracked paths and text must not contain that token in any case variant. Each use is replaced with the exact local concept, such as source-event references, provider metadata, model identity, context producer, configuration source, artifact identity, or recorded evidence. + +The rule applies to source, tests, documentation, active Agent Notes, prompts, snapshots, workflow files, scripts, identifiers, fields, exported types, and filenames. Because the repository is pre-release, coordinated public symbol and current durable-field renames are preferred over aliases. A compatibility reader may reconstruct the retired durable key at runtime without embedding the token in maintained text. + +`verify-concrete-terms` scans tracked filenames, symlink targets, and text case-insensitively after NFKC normalization. It runs as a quick leaf of `doc-sync`, and its tests prove rejection in prose, identifiers, and paths. Vendored sources and frozen archived Agent Notes remain excluded because their repository policies prohibit direct edits. + +This decision partially supersedes the earlier decision's rejection of fixed word bans and identifier renames for this one token. The earlier decision remains active for all other abstract language and for choosing each replacement according to its local meaning. + +## Alternatives considered + +**Continue semantic review without a mechanical ban.** Rejected because the label had already spread through unrelated contracts, and sentence-level review could not prevent recurrence in identifiers, fixtures, or filenames. + +**Replace every occurrence with one umbrella term such as `origin` or `source`.** Rejected because one new broad label would preserve the ambiguity between event references, model metadata, build identity, and recorded evidence. + +**Exempt identifiers, durable fields, and generated files.** Rejected because callers and generated reference material would continue teaching the retired label. The pre-release policy permits coordinated renames, while the legacy parser can retain read compatibility without retaining the literal token. + +**Scan vendored and archived sources.** Rejected because vendored content is updated through its synchronization procedure and archived Agent Notes are frozen records. Their explicit exclusions keep this policy consistent with those ownership rules. + +## Consequences + +Callers use more specific public names, and current recordings use provider metadata instead of the retired assistant field. Generated catalogs and recorded snapshots must be refreshed when an owning symbol changes. Compatibility code remains responsible for reading the retired durable field. + +Future uses fail `doc-sync` with the matching path or line. Reviews still classify the intended meaning before choosing a replacement; passing the gate proves absence of the token, not that the replacement is precise. diff --git a/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.zh.md b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.zh.md new file mode 100644 index 0000000000..1389d13992 --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.zh.md @@ -0,0 +1,37 @@ +# Agent Note: 禁止有歧义的来源名称 + +Status: implemented + +[English](2026-08-26-ban-ambiguous-origin-label.md) | 中文 + +## 问题 + +由 `prove` 与 `nance` 拼成的十个 ASCII 字母术语不区分大小写时,在仓库中已经承载了互不相关的含义。它曾表示来源事件引用、提供方与模型元数据、上下文生产方、已安装产物身份、配置来源、浏览器录制证据和发布证明。读者无法仅凭该名称判断实际记录的事实。 + +现有的[具体表述决策](2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md)要求逐句分类,且通常保留标识符。该规则改善了各个句子,却仍允许同一个有歧义名称留在 API、持久化兼容 fixture、文件名、生成目录和新行文中。 + +## 决策 + +仓库维护的已跟踪路径和文本不得包含该术语的任何大小写变体。每处使用都改为当前语境中的确切概念,例如来源事件引用、提供方元数据、模型身份、上下文生产方、配置来源、产物身份或已记录证据。 + +该规则适用于源码、测试、文档、活跃 Agent Note、提示词、快照、工作流文件、脚本、标识符、字段、导出类型和文件名。由于仓库尚未正式发布,公开符号与当前持久字段应协调重命名,而不是保留别名。兼容读取器可以在运行时构造已退役的持久键,无需在维护文本中嵌入该术语。 + +`verify-concrete-terms` 会在 NFKC 规范化后,不区分大小写地扫描已跟踪文件名、符号链接目标和文本。它作为 `doc-sync` 的快速叶子门禁运行;对应测试证明行文、标识符和路径中的违规都会被拒绝。vendored 源码与冻结的 archived Agent Note 仍被排除,因为各自的仓库规则禁止直接编辑这些内容。 + +本决策针对这一个术语,部分取代了旧决策中不采用固定禁词和标识符重命名的选择。旧决策仍适用于其他抽象语言,也仍负责要求根据每处使用的具体含义选择替代名称。 + +## 曾考虑的替代方案 + +**继续只做语义审查,不设置机械禁令。** 不予采纳:该名称已经扩散到互不相关的约定中,逐句审查无法防止它在标识符、fixture 或文件名中再次出现。 + +**用 `origin` 或 `source` 等单一总括术语替换所有位置。** 不予采纳:新的宽泛名称仍会混淆事件引用、模型元数据、构建身份和已记录证据。 + +**豁免标识符、持久字段和生成文件。** 不予采纳:调用方和生成的参考资料会继续传播已退役名称。预发布政策允许协调重命名,而旧格式解析器可以在不保留字面术语的情况下继续提供读取兼容性。 + +**扫描 vendored 与 archived 内容。** 不予采纳:vendored 内容通过同步流程更新,而 archived Agent Note 是冻结记录。明确排除两者,可以使本政策与对应的归属规则保持一致。 + +## 后果 + +调用方使用更具体的公开名称;当前录制使用提供方元数据,而不再使用已退役的 assistant 字段。归属符号变化后,生成目录和录制快照必须刷新。兼容代码仍负责读取已退役的持久字段。 + +以后新增的匹配项会使 `doc-sync` 失败,并报告对应路径或行号。审查仍需先判断预期含义再选择替代名称;通过门禁只能证明该术语不存在,不能证明替代名称足够精确。 diff --git a/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.i18n.yaml b/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.i18n.yaml index 6006f0447a..a331c0727e 100644 --- a/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.i18n.yaml +++ b/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md -2026-09-06-master-only-platform-ci.md: d32864a4eafa81433c6d4fd0e47892b4d17091b1 +2026-09-06-master-only-platform-ci.md: fcdc413d644c1129635a7f33fe80bdec1b4a0b7a 2026-09-06-master-only-platform-ci.zh.md: 4fb55d5e95f8fe75e6d1efb8b57295b8e8469e8d diff --git a/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md b/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md index d32864a4ea..fcdc413d64 100644 --- a/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md +++ b/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md @@ -16,7 +16,7 @@ Wine runs once as an independent hosted Ubuntu master job. Its existing image-ke The [superseded-CI cancellation policy](2026-09-09-cancel-superseded-ci.md) applies to the parent and reusable runtime workflows: newer master pushes or manual runs cancel older validation in the same workflow/ref group, while release-owned builds remain protected. A master push schedules all three selected carriers but does not guarantee every intermediate commit reaches a result. -This decision partially supersedes scheduling in the [installed-wheel validation](../testing/2026-08-23-installed-python-wheel-black-box-ci.md), [native Windows CI](2026-08-08-native-windows-pull-request-ci.md), [serial references](2026-07-21-serial-cross-platform-ci-reference.md), and [failover runbook](2026-07-26-ci-failover-runbook.md). Those notes remain active for artifact provenance, platform fidelity, serial completeness, and trust rules. +This decision partially supersedes scheduling in the [installed-wheel validation](../testing/2026-08-23-installed-python-wheel-black-box-ci.md), [native Windows CI](2026-08-08-native-windows-pull-request-ci.md), [serial references](2026-07-21-serial-cross-platform-ci-reference.md), and [failover runbook](2026-07-26-ci-failover-runbook.md). Those notes remain active for artifact origin, platform fidelity, serial completeness, and trust rules. ## Alternatives considered diff --git a/.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.i18n.yaml b/.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.i18n.yaml new file mode 100644 index 0000000000..37bf7d8e0c --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.md +2026-09-07-issue-policy-module-ownership.md: 696343ddffa3f6a6e411330d038bb4a470ce78d8 +2026-09-07-issue-policy-module-ownership.zh.md: abe539a5baed35cf2f356194eab8e53d61b67314 diff --git a/.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.md b/.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.md new file mode 100644 index 0000000000..696343ddff --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.md @@ -0,0 +1,29 @@ +# Agent Note: Issue policy module ownership + +Status: implemented + +English | [中文](2026-09-07-issue-policy-module-ownership.zh.md) + +## Problem + +Issue validation and Project lifecycle processing use the same reference parser, metadata rules, and GitHub reads, but they make different decisions and perform different writes. Keeping those responsibilities in the command entry makes it harder to reuse policy rules without also depending on event dispatch and output handling. + +## Decision + +Issue management separates pure decisions, GitHub access, pull-request evaluation, lifecycle mutations, and command dispatch into owner modules. [The owner reference](../../../../.github/issue-management/README.md#module-ownership) maps those responsibilities to source files. Callers and tests import the module that owns the operation rather than using the command entry as an export collection. + +The separation preserves policy results, diagnostics, credentials, API requests, lifecycle mutations, and workflow entry commands. [Selective evaluation](2026-09-07-selective-issue-policy-evaluation.md) remains the independent owner of eligibility, Project-read selection, and event scheduling; this decision does not redefine those behaviors. + +## Alternatives considered + +**Keep one command module.** A single file avoids imports between local owners, but ties reusable decisions and GitHub access to command handling. Separate owners let PR validation and lifecycle processing share their existing rules and reads without sharing command dispatch. + +## Consequences + +Maintainers can locate a rule, network operation, or event handler by responsibility. Reuse occurs through ordinary local ESM imports, not a new package or plugin API. The added modules introduce imports that must stay coordinated when a shared function changes. + +Module separation does not grant stronger credentials, change check authority, serialize Project mutations, or prevent a future behavior regression. Existing lifecycle races and field-management limitations remain documented by their behavior owners. + +## Verification + +[Policy tests](../../../../.github/issue-management/policy.test.mjs) exercise the owner modules and check observable policy and lifecycle behavior. [Workflow tests](../../../../scripts/ci-workflow.spec.ts) cover the command wiring and workflow declarations. Their evidence concerns the checked implementation, not a guarantee that later edits preserve behavior. diff --git a/.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.zh.md b/.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.zh.md new file mode 100644 index 0000000000..abe539a5ba --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.zh.md @@ -0,0 +1,29 @@ +# Agent Note: Issue 策略模块归属 + +Status: implemented + +[English](2026-09-07-issue-policy-module-ownership.md) | 中文 + +## 问题 + +Issue 校验与 Project 生命周期处理使用相同的引用解析器、元数据规则和 GitHub 读取,但各自作出不同决策、执行不同写入。将这些职责放在命令入口中,会使复用策略规则时更难摆脱对事件分派与输出处理的依赖。 + +## 决策 + +Issue 管理将纯决策、GitHub 访问、PR(Pull Request)求值、生命周期 mutation 和命令分派分配给各自的所属模块。[所属参考文档](../../../../.github/issue-management/README.zh.md#module-ownership)将这些职责对应到源文件。调用方和测试直接导入拥有该操作的模块,不把命令入口用作导出集合。 + +此职责分离保留策略结果、诊断、凭据、API 请求、生命周期 mutation 和工作流入口命令。[选择性求值](2026-09-07-selective-issue-policy-evaluation.zh.md)仍独立拥有强制范围、Project 读取选择和事件调度规则;本决策不重新定义这些行为。 + +## 考虑过的替代方案 + +**保留单一命令模块。** 单文件可以避免本地所属模块之间的导入,但会把可复用的决策与 GitHub 访问绑定到命令处理。独立的所属模块让 PR 校验和生命周期处理可以共享既有规则与读取,而无需共享命令分派。 + +## 影响 + +维护者可以按职责定位规则、网络操作或事件处理器。复用通过普通的本地 ESM 导入完成,而不是新增包或插件 API。新增模块引入的导入关系需要在共享函数变更时同步维护。 + +模块分离不会授予更强的凭据、更改检查权威来源、串行化 Project mutation,也不能防止未来的行为回归。既有生命周期竞态与字段管理限制仍由各自的行为文档记录。 + +## 验证 + +[策略测试](../../../../.github/issue-management/policy.test.mjs)运行所属模块,并检查可观察的策略与生命周期行为。[工作流测试](../../../../scripts/ci-workflow.spec.ts)覆盖命令接线与工作流声明。这些证据针对被检查的实现,不保证后续编辑仍保留行为。 diff --git a/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.i18n.yaml b/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.i18n.yaml new file mode 100644 index 0000000000..435048c1ba --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.md +2026-09-07-selective-issue-policy-evaluation.md: 5e05bef320980b6069ede9edb0d40446ddd27b0f +2026-09-07-selective-issue-policy-evaluation.zh.md: 37c531e14e485499a3cb76d042d4c17438d413a0 diff --git a/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.md b/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.md new file mode 100644 index 0000000000..5e05bef320 --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.md @@ -0,0 +1,43 @@ +# Agent Note: Selective Issue policy evaluation + +Status: implemented + +English | [中文](2026-09-07-selective-issue-policy-evaluation.zh.md) + +## Problem + +Informational Issue references provide context, while resolving references carry a Priority obligation. Requiring Project access for both makes unrelated board configuration or App availability block context-only PRs. Looking up referenced Issues before determining enforcement eligibility also spends credentials and API requests on PRs that cannot fail policy. + +Lifecycle events have a separate cost: an approval, comment, push, label change, or assignment change need not perform a Project status handoff. Allocating a runner for a known no-op consumes capacity without changing the Issue. + +## Decision + +[Issue policy](../../../../.github/workflows/issue-policy.yml) keeps its required job and trusted default-branch implementation. Enforcement eligibility precedes reference reads and Project App token creation: draft PRs, Bot/App authors, and human PRs with neither review requests nor submitted reviews do not require policy validation. + +The workflow checks the trusted checkout for a selective-preflight capability marker before invoking the command. A checkout without the marker uses full legacy validation for human PRs and preserves the legacy Bot/App exemption. This supports PR workflow YAML running against default-branch code that lacks preflight; execution errors never select the fallback. + +Eligible PRs resolve references through repository REST reads. Informational references prove Issue identity without Project access. Only actual Issues named by resolving references require Project Priority reads; a PR number cannot satisfy the Issue requirement or cause a Project query. [The owner reference](../../../../.github/issue-management/README.md) defines metadata validation and failure behavior. + +[Issue lifecycle](../../../../.github/workflows/issue-lifecycle.yml) subscribes to status-relevant PR events and filters title-only edits. It does not subscribe to PR pushes or label changes, or Issue assignment changes. Its job condition rejects approved/commented reviews before runner allocation. Changes-requested reviews retain their status command. + +This scheduling decision partially supersedes the no-op-job scheduling in [event-directed review status](2026-08-10-event-directed-pr-review-status.md), not its handoff semantics or human-ownership protection. [Project-local planning fields](2026-09-02-project-local-issue-planning-fields.md) still own opened-only, empty-only Start Date initialization for every referenced Issue, including informational references. The validation read exemption does not exempt that lifecycle mutation. + +## Alternatives considered + +**Read every referenced Issue's Project fields.** Informational references do not constrain Priority, so these queries add failure dependencies without contributing a validation result. + +**Keep successful no-op lifecycle jobs for approvals and comments.** That preserves a green job presentation but allocates a runner for an event with no lifecycle command. Lifecycle is separate from the retained required policy job. + +**Remove the required policy job or redesign check authority.** Selective reads and lifecycle scheduling can reduce avoidable work without changing which required check GitHub expects. Check-authority redesign is not part of this decision. + +## Consequences + +Informational-only validation needs repository access but not Project credentials. Resolving validation still fails when required Project reads or field checks fail. Preflight and final validation each read live REST state, duplicating repository requests rather than caching a verdict. Avoiding Project reads and token creation does not guarantee fewer total API requests. The required policy job still allocates a runner; this is not a zero-cost required check. + +Maintainers manually manage the Project custom Priority field. Native Issue-field skill guidance does not update that value. There is no Priority synchronization, field migration, or change to [presentation-neutral policy](2026-09-03-semantic-issue-templates-and-policy.md). + +Omitted lifecycle events cannot repair stale Project state. Event replay and concurrent writes retain the races documented by the lifecycle and planning-field owners. Actual Actions-minute savings and live GitHub App access require operational observation, not inference from a mocked API test. + +## Verification + +[Policy tests](../../../../.github/issue-management/policy.test.mjs) verify early exemptions, REST-only informational references, actual-Issue filtering, resolving Priority reads and failures, and the lifecycle command selection. [Workflow tests](../../../../scripts/ci-workflow.spec.ts) verify Project-token conditions, the retained required job, pruned subscriptions, and runner-level lifecycle filtering. Local fixtures do not establish live webhook delivery or billing outcomes. diff --git a/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.zh.md b/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.zh.md new file mode 100644 index 0000000000..37c531e14e --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.zh.md @@ -0,0 +1,43 @@ +# Agent Note: Issue 策略的选择性求值 + +Status: implemented + +[English](2026-09-07-selective-issue-policy-evaluation.md) | 中文 + +## 问题 + +信息型 Issue 引用提供背景,解决型引用则带有 Priority 义务。两者都要求 Project 访问,会让无关的看板配置或 App 可用性阻止仅提供背景的 PR(Pull Request)。在确定强制范围之前读取被引用 Issue,也会为不可能因策略而失败的 PR 消耗凭据与 API 请求。 + +生命周期事件有独立成本:批准、评论、推送、标签变更或指派变更不一定需要执行 Project 状态交接。为已知无操作的事件分配 runner,会占用容量而不改变 Issue。 + +## 决策 + +[Issue policy](../../../../.github/workflows/issue-policy.yml)保留必需 job 与受信任的默认分支实现。强制范围判定先于引用读取与 Project App token 创建:草稿 PR、Bot/App 作者,以及既无评审请求也无已提交评审的人类 PR 均不需要策略校验。 + +工作流在调用命令前检查受信任检出中的选择性预检能力标记。缺少标记的检出对人类 PR 执行完整旧版校验,并保留旧版 Bot/App 豁免。这支持 PR 工作流 YAML 与缺少预检功能的默认分支代码配合执行;执行错误不会触发回退。 + +强制范围内的 PR 通过仓库 REST 读取解析引用。信息型引用无需 Project 访问即可证明 Issue 身份。只有解决型引用指向的实际 Issue 需要读取 Project Priority;PR 编号既不能满足 Issue 引用要求,也不会引发 Project 查询。[所属参考文档](../../../../.github/issue-management/README.zh.md)定义元数据校验与失败行为。 + +[Issue lifecycle](../../../../.github/workflows/issue-lifecycle.yml)订阅与状态相关的 PR 事件,并过滤仅标题编辑。它不订阅 PR 推送、PR 标签变更或 Issue 指派变更。job 条件在 runner 分配前排除 approved/commented 评审。请求修改的评审保留其状态命令。 + +本调度决策部分取代[事件驱动评审状态](2026-08-10-event-directed-pr-review-status.zh.md)中无操作 job 的调度方式,但不取代交接语义或人工状态归属保护。[Project 局部规划字段](2026-09-02-project-local-issue-planning-fields.zh.md)仍拥有对每个被引用 Issue(包括信息型引用)仅在 PR 打开时、仅对空值初始化 Start Date 的规则。校验读取豁免不豁免该生命周期 mutation。 + +## 考虑过的替代方案 + +**读取每个被引用 Issue 的 Project 字段。** 信息型引用不约束 Priority,因此这些查询只增加失败依赖,不贡献校验结果。 + +**为批准与评论保留成功的无操作生命周期 job。** 这可以保持绿色 job 展示,却会为没有生命周期命令的事件分配 runner。生命周期与保留的必需策略 job 相互独立。 + +**删除必需策略 job 或重新设计检查权威来源。** 选择性读取和生命周期调度可以减少可避免的工作,无需改变 GitHub 期待的必需检查。检查权威来源的重新设计不属于本决策。 + +## 影响 + +仅含信息型引用的校验需要仓库访问,但不需要 Project 凭据。解决型校验在所需 Project 读取或字段检查失败时仍会失败。预检与最终校验各自读取实时 REST 状态,重复仓库请求而不缓存结论。避免 Project 读取与 token 创建不保证减少 API 请求总数。必需策略 job 仍分配 runner;它并非零成本的必需检查。 + +维护者手动管理 Project 自定义 Priority 字段。原生 Issue 字段的 skill 指引不会更新该值。不提供 Priority 同步或字段迁移,也不改变[不检查展示形式的策略](2026-09-03-semantic-issue-templates-and-policy.zh.md)。 + +被省略的生命周期事件不能修复过时的 Project 状态。事件重放和并发写入仍有生命周期与规划字段文档记录的竞态。实际 Actions 分钟节省与 GitHub App 实际访问权限需要运营观察,不能从模拟 API 测试推断。 + +## 验证 + +[策略测试](../../../../.github/issue-management/policy.test.mjs)验证早期豁免、仅使用 REST 的信息型引用、实际 Issue 过滤、解决型 Priority 读取及失败,以及生命周期命令选择。[工作流测试](../../../../scripts/ci-workflow.spec.ts)验证 Project token 条件、保留的必需 job、精简后的订阅及 runner 级生命周期过滤。本地 fixture 不能证明实际 webhook 交付或计费结果。 diff --git a/.agents/notes/implemented/process/2026-09-11-persistence-type-history.i18n.yaml b/.agents/notes/implemented/process/2026-09-11-persistence-type-history.i18n.yaml new file mode 100644 index 0000000000..906b897ed6 --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-11-persistence-type-history.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-11-persistence-type-history.md +2026-09-11-persistence-type-history.md: ef2cd0d8c7013883657d6dad9eb1c4894d9172e7 +2026-09-11-persistence-type-history.zh.md: 6045176ecbc41ef758e2dd98ddb3de6eb3c049f8 diff --git a/.agents/notes/implemented/process/2026-09-11-persistence-type-history.md b/.agents/notes/implemented/process/2026-09-11-persistence-type-history.md new file mode 100644 index 0000000000..ef2cd0d8c7 --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-11-persistence-type-history.md @@ -0,0 +1,41 @@ +# Agent Note: In-tree persistence-type history + +Status: implemented + +English | [中文](2026-09-11-persistence-type-history.zh.md) + +## Problem + +A persisted event can retain the same payload type expression while a referenced type changes. Reviewing declaration text alone does not expose every nested structural change. A digest detects a difference but cannot explain whether it adds optional data or changes an existing property. Regenerating a catalog also does not establish that the author reviewed the persistence consequences. + +## Decision + +One normalized type model supplies the readable persistence catalog, the complete schema inventory, and transitive per-root digests. Roots identify the logical Session header, the physical JSONL header line, the event envelope, and every repository-declared event. Source locations, alias names, comments, and property order are presentation details outside the digest. Referenced and recursive types participate in structural comparison; opaque values retain explicit coverage limits. + +The dedicated [persistence-change records](../../../../docs/persistence-changes/README.md) bind acknowledgement to the after digest of each affected root. A generated companion stores complete after schemas. The initial record covers all roots; later records name each root's predecessor. A predecessor's after schema supplies the next before schema. Verification rejects ambiguous history and requires current source to match the terminal recorded state without consulting Git history or remote services. + +Automatic classification permits optional body additions, required-to-optional body changes, and ordinary event additions within one version. Other structural changes require a version-bump decision that includes an increasing header version in that record. The [format-version procedure](../../../../docs/cookbook/adding-a-session-format-version.md) continues to own adjacent migration work. Every structural difference requires an explicit record, including additions allowed within the same version. + +The new document kind preserves the compatibility reasoning and evidence for a historical type transition. Agent Notes retain mechanism-level decisions; they do not become a growing inventory of individual acknowledgements. Machine declarations remain identical across the bilingual pair and are parsed once. + +Record commands generate the bilingual catalog and consistency records from repository-owned templates. Authors can supply the two languages' summary, compatibility reasoning, and actual verification evidence as structured input. Generation supplies identifiers, digests, and snapshots; it never invents a compatibility explanation or test result. Structured check output retains a nonzero failure exit status and reports stable change kinds independently of human-readable descriptions. + +Explicit update refreshes an unaccepted terminal record without deleting its authored prose. It recomputes the transition against the remaining history and rejects the baseline or any record with dependants. Review acceptance is not a fact available from the tree, so authors preserve accepted records and add successors. + +## Alternatives considered + +**Hash the displayed declarations.** Referenced definitions can change without altering the displayed expression. Normalized transitive types make those changes visible while omitting source-file and alias churn. + +**Bind every acknowledgement to one overall digest.** An unrelated event change would invalidate a reviewed acknowledgement. Per-root history limits invalidation to the affected header or event while the complete inventory retains coverage. + +**Compare with the PR merge-base or a release checkout.** Those inputs require history outside the current tree. Retained after schemas provide the comparison input to local checks and CI alike. A hash without its schema cannot support automatic structural classification. + +**Treat every changed digest as a version bump.** Optional body additions, required-to-optional changes, and ordinary event additions do not always require a new persistence version. Conservative classification distinguishes these cases and leaves the compatibility explanation to review. + +**Infer behavior from types or permit arbitrary compatibility overrides.** A type graph cannot prove replay semantics. The checker confines itself to detectable structure and rejects decisions below the mechanical classification; it does not claim to detect behavior-only changes. + +## Consequences + +Authors retain one snapshot per affected root per accepted transition and resolve history forks when integrating competing changes to that root. Independent roots can advance without rewriting unrelated acknowledgements. The [cookbook](../../../../docs/cookbook/reviewing-persistence-type-changes.md) provides the local authoring and verification procedure. + +The checks prove current-tree consistency, not that accepted history was never rewritten or that an explanation is semantically correct. Hidden structures inside `unknown` and similar opaque values remain undetectable. This mechanism adds no runtime digest or Session-format field. Existing [versioning](../architecture/2026-08-10-session-log-version-mechanism.md) and [released-generation migration](../architecture/2026-08-31-released-session-format-migrations.md) decisions retain their independent runtime guarantees. diff --git a/.agents/notes/implemented/process/2026-09-11-persistence-type-history.zh.md b/.agents/notes/implemented/process/2026-09-11-persistence-type-history.zh.md new file mode 100644 index 0000000000..6045176ecb --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-11-persistence-type-history.zh.md @@ -0,0 +1,41 @@ +# Agent Note: 目录内的持久化类型历史 + +Status: implemented + +[English](2026-09-11-persistence-type-history.md) | 中文 + +## 问题 + +持久化事件可以保留相同的载荷类型表达式,但其引用的类型已经改变。只审阅声明文本无法暴露所有嵌套结构变更。摘要能检测差异,却不能说明它是添加可选数据还是更改已有属性。重新生成目录也不能证明作者已审阅持久化方面的影响。 + +## 决策 + +一个规范化类型模型提供可读持久化目录、完整 schema 清单和包含传递引用的逐根摘要。根标识逻辑会话头、物理 JSONL 头行、事件封装以及每个仓库内声明的事件。源码位置、别名名称、注释和属性顺序属于摘要之外的展示细节。被引用类型和递归类型参与结构比较;不透明值保留明确的覆盖限制。 + +专用的[持久化变更记录](../../../../docs/persistence-changes/README.zh.md)将确认绑定到每个受影响根的变更后摘要。生成的伴随文件保存完整的变更后 schema。初始记录覆盖所有根;后续记录为每个根指定前驱。前驱的变更后 schema 提供下一次的变更前 schema。验证拒绝有歧义的历史,并要求当前源码匹配最终记录状态,无需查询 Git 历史或远端服务。 + +自动分类允许可选事件体新增、事件体属性从必选改为可选,以及普通事件新增保持同一版本。其他结构变更需要升版本决策,并在该记录中包含递增的头部版本。[格式版本流程](../../../../docs/cookbook/adding-a-session-format-version.zh.md)继续负责相邻迁移工作。每个结构差异都需要显式记录,包括允许保持同一版本的新增。 + +新文档类型保留历史类型转换的兼容性说明和证据。Agent Note 保留机制层面的决策,不积累单次确认清单。双语对中的机器声明保持相同,且只解析一次。 + +记录命令从仓库内模板生成双语目录和一致性记录。作者可以将两种语言的概述、兼容性说明和实际验证证据作为结构化输入。生成过程提供标识符、摘要和快照,不编造兼容性说明或测试结果。结构化检查输出保留非零失败退出码,并报告独立于可读描述的稳定变更种类。 + +显式更新可刷新尚未接受的末端记录,无需删除其人工说明。它根据剩余历史重新计算转换,并拒绝基线或已有依赖方的记录。审阅接受状态不是目录内可得的事实,因此作者保留已接受记录并添加后继。 + +## 考虑过的替代方案 + +**对展示的声明计算哈希。** 被引用的定义可能改变,而展示的表达式保持不变。规范化传递类型使这些变更可见,同时排除源文件和别名调整。 + +**将每次确认绑定到一个整体摘要。** 无关事件的变更会使已审阅的确认失效。逐根历史将失效范围限制在受影响的头部或事件,完整清单仍然保留覆盖范围。 + +**与 PR 的 merge-base 或发布版本检出目录比较。** 这些输入需要当前目录之外的历史。保留的变更后 schema 为本地检查和 CI 提供相同的比较输入。没有 schema 的哈希无法支持自动结构分类。 + +**把每次摘要变化都视为升版本。** 可选事件体新增、必选改可选,以及普通事件新增不一定需要新的持久化版本。保守分类区分这些情况,并将兼容性说明交给审阅。 + +**从类型推断行为,或允许任意兼容性豁免。** 类型图无法证明回放语义。检查器仅覆盖可检测结构,并拒绝低于机械分类要求的决策;它不声称能够检测纯行为变更。 + +## 影响 + +作者为每次已接受转换中的每个受影响根保留一份快照,并在集成该根的竞争变更时解决历史分叉。独立根可以演进而不改写无关确认。[实操手册](../../../../docs/cookbook/reviewing-persistence-type-changes.zh.md)提供本地编写与验证流程。 + +检查证明当前目录的一致性,不证明已接受历史从未被改写,也不证明说明在语义上正确。`unknown` 等不透明值内部的隐藏结构仍然无法检测。本机制不添加运行时摘要或会话格式字段。现有[版本机制](../architecture/2026-08-10-session-log-version-mechanism.zh.md)和[已发布代际迁移](../architecture/2026-08-31-released-session-format-migrations.zh.md)决策保留其独立的运行时保证。 diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml index b201d8a88e..16094b8d00 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md -2026-07-28-remove-synthetic-log-only-turns.md: e5138ad22a68d2c7dd6d201df5522536e1d26414 -2026-07-28-remove-synthetic-log-only-turns.zh.md: 2fb15ff837b4984a069a2a5306f167fe72177f6b +2026-07-28-remove-synthetic-log-only-turns.md: b35045d036dbb3ea18431b76cec2c41674de5607 +2026-07-28-remove-synthetic-log-only-turns.zh.md: 3dbcd0d9b258f4ea9f4515ba009e75487a96c342 diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md index e5138ad22a..b35045d036 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md @@ -20,7 +20,7 @@ Core session invariants continue to enforce core-owned execution relations: turn The title service appends `session/title` directly after its existing service, revision, cancellation, and live-session checks. The bundled model helper appends its literal `session/title-llm-request` record before dispatch. Persistence admits both through the bounded `session/event` path and drains them at ordinary checkpoints and lifecycle teardown; neither append forces a flush merely because it is between turns. A fallback, auxiliary request record, or accepted provider title may therefore appear after `turn/end` and before the next `turn/start`. Manual compaction uses the same between-turn capability for a `compaction/* { turn: null }` bracket, but explicitly flushes the closed attempt because `/compact` promises durability before releasing queued prompt admission. -A session fork may end at any stable event position outside an open turn, not only at `turn/end`. This preserves standalone title and other plugin-owned log-only records in a default fork while still rejecting a prefix cut through active execution. +`SessionStore.fork()` may end at any stable event position outside an open turn, not only at `turn/end`. This preserves standalone title and other plugin-owned log-only records in a default store fork while still rejecting a prefix cut through active execution. The [Session Controller fork decision](../bug-fix/2026-09-11-session-controller-fork-turn-cut.md) limits its completed-turn operation to the selected closing event. The historical [universal turn-enclosure decision](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md) remains useful only as the reason the synthetic mechanism was introduced. The [context-injection decision](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md) established the current meaning: one turn represents one model-loop execution. The [queued manual compaction decision](../feature/2026-07-30-queued-manual-compaction.md) applies that rule to a durable multi-event bracket and owns its marker and admission semantics. diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md index 2fb15ff837..3dbcd0d9b2 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md @@ -20,7 +20,7 @@ Status: implemented 标题服务会在完成既有的服务状态、修订、取消和活跃会话检查后,直接追加 `session/title`。随附模型辅助函数会在发起调用前追加其字面量 `session/title-llm-request` 记录。持久化通过有界 `session/event` 路径接纳两者,并在常规检查点与生命周期结束时排空;二者都不会仅因为位于轮次之间就强制刷写。因此,回退标题、辅助请求记录或已接受的提供方标题可以出现在 `turn/end` 之后、下一个 `turn/start` 之前。手动压缩(compaction)利用同一项轮次间能力记录 `compaction/* { turn: null }` 标记对,但会显式刷写已闭合的尝试,因为 `/compact` 承诺在放行排队中的提示词前完成持久化。 -会话 fork 可以结束于开放轮次之外的任意稳定事件位置,而不限于 `turn/end`。这样,默认 fork 会保留独立标题和其他插件所属的纯日志记录,同时仍拒绝在活跃执行过程中截断前缀。 +`SessionStore.fork()` 可以结束于开放轮次之外的任意稳定事件位置,而不限于 `turn/end`。这样,默认的 store fork 会保留独立标题和其他插件所属的纯日志记录,同时仍拒绝在活跃执行过程中截断前缀。[Session Controller 分叉决策](../bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md)将其已结束轮次操作限制在选中的结束事件处。 历史上的[通用轮次封闭决策](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md)如今只适合用于解释为何曾引入合成机制。[上下文注入决策](../architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md)确立了当前语义:一个轮次表示一次模型循环执行。[排队手动压缩决策](../feature/2026-07-30-queued-manual-compaction.zh.md)将该规则应用于持久多事件标记对,并拥有其标记与接纳语义。 diff --git a/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.i18n.yaml index 713c7e676b..11c416587f 100644 --- a/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md -2026-08-09-explicit-schedule-time-zone.md: fd8a09df4c6f8b0003e6e01f48510ecb28d7e568 -2026-08-09-explicit-schedule-time-zone.zh.md: 3a840edda83edf70e65d6acf6a8874d1d47b09b5 +2026-08-09-explicit-schedule-time-zone.md: 2f23394e65d37f9eb714c2e55b0556cdf0bc967e +2026-08-09-explicit-schedule-time-zone.zh.md: aafa5d56853339e51b9a4265a32fca1a24e7b1dd diff --git a/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md b/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md index fd8a09df4c..2f23394e65 100644 --- a/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md +++ b/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md @@ -6,17 +6,17 @@ English | [中文](2026-08-09-explicit-schedule-time-zone.zh.md) ## Problem -Implicit local `at` input made a browser fact into shared product state. Capturing a default zone on Session creation required new Session headers, create/resume/fork conflict rules, JSONL metadata, a SQLite migration, client creation plumbing, Host comparisons, and Schedule logic coupled to time-context markers. Travel, concurrent tabs, missing provenance, and old Sessions then needed a confirmation protocol merely to decide whether an omitted field was safe. +Implicit local `at` input made a browser fact into shared product state. Capturing a default zone on Session creation required new Session headers, create/resume/fork conflict rules, JSONL metadata, a SQLite migration, client creation plumbing, Host comparisons, and Schedule logic coupled to time-context markers. Travel, concurrent tabs, missing browser-zone records, and old Sessions then needed a confirmation protocol merely to decide whether an omitted field was safe. Most of that complexity sat outside Schedule. The model already interprets natural language before it calls the tool, so a durable Session default duplicated an assumption instead of strengthening the absolute-time boundary. ## Decision -Browser zone is request-local provenance. The Web client samples `Intl.DateTimeFormat().resolvedOptions().timeZone` for every prompt. The Host accepts an optional `clientTimeZone`, validates and canonicalizes `UTC` or an IANA Area/Location at the RPC boundary, and logs it on that exact `user-rpc` message. Invalid values reject prompt admission. Non-browser clients may omit it. +The browser zone is request-local source data. The Web client samples `Intl.DateTimeFormat().resolvedOptions().timeZone` for every prompt. The Host accepts an optional `clientTimeZone`, validates and canonicalizes `UTC` or an IANA Area/Location at the RPC boundary, and logs it on that exact `user-rpc` message. Invalid values reject prompt admission. Non-browser clients may omit it. -Time-context derives unique, mixed, or missing browser facts from original user-rpc messages in the open turn. A unique zone formats the clock and tells the model to interpret otherwise-unqualified dates and times in that zone. Mixed or missing provenance tells the model to ask the user. The configured or process zone is only a display fallback and is never presented as user authority. +Time-context derives unique, mixed, or missing browser facts from original user-rpc messages in the open turn. A unique zone formats the clock and tells the model to interpret otherwise-unqualified dates and times in that zone. Mixed or missing browser-zone records tell the model to ask the user. The configured or process zone is only a display fallback and is never presented as user authority. -Schedule accepts no implicit local zone. `at` is either a strict offset-bearing RFC 3339 string or exact `{ date, time, time_zone }`. The structured form requires its zone even when time-context just showed the model a browser zone. Schedule does not import time-context, inspect user-message provenance, read a Session header, or produce a confirmation error. Its parser validates the explicit value, rejects daylight-saving gaps, chooses the first instant in overlaps, and stores only canonical UTC `scheduledAt`. +Schedule accepts no implicit local zone. `at` is either a strict offset-bearing RFC 3339 string or exact `{ date, time, time_zone }`. The structured form requires its zone even when time-context just showed the model a browser zone. Schedule does not import time-context, inspect user-message sources, read a Session header, or produce a confirmation error. Its parser validates the explicit value, rejects daylight-saving gaps, chooses the first instant in overlaps, and stores only canonical UTC `scheduledAt`. No Session time-zone field, create/resume/fork zone conflict, JSONL header field, SQLite column or migration, connection default, or Schedule-specific Host/client presentation remains. The browser assumption crosses into Schedule only through the model's explicit tool arguments. @@ -26,11 +26,11 @@ No Session time-zone field, create/resume/fork zone conflict, JSONL header field **Use the most recent browser zone as mutable Session state.** This reduces confirmation prompts but lets one tab silently change another tab's interpretation and makes replay depend on update ordering. -**Let Schedule inspect the latest time-context message.** A prose snapshot is model-visible evidence, not a typed package seam. Consuming it would couple Schedule to AgentLoop history and duplicate validation against original provenance. +**Let Schedule inspect the latest time-context message.** A prose snapshot is model-visible evidence, not a typed package seam. Consuming it would couple Schedule to AgentLoop history and duplicate validation against the original user-message sources. **Let the Host inject `time_zone` into tool calls.** The Host cannot know which natural-language expression the model interpreted or whether the user named another zone. Rewriting model arguments hides meaning at the wrong boundary. -**Require the model to ask on every unqualified time.** This is safe but unnecessarily interrupts the common browser-local case. The request-local instruction provides the intended assumption while mixed or missing provenance still asks. +**Require the model to ask on every unqualified time.** This is safe but unnecessarily interrupts the common browser-local case. The request-local instruction provides the intended assumption while mixed or missing browser-zone records still ask. ## Verification @@ -42,6 +42,6 @@ Source audits reject `SessionHeader.timeZone`, persistence `time_zone` columns, - Browser-local natural language works without a persisted Session-zone subsystem. - Schedule has one explicit, independently testable absolute-time boundary. -- Travel and concurrent tabs affect only their own prompts; a turn with mixed provenance asks instead of mutating shared state. +- Travel and concurrent tabs affect only their own prompts; a turn with mixed browser-zone records asks instead of mutating shared state. - Non-browser clients remain valid but must provide enough natural-language context or explicit tool arguments. - The model may still make an interpretation error; the tool guarantees only that the explicit calendar value is valid and deterministic. diff --git a/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.zh.md b/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.zh.md index 3a840edda8..aafa5d5685 100644 --- a/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.zh.md +++ b/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.zh.md @@ -6,17 +6,17 @@ Status: implemented ## 问题 -隐式本地 `at` 输入把浏览器事实变成了共享产品状态。在 Session 创建时捕获默认时区,需要增加新的 Session header、create/resume/fork 冲突规则、JSONL metadata、SQLite migration、client 创建 plumbing、Host 比较,以及与 time-context 标记耦合的 Schedule 逻辑。随后,旅行、并发 tab、缺失 provenance 和旧 Session 都需要一套确认协议,仅仅为了判断省略字段是否安全。 +隐式本地 `at` 输入把浏览器事实变成了共享产品状态。在 Session 创建时捕获默认时区,需要增加新的 Session header、create/resume/fork 冲突规则、JSONL metadata、SQLite migration、client 创建 plumbing、Host 比较,以及与 time-context 标记耦合的 Schedule 逻辑。随后,旅行、并发 tab、缺失浏览器时区记录和旧 Session 都需要一套确认协议,仅仅为了判断省略字段是否安全。 大部分复杂度都位于 Schedule 之外。模型在调用工具前已经解释自然语言,因此持久 Session 默认值只是重复了一个假设,并没有强化绝对时间边界。 ## 决策 -浏览器时区是请求本地的 provenance。Web client 会为每条提示词采样 `Intl.DateTimeFormat().resolvedOptions().timeZone`。Host 接受可选的 `clientTimeZone`,在 RPC 边界校验并规范化 `UTC` 或 IANA Area/Location,再将其记录在确切的那条 `user-rpc` 消息上。无效值会使提示词准入被拒绝。非浏览器 client 可以省略它。 +浏览器时区是请求本地的来源数据。Web client 会为每条提示词采样 `Intl.DateTimeFormat().resolvedOptions().timeZone`。Host 接受可选的 `clientTimeZone`,在 RPC 边界校验并规范化 `UTC` 或 IANA Area/Location,再将其记录在确切的那条 `user-rpc` 消息上。无效值会使提示词准入被拒绝。非浏览器 client 可以省略它。 -Time-context 从 open turn 中的原始 user-rpc 消息派生唯一、混合或缺失的浏览器事实。唯一时区会用于格式化时钟,并告诉模型把未明确限定时区的日期和时间解释为该时区。provenance 混合或缺失时,模型会被告知询问用户。配置或进程时区只作为显示 fallback,绝不会被呈现为用户权威。 +Time-context 从 open turn 中的原始 user-rpc 消息派生唯一、混合或缺失的浏览器事实。唯一时区会用于格式化时钟,并告诉模型把未明确限定时区的日期和时间解释为该时区。浏览器时区记录混合或缺失时,模型会被告知询问用户。配置或进程时区只作为显示 fallback,绝不会被呈现为用户权威。 -Schedule 不接受隐式本地时区。`at` 要么是带显式偏移量且严格符合 RFC 3339 的字符串,要么是精确的 `{ date, time, time_zone }`。即使 time-context 刚向模型展示了浏览器时区,结构化形式仍要求自己的时区。Schedule 不导入 time-context、不检查 user message provenance、不读取 Session header,也不产生确认错误。它的 parser 会校验显式值、拒绝夏令时缺口、在重叠时选择第一个时点,并且只存储规范化后的 UTC `scheduledAt`。 +Schedule 不接受隐式本地时区。`at` 要么是带显式偏移量且严格符合 RFC 3339 的字符串,要么是精确的 `{ date, time, time_zone }`。即使 time-context 刚向模型展示了浏览器时区,结构化形式仍要求自己的时区。Schedule 不导入 time-context、不检查 user message 来源、不读取 Session header,也不产生确认错误。它的 parser 会校验显式值、拒绝夏令时缺口、在重叠时选择第一个时点,并且只存储规范化后的 UTC `scheduledAt`。 不再保留 Session 时区字段、create/resume/fork 时区冲突、JSONL header 字段、SQLite column 或 migration、连接默认值,也不再保留 Schedule 专属的 Host/client 呈现。浏览器假设只会通过模型的显式工具参数跨入 Schedule。 @@ -26,11 +26,11 @@ Schedule 不接受隐式本地时区。`at` 要么是带显式偏移量且严格 **把最近的浏览器时区用作可变 Session 状态。** 这会减少确认提示,却允许一个 tab 悄然改变另一个 tab 的解释,并使回放依赖更新顺序。 -**让 Schedule 检查最新的 time-context 消息。** prose snapshot(文本快照)是模型可见证据,而不是有类型的包 seam。消费它会使 Schedule 与 AgentLoop history 耦合,并针对原始 provenance 重复校验。 +**让 Schedule 检查最新的 time-context 消息。** prose snapshot(文本快照)是模型可见证据,而不是有类型的包 seam。消费它会使 Schedule 与 AgentLoop history 耦合,并针对原始 user message 来源重复校验。 **让 Host 向工具调用注入 `time_zone`。** Host 无法知道模型解释的是哪个自然语言表达式,也无法知道用户是否指定了另一个时区。重写模型参数会在错误的边界隐藏含义。 -**要求模型对每个未限定时区的时间都询问用户。** 这样做是安全的,却会不必要地打断常见的浏览器本地场景。请求本地指令提供预期假设,而 provenance 混合或缺失时仍会询问用户。 +**要求模型对每个未限定时区的时间都询问用户。** 这样做是安全的,却会不必要地打断常见的浏览器本地场景。请求本地指令提供预期假设,而浏览器时区记录混合或缺失时仍会询问用户。 ## 验证 @@ -42,6 +42,6 @@ Host 测试固定别名的规范化、可省略行为和进入 Agent(智能体 - 无需持久 Session 时区子系统,浏览器本地自然语言也能工作。 - Schedule 具有一个显式且可独立测试的绝对时间边界。 -- 旅行与并发 tab 只影响各自的提示词;provenance 混合的 turn 会询问用户,而不是改变共享状态。 +- 旅行与并发 tab 只影响各自的提示词;浏览器时区记录混合的 turn 会询问用户,而不是改变共享状态。 - 非浏览器 client 仍然有效,但必须提供足够的自然语言上下文或显式工具参数。 - 模型仍可能产生解释错误;工具只保证显式日历值有效且具有确定性。 diff --git a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml index d848027401..57d6fb3b1f 100644 --- a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md -2026-08-28-omit-unneeded-invariant-companions.md: d4ab138d62a67c2c6666ae7998028fedb3a53580 -2026-08-28-omit-unneeded-invariant-companions.zh.md: 609bacd6b631bef3f77d373475107c1c677acb26 +2026-08-28-omit-unneeded-invariant-companions.md: 82d421677cb2f2a425c92e203766527aac7ce860 +2026-08-28-omit-unneeded-invariant-companions.zh.md: d0fda9154675d038c1f304609313654fc4e51f23 diff --git a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md b/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md index d4ab138d62..82d421677c 100644 --- a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md +++ b/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md @@ -18,7 +18,7 @@ A package publishes `./invariant` only when it can compare observations that may Service or method presence, plugin metadata or effects, fixed pure examples, and probes that call the same mutation they claim to verify remain type, load, unit, or integration-test concerns. Parser and config input, model or tool JSON, durable files, worker and process messages, and wire input remain validated at their owning input operation. -The `dsh-time-context` companion remains published. Its check compares the plugin-produced context message with independently owned current-turn user-message provenance and durable event time, so attribution, turn position, and elapsed-time relations can diverge even when the formatter itself is correct. +The `dsh-time-context` companion remains published. Its check compares the plugin-produced context message with independently owned current-turn user-message source and durable event time, so attribution, turn position, and elapsed-time relations can diverge even when the formatter itself is correct. ### Omission is explicit in the package README @@ -36,7 +36,7 @@ Existing package behavior tests remain responsible for omitted relationships, in - **Keep explained empty companions.** Rejected because a source file, public subpath, dependency edges, build output, and tests are disproportionate machinery for saying that no check exists; the package README records that conclusion directly. - **Keep the webserver probe as a teardown sentinel.** Rejected because it mutates a reserved route on unrelated lifecycle events and verifies only the service method it invokes. Real routing and HMR tests exercise the behavior without production diagnostic effects. -- **Treat every producer-format parser as self-validation.** Rejected because a parser can compare independent provenance, timing, or durable history even when one producer owns the text. `dsh-time-context` qualifies because its message is checked against current-turn user messages and durable event time; a same-writer payload round trip alone would not qualify. +- **Treat every producer-format parser as self-validation.** Rejected because a parser can compare independent source identity, timing, or durable history even when one producer owns the text. `dsh-time-context` qualifies because its message is checked against current-turn user messages and durable event time; a same-writer payload round trip alone would not qualify. - **Require every package with mutable private state to publish a companion.** Rejected because private state without an independent event or second data source cannot be checked without duplicating the implementation or exposing new API solely for diagnostics. ## Consequences diff --git a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md b/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md index 609bacd6b6..d0fda91546 100644 --- a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md +++ b/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md @@ -18,7 +18,7 @@ Status: implemented 服务或方法是否存在、插件 metadata 或 effect、固定纯函数示例,以及调用同一变更操作来验证该操作的探针,仍属于类型、加载、单元或集成测试。parser 与 config 输入、模型或工具 JSON、持久文件、worker 与进程消息和 wire 输入,仍在拥有其输入的操作处校验。 -`dsh-time-context` 伴生入口继续发布。它把插件产生的 context message 与独立拥有的当前轮用户消息 provenance 和持久事件时间进行对照,因此即使 formatter 本身正确,attribution、轮次位置与 elapsed-time 关系仍可能产生分歧。 +`dsh-time-context` 伴生入口继续发布。它把插件产生的 context message 与独立拥有的当前轮用户消息 source 和持久事件时间进行对照,因此即使 formatter 本身正确,attribution、轮次位置与 elapsed-time 关系仍可能产生分歧。 ### 在包 README 中明确省略 @@ -36,7 +36,7 @@ Status: implemented - **保留带说明的空伴生入口。** 不采用:源文件、公共子路径、依赖边、构建输出和测试是一套过于繁重的机制,不应只用来表达不存在检查;包 README 可以直接记录该结论。 - **把 webserver 探针保留为清理 sentinel。** 不采用:它会在无关生命周期事件上修改保留路由,并且只验证自己调用的服务方法。真实路由与 HMR 测试可以在没有生产诊断 effect 的情况下覆盖该行为。 -- **把每个生产方格式 parser 都视为自校验。** 不采用:即使文本只有一个生产方,parser 仍可能对照独立 provenance、时间或持久历史。`dsh-time-context` 符合条件,因为其消息会与当前轮用户消息和持久事件时间对照;只对同一写入方的 payload 做往返检查不符合条件。 +- **把每个生产方格式 parser 都视为自校验。** 不采用:即使文本只有一个生产方,parser 仍可能对照独立 source identity、时间或持久历史。`dsh-time-context` 符合条件,因为其消息会与当前轮用户消息和持久事件时间对照;只对同一写入方的 payload 做往返检查不符合条件。 - **要求每个拥有私有可变状态的包都发布伴生入口。** 不采用:没有独立事件或第二数据源的私有状态只能通过重复实现来检查,或者需要专门为诊断暴露新 API。 ## 后果 diff --git a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.i18n.yaml new file mode 100644 index 0000000000..d2c8b4ead2 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md +2026-09-06-agent-request-freeze-evidence.md: e597ba64ca31da5e6a9be0c32b922737c9a1c074 +2026-09-06-agent-request-freeze-evidence.zh.md: b56b14ca0d69261eddd46507deffba9fa945004c diff --git a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.md b/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md similarity index 94% rename from .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.md rename to .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md index 1235a87ea5..e597ba64ca 100644 --- a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.md +++ b/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md @@ -2,7 +2,7 @@ Status: implemented -English | [中文](2026-09-06-agent-request-freeze-provenance.zh.md) +English | [中文](2026-09-06-agent-request-freeze-evidence.zh.md) ## Problem @@ -28,7 +28,7 @@ Apple M4 Pro, macOS arm64, Node 24.19.0; independent worktree dependencies and b The same 800-turn, four-tools-per-historical-turn history and 40 live requests complete in every sample: 13,923 events, no live tool calls. The repeat median is 72.9% below the isolated original. The historical 70 ms M4 expectation rounds above both optimized medians; applying the shared 2× CI scale and 1.25× headroom produced the 175 ms budget used in the table. These remain local reference measurements, not hosted-runner expectations. The explicit hosted calibration below owns the enforced request-history budget; no other case or memory budget changes here. -The first optimized slot also measures cold tool continuation: totals 185.839958, 185.235583, 185.865917, 189.213459, 185.279417; median 185.839958 ms. Every sample completes 40 requests and 160 tool calls with 14,143 events. Retained heap samples are 22.591591, 22.590355, 22.594795, 22.591743, 22.594681 MiB, below the unchanged 28.75 MiB budget. The earlier baseline's approximately 22.295 MiB highlights the small provenance-table cost; weak keys prevent the table itself retaining replaced messages. +The first optimized slot also measures cold tool continuation: totals 185.839958, 185.235583, 185.865917, 189.213459, 185.279417; median 185.839958 ms. Every sample completes 40 requests and 160 tool calls with 14,143 events. Retained heap samples are 22.591591, 22.590355, 22.594795, 22.591743, 22.594681 MiB, below the unchanged 28.75 MiB budget. The earlier baseline's approximately 22.295 MiB highlights the small freeze-evidence table cost; weak keys prevent the table itself retaining replaced messages. The same slot's shipped SDK profile completes 100 turns, 200 requests, and 800 real reads per sample. Totals are 1428.555292, 1160.396333, 1139.843500, 1135.834750, 1155.890334 ms; median 1155.890334 ms. The first sample includes 461.829250 ms boot time versus 164–169 ms for the others and is retained, not discarded. Provider serialization, network time, and browser rendering remain excluded as specified by the baseline owner. @@ -70,7 +70,7 @@ Deterministic controls call the timed case's `assertRequestHistoryBudget`. They Request construction still scans message identities and allocates a fresh array; it avoids recursively traversing already-proven history. Each loop pays one complete traversal for restored history. Local headers remain a per-request cost. Message values, request markers, previous request snapshots, cancellation, and serialized SDK outputs keep their existing behavior. -The [focused tests](../../../../packages/core/agent-loop/tests/request-freeze.spec.ts) exercise shallow-frozen restored roots with mutable descendants, wrapper identity and mutability, successful-only provenance, repeated requests, same-id compaction replacements, a fresh loop, nested tool schemas, adapter and `NO_ADAPTER` stop arrays, held requests, and live cancellation. Reconstruction and cancellation suites cover adjacent loop semantics. Performance measurements use the unchanged [continuation workload](../../../../benchmarks/agent-continuation/workload.ts), not a smaller synthetic microbenchmark. +The [focused tests](../../../../packages/core/agent-loop/tests/request-freeze.spec.ts) exercise shallow-frozen restored roots with mutable descendants, wrapper identity and mutability, successful-only freeze evidence, repeated requests, same-id compaction replacements, a fresh loop, nested tool schemas, adapter and `NO_ADAPTER` stop arrays, held requests, and live cancellation. Reconstruction and cancellation suites cover adjacent loop semantics. Performance measurements use the unchanged [continuation workload](../../../../benchmarks/agent-continuation/workload.ts), not a smaller synthetic microbenchmark. Validation runs 646 Agent-loop and LLM tests with 100% statement, branch, function, and line coverage of agent.ts. Keyless TypeScript SDK bash-tool and multi-turn snapshots pass against rebuilt libraries. Python sdk-minimal and sdk-snapshot checks pass against an independently packaged node24-macos-arm64 executable. Neither SDK requires an expected-output change. The packaging deploy temporarily removes workspace dependency links; a frozen-lockfile install restores them before source checks, without a tracked dependency change. diff --git a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.zh.md b/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.zh.md rename to .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.zh.md index 357b25b0f2..b56b14ca0d 100644 --- a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.zh.md +++ b/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.zh.md @@ -2,7 +2,7 @@ Status: implemented -[English](2026-09-06-agent-request-freeze-provenance.md) | 中文 +[English](2026-09-06-agent-request-freeze-evidence.md) | 中文 ## 问题 diff --git a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.i18n.yaml index a27a7a3c2e..c7d4cb6a88 100644 --- a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-07-file-content-scan.md -2026-09-07-file-content-scan.md: 5c52a6346fb934a4c10be305dfc6393f87b43f6a -2026-09-07-file-content-scan.zh.md: 87463ba340f68820e49fd2194d0295a53b93eb3c +2026-09-07-file-content-scan.md: 5a539f0b9c4e00545d9894647599aed713285014 +2026-09-07-file-content-scan.zh.md: 9a8fe390b8b430baf7975bfc1391ccd09ae1db6e diff --git a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.md b/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.md index 5c52a6346f..5a539f0b9c 100644 --- a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.md +++ b/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.md @@ -6,11 +6,11 @@ English | [中文](2026-09-07-file-content-scan.zh.md) ## Problem -Every model dispatch checks complete message content for files, including nested tool results. A request-history CPU profile attributes 23.540 ms of self time to `contentHasFile` and 5.584 ms to its callback. This traversal remains necessary even after [loop-owned freeze provenance](2026-09-06-agent-request-freeze-provenance.md) removes repeated request freezing. The hot LLM source is identical at master `bd5917`, master `112a5`, and the measured `f834b002826453e7918eeb558d052b2c24c56a76`; these observations do not establish PR causality. +Every model dispatch checks complete message content for files, including nested tool results. A request-history CPU profile attributes 23.540 ms of self time to `contentHasFile` and 5.584 ms to its callback. This traversal remains necessary even after [loop-owned freeze evidence](2026-09-06-agent-request-freeze-evidence.md) removes repeated request freezing. The hot LLM source is identical at master `bd5917`, master `112a5`, and the measured `f834b002826453e7918eeb558d052b2c24c56a76`; these observations do not establish PR causality. ## Decision -[`contentHasFile`](../../../../packages/llm/llm/src/content.ts) uses direct iteration instead of recursive `Array.some` callbacks. It preserves early exit, nested tool-result traversal, and false results for other block kinds. It stores no identities, validation results, or freeze proofs. Image detection, file projection, and request construction keep their existing behavior. The [request-freeze calibration](2026-09-06-agent-request-freeze-provenance.md) owns the request-history budget. +[`contentHasFile`](../../../../packages/llm/llm/src/content.ts) uses direct iteration instead of recursive `Array.some` callbacks. It preserves early exit, nested tool-result traversal, and false results for other block kinds. It stores no identities, validation results, or freeze proofs. Image detection, file projection, and request construction keep their existing behavior. The [request-freeze evidence](2026-09-06-agent-request-freeze-evidence.md) owns the request-history budget. ## Measurement evidence @@ -29,4 +29,4 @@ A weak negative-result cache needs proof that every relevant descendant is immut ## Consequences -The scan remains linear in visited blocks and rereads mutable nested content on each call. [Content tests](../../../../packages/llm/llm/tests/content.spec.ts) cover empty, frozen, nested, and subsequently mutated arrays; service tests preserve file-handle projection, and request-freeze, reconstruction, and resume tests preserve native-history semantics. No model-visible text or Session format changes. The freeze-provenance and [backend-baseline](../testing/2026-09-06-backend-continuation-performance.md) notes retain independent ownership; neither is superseded. +The scan remains linear in visited blocks and rereads mutable nested content on each call. [Content tests](../../../../packages/llm/llm/tests/content.spec.ts) cover empty, frozen, nested, and subsequently mutated arrays; service tests preserve file-handle projection, and request-freeze, reconstruction, and resume tests preserve native-history semantics. No model-visible text or Session format changes. The freeze-evidence and [backend-baseline](../testing/2026-09-06-backend-continuation-performance.md) notes retain independent ownership; neither is superseded. diff --git a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md b/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md index 87463ba340..9a8fe390b8 100644 --- a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md +++ b/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md @@ -6,11 +6,11 @@ Status: implemented ## Problem -每次模型分发都检查完整消息内容中的文件,包括嵌套工具结果。请求历史 CPU profile 将 23.540 ms 自身时间归于 `contentHasFile`,将 5.584 ms 归于其回调。即使[循环自有冻结来源证明](2026-09-06-agent-request-freeze-provenance.zh.md)消除了重复请求冻结,这次遍历仍然必需。master `bd5917`、master `112a5` 与实测的 `f834b002826453e7918eeb558d052b2c24c56a76` 的 LLM 热点源码完全相同;这些观察不能证明 PR 因果关系。 +每次模型分发都检查完整消息内容中的文件,包括嵌套工具结果。请求历史 CPU profile 将 23.540 ms 自身时间归于 `contentHasFile`,将 5.584 ms 归于其回调。即使[循环自有冻结证据](2026-09-06-agent-request-freeze-evidence.zh.md)消除了重复请求冻结,这次遍历仍然必需。master `bd5917`、master `112a5` 与实测的 `f834b002826453e7918eeb558d052b2c24c56a76` 的 LLM 热点源码完全相同;这些观察不能证明 PR 因果关系。 ## Decision -[`contentHasFile`](../../../../packages/llm/llm/src/content.ts) 使用直接迭代,替代递归的 `Array.some` 回调。它保留提前退出、嵌套工具结果遍历,以及其他块类型返回 false 的行为。它不存储身份、校验结果或冻结证明。图片检测、文件投影和请求构建保持既有行为。[请求冻结校准](2026-09-06-agent-request-freeze-provenance.zh.md)拥有请求历史预算。 +[`contentHasFile`](../../../../packages/llm/llm/src/content.ts) 使用直接迭代,替代递归的 `Array.some` 回调。它保留提前退出、嵌套工具结果遍历,以及其他块类型返回 false 的行为。它不存储身份、校验结果或冻结证明。图片检测、文件投影和请求构建保持既有行为。[请求冻结证据](2026-09-06-agent-request-freeze-evidence.zh.md)拥有请求历史预算。 ## Measurement evidence @@ -29,4 +29,4 @@ Apple M4 Pro、Node 24.19.0:九组交替原始/候选配对在全新的普通 ## Consequences -扫描仍与访问块数呈线性关系,每次调用都重新读取可变的嵌套内容。[内容测试](../../../../packages/llm/llm/tests/content.spec.ts)覆盖空数组、冻结数组、嵌套数组与后续变更的数组;服务测试保留文件 handle 投影,请求冻结、重建与恢复测试保留原生历史语义。模型可见文本与 Session 格式均不改变。冻结来源证明与[后端基线](../testing/2026-09-06-backend-continuation-performance.zh.md)记录仍各自拥有独立决策;两者均未被取代。 +扫描仍与访问块数呈线性关系,每次调用都重新读取可变的嵌套内容。[内容测试](../../../../packages/llm/llm/tests/content.spec.ts)覆盖空数组、冻结数组、嵌套数组与后续变更的数组;服务测试保留文件 handle 投影,请求冻结、重建与恢复测试保留原生历史语义。模型可见文本与 Session 格式均不改变。冻结证据与[后端基线](../testing/2026-09-06-backend-continuation-performance.zh.md)记录仍各自拥有独立决策;两者均未被取代。 diff --git a/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.i18n.yaml new file mode 100644 index 0000000000..1fe1deceac --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.md +2026-09-10-derived-workspace-recency.md: e8f304110c89bf563164dd24125beeb36e7644a6 +2026-09-10-derived-workspace-recency.zh.md: 60f2da36c277e77d367f152aa8a3159ff68d909c diff --git a/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.md b/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.md new file mode 100644 index 0000000000..e8f304110c --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.md @@ -0,0 +1,39 @@ +# Agent Note: Derive Workspace recency independently of manual order + +Status: implemented + +English | [中文](2026-09-10-derived-workspace-recency.zh.md) + +## Problem + +An editable activity-promoted list can disagree with its displayed timestamps without a drag: an older Session arriving late is promoted ahead of a newer Session already observed. Repeating the same complete list preserves that inversion. The historical [sidebar-order decision](../../archived/feature/2026-08-11-workspace-sidebar-order-and-folding.md) shared one editable order between Manual and Last updated to preserve positions when switching modes. That trade-off does not satisfy chronological browsing. + +## Decision + +Last updated is a pure projection of current Session summaries: ordinary rows sort by descending `updatedAt`, with Session id as the tie-break. The selected blank New Session precedes ordinary rows until its first prompt. Grouped, Ungrouped, and flat views use the same policy. Reloads and delayed summaries require no observed-timestamp history or promotion events in the view store. + +Only Manual uses browser-persisted Session display order. A Session drag snapshots every active account, applies the move to its browser-local account, and selects Manual atomically. Entering Manual also freezes every active account from the current chronological ordering. Returning to Last updated discards the manual layout; entering Manual again starts from the current timestamps. Reconciliation retains saved members that remain in the Workspace account, removes departed members, and appends newly known members at the end, newest first when several arrive together. New members without summaries wait for the summary, while previously saved slots survive a temporarily missing summary. Workspace membership and Workspace-group order remain Host-owned; Workspace-group drags still call the Host reorder operation. + +The selected blank New Session is a display invariant applied after either base order: it remains first and cannot start a drag. Manual persists the observed blank position even while the Workspace stream reconnects, preserving every other saved member until the complete baseline permits reconciliation. Waiting to record that position would lose it if the first prompt arrives before the baseline. Its first prompt removes the blank state. Manual then keeps the existing first slot and allows dragging; Last updated projects it from the prompt timestamp. The ordering subscription and reconciliation remain mounted when the sidebar is collapsed or search replaces the list body. + +The persistence key stores grouping, expansion, and saved Manual positions. Persisted observed-timestamp data is removed when active account keys are retained. Current metadata owns timestamp accuracy: a cold summary without prompt metadata can fall back to creation time, independently of view ordering. + +## Alternatives considered + +**Keep activity promotion and sort again on reconnect.** Late summaries and metadata corrections also arrive within a connection. A reconnect-only repair still allows chronology to depend on observation order. + +**Restore a separate manual layout on mode switches.** Replacing the visible list with old positions makes entering Manual surprising. Resetting the layout on a mode change gives Manual the simpler meaning of pausing the current ordering. + +**Persist a second chronological order.** Current timestamps already determine that order. A second ledger retains synchronization and recovery work without representing an independent user choice. + +**Disable dragging in Last updated.** Switching to Manual on a committed drag keeps the existing affordance and gives the edited order an explicit mode. A cancelled or ineffective drag leaves the selected mode unchanged. + +## Consequences + +Manual pauses automatic sorting of the current list. The default is Last updated, and reloads retain the selected mode and current manual positions. Chronological browsing gives up persistent drag exceptions. The view store owns manual Session positions but no activity timestamps; the Host Session-account order does not participate in this browser's display order. + +The archived sidebar note remains frozen historical evidence; its folding and Workspace-order decisions are outside this change. No active note owns the superseded shared-order policy. + +## Testing + +Component regressions cover older first arrivals, corrected and decreasing timestamps, delayed summaries, discarded-layout isolation, atomic mode switches, blank insertion and drag prevention, the first-prompt transition, collapsed-sidebar reconciliation, reloads, and automatic Manual selection on drag. The recorded-session Web scenario exercises the shipped composition with persisted positions, native dragging, grouped and flat views, a pinned blank, and reloads. Existing folding cases retain the provisional blank quota. diff --git a/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.zh.md b/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.zh.md new file mode 100644 index 0000000000..60f2da36c2 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.zh.md @@ -0,0 +1,39 @@ +# Agent Note: 独立于手动顺序派生 Workspace 最近更新顺序 + +Status: implemented + +[English](2026-09-10-derived-workspace-recency.md) | 中文 + +## 问题 + +可编辑且按活动置顶的列表即使没有拖拽,也可能与显示的时间不符:较旧的 Session 迟到后,会被提升到已观察到的较新 Session 之前。重复输入同一份完整列表仍会保留这个逆序。历史上的[侧边栏顺序决策](../../archived/feature/2026-08-11-workspace-sidebar-order-and-folding.md)让手动排序和最近更新共用一份可编辑顺序,以便切换模式时保留位置。这个取舍无法满足按时间浏览的需要。 + +## 决策 + +最近更新是当前 Session 摘要的纯投影:普通行按 `updatedAt` 降序排列,时间相同时按 Session id 排序。当前选中的空白新会话在首条提示词落地前排在普通行之前。分组、Ungrouped 和单列表视图采用相同策略。重新加载和摘要迟到不需要视图存储中的已观察时间历史或提升事件。 + +只有手动排序使用浏览器持久化的 Session 显示顺序。拖拽 Session 会一次性记录所有有效记账、在对应的浏览器本地记账中应用移动,并原子地选中手动排序。直接进入手动排序同样会从当前时间顺序冻结所有有效记账。返回最近更新会丢弃手动布局;再次进入手动排序时,从当前时间戳确定的顺序开始。对账会保留仍属于 Workspace 记账的已保存成员、移除已经离开的成员,并把新发现的成员追加到末尾;多条同时到达时按最近更新时间降序排列。尚无摘要的新成员会等待摘要,而已经保存的位置在摘要暂时缺失时仍会保留。Workspace 成员关系和 Workspace 分组顺序仍由 Host 拥有;拖拽 Workspace 分组仍会调用 Host 重排序操作。 + +当前选中的空白新会话是在两种基础顺序之后应用的显示不变式:它保持首位且无法发起拖拽。即使 Workspace 流正在重连,手动排序也会持久化已观察到的空白行位置,并保留其他所有已保存成员,直到完整基线允许对账。若等待基线后才记录该位置,首条提示词先到达时就会丢失它。首条提示词会移除空白状态;此后,手动排序保留原有首位并允许拖拽,最近更新则按提示词时间戳派生其位置。侧边栏折叠或搜索替代列表主体时,排序订阅和对账仍保持挂载。 + +持久化 key 保存分组方式、展开状态和手动排序位置。保留有效记账 key 时会移除持久化的已观察时间数据。时间准确性由当前元数据负责:缺少提示词元数据的冷摘要可能回退到创建时间,这与视图排序相互独立。 + +## 考虑过的替代方案 + +**保留活动提升,并在重连时再次排序。** 摘要迟到和元数据修正也会发生在一次连接内。仅在重连时修复仍会让时间顺序依赖观察先后。 + +**切换模式时恢复独立的手动布局。** 用旧位置替换可见列表会让进入手动排序的结果出乎预期。在模式变化时重置布局,让手动排序只表示暂停当前排列。 + +**持久化第二份时间顺序。** 当前时间戳已经决定该顺序。第二份账本保留了同步和恢复工作,却没有表达独立的用户选择。 + +**禁止在最近更新中拖拽。** 在提交拖拽时切到手动排序,可以保留既有操作入口,并为编辑后的顺序提供明确模式。取消或未产生有效移动的拖拽保持所选模式不变。 + +## 后果 + +手动排序暂停当前列表的自动排列。默认模式为最近更新,重新加载保留所选模式和当前手动位置。按时间浏览放弃持久化的拖拽例外。视图存储拥有 Session 手动位置,但不拥有活动时间戳;Host Session 记账顺序不参与本浏览器的显示顺序。 + +已归档的侧边栏记录保持为冻结的历史证据;其折叠和 Workspace 顺序决策不在本次修改范围内。没有活动记录拥有被替代的共享顺序策略。 + +## 测试 + +组件回归覆盖较旧记录首次到达、时间修正和回退、摘要迟到、已丢弃布局的隔离、原子模式切换、空白行插入与禁拖、首条提示词转换、侧边栏折叠时的对账、重新加载,以及拖拽后自动选中手动排序。录制会话 Web 场景在实际发布组合中验证持久化位置、原生拖拽、分组与单列表视图、固定首位的空白项及重新加载。既有折叠用例保留临时空白行配额。 diff --git a/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.i18n.yaml new file mode 100644 index 0000000000..f6d328096f --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.md +2026-09-11-remove-e2b-providers.md: 87bec5b728f64443e7132aa9b40feb684f9a0d0f +2026-09-11-remove-e2b-providers.zh.md: 3048a61ed28232bda2f86ddc6ba6b003060ff87d diff --git a/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.md b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.md new file mode 100644 index 0000000000..87bec5b728 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.md @@ -0,0 +1,51 @@ +# Agent Note: Remove the E2B execution providers + +Status: implemented + +English | [中文](2026-09-11-remove-e2b-providers.zh.md) + +## Problem + +The E2B experiment supplied one remote filesystem/process world for file tools, Bash, PTY and language servers while the Harness, credentials, model transport and session state stayed on the host. Its three providers demonstrated that those consumers can share the existing filesystem and subprocess interfaces without provider-specific tools. + +PTC Node confinement requires bidirectional control traffic separate from arbitrary program stdin/stdout/stderr. The pinned `e2b@2.29.1` SDK exposed standard process streams and PTY output, with no extra descriptor transport. Creating a descriptor inside the VM does not expose it to the Harness host; a remote relay or a separate authenticated stream must bridge it. + +Such a bridge owns independent progress when output blocks, bounded retention, channel closure, startup publication, command identity, cancellation and disconnect cleanup. The SDK retained complete stdout/stderr even when a consumer applied backpressure, so stream-level queue bounds alone could not bound host memory. Extending that adapter would turn the experiment into a separate remote transport project. + +## Decision + +The repository excludes the E2B sandbox owner, filesystem provider and subprocess provider, their SDK dependency, integration fixtures, opt-in workflow and generated registrations. It supplies no E2B execution backend. The removal trades that integration for a smaller maintained implementation set; it does not imply that E2B cannot support a suitable transport or that published packages have no external users. + +The [portable execution-world decision](../architecture/2026-07-28-portable-execution-world-consumers.md) remains authoritative for `ctx.fs` and `ctx.subprocess`. The pair describes one path namespace and process world. Providers own path conversion, executable lookup, ordinary streams, terminal allocation and managed cleanup; consumers retain tool semantics, policy and presentation. A local/remote metadata label adds no authority to those operations. + +Remote execution remains an intended capability. Terminal allocation, writes, foreground inspection and signalling retain their Promise contracts and pending-operation cancellation guarantees. A local implementation completing these calls synchronously does not justify removing the asynchronous behavior required by remote providers. Process completion, terminal readiness, emulator parsing and managed-range quiescence also retain their existing owners. + +This decision removes one provider family. It changes neither PTC execution nor the shared filesystem, subprocess and terminal interfaces. Direct Node confinement and validation of program-authored control traffic require their own implementation and evidence. + +## Alternatives considered + +**Retain E2B and refuse the control-channel request.** This preserves the existing remote functionality with little new transport code, but retains its SDK and remote lifecycle obligations while excluding the new execution capability. Retirement makes that maintenance tradeoff explicit. + +**Multiplex the channel over E2B standard streams.** A relay can frame stdin/control input and stdout/stderr/control output. It also needs channel-specific flow control and closure, bounded queues, and a solution to cumulative SDK output retention. These are remote transport semantics, not descriptor plumbing. + +**Use a separate authenticated stream.** This can avoid the SDK's retained-output path and let control traffic progress independently. It adds endpoint access, authentication, network policy, startup coordination and connection-failure cleanup. The removal does not introduce that transport. + +**Make terminal operations synchronous.** This simplifies local wrappers but removes delayed-operation behavior needed by remote providers. The shared asynchronous interface and its cancellation/ordering tests remain useful independently of E2B. + +## Consequences + +Custom compositions that depend on the repository's E2B packages lose that supplied backend. External adoption is unknown. Removing the integration also removes its live remote composition evidence; remaining local and delayed-provider tests protect their own contracts, not an unimplemented replacement. + +The [native containment](../architecture/2026-08-28-subprocess-native-containment.md), [outbound proxy](../architecture/2026-08-27-outbound-proxy-policy.md) and [workspace-file service](../architecture/2026-09-05-workspace-files-service.md) decisions remain active. Their E2B implementation and verification inventories are retired while their process ownership, routing and file-access requirements remain intact. The portability decision is partially superseded only in its E2B realization. + +## Reintroduction conditions + +A remote provider needs a concrete execution use case and evidence for shared file/process coordinates, policy enforcement, bounded transport retention, independent control progress, precise channel closure and managed cancellation. Source and built compositions must exercise those behaviors. A connection loss cannot justify replaying a possibly executed program or claiming unobserved cleanup succeeded. + +SSH remains a possible transport for those providers: binary channels avoid the E2B command SDK's retained-output path, but ordinary SSH exec does not map arbitrary child descriptors. A remote helper still owns control-stream bridging, file semantics, process lifetime and remote policy enforcement. The retained interfaces permit that work without claiming a replacement is already available. + +## Verification + +The removal inventory finds no live E2B package, workflow, import, dependency or catalog entry. Every surviving lockfile importer and package resolution matches its original value. The shared subprocess and terminal implementation, delayed-provider tests and PTC source are unchanged. + +The remaining filesystem, subprocess, Bash, terminal and LSP suites pass alongside the affected generator and workflow tests. `pnpm run build`, `pnpm run test:docs`, `pnpm run doc-sync`, `pnpm run lint` and `pnpm run hygiene` pass for the removal. These checks validate the resulting tree; deleted E2B tests provide no evidence for it. diff --git a/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.zh.md b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.zh.md new file mode 100644 index 0000000000..3048a61ed2 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.zh.md @@ -0,0 +1,51 @@ +# Agent Note: 移除 E2B 执行提供方 + +Status: implemented + +[English](2026-09-11-remove-e2b-providers.md) | 中文 + +## 问题 + +E2B 实验为文件工具、Bash、PTY 和语言服务器提供了一个远程文件系统/进程世界,而 Harness、凭据、模型传输和会话状态留在宿主。三个提供方证明,这些消费方可以共享现有文件系统与子进程接口,无需特定于提供方的工具。 + +PTC Node 约束需要独立于任意程序 stdin/stdout/stderr 的双向控制通信。固定版本的 `e2b@2.29.1` SDK 提供标准进程流与 PTY 输出,没有额外描述符传输。在 VM 中创建描述符不会使其暴露给 Harness 宿主;必须通过远程中继或独立的认证流进行桥接。 + +这样的桥接负责输出阻塞时的独立推进、有界保留、通道关闭、启动发布、命令身份、取消和断线清理。即使消费方施加反压,SDK 仍保留完整的 stdout/stderr,因此仅限制流级队列无法约束宿主内存。扩展这一适配器会把实验变成独立的远程传输项目。 + +## 决策 + +仓库不包含 E2B 沙箱所有者、文件系统提供方和子进程提供方,也不包含其 SDK 依赖、集成 fixture、可选工作流和生成注册项。仓库不提供 E2B 执行后端。移除用放弃该集成换取更小的维护范围;这不意味着 E2B 无法支持合适的传输,也不意味着已发布包没有外部用户。 + +[可移植执行世界决策](../architecture/2026-07-28-portable-execution-world-consumers.zh.md)继续负责 `ctx.fs` 与 `ctx.subprocess` 的约定。两者描述同一个路径命名空间与进程世界。提供方负责路径转换、可执行文件查找、普通流、终端分配和受管清理;消费方保留工具语义、策略和呈现。local/remote 元数据标签不会为这些操作增加权限依据。 + +远程执行仍是预期能力。终端分配、写入、前台检查和信号发送保留 Promise 约定,以及待处理操作的取消保证。本地实现同步完成这些调用,不足以支持移除远程提供方所需的异步行为。进程完成、终端就绪判断、模拟器解析和受管范围停稳也保留现有职责归属。 + +本决策移除一个提供方族。它既不改变 PTC 执行,也不改变共享文件系统、子进程和终端接口。直接 Node 约束与程序生成的控制通信验证需要各自的实现和证据。 + +## 考虑过的替代方案 + +**保留 E2B 并拒绝控制通道请求。** 这只需少量新传输代码即可保留现有远程功能,但仍保留 SDK 与远程生命周期维护责任,同时排除新的执行能力。移除明确表达这一维护取舍。 + +**通过 E2B 标准流多路复用通道。** 中继可以对 stdin/control 输入和 stdout/stderr/control 输出分帧。它还需要逐通道流量控制与关闭、有界队列,以及累计 SDK 输出保留的解决方案。这些是远程传输语义,不是描述符接线。 + +**使用独立的认证流。** 这可以避开 SDK 保留输出的路径,并让控制通信独立推进。它增加端点访问、认证、网络策略、启动协调和连接失败清理。本次移除不引入该传输。 + +**把终端操作改为同步。** 这可以简化本地包装,但会移除远程提供方需要的延迟操作行为。共享异步接口及其取消/排序测试在没有 E2B 时仍然有用。 + +## 后果 + +依赖仓库 E2B 包的自定义组合失去该后端。外部采用情况未知。移除集成也会移除其真实远程组合证据;保留的本地与延迟提供方测试保护各自约定,并不验证尚未实现的替代方案。 + +[原生约束](../architecture/2026-08-28-subprocess-native-containment.zh.md)、[出站代理](../architecture/2026-08-27-outbound-proxy-policy.zh.md)和[工作区文件服务](../architecture/2026-09-05-workspace-files-service.zh.md)决策保持有效。它们的 E2B 实现与验证清单被撤下,进程所有权、路由和文件访问要求保持完整。可移植性决策仅有 E2B 实现部分被取代。 + +## 重新引入条件 + +远程提供方需要具体的执行用例,并证明共享文件/进程坐标、策略强制、有界传输保留、独立控制推进、精确通道关闭和受管取消。源代码与构建后组合必须执行这些行为。连接丢失不能成为重放可能已执行程序的理由,也不能据此声称未观察到的清理已经成功。 + +SSH 仍可作为这些提供方的传输:二进制通道避开 E2B 命令 SDK 保留输出的路径,但普通 SSH exec 不映射任意子进程描述符。远程辅助进程仍负责控制流桥接、文件语义、进程生命周期和远程策略强制。保留的接口允许开展这项工作,但不声称替代方案已经可用。 + +## 验证 + +移除清点未发现有效的 E2B 包、工作流、导入、依赖或目录条目。锁文件中每个保留的 importer 与包解析结果都保持原值。共享子进程与终端实现、延迟提供方测试和 PTC 源代码保持不变。 + +保留的文件系统、子进程、Bash、终端与 LSP 套件,以及受影响的生成器与工作流测试均通过。移除后的 `pnpm run build`、`pnpm run test:docs`、`pnpm run doc-sync`、`pnpm run lint` 与 `pnpm run hygiene` 均通过。这些检查验证得到的代码树;已删除的 E2B 测试不为其提供证据。 diff --git a/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml b/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml index 6593e72d9c..753fc7fdcd 100644 --- a/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md -2026-08-23-installed-python-wheel-black-box-ci.md: 8821b93d0d1c5a32c7dbd97f67d78f7559769791 +2026-08-23-installed-python-wheel-black-box-ci.md: 8dfc6d7e344837b9663c1dbe5cff940731f53166 2026-08-23-installed-python-wheel-black-box-ci.zh.md: 05972560816193ff0b93323e6dcbea3ad5020215 diff --git a/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md b/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md index 8821b93d0d..8dfc6d7e34 100644 --- a/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md +++ b/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md @@ -14,7 +14,7 @@ The Python SDK unit suite drives fake peers, while the packaged-runtime workflow The required Python runtime workflow builds the pure SDK wheel and each platform runtime wheel before behavior tests. Every native target installs those two local files into a new Python 3.10 virtual environment, changes to a temporary directory outside the repository, unsets `PYTHONPATH` and `DSH_RUNTIME_MODE`, and invokes only the public Python modules plus the packaged executable. -The black-box harness rejects a non-venv process, repository-relative working directory, source or editable import, unequal distribution versions, an SDK dependency that does not exactly pin the runtime version, an executable outside the installed runtime package, or an executable absent from the runtime distribution record. This provenance check runs before the first agent request, so a behavior pass cannot conceal that the wrong code ran. +The black-box harness rejects a non-venv process, repository-relative working directory, source or editable import, unequal distribution versions, an SDK dependency that does not exactly pin the runtime version, an executable outside the installed runtime package, or an executable absent from the runtime distribution record. This installed-artifact identity check runs before the first agent request, so a behavior pass cannot conceal that the wrong code ran. ### Keyless behavior diff --git a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.i18n.yaml b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.i18n.yaml index cd3854e92a..9105d8e0e9 100644 --- a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md -2026-09-06-backend-continuation-performance.md: 94e9192df473515d39014c8be9e82e0413df47bc -2026-09-06-backend-continuation-performance.zh.md: 92556ad8e3434a3219ea585503bec9b17bbf576a +2026-09-06-backend-continuation-performance.md: d72e3145d9bfe0ffabab2822c8a3313785fdd44f +2026-09-06-backend-continuation-performance.zh.md: 66085e1310fbfb92cb384875015d3baa648650c5 diff --git a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md index 94e9192df4..d72e3145d9 100644 --- a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md +++ b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md @@ -39,7 +39,7 @@ The implementation reference is `925e012340f033f0521e802ba8569ce6dd7ef1ac` on Ap | Tool continuation | 358.953, 324.790, 318.861, 320.119, 322.896 | 324.280, 321.952, 340.409, 325.470, 324.312 | 322.896 / 324.312 | 340 | 850 | | Child catalog | 318.730, 309.006, 311.404, 308.565, 310.105 | 308.670, 310.030, 280.086, 303.084, 284.829 | 310.105 / 303.084 | 320 | 800 | -Continuation retains approximately 22.295 MiB; its source expectation is 23 MiB and its budget is 28.75 MiB. SDK time expectations use the existing [calibration helper](../../../../benchmarks/support/calibration.ts): 2× shared CI time scale and 1.25× variance headroom. Request history uses the direct hosted expectation in the [request-freeze calibration](../simplification/2026-09-06-agent-request-freeze-provenance.md), without the 2× scale. Memory uses only 1.25× headroom. The scale is inherited from the existing lane's calibration, not a new Linux measurement of these cases; CI evidence remains necessary when runner characteristics change. Baseline budgets protect the measured implementation; tighter budgets belong with a measured behavior-preserving fix. +Continuation retains approximately 22.295 MiB; its source expectation is 23 MiB and its budget is 28.75 MiB. SDK time expectations use the existing [calibration helper](../../../../benchmarks/support/calibration.ts): 2× shared CI time scale and 1.25× variance headroom. Request history uses the direct hosted expectation in the [request-freeze calibration](../simplification/2026-09-06-agent-request-freeze-evidence.md), without the 2× scale. Memory uses only 1.25× headroom. The scale is inherited from the existing lane's calibration, not a new Linux measurement of these cases; CI evidence remains necessary when runner characteristics change. Baseline budgets protect the measured implementation; tighter budgets belong with a measured behavior-preserving fix. A separate plain-Node request-history CPU profile attributes 132.876 ms of sampled self time to deepFreeze called by buildRequest during a 211.300 ms operation. This identifies repeated traversal of already-frozen history as a focused investigation target, not a proven optimization result. Catalog first/repeat timings remain separate because a second listing still reads body-bearing seeded children after observations are released. diff --git a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.zh.md b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.zh.md index 92556ad8e3..66085e1310 100644 --- a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.zh.md +++ b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.zh.md @@ -39,7 +39,7 @@ SDK fixture 通过 profile patch 显式插入 `fs-local` 和 `str_replace_editor | 工具续聊 | 358.953, 324.790, 318.861, 320.119, 322.896 | 324.280, 321.952, 340.409, 325.470, 324.312 | 322.896 / 324.312 | 340 | 850 | | 子会话目录 | 318.730, 309.006, 311.404, 308.565, 310.105 | 308.670, 310.030, 280.086, 303.084, 284.829 | 310.105 / 303.084 | 320 | 800 | -续聊保留约 22.295 MiB;源码期望值为 23 MiB,预算为 28.75 MiB。SDK 时间期望值使用现有[校准辅助函数](../../../../benchmarks/support/calibration.ts):2× 共享 CI 时间比例和 1.25× 波动余量。请求历史使用[请求冻结校准](../simplification/2026-09-06-agent-request-freeze-provenance.zh.md)中的直接托管期望值,不乘以 2× 比例。内存只使用 1.25× 余量。比例继承现有通道的校准,并非这些用例的新 Linux 实测值;runner 特征变化时仍需 CI 证据。基线预算保护实测实现;更紧预算属于有测量依据且保持行为的修复。 +续聊保留约 22.295 MiB;源码期望值为 23 MiB,预算为 28.75 MiB。SDK 时间期望值使用现有[校准辅助函数](../../../../benchmarks/support/calibration.ts):2× 共享 CI 时间比例和 1.25× 波动余量。请求历史使用[请求冻结校准](../simplification/2026-09-06-agent-request-freeze-evidence.zh.md)中的直接托管期望值,不乘以 2× 比例。内存只使用 1.25× 余量。比例继承现有通道的校准,并非这些用例的新 Linux 实测值;runner 特征变化时仍需 CI 证据。基线预算保护实测实现;更紧预算属于有测量依据且保持行为的修复。 独立的纯 Node 请求历史 CPU profile 在一次 211.300 ms 操作中,将 132.876 ms 采样自身时间归因于 buildRequest 调用的 deepFreeze。这把重复遍历已冻结历史定位为聚焦调查目标,不是已证实的优化结果。目录首次/重复时间分别保留,因为观察释放后第二次列举仍读取带种子子会话的正文。 diff --git a/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.i18n.yaml b/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.i18n.yaml index f9f26d4043..834bf27d4b 100644 --- a/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.md -2026-09-06-python-runtime-install-retry.md: c52e87c2d18393113c0e884d7e6d1e56cc6e6145 +2026-09-06-python-runtime-install-retry.md: 639fe2814ae1b3beadcd47d3b9eac15408c585cc 2026-09-06-python-runtime-install-retry.zh.md: 7a7943f43fcec536542c5aef0e608e177f64907f diff --git a/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.md b/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.md index c52e87c2d1..639fe2814a 100644 --- a/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.md +++ b/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.md @@ -10,7 +10,7 @@ The Python runtime lane's `Install (immutable)` step runs `pnpm install` on ever ## Decision -The install step retries `pnpm install --frozen-lockfile` up to three attempts total with a ten-second pause between failures, running under `bash` on every platform (Git Bash is on the hosted Windows images). Success on any attempt ends the step immediately; a file-lock check or native-build error that would fail every attempt still fails the step after the bounded budget. This mirrors the Wine lane's documented bounded-transfer policy without pulling in a mirror, because these installs also resolve native addons whose second-download provenance matters. +The install step retries `pnpm install --frozen-lockfile` up to three attempts total with a ten-second pause between failures, running under `bash` on every platform (Git Bash is on the hosted Windows images). Success on any attempt ends the step immediately; a file-lock check or native-build error that would fail every attempt still fails the step after the bounded budget. This mirrors the Wine lane's documented bounded-transfer policy without pulling in a mirror, because these installs also resolve native addons whose second-download source matters. ## Alternatives considered diff --git a/.agents/skills/dsh-doc/SKILL.md b/.agents/skills/dsh-doc/SKILL.md index 96e2b60a8b..39ec90a6ff 100644 --- a/.agents/skills/dsh-doc/SKILL.md +++ b/.agents/skills/dsh-doc/SKILL.md @@ -54,6 +54,7 @@ The `kind` frontmatter field selects exactly one README template. Every kind in - `package-reference` → [templates/package-reference.md](templates/package-reference.md): a Cordis plugin or service package — mount configuration, the config table, folded implementation, Model Experience and Known Limitations in the gate-owned forms. - `package-library` → [templates/package-library.md](templates/package-library.md): a package with no plugin surface — consumer entry points, no profile-install path, no mount configuration. - `package-bundle` → [templates/package-bundle.md](templates/package-bundle.md): a package declaring `dsh.bundle.patch` — the verified `dsh plugin` install path, layer semantics, patch document. +- `persistence-change` → [templates/persistence-change.md](templates/persistence-change.md): a dated record in `docs/persistence-changes/` — acknowledge a detected type transition with per-root predecessors and generated after schemas. Open the template before writing and follow its skeleton and rules; it states what the kind is, how the page is structured, and the fact checks each section owes. Add a new kind only together with a distinct template file, a documented repository position or declared owner, and a focused check that maps documents to it. @@ -64,7 +65,7 @@ These rules decide what a section may say. They apply to every authored human-fa - **Summary says what the subject does.** The opening `Summary` and the user-facing sections describe what a user or agent can DO with the subject — outcomes, benefits, when to choose it, main cost — never its role, type, or internal identity. In a package Summary, “what it is” means only its reader-visible capability, not its Cordis role, registrations, or internal components. Omit source identifiers unless the reader directly uses them in configuration, a command, or a public API. "The seam registers `ctx.x` and appends `x/event` records" is identity narration; "you can save a note per message and it survives restarts" is what it does. - **Developer sections explain, never enumerate.** Folded implementation content covers the overall design concept, architecture, and hand-waving dataflow — enough to understand how the package works — and links code for exact detail. No full API catalogs, exhaustive column lists, event-payload enumerations, or JSDoc restatement inside the folds. - **Dev Note is the only slop zone.** Partial ideas, scratches, undecided directions, measured artifacts, and working hypotheses live only in the final Dev Note, marked explicitly non-authoritative. Every other section is polished, current-state prose. -- **Current state only.** No compatibility shims, migration talk, or history ("previously", "now", "no longer", renamed) outside the Dev Note; the codebase as it is today is the only subject. +- **Current state only.** Ordinary documentation describes the current codebase. Dedicated `persistence-change` records retain the before/after facts and compatibility evidence for their detected type transition; Agent Notes and postmortems retain their own historical scope. - **Use controlled technical English.** Give each sentence an explicit actor and one main action when ambiguity can change behavior. Reuse one term per concept, prefer direct verbs, split stacked instructions and conditions, and preserve modality and exceptions. Apply the non-certified, ASD-STE100-inspired discipline in [the page-style reference](references/style.md#controlled-technical-english). Do not force a shorter sentence when precision would fall. ## Quality criteria @@ -106,7 +107,7 @@ Load only the reference needed for the task. Each reference links directly from - [Review criteria](references/review.md): newcomer test, evidence checks, package README review, the reference example, and verification commands. - [Website publication](references/website-sync.md): manifest fields, projector link rules, preview and validation, and deployment separation. -The four README templates in [`templates/`](templates/) are the working skeletons for the four `kind` labels; open the one your document's kind names before writing. +The templates in [`templates/`](templates/) provide one working skeleton per `kind`; open the one your document's kind names before writing. Use [dsh-prose-standard](../dsh-prose-standard/SKILL.md) for sentence-level contract coverage and editorial judgment. The `session-persistence-jsonl` README pair ([English](../../../packages/session/session-persistence-jsonl/README.md), [Chinese](../../../packages/session/session-persistence-jsonl/README.zh.md)) is the reference example: searchable YAML, Summary and Table of Contents, user-to-developer progression with a folded developer section, Further Exploration, canonical Model Experience and Known Limitations sections, and a final Dev Note. diff --git a/.agents/skills/dsh-doc/references/metadata-links-i18n.md b/.agents/skills/dsh-doc/references/metadata-links-i18n.md index caa9e444e2..52ba4a59af 100644 --- a/.agents/skills/dsh-doc/references/metadata-links-i18n.md +++ b/.agents/skills/dsh-doc/references/metadata-links-i18n.md @@ -29,7 +29,7 @@ kind: "package-reference" ## The kind system -`kind` selects the document template directly; every kind maps to exactly one template that exists in this skill, and no template exists without a kind. Derive the kind mechanically, in this order: +`kind` selects the document template directly; every kind maps to exactly one template that exists in this skill, and no template exists without a kind. Files named `docs/persistence-changes/YYYY-MM-DD-slug.md` use `persistence-change`; their Chinese siblings use the same kind. The folder README is an index, not a change record. Derive package README kinds mechanically, in this order: 1. The README is `packages/README.md` or `packages//README.md` → `package-group`. 2. The package manifest declares `dsh.bundle.patch` → `package-bundle`. @@ -42,6 +42,7 @@ kind: "package-reference" | `package-reference` | `packages///README.md` with a plugin entry | [package-reference.md](../templates/package-reference.md) | Package contract: follow the [package README review standard](review.md#package-readme-review) and the canonical [package documentation requirements](../../../../docs/cookbook/adding-a-package.md#4-write-the-package-readme). | | `package-library` | `packages///README.md` with a plain module entry | [package-library.md](../templates/package-library.md) | Library contract: consumer entry points and boundaries; no profile-install path and no mount configuration. | | `package-bundle` | `packages///README.md` declaring `dsh.bundle.patch` | [package-bundle.md](../templates/package-bundle.md) | Installable layer: the verified `dsh plugin` install path, layer semantics, and patch document. | +| `persistence-change` | `docs/persistence-changes/YYYY-MM-DD-slug.md` and its Chinese sibling | [persistence-change.md](../templates/persistence-change.md) | Historical acknowledgement: mechanically detected type changes, per-root predecessor references, compatibility decision, and generated after schemas. | Before assigning `package-library` or `package-bundle`, inspect the facts: read `package.json` for `dsh.bundle.patch` and `src/index.ts` for the entry shape (`apply` export or a default service export is a plugin; a plain module API is a library). `dsh plugin --profile add ` installs any npm dependency, but the profile reconcile activates a layer only for a package that declares `dsh.bundle`; never present that command as an install path for a library or a plain plugin. The documentation check derives the expected kind from these same facts, rejects another value, and rejects `name`, `audience`, `tags`, and README-local `i18n` metadata. Add a new kind only with a distinct template, an unambiguous repository position or declared owner, and a focused check that maps documents to it. diff --git a/.agents/skills/dsh-doc/templates/persistence-change.md b/.agents/skills/dsh-doc/templates/persistence-change.md new file mode 100644 index 0000000000..4d5e720254 --- /dev/null +++ b/.agents/skills/dsh-doc/templates/persistence-change.md @@ -0,0 +1,73 @@ +# Template: persistence-change + +Use this template for `docs/persistence-changes/YYYY-MM-DD-slug.md` and its Chinese sibling. The folder README is an index, not a record. A record acknowledges only mechanically detected persistence-type changes. Its generated JSON sibling retains complete after schemas so verification needs no Git baseline. + +## Frontmatter + +```yaml +--- +description: "The persisted types changed and the compatibility decision this record explains." +kind: persistence-change +--- +``` + +## Skeleton + +The generator fills the machine declaration, schema companion, and consistency records, inferring the minimum version decision from the fixed rules. Supply `--prose` JSON with `en` and `zh` objects, each containing authored `summary`, `compatibility`, and `verification` strings; the [cookbook](../../../../docs/cookbook/reviewing-persistence-type-changes.md) shows the complete operation. The following values illustrate the document fields; use the actual generated root names, record ids, and digests. + +````markdown +# Persistence change: + +English | [中文](YYYY-MM-DD-slug.zh.md) + +## Summary + +Describe the mechanically detected change and its version decision. + +## Table of Contents + +- [Declaration](#declaration) +- [Compatibility](#compatibility) +- [Verification](#verification) +- [Dev Note](#dev-note) + + +## Declaration + +```yaml persistence-change +schemaVersion: 1 +id: YYYY-MM-DD-slug +baseline: false +changes: + - root: event:example/message + previous: YYYY-MM-DD-predecessor + after: + decision: same-version +``` + + +## Compatibility + +Explain the declared decision for the detected paths. For an optional addition, explain why existing records can omit it and older readers can ignore it without changing replay. For required-to-optional changes, explain how readers handle absence. For a version bump, link the adjacent migration and its verification owner. + + +## Verification + +Record the commands actually run and their outcomes. Link focused tests or preserved evidence that support the compatibility explanation. + + +## Dev Note + +None. +```` + +## Rules + +- Keep exactly one `yaml persistence-change` block. The English and Chinese blocks are byte-identical; the verifier parses the English record once and checks its counterpart. +- The filename stem and `id` agree. A root's `previous` identifies its predecessor record; `null` introduces a root. Its `after` matches the generated companion schema digest; `null` records deletion. +- One initial `baseline: true` record covers the complete inventory. Routine changes use `baseline: false` and preserve accepted predecessors. A version-bump record carries its own increasing `SessionHeader.version` transition. +- Omit `--decision` in the ordinary authoring flow. An explicit value asserts the decision and cannot bypass classification or the required header increase. The command supplies no source changes or validation claims. +- Explain only the detected persistence-type changes. This kind permits the before/after facts and historical evidence needed for that transition; behavior-only changes remain out of scope. +- Record creation and terminal-record update generate the catalog pair and consistency sidecars. Review both languages and retain the generated companions. Direct prose edits still require re-recording pairing; do not hand-maintain schema nodes or digests. +- Use explicit `--update` only for an unaccepted terminal record. It rejects baselines and depended-on records; `--prose` replaces its authored explanations, while omission preserves them. Accepted records receive successors. +- The [folder reference](../../../../docs/persistence-changes/README.md) owns compatibility rules; the [cookbook](../../../../docs/cookbook/reviewing-persistence-type-changes.md) owns commands and recovery. diff --git a/.agents/skills/dsh-trim-cot-leakage/SKILL.md b/.agents/skills/dsh-trim-cot-leakage/SKILL.md index fb49a23b7f..cd26b03891 100644 --- a/.agents/skills/dsh-trim-cot-leakage/SKILL.md +++ b/.agents/skills/dsh-trim-cot-leakage/SKILL.md @@ -30,7 +30,7 @@ Unaided citation passes fail in both directions by deleting durable references a - **Merged-PR and issue citations inside Agent Notes and postmortems** — sanctioned evidence per the [documentation standard](../../../docs/AGENTS.md)'s change-story routing. - **Suppression justifications** — `oxlint-disable … -- reason`, coverage-ignore reasons, empty-catch explanations are required prose; fix a false reason, never delete it. - **Counterfactual-present regression pins** — "without X, Y happens", "a naive X would…". -- **Measured bounds** — "(measured: 512 nests ≈ 0.15s)" calibrating a constant; the provenance word "measured" is load-bearing. +- **Measured bounds** — "(measured: 512 nests ≈ 0.15s)" calibrating a constant; the evidence word "measured" is load-bearing. - **Runtime old/new states** — "the old connection drains before the new one accepts" is runtime lifecycle, not change history. - **Historical stage names inside a note's change-story sections** — "the first cut shipped X" is current-state-safe there; indexical stamps ("this cut") stay banned everywhere. - **External references that resolve outside the repo by design** — standards sections (RFC 9110 §10.1.5), Figma frame names; the §-ban covers uncommitted internal drafts, not external standards or committed docs that own their §-numbering. @@ -41,5 +41,5 @@ Unaided citation passes fail in both directions by deleting durable references a 1. Scope and exclusions per [dsh-prose-standard](../dsh-prose-standard/SKILL.md): require an explicit scope; never touch `vendor/` or `.agents/notes/archived/`. Recorded fixtures and snapshots are derivatives, not prose targets: change the owning source or scenario and regenerate them only when an authorized behavior change requires new evidence. 2. Audit read-only first: run the [recall batteries](references/recall-batteries.md) (with `--hidden` so `.agents/` is searched), calibrating each probe against a known positive and a near-miss negative before trusting its output, then judge every hit semantically. The batteries are probes, not the definition — each review round of the original purge found cases the batteries missed, so also read the densest prose in scope (module JSDoc, READMEs, Agent Notes) without a pattern in hand. 3. Fix owner-first per surface: generated catalogs → trace every consumer, fix the source JSDoc or generator template, then regenerate all derivatives; type-equivalence fences → fix the source JSDoc, then re-paste both bilingual pages (`verify-type-equiv` pins them); bilingual prose → update the counterpart minimally and re-record it through the [lightweight routine](../../../docs/AGENTS.md#writing-rules); bilingual fences → copy the corrected verbatim block byte-for-byte into both sides per [dsh-doc](../dsh-doc/SKILL.md), then re-record the pair; model- or user-visible strings → route through [dsh-prose-standard](../dsh-prose-standard/SKILL.md) and change only with owning behavior evidence, otherwise leave unchanged and report the deferral. -4. Before deleting anything, enumerate the passage's propositions (prose-standard) and check the [overcorrection traps](references/examples.md#overcorrection-traps): trims that flip an obligation into an endorsement, promote a hypothetical to a shipped feature, delete a true fact, or drop provenance. +4. Before deleting anything, enumerate the passage's propositions (prose-standard) and check the [overcorrection traps](references/examples.md#overcorrection-traps): trims that flip an obligation into an endorsement, promote a hypothetical to a shipped feature, delete a true fact, or drop the evidence source. 5. Verify: re-run the batteries expecting only sanctioned keeps, this skill's own directory, and the owning note's quoted evidence; confirm every remaining citation resolves at HEAD; run the gates for touched surfaces (`doc-sync` for docs, `verify-type-equiv`, `verify-translation-pairing`). diff --git a/.agents/skills/dsh-trim-cot-leakage/references/examples.md b/.agents/skills/dsh-trim-cot-leakage/references/examples.md index a0db4fbb26..1d67019c66 100644 --- a/.agents/skills/dsh-trim-cot-leakage/references/examples.md +++ b/.agents/skills/dsh-trim-cot-leakage/references/examples.md @@ -174,7 +174,7 @@ Replace the hedge with the actual bound and the failure behavior when it is exce **Fixed:** "The renderer runs on the client side; spacing follows the Figma frame `widget-badges`." -Working-language fragments and session separators are transcription residue. The Figma frame name stays: external provenance that resolves outside the repo by design. +Working-language fragments and session separators are transcription residue. The Figma frame name stays: an external source that resolves outside the repo by design. ### Authoring-language slip inside a paired fence @@ -216,7 +216,7 @@ The justification clause is required prose. When the stated reason is false (the **Keep:** "Depth cap (measured: 512 nests ≈ 0.15s synchronous; 4096 blocks the loop)." -The measurement pins the constant against uninformed retuning, and "measured" is the provenance that distinguishes data from a guess. +The measurement pins the constant against uninformed retuning, and "measured" identifies data rather than a guess. ### Runtime old/new is not change history @@ -264,7 +264,7 @@ Deleting the future-marker alone turns a design illustration into a claim that t Half the sentence was narration; the other half was a load-bearing coupling. Delete clauses, not sentences, when propositions share a line. -### Dropping provenance while keeping the number +### Dropping the measurement source while keeping the number **Original:** "The 4 MiB ceiling is measured: the largest generated `py-types` module is 3.1 MiB." diff --git a/.agents/skills/record-browser-gif/SKILL.md b/.agents/skills/record-browser-gif/SKILL.md index 9222144902..b425b35b6f 100644 --- a/.agents/skills/record-browser-gif/SKILL.md +++ b/.agents/skills/record-browser-gif/SKILL.md @@ -33,9 +33,9 @@ A GIF for a specific pull request demonstrates that pull request's tree, so stag ## Record the flow -Follow the available browser-control workflow's setup, interaction, and cleanup instructions. When it exposes `recordVideo`, enable video on the same controlled context to capture more intermediate frames. Otherwise use [screenshot capture](#screenshot-capture) within that workflow; video availability does not determine which browser-control workflow to use. Existing user browser state remains an explicit provenance exception. +Follow the available browser-control workflow's setup, interaction, and cleanup instructions. When it exposes `recordVideo`, enable video on the same controlled context to capture more intermediate frames. Otherwise use [screenshot capture](#screenshot-capture) within that workflow; video availability does not determine which browser-control workflow to use. Existing user browser state remains an explicit isolation exception. -Only when browser control is unavailable, use the repository-declared Playwright dependency in an isolated headless browser and state that fallback in the provenance. In this repository it resolves from `apps/web/package.json`; do not install another driver or open the user's browser. +Only when browser control is unavailable, use the repository-declared Playwright dependency in an isolated headless browser and state that fallback in the capture notes. In this repository it resolves from `apps/web/package.json`; do not install another driver or open the user's browser. Before recording, identify the origin, built or development server, transport, and any mode overrides. When a production default opens a native surface that automation cannot drive, select an official browser-operable production backend through normal application configuration and disclose the override. diff --git a/.gitattributes b/.gitattributes index ac01e65042..2b2c13bf26 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,3 +11,7 @@ # Pairing records are generated from the three confirmed owner-blob pairs. # The worktree-local installer registers the fail-closed driver command. *.i18n.yaml merge=dsh-translation-pairing + +# The persistence catalog is the readable projection of these generated schemas. +docs/persistence-schema.json linguist-generated=true +docs/persistence-changes/*.schema.json linguist-generated=true diff --git a/packages/e2b/README.i18n.yaml b/.github/issue-management/README.i18n.yaml similarity index 56% rename from packages/e2b/README.i18n.yaml rename to .github/issue-management/README.i18n.yaml index cb27804fbb..7d31f8418d 100644 --- a/packages/e2b/README.i18n.yaml +++ b/.github/issue-management/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/e2b/README.md -README.md: d4be37306737704e7db461c23782a2b5c6167791 -README.zh.md: 3b4267cd1cbd9c46f91d4c11686ab8afdee4fde6 +# pnpm run verify-translation-pairing --write .github/issue-management/README.md +README.md: a936b9358810fd3a8b8104609a5523e305e1aaa5 +README.zh.md: df743cac581c2125df9ac9e76f04d2528204eb74 diff --git a/.github/issue-management/README.md b/.github/issue-management/README.md new file mode 100644 index 0000000000..a936b93588 --- /dev/null +++ b/.github/issue-management/README.md @@ -0,0 +1,93 @@ +--- +description: "Issue policy enforcement, Project access, and lifecycle events for repository maintainers." +--- + +# Issue management + +English | [中文](README.zh.md) + +## Summary + +Contributors can link Issues as context without coupling pull-request validation to Project availability. Resolving references additionally enforce Project Priority. The required `Issue policy` job and the separate lifecycle workflow use trusted default-branch code. + +## Table of Contents + +- [Pull-request policy](#pull-request-policy) +- [Lifecycle events](#lifecycle-events) +- [Configuration and limitations](#configuration-and-limitations) +- [Module ownership](#module-ownership) +- [Verification](#verification) +- [Dev Note](#dev-note) + +----- + + +## Pull-request policy + +[Issue policy](../workflows/issue-policy.yml) applies to non-draft, human-authored PRs with a requested review or submitted review. Exempt PRs finish successfully without resolving Issue references, minting a Project App token, or querying ProjectV2. Eligibility uses live repository state before expensive reads; the required job remains present for subscribed events. Final validation re-reads live state: preflight is not a cached verdict or an exemption for metadata edits. + +Selective preflight requires [selective-preflight.json](selective-preflight.json) in the trusted checkout. Without that marker, the workflow preserves legacy behavior: human PRs receive a Project token and full legacy validation; Bot/App PRs skip both. A failed supported preflight fails the job rather than falling back. + +Eligible PRs need at least one same-repository Issue reference, exactly one canonical `kind/*`, at least one `area/*`, and at most one `p0`–`p3` label. Unsupported kinds, retired aliases, and `source/*` labels fail validation; [label taxonomy](../../.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.md) owns their meanings. + +- Informational references, such as `Refs #3624`, establish context. Validation uses REST to distinguish Issues from PR numbers and does not read their Project fields. An informational-only PR can carry its own Priority without matching the referenced Issue. +- Resolving references use closing keywords such as `Fixes #123`, `Closes #123`, or `Resolves #123`. Only references that resolve to actual Issues require Project reads during validation. A PR Priority must match the highest resolving-Issue Priority; a resolving PR with a Priority label requires every resolving Issue to have Priority. If all resolving Priorities are empty, the PR may omit Priority. +- References inside HTML comments, code fences, or inline code do not count. Cross-repository references and references to PRs do not satisfy the Issue requirement. + +REST reads use the repository `GITHUB_TOKEN`. Project validation uses a separate App token with Issues and organization Projects read permissions. Missing required Project access or invalid field configuration fails validation rather than bypassing resolving-Issue Priority checks. + +----- + + +## Lifecycle events + +[Issue lifecycle](../workflows/issue-lifecycle.yml) mutates Project data independently of PR validation eligibility. PR opened/reopened events and body edits can advance resolving Issues to `In progress`; title-only edits do not. Review requests target `In review`. Changes-requested reviews target `In progress`, with the [human-ownership and terminal-status protections](../../.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md). + +Approval-only and comment-only reviews do not allocate a lifecycle runner. PR pushes and label changes, and Issue assignment changes, do not trigger lifecycle work. Other subscribed Issue events maintain membership, state, and audit comments; exact subscriptions live in the workflow. + +PR opening initializes an empty Project `Start Date` for every referenced Issue, including informational references, using the PR creation date in the configured time zone. This lifecycle operation can add Project membership and needs Project write access; the informational-reference read exemption applies only to PR validation. [Planning-field ownership](../../.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.md) defines date preservation. + +----- + + +## Configuration and limitations + +[config.json](config.json) selects the repository, Project, field names, statuses, lifecycle actor, and time zone. The policy reads the Project custom single-select `Priority` field, not a native organization Issue Priority field. Maintainers set Project Priority manually; skill guidance that directs edits to native Issue fields does not populate this value. Issue audits remove PR-only kinds and retired label aliases before validating the remaining metadata. There is no field migration or Priority synchronization. + +Lifecycle processing is event-driven, not a reconciler. Omitted events do not repair Project state, and concurrent Project mutations have no atomic compare-and-swap. Selective evaluation does not redesign required-check authority or guarantee measured Actions-minute savings. The [selective-evaluation decision](../../.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.md) records the trade-offs. + +----- + + +## Module ownership + +Maintainers reuse the owning module directly; [policy.mjs](policy.mjs) only reads the event file, dispatches commands, and reports command failures. The [module-ownership decision](../../.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.md) explains this separation. + +
+Implementation owners + +[rules.mjs](rules.mjs) owns pure validation, reference parsing, status decisions, and date conversion. [github.mjs](github.mjs) owns credential selection, REST/GraphQL transport, Issue/Project reads, and Project membership and field writes. + +[pull-request.mjs](pull-request.mjs) assembles read-only PR snapshots and runs policy preflight and validation, including their workflow outputs. [lifecycle.mjs](lifecycle.mjs) reuses the PR reference reader and shared rules to coordinate Project mutations, Issue label repairs, and audit comments. Snapshot readers do not mutate GitHub; the shared transport also supports writes, so importing it does not restrict a caller's permissions. + +
+ +----- + + +## Verification + +The focused, keyless policy suite runs from the repository root: + +```sh +node --test .github/issue-management/policy.test.mjs +``` + +[Workflow tests](../../scripts/ci-workflow.spec.ts) verify trigger and permission declarations. Local tests do not establish live GitHub delivery, App installation access, or actual runner cost; repository maintainers verify those in Actions. + +----- + + +## Dev Note + +None. diff --git a/.github/issue-management/README.zh.md b/.github/issue-management/README.zh.md new file mode 100644 index 0000000000..df743cac58 --- /dev/null +++ b/.github/issue-management/README.zh.md @@ -0,0 +1,93 @@ +--- +description: "面向仓库维护者的 Issue 策略强制范围、Project 访问与生命周期事件参考。" +--- + +# Issue 管理 + +[English](README.md) | 中文 + +## 摘要 + +贡献者可以引用 Issue 作为背景,而无需让 PR(Pull Request)校验依赖 Project 可用性。解决型引用还会强制检查 Project Priority。必需的 `Issue policy` job 与独立的生命周期工作流使用受信任的默认分支代码。 + +## 目录 + +- [PR 策略](#pull-request-policy) +- [生命周期事件](#lifecycle-events) +- [配置与限制](#configuration-and-limitations) +- [模块归属](#module-ownership) +- [验证](#verification) +- [开发备注](#dev-note) + +----- + + +## PR 策略 + +[Issue policy](../workflows/issue-policy.yml)适用于已请求评审或已有评审、非草稿且由人类创建的 PR。豁免 PR 成功结束,不解析 Issue 引用、不签发 Project App token,也不查询 ProjectV2。工作流在昂贵读取前根据仓库实时状态判断强制范围;订阅事件仍保留必需 job。最终校验重新读取实时状态:预检不是缓存结论,也不是元数据编辑的豁免。 + +选择性预检要求受信任的检出中存在 [selective-preflight.json](selective-preflight.json)。缺少该标记时,工作流保留旧版行为:人类 PR 获取 Project token 并执行完整旧版校验;Bot/App PR 跳过两者。受支持的预检执行失败时,job 失败而不回退。 + +强制范围内的 PR 至少需要一个同仓库 Issue 引用、恰好一个规范的 `kind/*`、至少一个 `area/*`,以及最多一个 `p0`–`p3` 标签。不支持的 kind、退役别名和 `source/*` 标签会使校验失败;[标签分类](../../.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.zh.md)定义其含义。 + +- 信息型引用(如 `Refs #3624`)提供背景。校验通过 REST 区分 Issue 与 PR 编号,不读取这些引用的 Project 字段。仅含信息型引用的 PR 可以使用自己的 Priority,无需匹配所引用的 Issue。 +- 解决型引用使用关闭关键词,如 `Fixes #123`、`Closes #123` 或 `Resolves #123`。校验期间只有解析为实际 Issue 的解决型引用需要读取 Project。PR Priority 必须匹配被解决 Issue 中的最高 Priority;带 Priority 标签的解决型 PR 要求每个被解决 Issue 均有 Priority。若所有被解决 Issue 的 Priority 均为空,PR 可以省略 Priority。 +- HTML 注释、代码围栏或行内代码中的引用不计入。跨仓库引用与指向 PR 的引用不能满足 Issue 引用要求。 + +REST 读取使用仓库 `GITHUB_TOKEN`。Project 校验使用独立的 App token,具有 Issues 和组织 Projects 读取权限。缺少所需 Project 访问权限或字段配置无效时,校验失败,而不是绕过解决型 Issue 的 Priority 检查。 + +----- + + +## 生命周期事件 + +[Issue lifecycle](../workflows/issue-lifecycle.yml)独立于 PR 校验强制范围修改 Project 数据。PR 打开、重新打开和正文编辑可将解决型 Issue 推进至 `In progress`;仅编辑标题不会。请求评审以 `In review` 为目标。请求修改的评审以 `In progress` 为目标,并遵守[人工状态归属与终态保护](../../.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.zh.md)。 + +仅批准或仅评论的评审不分配生命周期 runner。PR 推送与标签变更,以及 Issue 指派变更,不触发生命周期工作。其他已订阅的 Issue 事件维护 Project 归属、状态及审计评论;精确订阅列表由工作流定义。 + +PR 打开时,工作流按配置时区中的 PR 创建日期,为每个被引用 Issue(包括信息型引用)初始化空的 Project `Start Date`。此生命周期操作可以添加 Project 归属,并需要 Project 写权限;信息型引用的读取豁免仅适用于 PR 校验。[规划字段归属](../../.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.zh.md)定义日期保留规则。 + +----- + + +## 配置与限制 + +[config.json](config.json)选择仓库、Project、字段名、状态、生命周期操作者和时区。策略读取 Project 自定义单选 `Priority` 字段,而非组织原生 Issue Priority 字段。维护者手动设置 Project Priority;指引编辑原生 Issue 字段的 skill 不会填充该值。Issue 审计先移除 PR 专用 kind 标签和已停用的标签别名,再校验其余元数据。不提供字段迁移或 Priority 同步。 + +生命周期处理由事件驱动,不是协调器。被省略的事件不会修复 Project 状态,并发 Project mutation 也没有原子比较并交换保护。选择性求值不重新设计必需检查的权威来源,也不保证已测得的 Actions 分钟节省。[选择性求值决策](../../.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.zh.md)记录取舍。 + +----- + + +## 模块归属 + +维护者直接复用所属模块;[policy.mjs](policy.mjs)仅负责读取事件文件、分派命令并报告命令失败。[模块归属决策](../../.agents/notes/implemented/process/2026-09-07-issue-policy-module-ownership.zh.md)解释此职责分离。 + +
+实现职责 + +[rules.mjs](rules.mjs)拥有纯校验、引用解析、状态决策和日期转换。[github.mjs](github.mjs)拥有凭据选择、REST/GraphQL 传输、Issue/Project 读取,以及 Project 归属与字段写入。 + +[pull-request.mjs](pull-request.mjs)组装只读 PR 快照,并执行策略预检和校验,包括其工作流输出。[lifecycle.mjs](lifecycle.mjs)复用 PR 引用读取器与共享规则,协调 Project mutation、Issue 标签修复和审计评论。快照读取器不修改 GitHub;共享传输也支持写入,因此导入该模块不会限制调用方权限。 + +
+ +----- + + +## 验证 + +在仓库根目录运行聚焦的无密钥策略测试: + +```sh +node --test .github/issue-management/policy.test.mjs +``` + +[工作流测试](../../scripts/ci-workflow.spec.ts)验证触发器与权限声明。本地测试不能证明 GitHub 实际事件交付、App 安装访问权限或实际 runner 成本;仓库维护者在 Actions 中验证这些内容。 + +----- + + +## 开发备注 + +无。 diff --git a/.github/issue-management/github.mjs b/.github/issue-management/github.mjs new file mode 100644 index 0000000000..34350e8ab7 --- /dev/null +++ b/.github/issue-management/github.mjs @@ -0,0 +1,300 @@ +/** GitHub transport, Issue/Project readers, and Project membership and field writes. */ + +import process from 'node:process' + +import config from './config.json' with { type: 'json' } + +const API_VERSION = '2026-03-10' + +function token() { + const value = process.env.GH_TOKEN || process.env.GITHUB_TOKEN + if (!value) throw new Error('GH_TOKEN 或 GITHUB_TOKEN 未设置') + return value +} + +function projectToken() { + return process.env.PROJECT_TOKEN || token() +} + +/** + * Send one GitHub REST request; HTTP failures retain the method, path, status, and body. + * @param {string} path API path including any query string. + * @param {object} options Fetch options; allow404 returns null for a 404. + * @returns {Promise} Decoded JSON, or null for an allowed 404 or a 204. + */ +export async function api(path, options = {}) { + const { allow404 = false, ...requestOptions } = options + const response = await fetch(`${process.env.GITHUB_API_URL ?? 'https://api.github.com'}${path}`, { + ...requestOptions, + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${token()}`, + 'X-GitHub-Api-Version': API_VERSION, + 'User-Agent': 'dsh-issue-policy', + ...options.headers, + }, + }) + if (allow404 && response.status === 404) return null + if (!response.ok) { + const body = await response.text() + throw new Error(`${requestOptions.method ?? 'GET'} ${path}: ${response.status} ${body}`) + } + if (response.status === 204) return null + return response.json() +} + +/** + * Send one Project-authenticated GraphQL request; reject joined GraphQL error messages. + * @param {string} query GraphQL document. + * @param {object} variables GraphQL variables. + * @returns {Promise} GraphQL data. + */ +export async function graphql(query, variables) { + const result = await api('/graphql', { + method: 'POST', + body: JSON.stringify({ query, variables }), + headers: { + Authorization: `Bearer ${projectToken()}`, + 'Content-Type': 'application/json', + }, + }) + if (result.errors?.length) throw new Error(result.errors.map((error) => error.message).join('; ')) + return result.data +} + +/** + * Read one Issue together with its Project planning values. + * @param {number} number Same-repository Issue number. + * @param {string|null|undefined} status Optional known Project status. + * @returns {Promise} Issue snapshot, or null when the number identifies a pull request. + */ +export async function issueSnapshot(number, status = undefined) { + const issue = await api(`/repos/${config.organization}/${config.repository}/issues/${number}`) + if (issue.pull_request) return null + const context = await projectContext(number) + return { + number, + nodeId: issue.node_id, + labels: issue.labels.map((label) => label.name), + type: issue.type?.name ?? null, + priority: context.item?.priorityValue?.name ?? null, + status: status === undefined ? (context.item?.fieldValueByName?.name ?? null) : status, + state: issue.state, + stateReason: issue.state_reason ?? null, + } +} + +/** + * Read and validate the configured Project fields and one Issue’s membership. + * @param {number} number Same-repository Issue number. + * @param {boolean} includeStatusActor Include the latest matching status-event actor. + * @param {boolean} includeStartDate Include and validate the Project Start Date field. + * @returns {Promise} Project, Issue, fields, optional item, and status actor; never writes. + */ +export async function projectContext(number, includeStatusActor = false, includeStartDate = false) { + const data = await graphql( + `query( + $organization: String! + $repository: String! + $number: Int! + $project: Int! + $includeStatusActor: Boolean! + $includeStartDate: Boolean! + $priorityField: String! + $startDateField: String! + ) { + organization(login: $organization) { + projectV2(number: $project) { + id + title + fields(first: 50) { + nodes { + ... on ProjectV2Field { + id + name + dataType + isIssueField + } + ... on ProjectV2SingleSelectField { + id + name + dataType + isIssueField + options { id name } + } + } + } + } + } + repository(owner: $organization, name: $repository) { + issue(number: $number) { + id + timelineItems(last: 100, itemTypes: [PROJECT_V2_ITEM_STATUS_CHANGED_EVENT]) + @include(if: $includeStatusActor) { + nodes { + ... on ProjectV2ItemStatusChangedEvent { + actor { login } + project { id } + status + } + } + } + projectItems(first: 20, includeArchived: true) { + nodes { + id + project { id } + fieldValueByName(name: "Status") { + ... on ProjectV2ItemFieldSingleSelectValue { name optionId } + } + priorityValue: fieldValueByName(name: $priorityField) { + ... on ProjectV2ItemFieldSingleSelectValue { name optionId } + } + startDateValue: fieldValueByName(name: $startDateField) + @include(if: $includeStartDate) { + ... on ProjectV2ItemFieldDateValue { date } + } + } + } + } + } + }`, + { + organization: config.organization, + repository: config.repository, + number, + project: config.projectNumber, + includeStatusActor, + includeStartDate, + priorityField: config.priorityField, + startDateField: config.startDateField, + }, + ) + const project = data.organization?.projectV2 + const issue = data.repository?.issue + if (!project || project.title !== config.projectTitle) throw new Error('目标 Project 不存在或标题不匹配') + if (!issue) throw new Error(`#${number} 不存在`) + const statusField = project.fields.nodes.find((field) => field?.name === 'Status') + if (!statusField) throw new Error('Project 缺少 Status 字段') + const priorityField = project.fields.nodes.find((field) => field?.name === config.priorityField) + if (!priorityField) throw new Error(`Project 缺少 ${config.priorityField} 字段`) + if (priorityField.dataType !== 'SINGLE_SELECT') { + throw new Error(`Project ${config.priorityField} 字段必须为 Single Select`) + } + if (priorityField.isIssueField) { + throw new Error(`Project ${config.priorityField} 字段必须为 Project custom field`) + } + const startDateField = includeStartDate + ? project.fields.nodes.find((field) => field?.name === config.startDateField) + : null + if (includeStartDate && !startDateField) { + throw new Error(`Project 缺少 ${config.startDateField} 字段`) + } + if (startDateField && startDateField.dataType !== 'DATE') { + throw new Error(`Project ${config.startDateField} 字段必须为 Date`) + } + if (startDateField?.isIssueField) { + throw new Error(`Project ${config.startDateField} 字段必须为 Project Date 字段`) + } + const item = issue.projectItems.nodes.find((candidate) => candidate.project.id === project.id) + const latestStatusEvent = issue.timelineItems?.nodes + ?.filter((event) => event?.project?.id === project.id) + .at(-1) + const statusActor = + latestStatusEvent && latestStatusEvent.status === item?.fieldValueByName?.name + ? (latestStatusEvent.actor?.login ?? null) + : null + return { project, issue, statusField, priorityField, startDateField, item, statusActor } +} + +/** + * Read Project membership and add the Issue when absent. + * @param {number} number Same-repository Issue number. + * @param {boolean} includeStartDate Include and validate the Project Start Date field. + * @returns {Promise} Project context with an existing or newly added item. + */ +export async function ensureProjectItem(number, includeStartDate = false) { + const context = await projectContext(number, false, includeStartDate) + if (context.item) return context + const data = await graphql( + `mutation($projectId: ID!, $contentId: ID!) { + addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) { + item { id } + } + }`, + { projectId: context.project.id, contentId: context.issue.id }, + ) + return { + ...context, + item: { + id: data.addProjectV2ItemById.item.id, + fieldValueByName: null, + priorityValue: null, + startDateValue: null, + }, + } +} + +/** + * Initialize one Issue's Project Start Date when it is empty. + * @param {number} number Same-repository Issue number. + * @param {string} date Date in YYYY-MM-DD form. + * @returns {Promise} Resolves after the conditional Project update. + */ +export async function initializeIssueStartDate(number, date) { + const context = await ensureProjectItem(number, true) + if (context.item.startDateValue?.date) return + await graphql( + `mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $date: Date!) { + updateProjectV2ItemFieldValue(input: { + projectId: $projectId, + itemId: $itemId, + fieldId: $fieldId, + value: {date: $date} + }) { projectV2Item { id } } + }`, + { + projectId: context.project.id, + itemId: context.item.id, + fieldId: context.startDateField.id, + date, + }, + ) +} + +/** + * Write the named Status option unless the supplied item already has it. + * @param {object} context Project context with an item and Status options. + * @param {string} status Configured Status option name. + * @returns {Promise} Resolves after the optional update; rejects an unknown option. + */ +export async function updateStatus(context, status) { + const option = context.statusField.options.find((candidate) => candidate.name === status) + if (!option) throw new Error(`Status 不存在:${status}`) + if (context.item.fieldValueByName?.name === status) return + await graphql( + `mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { + updateProjectV2ItemFieldValue(input: { + projectId: $projectId, + itemId: $itemId, + fieldId: $fieldId, + value: {singleSelectOptionId: $optionId} + }) { projectV2Item { id } } + }`, + { + projectId: context.project.id, + itemId: context.item.id, + fieldId: context.statusField.id, + optionId: option.id, + }, + ) +} + +/** + * Ensure Project membership before updating an Issue’s Status. + * @param {number} number Same-repository Issue number. + * @param {string} status Configured Status option name. + * @returns {Promise} Resolves after membership and Status updates. + */ +export async function setStatus(number, status) { + await updateStatus(await ensureProjectItem(number), status) +} diff --git a/.github/issue-management/lifecycle.mjs b/.github/issue-management/lifecycle.mjs new file mode 100644 index 0000000000..46e32389b9 --- /dev/null +++ b/.github/issue-management/lifecycle.mjs @@ -0,0 +1,155 @@ +/** Repository event orchestration and Issue audit comments. */ + +import config from './config.json' with { type: 'json' } +import { + api, + ensureProjectItem, + initializeIssueStartDate, + issueSnapshot, + projectContext, + setStatus, + updateStatus, +} from './github.mjs' +import { lifecyclePullRequestSnapshot } from './pull-request.mjs' +import { + isInvalidIssueLabel, + nextResolvingIssueStatus, + projectDate, + resolvingIssueStatusCommand, + validateIssue, +} from './rules.mjs' + +const AUDIT_MARKER = '' + +/** + * Initialize every referenced Issue from a newly opened PR. + * @param {{createdAt: string, references: {all: number[]}}} pull Pull-request snapshot. + * @param {string} action Pull-request event action. + * @param {(number: number, date: string) => Promise} initialize Date writer. + * @returns {Promise} Resolves after all eligible Issues are processed. + */ +export async function initializePullRequestStartDates( + pull, + action, + initialize = initializeIssueStartDate, +) { + if (action !== 'opened') return + const date = projectDate(pull.createdAt) + for (const number of pull.references.all) await initialize(number, date) +} + +async function upsertAudit(number, errors) { + const comments = await api( + `/repos/${config.organization}/${config.repository}/issues/${number}/comments?per_page=100`, + ) + const existing = comments.find( + (comment) => comment.user?.type === 'Bot' && comment.body?.includes(AUDIT_MARKER), + ) + if (errors.length === 0) { + if (existing) { + await api(`/repos/${config.organization}/${config.repository}/issues/comments/${existing.id}`, { + method: 'DELETE', + }) + } + return + } + const body = `${AUDIT_MARKER}\n⚠️ Issue policy 未通过:\n\n${errors.map((error) => `- ${error}`).join('\n')}` + if (existing) { + if (existing.body === body) return + await api(`/repos/${config.organization}/${config.repository}/issues/comments/${existing.id}`, { + method: 'PATCH', + body: JSON.stringify({ body }), + headers: { 'Content-Type': 'application/json' }, + }) + } else { + await api(`/repos/${config.organization}/${config.repository}/issues/${number}/comments`, { + method: 'POST', + body: JSON.stringify({ body }), + headers: { 'Content-Type': 'application/json' }, + }) + } +} + +/** + * Remove pull-request kinds and retired aliases from one Issue snapshot. + * @param {{number: number, labels: string[]}} issue Issue snapshot. + * @returns {Promise} Snapshot containing only labels that remain on the Issue. + */ +export async function repairIssueLabels(issue) { + const invalidLabels = issue.labels.filter(isInvalidIssueLabel) + for (const label of invalidLabels) { + await api( + `/repos/${config.organization}/${config.repository}/issues/${issue.number}/labels/${encodeURIComponent(label)}`, + { method: 'DELETE', allow404: true }, + ) + } + return { + ...issue, + labels: issue.labels.filter((label) => !isInvalidIssueLabel(label)), + } +} + +/** + * Repair deterministic Issue metadata violations and publish the remaining audit result. + * @param {number} number Same-repository Issue number. + * @param {string[]} extraErrors Errors supplied by the triggering lifecycle operation. + * @param {string|null|undefined} status Optional known Project status. + * @returns {Promise} Violations that remain after repair. + */ +export async function auditIssue(number, extraErrors = [], status = undefined) { + const issue = await issueSnapshot(number, status) + if (!issue) return [] + const repairedIssue = await repairIssueLabels(issue) + const errors = [...extraErrors, ...validateIssue(repairedIssue)] + await upsertAudit(number, errors) + return errors +} + +async function transitionResolvingIssues(pull, command) { + for (const number of pull.references.resolving) { + const context = await projectContext(number, command === 'changes-requested') + const target = nextResolvingIssueStatus( + context.item?.fieldValueByName?.name ?? null, + command, + context.statusActor, + ) + if (!target) continue + // TODO: Replace this latest-state guard with per-Issue serialization or a + // conditional ProjectV2 update; GraphQL currently has no compare-and-swap. + await updateStatus(context, target) + await auditIssue(number) + } +} + +/** + * Apply repository lifecycle events; irrelevant PR events perform no requests. + * @param {string} eventName GitHub event name. + * @param {object} event GitHub event payload. + * @returns {Promise} Resolves after lifecycle updates and audits. + */ +export async function runLifecycle(eventName, event) { + if (eventName === 'issues') { + const number = event.issue.number + if (event.action === 'opened') await setStatus(number, 'Inbox') + if (event.action === 'closed') { + const target = event.issue.state_reason === 'not_planned' ? 'No action' : 'Done' + await setStatus(number, target) + } + if (event.action === 'reopened') { + await setStatus(number, 'Inbox') + } + await ensureProjectItem(number) + await auditIssue(number) + return + } + + if (eventName === 'pull_request' || eventName === 'pull_request_review') { + const command = resolvingIssueStatusCommand(eventName, event) + if (!command) return + const pull = await lifecyclePullRequestSnapshot(event.pull_request.number) + await transitionResolvingIssues(pull, command) + if (eventName === 'pull_request') { + await initializePullRequestStartDates(pull, event.action) + } + } +} diff --git a/.github/issue-management/policy.mjs b/.github/issue-management/policy.mjs index c9793f14b3..bd726e571b 100644 --- a/.github/issue-management/policy.mjs +++ b/.github/issue-management/policy.mjs @@ -4,741 +4,9 @@ import fs from 'node:fs' import process from 'node:process' import { pathToFileURL } from 'node:url' -import config from './config.json' with { type: 'json' } - -const API_VERSION = '2026-03-10' -const AUDIT_MARKER = '' -const TYPES = new Set(['Idea', 'Feature', 'Bug', 'Research', 'Task']) -const PRIORITIES = ['p0', 'p1', 'p2', 'p3'] -const PR_KINDS = new Set([ - 'kind/feature', - 'kind/bug-fix', - 'kind/doc', - 'kind/testing', - 'kind/cleanup', - 'kind/dependency', -]) -// Retired label aliases stay reserved so they cannot be recreated. -const LEGACY_LABELS = new Set([ - 'kind/bug', - 'kind/documentation', - 'feature', - 'bug-fix', - 'doc', - 'cleanup', - 'testing', - 'dependencies', - 'ci', - 'cli', - 'llm', - 'web-search', -]) -const TERMINAL_STATUSES = new Set(['Done', 'No action']) -const ACTIVE_STATUS_ORDER = config.statuses.filter((status) => !TERMINAL_STATUSES.has(status)) -const IMPLEMENTATION_PULL_REQUEST_ACTIONS = new Set([ - 'opened', - 'edited', - 'synchronize', - 'reopened', - 'labeled', - 'unlabeled', -]) - -for (const status of ['In progress', 'In review']) { - if (!ACTIVE_STATUS_ORDER.includes(status)) throw new Error(`config.statuses 缺少 ${status}`) -} -if (typeof config.lifecycleActor !== 'string' || !config.lifecycleActor) { - throw new Error('config.lifecycleActor 未设置') -} -if (typeof config.priorityField !== 'string' || !config.priorityField) { - throw new Error('config.priorityField 未设置') -} -if (typeof config.startDateField !== 'string' || !config.startDateField) { - throw new Error('config.startDateField 未设置') -} -if (typeof config.projectTimeZone !== 'string' || !config.projectTimeZone) { - throw new Error('config.projectTimeZone 未设置') -} -Intl.DateTimeFormat('en-US', { timeZone: config.projectTimeZone }) - -/** - * Decide whether the human-review policy applies to a PR. - * @param {{isDraft: boolean, authorType: string, reviewRequestCount: number, reviewCount: number}} input PR state. - * @returns {boolean} Whether the PR policy is mandatory. - */ -export function requiresPullRequestPolicy({ - isDraft, - authorType, - reviewRequestCount, - reviewCount, -}) { - const automated = authorType === 'Bot' || authorType === 'App' - return !isDraft && !automated && (reviewRequestCount > 0 || reviewCount > 0) -} - -/** - * Translate a repository event into one resolving-Issue lifecycle command. - * @param {string} eventName GitHub event name. - * @param {{action?: string, review?: {state?: string}}} event GitHub event payload. - * @returns {'implementation'|'review-requested'|'changes-requested'|null} Lifecycle command. - */ -export function resolvingIssueStatusCommand(eventName, event) { - if (eventName === 'pull_request') { - if (event.action === 'review_requested') return 'review-requested' - return IMPLEMENTATION_PULL_REQUEST_ACTIONS.has(event.action) ? 'implementation' : null - } - if ( - eventName === 'pull_request_review' && - event.action === 'submitted' && - event.review?.state?.toLowerCase() === 'changes_requested' - ) { - return 'changes-requested' - } - return null -} - -/** - * Plan one event-directed resolving-Issue status transition. - * @param {string|null} currentStatus Current Project status. - * @param {'implementation'|'review-requested'|'changes-requested'} command Lifecycle command. - * @param {string|null} currentStatusActor Actor that last set the current Project status. - * @returns {string|null} Status to write, or null when no permitted transition exists. - */ -export function nextResolvingIssueStatus(currentStatus, command, currentStatusActor = null) { - let target - if (command === 'review-requested') target = 'In review' - else if (command === 'implementation' || command === 'changes-requested') target = 'In progress' - else throw new Error(`未知 lifecycle command:${command}`) - - const currentIndex = ACTIVE_STATUS_ORDER.indexOf(currentStatus) - const targetIndex = ACTIVE_STATUS_ORDER.indexOf(target) - if ( - command === 'changes-requested' && - currentStatus === 'In review' && - currentStatusActor === config.lifecycleActor - ) { - return target - } - return currentIndex >= 0 && currentIndex < targetIndex ? target : null -} - -/** - * Convert a GitHub timestamp to a Project date in one configured time zone. - * @param {string} timestamp ISO timestamp. - * @param {string} timeZone IANA time-zone name. - * @returns {string} Calendar date in YYYY-MM-DD form. - */ -export function projectDate(timestamp, timeZone = config.projectTimeZone) { - const instant = new Date(timestamp) - if (Number.isNaN(instant.getTime())) throw new Error(`无效的 PR 创建时间:${timestamp}`) - const parts = Object.fromEntries( - new Intl.DateTimeFormat('en-US', { - timeZone, - year: 'numeric', - month: '2-digit', - day: '2-digit', - }) - .formatToParts(instant) - .filter((part) => part.type !== 'literal') - .map((part) => [part.type, part.value]), - ) - return `${parts.year}-${parts.month}-${parts.day}` -} - -function stripIgnoredMarkdown(body) { - const lines = body.replace(//g, '').split(/\r?\n/) - const kept = [] - let fence = null - for (const line of lines) { - const marker = line.match(/^\s*([\u0060~]{3,})/) - if (marker) { - if (fence === null) fence = marker[1][0] - else if (marker[1][0] === fence) fence = null - continue - } - if (fence === null) kept.push(line) - } - return kept.join('\n').replace(/\u0060[^\u0060]*\u0060/g, ' ') -} - -/** - * Parse same-repository resolving and informational references. - * @param {{body: string, repository: string}} input PR body and repository. - * @returns {{all: number[], resolving: number[], related: number[]}} References. - */ -export function parseReferences({ body, repository }) { - const source = stripIgnoredMarkdown(body) - const expected = repository.toLowerCase() - const all = new Set() - const resolving = new Set() - const reference = - /(?:([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#|#)(\d+)|https:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\/issues\/(\d+)/gi - const closing = - /\b(?:close(?:s|d)?|fix(?:es|ed)?|resolve(?:s|d)?)\s*:?\s+(?:(?:([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#|#)(\d+)|https:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\/issues\/(\d+))/gi - - for (const match of source.matchAll(reference)) { - const explicit = (match[1] ?? match[3] ?? '').toLowerCase() - const number = Number(match[2] ?? match[4]) - if (!explicit || explicit === expected) all.add(number) - } - for (const match of source.matchAll(closing)) { - const explicit = (match[1] ?? match[3] ?? '').toLowerCase() - const number = Number(match[2] ?? match[4]) - if (!explicit || explicit === expected) { - all.add(number) - resolving.add(number) - } - } - return { - all: [...all].sort((left, right) => left - right), - resolving: [...resolving].sort((left, right) => left - right), - related: [...all].filter((number) => !resolving.has(number)).sort((a, b) => a - b), - } -} - -/** - * Retain only references that resolve to Issues rather than pull requests. - * @param {{all: number[], resolving: number[], related: number[]}} references Parsed references. - * @param {Map} issues Resolved same-repository Issues. - * @returns {{all: number[], resolving: number[], related: number[]}} Issue-only references. - */ -export function retainIssueReferences(references, issues) { - return { - all: references.all.filter((number) => issues.has(number)), - resolving: references.resolving.filter((number) => issues.has(number)), - related: references.related.filter((number) => issues.has(number)), - } -} - -/** - * Validate one Issue with its Project status. - * @param {{labels: string[], type: string|null, priority: string|null, status: string|null, state: string, stateReason: string|null}} issue Issue snapshot. - * @returns {string[]} Validation errors. - */ -export function validateIssue(issue) { - const errors = [] - const status = issue.status - const invalidLabels = issue.labels.filter(isInvalidIssueLabel) - - if (invalidLabels.length > 0) { - errors.push(`Issue 不得使用 PR kind 或旧版标签:${invalidLabels.join(', ')}`) - } - if (!TYPES.has(issue.type ?? '')) errors.push('Type 必须是五种原生英文 Type 之一') - if (!status || !config.statuses.includes(status)) errors.push('Issue 必须在 Project 中且具有合法 Status') - if (issue.priority !== null && !PRIORITIES.includes(issue.priority.toLowerCase())) { - errors.push('Priority 必须为空或为 P0–P3') - } - if (status === 'Done' && (issue.state !== 'closed' || issue.stateReason !== 'completed')) { - errors.push('Done 必须对应 Completed 关闭原因') - } - if ( - status === 'No action' && - (issue.state !== 'closed' || issue.stateReason !== 'not_planned') - ) { - errors.push('No action 必须对应 Not planned 关闭原因') - } - if (!['Done', 'No action'].includes(status ?? '') && issue.state !== 'open') { - errors.push(`${status} 必须对应开放 Issue`) - } - return errors -} - -function isInvalidIssueLabel(label) { - return label.startsWith('kind/') || LEGACY_LABELS.has(label) -} - -/** - * Validate PR metadata and its referenced Issues. - * @param {{authorType: string, labels: string[], references: ReturnType, issues: Map}} input PR snapshot. - * @returns {string[]} Validation errors. - */ -export function validatePullRequest(input) { - if (!requiresPullRequestPolicy(input)) return [] - const errors = [] - const kinds = input.labels.filter((label) => PR_KINDS.has(label)) - const unknownKinds = input.labels.filter( - (label) => label.startsWith('kind/') && !PR_KINDS.has(label) && !LEGACY_LABELS.has(label), - ) - const legacyLabels = input.labels.filter((label) => LEGACY_LABELS.has(label)) - const sourceLabels = input.labels.filter((label) => label.startsWith('source/')) - const priorities = input.labels.filter((label) => PRIORITIES.includes(label)) - const areas = input.labels.filter((label) => label.startsWith('area/')) - - if (input.references.all.length === 0) errors.push('PR 正文必须引用至少一个同仓库 Issue') - if (kinds.length !== 1) { - errors.push(`PR 必须恰好有一个允许的 kind/*,当前为 ${kinds.length}`) - } - if (unknownKinds.length > 0) { - errors.push(`PR 含不支持的 kind/*:${unknownKinds.join(', ')}`) - } - if (legacyLabels.length > 0) errors.push(`PR 含旧版标签:${legacyLabels.join(', ')}`) - if (sourceLabels.length > 0) errors.push(`source/* 仅用于 Issue:${sourceLabels.join(', ')}`) - if (priorities.length > 1) errors.push(`PR 最多有一个 p0–p3,当前为 ${priorities.length}`) - if (areas.length === 0) errors.push('PR 必须至少有一个 area/*') - for (const number of input.references.all) { - if (!input.issues.has(number)) errors.push(`#${number} 不是同仓库 Issue`) - } - - const resolving = input.references.resolving - .map((number) => [number, input.issues.get(number)]) - .filter((entry) => entry[1]) - if (resolving.length === 0) return errors - - const issuePriorities = resolving - .map(([, issue]) => issue.priority?.toLowerCase()) - .filter((priority) => PRIORITIES.includes(priority)) - if (priorities.length === 0 && issuePriorities.length > 0) { - const highest = issuePriorities.sort( - (left, right) => PRIORITIES.indexOf(left) - PRIORITIES.indexOf(right), - )[0] - errors.push(`PR Priority 应为 ${highest}`) - } else if (priorities.length === 1 && issuePriorities.length !== resolving.length) { - errors.push('有 Priority 的解决型 PR 要求每个被解决 Issue 都设置 Priority') - } else if (priorities.length === 1) { - const highest = issuePriorities.sort( - (left, right) => PRIORITIES.indexOf(left) - PRIORITIES.indexOf(right), - )[0] - if (priorities[0] !== highest) errors.push(`PR Priority 应为 ${highest}`) - } - return errors -} - -function token() { - const value = process.env.GH_TOKEN || process.env.GITHUB_TOKEN - if (!value) throw new Error('GH_TOKEN 或 GITHUB_TOKEN 未设置') - return value -} - -function projectToken() { - return process.env.PROJECT_TOKEN || token() -} - -async function api(path, options = {}) { - const { allow404 = false, ...requestOptions } = options - const response = await fetch(`${process.env.GITHUB_API_URL ?? 'https://api.github.com'}${path}`, { - ...requestOptions, - headers: { - Accept: 'application/vnd.github+json', - Authorization: `Bearer ${token()}`, - 'X-GitHub-Api-Version': API_VERSION, - 'User-Agent': 'dsh-issue-policy', - ...options.headers, - }, - }) - if (allow404 && response.status === 404) return null - if (!response.ok) { - const body = await response.text() - throw new Error(`${requestOptions.method ?? 'GET'} ${path}: ${response.status} ${body}`) - } - if (response.status === 204) return null - return response.json() -} - -async function graphql(query, variables) { - const result = await api('/graphql', { - method: 'POST', - body: JSON.stringify({ query, variables }), - headers: { - Authorization: `Bearer ${projectToken()}`, - 'Content-Type': 'application/json', - }, - }) - if (result.errors?.length) throw new Error(result.errors.map((error) => error.message).join('; ')) - return result.data -} - -/** - * Read one Issue together with its Project planning values. - * @param {number} number Same-repository Issue number. - * @param {string|null|undefined} status Optional known Project status. - * @returns {Promise} Issue snapshot, or null when the number identifies a pull request. - */ -export async function issueSnapshot(number, status = undefined) { - const issue = await api(`/repos/${config.organization}/${config.repository}/issues/${number}`) - if (issue.pull_request) return null - const context = await projectContext(number) - return { - number, - nodeId: issue.node_id, - labels: issue.labels.map((label) => label.name), - type: issue.type?.name ?? null, - priority: context.item?.priorityValue?.name ?? null, - status: status === undefined ? (context.item?.fieldValueByName?.name ?? null) : status, - state: issue.state, - stateReason: issue.state_reason ?? null, - } -} - -async function projectContext(number, includeStatusActor = false, includeStartDate = false) { - const data = await graphql( - `query( - $organization: String! - $repository: String! - $number: Int! - $project: Int! - $includeStatusActor: Boolean! - $includeStartDate: Boolean! - $priorityField: String! - $startDateField: String! - ) { - organization(login: $organization) { - projectV2(number: $project) { - id - title - fields(first: 50) { - nodes { - ... on ProjectV2Field { - id - name - dataType - isIssueField - } - ... on ProjectV2SingleSelectField { - id - name - dataType - isIssueField - options { id name } - } - } - } - } - } - repository(owner: $organization, name: $repository) { - issue(number: $number) { - id - timelineItems(last: 100, itemTypes: [PROJECT_V2_ITEM_STATUS_CHANGED_EVENT]) - @include(if: $includeStatusActor) { - nodes { - ... on ProjectV2ItemStatusChangedEvent { - actor { login } - project { id } - status - } - } - } - projectItems(first: 20, includeArchived: true) { - nodes { - id - project { id } - fieldValueByName(name: "Status") { - ... on ProjectV2ItemFieldSingleSelectValue { name optionId } - } - priorityValue: fieldValueByName(name: $priorityField) { - ... on ProjectV2ItemFieldSingleSelectValue { name optionId } - } - startDateValue: fieldValueByName(name: $startDateField) - @include(if: $includeStartDate) { - ... on ProjectV2ItemFieldDateValue { date } - } - } - } - } - } - }`, - { - organization: config.organization, - repository: config.repository, - number, - project: config.projectNumber, - includeStatusActor, - includeStartDate, - priorityField: config.priorityField, - startDateField: config.startDateField, - }, - ) - const project = data.organization?.projectV2 - const issue = data.repository?.issue - if (!project || project.title !== config.projectTitle) throw new Error('目标 Project 不存在或标题不匹配') - if (!issue) throw new Error(`#${number} 不存在`) - const statusField = project.fields.nodes.find((field) => field?.name === 'Status') - if (!statusField) throw new Error('Project 缺少 Status 字段') - const priorityField = project.fields.nodes.find((field) => field?.name === config.priorityField) - if (!priorityField) throw new Error(`Project 缺少 ${config.priorityField} 字段`) - if (priorityField.dataType !== 'SINGLE_SELECT') { - throw new Error(`Project ${config.priorityField} 字段必须为 Single Select`) - } - if (priorityField.isIssueField) { - throw new Error(`Project ${config.priorityField} 字段必须为 Project custom field`) - } - const startDateField = includeStartDate - ? project.fields.nodes.find((field) => field?.name === config.startDateField) - : null - if (includeStartDate && !startDateField) { - throw new Error(`Project 缺少 ${config.startDateField} 字段`) - } - if (startDateField && startDateField.dataType !== 'DATE') { - throw new Error(`Project ${config.startDateField} 字段必须为 Date`) - } - if (startDateField?.isIssueField) { - throw new Error(`Project ${config.startDateField} 字段必须为 Project Date 字段`) - } - const item = issue.projectItems.nodes.find((candidate) => candidate.project.id === project.id) - const latestStatusEvent = issue.timelineItems?.nodes - ?.filter((event) => event?.project?.id === project.id) - .at(-1) - const statusActor = - latestStatusEvent && latestStatusEvent.status === item?.fieldValueByName?.name - ? (latestStatusEvent.actor?.login ?? null) - : null - return { project, issue, statusField, priorityField, startDateField, item, statusActor } -} - -async function ensureProjectItem(number, includeStartDate = false) { - const context = await projectContext(number, false, includeStartDate) - if (context.item) return context - const data = await graphql( - `mutation($projectId: ID!, $contentId: ID!) { - addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) { - item { id } - } - }`, - { projectId: context.project.id, contentId: context.issue.id }, - ) - return { - ...context, - item: { - id: data.addProjectV2ItemById.item.id, - fieldValueByName: null, - priorityValue: null, - startDateValue: null, - }, - } -} - -/** - * Initialize one Issue's Project Start Date when it is empty. - * @param {number} number Same-repository Issue number. - * @param {string} date Date in YYYY-MM-DD form. - * @returns {Promise} Resolves after the conditional Project update. - */ -export async function initializeIssueStartDate(number, date) { - const context = await ensureProjectItem(number, true) - if (context.item.startDateValue?.date) return - await graphql( - `mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $date: Date!) { - updateProjectV2ItemFieldValue(input: { - projectId: $projectId, - itemId: $itemId, - fieldId: $fieldId, - value: {date: $date} - }) { projectV2Item { id } } - }`, - { - projectId: context.project.id, - itemId: context.item.id, - fieldId: context.startDateField.id, - date, - }, - ) -} - -/** - * Initialize every referenced Issue from a newly opened PR. - * @param {{createdAt: string, references: {all: number[]}}} pull Pull-request snapshot. - * @param {string} action Pull-request event action. - * @param {(number: number, date: string) => Promise} initialize Date writer. - * @returns {Promise} Resolves after all eligible Issues are processed. - */ -export async function initializePullRequestStartDates( - pull, - action, - initialize = initializeIssueStartDate, -) { - if (action !== 'opened') return - const date = projectDate(pull.createdAt) - for (const number of pull.references.all) await initialize(number, date) -} - -async function updateStatus(context, status) { - const option = context.statusField.options.find((candidate) => candidate.name === status) - if (!option) throw new Error(`Status 不存在:${status}`) - if (context.item.fieldValueByName?.name === status) return - await graphql( - `mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { - updateProjectV2ItemFieldValue(input: { - projectId: $projectId, - itemId: $itemId, - fieldId: $fieldId, - value: {singleSelectOptionId: $optionId} - }) { projectV2Item { id } } - }`, - { - projectId: context.project.id, - itemId: context.item.id, - fieldId: context.statusField.id, - optionId: option.id, - }, - ) -} - -async function setStatus(number, status) { - await updateStatus(await ensureProjectItem(number), status) -} - -/** - * Remove pull-request kinds and retired aliases from one Issue snapshot. - * @param {{number: number, labels: string[]}} issue Issue snapshot. - * @returns {Promise} Snapshot containing only labels that remain on the Issue. - */ -export async function repairIssueLabels(issue) { - const invalidLabels = issue.labels.filter(isInvalidIssueLabel) - for (const label of invalidLabels) { - await api( - `/repos/${config.organization}/${config.repository}/issues/${issue.number}/labels/${encodeURIComponent(label)}`, - { method: 'DELETE', allow404: true }, - ) - } - return { - ...issue, - labels: issue.labels.filter((label) => !isInvalidIssueLabel(label)), - } -} - -async function upsertAudit(number, errors) { - const comments = await api( - `/repos/${config.organization}/${config.repository}/issues/${number}/comments?per_page=100`, - ) - const existing = comments.find( - (comment) => comment.user?.type === 'Bot' && comment.body?.includes(AUDIT_MARKER), - ) - if (errors.length === 0) { - if (existing) { - await api(`/repos/${config.organization}/${config.repository}/issues/comments/${existing.id}`, { - method: 'DELETE', - }) - } - return - } - const body = `${AUDIT_MARKER}\n⚠️ Issue policy 未通过:\n\n${errors.map((error) => `- ${error}`).join('\n')}` - if (existing) { - if (existing.body === body) return - await api(`/repos/${config.organization}/${config.repository}/issues/comments/${existing.id}`, { - method: 'PATCH', - body: JSON.stringify({ body }), - headers: { 'Content-Type': 'application/json' }, - }) - } else { - await api(`/repos/${config.organization}/${config.repository}/issues/${number}/comments`, { - method: 'POST', - body: JSON.stringify({ body }), - headers: { 'Content-Type': 'application/json' }, - }) - } -} - -/** - * Repair deterministic Issue metadata violations and publish the remaining audit result. - * @param {number} number Same-repository Issue number. - * @param {string[]} extraErrors Errors supplied by the triggering lifecycle operation. - * @param {string|null|undefined} status Optional known Project status. - * @returns {Promise} Violations that remain after repair. - */ -export async function auditIssue(number, extraErrors = [], status = undefined) { - const issue = await issueSnapshot(number, status) - if (!issue) return [] - const repairedIssue = await repairIssueLabels(issue) - const errors = [...extraErrors, ...validateIssue(repairedIssue)] - await upsertAudit(number, errors) - return errors -} - -async function resolvingReferencesSnapshot(number, pull) { - const references = parseReferences({ - body: pull.body ?? '', - repository: `${config.organization}/${config.repository}`, - }) - const issues = new Map() - for (const issueNumber of references.all) { - const issue = await issueSnapshot(issueNumber, null) - if (issue) issues.set(issueNumber, issue) - } - return { - number, - references: retainIssueReferences(references, issues), - issues, - } -} - -async function pullRequestSnapshot(number) { - const [pull, reviewRequests, reviews] = await Promise.all([ - api(`/repos/${config.organization}/${config.repository}/pulls/${number}`), - api(`/repos/${config.organization}/${config.repository}/pulls/${number}/requested_reviewers`), - api(`/repos/${config.organization}/${config.repository}/pulls/${number}/reviews?per_page=100`), - ]) - const resolving = await resolvingReferencesSnapshot(number, pull) - return { - ...resolving, - isDraft: pull.draft, - authorType: pull.user?.type ?? 'User', - reviewRequestCount: reviewRequests.users.length + reviewRequests.teams.length, - reviewCount: reviews.length, - labels: pull.labels.map((label) => label.name), - } -} - -async function lifecyclePullRequestSnapshot(number) { - const pull = await api(`/repos/${config.organization}/${config.repository}/pulls/${number}`) - return { - ...(await resolvingReferencesSnapshot(number, pull)), - createdAt: pull.created_at, - } -} - -async function transitionResolvingIssues(pull, command) { - for (const number of pull.references.resolving) { - const context = await projectContext(number, command === 'changes-requested') - const target = nextResolvingIssueStatus( - context.item?.fieldValueByName?.name ?? null, - command, - context.statusActor, - ) - if (!target) continue - // TODO: Replace this latest-state guard with per-Issue serialization or a - // conditional ProjectV2 update; GraphQL currently has no compare-and-swap. - await updateStatus(context, target) - await auditIssue(number) - } -} - -async function runPullRequestCheck(event) { - const pull = await pullRequestSnapshot(event.pull_request.number) - const errors = validatePullRequest(pull) - if (errors.length > 0) { - for (const error of errors) process.stdout.write(`::error::${error}\n`) - throw new Error(`Issue policy 未通过,共 ${errors.length} 项`) - } - process.stdout.write( - requiresPullRequestPolicy(pull) ? 'Issue policy 通过。\n' : 'PR 尚未进入 Issue policy 强制范围。\n', - ) -} - -async function runLifecycle(eventName, event) { - if (eventName === 'issues') { - const number = event.issue.number - if (event.action === 'opened') await setStatus(number, 'Inbox') - if (event.action === 'closed') { - const target = event.issue.state_reason === 'not_planned' ? 'No action' : 'Done' - await setStatus(number, target) - } - if (event.action === 'reopened') { - await setStatus(number, 'Inbox') - } - await ensureProjectItem(number) - await auditIssue(number) - return - } - - if (eventName === 'pull_request' || eventName === 'pull_request_review') { - const command = resolvingIssueStatusCommand(eventName, event) - if (!command) return - const pull = await lifecyclePullRequestSnapshot(event.pull_request.number) - await transitionResolvingIssues(pull, command) - if (eventName === 'pull_request') { - await initializePullRequestStartDates(pull, event.action) - } - } -} +/** Command-line dispatch for PR policy checks and Issue lifecycle events. */ +import { runLifecycle } from './lifecycle.mjs' +import { runPullRequestCheck, runPullRequestPreflight } from './pull-request.mjs' function readEvent() { if (!process.env.GITHUB_EVENT_PATH) throw new Error('GITHUB_EVENT_PATH 未设置') @@ -747,9 +15,10 @@ function readEvent() { async function main(argv) { const [command] = argv - if (command === 'pr') await runPullRequestCheck(readEvent()) + if (command === 'pr-preflight') await runPullRequestPreflight(readEvent()) + else if (command === 'pr') await runPullRequestCheck(readEvent()) else if (command === 'lifecycle') await runLifecycle(process.env.GITHUB_EVENT_NAME, readEvent()) - else throw new Error('用法:policy.mjs pr|lifecycle') + else throw new Error('用法:policy.mjs pr-preflight|pr|lifecycle') } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { diff --git a/.github/issue-management/policy.test.mjs b/.github/issue-management/policy.test.mjs index 7ee2a85dc2..91939c029a 100644 --- a/.github/issue-management/policy.test.mjs +++ b/.github/issue-management/policy.test.mjs @@ -1,22 +1,28 @@ import assert from 'node:assert/strict' -import { readFileSync, readdirSync } from 'node:fs' +import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs' +import { spawnSync } from 'node:child_process' +import { tmpdir } from 'node:os' +import { join } from 'node:path' import test from 'node:test' +import { api, graphql, initializeIssueStartDate, issueSnapshot } from './github.mjs' +import { auditIssue, initializePullRequestStartDates, repairIssueLabels, runLifecycle } from './lifecycle.mjs' +import { + lifecyclePullRequestSnapshot, + pullRequestSnapshot, + runPullRequestCheck, + runPullRequestPreflight, +} from './pull-request.mjs' import { - auditIssue, - initializeIssueStartDate, - initializePullRequestStartDates, - issueSnapshot, nextResolvingIssueStatus, parseReferences, projectDate, - repairIssueLabels, retainIssueReferences, resolvingIssueStatusCommand, requiresPullRequestPolicy, validateIssue, validatePullRequest, -} from './policy.mjs' +} from './rules.mjs' const projectGraphqlData = ({ projectItem = true, @@ -636,10 +642,21 @@ test('maps only explicit review handoffs to review status commands', () => { ) }) -test('keeps ordinary pull request events as forward-only implementation signals', () => { - for (const action of ['opened', 'edited', 'synchronize', 'reopened', 'labeled', 'unlabeled']) { +test('keeps PR opening, reopening, and body edits as implementation signals', () => { + for (const action of ['opened', 'reopened']) { assert.equal(resolvingIssueStatusCommand('pull_request', { action }), 'implementation') } + assert.equal( + resolvingIssueStatusCommand('pull_request', { action: 'edited', changes: { body: { from: '' } } }), + 'implementation', + ) + for (const action of ['synchronize', 'labeled', 'unlabeled', 'edited']) { + assert.equal(resolvingIssueStatusCommand('pull_request', { action }), null) + } + assert.equal( + resolvingIssueStatusCommand('pull_request', { action: 'edited', changes: { title: { from: '' } } }), + null, + ) assert.equal( resolvingIssueStatusCommand('pull_request', { action: 'review_request_removed' }), null, @@ -749,6 +766,293 @@ test('rejects multiple, unknown, legacy, and Issue-source PR labels', () => { ) }) +const mockPolicyApi = (t, { pull = {}, requested = true, reviews = [], issues = {}, priority = 'P1', projectError = false } = {}) => { + const environment = ['GH_TOKEN', 'GITHUB_TOKEN', 'PROJECT_TOKEN', 'GITHUB_API_URL', 'GITHUB_OUTPUT'] + const previous = new Map(environment.map((key) => [key, process.env[key]])) + const directory = mkdtempSync(join(tmpdir(), 'dsh-policy-')) + t.after(() => { + for (const [key, value] of previous) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + rmSync(directory, { recursive: true, force: true }) + }) + for (const key of environment) delete process.env[key] + process.env.GITHUB_TOKEN = 'repository-token' + process.env.GITHUB_OUTPUT = join(directory, 'output') + const requests = [] + const output = [] + t.mock.method(process.stdout, 'write', (text) => { output.push(text); return true }) + t.mock.method(globalThis, 'fetch', async (url, options) => { + const path = new URL(url).pathname + new URL(url).search + requests.push(path) + if (path.endsWith('/pulls/10')) return Response.json({ + draft: false, user: { type: 'User' }, body: 'Refs #2', + labels: [{ name: 'kind/cleanup' }, { name: 'area/infra' }], ...pull, + }) + if (path.endsWith('/requested_reviewers')) { + return Response.json({ users: requested ? [{}] : [], teams: [] }) + } + if (path.endsWith('/reviews?per_page=100')) return Response.json(reviews) + if (path === '/graphql') { + assert.equal(options.headers.Authorization, 'Bearer repository-token') + if (projectError) return Response.json({ errors: [{ message: 'Project access denied' }] }) + return Response.json({ data: projectGraphqlData({ priority }) }) + } + const number = Number(path.match(/\/issues\/(\d+)$/)?.[1]) + assert.ok(Object.hasOwn(issues, number), 'Unexpected request: ' + path) + const issue = issues[number] + return Response.json(issue ?? { message: 'Not Found' }, { status: issue === null ? 404 : 200 }) + }) + return { requests, output, workflowOutput: () => readFileSync(process.env.GITHUB_OUTPUT, 'utf8') } +} + +for (const [name, pull, requested, count] of [ + ['draft', { draft: true }, true, 1], + ['Bot', { user: { type: 'Bot' } }, true, 1], + ['App', { user: { type: 'App' } }, true, 1], + ['not reviewed', {}, false, 3], +]) { + test('reads no Issue or Project for a currently exempt ' + name + ' PR', async (t) => { + const fixture = mockPolicyApi(t, { pull: { body: 'Fixes #999', ...pull }, requested }) + const event = { pull_request: { number: 10, draft: false, user: { type: 'User' } } } + assert.deepEqual(await runPullRequestPreflight(event), { eligible: false, needsProject: false }) + assert.equal(fixture.requests.length, count) + assert.equal(fixture.workflowOutput(), 'eligible=false\nexempt=true\nneeds-project=false\n') + await runPullRequestCheck(event) + assert.equal(fixture.requests.length, count * 2) + assert.ok(fixture.output.every((text) => text.includes('Issue policy exempt'))) + }) +} + +test('validates informational Issues and ignores PR numbers without Project reads', async (t) => { + const fixture = mockPolicyApi(t, { pull: { body: 'Refs #2; Fixes #3' }, issues: { 2: {}, 3: { pull_request: {} } } }) + const event = { pull_request: { number: 10, draft: true, body: 'Fixes #999' } } + assert.deepEqual(await runPullRequestPreflight(event), { eligible: true, needsProject: false }) + assert.equal(fixture.requests.length, 5) + assert.equal(fixture.workflowOutput(), 'eligible=true\nexempt=false\nneeds-project=false\n') + await runPullRequestCheck(event) + assert.equal(fixture.requests.length, 10) + assert.ok(!fixture.requests.includes('/graphql')) +}) + +test('requires a real Issue and explains why stacked PR references do not qualify', async (t) => { + const fixture = mockPolicyApi(t, { pull: { body: 'Fixes #3' }, issues: { 3: { pull_request: {} } } }) + await assert.rejects(runPullRequestCheck({ pull_request: { number: 10 } }), /Issue policy 未通过/) + assert.equal(fixture.requests.length, 4) + assert.match(fixture.output.join(''), /PR 编号(包括堆叠依赖 PR)不算 Issue 引用/) +}) + +test('fetches Project Priority only for resolving Issues and enforces mismatch', async (t) => { + const fixture = mockPolicyApi(t, { pull: { body: 'Fixes #2; Refs #4; Fixes #3' }, issues: { 2: {}, 4: {}, 3: { pull_request: {} } } }) + const event = { pull_request: { number: 10 } } + assert.deepEqual(await runPullRequestPreflight(event), { eligible: true, needsProject: true }) + assert.equal(fixture.requests.length, 6) + assert.equal(fixture.workflowOutput(), 'eligible=true\nexempt=false\nneeds-project=true\n') + assert.ok(!fixture.requests.includes('/graphql')) + await assert.rejects(runPullRequestCheck(event), /Issue policy 未通过/) + assert.equal(fixture.requests.length, 13) + assert.equal(fixture.requests.filter((path) => path === '/graphql').length, 1) + assert.match(fixture.output.join(''), /PR Priority 应为 p1/) +}) + +test('enforces current metadata on title edits and prior reviews without requested reviewers', async (t) => { + const fixture = mockPolicyApi(t, { requested: false, reviews: [{}], pull: { labels: [] }, issues: { 2: {} } }) + await assert.rejects(runPullRequestCheck({ action: 'edited', changes: { title: { from: 'old' } }, pull_request: { number: 10 } }), /Issue policy 未通过/) + assert.equal(fixture.requests.length, 4) + assert.match(fixture.output.join(''), /PR 必须至少有一个 area/) +}) + +test('fails closed on missing referenced numbers and unavailable Project access', async (t) => { + const fixture = mockPolicyApi(t, { pull: { body: 'Fixes #2' }, issues: { 2: null } }) + await assert.rejects(runPullRequestPreflight({ pull_request: { number: 10 } }), /404/) + assert.equal(fixture.requests.length, 4) +}) + +test('fails closed when current resolving Issues need a Project token preflight did not mint', async (t) => { + const pull = { draft: true, body: 'Fixes #2' } + const fixture = mockPolicyApi(t, { pull, issues: { 2: {} }, projectError: true }) + const event = { pull_request: { number: 10 } } + assert.deepEqual(await runPullRequestPreflight(event), { eligible: false, needsProject: false }) + pull.draft = false + await assert.rejects(runPullRequestCheck(event), /Project access denied/) + assert.equal(fixture.requests.length, 6) +}) + +test('performs no lifecycle requests for removed signals or title-only edits', async (t) => { + const fixture = mockPolicyApi(t) + for (const action of ['synchronize', 'labeled', 'unlabeled']) { + await runLifecycle('pull_request', { action, pull_request: { number: 10 } }) + } + await runLifecycle('pull_request', { action: 'edited', changes: { title: { from: '' } }, pull_request: { number: 10 } }) + for (const state of ['approved', 'commented']) { + await runLifecycle('pull_request_review', { action: 'submitted', review: { state }, pull_request: { number: 10 } }) + } + assert.deepEqual(fixture.requests, []) +}) + +test('keeps trusted preflight before token minting and required policy unconditional', () => { + const source = readFileSync(new URL('../workflows/issue-policy.yml', import.meta.url), 'utf8') + const job = source.slice(source.indexOf(' policy:')) + assert.ok(job.includes(' name: Issue policy')) + assert.ok(!job.slice(0, job.indexOf(' steps:')).includes(' if:')) + assert.ok(source.includes('types: [opened, edited, synchronize, reopened, labeled, unlabeled, ready_for_review, review_requested]')) + const steps = job.split(' - name: ').slice(1) + assert.equal(steps.length, 4) + assert.ok(steps[0].includes('ref: ${{ github.event.repository.default_branch }}')) + assert.ok(steps[0].includes('persist-credentials: false')) + assert.doesNotMatch(source, /pull_request\.head|pull_request_target/) + assert.ok(steps[1].includes('id: preflight')) + assert.ok(steps[1].includes('GITHUB_TOKEN: ${{ github.token }}')) + assert.ok(steps[1].includes('node .github/issue-management/policy.mjs pr-preflight')) + assert.ok(steps[1].includes('if [ -f .github/issue-management/selective-preflight.json ]; then')) + assert.doesNotMatch(steps[1], /secrets\.|PROJECT_TOKEN|if:/) + assert.ok(steps[2].includes("if: ${{ steps.preflight.outputs.needs-project == 'true' }}")) + assert.ok(steps[2].includes('permission-organization-projects: read')) + assert.ok(steps[3].includes('PROJECT_TOKEN: ${{ steps.app-token.outputs.token }}')) + assert.ok(steps[3].includes('run: node .github/issue-management/policy.mjs pr')) + assert.ok(steps[3].includes("if: ${{ steps.preflight.outputs.legacy-automated != 'true' }}")) +}) + +test('runs trusted rollout selection with absent and present capability markers', { skip: process.platform === 'win32' ? 'The policy workflow executes under hosted Ubuntu bash' : false }, (t) => { + const directory = mkdtempSync(join(tmpdir(), 'dsh-policy-rollout-')) + t.after(() => rmSync(directory, { recursive: true, force: true })) + const source = readFileSync(new URL('../workflows/issue-policy.yml', import.meta.url), 'utf8') + const script = source.split(' run: |\n')[1].split(' - name: Create Project read token')[0] + .split('\n').map((line) => line.slice(10)).join('\n') + assert.deepEqual(JSON.parse(readFileSync(new URL('./selective-preflight.json', import.meta.url), 'utf8')), { version: 1 }) + const cases = [ + { name: 'legacy human draft', type: 'User', draft: true, marker: false, expected: 'legacy-automated=false\nneeds-project=true\n' }, + { name: 'legacy human ready', type: 'User', draft: false, marker: false, expected: 'legacy-automated=false\nneeds-project=true\n' }, + { name: 'legacy bot', type: 'Bot', marker: false, expected: 'legacy-automated=true\nneeds-project=false\n' }, + { name: 'legacy app', type: 'App', marker: false, expected: 'legacy-automated=true\nneeds-project=false\n' }, + { name: 'modern exempt', type: 'Bot', marker: true, expected: 'exempt=true\nneeds-project=false\n' }, + { name: 'modern failure', type: 'User', marker: true, failure: true, expected: '' }, + ] + for (const [index, fixture] of cases.entries()) { + const cwd = join(directory, String(index)) + const policyDirectory = join(cwd, '.github', 'issue-management') + mkdirSync(policyDirectory, { recursive: true }) + const eventPath = join(cwd, 'event.json') + const outputPath = join(cwd, 'output') + writeFileSync(eventPath, JSON.stringify({ pull_request: { user: { type: fixture.type }, draft: fixture.draft } })) + writeFileSync(outputPath, '') + if (fixture.marker) writeFileSync(join(policyDirectory, 'selective-preflight.json'), '{"version":1}\n') + writeFileSync(join(policyDirectory, 'policy.mjs'), fixture.marker && !fixture.failure + ? "import fs from 'node:fs'; if (process.argv[2] !== 'pr-preflight') throw Error('wrong command'); fs.appendFileSync(process.env.GITHUB_OUTPUT, 'exempt=true\\nneeds-project=false\\n')\n" + : "throw new Error('preflight unavailable or failed')\n") + const result = spawnSync('bash', ['--noprofile', '--norc', '-eo', 'pipefail', '-c', script], { + cwd, + env: { PATH: process.env.PATH, GITHUB_EVENT_PATH: eventPath, GITHUB_OUTPUT: outputPath }, + encoding: 'utf8', + timeout: 30_000, + }) + assert.equal(result.error, undefined, fixture.name) + assert.equal(result.signal, null, fixture.name) + assert.equal(result.status, fixture.failure ? 1 : 0, fixture.name + ': ' + result.stderr) + assert.equal(readFileSync(outputPath, 'utf8'), fixture.expected, fixture.name) + if (fixture.marker) assert.doesNotMatch(result.stdout, /preserving legacy/) + else assert.match(result.stdout, /preserving legacy policy enforcement/) + } +}) + +test('allocates lifecycle runners only for relevant reviews and PR body edits', () => { + const source = readFileSync(new URL('../workflows/issue-lifecycle.yml', import.meta.url), 'utf8') + const issues = source.split(' issues:')[1].split(' pull_request:')[0] + const pulls = source.split(' pull_request:')[1].split(' pull_request_review:')[0] + const actions = (block) => [...block.matchAll(/^ - (\w+)$/gm)].map((match) => match[1]) + assert.deepEqual(actions(issues), ['opened', 'edited', 'labeled', 'unlabeled', 'closed', 'reopened', 'typed', 'untyped', 'field_added', 'field_removed']) + assert.deepEqual(actions(pulls), ['opened', 'edited', 'reopened', 'review_requested']) + const job = source.slice(source.indexOf(' lifecycle:')) + const beforeSteps = job.slice(0, job.indexOf(' steps:')) + assert.ok(beforeSteps.includes(' if: >-')) + assert.ok(beforeSteps.includes("(github.event_name != 'pull_request_review' || github.event.review.state == 'changes_requested') &&")) + assert.ok(beforeSteps.includes("(github.event_name != 'pull_request' || github.event.action != 'edited' || github.event.changes.body != null)")) + assert.ok(source.includes('ref: ${{ github.event.repository.default_branch }}')) + assert.ok(source.includes('persist-credentials: false')) +}) + +test('keeps REST headers, null responses, and transport errors unchanged', async (t) => { + mockPolicyApi(t) + process.env.GH_TOKEN = 'preferred-token' + process.env.PROJECT_TOKEN = 'project-token' + process.env.GITHUB_API_URL = 'https://github.example/api/v3' + const requests = [] + const responses = [ + Response.json({ ok: true }), + new Response(null, { status: 204 }), + new Response('missing', { status: 404 }), + new Response('denied', { status: 403 }), + Response.json({ errors: [{ message: 'first' }, { message: 'second' }] }), + ] + t.mock.method(globalThis, 'fetch', async (url, options) => { + requests.push({ url, options }) + return responses.shift() + }) + assert.deepEqual(await api('/example'), { ok: true }) + assert.deepEqual(requests[0], { + url: 'https://github.example/api/v3/example', + options: { headers: { + Accept: 'application/vnd.github+json', + Authorization: 'Bearer preferred-token', + 'X-GitHub-Api-Version': '2026-03-10', + 'User-Agent': 'dsh-issue-policy', + } }, + }) + assert.equal(await api('/empty'), null) + assert.equal(await api('/missing', { allow404: true }), null) + await assert.rejects(api('/denied', { method: 'PATCH' }), { message: 'PATCH /denied: 403 denied' }) + await assert.rejects(graphql('query { viewer { login } }', {}), { message: 'first; second' }) + assert.equal(requests[4].options.headers.Authorization, 'Bearer project-token') + assert.equal(requests[4].options.method, 'POST') + assert.equal(requests[4].options.body, JSON.stringify({ query: 'query { viewer { login } }', variables: {} })) + assert.equal(requests.length, 5) +}) + +test('reads policy snapshots in reference order and only resolving Project priorities', async (t) => { + const fixture = mockPolicyApi(t, { + pull: { body: 'Refs #4; Fixes #3; Fixes #2' }, + issues: { 2: {}, 3: { pull_request: {} }, 4: {} }, + }) + assert.deepEqual(await pullRequestSnapshot(10), { + number: 10, + isDraft: false, + authorType: 'User', + reviewRequestCount: 1, + reviewCount: 0, + labels: ['kind/cleanup', 'area/infra'], + references: { all: [2, 4], resolving: [2], related: [4] }, + issues: new Map([[2, { priority: 'P1' }], [4, { priority: null }]]), + }) + const repo = '/repos/deepseek-harness/deepseek-harness' + assert.deepEqual(fixture.requests, [ + repo + '/pulls/10', + repo + '/pulls/10/requested_reviewers', + repo + '/pulls/10/reviews?per_page=100', + repo + '/issues/2', + repo + '/issues/3', + repo + '/issues/4', + '/graphql', + ]) + assert.deepEqual(fixture.output, []) +}) + +test('reads lifecycle references for draft Bot PRs without review or Project requests', async (t) => { + const fixture = mockPolicyApi(t, { + pull: { draft: true, user: { type: 'Bot' }, body: 'Fixes #2; Refs #4', created_at: '2026-08-27T16:00:00Z' }, + issues: { 2: {}, 4: {} }, + }) + assert.deepEqual(await lifecyclePullRequestSnapshot(10), { + number: 10, + references: { all: [2, 4], resolving: [2], related: [4] }, + issues: new Map([[2, { priority: null }], [4, { priority: null }]]), + createdAt: '2026-08-27T16:00:00Z', + }) + const repo = '/repos/deepseek-harness/deepseek-harness' + assert.deepEqual(fixture.requests, [repo + '/pulls/10', repo + '/issues/2', repo + '/issues/4']) + assert.deepEqual(fixture.output, []) +}) + test('allows missing Priority only when resolving Issues are also unprioritized', () => { const pull = { isDraft: false, diff --git a/.github/issue-management/pull-request.mjs b/.github/issue-management/pull-request.mjs new file mode 100644 index 0000000000..db75cfd569 --- /dev/null +++ b/.github/issue-management/pull-request.mjs @@ -0,0 +1,126 @@ +/** Read-only PR snapshots and policy preflight/check workflow entry points. */ + +import fs from 'node:fs' +import process from 'node:process' + +import config from './config.json' with { type: 'json' } +import { api, projectContext } from './github.mjs' +import { + parseReferences, + retainIssueReferences, + requiresPullRequestPolicy, + validatePullRequest, +} from './rules.mjs' + +/** + * Resolve all same-repository body references with REST, excluding pull-request numbers. + * @param {number} number Pull-request number. + * @param {{body?: string}} pull Current REST pull-request data. + * @returns {Promise} Issue-only references and placeholder priorities; never reads Project data. + */ +export async function resolvingReferencesSnapshot(number, pull) { + const references = parseReferences({ + body: pull.body ?? '', + repository: `${config.organization}/${config.repository}`, + }) + const issues = new Map() + for (const issueNumber of references.all) { + const issue = await api( + `/repos/${config.organization}/${config.repository}/issues/${issueNumber}`, + ) + if (!issue.pull_request) issues.set(issueNumber, { priority: null }) + } + return { + number, + references: retainIssueReferences(references, issues), + issues, + } +} + +/** + * Read current PR policy inputs, skipping references for exempt PRs. + * @param {number} number Pull-request number. + * @param {boolean} includeProject Read Project Priority for resolving Issues when true. + * @returns {Promise} Policy snapshot; rejects any failed read and performs no writes. + */ +export async function pullRequestSnapshot(number, includeProject = true) { + const pull = await api(`/repos/${config.organization}/${config.repository}/pulls/${number}`) + const snapshot = { + number, + isDraft: pull.draft, + authorType: pull.user?.type ?? 'User', + reviewRequestCount: 0, + reviewCount: 0, + labels: pull.labels.map((label) => label.name), + references: { all: [], resolving: [], related: [] }, + issues: new Map(), + } + if (snapshot.isDraft || ['Bot', 'App'].includes(snapshot.authorType)) return snapshot + const [reviewRequests, reviews] = await Promise.all([ + api(`/repos/${config.organization}/${config.repository}/pulls/${number}/requested_reviewers`), + api(`/repos/${config.organization}/${config.repository}/pulls/${number}/reviews?per_page=100`), + ]) + snapshot.reviewRequestCount = reviewRequests.users.length + reviewRequests.teams.length + snapshot.reviewCount = reviews.length + if (!requiresPullRequestPolicy(snapshot)) return snapshot + Object.assign(snapshot, await resolvingReferencesSnapshot(number, pull)) + if (includeProject) { + for (const issueNumber of snapshot.references.resolving) { + const context = await projectContext(issueNumber) + snapshot.issues.get(issueNumber).priority = context.item?.priorityValue?.name ?? null + } + } + return snapshot +} + +/** + * Read PR creation time and Issue references without review eligibility gating. + * @param {number} number Pull-request number. + * @returns {Promise} Lifecycle snapshot without Project reads or writes. + */ +export async function lifecyclePullRequestSnapshot(number) { + const pull = await api(`/repos/${config.organization}/${config.repository}/pulls/${number}`) + return { + ...(await resolvingReferencesSnapshot(number, pull)), + createdAt: pull.created_at, + } +} + +const EXEMPT_MESSAGE = + 'Issue policy exempt:当前 PR 不在强制范围(Draft、Bot/App 或尚无 review request/review)。\n' + +/** + * Determine current policy eligibility and Project access needs without Project credentials. + * @param {{pull_request: {number: number}}} event GitHub event identifying the PR. + * @returns {Promise<{eligible: boolean, needsProject: boolean}>} Trusted workflow decisions. + */ +export async function runPullRequestPreflight(event) { + const pull = await pullRequestSnapshot(event.pull_request.number, false) + const eligible = requiresPullRequestPolicy(pull) + const needsProject = eligible && pull.references.resolving.length > 0 + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync( + process.env.GITHUB_OUTPUT, + `eligible=${eligible}\nexempt=${!eligible}\nneeds-project=${needsProject}\n`, + ) + } + process.stdout.write(eligible ? 'Issue policy applicable;执行完整校验。\n' : EXEMPT_MESSAGE) + return { eligible, needsProject } +} + +/** + * Enforce all PR rules against current GitHub state, independently of preflight. + * @param {{pull_request: {number: number}}} event GitHub event identifying the PR. + * @returns {Promise} Resolves on success or exemption; rejects policy failures. + */ +export async function runPullRequestCheck(event) { + const pull = await pullRequestSnapshot(event.pull_request.number) + const errors = validatePullRequest(pull) + if (errors.length > 0) { + for (const error of errors) process.stdout.write(`::error::${error}\n`) + throw new Error(`Issue policy 未通过,共 ${errors.length} 项`) + } + process.stdout.write( + requiresPullRequestPolicy(pull) ? 'Issue policy 通过。\n' : EXEMPT_MESSAGE, + ) +} diff --git a/.github/issue-management/rules.mjs b/.github/issue-management/rules.mjs new file mode 100644 index 0000000000..d8c5e20a31 --- /dev/null +++ b/.github/issue-management/rules.mjs @@ -0,0 +1,303 @@ +/** Pure Issue/PR validation, reference parsing, lifecycle decisions, and Project dates. */ + +import config from './config.json' with { type: 'json' } + +const TYPES = new Set(['Idea', 'Feature', 'Bug', 'Research', 'Task']) +const PRIORITIES = ['p0', 'p1', 'p2', 'p3'] +const PR_KINDS = new Set([ + 'kind/feature', + 'kind/bug-fix', + 'kind/doc', + 'kind/testing', + 'kind/cleanup', + 'kind/dependency', +]) +// Retired label aliases stay reserved so they cannot be recreated. +const LEGACY_LABELS = new Set([ + 'kind/bug', + 'kind/documentation', + 'feature', + 'bug-fix', + 'doc', + 'cleanup', + 'testing', + 'dependencies', + 'ci', + 'cli', + 'llm', + 'web-search', +]) +const TERMINAL_STATUSES = new Set(['Done', 'No action']) +const ACTIVE_STATUS_ORDER = config.statuses.filter((status) => !TERMINAL_STATUSES.has(status)) +const IMPLEMENTATION_PULL_REQUEST_ACTIONS = new Set([ + 'opened', + 'edited', + 'reopened', +]) + +for (const status of ['In progress', 'In review']) { + if (!ACTIVE_STATUS_ORDER.includes(status)) throw new Error(`config.statuses 缺少 ${status}`) +} +if (typeof config.lifecycleActor !== 'string' || !config.lifecycleActor) { + throw new Error('config.lifecycleActor 未设置') +} +if (typeof config.priorityField !== 'string' || !config.priorityField) { + throw new Error('config.priorityField 未设置') +} +if (typeof config.startDateField !== 'string' || !config.startDateField) { + throw new Error('config.startDateField 未设置') +} +if (typeof config.projectTimeZone !== 'string' || !config.projectTimeZone) { + throw new Error('config.projectTimeZone 未设置') +} +Intl.DateTimeFormat('en-US', { timeZone: config.projectTimeZone }) + +/** + * Decide whether the human-review policy applies to a PR. + * @param {{isDraft: boolean, authorType: string, reviewRequestCount: number, reviewCount: number}} input PR state. + * @returns {boolean} Whether the PR policy is mandatory. + */ +export function requiresPullRequestPolicy({ + isDraft, + authorType, + reviewRequestCount, + reviewCount, +}) { + const automated = authorType === 'Bot' || authorType === 'App' + return !isDraft && !automated && (reviewRequestCount > 0 || reviewCount > 0) +} + +/** + * Translate a repository event into one resolving-Issue lifecycle command. + * @param {string} eventName GitHub event name. + * @param {{action?: string, changes?: {body?: object}, review?: {state?: string}}} event GitHub event payload. + * @returns {'implementation'|'review-requested'|'changes-requested'|null} Lifecycle command. + */ +export function resolvingIssueStatusCommand(eventName, event) { + if (eventName === 'pull_request') { + if (event.action === 'edited' && !event.changes?.body) return null + if (event.action === 'review_requested') return 'review-requested' + return IMPLEMENTATION_PULL_REQUEST_ACTIONS.has(event.action) ? 'implementation' : null + } + if ( + eventName === 'pull_request_review' && + event.action === 'submitted' && + event.review?.state?.toLowerCase() === 'changes_requested' + ) { + return 'changes-requested' + } + return null +} + +/** + * Plan one event-directed resolving-Issue status transition. + * @param {string|null} currentStatus Current Project status. + * @param {'implementation'|'review-requested'|'changes-requested'} command Lifecycle command. + * @param {string|null} currentStatusActor Actor that last set the current Project status. + * @returns {string|null} Status to write, or null when no permitted transition exists. + */ +export function nextResolvingIssueStatus(currentStatus, command, currentStatusActor = null) { + let target + if (command === 'review-requested') target = 'In review' + else if (command === 'implementation' || command === 'changes-requested') target = 'In progress' + else throw new Error(`未知 lifecycle command:${command}`) + + const currentIndex = ACTIVE_STATUS_ORDER.indexOf(currentStatus) + const targetIndex = ACTIVE_STATUS_ORDER.indexOf(target) + if ( + command === 'changes-requested' && + currentStatus === 'In review' && + currentStatusActor === config.lifecycleActor + ) { + return target + } + return currentIndex >= 0 && currentIndex < targetIndex ? target : null +} + +/** + * Convert a GitHub timestamp to a Project date in one configured time zone. + * @param {string} timestamp ISO timestamp. + * @param {string} timeZone IANA time-zone name. + * @returns {string} Calendar date in YYYY-MM-DD form. + */ +export function projectDate(timestamp, timeZone = config.projectTimeZone) { + const instant = new Date(timestamp) + if (Number.isNaN(instant.getTime())) throw new Error(`无效的 PR 创建时间:${timestamp}`) + const parts = Object.fromEntries( + new Intl.DateTimeFormat('en-US', { + timeZone, + year: 'numeric', + month: '2-digit', + day: '2-digit', + }) + .formatToParts(instant) + .filter((part) => part.type !== 'literal') + .map((part) => [part.type, part.value]), + ) + return `${parts.year}-${parts.month}-${parts.day}` +} + +function stripIgnoredMarkdown(body) { + const lines = body.replace(//g, '').split(/\r?\n/) + const kept = [] + let fence = null + for (const line of lines) { + const marker = line.match(/^\s*([\u0060~]{3,})/) + if (marker) { + if (fence === null) fence = marker[1][0] + else if (marker[1][0] === fence) fence = null + continue + } + if (fence === null) kept.push(line) + } + return kept.join('\n').replace(/\u0060[^\u0060]*\u0060/g, ' ') +} + +/** + * Parse same-repository resolving and informational references. + * @param {{body: string, repository: string}} input PR body and repository. + * @returns {{all: number[], resolving: number[], related: number[]}} References. + */ +export function parseReferences({ body, repository }) { + const source = stripIgnoredMarkdown(body) + const expected = repository.toLowerCase() + const all = new Set() + const resolving = new Set() + const reference = + /(?:([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#|#)(\d+)|https:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\/issues\/(\d+)/gi + const closing = + /\b(?:close(?:s|d)?|fix(?:es|ed)?|resolve(?:s|d)?)\s*:?\s+(?:(?:([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#|#)(\d+)|https:\/\/github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\/issues\/(\d+))/gi + + for (const match of source.matchAll(reference)) { + const explicit = (match[1] ?? match[3] ?? '').toLowerCase() + const number = Number(match[2] ?? match[4]) + if (!explicit || explicit === expected) all.add(number) + } + for (const match of source.matchAll(closing)) { + const explicit = (match[1] ?? match[3] ?? '').toLowerCase() + const number = Number(match[2] ?? match[4]) + if (!explicit || explicit === expected) { + all.add(number) + resolving.add(number) + } + } + return { + all: [...all].sort((left, right) => left - right), + resolving: [...resolving].sort((left, right) => left - right), + related: [...all].filter((number) => !resolving.has(number)).sort((a, b) => a - b), + } +} + +/** + * Retain only references that resolve to Issues rather than pull requests. + * @param {{all: number[], resolving: number[], related: number[]}} references Parsed references. + * @param {Map} issues Resolved same-repository Issues. + * @returns {{all: number[], resolving: number[], related: number[]}} Issue-only references. + */ +export function retainIssueReferences(references, issues) { + return { + all: references.all.filter((number) => issues.has(number)), + resolving: references.resolving.filter((number) => issues.has(number)), + related: references.related.filter((number) => issues.has(number)), + } +} + +/** + * Validate one Issue with its Project status. + * @param {{labels: string[], type: string|null, priority: string|null, status: string|null, state: string, stateReason: string|null}} issue Issue snapshot. + * @returns {string[]} Validation errors. + */ +export function validateIssue(issue) { + const errors = [] + const status = issue.status + const invalidLabels = issue.labels.filter(isInvalidIssueLabel) + + if (invalidLabels.length > 0) { + errors.push(`Issue 不得使用 PR kind 或旧版标签:${invalidLabels.join(', ')}`) + } + if (!TYPES.has(issue.type ?? '')) errors.push('Type 必须是五种原生英文 Type 之一') + if (!status || !config.statuses.includes(status)) errors.push('Issue 必须在 Project 中且具有合法 Status') + if (issue.priority !== null && !PRIORITIES.includes(issue.priority.toLowerCase())) { + errors.push('Priority 必须为空或为 P0–P3') + } + if (status === 'Done' && (issue.state !== 'closed' || issue.stateReason !== 'completed')) { + errors.push('Done 必须对应 Completed 关闭原因') + } + if ( + status === 'No action' && + (issue.state !== 'closed' || issue.stateReason !== 'not_planned') + ) { + errors.push('No action 必须对应 Not planned 关闭原因') + } + if (!['Done', 'No action'].includes(status ?? '') && issue.state !== 'open') { + errors.push(`${status} 必须对应开放 Issue`) + } + return errors +} + +/** + * Identify PR kinds and retired aliases that cannot label an Issue. + * @param {string} label Label name. + * @returns {boolean} Whether the label is invalid for Issues. + */ +export function isInvalidIssueLabel(label) { + return label.startsWith('kind/') || LEGACY_LABELS.has(label) +} + +/** + * Validate PR metadata and its referenced Issues. + * @param {{authorType: string, labels: string[], references: ReturnType, issues: Map}} input PR snapshot. + * @returns {string[]} Validation errors. + */ +export function validatePullRequest(input) { + if (!requiresPullRequestPolicy(input)) return [] + const errors = [] + const kinds = input.labels.filter((label) => PR_KINDS.has(label)) + const unknownKinds = input.labels.filter( + (label) => label.startsWith('kind/') && !PR_KINDS.has(label) && !LEGACY_LABELS.has(label), + ) + const legacyLabels = input.labels.filter((label) => LEGACY_LABELS.has(label)) + const sourceLabels = input.labels.filter((label) => label.startsWith('source/')) + const priorities = input.labels.filter((label) => PRIORITIES.includes(label)) + const areas = input.labels.filter((label) => label.startsWith('area/')) + + if (input.references.all.length === 0) { + errors.push('PR 正文必须引用至少一个同仓库 Issue;PR 编号(包括堆叠依赖 PR)不算 Issue 引用') + } + if (kinds.length !== 1) { + errors.push(`PR 必须恰好有一个允许的 kind/*,当前为 ${kinds.length}`) + } + if (unknownKinds.length > 0) { + errors.push(`PR 含不支持的 kind/*:${unknownKinds.join(', ')}`) + } + if (legacyLabels.length > 0) errors.push(`PR 含旧版标签:${legacyLabels.join(', ')}`) + if (sourceLabels.length > 0) errors.push(`source/* 仅用于 Issue:${sourceLabels.join(', ')}`) + if (priorities.length > 1) errors.push(`PR 最多有一个 p0–p3,当前为 ${priorities.length}`) + if (areas.length === 0) errors.push('PR 必须至少有一个 area/*') + for (const number of input.references.all) { + if (!input.issues.has(number)) errors.push(`#${number} 不是同仓库 Issue`) + } + + const resolving = input.references.resolving + .map((number) => [number, input.issues.get(number)]) + .filter((entry) => entry[1]) + if (resolving.length === 0) return errors + + const issuePriorities = resolving + .map(([, issue]) => issue.priority?.toLowerCase()) + .filter((priority) => PRIORITIES.includes(priority)) + if (priorities.length === 0 && issuePriorities.length > 0) { + const highest = issuePriorities.sort( + (left, right) => PRIORITIES.indexOf(left) - PRIORITIES.indexOf(right), + )[0] + errors.push(`PR Priority 应为 ${highest}`) + } else if (priorities.length === 1 && issuePriorities.length !== resolving.length) { + errors.push('有 Priority 的解决型 PR 要求每个被解决 Issue 都设置 Priority') + } else if (priorities.length === 1) { + const highest = issuePriorities.sort( + (left, right) => PRIORITIES.indexOf(left) - PRIORITIES.indexOf(right), + )[0] + if (priorities[0] !== highest) errors.push(`PR Priority 应为 ${highest}`) + } + return errors +} diff --git a/.github/issue-management/selective-preflight.json b/.github/issue-management/selective-preflight.json new file mode 100644 index 0000000000..3f562dd675 --- /dev/null +++ b/.github/issue-management/selective-preflight.json @@ -0,0 +1 @@ +{ "version": 1 } diff --git a/.github/workflows/build-exe-for-python-sdk.yml b/.github/workflows/build-exe-for-python-sdk.yml index 49866ba5ea..7df809d414 100644 --- a/.github/workflows/build-exe-for-python-sdk.yml +++ b/.github/workflows/build-exe-for-python-sdk.yml @@ -63,7 +63,9 @@ jobs: plan: name: plan targets if: inputs.ci || inputs.release || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest + runs-on: >- + ${{ inputs.ci && !inputs.release && vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' + && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 5 outputs: matrix: ${{ steps.plan.outputs.matrix }} @@ -123,7 +125,9 @@ jobs: sdk-wheel: needs: plan name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl - runs-on: ubuntu-latest + runs-on: >- + ${{ inputs.ci && !inputs.release && vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' + && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 5 steps: - uses: actions/checkout@v6 @@ -151,7 +155,12 @@ jobs: build: needs: [plan, sdk-wheel] name: ${{ matrix.target }} - runs-on: ${{ matrix.runner }} + runs-on: >- + ${{ inputs.ci && !inputs.release && matrix.target == 'node24-linux-x64' + && vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' && 'blacksmith-16vcpu-ubuntu-2404' + || inputs.ci && !inputs.release && matrix.target == 'node24-win-x64' + && vars.DSH_CI_FAILOVER_WINDOWS == 'blacksmith' && 'blacksmith-16vcpu-windows-2025' + || matrix.runner }} timeout-minutes: 45 strategy: fail-fast: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57ed55c36d..c1e529c93f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -325,6 +325,25 @@ jobs: if (( install_status != 0 )); then exit "$install_status"; fi exit "$sandbox_status" + # Blacksmith's Ubuntu HTTP archive endpoints can be unreachable while HTTPS works. + - name: Use HTTPS for Ubuntu archives on Blacksmith + if: vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' + run: | + sudo python3 - <<'PY' + from pathlib import Path + import re + + apt = Path('/etc/apt') + sources = [apt / 'sources.list', apt / 'blacksmith-ubuntu-mirrors.txt', + *(apt / 'sources.list.d').glob('*.list'), + *(apt / 'sources.list.d').glob('*.sources')] + for source in sources: + if source.is_file(): + source.write_text(re.sub( + r'http://((?:[a-z]{2}\.)?archive\.ubuntu\.com|security\.ubuntu\.com)/', + r'https://\1/', source.read_text())) + PY + - name: Install Playwright Chromium and hosted dependencies if: vars.DSH_CI_FAILOVER_LINUX != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' run: pnpm --filter @deepseek-ai/dsh-web-frontend exec playwright install --with-deps chromium diff --git a/.github/workflows/e2b-e2e.yml b/.github/workflows/e2b-e2e.yml deleted file mode 100644 index 0442b0e093..0000000000 --- a/.github/workflows/e2b-e2e.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: E2E (E2B sandbox) - -# This suite provisions external E2B sandboxes and is intentionally opt-in. -# It has no push, pull_request, schedule, or workflow_call trigger. -on: - workflow_dispatch: - -permissions: - contents: read - -env: - # CI runs must never report to the production telemetry endpoint baked - # into apps/cli/cordis.yml (AppCLIEntry disables the row when set). - DSH_TELEMETRY_DISABLED: '1' - -jobs: - e2b: - runs-on: ubuntu-latest - name: E2B live Loader composition - timeout-minutes: 30 - steps: - - uses: actions/checkout@v6 - - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - - - name: Install (immutable) - run: pnpm install --frozen-lockfile - - # The tests self-skip locally when the credential is absent. A manually - # dispatched run must fail instead of reporting an all-skipped green. - - name: Preflight (require E2B API key) - env: - E2B_API_KEY: ${{ secrets.E2B_API_KEY_EXTERNAL }} - run: | - set -euo pipefail - if [ -z "${E2B_API_KEY:-}" ]; then - echo "::error::E2B_API_KEY is empty. Configure the E2B_API_KEY_EXTERNAL repository secret." - exit 1 - fi - echo "E2B_API_KEY present." - - # The Loader smoke runs package exports under plain Node in lib mode. - - name: Build (lib for the E2B Loader smoke) - run: pnpm run build:official - - - name: E2B tests (live sandbox) - env: - E2B_API_KEY: ${{ secrets.E2B_API_KEY_EXTERNAL }} - DSH_E2E_MAX_WORKERS: '1' - DSH_EXAMPLE_MODE: lib - run: >- - pnpm exec vitest run --config vitest.e2e.config.ts - packages/e2b/e2b/tests/composition.e2e.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 3b7ff878f8..5bc604e7a0 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -53,7 +53,9 @@ env: jobs: e2e: - runs-on: ubuntu-latest + runs-on: >- + ${{ vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' + && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} name: e2e # Run on every trusted event. Skip untrusted PRs (forks + Dependabot) where # the secret is withheld — they would otherwise hard-fail the preflight. diff --git a/.github/workflows/issue-lifecycle.yml b/.github/workflows/issue-lifecycle.yml index 8e2265fb19..519fe1c7df 100644 --- a/.github/workflows/issue-lifecycle.yml +++ b/.github/workflows/issue-lifecycle.yml @@ -5,22 +5,19 @@ on: types: - opened - edited - - assigned - - unassigned - labeled - unlabeled - closed - reopened + - typed + - untyped - field_added - field_removed pull_request: types: - opened - edited - - synchronize - reopened - - labeled - - unlabeled - review_requested pull_request_review: types: @@ -36,11 +33,9 @@ concurrency: jobs: lifecycle: name: Issue lifecycle - # Runs on every pull_request_review event so the check reports success rather - # than a gray "skipped" segment. The token-creating and board-mutating steps - # are gated at step level (a skipped step does not gray the job): only a - # changes_requested review drives the Project board; approved/commented - # reviews never mint a write-capable App token. + if: >- + (github.event_name != 'pull_request_review' || github.event.review.state == 'changes_requested') && + (github.event_name != 'pull_request' || github.event.action != 'edited' || github.event.changes.body != null) runs-on: ubuntu-latest steps: - name: Check out trusted policy @@ -50,7 +45,6 @@ jobs: persist-credentials: false - name: Create project token id: app-token - if: ${{ github.event_name != 'pull_request_review' || github.event.review.state == 'changes_requested' }} uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with: client-id: ${{ vars.DSH_ISSUE_APP_CLIENT_ID }} @@ -58,7 +52,6 @@ jobs: owner: deepseek-harness repositories: deepseek-harness - name: Handle repository event - if: ${{ github.event_name != 'pull_request_review' || github.event.review.state == 'changes_requested' }} env: GH_TOKEN: ${{ steps.app-token.outputs.token }} run: node .github/issue-management/policy.mjs lifecycle diff --git a/.github/workflows/issue-policy.yml b/.github/workflows/issue-policy.yml index 9428569632..6e07adb502 100644 --- a/.github/workflows/issue-policy.yml +++ b/.github/workflows/issue-policy.yml @@ -21,9 +21,26 @@ jobs: with: ref: ${{ github.event.repository.default_branch }} persist-credentials: false + - name: Determine policy eligibility + id: preflight + env: + GITHUB_TOKEN: ${{ github.token }} + shell: bash + run: | + if [ -f .github/issue-management/selective-preflight.json ]; then + node .github/issue-management/policy.mjs pr-preflight + else + node --input-type=module <<'NODE' + import fs from 'node:fs' + const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8')) + const automated = ['Bot', 'App'].includes(event.pull_request.user.type) + fs.appendFileSync(process.env.GITHUB_OUTPUT, `legacy-automated=${automated}\nneeds-project=${!automated}\n`) + console.log('Trusted policy has no selective preflight; preserving legacy policy enforcement.') + NODE + fi - name: Create Project read token id: app-token - if: ${{ github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.type != 'App' }} + if: ${{ steps.preflight.outputs.needs-project == 'true' }} uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with: client-id: ${{ vars.DSH_ISSUE_APP_CLIENT_ID }} @@ -32,8 +49,9 @@ jobs: repositories: deepseek-harness permission-issues: read permission-organization-projects: read + # Re-read current state even after an exempt preflight; never replace a failure with an edit skip. - name: Validate pull request - if: ${{ github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.type != 'App' }} + if: ${{ steps.preflight.outputs.legacy-automated != 'true' }} env: GITHUB_TOKEN: ${{ github.token }} PROJECT_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index f3d9ab44c3..b756f5bf38 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -211,7 +211,7 @@ jobs: with: packages-dir: dist/runtime/ # Public attestations reveal the private publisher repository. OIDC - # authentication remains enabled without uploading that provenance. + # authentication remains enabled without uploading that repository identity. attestations: false # Keep the SDK in a dependent job. If its upload fails after the immutable diff --git a/AGENTS.md b/AGENTS.md index b5b8d0295c..7893cbb5d3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,6 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// api/ Remote BFF assembly and Typert RPC gateway typert/ type graph generator, loader, and runtime registry llm/ LLM capability: Service Definition/Consumer + DeepSeek providers - e2b/ E2B POC: sandbox + FS/subprocess adapters shell/ bash capability: Service Definition + local/pwsh providers + shell Consumers subprocess/ subprocess capability + local process-tree provider + shared Win32 library terminal/ persistent sessions @@ -119,8 +118,9 @@ Real-API tests and demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, - **Trust TypeScript at typed same-process boundaries.** Do not add runtime validation, fallback behavior, or hostile-input tests solely for values the static interface requires; validate at parser/config, queued, model/tool JSON, durable/file, worker, process, and wire boundaries. - **Source plane vs artifact plane, never mixed.** Static gates and tests resolve workspace imports through tsconfig `paths` to `src` and pass on a clean tree; gates consuming built `lib/` declare that dependency ([layout](docs/development.md#typescript-project-layout)). - **Keep compiler faces explicit.** A package with both Host and Client programs exposes face-specific leaf configs and a solution-only root; repo-wide programs seed a face config, never the root solution ([layout](docs/development.md#typescript-project-layout)). -- **An empty `catch` names what it swallows** and why nothing else can reach it; keep the `try` to one statement. -- **Keep comments local.** Do not restate code, explain distant behavior unless locally required, or expand unrelated comments ([rationale](.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md)). +- **An empty `catch` names the error** and why; keep its `try` to one statement. +- **Keep comments local.** Do not restate code, expand unrelated comments, or explain distant behavior without local need ([rationale](.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md)). +- **Ban `prove` + `nance`** ([rule](.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md)). - **Prefer symmetry for parallel values**; unexplained asymmetry usually signals a missed extraction. - **Tests describe behavior, not correctness.** Change obsolete behavior with its tests; explain why in the PR. - **Non-trivial changes MUST include an Agent Note in the same PR;** only mechanical/local edits are exempt ([scope](.agents/notes/README.md#when-to-write-one)). Archived notes are frozen: never edit or treat them as current authority ([archive policy](.agents/notes/README.md#archiving-and-deletion)). diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 34b3f4481d..345b2ff9ca 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -66,7 +66,6 @@ External packages installed for runtime use or distributed inside the prebuilt b | [`commander`](https://github.com/tj/commander.js) | MIT | | [`compression`](https://github.com/expressjs/compression) | MIT | | [`diff`](https://github.com/kpdecker/jsdiff) | BSD-3-Clause | -| [`e2b`](https://github.com/e2b-dev/e2b) | MIT | | [`electron-updater`](https://github.com/electron-userland/electron-builder) | MIT | | [`eventsource-parser`](https://github.com/rexxars/eventsource-parser) | MIT | | [`fflate`](https://github.com/101arrowz/fflate) | MIT | diff --git a/apps/cli/src/profile-boot.ts b/apps/cli/src/profile-boot.ts index 1c50e1b581..2de773c5d8 100644 --- a/apps/cli/src/profile-boot.ts +++ b/apps/cli/src/profile-boot.ts @@ -336,7 +336,7 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con const ctx = await boot(NAME, rootConfig, structuredClone(allPatches(composed)), (hostCtx) => { app.current = hostCtx // Before any config-tree entry mounts, so plugins resolve all launch-time - // environment values from the same immutable provenance snapshot. + // environment values from the same immutable launch snapshot. hostCtx.provide(DSH_LAUNCH_ENVIRONMENT_KEY, options.environment) // The command line and bounded exit request are launcher facts available // to every app plugin that injects the argument snapshot. diff --git a/apps/cli/tests/agent-team-headless.e2e.ts b/apps/cli/tests/agent-team-headless.e2e.ts index 92b3b58548..481cfc7332 100644 --- a/apps/cli/tests/agent-team-headless.e2e.ts +++ b/apps/cli/tests/agent-team-headless.e2e.ts @@ -54,7 +54,7 @@ describe('dsh run with Agent Teams enabled', () => { ].join('\n')) const launch = resolveExampleLaunch({ srcBin: dshBinScript, - configArgs: ['--profile', 'headless', '请明确使用 Agent Teams,把调研和实现拆给两个 teammate,等待完成后汇总。'], + configArgs: ['--profile', 'headless', '请先运行 workflow 检查,再使用 Agent Teams 把调研和实现拆给两个 teammate,等待完成后汇总。'], tsconfigPath, env: { DSH_HOME: home, @@ -85,9 +85,17 @@ describe('dsh run with Agent Teams enabled', () => { const files = (await readdir(sessions, { recursive: true })) .filter(file => file.endsWith('.jsonl')) - expect(files).toHaveLength(3) + expect(files).toHaveLength(4) const logs = await Promise.all(files.map(file => readFile(join(sessions, file), 'utf8'))) const parsed = logs.map(records) + const workflowChild = parsed.find(log => log.some(record => record.type === 'subagent/descriptor' + && (record.data as { mode: string }).mode === 'one-shot')) + expect(workflowChild).toBeDefined() + expect(workflowChild!.find(record => record.type === 'subagent/descriptor')?.data) + .toMatchObject({ mode: 'one-shot', provider: 'spawn' }) + expect(workflowChild!.filter(record => record.type === 'user/message' + && (record.data as { source: { kind: string } }).source.kind === 'user').map(record => record.data)) + .toEqual([expect.objectContaining({ content: [{ type: 'text', text: 'TEAM_WORKFLOW_CHILD' }] })]) const root = parsed.find((log) => { const header = log[0] return header?.type === 'session' && typeof header.parentSession !== 'string' @@ -107,6 +115,9 @@ describe('dsh run with Agent Teams enabled', () => { expect(toolNames).toContain('wait_agent') expect(toolNames).toContain('team_task_list') expect(toolNames).toContain('list_agents') + expect(toolNames).toContain('workflow') + expect(root!.find(record => record.type === 'tool-workflow/run-end')?.data) + .toMatchObject({ stopReason: 'completed' }) } finally { await rm(cwd, { recursive: true, force: true }) } diff --git a/apps/cli/tests/github-webhook-real.e2e.ts b/apps/cli/tests/github-webhook-real.e2e.ts index f6583c79fa..45e4330ca8 100644 --- a/apps/cli/tests/github-webhook-real.e2e.ts +++ b/apps/cli/tests/github-webhook-real.e2e.ts @@ -412,7 +412,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('GitHub webhook through the real const admitted = await eventually( child, observation.text, - 'webhook provenance, title, and permission events', + 'webhook source, title, and permission events', async () => await history(baseUrl, sessionId), (page) => { const events = historyEvents(page) diff --git a/apps/cli/tests/profiles/headless/team-snapshot.patch.yml b/apps/cli/tests/profiles/headless/team-snapshot.patch.yml index ed0c4d3eb7..c7048d2406 100644 --- a/apps/cli/tests/profiles/headless/team-snapshot.patch.yml +++ b/apps/cli/tests/profiles/headless/team-snapshot.patch.yml @@ -31,19 +31,10 @@ disabled: true - id: tool-subagent - config: - provider: spawn - toolName: subagent - backgroundMode: one-shot - maxDepth: 1 + disabled: true - id: tool-subagent-fork - config: - provider: fork - toolName: subagent_fork - backgroundMode: one-shot - enableRunInBackground: false - maxDepth: 1 + disabled: true - insert: - id: agent-team diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs b/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs index 0e31795018..8319ed9539 100644 --- a/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs +++ b/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs @@ -134,6 +134,12 @@ function implementer(messages) { function lead(messages) { const names = calls(messages) const last = latestAssistantCalls(messages) + if (!names.includes('workflow')) { + return toolChunks([{ name: 'workflow', args: { + meta: { name: 'team-preflight', description: 'Check fresh workflow delegation alongside teammates.' }, + script: 'return await agent("TEAM_WORKFLOW_CHILD");', + } }]) + } const spawned = names.filter(name => name === 'spawn_teammate').length if (spawned === 0) { return toolChunks([{ @@ -174,12 +180,20 @@ function lead(messages) { class TeamFixtureAdapter extends LlmAdapter { async * stream(options) { - const userText = options.messages.flatMap(message => message.role === 'user' - ? message.content.filter(block => block.type === 'text').map(block => block.text) - : []).join('\n') - const chunks = userText.includes('RESEARCHER_MARK') + const tools = options.tools.map(tool => tool.name) + if (tools.includes('subagent') || tools.includes('subagent_fork')) { + throw new Error('Team profile exposes a direct subagent tool') + } + const initial = options.messages.findLast(message => message.role === 'user' && message.source.kind === 'user') + const identity = initial?.content[0]?.text?.trimEnd() + if (identity !== 'TEAM_WORKFLOW_CHILD' && (!tools.includes('spawn_teammate') || !tools.includes('workflow'))) { + throw new Error('Team profile is missing teammate or workflow tools') + } + const chunks = identity === 'TEAM_WORKFLOW_CHILD' + ? textChunks('Fresh workflow child complete.') + : identity === '\nYou are teammate "researcher".\n' ? researcher(options.messages) - : userText.includes('IMPLEMENTER_MARK') + : identity === '\nYou are teammate "implementer".\n' ? implementer(options.messages) : lead(options.messages) for (const chunk of chunks) { diff --git a/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts index bb31c90060..36a9e5efda 100644 --- a/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts @@ -1,6 +1,7 @@ -import { readFile, readdir, writeFile } from 'node:fs/promises' +import { mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises' import { createServer } from 'node:http' import type { IncomingMessage, ServerResponse } from 'node:http' +import { tmpdir } from 'node:os' import { join } from 'node:path' import { fileURLToPath } from 'node:url' import { @@ -251,6 +252,117 @@ describe('headless stream-json snapshots', () => { expect(result.stderr).toBe(await readFile(headlessReasoningExpected, 'utf8')) }, LOADER_SMOKE_TEST_TIMEOUT_MS) + it('projects the same run as JSON events under a generated session identity', async () => { + const task = 'Prove the machine-readable product headless profile path.' + const result = await runLoaderSmoke({ + label: 'product headless profile json snapshot', + tempDirPrefix: 'headless-snapshot-profile-json-', + binScript: dshBinScript, + configPath: headlessOverlayPath, + binArgs: [ + '--profile', 'headless', '--patch', headlessOverlayPath, + '--json', task, + ], + tsconfigPath, + env: { + DSH_PERMISSION_MODE: 'danger-full-access', + DSH_TELEMETRY_DISABLED: '1', + NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '), + }, + }) + + const events = result.stdout.trim().split('\n').map(line => JSON.parse(line) as JsonObject) + expect(events[0]).toMatchObject({ type: 'session' }) + expect(events[0]?.sessionId).toMatch(/^session-/) + expect(typeof events[0]?.cwd).toBe('string') + expect(events.at(-1)).toMatchObject({ type: 'final', text: 'CLI tool round trip complete: CLI_TOOL_ROUND_TRIP' }) + expect(events.map(event => event.type)).toContain('thinking') + expect(events.map(event => event.type)).toContain('tool_call') + expect(events.map(event => event.type)).toContain('tool_result') + expect(events.map(event => event.type)).not.toContain('error') + expect(result.stderr).toBe('') + }, LOADER_SMOKE_TEST_TIMEOUT_MS) + + it('fails the JSON run when --session-id names no stored Session', async () => { + const result = await runLoaderSmoke({ + label: 'product headless profile unknown session', + tempDirPrefix: 'headless-snapshot-profile-unknown-session-', + binScript: dshBinScript, + configPath: headlessOverlayPath, + binArgs: [ + '--profile', 'headless', '--patch', headlessOverlayPath, + '--json', '--session-id', 'headless-unknown-session', 'Continue the conversation.', + ], + tsconfigPath, + expectedExitCode: 1, + env: { + DSH_TELEMETRY_DISABLED: '1', + NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '), + }, + }) + + const events = result.stdout.trim().split('\n').map(line => JSON.parse(line) as JsonObject) + expect(events).toEqual([{ + type: 'error', + message: 'session "headless-unknown-session" does not exist; omit --session-id to start a new Session', + }]) + expect(result.stderr).toContain('omit --session-id to start a new Session') + }, LOADER_SMOKE_TEST_TIMEOUT_MS) + + it('resumes one persisted Session across two real --session-id wakes', async () => { + const firstTask = 'Record the first wake of the resume proof.' + const secondTask = 'Continue from the first wake of the resume proof.' + const env = { + DSH_PERMISSION_MODE: 'danger-full-access', + DSH_TELEMETRY_DISABLED: '1', + NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '), + } + const cwd = await mkdtemp(join(tmpdir(), 'headless-session-resume-')) + try { + const first = await runLoaderSmoke({ + label: 'product headless profile resume first wake', + cwd, + binScript: dshBinScript, + configPath: headlessOverlayPath, + binArgs: ['--profile', 'headless', '--patch', headlessOverlayPath, '--json', firstTask], + tsconfigPath, + env, + }) + const firstEvents = first.stdout.trim().split('\n').map(line => JSON.parse(line) as JsonObject) + expect(firstEvents[0]).toMatchObject({ type: 'session' }) + const sessionId = firstEvents[0]?.sessionId + if (typeof sessionId !== 'string') throw new Error('the first wake reported no Session identity') + + const second = await runLoaderSmoke({ + label: 'product headless profile resume second wake', + cwd, + binScript: dshBinScript, + configPath: headlessOverlayPath, + binArgs: [ + '--profile', 'headless', '--patch', headlessOverlayPath, + '--json', '--session-id', sessionId, secondTask, + ], + tsconfigPath, + env, + inspect: async (inspected) => { + const logs = await persistedLogs(inspected, join(inspected, '.dsh', 'sessions')) + expect(logs).toHaveLength(1) + const content = logs[0]?.content ?? '' + expect(content).toContain(firstTask) + expect(content).toContain(secondTask) + }, + }) + const secondEvents = second.stdout.trim().split('\n').map(line => JSON.parse(line) as JsonObject) + expect(secondEvents[0]).toMatchObject({ type: 'session', sessionId }) + expect(secondEvents.at(-1)).toMatchObject({ + type: 'final', + text: 'CLI tool round trip complete: CLI_TOOL_ROUND_TRIP', + }) + } finally { + await rm(cwd, { recursive: true, force: true }) + } + }, LOADER_SMOKE_TEST_TIMEOUT_MS * 2) + it('prints a terminal model failure through the product headless profile command', async () => { const result = await runLoaderSmoke({ label: 'product headless profile model failure snapshot', @@ -607,7 +719,7 @@ describe('headless stream-json snapshots', () => { configPath: teamConfigPath, binArgs: [ teamConfigPath, - '请明确使用 Agent Teams,把调研和实现拆给两个 teammate,等待完成后汇总。', + '请先运行 workflow 检查,再使用 Agent Teams 把调研和实现拆给两个 teammate,等待完成后汇总。', ], tsconfigPath, processTimeoutMs: 60_000, @@ -620,6 +732,15 @@ describe('headless stream-json snapshots', () => { const parent = logs.find(log => typeof log.header.parentSession !== 'string') if (parent === undefined) throw new Error('Agent Teams snapshot did not persist its Lead') const rows = parseJsonl(parent.content) + const workflowChild = logs.find(log => parseJsonl(log.content).some(row => row.type === 'subagent/descriptor' + && (row.data as JsonObject).mode === 'one-shot')) + if (workflowChild === undefined) throw new Error('Team profile did not persist its workflow child') + const workflowRows = parseJsonl(workflowChild.content) + expect(workflowRows.find(row => row.type === 'subagent/descriptor')?.data) + .toMatchObject({ mode: 'one-shot', provider: 'spawn' }) + expect(workflowRows.filter(row => row.type === 'user/message' + && ((row.data as JsonObject).source as JsonObject).kind === 'user').map(row => row.data)) + .toEqual([expect.objectContaining({ content: [{ type: 'text', text: 'TEAM_WORKFLOW_CHILD' }] })]) const members = rows.filter(row => row.type === 'team/member') .map(row => ((row.data as JsonObject).member as JsonObject)) const tasks = rows.filter(row => row.type === 'team/task') @@ -668,9 +789,22 @@ describe('headless stream-json snapshots', () => { if (data.name !== 'team_task_update' || typeof data.arguments !== 'string') return false return (JSON.parse(data.arguments) as JsonObject).action === 'complete' }) + const identityReminders = logs.flatMap((log) => { + if (log === parent || log === workflowChild) return [] + const initial = parseJsonl(log.content).find(row => row.type === 'user/message' + && ((row.data as JsonObject).source as JsonObject).kind === 'user') + if (initial === undefined) throw new Error('Teammate Session has no initial task') + const content = (initial.data as JsonObject).content as JsonObject[] + expect(content).toHaveLength(2) + const identity = content[0]!.text + if (typeof identity !== 'string') throw new Error('Teammate initial task has no identity text') + return [identity.trimEnd()] + }).sort() projection = { sessions: logs.length, + workflowStopReason: (rows.find(row => row.type === 'tool-workflow/run-end')?.data as JsonObject)?.stopReason, memberEdges: members.length, + identityReminders, activeMembers: members.filter(member => member.phase === 'active').map(member => member.name).sort(), tasks: latestTasks.map(task => ({ subject: task.subject, @@ -705,9 +839,17 @@ describe('headless stream-json snapshots', () => { ], "checkedRoster": true, "deliveredMessages": 2, + "identityReminders": [ + " + You are teammate "implementer". + ", + " + You are teammate "researcher". + ", + ], "memberEdges": 4, "queuedMessages": 2, - "sessions": 3, + "sessions": 4, "steerEvidence": { "completedAfterMessage": true, "enteredOpenTurn": true, @@ -727,6 +869,7 @@ describe('headless stream-json snapshots', () => { }, ], "waited": true, + "workflowStopReason": "completed", } `) }, 75_000) diff --git a/apps/web/tests/agent-team-panel.overlay.yml b/apps/web/tests/agent-team-panel.overlay.yml index 4db0a50c29..cb6dd2decd 100644 --- a/apps/web/tests/agent-team-panel.overlay.yml +++ b/apps/web/tests/agent-team-panel.overlay.yml @@ -7,16 +7,10 @@ disabled: true - id: tool-subagent - config: - provider: spawn - toolName: subagent - backgroundMode: one-shot + disabled: true - id: tool-subagent-fork - config: - provider: fork - toolName: subagent_fork - backgroundMode: one-shot + disabled: true - insert: - id: agent-team diff --git a/apps/web/tests/expected/sidebar-subagent-activity/owner-running.expected.md b/apps/web/tests/expected/sidebar-subagent-activity/owner-running.expected.md index 7e1864ff58..62671cbb8b 100644 --- a/apps/web/tests/expected/sidebar-subagent-activity/owner-running.expected.md +++ b/apps/web/tests/expected/sidebar-subagent-activity/owner-running.expected.md @@ -2,5 +2,5 @@ - treeitem "workspace" [expanded]: - img - text: workspace - - treeitem "1 subagent running Delegate a background job. now" - treeitem "New Session" [selected] + - treeitem "1 subagent running Delegate a background job. now" diff --git a/apps/web/tests/message-actions.e2e.ts b/apps/web/tests/message-actions.e2e.ts index 5036683851..fb0f8b32ce 100644 --- a/apps/web/tests/message-actions.e2e.ts +++ b/apps/web/tests/message-actions.e2e.ts @@ -28,13 +28,14 @@ const SEED_ID = 'message-actions-web-e2e' const PROMPT = 'Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop.' const MID_TURN_TEXT = 'I will read both files before answering.' const SECOND_PROMPT = 'Now give the final answer.' +const NEXT_PROMPT = 'Keep this later input in the original conversation.' /** * Adapt the borrowed recording into response -> tools -> interrupted Think, - * followed by one ordinary completed response. The first response keeps + * followed by two ordinary completed responses. The first response keeps * copy/clock but is not a legal branch point; the second is the real turn tail. * @param raw - Recorded seeded-history JSONL. - * @returns A contiguous, closed two-turn fixture. + * @returns A contiguous, closed three-turn fixture. */ function completedTailFixture(raw: string): string { const decoded = parseSeedFixture(raw) @@ -137,6 +138,48 @@ function completedTailFixture(raw: string): string { }), at({ type: 'step/end', data: { turn: 2, step: 1 } }), at({ type: 'turn/end', data: { turn: 2, reason: { kind: 'completed' } } }), + at({ + type: 'agent/inbox/spliced', + data: { + target: 'next-turn', start: 0, + inserted: [{ + role: 'user', id: '{{message:100}}', source: { kind: 'user' }, + content: [{ type: 'text', text: NEXT_PROMPT }], + }], + }, + }), + at({ type: 'turn/start', data: { turn: 3 } }), + at({ + type: 'agent/inbox/spliced', + data: { target: 'next-turn', start: 0, removedCount: 1, inserted: [] }, + }), + at({ type: 'step/start', data: { turn: 3, step: 1 } }), + at({ + type: 'user/message', surfaceOp: 'append', + data: { + role: 'user', id: '{{message:100}}', source: { kind: 'user' }, + content: [{ type: 'text', text: NEXT_PROMPT }], + }, + }), + at({ + type: 'assistant/message', surfaceOp: 'append', + data: { + turn: 3, step: 1, + message: { + role: 'assistant', id: '{{message:101}}', + source: { kind: 'model', provider: 'deepseek-official', model: 'deepseek-v4-flash' }, + content: [{ type: 'text', text: 'ORIGINAL ONLY' }], + }, + stream: [ + { type: 'chunk', time: 0, chunk: { type: 'block-start', index: 0, blockType: 'text' } }, + { type: 'text-chunks', time0: 0, index: 0, dt: [], texts: ['ORIGINAL ONLY'] }, + { type: 'chunk', time: 0, chunk: { type: 'block-end', index: 0, block: { type: 'text', text: 'ORIGINAL ONLY' } } }, + { type: 'chunk', time: 0, chunk: { type: 'finish', reason: { kind: 'stop' } } }, + ], + }, + }), + at({ type: 'step/end', data: { turn: 3, step: 1 } }), + at({ type: 'turn/end', data: { turn: 3, reason: { kind: 'completed' } } }), ] return renderSeedFixture(decoded.headerLine, [...kept, ...tail]) } @@ -154,7 +197,7 @@ describe('web e2e: message IconActions and clocks on settled history', () => { await writeFile(join(sessionCwd, 'a.txt'), 'alpha\n') await writeFile(join(sessionCwd, 'b.txt'), 'beta\n') const raw = completedTailFixture(await readFile(SEED, 'utf8')) - expect(fixtureUserPrompts(raw), 'adapted seed must carry both prompts').toEqual([PROMPT, SECOND_PROMPT]) + expect(fixtureUserPrompts(raw), 'adapted seed must carry all prompts').toEqual([PROMPT, SECOND_PROMPT, NEXT_PROMPT]) expect(parseSeedFixture(raw).events.flatMap(event => event.type === 'request/header' ? [event.data.reason] : []), 'adapted seed must carry an unchanged resume header').toEqual(['initial', 'resume']) @@ -193,11 +236,11 @@ describe('web e2e: message IconActions and clocks on settled history', () => { await expect.poll(() => copyButtons.count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(4) await copyButtons.first().focus() const branchButtons = page.getByRole('button', { name: 'Branch into a new conversation' }) - await expect.poll(() => branchButtons.count(), { timeout: 5_000 }).toBe(2) + await expect.poll(() => branchButtons.count(), { timeout: 5_000 }).toBe(3) await expect.poll( () => branchButtons.evaluateAll(buttons => buttons.map(button => button.getAttribute('aria-disabled'))), { timeout: 5_000 }, - ).toEqual(['true', null]) + ).toEqual(['true', null, null]) await branchButtons.first().focus() await expect.poll(() => page.getByRole('tooltip').textContent(), { timeout: 5_000 }) .toBe('Available only on the last message of a completed turn') @@ -219,12 +262,18 @@ describe('web e2e: message IconActions and clocks on settled history', () => { it.skipIf(MODE === 'record')('forks through the settled-message and session-row actions', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-message-fork')) - // The last message action belongs to the completed second-turn assistant. - await page.getByRole('button', { name: 'Branch into a new conversation' }).last().click() + // The second answer is followed by another completed user turn in the source. + await page.getByRole('button', { name: 'Branch into a new conversation' }).nth(1).click() await expect.poll( () => scaffold.ctx.agents.list().find(agent => agent.session.header.parentSession === SessionId(SEED_ID)), { timeout: 15_000 }, ).toBeDefined() + const child = scaffold.ctx.agents.list().find(agent => + agent.session.header.parentSession === SessionId(SEED_ID))! + expect(child.inbox.nextTurn).toEqual([]) + expect(child.session.snapshotEvents().some(event => event.type === 'agent/inbox/spliced' + && event.data.inserted.some(message => message.content.some(part => + part.type === 'text' && part.text === NEXT_PROMPT)))).toBe(false) await expect.poll( () => page.locator('[role="treeitem"]').count(), { timeout: 10_000 }, diff --git a/apps/web/tests/workspace-recency.e2e.ts b/apps/web/tests/workspace-recency.e2e.ts new file mode 100644 index 0000000000..9b39bce6ce --- /dev/null +++ b/apps/web/tests/workspace-recency.e2e.ts @@ -0,0 +1,167 @@ +/** Recency and saved manual order through the shipped Web composition. */ +import { readFile } from 'node:fs/promises' +import { basename, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { chromium, type Browser, type Page, type WebSocketRoute } from 'playwright' +import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest' +import { + acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden, + launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold, +} from './scaffold.ts' +import { newEnglishPage, saveFailureShot } from './support.ts' + +const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/workspace-recency', import.meta.url)) +const SEED = fileURLToPath(new URL('../../../snapshots/web/seeded-history/session.v3.jsonl', import.meta.url)) +const MODE = webSnapshotMode() +const TITLES = ['Newest conversation', 'Middle conversation', 'Oldest conversation'] + +describe('web e2e: workspace recency', () => { + let scaffold: WebScaffold + let browser: Browser + let page: Page + let tripwire: ReturnType + let socket: WebSocketRoute + let holdWorkspace = false + let releaseWorkspace: (() => void) | undefined + + beforeAll(async () => { + scaffold = await launchWebScaffold({}) + const fixture = await readFile(SEED, 'utf8') + const workspace = await scaffold.ctx.workspaceRegistry.create(scaffold.workspaceCwd) + const now = Date.now() + const ids = [] + for (const [index, title] of TITLES.entries()) { + const id = await seedSession(scaffold, fixture, `workspace-recency-${index}`, undefined, { + createdAt: now - (index + 1) * 60_000, + }) + await workspace.attachSession(id) + await scaffold.ctx.sessionController.rename({ sessionId: id, title }) + ids.push(id) + } + browser = await chromium.launch() + page = await newEnglishPage(browser) + await page.routeWebSocket('**/api/remote.mux', (route) => { + socket = route + const server = route.connectToServer() + let heldStream: string | undefined + const pending: (string | Buffer)[] = [] + server.onMessage((message) => { + const frame = JSON.parse(String(message)) as { + type: string + streamId: string + value?: { type: string; value?: { archivedSessionIds?: string[] } } + } + if (holdWorkspace && frame.type === 'item' && frame.value?.type === 'baseline' + && frame.value.value?.archivedSessionIds !== undefined) { + heldStream = frame.streamId + releaseWorkspace = () => { + heldStream = undefined + for (const buffered of pending.splice(0)) route.send(buffered) + } + } + if (frame.streamId === heldStream) pending.push(message) + else route.send(message) + }) + }) + await page.clock.setFixedTime(now) + tripwire = watchConsole(page) + await page.addInitScript(({ account, ids }) => { + if (localStorage.getItem('dsh.workspace.view.v5') !== null) return + localStorage.setItem('dsh.sessions.current', JSON.stringify({ sessionId: ids[0] })) + localStorage.setItem('dsh.workspace.view.v5', JSON.stringify({ + groupBy: 'workspace', orderBy: 'updated', groupExpansion: { [account]: true }, + sessionOrderByAccount: { [account]: [...ids].reverse(), __flat_session_order__: [...ids].reverse() }, + })) + }, { account: workspace.id, ids }) + await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' }) + await page.waitForSelector('[class*="frame"]', { timeout: 30_000 }) + }) + + afterAll(async () => { + await browser?.close() + await scaffold?.close() + }) + + it('pauses current recency in Manual, preserves drags on reload, and resets on Last updated', async () => { + onTestFailed(() => saveFailureShot(page, 'web-e2e-workspace-recency')) + const titles = () => page.locator('[role="treeitem"]:not([aria-expanded]) [class*="title"]').allTextContents() + const pick = async (name: string): Promise => { + await page.getByRole('button', { name: 'View options' }).click() + await page.getByRole('menuitem', { name, exact: true }).click() + } + const captureSidebar = async (): Promise => { + await page.getByRole('button', { name: 'View options' }).hover() + await expect.poll(() => page.getByRole('button', { name: /^Session actions for / }).count()).toBe(0) + return captureStableAria(page, '[role="tree"][aria-label="Sessions"]', scaffold.workspaceCwd) + } + await expect.poll(titles).toEqual(TITLES) + await compareOrRefreshGolden( + join(SNAPSHOT_DIR, 'sidebar.expected.md'), + await captureSidebar(), MODE, + ) + await pick('Manual') + await expect.poll(titles).toEqual(TITLES) + await pick('Last updated') + await expect.poll(titles).toEqual(TITLES) + const source = page.getByRole('treeitem').filter({ has: page.getByText(TITLES[2]!, { exact: true }) }) + const target = page.getByRole('treeitem').filter({ has: page.getByText(TITLES[0]!, { exact: true }) }) + await source.dragTo(target, { targetPosition: { x: 30, y: 3 } }) + const dragged = [TITLES[2], TITLES[0], TITLES[1]] + await expect.poll(titles).toEqual(dragged) + const manualWarningStart = tripwire.warnings.length + await page.reload({ waitUntil: 'load' }) + await expect.poll(titles).toEqual(dragged) + acknowledgeReloadConnectionLoss(tripwire, manualWarningStart) + await pick('Last updated') + await expect.poll(titles).toEqual(TITLES) + await pick('In one list') + await expect.poll(titles).toEqual(TITLES) + const recencyWarningStart = tripwire.warnings.length + await page.reload({ waitUntil: 'load' }) + await expect.poll(titles).toEqual(TITLES) + acknowledgeReloadConnectionLoss(tripwire, recencyWarningStart) + await pick('Manual') + await expect.poll(titles).toEqual(TITLES) + await pick('Last updated') + await pick('WorkSpace') + await pick('Manual') + const reconnectWarningStart = tripwire.warnings.length + holdWorkspace = true + await socket.close() + await expect.poll(() => releaseWorkspace !== undefined).toBe(true) + const workspaceTitle = basename(scaffold.workspaceCwd) + await page.getByRole('treeitem').filter({ has: page.getByText(workspaceTitle, { exact: true }) }).hover() + await page.getByRole('button', { name: `New session in ${workspaceTitle}` }).click() + await pick('In one list') + await expect.poll(titles).toEqual(['New Session', ...TITLES]) + await expect.poll(() => page.evaluate(() => { + const { sessionId } = JSON.parse(localStorage.getItem('dsh.sessions.current')!) as { sessionId: string } + const { sessionOrderByAccount } = JSON.parse(localStorage.getItem('dsh.workspace.view.v5')!) as { + sessionOrderByAccount: Record + } + return sessionOrderByAccount.__flat_session_order__?.[0] === sessionId + })).toBe(true) + holdWorkspace = false + releaseWorkspace!() + await pick('WorkSpace') + acknowledgeReloadConnectionLoss(tripwire, reconnectWarningStart) + await expect.poll(titles).toEqual(['New Session', ...TITLES]) + await compareOrRefreshGolden( + join(SNAPSHOT_DIR, 'manual-blank.expected.md'), + await captureSidebar(), MODE, + ) + const blank = page.getByRole('treeitem').filter({ has: page.getByText('New Session', { exact: true }) }) + await expect.poll(() => blank.getAttribute('draggable')).toBe('false') + const blankWarningStart = tripwire.warnings.length + await page.reload({ waitUntil: 'load' }) + await expect.poll(titles).toEqual(['New Session', ...TITLES]) + acknowledgeReloadConnectionLoss(tripwire, blankWarningStart) + await pick('Last updated') + await expect.poll(titles).toEqual(['New Session', ...TITLES]) + await pick('Manual') + await expect.poll(titles).toEqual(['New Session', ...TITLES]) + await assertFixtureInventory(SNAPSHOT_DIR, ['sidebar.expected.md', 'manual-blank.expected.md']) + expect(tripwire.pageErrors).toEqual([]) + expect(tripwire.warnings).toEqual([]) + }) +}) diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 33278a9b60..552712faf8 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -57,6 +57,7 @@ "tests/remote-welcome.e2e.ts", "tests/workspace-new-session-folding.e2e.ts", "tests/workspace-management.e2e.ts", + "tests/workspace-recency.e2e.ts", "tests/replay-round-trip.e2e.ts", "tests/hmr-live.e2e.ts", "tests/preview-boot.e2e.ts", diff --git a/benchmarks/agent-continuation/README.i18n.yaml b/benchmarks/agent-continuation/README.i18n.yaml index c13ad6f35d..3e8b5a92e3 100644 --- a/benchmarks/agent-continuation/README.i18n.yaml +++ b/benchmarks/agent-continuation/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write benchmarks/agent-continuation/README.md -README.md: 489939499af8d98922df2cbbdd6be793bf6e3796 -README.zh.md: e51fac2d0df005de9d609cb634778f30b537ae41 +README.md: 98a8f837b9021ee7efbf40c2275362185a215ca9 +README.zh.md: 1b7d0d3efd9b8ee4be8f4acf48fc97af11d3fd3e diff --git a/benchmarks/agent-continuation/README.md b/benchmarks/agent-continuation/README.md index 489939499a..98a8f837b9 100644 --- a/benchmarks/agent-continuation/README.md +++ b/benchmarks/agent-continuation/README.md @@ -18,7 +18,7 @@ Measure long-history request processing, cold tool-heavy continuation, and repea From the repository root, build the libraries and workers with `pnpm run build:bench`, then run `pnpm exec vitest run --config vitest.bench.config.ts benchmarks/agent-continuation/agent-continuation.bench.ts`. Do not overlap timing runs with builds or other benchmarks. -The test reports all five fresh-process samples, CPU models, available parallelism, platform/architecture, and Node/V8 versions, and enforces reviewed median budgets. Catalog and tool continuation each use a 900 ms standard hosted CI expectation with 1.25× headroom (1,125 ms); request history uses a separately reviewed 297 ms hosted limit ([calibration](../../.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.md)), and SDK continuation uses reference-machine scaling. A failed worker reports its exit, signal, timeout, and stderr; temporary roots are removed even on failure. The required benchmark lane discovers this file automatically. +The test reports all five fresh-process samples, CPU models, available parallelism, platform/architecture, and Node/V8 versions, and enforces reviewed median budgets. Catalog and tool continuation each use a 900 ms standard hosted CI expectation with 1.25× headroom (1,125 ms); request history uses a separately reviewed 297 ms hosted limit ([evidence](../../.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md)), and SDK continuation uses reference-machine scaling. A failed worker reports its exit, signal, timeout, and stderr; temporary roots are removed even on failure. The required benchmark lane discovers this file automatically. diff --git a/benchmarks/agent-continuation/README.zh.md b/benchmarks/agent-continuation/README.zh.md index e51fac2d0d..1b7d0d3efd 100644 --- a/benchmarks/agent-continuation/README.zh.md +++ b/benchmarks/agent-continuation/README.zh.md @@ -18,7 +18,7 @@ 在仓库根目录使用 `pnpm run build:bench` 构建库和 worker,然后运行 `pnpm exec vitest run --config vitest.bench.config.ts benchmarks/agent-continuation/agent-continuation.bench.ts`。不要让计时运行与构建或其他基准重叠。 -测试报告全部五个新进程样本、CPU 型号、可用并行度、平台/架构和 Node/V8 版本,并约束经审查的中位数预算。目录和工具续聊用例均使用标准托管 CI 的 900 ms 期望值与 1.25× 余量(1,125 ms);请求历史使用单独审查的 297 ms 托管上限([校准依据](../../.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.zh.md)),SDK 续聊使用参考机器缩放。worker 失败时报告退出状态、信号、超时和 stderr;失败时也会删除临时根目录。必需基准通道自动发现此文件。 +测试报告全部五个新进程样本、CPU 型号、可用并行度、平台/架构和 Node/V8 版本,并约束经审查的中位数预算。目录和工具续聊用例均使用标准托管 CI 的 900 ms 期望值与 1.25× 余量(1,125 ms);请求历史使用单独审查的 297 ms 托管上限([证据](../../.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.zh.md)),SDK 续聊使用参考机器缩放。worker 失败时报告退出状态、信号、超时和 stderr;失败时也会删除临时根目录。必需基准通道自动发现此文件。 diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 7c345ecac8..c42006edcc 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -24,6 +24,7 @@ Each fact has one home: the tier whose job it is; elsewhere, link there. | [subsystems/](subsystems/README.md) | One reference page per subsystem: type definitions, semantics, and the generated Cordis API | Behavior narration (→ architecture.md) | | [Agent Notes](../.agents/notes/README.md) | Active decision records: the why, what-was-given-up, and required verification; `implemented/` notes describe shipped reality in present tense | Migration plans, acceptance-task checklists, fixture walkthroughs, and spec-speak ("should…") once the decision has shipped; archived notes are frozen history, never current authority | | [postmortem/](postmortem/README.md) | Incident stories — the only tier where war-story narrative belongs | — | +| [persistence-changes/](persistence-changes/README.md) | Historical acknowledgements of mechanically detected persistence-type changes, with generated schema companions | Behavior-only changes; current runtime contracts | | [cookbook/](cookbook/adding-a-package.md) | Step-by-step how-tos with numbered verify steps | Design rationale (→ the Agent Note each guide links) | | [user/](user/index.md) | Product-facing guides published by the documentation website | Generated reference tables, contributor procedures, decision history | | Package README | The per-package contract: config, semantics, limitations, extension points, and [Model Experience](cookbook/adding-a-package.md#4-write-the-package-readme) | JSDoc restatement, generated-catalog restatement (event/tool tables), other packages' concerns | @@ -35,7 +36,7 @@ Placement: bugs → postmortems; rationale → Agent Notes; procedures → cookb ## Writing rules -- **Document current state, not change history.** Name live mechanisms, not PRs, commits, stack positions, or "previously/now/no longer". Keep history in commits, PRs, Agent Notes, or postmortems. General Session-format prose links [version/status authority](session-format-status.md); retain numbers for version-specific contracts, examples, or evidence. +- **Document current state.** Keep history in commits, PRs, Agent Notes, postmortems, or scoped `persistence-change` records. Other prose names live mechanisms, not changes or stack positions. General Session-format prose links [version/status authority](session-format-status.md); retain numbers for version-specific contracts, examples, or evidence. - **Every non-trivial change includes at least one Agent Note in the same PR.** Update the owning note or add one; only mechanical/local edits are exempt ([scope](../.agents/notes/README.md#when-to-write-one)). - **One physical line per paragraph** (`verify-md-wrap`): use editor soft-wrap. Code blocks, tables, and list structure keep their formatting; code comments stay under the linter's column limit. - **Fenced `ts` blocks must compile** (`doc-typecheck`); a pasted type declaration and its original JSDoc use ` ```ts type-equiv `, while a body-stripped public class declaration uses ` ```ts public-api `; register either in the manifest so neither can drift ([mechanics](development.md#documenting-types-verbatim-ts-type-equiv)). @@ -60,8 +61,8 @@ Ceilings are guardrails, not reduction targets. At or below target, retain at le Hunt these in any doc; [dsh-doc](../.agents/skills/dsh-doc/SKILL.md) runs this list as an audit: -- The same rule stated in more than one home. Grep a distinctive phrase; keep one home and link the rest. -- Narrated history or war stories: "previously", "now", "no longer", "used to", "renamed", "was moved", PRs, or commits. State the current fact; link an Agent Note or postmortem when needed. +- Duplicated rules: search a distinctive phrase; keep one home and link the rest. +- History outside its permitted tier: state current facts and link the historical owner. - Implementation-status annotations in prose or diagrams ("implemented!", "future: …"). Status rots; the repo layout and package manifests carry it. - Hand-restated catalogs, JSDoc, or inventories of tests, packages, and status when source or a generator is authoritative. - Reasoning transcripts: step-by-step implementation narration, proof of obvious branches, test walkthroughs, or rejected local alternatives. Keep the resulting contract or durable rationale; delete the path used to derive it. diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index e71254886c..799699d836 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/architecture.md -architecture.md: 2a3c291c5acb7e94bd1f666609af901e928671bc +architecture.md: 4c8b78554cd744838a26da9bde39a405b275b739 architecture.zh.md: 0eea56b18ae79b297178dfb831ce78e5aed66699 diff --git a/docs/architecture.md b/docs/architecture.md index 2a3c291c5a..4c8b78554c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -108,7 +108,7 @@ Input reaches the driver through one inbox. Some messages wake it immediately; i `agent/pre-step` decides the accepted input. Listeners may rewrite or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, `agent/request` and `prepareCall()` resolve the actual route before the system prompt and accepted users are committed; cancellation during either async phase commits neither. The prepared call capability governs prompt admission, not the preceding `request/context`. Every attempt synchronously reconciles the same rendered assembly, appends users only on the first attempt, logs header/context as needed, and derives and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The first admitted step reserves the system head before user messages even for an empty prompt (no wire message). The prompt travels only as `system/message` history: an empty rendering clears all active system nodes, leaving no old prompt model-visible; capable routes can append non-empty updates after the cached prefix; incapable routes and new request series consolidate non-empty prompt text at the first system node, with logged empty replacements for non-empty later system nodes ([decision](../.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md); [decision rule](../packages/core/agent-loop/README.md#understand-the-implementation)). -The loop sends immutable requests while keeping cancellation live. It reuses message-freeze provenance only for identities it has fully frozen; [agent-loop](../packages/core/agent-loop/README.md) owns the request construction rules. +The loop sends immutable requests while keeping cancellation live. It reuses message-freeze evidence only for identities it has fully frozen; [agent-loop](../packages/core/agent-loop/README.md) owns the request construction rules. Details: the [sequence diagram](agent-lifecycle.md), the [tool pipeline](tool-execution-pipeline.md), and [cancellation and error recovery](subsystems/core.md#the-agent-handle). diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml index ab602f097d..692f58e674 100644 --- a/docs/capability-seams.i18n.yaml +++ b/docs/capability-seams.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/capability-seams.md -capability-seams.md: 9cc7a6fc50b81e9f82d58600c84d1ff6494634c9 -capability-seams.zh.md: e898bdf3c2c728a1bae427bbb737eb5269a1c500 +capability-seams.md: 547414830411d1d80781c614e75d839f8a9547fb +capability-seams.zh.md: 8b89c0240a9881625f3d6a65bb0f1a31e887e1ef diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 9cc7a6fc50..5474148304 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -133,10 +133,6 @@ flowchart LR pkg_sdk_minimal["sdk-minimal"] pkg_goal["goal"] svc_goals["ctx.goals
Same-session goal domain"] - pkg_e2b["e2b"] - svc_e2b["ctx.e2b
E2B sandbox lifecycle owner"] - pkg_fs_e2b["fs-e2b"] - pkg_subprocess_e2b["subprocess-e2b"] pkg_subprocess["subprocess"] svc_subprocess["ctx.subprocess
Subprocess seam"] pkg_subprocess_local["subprocess-local"] @@ -256,13 +252,11 @@ flowchart LR pkg_credentials --> svc_credentials pkg_credentials_local --> svc_credentials pkg_deepseek_llm_api_extensions --> svc_deepseekLlmApiExtensions - pkg_e2b --> svc_e2b pkg_experimental_agent_team --> svc_agentTeams pkg_experimental_code_runtime_python --> svc_codeRuntime pkg_file_reference --> svc_fileReferences pkg_file_reference_local --> svc_fileReferences pkg_fs --> svc_fs - pkg_fs_e2b --> svc_fs pkg_fs_local --> svc_fs pkg_fs_sandbox --> svc_fs pkg_goal --> svc_goals @@ -323,7 +317,6 @@ flowchart LR pkg_subagent_fork_in_process --> svc_subagents pkg_subagent_spawn_in_process --> svc_subagents pkg_subprocess --> svc_subprocess - pkg_subprocess_e2b --> svc_subprocess pkg_subprocess_local --> svc_subprocess pkg_system_prompt --> svc_systemPrompt pkg_terminal --> svc_terminals @@ -370,8 +363,6 @@ flowchart LR svc_deepseekLlmApiExtensions --> pkg_llm_deepseek svc_directoryPicker --> pkg_api_workspace_controller svc_dynamicCordisRunner --> pkg_tool_cordis - svc_e2b --> pkg_fs_e2b - svc_e2b --> pkg_subprocess_e2b svc_fileReferences --> pkg_api_session_controller svc_fileUploads --> pkg_api_session_controller svc_fs --> pkg_tool_fs @@ -520,8 +511,7 @@ flowchart LR | `ctx.agentDefaultModel` | `core` | [`agent-default-model`](../packages/core/agent-default-model) | - | [`api-session-controller`](../packages/api/session-controller), [`headless`](../packages/bundle/headless) | - | Layers the default ModelSelection through settings so direct and Host-backed Agent entry points share one state owner. | | `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`base`](../packages/bundle/base), [`sdk-minimal`](../packages/bundle/sdk-minimal) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. | | `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | Folds revisioned objective state from the session log and keeps live continuation activation process-local. | -| `ctx.e2b` | `core` | [`e2b`](../packages/e2b/e2b) | - | [`fs-e2b`](../packages/e2b/fs-e2b), [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | - | Owns one shared E2B SDK handle, remote working directory, and final sandbox disposition so both fundamental E2B providers inhabit the same Linux runtime. | -| `ctx.subprocess` | `seam` | [`subprocess`](../packages/subprocess/subprocess) | [`subprocess-local`](../packages/subprocess/subprocess-local), [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash), [`lsp-stdio`](../packages/lsp/lsp-stdio), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | - | The bash executors, the PTY shell backend, the LSP host, and the out-of-process ACP, Codex, and Claude Code subagent backends spawn through ctx.subprocess; the service owns process coordinates, tree/session lifetime, stdio dispositions, terminal mechanics, and kill escalation. | +| `ctx.subprocess` | `seam` | [`subprocess`](../packages/subprocess/subprocess) | [`subprocess-local`](../packages/subprocess/subprocess-local) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash), [`lsp-stdio`](../packages/lsp/lsp-stdio), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | - | The bash executors, the PTY shell backend, the LSP host, and the out-of-process ACP, Codex, and Claude Code subagent backends spawn through ctx.subprocess; the service owns process coordinates, tree/session lifetime, stdio dispositions, terminal mechanics, and kill escalation. | | `ctx.shell` | `seam` | [`shell`](../packages/shell/shell) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`pwsh-local`](../packages/shell/pwsh-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) | - | The model-facing shell tools and hook bridges consume this seam; sandboxed, remote, or PowerShell executors replace bash-local without touching them. | | `ctx.shellEnv` | `core` | [`shell-env`](../packages/shell/shell-env) | - | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh) | - | Plugins declare effect-scoped DSH_* facts; each shell tool collects one trusted snapshot per execution and its executor rebuilds the namespace. | | `ctx.terminals` | `seam` | [`terminal`](../packages/terminal/terminal) | [`terminal-bash`](../packages/terminal/terminal-bash) | [`tool-terminal`](../packages/terminal/tool-terminal) | - | The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-terminal exposes the owner-scoped model tools. | @@ -530,7 +520,7 @@ flowchart LR | `ctx.approval` | `seam` | [`user-approval`](../packages/interaction/user-approval) | - | [`tools`](../packages/core/tools), [`tool-bash`](../packages/shell/tool-bash), [`acp`](../packages/acp/acp) | - | One-shot permission decisions dispatched over the `approval/request` waterfall; answerers are listeners (the ACP bridge for its own agents), absence fails closed to `unavailable`. | | `ctx.permissionPresets` | `core` | [`permission-presets`](../packages/interaction/permission-presets) | - | - | - | User-facing preset table (`workspace-write`/`danger-full-access`) bundling the sandbox-mode and approval-policy knobs; a switch writes one `permission/preset` event through to both knob events. | | `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | [`code-runtime-worker-thread`](../packages/code-runtime/code-runtime-worker-thread), [`experimental-code-runtime-python`](../packages/experimental/code-runtime-python) | [`tools`](../packages/core/tools) | - | Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the tool registry consumes it for PTC mode). | -| `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-e2b`](../packages/e2b/fs-e2b) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-observation-policy contributes observed-state checks through the fs/* event gate. | +| `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-observation-policy contributes observed-state checks through the fs/* event gate. | | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; there is no model-facing compact tool. | | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | Providers implement transports; the service also owns optional Activation-based continuation orchestration, tool-subagent selects one-shot or continuable delegation, tool-subagent-control delivers follow-ups, and tool-ralph requires one fresh structured-output route. | | `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team), [`experimental-client-ui-agent-team`](../packages/experimental/client-ui-agent-team) | - | Owns the implicit-root roster, durable peer mailbox, shared task DAG, continuable-child lifecycle, and generated Team Remote methods; tool-agent-team contributes model controls and client-ui-agent-team mounts the browser contribution. | diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index e898bdf3c2..8b89c0240a 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -135,10 +135,6 @@ flowchart LR pkg_sdk_minimal["sdk-minimal"] pkg_goal["goal"] svc_goals["ctx.goals
Same-session goal domain"] - pkg_e2b["e2b"] - svc_e2b["ctx.e2b
E2B sandbox lifecycle owner"] - pkg_fs_e2b["fs-e2b"] - pkg_subprocess_e2b["subprocess-e2b"] pkg_subprocess["subprocess"] svc_subprocess["ctx.subprocess
Subprocess seam"] pkg_subprocess_local["subprocess-local"] @@ -258,13 +254,11 @@ flowchart LR pkg_credentials --> svc_credentials pkg_credentials_local --> svc_credentials pkg_deepseek_llm_api_extensions --> svc_deepseekLlmApiExtensions - pkg_e2b --> svc_e2b pkg_experimental_agent_team --> svc_agentTeams pkg_experimental_code_runtime_python --> svc_codeRuntime pkg_file_reference --> svc_fileReferences pkg_file_reference_local --> svc_fileReferences pkg_fs --> svc_fs - pkg_fs_e2b --> svc_fs pkg_fs_local --> svc_fs pkg_fs_sandbox --> svc_fs pkg_goal --> svc_goals @@ -325,7 +319,6 @@ flowchart LR pkg_subagent_fork_in_process --> svc_subagents pkg_subagent_spawn_in_process --> svc_subagents pkg_subprocess --> svc_subprocess - pkg_subprocess_e2b --> svc_subprocess pkg_subprocess_local --> svc_subprocess pkg_system_prompt --> svc_systemPrompt pkg_terminal --> svc_terminals @@ -372,8 +365,6 @@ flowchart LR svc_deepseekLlmApiExtensions --> pkg_llm_deepseek svc_directoryPicker --> pkg_api_workspace_controller svc_dynamicCordisRunner --> pkg_tool_cordis - svc_e2b --> pkg_fs_e2b - svc_e2b --> pkg_subprocess_e2b svc_fileReferences --> pkg_api_session_controller svc_fileUploads --> pkg_api_session_controller svc_fs --> pkg_tool_fs @@ -522,8 +513,7 @@ flowchart LR | `ctx.agentDefaultModel` | `core` | [`agent-default-model`](../packages/core/agent-default-model) | - | [`api-session-controller`](../packages/api/session-controller), [`headless`](../packages/bundle/headless) | - | 通过 settings 分层默认 `ModelSelection`,让直接入口与 Host 支撑的 Agent 入口共享同一个状态所有者。 | | `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`base`](../packages/bundle/base), [`sdk-minimal`](../packages/bundle/sdk-minimal) | - | 唯一的具体循环插件;扩展包依赖 dsh-agent 的事件和服务,而不依赖此包。 | | `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | 从会话日志折叠带修订版本的目标状态,并将实时延续激活保留在进程本地。 | -| `ctx.e2b` | `core` | [`e2b`](../packages/e2b/e2b) | - | [`fs-e2b`](../packages/e2b/fs-e2b), [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | - | 拥有一个共享的 E2B SDK 句柄、远程工作目录和最终沙箱处置,使两个基础 E2B 提供方处于同一个 Linux 运行时中。 | -| `ctx.subprocess` | `seam` | [`subprocess`](../packages/subprocess/subprocess) | [`subprocess-local`](../packages/subprocess/subprocess-local), [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash), [`lsp-stdio`](../packages/lsp/lsp-stdio), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | - | Bash 执行器、PTY shell 后端、LSP Host,以及进程外 ACP、Codex 和 Claude Code subagent 后端都通过 ctx.subprocess 执行 spawn;该服务负责进程坐标、进程树/会话生命周期、stdio 处置、终端机制和 kill 升级。 | +| `ctx.subprocess` | `seam` | [`subprocess`](../packages/subprocess/subprocess) | [`subprocess-local`](../packages/subprocess/subprocess-local) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash), [`lsp-stdio`](../packages/lsp/lsp-stdio), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | - | Bash 执行器、PTY shell 后端、LSP Host,以及进程外 ACP、Codex 和 Claude Code subagent 后端都通过 ctx.subprocess 执行 spawn;该服务负责进程坐标、进程树/会话生命周期、stdio 处置、终端机制和 kill 升级。 | | `ctx.shell` | `seam` | [`shell`](../packages/shell/shell) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`pwsh-local`](../packages/shell/pwsh-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) | - | 面向模型的 shell 工具和钩子桥接消费此 seam;沙箱、远程或 PowerShell 执行器可以替换 bash-local,而无需改动这些消费方。 | | `ctx.shellEnv` | `core` | [`shell-env`](../packages/shell/shell-env) | - | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh) | - | 插件声明限定于 effect 作用域的 DSH_* 事实;每个 shell 工具在每次执行时收集一份可信快照,其执行器据此重建命名空间。 | | `ctx.terminals` | `seam` | [`terminal`](../packages/terminal/terminal) | [`terminal-bash`](../packages/terminal/terminal-bash) | [`tool-terminal`](../packages/terminal/tool-terminal) | - | 注册表负责精确到 Agent 的会话身份和清理;后端负责终端机制,tool-terminal 则提供限定于所有者作用域的模型接口。 | @@ -532,7 +522,7 @@ flowchart LR | `ctx.approval` | `seam` | [`user-approval`](../packages/interaction/user-approval) | - | [`tools`](../packages/core/tools), [`tool-bash`](../packages/shell/tool-bash), [`acp`](../packages/acp/acp) | - | 一次性权限决策通过 `approval/request` waterfall(瀑布式事件)分派;回答方是监听器(即 ACP 为自身 agent 提供的桥接),没有回答方时以 `unavailable` 关闭失败。 | | `ctx.permissionPresets` | `core` | [`permission-presets`](../packages/interaction/permission-presets) | - | - | - | 面向用户的预设表(`workspace-write`/`danger-full-access`),将沙箱模式与审批策略选项组合在一起;一次切换会写入一个 `permission/preset` 事件,并贯通到两个选项事件。 | | `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | [`code-runtime-worker-thread`](../packages/code-runtime/code-runtime-worker-thread), [`experimental-code-runtime-python`](../packages/experimental/code-runtime-python) | [`tools`](../packages/core/tools) | - | 使用 Host 提供的异步绑定运行一段由模型编写的程序;各后端采用不同的基础环境和语言(工具注册表在 PTC mode 下消费该服务)。 | -| `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-e2b`](../packages/e2b/fs-e2b) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs 通过 ctx.fs 执行读取/写入/编辑;fs-sandbox 按共享沙箱模式限制变更;fs-observation-policy 通过 fs/* 事件门禁贡献基于观测状态的检查。 | +| `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs 通过 ctx.fs 执行读取/写入/编辑;fs-sandbox 按共享沙箱模式限制变更;fs-observation-policy 通过 fs/* 事件门禁贡献基于观测状态的检查。 | | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 基础后端消费步骤后的压力事件和请求错误恢复事件;不存在面向模型的压缩工具。 | | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 提供方实现传输;该服务还负责可选的、基于 Activation 的延续编排,tool-subagent 选择一次性或可延续委派,tool-subagent-control 传递后续消息,而 tool-ralph 要求一条全新的结构化输出路由。 | | `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team), [`experimental-client-ui-agent-team`](../packages/experimental/client-ui-agent-team) | - | 负责隐式 Root roster、持久 peer mailbox、共享任务 DAG、continuable child 生命周期与生成式 Team Remote method;tool-agent-team 提供模型控制工具,client-ui-agent-team 挂载浏览器 contribution。 | diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index 2fabd98259..6d93653a53 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/config-catalog.md -config-catalog.md: c74e345f819eddf4743201b8ec4f6c9e16d23cde -config-catalog.zh.md: b4332f7307cf41962dd40c36daaa2925cb774946 +config-catalog.md: 1cae88929488b55cae52fc5acbbcf1fd1158b5b6 +config-catalog.zh.md: 73c2dad801ab04fe194c4d702e3b9d51b10c3d41 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index c74e345f81..1cae889294 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -540,24 +540,6 @@ export interface Config { Source: [`packages/credentials/credentials-local/src/index.ts:64`](../packages/credentials/credentials-local/src/index.ts) - - -## `@deepseek-ai/dsh-e2b` - -```ts config-catalog -/** Configuration for the shared E2B sandbox owner. */ -export interface Config { - /** API key; omission reads `E2B_API_KEY`. It is never forwarded into the sandbox. */ - apiKey?: string - /** Shared remote working directory, created before adapters receive the sandbox. */ - cwd?: string - /** E2B sandbox lifetime in milliseconds; expiry always deletes the sandbox. */ - timeoutMs?: number -} -``` - -Source: [`packages/e2b/e2b/src/index.ts:45`](../packages/e2b/e2b/src/index.ts) - ## `@deepseek-ai/dsh-experimental-agent-team` @@ -817,14 +799,18 @@ Source: [`packages/goal/goal/src/index.ts:172`](../packages/goal/goal/src/index. Requires: `agentDefaultModel` · `agents` · `sessions` ```ts config-catalog -/** Plugin config: the task resolved from this app's injected provider service. */ +/** Plugin config: the task and run options resolved from this app's injected provider service. */ export interface Config { - /** The prompt text for the single run. */ - task: string + /** The prompt text for the single run; absent when the task arrives on stdin. */ + task?: string + /** Exact Session identity to adopt; absent for a fresh random identity. An id with no stored Session fails. */ + sessionId?: string + /** Whether stdout carries the machine-readable event stream instead of final text. */ + json?: boolean } ``` -Source: [`packages/bundle/headless/src/index.ts:34`](../packages/bundle/headless/src/index.ts) +Source: [`packages/bundle/headless/src/index.ts:41`](../packages/bundle/headless/src/index.ts) @@ -2571,22 +2557,6 @@ export interface Config { Source: [`packages/subagent/subagent-spawn-in-process/src/index.ts:25`](../packages/subagent/subagent-spawn-in-process/src/index.ts) - - -## `@deepseek-ai/dsh-subprocess-e2b` - -Requires: `e2b` - -```ts config-catalog -/** Configuration for the E2B subprocess adapter. */ -export interface Config { - /** Remote status/liveness poll cadence in milliseconds; each tick is one control-plane request. */ - pollMs?: number -} -``` - -Source: [`packages/e2b/subprocess-e2b/src/index.ts:26`](../packages/e2b/subprocess-e2b/src/index.ts) - ## `@deepseek-ai/dsh-system-prompt` @@ -3510,7 +3480,6 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co - `@deepseek-ai/dsh-deepseek-llm-api-extensions` ([`packages/llm/deepseek-llm-api-extensions/src/index.ts`](../packages/llm/deepseek-llm-api-extensions/src/index.ts)) - `@deepseek-ai/dsh-experimental-auto-review` — requires `llm` · `permissionPresets` · `sessions` · `tools` ([`packages/experimental/auto-review/src/index.ts`](../packages/experimental/auto-review/src/index.ts)) - `@deepseek-ai/dsh-experimental-client-ui-agent-team` ([`packages/experimental/client-ui-agent-team/src/index.ts`](../packages/experimental/client-ui-agent-team/src/index.ts)) -- `@deepseek-ai/dsh-fs-e2b` — requires `e2b` ([`packages/e2b/fs-e2b/src/index.ts`](../packages/e2b/fs-e2b/src/index.ts)) - `@deepseek-ai/dsh-fs-observation-policy` ([`packages/fs/fs-observation-policy/src/index.ts`](../packages/fs/fs-observation-policy/src/index.ts)) - `@deepseek-ai/dsh-goal-round-driver` — requires `agents` · `goals` · `sessions` ([`packages/goal/goal-round-driver/src/index.ts`](../packages/goal/goal-round-driver/src/index.ts)) - `@deepseek-ai/dsh-host-directory-picker-auto` — requires `webServer` · `loader` ([`packages/host/directory-picker-auto/src/index.ts`](../packages/host/directory-picker-auto/src/index.ts)) diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index b4332f7307..73c2dad801 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -542,24 +542,6 @@ export interface Config { 来源:[`packages/credentials/credentials-local/src/index.ts:64`](../packages/credentials/credentials-local/src/index.ts) - - -## `@deepseek-ai/dsh-e2b` - -```ts config-catalog -/** Configuration for the shared E2B sandbox owner. */ -export interface Config { - /** API key; omission reads `E2B_API_KEY`. It is never forwarded into the sandbox. */ - apiKey?: string - /** Shared remote working directory, created before adapters receive the sandbox. */ - cwd?: string - /** E2B sandbox lifetime in milliseconds; expiry always deletes the sandbox. */ - timeoutMs?: number -} -``` - -来源:[`packages/e2b/e2b/src/index.ts:45`](../packages/e2b/e2b/src/index.ts) - ## `@deepseek-ai/dsh-experimental-agent-team` @@ -819,14 +801,18 @@ export interface Config { 需要:`agentDefaultModel` · `agents` · `sessions` ```ts config-catalog -/** Plugin config: the task resolved from this app's injected provider service. */ +/** Plugin config: the task and run options resolved from this app's injected provider service. */ export interface Config { - /** The prompt text for the single run. */ - task: string + /** The prompt text for the single run; absent when the task arrives on stdin. */ + task?: string + /** Exact Session identity to adopt; absent for a fresh random identity. An id with no stored Session fails. */ + sessionId?: string + /** Whether stdout carries the machine-readable event stream instead of final text. */ + json?: boolean } ``` -来源:[`packages/bundle/headless/src/index.ts:34`](../packages/bundle/headless/src/index.ts) +来源:[`packages/bundle/headless/src/index.ts:41`](../packages/bundle/headless/src/index.ts) @@ -2573,22 +2559,6 @@ export interface Config { 来源:[`packages/subagent/subagent-spawn-in-process/src/index.ts:25`](../packages/subagent/subagent-spawn-in-process/src/index.ts) - - -## `@deepseek-ai/dsh-subprocess-e2b` - -需要:`e2b` - -```ts config-catalog -/** Configuration for the E2B subprocess adapter. */ -export interface Config { - /** Remote status/liveness poll cadence in milliseconds; each tick is one control-plane request. */ - pollMs?: number -} -``` - -来源:[`packages/e2b/subprocess-e2b/src/index.ts:26`](../packages/e2b/subprocess-e2b/src/index.ts) - ## `@deepseek-ai/dsh-system-prompt` @@ -3512,7 +3482,6 @@ export interface Config { - `@deepseek-ai/dsh-deepseek-llm-api-extensions`([`packages/llm/deepseek-llm-api-extensions/src/index.ts`](../packages/llm/deepseek-llm-api-extensions/src/index.ts)) - `@deepseek-ai/dsh-experimental-auto-review` — 需要 `llm` · `permissionPresets` · `sessions` · `tools`([`packages/experimental/auto-review/src/index.ts`](../packages/experimental/auto-review/src/index.ts)) - `@deepseek-ai/dsh-experimental-client-ui-agent-team`([`packages/experimental/client-ui-agent-team/src/index.ts`](../packages/experimental/client-ui-agent-team/src/index.ts)) -- `@deepseek-ai/dsh-fs-e2b` — 需要 `e2b`([`packages/e2b/fs-e2b/src/index.ts`](../packages/e2b/fs-e2b/src/index.ts)) - `@deepseek-ai/dsh-fs-observation-policy`([`packages/fs/fs-observation-policy/src/index.ts`](../packages/fs/fs-observation-policy/src/index.ts)) - `@deepseek-ai/dsh-goal-round-driver` — 需要 `agents` · `goals` · `sessions`([`packages/goal/goal-round-driver/src/index.ts`](../packages/goal/goal-round-driver/src/index.ts)) - `@deepseek-ai/dsh-host-directory-picker-auto` — 需要 `webServer` · `loader`([`packages/host/directory-picker-auto/src/index.ts`](../packages/host/directory-picker-auto/src/index.ts)) diff --git a/docs/cookbook/reviewing-persistence-type-changes.i18n.yaml b/docs/cookbook/reviewing-persistence-type-changes.i18n.yaml new file mode 100644 index 0000000000..67a9df2ffb --- /dev/null +++ b/docs/cookbook/reviewing-persistence-type-changes.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/cookbook/reviewing-persistence-type-changes.md +reviewing-persistence-type-changes.md: 881bc689af656f0c4976e909a49848e38d53c453 +reviewing-persistence-type-changes.zh.md: f1961013886cfab1abdbd7e2cd7e09bcfce2101d diff --git a/docs/cookbook/reviewing-persistence-type-changes.md b/docs/cookbook/reviewing-persistence-type-changes.md new file mode 100644 index 0000000000..881bc689af --- /dev/null +++ b/docs/cookbook/reviewing-persistence-type-changes.md @@ -0,0 +1,108 @@ +--- +description: "Generate, acknowledge, and verify Session persistence-type changes locally before opening a pull request." +--- + +# Cookbook: reviewing persistence-type changes + +English | [中文](reviewing-persistence-type-changes.zh.md) + +## Summary + +Use this tutorial after changing a declared Session persistence type in a contributor checkout with dependencies installed. Supply a bilingual compatibility explanation, then let one command classify the change and generate its records. The [record reference](../persistence-changes/README.md) explains the files and automatic rules. All comparison inputs live in the checkout; no base branch or network access is required. + +## Table of Contents + +- [Optional: inspect the change](#generate) +- [1. Record the change](#acknowledge) +- [2. Check, commit, and push](#verify) +- [Update an unaccepted record](#competing-records) +- [Dev Note](#dev-note) + +----- + + +## Optional: inspect the change + +For a preview before recording, run from the repository root: + +```sh +pnpm --silent run verify-persistence-changes --json +``` + +Use `--silent` when consuming JSON: pnpm otherwise appends lifecycle failure text to stdout. Failed commands still exit with code 1. + +Read the reported root, path, change kind, and version requirement. A referenced type can affect several event digests; inspect every affected root. Until the history covers the new schemas, verification fails. A stale generated inventory also fails verification; the recording command refreshes it. If `changes` is empty after reordering fields or union alternatives, run `pnpm run gen-persistence-catalog` and repeat the check. An unchanged digest needs no new acknowledgement even when copied declarations or source locations produce a catalog diff. + + +## 1. Record the change + +Write a local JSON file containing `en` and `zh`, each with `summary`, `compatibility`, and `verification` strings. The following input describes an exercised required-to-optional hook audit field change. Replace the explanation and test evidence with facts about your change; the CLI does not establish these claims. + +Save the input as `.artifacts/persistence-change.prose.json`, creating the ignored directory if needed: + +```json +{ + "en": { + "summary": "Makes the persisted hook audit decision optional.", + "compatibility": "Existing records remain valid. Hook execution consumes HookOutput instead of replaying this audit field. Producers still write decisions, and absence does not imply pass.", + "verification": "pnpm exec vitest run packages/hooks/hook-protocol/tests/events.spec.ts: 10 tests passed." + }, + "zh": { + "summary": "将持久化的钩子审计决策改为可选。", + "compatibility": "已有记录仍然有效。钩子执行消费 HookOutput,不回放此审计字段。写入方仍然记录决策,缺失不代表 pass。", + "verification": "pnpm exec vitest run packages/hooks/hook-protocol/tests/events.spec.ts:10 个测试通过。" + } +} +``` + +Use a date and descriptive slug in place of this example id: + +```sh +pnpm --silent run persistence-changes --record 2026-09-11-poc-optional --prose .artifacts/persistence-change.prose.json --json +``` + +The command validates the history and paired prose, infers the minimum version decision, and checks any required header increase before writing. It generates the record pair, complete after schemas, both catalogs, the machine inventory, and pairing records. Review the explanations and returned `changes`, `roots`, and `files` before committing. Omitting `--prose` creates unfinished drafts that verification rejects until their explanations are completed. + +Inference follows the [fixed compatibility rules](../persistence-changes/README.md#compatibility-rules); it never changes source or relaxes them. If a bump is required, first follow [adding a Session format version](adding-a-session-format-version.md). The record must include its own increasing `SessionHeader.version` transition; an unrelated historical bump cannot authorize it. Routine changes never create another baseline. + + +## 2. Check, commit, and push + +Select the changed owner's behavior checks through the [testing policy](../testing.md), then run the documentation checks: + +```sh +pnpm run doc-sync +``` + +`doc-sync` checks persistence inventory and catalog freshness, the complete history, and bilingual pairing. A recording command's `ok: true` does not replace these checks or the owner's behavior and migration tests. JSON failures retain `ok: false`, a diagnostic `code`, and exit code 1. Structured changes include stable kinds and per-root before/after digests, so automation need not parse descriptions. + +Record generation owns its catalog and record pairs; edits to a package README or other bilingual page still follow their normal pairing workflow. Review and stage the intended diff, then commit and push normally. The staged lint, pairing, and whitespace hooks and the pre-push Host/Client typecheck still apply. + + +## Update an unaccepted record + +If source changes after recording, review the compatibility explanation and refresh the same unaccepted terminal record: + +```sh +pnpm --silent run persistence-changes --update 2026-09-11-poc-optional --prose .artifacts/persistence-change.prose.json --json +``` + +The command refreshes the machine declaration, schemas, catalogs, and pairing. Without `--prose`, it preserves the existing explanation. Update refuses the initial baseline and records that another record depends on. The tree cannot identify which records were accepted in review: preserve accepted history and create a successor instead. + +When integration creates competing terminal records, update the unaccepted record against the remaining history, then reassess the resulting diff. An unrelated root's acknowledgement does not need refreshing. The [mechanism decision](../../.agents/notes/implemented/process/2026-09-11-persistence-type-history.md) explains why complete snapshots and per-root predecessors are retained. + +An explicit `--decision` remains a checked assertion. For an existing property's value-type change, the following deliberately wrong assertion fails before writing: + +```sh +pnpm --silent run persistence-changes --update 2026-09-11-poc-optional --decision same-version --json +``` + + +## Dev Note + +
+Working context for maintainers — click to expand + +None. + +
diff --git a/docs/cookbook/reviewing-persistence-type-changes.zh.md b/docs/cookbook/reviewing-persistence-type-changes.zh.md new file mode 100644 index 0000000000..f196101388 --- /dev/null +++ b/docs/cookbook/reviewing-persistence-type-changes.zh.md @@ -0,0 +1,108 @@ +--- +description: "在创建 PR 前,本地生成、确认并验证会话持久化类型变更。" +--- + +# 实操手册:审阅持久化类型变更 + +[English](reviewing-persistence-type-changes.md) | 中文 + +## 概述 + +在已安装依赖的贡献者检出目录中修改会话持久化类型声明后,使用本教程。提供双语兼容性说明,再用一条命令分类变更并生成记录。[记录参考](../persistence-changes/README.zh.md)解释文件和自动规则。所有比较输入都在检出目录中;不需要基线分支或网络访问。 + +## 目录 + +- [可选:检查变更](#generate) +- [1. 记录变更](#acknowledge) +- [2. 检查、提交并推送](#verify) +- [更新尚未接受的记录](#competing-records) +- [开发备注](#dev-note) + +----- + + +## 可选:检查变更 + +若需在记录前预览,在仓库根目录运行: + +```sh +pnpm --silent run verify-persistence-changes --json +``` + +消费 JSON 时使用 `--silent`:否则 pnpm 会把生命周期失败文本追加到标准输出。失败命令仍以退出码 1 结束。 + +阅读报告中的根、路径、变更种类和版本要求。被引用类型可能影响多个事件摘要;检查每个受影响的根。在历史覆盖新 schema 之前,验证会失败。陈旧生成清单也会导致验证失败;记录命令会刷新它。若重排字段或联合类型分支后 `changes` 为空,运行 `pnpm run gen-persistence-catalog` 并重新检查。即使复制的声明或源码位置产生目录 diff,未变的摘要也无需新增确认记录。 + + +## 1. 记录变更 + +编写包含 `en` 和 `zh` 的本地 JSON 文件,两者分别包含 `summary`、`compatibility` 和 `verification` 字符串。以下输入描述一个经过验证的钩子审计字段从必选改为可选的变更。用你所做变更的事实替换说明和测试证据;CLI(命令行界面)不会证明这些声明。 + +将输入保存为 `.artifacts/persistence-change.prose.json`,必要时创建该被忽略的目录: + +```json +{ + "en": { + "summary": "Makes the persisted hook audit decision optional.", + "compatibility": "Existing records remain valid. Hook execution consumes HookOutput instead of replaying this audit field. Producers still write decisions, and absence does not imply pass.", + "verification": "pnpm exec vitest run packages/hooks/hook-protocol/tests/events.spec.ts: 10 tests passed." + }, + "zh": { + "summary": "将持久化的钩子审计决策改为可选。", + "compatibility": "已有记录仍然有效。钩子执行消费 HookOutput,不回放此审计字段。写入方仍然记录决策,缺失不代表 pass。", + "verification": "pnpm exec vitest run packages/hooks/hook-protocol/tests/events.spec.ts:10 个测试通过。" + } +} +``` + +用日期和描述性短名替换示例 id: + +```sh +pnpm --silent run persistence-changes --record 2026-09-11-poc-optional --prose .artifacts/persistence-change.prose.json --json +``` + +命令在写入前验证历史和双语说明、推断最低版本决策,并检查所需的头部版本递增。它生成记录对、完整的变更后 schema、两份目录、机器清单和配对记录。提交前审阅说明及返回的 `changes`、`roots` 和 `files`。省略 `--prose` 会创建未完成草稿,验证将拒绝它们,直到说明补齐。 + +推断遵循[固定兼容性规则](../persistence-changes/README.zh.md#compatibility-rules),不会更改源码或放宽规则。需要升版本时,先遵循[添加会话格式版本](adding-a-session-format-version.zh.md)。记录必须包含其自身的 `SessionHeader.version` 递增转换;无关的历史升版本不能授权它。日常变更不创建另一条基线。 + + +## 2. 检查、提交并推送 + +根据[测试政策](../testing.zh.md)选择变更所属模块的行为检查,再运行文档检查: + +```sh +pnpm run doc-sync +``` + +`doc-sync` 检查持久化清单和目录新鲜度、完整历史及双语配对。记录命令的 `ok: true` 不能替代这些检查,也不能替代所属模块的行为与迁移测试。JSON 失败响应保留 `ok: false`、诊断 `code` 和退出码 1。结构化变更包含稳定种类和逐根的变更前后摘要,自动化无需解析描述文本。 + +记录生成负责其目录和记录的双语对;包 README 或其他双语页面的编辑仍遵循常规配对流程。审阅并暂存预期差异,然后正常提交和推送。暂存 lint、配对、空白 hooks,以及 pre-push Host/Client 类型检查仍须执行。 + + +## 更新尚未接受的记录 + +记录后源码再次变化时,审阅兼容性说明,并刷新同一条尚未接受的末端记录: + +```sh +pnpm --silent run persistence-changes --update 2026-09-11-poc-optional --prose .artifacts/persistence-change.prose.json --json +``` + +命令刷新机器声明、schema、目录和配对。没有 `--prose` 时,它保留已有说明。更新会拒绝初始基线和被其他记录依赖的记录。目录本身无法识别哪些记录已获审阅接受:保留已接受历史,并创建后继。 + +集成产生竞争末端记录时,根据剩余历史更新尚未接受的记录,再重新评估最终差异。无关根的确认无需刷新。[机制决策](../../.agents/notes/implemented/process/2026-09-11-persistence-type-history.zh.md)解释为何保留完整快照和逐根前驱。 + +显式 `--decision` 仍是受检查的断言。若已有属性的值类型发生变化,下面这个故意错误的断言会在写入前失败: + +```sh +pnpm --silent run persistence-changes --update 2026-09-11-poc-optional --decision same-version --json +``` + + +## 开发备注 + +
+维护者的工作上下文——点击展开 + +无。 + +
diff --git a/docs/deepseek-llm-api-wire-extensions.i18n.yaml b/docs/deepseek-llm-api-wire-extensions.i18n.yaml index e0b7be4a17..f73024d8f0 100644 --- a/docs/deepseek-llm-api-wire-extensions.i18n.yaml +++ b/docs/deepseek-llm-api-wire-extensions.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/deepseek-llm-api-wire-extensions.md -deepseek-llm-api-wire-extensions.md: a6689c97670260a7b673fc73d455726e357fd022 +deepseek-llm-api-wire-extensions.md: 28a320fc277f82dcc69b7f335cd94d02a562e282 deepseek-llm-api-wire-extensions.zh.md: 9f54d78609186055d3ac5e3b9b4f4272c7304e83 diff --git a/docs/deepseek-llm-api-wire-extensions.md b/docs/deepseek-llm-api-wire-extensions.md index a6689c9767..28a320fc27 100644 --- a/docs/deepseek-llm-api-wire-extensions.md +++ b/docs/deepseek-llm-api-wire-extensions.md @@ -67,7 +67,7 @@ Every request re-reads active non-group Loader entries from the host tree and, w The sender deduplicates exact `(name, version)` pairs and sorts first by `name`, then by `version`, with a locale-independent text comparison. Simultaneously active versions of one package remain separate entries. Receivers must not collapse the array by package name or infer package activation from array order. -Disabled, pending, failed, unloading, disposed, and structural Loader entries are absent. Ordinary dependencies, loose modules without a named owning package, programmatically mounted child fibers, and in-memory dynamic plugins are also absent because they have no authoritative Loader package provenance. +Disabled, pending, failed, unloading, disposed, and structural Loader entries are absent. Ordinary dependencies, loose modules without a named owning package, programmatically mounted child fibers, and in-memory dynamic plugins are also absent because they have no authoritative Loader-backed package identity. An enabled inventory with no qualifying entries sends `packages: []`; disabling the contributor omits the entire `dsh_plugin_packages` field. Package identities are provider metadata and never enter model input. diff --git a/docs/event-producer-consumer.i18n.yaml b/docs/event-producer-consumer.i18n.yaml index a97878c6ac..fd9cd4bdd8 100644 --- a/docs/event-producer-consumer.i18n.yaml +++ b/docs/event-producer-consumer.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/event-producer-consumer.md -event-producer-consumer.md: 862a3d0be7125a2f1369c96fe2c73e1651b15181 -event-producer-consumer.zh.md: 9724d5064fd783ac9d05af142f26a09bfb6a56bd +event-producer-consumer.md: 813c473da7a281fc073572f8db0fa063c3d82f55 +event-producer-consumer.zh.md: 97e6a423a24320b00aa9af563debb8027a2bf2c2 diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 862a3d0be7..813c473da7 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -51,7 +51,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:43`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | | `session/created` | `emit` | [`packages/core/session/src/index.ts:50`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), `session-controller`, [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | `session/disposed` | `emit` | [`packages/core/session/src/index.ts:60`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:72`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:72`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:81`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) | | `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:105`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `remotes` | | `settings/updated` | `emit` | [`packages/settings/settings/src/types.ts:92`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index 9724d5064f..97e6a423a2 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -53,7 +53,7 @@ | `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:43`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | | `session/created` | `emit` | [`packages/core/session/src/index.ts:50`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), `session-controller`, [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | `session/disposed` | `emit` | [`packages/core/session/src/index.ts:60`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:72`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:72`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:81`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) | | `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:105`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `remotes` | | `settings/updated` | `emit` | [`packages/settings/settings/src/types.ts:92`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) | diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml index b2f4630aba..c56c4c2954 100644 --- a/docs/i18n/README.i18n.yaml +++ b/docs/i18n/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/i18n/README.md -README.md: 2ff8fc62f21d58a4d31b8aadd80c7a0c14556e6d -README.zh.md: 2e0c183f6f9374c08d698fbd7109eb4e59f87c10 +README.md: f9b6b97d51541a12315c8b4503bd9b4399220888 +README.zh.md: 1f0ab74d3fbb8301dedb3c396c1b65334ffc8503 diff --git a/docs/i18n/README.md b/docs/i18n/README.md index 2ff8fc62f2..f9b6b97d51 100644 --- a/docs/i18n/README.md +++ b/docs/i18n/README.md @@ -43,7 +43,11 @@ The gate's limit, stated plainly: **a green gate means the pair was confirmed co **Scope**: the root `CONTRIBUTING.md`, `BRAND_GUIDELINES.md`, and `SAFETY.md` documents, every non-vendor README, and every active document under `.agents/notes/**`, `docs/**`, and `python/**`. README matching is case-insensitive on the basename and covers future directories without another manifest edit. Dependency and ignored build-output trees and the frozen `.agents/notes/archived/` tree are discovery exclusions, not evolving translation source. -Generated English references and graphs participate in pairing when a reviewed Chinese counterpart is available. Their generators remain the English source of truth, and freshness and pairing gates enforce their respective invariants independently; regeneration that changes English leaves the pair out of sync until the reviewed Chinese counterpart is updated and re-recorded. A generator that owns both sides, such as the Cordis subsystem-region generator, projects paired document paths to each output locale while keeping every other generated byte equal. Generated English sources omit the language switcher that ordinary authored sources carry, because adding it would make the generator stale; their Chinese counterparts still link back to the English source. A generated page's Chinese counterpart may rewrite only self-referential generation and maintenance statements that would otherwise be false for the reviewed translation; all technical content remains subject to the ordinary faithfulness rules. +Generated English references and graphs participate in pairing when a reviewed Chinese counterpart is available. When a generator owns only English, it remains that source of truth; regeneration that changes English leaves the pair out of sync until the reviewed Chinese counterpart is updated and re-recorded. Freshness and pairing gates enforce their respective invariants independently. + +Shared generated regions, such as the Cordis subsystem regions, keep every generated byte identical across languages except localized paired-document paths. A full-page generator can instead render source-owned paired prose, such as [persistence-catalog-text.ts](../../scripts/persistence-catalog-text.ts), from the same structural data with matching Markdown structure and identical code blocks. It regenerates both pages and their consistency record together; review still verifies the paired prose's meaning and terminology. + +Generated English sources omit the language switcher that ordinary authored sources carry, because adding it would make the generator stale; their Chinese counterparts still link back to the English source. A manually maintained Chinese counterpart may rewrite only self-referential generation and maintenance statements that would otherwise be false for the reviewed translation; all technical content remains subject to the ordinary faithfulness rules. **Excluded** (never paired, and the gate rejects a `.zh.md` or `.i18n.yaml` for them): diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md index 2e0c183f6f..1f0ab74d3f 100644 --- a/docs/i18n/README.zh.md +++ b/docs/i18n/README.zh.md @@ -45,7 +45,11 @@ **范围**:根目录 `CONTRIBUTING.md`、`BRAND_GUIDELINES.md` 与 `SAFETY.md` 文档、除 vendor 源码外的全部 README,以及 `.agents/notes/**`、`docs/**` 与 `python/**` 下的全部活跃文档。匹配 README 时只看文件名且不区分大小写,因此今后新增的目录无需再修改 manifest。依赖目录、被忽略的构建产物目录以及冻结的 `.agents/notes/archived/` 目录树只在发现阶段排除,不属于持续演进的翻译源文档。 -有经评审的中文对侧的生成英文参考文档和图文档遵循配对规则。生成器仍是英文真源,新鲜度门禁与配对门禁各自独立强制其约束;重新生成导致英文变化后,配对会保持失去同步状态,直至经评审的中文对侧完成更新并重新记录。Cordis subsystem 区块生成器等同时拥有两侧输出的生成器,会把配对文档路径投影到各自 locale,同时保持其余生成字节一致。生成的英文源文件不含普通撰写文档所带的语言切换行,因为添加该行会使生成器新鲜度检查失败;中文对侧仍链接回英文源。生成页的中文对侧只能改写若直译便不再符合经评审译文事实的自指生成与维护说明;所有技术内容仍受普通忠实性规则约束。 +有经评审的中文对侧的生成英文参考文档和图文档遵循配对规则。生成器只负责英文时,它仍是该语言的真源;重新生成导致英文变化后,配对会保持失去同步状态,直至经评审的中文对侧完成更新并重新记录。新鲜度门禁与配对门禁各自独立强制其约束。 + +共享生成区域(例如 Cordis 子系统区域)在两种语言间保持每个生成字节相同,仅配对文档路径按语言本地化。整页生成器则可以从相同的结构数据渲染源码维护的双语说明,例如 [persistence-catalog-text.ts](../../scripts/persistence-catalog-text.ts),并保持 Markdown 结构对应、代码块完全相同。它同时重新生成两页及其一致性记录;评审仍须验证双语说明的含义与术语。 + +生成的英文源文件不含普通撰写文档所带的语言切换行,因为添加该行会使生成器新鲜度检查失败;中文对侧仍链接回英文源。人工维护的中文对侧只能改写若直译便不再符合经评审译文事实的自指生成与维护说明;所有技术内容仍受普通忠实性规则约束。 **排除**(永不配对,门禁拒绝为它们建 `.zh.md` 或 `.i18n.yaml`): diff --git a/docs/module-graph.i18n.yaml b/docs/module-graph.i18n.yaml index dad87306b0..a4b753f805 100644 --- a/docs/module-graph.i18n.yaml +++ b/docs/module-graph.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/module-graph.md -module-graph.md: d24d683ad8bc26f8a4f753d7e96b172b3b001c5a -module-graph.zh.md: 16001858badf51c516d9052ca967a32ff1cf58e2 +module-graph.md: 84a58adfe3a04214fb3d695c12eaff9fe3940c63 +module-graph.zh.md: 8720d80037aa7746a4fbd9e78d1b043c2e03ab90 diff --git a/docs/module-graph.md b/docs/module-graph.md index d24d683ad8..84a58adfe3 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -210,11 +210,6 @@ flowchart TD pkg_credentials["credentials"] pkg_credentials_local["credentials-local"] end - subgraph group_e2b["packages/e2b"] - pkg_e2b["e2b"] - pkg_fs_e2b["fs-e2b"] - pkg_subprocess_e2b["subprocess-e2b"] - end subgraph group_experimental["packages/experimental"] pkg_experimental_agent_team["experimental-agent-team"] pkg_experimental_agent_team_profile["experimental-agent-team-profile"] @@ -380,7 +375,6 @@ flowchart TD pkg_web --> pkg_llm pkg_attachment --> pkg_brand pkg_credentials --> pkg_invariants - pkg_e2b --> pkg_http_proxy pkg_experimental_code_runtime_python --> pkg_code_runtime pkg_experimental_code_runtime_python --> pkg_timeout pkg_experimental_code_runtime_python --> pkg_util_values @@ -430,9 +424,6 @@ flowchart TD pkg_credentials_local --> pkg_credentials pkg_credentials_local --> pkg_home_paths pkg_credentials_local --> pkg_launch_environment - pkg_subprocess_e2b --> pkg_e2b - pkg_subprocess_e2b --> pkg_subprocess - pkg_subprocess_e2b --> pkg_timeout pkg_subprocess_local --> pkg_subprocess pkg_subprocess_local --> pkg_timeout pkg_skill_badge --> pkg_skill @@ -570,8 +561,6 @@ flowchart TD pkg_tmux_context --> pkg_session pkg_tmux_context --> pkg_session_projection pkg_tmux_context --> pkg_shell - pkg_fs_e2b --> pkg_e2b - pkg_fs_e2b --> pkg_fs pkg_commands --> pkg_agent pkg_commands --> pkg_attachment pkg_commands --> pkg_brand @@ -653,10 +642,6 @@ flowchart TD pkg_fs_sandbox --> pkg_fs_local pkg_fs_sandbox --> pkg_sandbox pkg_fs_sandbox --> pkg_sandbox_policy - pkg_headless --> pkg_agent - pkg_headless --> pkg_agent_default_model - pkg_headless --> pkg_llm - pkg_headless --> pkg_session pkg_compaction --> pkg_brand pkg_compaction --> pkg_commands pkg_compaction --> pkg_invariants @@ -1015,6 +1000,11 @@ flowchart TD pkg_tool_session_query --> pkg_system_prompt pkg_tool_session_query --> pkg_timeout pkg_tool_session_query --> pkg_tools + pkg_headless --> pkg_agent + pkg_headless --> pkg_agent_default_model + pkg_headless --> pkg_llm + pkg_headless --> pkg_session + pkg_headless --> pkg_session_query pkg_compaction_basic --> pkg_agent pkg_compaction_basic --> pkg_commands pkg_compaction_basic --> pkg_compaction @@ -1297,7 +1287,6 @@ flowchart TD | [`web`](../packages/web/web) | `web` | [`llm`](../packages/llm/llm) | | [`attachment`](../packages/attachment/attachment) | `attachment` | [`brand`](../packages/util/brand) | | [`credentials`](../packages/credentials/credentials) | `credentials` | [`invariants`](../packages/runtime-diagnostics/invariants) | -| [`e2b`](../packages/e2b/e2b) | `e2b` | [`http-proxy`](../packages/util/http-proxy) | | [`experimental-code-runtime-python`](../packages/experimental/code-runtime-python) | `experimental` | [`code-runtime`](../packages/code-runtime/code-runtime), [`timeout`](../packages/util/timeout), [`util-values`](../packages/util/values) | | [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | @@ -1321,7 +1310,6 @@ flowchart TD | [`client-file-upload`](../packages/client/file-upload) | `client` | [`scope`](../packages/core/scope) | | [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) | | [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) | -| [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`skill`](../packages/skill/skill) | | [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | @@ -1361,7 +1349,6 @@ flowchart TD | [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent) | | [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) | -| [`fs-e2b`](../packages/e2b/fs-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`fs`](../packages/fs/fs) | | [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | | [`user-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | @@ -1379,7 +1366,6 @@ flowchart TD | [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm) | | [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) | -| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`commands`](../packages/interaction/commands), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | @@ -1441,6 +1427,7 @@ flowchart TD | [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time) | | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) | | [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) | | [`webhook-github`](../packages/webhook/webhook-github) | `webhook` | [`credentials`](../packages/credentials/credentials), [`host-webserver`](../packages/host/webserver), [`session`](../packages/core/session), [`webhook`](../packages/webhook/webhook) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 16001858ba..8720d80037 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -212,11 +212,6 @@ flowchart TD pkg_credentials["credentials"] pkg_credentials_local["credentials-local"] end - subgraph group_e2b["packages/e2b"] - pkg_e2b["e2b"] - pkg_fs_e2b["fs-e2b"] - pkg_subprocess_e2b["subprocess-e2b"] - end subgraph group_experimental["packages/experimental"] pkg_experimental_agent_team["experimental-agent-team"] pkg_experimental_agent_team_profile["experimental-agent-team-profile"] @@ -382,7 +377,6 @@ flowchart TD pkg_web --> pkg_llm pkg_attachment --> pkg_brand pkg_credentials --> pkg_invariants - pkg_e2b --> pkg_http_proxy pkg_experimental_code_runtime_python --> pkg_code_runtime pkg_experimental_code_runtime_python --> pkg_timeout pkg_experimental_code_runtime_python --> pkg_util_values @@ -432,9 +426,6 @@ flowchart TD pkg_credentials_local --> pkg_credentials pkg_credentials_local --> pkg_home_paths pkg_credentials_local --> pkg_launch_environment - pkg_subprocess_e2b --> pkg_e2b - pkg_subprocess_e2b --> pkg_subprocess - pkg_subprocess_e2b --> pkg_timeout pkg_subprocess_local --> pkg_subprocess pkg_subprocess_local --> pkg_timeout pkg_skill_badge --> pkg_skill @@ -572,8 +563,6 @@ flowchart TD pkg_tmux_context --> pkg_session pkg_tmux_context --> pkg_session_projection pkg_tmux_context --> pkg_shell - pkg_fs_e2b --> pkg_e2b - pkg_fs_e2b --> pkg_fs pkg_commands --> pkg_agent pkg_commands --> pkg_attachment pkg_commands --> pkg_brand @@ -655,10 +644,6 @@ flowchart TD pkg_fs_sandbox --> pkg_fs_local pkg_fs_sandbox --> pkg_sandbox pkg_fs_sandbox --> pkg_sandbox_policy - pkg_headless --> pkg_agent - pkg_headless --> pkg_agent_default_model - pkg_headless --> pkg_llm - pkg_headless --> pkg_session pkg_compaction --> pkg_brand pkg_compaction --> pkg_commands pkg_compaction --> pkg_invariants @@ -1017,6 +1002,11 @@ flowchart TD pkg_tool_session_query --> pkg_system_prompt pkg_tool_session_query --> pkg_timeout pkg_tool_session_query --> pkg_tools + pkg_headless --> pkg_agent + pkg_headless --> pkg_agent_default_model + pkg_headless --> pkg_llm + pkg_headless --> pkg_session + pkg_headless --> pkg_session_query pkg_compaction_basic --> pkg_agent pkg_compaction_basic --> pkg_commands pkg_compaction_basic --> pkg_compaction @@ -1299,7 +1289,6 @@ flowchart TD | [`web`](../packages/web/web) | `web` | [`llm`](../packages/llm/llm) | | [`attachment`](../packages/attachment/attachment) | `attachment` | [`brand`](../packages/util/brand) | | [`credentials`](../packages/credentials/credentials) | `credentials` | [`invariants`](../packages/runtime-diagnostics/invariants) | -| [`e2b`](../packages/e2b/e2b) | `e2b` | [`http-proxy`](../packages/util/http-proxy) | | [`experimental-code-runtime-python`](../packages/experimental/code-runtime-python) | `experimental` | [`code-runtime`](../packages/code-runtime/code-runtime), [`timeout`](../packages/util/timeout), [`util-values`](../packages/util/values) | | [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | @@ -1323,7 +1312,6 @@ flowchart TD | [`client-file-upload`](../packages/client/file-upload) | `client` | [`scope`](../packages/core/scope) | | [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) | | [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) | -| [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`skill`](../packages/skill/skill) | | [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | @@ -1363,7 +1351,6 @@ flowchart TD | [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent) | | [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) | -| [`fs-e2b`](../packages/e2b/fs-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`fs`](../packages/fs/fs) | | [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | | [`user-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | @@ -1381,7 +1368,6 @@ flowchart TD | [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm) | | [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) | -| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`commands`](../packages/interaction/commands), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | @@ -1443,6 +1429,7 @@ flowchart TD | [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time) | | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) | | [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) | | [`webhook-github`](../packages/webhook/webhook-github) | `webhook` | [`credentials`](../packages/credentials/credentials), [`host-webserver`](../packages/host/webserver), [`session`](../packages/core/session), [`webhook`](../packages/webhook/webhook) | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index 750aac5612..2304c6d30f 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/persistence-catalog.md -persistence-catalog.md: 716d551d3fe3ceb7f74f9746282593c0b70ba4df -persistence-catalog.zh.md: 69e491d920d01696819ba266c540c338ffdf39aa +persistence-catalog.md: a85ff0d2ea49c6b1a359d311d6e540ec4e9e5d65 +persistence-catalog.zh.md: 504beffabe1239ea8c4ae49d0e476107246e0041 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 716d551d3f..a85ff0d2ea 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -3,11 +3,79 @@ # Session Persistence Event Catalog -Every event type that can appear in a session's durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge into `@deepseek-ai/dsh-session/types` in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](subsystems/persistence.md) (how the log is made durable), and the generated region of [session.md](subsystems/session.md#cordis-surface) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit). +Every repository-declared durable Session event appears here with its source declaration and resolved types. The catalog covers the logical and physical headers, event envelopes, and every plugin declaration merge. See [Session](subsystems/session.md) for replay and [persistence](subsystems/persistence.md) for storage. -This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks retain the source declaration and nested property JSDoc, removing only the indentation imposed by a containing interface/module, and use a `ts persistence-catalog` fence (skipped by doc-typecheck because declarations reference types from their owning modules). Type names in a payload link to the page that documents them. The archived [persistence-log-catalog record](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md) documents the original catalog decision. +Run `pnpm run gen-persistence-catalog` to regenerate both catalog languages, their pairing record, the known-event module, and the machine schema inventory. `pnpm run verify-persistence-catalog` checks all generated files. Declaration fences preserve source JSDoc and type references; resolved definitions expose their transitive structure. -The envelope declarations below compose each event's `type`, monotonic `seq`, epoch-ms `time`, `data`, the optional `ignorable` unknown-type skip marker, and the conditional `surfaceOp`/`sourceEventSeqs` fields. **surface** marks a `SurfaceEventType` member: it produces an LLM message and declares how it joins the surface list. **log-only** marks everything else: a durable, replayable record with no derived-history contribution. Every payload is JSON-serializable (enforced at `Session.append`). Current writers stamp `SESSION_FORMAT_VERSION`; supported historical artifacts reach this current vocabulary through the build-static adjacent migration catalog ([the version lifecycle](subsystems/persistence.md)). Scope: the packages in this repo; a downstream plugin can merge further current-version event types, which are outside this catalog by construction and require an explicit disposition at a later format edge. +The envelope carries `type`, `seq`, `time`, `data`, optional `ignorable`, and conditional `surfaceOp` / `sourceEventSeqs`. A **surface** event produces model history; a **log-only** event does not. The inventory covers this repository; external plugin types require their own declarations and are outside this catalog. + +## Persistence type fingerprints + +The [machine inventory](persistence-schema.json) contains every reachable normalized type and its SHA-256 digest. Root digests include referenced types. Comments, source locations, alias names, erased brands, readonly markers, and harmless field, union, or intersection reordering do not affect these fingerprints. Tuple order, property names, value types, and optionality do. Catalog text and source locations can still produce a diff when digests stay unchanged. + +The [change records](persistence-changes/README.md) acknowledge exact transitions using snapshots kept in this tree. Follow the [review workflow](cookbook/reviewing-persistence-type-changes.md) to classify a change and record it. These checks cover declared type structure; opaque payload contents and behavior without type changes are outside their scope. + +| Root | Kind | SHA-256 | Resolved type | +|---|---|---|---| +| `SessionHeader` | header | `2a8b114637e66faab5fae1c6e2fba65320db6224c80f5f36c5bf32f937154d03` | [`packages/core/session/src/types.ts#SessionHeader`](#persistence-type-packagescoresessionsrctypestssessionheader) | +| `JsonlHeaderLine` | header | `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` | [`packages/session/session-persistence-jsonl/src/format.ts#HeaderLine`](#persistence-type-packagessessionsession-persistence-jsonlsrcformattsheaderline) | +| `SessionEventEnvelope` | envelope | `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` | [`SessionEventEnvelope`](#persistence-type-sessioneventenvelope) | +| `event:agent-preset/selected` | event | `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | [`event:agent-preset/selected`](#persistence-type-eventagent-presetselected) | +| `event:agent/inbox/spliced` | event | `f757322e25914d63799c6235af4ed4b4f36d7d083fef40d6873a45e94fe6ad65` | [`event:agent/inbox/spliced`](#persistence-type-eventagentinboxspliced) | +| `event:approval/asked` | event | `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` | [`event:approval/asked`](#persistence-type-eventapprovalasked) | +| `event:approval/decided` | event | `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` | [`event:approval/decided`](#persistence-type-eventapprovaldecided) | +| `event:approval/policy` | event | `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` | [`event:approval/policy`](#persistence-type-eventapprovalpolicy) | +| `event:assistant/attempt` | event | `3516f54b427775f483febe8e938e4e10f9afd3583610861547bbe2999ed4d8d9` | [`event:assistant/attempt`](#persistence-type-eventassistantattempt) | +| `event:assistant/message` | event | `a58ca1bed502d425a8b65cac02f59717498cd296de9513c6f81b83b791c20e9b` | [`event:assistant/message`](#persistence-type-eventassistantmessage) | +| `event:command/done` | event | `15196447222782e773eb943c92b18316ce96b9af0f0cfddb6e57ba8274ecc5ff` | [`event:command/done`](#persistence-type-eventcommanddone) | +| `event:command/run` | event | `37184378c6439257d105c4e2022d80fc9c3a3f7c7f6ac661b00bc9f18d871006` | [`event:command/run`](#persistence-type-eventcommandrun) | +| `event:compaction/end` | event | `b0127044ab31a702bddfd785d345f5abd7a70876746e895ce443afa3e60ddf2d` | [`event:compaction/end`](#persistence-type-eventcompactionend) | +| `event:compaction/prune` | event | `7f7fd5a6b0064f597534b29ff62ef26e786dffccf5e14f654a7d4fcea2c35f04` | [`event:compaction/prune`](#persistence-type-eventcompactionprune) | +| `event:compaction/start` | event | `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` | [`event:compaction/start`](#persistence-type-eventcompactionstart) | +| `event:compaction/summary` | event | `eb1d32dcd3d76833392f351e039dcbd40686a1ab24b252a7a5003426f9cf4c89` | [`event:compaction/summary`](#persistence-type-eventcompactionsummary) | +| `event:deliverables/presented` | event | `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | [`event:deliverables/presented`](#persistence-type-eventdeliverablespresented) | +| `event:feedback/message-delete` | event | `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` | [`event:feedback/message-delete`](#persistence-type-eventfeedbackmessage-delete) | +| `event:feedback/message-put` | event | `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` | [`event:feedback/message-put`](#persistence-type-eventfeedbackmessage-put) | +| `event:feedback/record` | event | `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` | [`event:feedback/record`](#persistence-type-eventfeedbackrecord) | +| `event:goal/change` | event | `763c8a20af487263a0080548274f7437ef4a86e0ba8769127d1ced10a72c664d` | [`event:goal/change`](#persistence-type-eventgoalchange) | +| `event:hook/invoked` | event | `8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0` | [`event:hook/invoked`](#persistence-type-eventhookinvoked) | +| `event:hook/result` | event | `e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4` | [`event:hook/result`](#persistence-type-eventhookresult) | +| `event:llm/retry` | event | `91c397f8f870e812e1dc5ac69c2745f9f105be9dc97dadc980d19ef415a65145` | [`event:llm/retry`](#persistence-type-eventllmretry) | +| `event:llm/retry-started` | event | `48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3` | [`event:llm/retry-started`](#persistence-type-eventllmretry-started) | +| `event:model/selection` | event | `35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc` | [`event:model/selection`](#persistence-type-eventmodelselection) | +| `event:permission/preset` | event | `5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b` | [`event:permission/preset`](#persistence-type-eventpermissionpreset) | +| `event:plan/mode` | event | `a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f` | [`event:plan/mode`](#persistence-type-eventplanmode) | +| `event:request/context` | event | `37cbc9cf06d494cfe5c67f078af1605eacb8e4c2c853e9e26455b73de0ee7ccf` | [`event:request/context`](#persistence-type-eventrequestcontext) | +| `event:request/header` | event | `8ea13bcd983eab596dbfd41c5c2b62a0b318b71cd2a987a6fd752c6ced7e4489` | [`event:request/header`](#persistence-type-eventrequestheader) | +| `event:sandbox/mode` | event | `516da4cdd6d2f1e5ce488e648578ca51f40e458f707b803e5b24de870e799415` | [`event:sandbox/mode`](#persistence-type-eventsandboxmode) | +| `event:schedule/change` | event | `2a7f86849ae54b3398ee49661a757c4fcb59a6192b7036ee2ff514617e13fb42` | [`event:schedule/change`](#persistence-type-eventschedulechange) | +| `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`event:session-log-deepseek/delivery-accepted`](#persistence-type-eventsession-log-deepseekdelivery-accepted) | +| `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`event:session/end-seed`](#persistence-type-eventsessionend-seed) | +| `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`event:session/title`](#persistence-type-eventsessiontitle) | +| `event:session/title-llm-request` | event | `6091f64d426ada2adc646094a6fe662a1b81d9ee5770929fe76800524e2c89a8` | [`event:session/title-llm-request`](#persistence-type-eventsessiontitle-llm-request) | +| `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`event:step/end`](#persistence-type-eventstepend) | +| `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`event:step/start`](#persistence-type-eventstepstart) | +| `event:subagent/catalog` | event | `ae1f7110feeec697b8cab42b68f7709aa7b3279764099dfb53c25890d2e5c871` | [`event:subagent/catalog`](#persistence-type-eventsubagentcatalog) | +| `event:subagent/descriptor` | event | `b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5` | [`event:subagent/descriptor`](#persistence-type-eventsubagentdescriptor) | +| `event:subagent/model-selection-policy` | event | `a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc` | [`event:subagent/model-selection-policy`](#persistence-type-eventsubagentmodel-selection-policy) | +| `event:system/message` | event | `0ae7ccdc1cc5d9d43772ebd5c7357ff509011c3a6ba188d9c274d33df2542c42` | [`event:system/message`](#persistence-type-eventsystemmessage) | +| `event:team/member` | event | `4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc` | [`event:team/member`](#persistence-type-eventteammember) | +| `event:team/message/delivered` | event | `48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb` | [`event:team/message/delivered`](#persistence-type-eventteammessagedelivered) | +| `event:team/message/queued` | event | `2ffcad547cb840f462eb1849fa5f6cb0e17eb93f5d07cc88b61d749b1b78d632` | [`event:team/message/queued`](#persistence-type-eventteammessagequeued) | +| `event:team/task` | event | `d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a` | [`event:team/task`](#persistence-type-eventteamtask) | +| `event:todo/write` | event | `b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26` | [`event:todo/write`](#persistence-type-eventtodowrite) | +| `event:tool-workflow/agent-end` | event | `babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7` | [`event:tool-workflow/agent-end`](#persistence-type-eventtool-workflowagent-end) | +| `event:tool-workflow/agent-start` | event | `5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627` | [`event:tool-workflow/agent-start`](#persistence-type-eventtool-workflowagent-start) | +| `event:tool-workflow/run-end` | event | `42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f` | [`event:tool-workflow/run-end`](#persistence-type-eventtool-workflowrun-end) | +| `event:tool-workflow/run-start` | event | `c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058` | [`event:tool-workflow/run-start`](#persistence-type-eventtool-workflowrun-start) | +| `event:tool/call` | event | `3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48` | [`event:tool/call`](#persistence-type-eventtoolcall) | +| `event:tool/ptc-dispatch` | event | `da807583a19f4ebe2bc9115c9686016e1521e405bdfaced296ae652b91e8e5f6` | [`event:tool/ptc-dispatch`](#persistence-type-eventtoolptc-dispatch) | +| `event:tool/ptc-dispatch-start` | event | `ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93` | [`event:tool/ptc-dispatch-start`](#persistence-type-eventtoolptc-dispatch-start) | +| `event:tool/result` | event | `cc3e270ab982cbafff602b753647b6f056decb7fe4fd523afd2f157a65c3d622` | [`event:tool/result`](#persistence-type-eventtoolresult) | +| `event:turn/end` | event | `84c24f1209fc3e0153de6ac85d58fd6968b851f955e0e761b92321053956609e` | [`event:turn/end`](#persistence-type-eventturnend) | +| `event:turn/start` | event | `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` | [`event:turn/start`](#persistence-type-eventturnstart) | +| `event:user/message` | event | `2e49ede7fb3a592cce8919ebe982f6ca1c1f67abe4def359ffc028c85e6d1ad8` | [`event:user/message`](#persistence-type-eventusermessage) | +| `event:web/deepseek-search-llm-request` | event | `cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331` | [`event:web/deepseek-search-llm-request`](#persistence-type-eventwebdeepseek-search-llm-request) | ## Event envelope @@ -1151,3 +1219,5373 @@ Source: [`packages/core/session/src/types.ts:297`](../packages/core/session/src/ ``` Source: [`packages/web/web-search-deepseek/src/provider.ts:83`](../packages/web/web-search-deepseek/src/provider.ts) + +## Resolved persistence types + +Each definition appears once. References preserve sharing and recursion; the digest beside a definition includes its complete reachable structure. Source names and locations identify its declarations but are excluded from its digest. + + + +### `0` + +SHA-256: `93d38436347f67c9022aacbc4472e283d722bcab8eb73fc376aaf7b6a6b9fb35` + +`0` + + + +### `1` + +SHA-256: `f88f1b7b6bb733f685f5fb6a8744a5db9199fa53c90463ea1a33bad51153671d` + +`1` + + + +### `2` + +SHA-256: `a0f906856fc06f1c6f2083eb970e5a657a82b1a84c06c095445e5d6d161cda80` + +`2` + + + +### `3` + +SHA-256: `137ac60e26bcb192210d9a32a704b73b0924ed34019323defa1b787be95bd45e` + +`3` + + + +### `"aborted"` + +SHA-256: `1e0b0b66b6f0c53fc72e52289d06b4708d776f1ca9df4c86a22bff621d403cf0` + +`"aborted"` + + + +### `"active"` + +SHA-256: `c8e6f48878cc2a2f0dc59682280d81f407943fb503f32c64bf9ff3d9393c3eff` + +`"active"` + + + +### `"after"` + +SHA-256: `5b232a9b9bfcc0fc6794dab7865bcf203a65785c5de5aa647a51228bff628413` + +`"after"` + + + +### `"agent-instructions"` + +SHA-256: `fbd2c74b346a1595b376139c587fadd23e4a21acc9c0abce342975b6cfc60986` + +`"agent-instructions"` + + + +### `"agent-message"` + +SHA-256: `82470020b5bfcb1a843e2aa05602af7d37df99e82d01f015c7c589ba054fd89d` + +`"agent-message"` + + + +### `"agent-preset/selected"` + +SHA-256: `557e148d30b79ee48645850832b30c717d7c3b64641deb5d6133b8b4e91ee0b8` + +`"agent-preset/selected"` + + + +### `"agent/inbox/spliced"` + +SHA-256: `829a2fb3be6efd4ba36aef8ed2d381a4c6fb2a0aaf50c823a6f3f8290786b809` + +`"agent/inbox/spliced"` + + + +### `"allowed-once"` + +SHA-256: `811229eedcec57979dbadca7dbea832a59f8580cff30cb217fca5e2da16314bd` + +`"allowed-once"` + + + +### `"always"` + +SHA-256: `142c37d302aa22a0a57915b5255939d3ca91c1811ca13fddcf84cf76ea00758a` + +`"always"` + + + +### `"append"` + +SHA-256: `e438a9d4fb883b3e4a8d9fc8913aa1550ed50eb384bfbf943100a5605b537498` + +`"append"` + + + +### `"approval/asked"` + +SHA-256: `cd186c309e2bef305705e1d324139240f5e681f4f6a957d6a25b0225f65560df` + +`"approval/asked"` + + + +### `"approval/decided"` + +SHA-256: `378f90f9f6022a0991928a1eaf7991094ea1920fe89a9248358a1da106e1a970` + +`"approval/decided"` + + + +### `"approval/policy"` + +SHA-256: `36d6a9a871d949ea4f3edd919d81d66aa69218bebef38899d2a1282c39420a1f` + +`"approval/policy"` + + + +### `"ask"` + +SHA-256: `9e07693fd7cd9babe0688c22c986e4d519ae1c0e40eeea13b2dc583bed2abc86` + +`"ask"` + + + +### `"assistant"` + +SHA-256: `95c0e88fcf0bafb2087f912ad9e2369de6ae6530cc0c4c2772c96205b57f7f60` + +`"assistant"` + + + +### `"assistant/attempt"` + +SHA-256: `e95f957154b2b1b58692af53d4131180b6dd19e2264869f876dbaa959991494e` + +`"assistant/attempt"` + + + +### `"assistant/message"` + +SHA-256: `6ab45ac76839722df912f3ba50f1c6b41f2d614bde11f1aabfae04117ccdc80d` + +`"assistant/message"` + + + +### `"at"` + +SHA-256: `17aae616ef020b8b9f011ec398ec935450fb06d3e2a0722c456f63aeaf5ff728` + +`"at"` + + + +### `"block"` + +SHA-256: `1e346446e183ee76f48d7994c97ecb302bddc682abb60b8a10ea0bb26223a69c` + +`"block"` + + + +### `"block-end"` + +SHA-256: `eefbea819caeeea91ccb02dfafc16c68dd7f961cf9c1b2be25a2084509cf5c5a` + +`"block-end"` + + + +### `"block-start"` + +SHA-256: `cbca49bde16b656d5b8fbe4a8a507769f7b594c6287782b809c03814941bc5e3` + +`"block-start"` + + + +### `"blocked"` + +SHA-256: `254dce209e735f9cb6f6b7aec0354712b1611759ec02af4d35a96f2081e666f6` + +`"blocked"` + + + +### `boolean` + +SHA-256: `aa93010d0e521ad7c42273f58d5c9b8bf1dfc94edf1b805a3971b364a35c1877` + +`boolean` + + + +### `"canceled"` + +SHA-256: `d4e4d575abeb4b72d616dc65c17ac0eaa943cc135609395044c38d5afcb10e31` + +`"canceled"` + + + +### `"cancelled"` + +SHA-256: `2142e391b9ccb60d487afcf373ce74db98d952f69e8289eddb352a7e16e996c5` + +`"cancelled"` + + + +### `"catalog"` + +SHA-256: `aec150797b655c5f15ebb7a501454b0c208fbaccec9ace49d7de2171fc02f47d` + +`"catalog"` + + + +### `"change"` + +SHA-256: `f789af5ab844827eb5a4f357c0afae1eeed108c6c22d8ca1a56e586b349a7c85` + +`"change"` + + + +### `"chunk"` + +SHA-256: `14230f9b8b5797bb7e8c621fa636531cfbde2ffba1ef5f1fd058216ff21952a0` + +`"chunk"` + + + +### `"claude-code"` + +SHA-256: `aff60ef947c18b012644852bdd0d5bfe72065e061dd563787c76c646b4a10f6b` + +`"claude-code"` + + + +### `"clear"` + +SHA-256: `e7e154d47d5fbc5366330fb4dade1f108c6dfe4335044d21ded14b7bf1dce084` + +`"clear"` + + + +### `"codex"` + +SHA-256: `15495614ccfa9cbdeefb0ec2abd76406435f4c6ff3dae42acae8e09ef42025c9` + +`"codex"` + + + +### `"command/done"` + +SHA-256: `8cdd3fdd295cee53035de16a1b23411716f75e9f994fe9510cac5d7fef605c76` + +`"command/done"` + + + +### `"command/run"` + +SHA-256: `f1996af2602e6f62b5ab179ca5c4af00d8753938b74cc6ef102de09e99e74a7e` + +`"command/run"` + + + +### `"compaction/end"` + +SHA-256: `1986c2664009c99155e44795676c887fa67f7c55cd2d5e6b648ff763e0ab033a` + +`"compaction/end"` + + + +### `"compaction/prune"` + +SHA-256: `10e386801903f9d424602c31d3ff837a94aef623c8b391376d8d9e253525fabe` + +`"compaction/prune"` + + + +### `"compaction/start"` + +SHA-256: `e2d0367e5716bc188435e51718ebff8ce873190625fa93b1c5331102d98758fa` + +`"compaction/start"` + + + +### `"compaction/summary"` + +SHA-256: `ef4f00923b9edbae15c7ef05320280bf61147ca17146cf59690359bfd5e5da45` + +`"compaction/summary"` + + + +### `"complete"` + +SHA-256: `d9196086e41e4dd5c2ce87f5c25529c8112a1731d258cf0728747d41a9ee3241` + +`"complete"` + + + +### `"completed"` + +SHA-256: `1ebc6f9ff3aa9e3a38f8695f49bacdab1f8b7a3832741853ca46f40af56ccd01` + +`"completed"` + + + +### `"continuable"` + +SHA-256: `e5d3828df1ec3e2a66879de0659e0f4866d9cfffc00825ee731a66f8a0c03d98` + +`"continuable"` + + + +### `"create"` + +SHA-256: `7624ca28c61ca0eaa4f015140f3cea6e0c16cce2f2960fab5f531203d253903e` + +`"create"` + + + +### `"danger-full-access"` + +SHA-256: `18fce9540bb20897a66275499faee6b8296c97e9901fa1ff45a191772448ad1a` + +`"danger-full-access"` + + + +### `"delegation"` + +SHA-256: `76632533f67879dcb346fcae640540dbc89b2472b0bce88c59f7e48fc5c0e668` + +`"delegation"` + + + +### `"delete"` + +SHA-256: `d0fbb0ca1bbba059830826f33f801da1aced35a2504376d61e020bdfec8d50c0` + +`"delete"` + + + +### `"deleted"` + +SHA-256: `e7e8f1318170b8284f16ab45a557afd4675c96071c0f2a456636481dab20b427` + +`"deleted"` + + + +### `"deliverables/presented"` + +SHA-256: `1a2718b00937b236a0d0910ef5dab2f9f8b65e4bde410cc21f15fbbf29f584d3` + +`"deliverables/presented"` + + + +### `"dispatch"` + +SHA-256: `26c879bdd130e8b8683a6ce588414fad2244783d6573ca399fa0de184a2b0bb1` + +`"dispatch"` + + + +### `"disposed"` + +SHA-256: `b38f206d59bad891fa58c89f97a0036987b3defe7685acbd318fa55eb85f9751` + +`"disposed"` + + + +### `"edit"` + +SHA-256: `ad22a5ca5bbaab2e1440779cd3648e85ddfc6f73b5207394eda699555cf31430` + +`"edit"` + + + +### `"error"` + +SHA-256: `a2be0f0cb59f0d4a06557d45847d1938aced238baa77a6da416e590b8b958553` + +`"error"` + + + +### `event:agent-preset/selected` + +SHA-256: `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:agent-preset/selected.data`](#persistence-type-eventagent-presetselecteddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"agent-preset/selected"` | + + + +### `event:agent-preset/selected.data` + +SHA-256: `6e52dd55a1d48954ed5464ec46fe34d9eb65f709baabc9561e7a279910b0c406` + +Sources: [`packages/preset/agent-presets/src/session.ts:28`](../packages/preset/agent-presets/src/session.ts) + +| Property | Presence | Type | +|---|---|---| +| `agentPreset` | required | `string` | + + + +### `event:agent/inbox/spliced` + +SHA-256: `f757322e25914d63799c6235af4ed4b4f36d7d083fef40d6873a45e94fe6ad65` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:agent/inbox/spliced.data`](#persistence-type-eventagentinboxspliceddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"agent/inbox/spliced"` | + + + +### `event:agent/inbox/spliced.data` + +SHA-256: `7115142af1bff1ad9b52c3c785109bf5c989e77e3ddfe899014e213731352042` + +Sources: [`packages/core/agent/src/types.ts:87`](../packages/core/agent/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `inserted` | required | [`event:agent/inbox/spliced.data.inserted`](#persistence-type-eventagentinboxspliceddatainserted) | +| `outcome` | optional | `"canceled"` | +| `removedCount` | optional | `number` | +| `start` | required | `number` | +| `target` | required | [`packages/core/agent/src/types.ts#InboxTarget`](#persistence-type-packagescoreagentsrctypestsinboxtarget) | + + + +### `event:agent/inbox/spliced.data.inserted` + +SHA-256: `86560bf0779bb2d93975405fe895d9d42c1020ab49fbbd2a06e1b3c74fbbc257` + +Array of [`packages/llm/llm/src/message.ts#UserMessage`](#persistence-type-packagesllmllmsrcmessagetsusermessage). + + + +### `event:agent/inbox/spliced.data.inserted[0].content` + +SHA-256: `0e560aad8fa9f338a60e76cbce6593ce93486ab30d6dd7833b8b5b07c6e6963d` + +Array of [`packages/llm/llm/src/types.ts#ContentBlock`](#persistence-type-packagesllmllmsrctypestscontentblock). + + + +### `event:agent/inbox/spliced.data.inserted[0].content[0][2].attachment.originalDimensions` + +SHA-256: `0f46153645c297846a3fad3911636659ca4717a5e7d71c9f50bffacd312115ef` + +Sources: [`packages/attachment/attachment/src/types.ts:28`](../packages/attachment/attachment/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `height` | required | `number` | +| `width` | required | `number` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[0]` + +SHA-256: `ada310bf0bdb8fed51f3b56ea63f6ea6b18bbd587f04fccb63a14ab0b2a24e05` + +Sources: [`packages/core/session/src/types.ts:189`](../packages/core/session/src/types.ts) · [`packages/interaction/commands/src/types.ts:75`](../packages/interaction/commands/src/types.ts) · [`packages/interaction/commands/src/types.ts:79`](../packages/interaction/commands/src/types.ts) · [`packages/llm/llm/src/message.ts:103`](../packages/llm/llm/src/message.ts) · [`packages/session/session-title/src/types.ts:35`](../packages/session/session-title/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"user"` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[1]` + +SHA-256: `0ddab4577db37a3e0d9e3a91761735c8ef6f5629d526ab8ecaeeea19c097c8f8` + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"plugin"` | +| `plugin` | required | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[10]` + +SHA-256: `6a4f72e2e179e17b922f2a9392c0e1c8f8c707f32494372454850a3eb184a6e7` + +Sources: [`packages/api/session-controller/src/types.ts:380`](../packages/api/session-controller/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `clientTimeZone` | optional | `string` | +| `kind` | required | `"user"` | +| `rpcId` | required | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[15].references` + +SHA-256: `df843efd87c78cf8de98f8e0d0216956cd568d0d0e56a22c853663e274aa1a18` + +Array of [`event:agent/inbox/spliced.data.inserted[0].source[15].references[0]`](#persistence-type-eventagentinboxspliceddatainserted0source15references0). + + + +### `event:agent/inbox/spliced.data.inserted[0].source[15].references[0]` + +SHA-256: `4f91f187baeabef5c7950c4780d31bbe39f3345dff591a6cf26ac7e135ac1cf4` + +Sources: [`packages/context/session-reference/src/types.ts:18`](../packages/context/session-reference/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `capturedFormatVersion` | optional | `number` | +| `capturedThroughSeq` | required | [`event:compaction/end.data.turn`](#persistence-type-eventcompactionenddataturn) | +| `compacted` | required | `boolean` | +| `inputIndex` | required | `number` | +| `label` | required | `string` | +| `omittedBytes` | required | `number` | +| `omittedMessages` | required | `number` | +| `originalMessages` | required | `number` | +| `retainedMessages` | required | `number` | +| `sessionId` | required | `string` | +| `truncated` | required | `boolean` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[16].entries` + +SHA-256: `aba175fb20ffc71112c55efe70458ff5d7dbfb83501892b4ba8d077f0ea5d7ba` + +Array of [`event:agent/inbox/spliced.data.inserted[0].source[16].entries[0]`](#persistence-type-eventagentinboxspliceddatainserted0source16entries0). + + + +### `event:agent/inbox/spliced.data.inserted[0].source[16].entries[0]` + +SHA-256: `83079c8a3f733ac3fa603eefa0fbb4737ca4d8b69e7e2e7298b60f9ac4098693` + +Sources: [`packages/skill/tool-skill/src/index.ts:40`](../packages/skill/tool-skill/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| `description` | required | `string` | +| `name` | required | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[17].changes` + +SHA-256: `2ab12a0e345658f9342816d773fe610f049982bcad82631f43b9205abe43e5ee` + +Array of [`packages/context/agent-instructions/src/render.ts#AgentInstructionChange`](#persistence-type-packagescontextagent-instructionssrcrendertsagentinstructionchange). + + + +### `event:agent/inbox/spliced.data.inserted[0].source[17].changes[0].action` + +SHA-256: `904bf499d98be4a3a5f836fcac533b6c4899569b018071e6604e1d32657d00fd` + +One of: + +- `"remove"` +- `"replace"` +- `"set"` + + + +### `event:agent/inbox/spliced.data.inserted[0].source[19]` + +SHA-256: `9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94` + +Sources: [`packages/webhook/webhook/src/types.ts:74`](../packages/webhook/webhook/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `deliveryId` | required | `string` | +| `form` | required | `"notice"` | +| `kind` | required | `"webhook"` | +| `provider` | required | `string` | +| `ruleId` | required | `string` | +| `source` | required | `string` | +| `summary` | required | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[2]` + +SHA-256: `dda6e111a8ccfc12f6e742e977a1a4e3549e8c03732a5ff6c8d7c3d4bcb7a412` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"instructions"` | +| `kind` | required | `"plugin"` | +| `plugin` | required | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[3]` + +SHA-256: `2f90a7b3646fea3e677bd7b4000aa96f3b8964f107b614e204e11ad4051c24fc` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"catalog"` | +| `kind` | required | `"plugin"` | +| `plugin` | required | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[4]` + +SHA-256: `e8d26e6993445743e6c4342c31b284c7d8f5574369abdd6021183af1a6b3538f` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"snapshot"` | +| `kind` | required | `"plugin"` | +| `plugin` | required | `string` | +| `sections` | required | [`event:agent/inbox/spliced.data.inserted[0].source[4].sections`](#persistence-type-eventagentinboxspliceddatainserted0source4sections) | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[4].sections` + +SHA-256: `c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262` + +Array of [`packages/llm/llm/src/message.ts#ContextSnapshotSection`](#persistence-type-packagesllmllmsrcmessagetscontextsnapshotsection). + + + +### `event:agent/inbox/spliced.data.inserted[0].source[5]` + +SHA-256: `b4d298d90c798fa4d1f5b030ce3ce8031c4c41beafdb05323bec8b2a06903b43` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"notice"` | +| `kind` | required | `"plugin"` | +| `plugin` | required | `string` | +| `summary` | required | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[6]` + +SHA-256: `2ce7ef3b3daebf95815c7ae454a751d3e67ca7a22e2e0734fe2942df393bb66c` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"relay"` | +| `kind` | required | `"plugin"` | +| `plugin` | required | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[7]` + +SHA-256: `85e1bd563d6c80c1f377f99f91103fec4e315148a7330f3b3dae7c2d04e33b01` + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"recall"` | +| `kind` | required | `"plugin"` | +| `plugin` | required | `string` | + + + +### `event:approval/asked` + +SHA-256: `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:approval/asked.data`](#persistence-type-eventapprovalaskeddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"approval/asked"` | + + + +### `event:approval/asked.data` + +SHA-256: `45f2a07ea94573123b6ae14db787a40062dec3834f36d8be92c523d40ba28448` + +Sources: [`packages/interaction/user-approval/src/types.ts:44`](../packages/interaction/user-approval/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `callId` | optional | `string` | +| `id` | required | `string` | +| `reason` | optional | `string` | +| `toolName` | required | `string` | + + + +### `event:approval/decided` + +SHA-256: `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:approval/decided.data`](#persistence-type-eventapprovaldecideddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"approval/decided"` | + + + +### `event:approval/decided.data` + +SHA-256: `93cbbc5a7fd0b1aa1ff1b9a6bb5ec52b073f48e5a5200365c45c0cd54059e1b9` + +Sources: [`packages/interaction/user-approval/src/types.ts:55`](../packages/interaction/user-approval/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `id` | required | `string` | +| `outcome` | required | [`packages/interaction/user-approval/src/types.ts#ApprovalOutcome`](#persistence-type-packagesinteractionuser-approvalsrctypestsapprovaloutcome) | + + + +### `event:approval/policy` + +SHA-256: `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:approval/policy.data`](#persistence-type-eventapprovalpolicydata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"approval/policy"` | + + + +### `event:approval/policy.data` + +SHA-256: `11f70b595aa76897fc4e25a352bfa718c659f44b799e5aa14e84e5a38fe72912` + +Sources: [`packages/interaction/user-approval/src/index.ts:33`](../packages/interaction/user-approval/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| `policy` | required | [`packages/interaction/user-approval/src/index.ts#ApprovalPolicy`](#persistence-type-packagesinteractionuser-approvalsrcindextsapprovalpolicy) | +| `source` | optional | `"delegation"` | + + + +### `event:assistant/attempt` + +SHA-256: `3516f54b427775f483febe8e938e4e10f9afd3583610861547bbe2999ed4d8d9` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:assistant/attempt.data`](#persistence-type-eventassistantattemptdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"assistant/attempt"` | + + + +### `event:assistant/attempt.data` + +SHA-256: `8725743fcd3db5ddaf3e0aed550cd5d95785d8e30c9d4cbec11b7c90198d3603` + +Sources: [`packages/core/session/src/types.ts:335`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `step` | required | `number` | +| `stream` | required | [`event:assistant/attempt.data.stream`](#persistence-type-eventassistantattemptdatastream) | +| `turn` | required | `number` | + + + +### `event:assistant/attempt.data.stream` + +SHA-256: `86755151e6c4694ce5ae9511313e1e50f9fb6ead49c67d0a93c056e8650396c4` + +Array of [`packages/llm/llm/src/assistant-stream.ts#AssistantStreamRecord`](#persistence-type-packagesllmllmsrcassistant-streamtsassistantstreamrecord). + + + +### `event:assistant/attempt.data.stream[0][0]` + +SHA-256: `e72269f931559e901e5e8fe1cfcbd0669fb795f43cc038ff39fba62f60403c33` + +Sources: [`packages/llm/llm/src/assistant-stream.ts:21`](../packages/llm/llm/src/assistant-stream.ts) + +| Property | Presence | Type | +|---|---|---| +| `dt` | required | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `index` | required | `number` | +| `texts` | required | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `time0` | required | `number` | +| `type` | required | `"text-chunks"` | + + + +### `event:assistant/attempt.data.stream[0][0].texts` + +SHA-256: `93c33d9687613293f8c95d46c4d922fe9ceae83b84c384beff5c3315abe005f2` + +Array of `string`. + + + +### `event:assistant/attempt.data.stream[0][1]` + +SHA-256: `71bb3254ec8f1e491ce63b762a7b5f18709de23aa4e8abcc8b886f4b6ce051dd` + +Sources: [`packages/llm/llm/src/assistant-stream.ts:28`](../packages/llm/llm/src/assistant-stream.ts) + +| Property | Presence | Type | +|---|---|---| +| `dt` | required | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `index` | required | `number` | +| `texts` | required | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `time0` | required | `number` | +| `type` | required | `"reasoning-chunks"` | + + + +### `event:assistant/attempt.data.stream[0][2]` + +SHA-256: `3860b4757d512a2869be833acb0faf834335b1b056bf3f83df5474dce95f8fa6` + +Sources: [`packages/llm/llm/src/assistant-stream.ts:35`](../packages/llm/llm/src/assistant-stream.ts) + +| Property | Presence | Type | +|---|---|---| +| `args` | required | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `dt` | required | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `id` | required | `string` | +| `index` | required | `number` | +| `name` | optional | `string` | +| `time0` | required | `number` | +| `type` | required | `"tool-call-chunks"` | + + + +### `event:assistant/attempt.data.stream[0][3]` + +SHA-256: `3a896379a74576c5a527e4720be2fe6807d3225c27134e978a2b89a94068368e` + +Sources: [`packages/llm/llm/src/assistant-stream.ts:44`](../packages/llm/llm/src/assistant-stream.ts) + +| Property | Presence | Type | +|---|---|---| +| `chunk` | required | [`packages/llm/llm/src/types.ts#StreamChunk`](#persistence-type-packagesllmllmsrctypestsstreamchunk) | +| `time` | required | `number` | +| `type` | required | `"chunk"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[0]` + +SHA-256: `647cb2e3aeb58e27d1d4d60011f208bb66dce257e8654193c533b47c4c22c253` + +Sources: [`packages/llm/llm/src/types.ts:391`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `blockType` | required | [`packages/llm/llm/src/types.ts#ContentBlockType`](#persistence-type-packagesllmllmsrctypestscontentblocktype) | +| `index` | required | `number` | +| `type` | required | `"block-start"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[1]` + +SHA-256: `95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65` + +Sources: [`packages/llm/llm/src/types.ts:392`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `index` | required | `number` | +| `text` | required | `string` | +| `type` | required | `"text-delta"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[2]` + +SHA-256: `2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095` + +Sources: [`packages/llm/llm/src/types.ts:393`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `index` | required | `number` | +| `text` | required | `string` | +| `type` | required | `"reasoning-delta"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[3]` + +SHA-256: `88a610b831be6d92612a464be5c7d0b88ec24d97bd299837bf74bf7672183c9a` + +Sources: [`packages/llm/llm/src/types.ts:394`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `argumentsDelta` | required | `string` | +| `id` | required | `string` | +| `index` | required | `number` | +| `name` | optional | `string` | +| `type` | required | `"tool-call-delta"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[4]` + +SHA-256: `6326a7ecfed005f3d54b737ab9d702da4daddb75cfc635d9096e9abb4788024e` + +Sources: [`packages/llm/llm/src/types.ts:395`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `block` | required | [`packages/llm/llm/src/types.ts#ContentBlock`](#persistence-type-packagesllmllmsrctypestscontentblock) | +| `index` | required | `number` | +| `type` | required | `"block-end"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[5]` + +SHA-256: `f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515` + +Sources: [`packages/llm/llm/src/types.ts:396`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `type` | required | `"usage"` | +| `usage` | required | [`packages/llm/llm/src/types.ts#TokenUsage`](#persistence-type-packagesllmllmsrctypeststokenusage) | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6]` + +SHA-256: `a4ed3446d32625902170fce527fb64a0283d6f4817930f54a16fdce3a482b36a` + +Sources: [`packages/llm/llm/src/types.ts:397`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `reason` | required | [`packages/llm/llm/src/types.ts#FinishReason`](#persistence-type-packagesllmllmsrctypestsfinishreason) | +| `replayState` | optional | [`packages/llm/llm/src/types.ts#ReplayEnvelope`](#persistence-type-packagesllmllmsrctypestsreplayenvelope) | +| `type` | required | `"finish"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].reason[0]` + +SHA-256: `64cfc4dbd3739f0ee040689482cb27058890f1d6113abb1de756836318aeebbc` + +Sources: [`packages/llm/llm/src/types.ts:134`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `failure` | required | [`packages/llm/llm/src/types.ts#LlmFailure`](#persistence-type-packagesllmllmsrctypestsllmfailure) | +| `kind` | required | `"aborted"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].reason[1]` + +SHA-256: `ca13866102dfc53a503fccddeebc9caf667b8a06ef27217dd65de29f0495e65d` + +Sources: [`packages/llm/llm/src/types.ts:135`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `failure` | required | [`packages/llm/llm/src/types.ts#LlmFailure`](#persistence-type-packagesllmllmsrctypestsllmfailure) | +| `kind` | required | `"error"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].reason[2]` + +SHA-256: `ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7` + +Sources: [`packages/core/session/src/types.ts:213`](../packages/core/session/src/types.ts) · [`packages/llm/llm/src/types.ts:133`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"max-tokens"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].reason[3]` + +SHA-256: `1d76406cbb2ddadb0dbbb51e9d8bc6cf678fed455cf8eb512f36f7c86d37a62f` + +Sources: [`packages/llm/llm/src/types.ts:131`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"stop"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].reason[4]` + +SHA-256: `287211846beea1eccb5f9acee00d20388a38fcb2e95858fa2332d6f58529379a` + +Sources: [`packages/llm/llm/src/types.ts:132`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"tool-calls"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].replayState.blocks` + +SHA-256: `b1664fa89f8eab49a14e7e3168d8bc4da4aa9fd76332f6c4a053d839e5276ae7` + +Array of `unknown` (opaque). + + + +### `event:assistant/message` + +SHA-256: `a58ca1bed502d425a8b65cac02f59717498cd296de9513c6f81b83b791c20e9b` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:assistant/message.data`](#persistence-type-eventassistantmessagedata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `surfaceOp` | required | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | required | `number` | +| `type` | required | `"assistant/message"` | + + + +### `event:assistant/message.data` + +SHA-256: `a2418dd9dd938f04a3eaf1f27694b33facfb931b6e495dcfb4d5fcfa2ef82b28` + +Sources: [`packages/core/session/src/types.ts:321`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `interrupted` | optional | `true` | +| `message` | required | [`packages/llm/llm/src/message.ts#AssistantMessage`](#persistence-type-packagesllmllmsrcmessagetsassistantmessage) | +| `step` | required | `number` | +| `stream` | required | [`event:assistant/attempt.data.stream`](#persistence-type-eventassistantattemptdatastream) | +| `turn` | required | `number` | +| `usage` | optional | [`packages/llm/llm/src/types.ts#TokenUsage`](#persistence-type-packagesllmllmsrctypeststokenusage) | + + + +### `event:command/done` + +SHA-256: `15196447222782e773eb943c92b18316ce96b9af0f0cfddb6e57ba8274ecc5ff` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:command/done.data`](#persistence-type-eventcommanddonedata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"command/done"` | + + + +### `event:command/done.data` + +SHA-256: `5b6fb1f226ff56402db08a6aae82af76100b14056271875f69ec526a1ae05d51` + +Sources: [`packages/interaction/commands/src/types.ts:112`](../packages/interaction/commands/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `commandId` | required | `string` | +| `kind` | required | [`event:command/done.data.kind`](#persistence-type-eventcommanddonedatakind) | +| `sourceEventSeq` | optional | `number` | +| `text` | optional | `string` | + + + +### `event:command/done.data.kind` + +SHA-256: `1ab2ef1296f157ba96998f179bb5daa9d21a82480da2901127aeab2730797c92` + +One of: + +- `"error"` +- `"success"` + + + +### `event:command/run` + +SHA-256: `37184378c6439257d105c4e2022d80fc9c3a3f7c7f6ac661b00bc9f18d871006` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:command/run.data`](#persistence-type-eventcommandrundata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"command/run"` | + + + +### `event:command/run.data` + +SHA-256: `a853902cfab417b8f08aa51ad6855fea46f34ed4588a74e47403035111d0fb86` + +Sources: [`packages/interaction/commands/src/types.ts:105`](../packages/interaction/commands/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `args` | optional | `string` | +| `commandId` | required | `string` | +| `name` | required | `string` | +| `source` | required | [`event:agent/inbox/spliced.data.inserted[0].source[0]`](#persistence-type-eventagentinboxspliceddatainserted0source0) | + + + +### `event:compaction/end` + +SHA-256: `b0127044ab31a702bddfd785d345f5abd7a70876746e895ce443afa3e60ddf2d` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:compaction/end.data`](#persistence-type-eventcompactionenddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"compaction/end"` | + + + +### `event:compaction/end.data` + +SHA-256: `ca59530d1a4f3e191b4032bc4a20eded0d827db8b5a7efb27b3af9e9b456c6f2` + +Sources: [`packages/compaction/compaction/src/types.ts:72`](../packages/compaction/compaction/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `compactionId` | required | `string` | +| `error` | optional | `string` | +| `sourceCommandId` | optional | `string` | +| `turn` | required | [`event:compaction/end.data.turn`](#persistence-type-eventcompactionenddataturn) | + + + +### `event:compaction/end.data.turn` + +SHA-256: `3bd652ebfa8726b3ce3937a4f1bd2759e02f86b3a08a20f9e41a8513656fbc5f` + +Sources: [`packages/core/session/src/types.ts:64`](../packages/core/session/src/types.ts) + +One of: + +- `null` +- `number` + + + +### `event:compaction/prune` + +SHA-256: `7f7fd5a6b0064f597534b29ff62ef26e786dffccf5e14f654a7d4fcea2c35f04` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:compaction/prune.data`](#persistence-type-eventcompactionprunedata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"compaction/prune"` | + + + +### `event:compaction/prune.data` + +SHA-256: `5dd766ccd1702a1142ad8b4bd44f2242f8e8a6752669ad3de0a5d224329b1438` + +Sources: [`packages/compaction/compaction/src/types.ts:82`](../packages/compaction/compaction/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `shadowedRange` | required | [`event:compaction/prune.data.shadowedRange`](#persistence-type-eventcompactionprunedatashadowedrange) | +| `shadowedSeqs` | required | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `shadowedTokenCount` | required | `number` | + + + +### `event:compaction/prune.data.shadowedRange` + +SHA-256: `9e41386b3a0c9572b0d63078492ebb3997da7d3a830d44e0259418bd02f4bcb2` + +Sources: [`packages/compaction/compaction/src/types.ts:38`](../packages/compaction/compaction/src/types.ts) · [`packages/compaction/compaction/src/types.ts:84`](../packages/compaction/compaction/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `end` | required | `number` | +| `start` | required | `number` | + + + +### `event:compaction/start` + +SHA-256: `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:compaction/start.data`](#persistence-type-eventcompactionstartdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"compaction/start"` | + + + +### `event:compaction/start.data` + +SHA-256: `3e4e7683b0192c9f0e6a75c59076108f017892cdb302a98032c2632fd79c356f` + +Sources: [`packages/compaction/compaction/src/types.ts:24`](../packages/compaction/compaction/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `compactionId` | required | `string` | +| `sourceCommandId` | optional | `string` | +| `turn` | required | [`event:compaction/end.data.turn`](#persistence-type-eventcompactionenddataturn) | + + + +### `event:compaction/summary` + +SHA-256: `eb1d32dcd3d76833392f351e039dcbd40686a1ab24b252a7a5003426f9cf4c89` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:compaction/summary.data`](#persistence-type-eventcompactionsummarydata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"compaction/summary"` | + + + +### `event:compaction/summary.data` + +SHA-256: `0a6fae8c3249943e23a024ae6c0d9dd707df9bff0badc69786f0dc8ad9482d8e` + +One of: + +- [`event:compaction/summary.data[0]`](#persistence-type-eventcompactionsummarydata0) +- [`event:compaction/summary.data[1]`](#persistence-type-eventcompactionsummarydata1) + + + +### `event:compaction/summary.data[0]` + +SHA-256: `1e75cbc0af53f62e5685d751108dc9f6320fe835aed222e579a2a0080dc8fc59` + +| Property | Presence | Type | +|---|---|---| +| `compactionId` | required | `string` | +| `llmStreamCall` | required | `true` | +| `maxTokens` | optional | `number` | +| `model` | required | `string` | +| `provider` | required | `string` | +| `rawOutput` | required | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `shadowedRange` | required | [`event:compaction/prune.data.shadowedRange`](#persistence-type-eventcompactionprunedatashadowedrange) | +| `shadowedSeqs` | required | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `shadowedTokenCount` | required | `number` | +| `sourceCommandId` | optional | `string` | +| `summary` | required | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `usage` | optional | [`packages/llm/llm/src/types.ts#TokenUsage`](#persistence-type-packagesllmllmsrctypeststokenusage) | + + + +### `event:compaction/summary.data[1]` + +SHA-256: `ac5a8baa3dda8fa667b4975d6a8b40a9bde47d8d754ca8ed6b8ce923a1651828` + +| Property | Presence | Type | +|---|---|---| +| `compactionId` | required | `string` | +| `maxTokens` | optional | `number` | +| `model` | required | `string` | +| `provider` | required | `string` | +| `rawOutput` | optional | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `shadowedRange` | required | [`event:compaction/prune.data.shadowedRange`](#persistence-type-eventcompactionprunedatashadowedrange) | +| `shadowedSeqs` | required | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `shadowedTokenCount` | required | `number` | +| `sourceCommandId` | optional | `string` | +| `summary` | required | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `usage` | optional | [`packages/llm/llm/src/types.ts#TokenUsage`](#persistence-type-packagesllmllmsrctypeststokenusage) | + + + +### `event:deliverables/presented` + +SHA-256: `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:deliverables/presented.data`](#persistence-type-eventdeliverablespresenteddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"deliverables/presented"` | + + + +### `event:deliverables/presented.data` + +SHA-256: `1528539c63db8b23506f0209a99ce77d8ad138adfbfcee3d4769b7382d93756c` + +Sources: [`packages/fs/tool-present/src/types.ts:15`](../packages/fs/tool-present/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `callId` | required | `string` | +| `files` | required | [`event:deliverables/presented.data.files`](#persistence-type-eventdeliverablespresenteddatafiles) | +| `turn` | required | `number` | + + + +### `event:deliverables/presented.data.files` + +SHA-256: `1d9cb3caa96100b18b1911fa3ff8c751ef6992971a03c5f4d9aefc1a0004a345` + +Array of [`packages/fs/tool-present/src/types.ts#PresentedFile`](#persistence-type-packagesfstool-presentsrctypestspresentedfile). + + + +### `event:feedback/message-delete` + +SHA-256: `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/feedback/message-feedback/src/types.ts#MessageFeedbackDelete`](#persistence-type-packagesfeedbackmessage-feedbacksrctypestsmessagefeedbackdelete) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"feedback/message-delete"` | + + + +### `event:feedback/message-put` + +SHA-256: `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/feedback/message-feedback/src/types.ts#MessageFeedbackPut`](#persistence-type-packagesfeedbackmessage-feedbacksrctypestsmessagefeedbackput) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"feedback/message-put"` | + + + +### `event:feedback/message-put.data.item.category` + +SHA-256: `3305a4bb6b6c29b482ba91d59c60b47e8f98282d3dacc8c29e4a4c2712209bdd` + +One of: + +- `"instruction-following"` +- `"other"` +- `"product-interaction"` +- `"resource-cost"` +- `"security-privacy-permission"` +- `"service-stability"` +- `"task-result"` + + + +### `event:feedback/record` + +SHA-256: `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/feedback/command-feedback/src/types.ts#FeedbackRecord`](#persistence-type-packagesfeedbackcommand-feedbacksrctypestsfeedbackrecord) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"feedback/record"` | + + + +### `event:goal/change` + +SHA-256: `763c8a20af487263a0080548274f7437ef4a86e0ba8769127d1ced10a72c664d` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/goal/goal/src/domain.ts#GoalChangeMeta`](#persistence-type-packagesgoalgoalsrcdomaintsgoalchangemeta) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"goal/change"` | + + + +### `event:goal/change.data[0].operation` + +SHA-256: `62e6a429ff3f390a46ac39fb468692032f42936ca0db19b53454184189401379` + +One of: + +- `"block"` +- `"complete"` +- `"create"` +- `"edit"` +- `"pause"` +- `"resume"` + + + +### `event:hook/invoked` + +SHA-256: `8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:hook/invoked.data`](#persistence-type-eventhookinvokeddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"hook/invoked"` | + + + +### `event:hook/invoked.data` + +SHA-256: `119abe2b9c0b2f46ecb31d82b9acba4d4b30e049f655e2475e8b4ae12d5a9e25` + +Sources: [`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `dialect` | required | [`packages/hooks/hook-protocol/src/types.ts#HookDialect`](#persistence-type-packageshookshook-protocolsrctypestshookdialect) | +| `handlerId` | required | `string` | +| `matcher` | optional | `string` | +| `point` | required | `string` | +| `turn` | required | `number` | + + + +### `event:hook/result` + +SHA-256: `e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:hook/result.data`](#persistence-type-eventhookresultdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"hook/result"` | + + + +### `event:hook/result.data` + +SHA-256: `8ad45ad053b4d89f8865c17cc9ff3b8748e86045f3f0fb816269a34af2619141` + +Sources: [`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-protocol/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `decision` | required | `string` | +| `durationMs` | required | `number` | +| `exitCode` | optional | `number` | +| `handlerId` | required | `string` | +| `point` | required | `string` | +| `stderrSummary` | optional | `string` | +| `turn` | required | `number` | + + + +### `event:llm/retry` + +SHA-256: `91c397f8f870e812e1dc5ac69c2745f9f105be9dc97dadc980d19ef415a65145` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/llm/llm-retry/src/types.ts#LlmRetryEventData`](#persistence-type-packagesllmllm-retrysrctypestsllmretryeventdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"llm/retry"` | + + + +### `event:llm/retry-started` + +SHA-256: `48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/llm/llm-retry/src/types.ts#LlmRetryStartedEventData`](#persistence-type-packagesllmllm-retrysrctypestsllmretrystartedeventdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"llm/retry-started"` | + + + +### `event:llm/retry.data[0]` + +SHA-256: `9096d1dd86a3da77a593e12cd95e596b2ef965fa94fcbee40e4bdcf0433924a1` + +Sources: [`packages/llm/llm-retry/src/types.ts:17`](../packages/llm/llm-retry/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `delayMs` | required | `number` | +| `failure` | required | [`packages/llm/llm/src/types.ts#LlmFailure`](#persistence-type-packagesllmllmsrctypestsllmfailure) | +| `maxRetries` | required | `number` | +| `mode` | required | `"normal"` | +| `policyKey` | required | `string` | +| `provider` | required | `string` | +| `retry` | required | `number` | +| `retryId` | required | `string` | +| `step` | required | `number` | +| `turn` | required | `number` | + + + +### `event:llm/retry.data[1]` + +SHA-256: `c0f91c064454e9ae25a4b5caacdc487712aee3acf414b07b7d5f7fd3530a0452` + +Sources: [`packages/llm/llm-retry/src/types.ts:30`](../packages/llm/llm-retry/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `delayMs` | required | `number` | +| `failure` | required | [`packages/llm/llm/src/types.ts#LlmFailure`](#persistence-type-packagesllmllmsrctypestsllmfailure) | +| `mode` | required | `"always"` | +| `policyKey` | required | `string` | +| `provider` | required | `string` | +| `retry` | required | `number` | +| `retryId` | required | `string` | +| `step` | required | `number` | +| `turn` | required | `number` | + + + +### `event:model/selection` + +SHA-256: `35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/api/session-controller/src/types.ts#ModelSelection`](#persistence-type-packagesapisession-controllersrctypestsmodelselection) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"model/selection"` | + + + +### `event:permission/preset` + +SHA-256: `5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:permission/preset.data`](#persistence-type-eventpermissionpresetdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"permission/preset"` | + + + +### `event:permission/preset.data` + +SHA-256: `8a9c990773f798eac10ca8878e6bcc531ee080bc589cac956730e462cc6a09ad` + +Sources: [`packages/interaction/permission-presets/src/index.ts:54`](../packages/interaction/permission-presets/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| `preset` | required | `string` | + + + +### `event:plan/mode` + +SHA-256: `a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:plan/mode.data`](#persistence-type-eventplanmodedata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"plan/mode"` | + + + +### `event:plan/mode.data` + +SHA-256: `20a57544bbf204dacd1ae6beb2ab29444365c3c869f2afdf489dcdcbc3d9c8c5` + +Sources: [`packages/plan/plan-mode/src/index.ts:47`](../packages/plan/plan-mode/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| `active` | required | `boolean` | + + + +### `event:request/context` + +SHA-256: `37cbc9cf06d494cfe5c67f078af1605eacb8e4c2c853e9e26455b73de0ee7ccf` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/core/session/src/types.ts#RequestContext`](#persistence-type-packagescoresessionsrctypestsrequestcontext) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"request/context"` | + + + +### `event:request/header` + +SHA-256: `8ea13bcd983eab596dbfd41c5c2b62a0b318b71cd2a987a6fd752c6ced7e4489` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:request/header.data`](#persistence-type-eventrequestheaderdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"request/header"` | + + + +### `event:request/header.data` + +SHA-256: `2f0aefb72ab453c4bf21a6687f436775d359dc6f8f10410fd4c0615b20bd9d76` + +Sources: [`packages/core/session/src/types.ts:365`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `header` | required | [`packages/core/session/src/types.ts#EpochHeader`](#persistence-type-packagescoresessionsrctypestsepochheader) | +| `reason` | required | [`packages/core/session/src/types.ts#RequestHeaderReason`](#persistence-type-packagescoresessionsrctypestsrequestheaderreason) | +| `startsSeries` | optional | `true` | + + + +### `event:request/header.data.header.tools` + +SHA-256: `6d63f25a28b70801937a46b1058be5f52d4efbaf3516141470463ae469b59c23` + +Array of [`packages/llm/llm/src/types.ts#ToolSchema`](#persistence-type-packagesllmllmsrctypeststoolschema). + + + +### `event:request/header.data.header.tools[0].parameters` + +SHA-256: `0aa62ab51b36c0e64b134bd03ba8725d758744180a1a447da49beaad194f219f` + +| Property | Presence | Type | +|---|---|---| +| [`string`] | index signature | `unknown` (opaque) | + + + +### `event:sandbox/mode` + +SHA-256: `516da4cdd6d2f1e5ce488e648578ca51f40e458f707b803e5b24de870e799415` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:sandbox/mode.data`](#persistence-type-eventsandboxmodedata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"sandbox/mode"` | + + + +### `event:sandbox/mode.data` + +SHA-256: `9890113e97dd3541787bfe6704f2b223d3a890b5325ca0acae2b0398909b413e` + +Sources: [`packages/sandbox/sandbox-policy/src/session-mode.ts:33`](../packages/sandbox/sandbox-policy/src/session-mode.ts) + +| Property | Presence | Type | +|---|---|---| +| `mode` | required | [`packages/sandbox/sandbox/src/index.ts#SandboxMode`](#persistence-type-packagessandboxsandboxsrcindextssandboxmode) | +| `source` | optional | `"delegation"` | + + + +### `event:schedule/change` + +SHA-256: `2a7f86849ae54b3398ee49661a757c4fcb59a6192b7036ee2ff514617e13fb42` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/schedule/schedule/src/types.ts#ScheduleChange`](#persistence-type-packagesscheduleschedulesrctypestsschedulechange) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"schedule/change"` | + + + +### `event:session-log-deepseek/delivery-accepted` + +SHA-256: `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:session-log-deepseek/delivery-accepted.data`](#persistence-type-eventsession-log-deepseekdelivery-accepteddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"session-log-deepseek/delivery-accepted"` | + + + +### `event:session-log-deepseek/delivery-accepted.data` + +SHA-256: `8bb8c3751a6ce64b61aba1ec7ae798c00a5803c4d868a0430873216c6569ed52` + +Sources: [`packages/session/session-log-deepseek/src/types.ts:81`](../packages/session/session-log-deepseek/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `sessionFormatVersion` | optional | `number` | +| `sessionId` | required | `string` | +| `throughSeq` | required | `number` | + + + +### `event:session/end-seed` + +SHA-256: `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:session/end-seed.data`](#persistence-type-eventsessionend-seeddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"session/end-seed"` | + + + +### `event:session/end-seed.data` + +SHA-256: `17d1afb770d9941936130996da00dc86782cfef731d8d6526162c301256a4ac3` + +Sources: [`packages/core/session/src/types.ts:400`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `inherited` | optional | `true` | + + + +### `event:session/title` + +SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/session/session-title/src/types.ts#SessionTitleEventData`](#persistence-type-packagessessionsession-titlesrctypestssessiontitleeventdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"session/title"` | + + + +### `event:session/title-llm-request` + +SHA-256: `6091f64d426ada2adc646094a6fe662a1b81d9ee5770929fe76800524e2c89a8` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData`](#persistence-type-packagessessionsession-title-llmsrcindextssessiontitlellmrequesteventdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"session/title-llm-request"` | + + + +### `event:session/title-llm-request.data.messages` + +SHA-256: `f3cfc98798d8b0e726ee22b05b13ada5cb19e9cdbe105ef3529699de99ce357a` + +Array of [`packages/llm/llm/src/message.ts#Message`](#persistence-type-packagesllmllmsrcmessagetsmessage). + + + +### `event:session/title-llm-request.data.messages[0].role` + +SHA-256: `acd36e1717d2b4ef9e98a8e6fac17ca1203f3f448e5bca51677f129a8f4999e2` + +One of: + +- `"assistant"` +- `"system"` +- `"user"` + + + +### `event:session/title.data.source[0]` + +SHA-256: `1b5649238876e28811e80f10ec67a00c197ad3babd4ecc61a22b09064b88c597` + +Sources: [`packages/session/session-title/src/types.ts:29`](../packages/session/session-title/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"fallback"` | + + + +### `event:session/title.data.source[1]` + +SHA-256: `b81e9de81a481b24e1e1f9adf038bfc59e57b4f35ce62f8f1e45284186f4f812` + +Sources: [`packages/session/session-title/src/types.ts:30`](../packages/session/session-title/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"provider"` | +| `model` | optional | [`packages/session/session-title/src/types.ts#SessionTitleModelIdentity`](#persistence-type-packagessessionsession-titlesrctypestssessiontitlemodelidentity) | +| `provider` | required | `string` | + + + +### `event:step/end` + +SHA-256: `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:step/end.data`](#persistence-type-eventstependdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"step/end"` | + + + +### `event:step/end.data` + +SHA-256: `ef11a3b7fa2033ae4279545ff8a8a2ee4e301b9b73e5b91654c48e6d08283545` + +Sources: [`packages/core/session/src/types.ts:287`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:289`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `step` | required | `number` | +| `turn` | required | `number` | + + + +### `event:step/start` + +SHA-256: `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:step/end.data`](#persistence-type-eventstependdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"step/start"` | + + + +### `event:subagent/catalog` + +SHA-256: `ae1f7110feeec697b8cab42b68f7709aa7b3279764099dfb53c25890d2e5c871` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/subagent/subagent/src/catalog.ts#SubagentCatalogEvent`](#persistence-type-packagessubagentsubagentsrccatalogtssubagentcatalogevent) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"subagent/catalog"` | + + + +### `event:subagent/catalog.data[0]` + +SHA-256: `5d542ac7bdf13310c46e1a884dec9fb18ad8c6e153cff207e639cd7bf9d7e694` + +| Property | Presence | Type | +|---|---|---| +| `childCreatedAt` | required | `number` | +| `childId` | required | `string` | +| `label` | optional | `string` | +| `mode` | required | `"one-shot"` | +| `version` | required | `0` | + + + +### `event:subagent/catalog.data[1]` + +SHA-256: `0e10d7c096b47e61defe372a5fc8bd37d835199410eac44367f95c45082498bd` + +| Property | Presence | Type | +|---|---|---| +| `childCreatedAt` | required | `number` | +| `childId` | required | `string` | +| `label` | required | `string` | +| `mode` | required | `"continuable"` | +| `version` | required | `0` | + + + +### `event:subagent/descriptor` + +SHA-256: `b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/subagent/subagent/src/descriptor.ts#SubagentDescriptorData`](#persistence-type-packagessubagentsubagentsrcdescriptortssubagentdescriptordata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"subagent/descriptor"` | + + + +### `event:subagent/model-selection-policy` + +SHA-256: `a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:subagent/model-selection-policy.data`](#persistence-type-eventsubagentmodel-selection-policydata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"subagent/model-selection-policy"` | + + + +### `event:subagent/model-selection-policy.data` + +SHA-256: `0d47a8f3d847243c4488e755ad4cab7a04ac5d4621b9c81865524cf3f4d8536f` + +Sources: [`packages/subagent/tool-subagent/src/model-selection-state.ts:17`](../packages/subagent/tool-subagent/src/model-selection-state.ts) + +| Property | Presence | Type | +|---|---|---| +| `allowedModels` | required | [`event:subagent/model-selection-policy.data.allowedModels`](#persistence-type-eventsubagentmodel-selection-policydataallowedmodels) | + + + +### `event:subagent/model-selection-policy.data.allowedModels` + +SHA-256: `ba77b58995e36f14bb13886b0493d0a1a63d5db10b1a6cd9f6ac65a72cf832ec` + +Array of [`packages/session/session-title/src/types.ts#SessionTitleModelIdentity`](#persistence-type-packagessessionsession-titlesrctypestssessiontitlemodelidentity). + + + +### `event:system/message` + +SHA-256: `0ae7ccdc1cc5d9d43772ebd5c7357ff509011c3a6ba188d9c274d33df2542c42` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:system/message.data`](#persistence-type-eventsystemmessagedata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `sourceEventSeqs` | optional | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `surfaceOp` | required | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | required | `number` | +| `type` | required | `"system/message"` | + + + +### `event:system/message.data` + +SHA-256: `d489162978ec04f1f76f40a68c7715326f46ec4f696edf1f321efdf88545fc18` + +Sources: [`packages/core/session/src/types.ts:310`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `message` | required | [`packages/llm/llm/src/message.ts#SystemMessage`](#persistence-type-packagesllmllmsrcmessagetssystemmessage) | +| `step` | required | `number` | +| `turn` | required | `number` | + + + +### `event:system/message.data.message.source` + +SHA-256: `23cf7e0cf3fec1283b60fa15713506169bc9c079a08e124d1cb9733baca80b40` + +One of: + +- [`event:agent/inbox/spliced.data.inserted[0].source[4]`](#persistence-type-eventagentinboxspliceddatainserted0source4) +- [`event:agent/inbox/spliced.data.inserted[0].source[5]`](#persistence-type-eventagentinboxspliceddatainserted0source5) +- [`event:agent/inbox/spliced.data.inserted[0].source[3]`](#persistence-type-eventagentinboxspliceddatainserted0source3) +- [`event:agent/inbox/spliced.data.inserted[0].source[2]`](#persistence-type-eventagentinboxspliceddatainserted0source2) +- [`event:agent/inbox/spliced.data.inserted[0].source[7]`](#persistence-type-eventagentinboxspliceddatainserted0source7) +- [`event:agent/inbox/spliced.data.inserted[0].source[6]`](#persistence-type-eventagentinboxspliceddatainserted0source6) +- [`event:agent/inbox/spliced.data.inserted[0].source[1]`](#persistence-type-eventagentinboxspliceddatainserted0source1) + + + +### `event:team/member` + +SHA-256: `4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:team/member.data`](#persistence-type-eventteammemberdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"team/member"` | + + + +### `event:team/member.data` + +SHA-256: `b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1` + +Sources: [`packages/experimental/agent-team/src/types.ts:221`](../packages/experimental/agent-team/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `member` | required | [`packages/experimental/agent-team/src/types.ts#TeamMemberSnapshot`](#persistence-type-packagesexperimentalagent-teamsrctypeststeammembersnapshot) | +| `teamId` | required | `string` | +| `version` | required | `2` | + + + +### `event:team/member.data.member.context` + +SHA-256: `50cf02d875f8a1f1411f4a6fbcffab9120ccef5f74a47331fbf29480e5f68f48` + +One of: + +- `"fork"` +- `"fresh"` + + + +### `event:team/message/delivered` + +SHA-256: `48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:team/message/delivered.data`](#persistence-type-eventteammessagedelivereddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"team/message/delivered"` | + + + +### `event:team/message/delivered.data` + +SHA-256: `0bcfaf5566d271028429ac7b562cb59053c9d78b8ae14708c35fac835108d1b1` + +Sources: [`packages/experimental/agent-team/src/types.ts:227`](../packages/experimental/agent-team/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `messageId` | required | `string` | +| `targetId` | required | `string` | +| `teamId` | required | `string` | +| `version` | required | `2` | + + + +### `event:team/message/queued` + +SHA-256: `2ffcad547cb840f462eb1849fa5f6cb0e17eb93f5d07cc88b61d749b1b78d632` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:team/message/queued.data`](#persistence-type-eventteammessagequeueddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"team/message/queued"` | + + + +### `event:team/message/queued.data` + +SHA-256: `6132f7814b28287473605ebc6dacd63c7c699435452487da6152bc735b8ba8c4` + +Sources: [`packages/experimental/agent-team/src/types.ts:225`](../packages/experimental/agent-team/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `message` | required | [`packages/experimental/agent-team/src/types.ts#TeamMessageSnapshot`](#persistence-type-packagesexperimentalagent-teamsrctypeststeammessagesnapshot) | +| `teamId` | required | `string` | +| `version` | required | `2` | + + + +### `event:team/task` + +SHA-256: `d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:team/task.data`](#persistence-type-eventteamtaskdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"team/task"` | + + + +### `event:team/task.data` + +SHA-256: `a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c` + +Sources: [`packages/experimental/agent-team/src/types.ts:223`](../packages/experimental/agent-team/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `task` | required | [`packages/experimental/agent-team/src/types.ts#TeamTaskSnapshot`](#persistence-type-packagesexperimentalagent-teamsrctypeststeamtasksnapshot) | +| `teamId` | required | `string` | +| `version` | required | `2` | + + + +### `event:todo/write` + +SHA-256: `b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:todo/write.data`](#persistence-type-eventtodowritedata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"todo/write"` | + + + +### `event:todo/write.data` + +SHA-256: `79ddc907312f97cf9553d4841e8b3fc937097bd8d97900a790efad3c05255e99` + +Sources: [`packages/todo/tool-todo/src/types.ts:31`](../packages/todo/tool-todo/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `todos` | required | [`event:todo/write.data.todos`](#persistence-type-eventtodowritedatatodos) | + + + +### `event:todo/write.data.todos` + +SHA-256: `4b7ddaf3710b88ff9b8df66304f5f97bf03688fd19730eedc2e9535203c820ea` + +Array of [`packages/todo/tool-todo/src/types.ts#TodoItem`](#persistence-type-packagestodotool-todosrctypeststodoitem). + + + +### `event:todo/write.data.todos[0].status` + +SHA-256: `fd0d91a1f9dd2efb8959bdf5e07f8f0a98b1cdfa38ce8d4732ebde0b44c3abd4` + +One of: + +- `"completed"` +- `"in_progress"` +- `"pending"` + + + +### `event:tool-workflow/agent-end` + +SHA-256: `babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentEndData`](#persistence-type-packagesworkflowtool-workflowsrctypeststoolworkflowagentenddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"tool-workflow/agent-end"` | + + + +### `event:tool-workflow/agent-start` + +SHA-256: `5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentStartData`](#persistence-type-packagesworkflowtool-workflowsrctypeststoolworkflowagentstartdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"tool-workflow/agent-start"` | + + + +### `event:tool-workflow/run-end` + +SHA-256: `42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunEndData`](#persistence-type-packagesworkflowtool-workflowsrctypeststoolworkflowrunenddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"tool-workflow/run-end"` | + + + +### `event:tool-workflow/run-start` + +SHA-256: `c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunStartData`](#persistence-type-packagesworkflowtool-workflowsrctypeststoolworkflowrunstartdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"tool-workflow/run-start"` | + + + +### `event:tool/call` + +SHA-256: `3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:tool/call.data`](#persistence-type-eventtoolcalldata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"tool/call"` | + + + +### `event:tool/call.data` + +SHA-256: `e4c18e294232c3ba6c9f1999f168263cc55956147cfe4121720899be8e52edd1` + +Sources: [`packages/core/session/src/types.ts:341`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `arguments` | required | `string` | +| `callId` | required | `string` | +| `name` | required | `string` | +| `step` | required | `number` | +| `turn` | required | `number` | + + + +### `event:tool/ptc-dispatch` + +SHA-256: `da807583a19f4ebe2bc9115c9686016e1521e405bdfaced296ae652b91e8e5f6` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/core/tools/src/types.ts#PtcDispatchEventData`](#persistence-type-packagescoretoolssrctypestsptcdispatcheventdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"tool/ptc-dispatch"` | + + + +### `event:tool/ptc-dispatch-start` + +SHA-256: `ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/core/tools/src/types.ts#PtcDispatchStartEventData`](#persistence-type-packagescoretoolssrctypestsptcdispatchstarteventdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"tool/ptc-dispatch-start"` | + + + +### `event:tool/result` + +SHA-256: `cc3e270ab982cbafff602b753647b6f056decb7fe4fd523afd2f157a65c3d622` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:tool/result.data`](#persistence-type-eventtoolresultdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `sourceEventSeqs` | optional | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `surfaceOp` | required | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | required | `number` | +| `type` | required | `"tool/result"` | + + + +### `event:tool/result.data` + +SHA-256: `a1082bd67be26461ebcc4cc9caba60ef548cd6ec625b51558d5cfa5b6b38718a` + +Sources: [`packages/core/session/src/types.ts:353`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `error` | optional | [`event:tool/result.data.error`](#persistence-type-eventtoolresultdataerror) | +| `message` | required | [`packages/llm/llm/src/message.ts#ToolResultMessage`](#persistence-type-packagesllmllmsrcmessagetstoolresultmessage) | +| `meta` | optional | [`event:tool/result.data.meta`](#persistence-type-eventtoolresultdatameta) | +| `step` | required | `number` | +| `turn` | required | `number` | + + + +### `event:tool/result.data.error` + +SHA-256: `55e3b6df99b0d19241e57618b04b84824c3df80ed102e440719139300ea703f6` + +Sources: [`packages/core/session/src/types.ts:358`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `code` | required | `string` | +| `name` | required | `string` | + + + +### `event:tool/result.data.message.content` + +SHA-256: `5eedf09e88b5ed9ca2d5efdb5a32cd4f51352564af5ff82927c17d841a2e3d9e` + +| Position | Presence | Type | +|---|---|---| +| 0 | required | [`packages/llm/llm/src/types.ts#ToolResultBlock`](#persistence-type-packagesllmllmsrctypeststoolresultblock) | + + + +### `event:tool/result.data.meta` + +SHA-256: `c592ce75aab73fcab19c1d7845684c72cf402b78d2e1f2833a58ecf9f3598ed6` + +Sources: [`packages/util/values/src/index.ts:4`](../packages/util/values/src/index.ts) + +One of: + +- [`event:tool/result.data.meta[5]`](#persistence-type-eventtoolresultdatameta5) +- [`event:tool/result.data.meta[5][0][6]`](#persistence-type-eventtoolresultdatameta506) +- `string` +- `boolean` +- `null` +- `number` + + + +### `event:tool/result.data.meta[5]` + +SHA-256: `72ec79127a9c0d0241b1106a74cc2b24c81ce467170f1c3c93f7b71a9496d227` + +Array of [`event:tool/result.data.meta`](#persistence-type-eventtoolresultdatameta). + + + +### `event:tool/result.data.meta[5][0][6]` + +SHA-256: `529e0ccd0e34079ab4ff21ed87f810cee0388246263bf22e8aba5986b56fae05` + +Sources: [`packages/util/values/src/index.ts:4`](../packages/util/values/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| [`string`] | index signature | [`event:tool/result.data.meta`](#persistence-type-eventtoolresultdatameta) | + + + +### `event:turn/end` + +SHA-256: `84c24f1209fc3e0153de6ac85d58fd6968b851f955e0e761b92321053956609e` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:turn/end.data`](#persistence-type-eventturnenddata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"turn/end"` | + + + +### `event:turn/end.data` + +SHA-256: `508c0856b8df8574e396880a756f087699b138630428441fefca4a4380f01a28` + +Sources: [`packages/core/session/src/types.ts:285`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `reason` | required | [`packages/core/session/src/types.ts#TurnEndReason`](#persistence-type-packagescoresessionsrctypeststurnendreason) | +| `turn` | required | `number` | + + + +### `event:turn/end.data.reason[0]` + +SHA-256: `b6ef9c6210be8350cd465f485cf520031fffa6bf3809c61a1e713e834c5982d0` + +Sources: [`packages/core/session/src/types.ts:201`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"completed"` | + + + +### `event:turn/end.data.reason[1]` + +SHA-256: `0a30c3be336cdd4f8f34e66543a3d46d611c43986edd9b3ce6cbb9b721d3b5d3` + +Sources: [`packages/core/session/src/types.ts:203`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"aborted"` | +| `reason` | required | [`packages/core/session/src/types.ts#TurnEndCancelCause`](#persistence-type-packagescoresessionsrctypeststurnendcancelcause) | + + + +### `event:turn/end.data.reason[1].reason[1]` + +SHA-256: `6c03546143cdf6ae88f1745ac98c5139efa1588d1415249bbc5f3104d32ab800` + +Sources: [`packages/core/session/src/types.ts:190`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"parent"` | + + + +### `event:turn/end.data.reason[1].reason[2]` + +SHA-256: `6c5a37d42ebdb12db4410699259335f1e149e3a0260425684c00b153afc8990d` + +Sources: [`packages/core/session/src/types.ts:191`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"hook"` | +| `reason` | required | `string` | + + + +### `event:turn/end.data.reason[1].reason[3]` + +SHA-256: `4bb451330ff699f36b45539ceb0eb2606ead72084e2c5bb637c36cc80e2252db` + +Sources: [`packages/core/session/src/types.ts:192`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"disposed"` | + + + +### `event:turn/end.data.reason[1].reason[4]` + +SHA-256: `eaa71be0fbf0f702df8ea6c11d0aa2f9782263d45a10d0fb14eda662f4bfe04c` + +Sources: [`packages/core/session/src/types.ts:195`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"legacy"` | + + + +### `event:turn/end.data.reason[2]` + +SHA-256: `023a355b2be0d688fb6e4df8388dadb942761d46cfe96d0f499aae9631a159d4` + +Sources: [`packages/core/session/src/types.ts:205`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"blocked"` | + + + +### `event:turn/end.data.reason[3]` + +SHA-256: `3dad205ad99fbda835c493ddc6bc93fe1441cef2f82774ffa2856364e2cede22` + +Sources: [`packages/core/session/src/types.ts:211`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `error` | required | [`packages/llm/llm/src/types.ts#LlmFailure`](#persistence-type-packagesllmllmsrctypestsllmfailure) | +| `kind` | required | `"error"` | + + + +### `event:turn/end.data.reason[5]` + +SHA-256: `56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23` + +Sources: [`packages/core/session/src/types.ts:220`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"interrupted"` | + + + +### `event:turn/start` + +SHA-256: `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`event:turn/start.data`](#persistence-type-eventturnstartdata) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"turn/start"` | + + + +### `event:turn/start.data` + +SHA-256: `f90eb4ab2d3897bc20c521a038e3119d581f791ec974b1a0004f63a8ab9e232d` + +Sources: [`packages/core/session/src/types.ts:276`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `turn` | required | `number` | + + + +### `event:user/message` + +SHA-256: `2e49ede7fb3a592cce8919ebe982f6ca1c1f67abe4def359ffc028c85e6d1ad8` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/llm/llm/src/message.ts#UserMessage`](#persistence-type-packagesllmllmsrcmessagetsusermessage) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `sourceEventSeqs` | optional | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `surfaceOp` | required | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | required | `number` | +| `type` | required | `"user/message"` | + + + +### `event:web/deepseek-search-llm-request` + +SHA-256: `cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`packages/web/web-search-deepseek/src/provider.ts#DeepSeekSearchLlmRequest`](#persistence-type-packageswebweb-search-deepseeksrcprovidertsdeepseeksearchllmrequest) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"web/deepseek-search-llm-request"` | + + + +### `event:web/deepseek-search-llm-request.data.body` + +SHA-256: `930a6567a10bb62ddd157bd8abdf4b182810c8c3b49d91309b3b009a5fed9731` + +Sources: [`packages/web/web-search-deepseek/src/provider.ts:62`](../packages/web/web-search-deepseek/src/provider.ts) + +| Property | Presence | Type | +|---|---|---| +| `max_tokens` | required | `number` | +| `messages` | required | [`event:web/deepseek-search-llm-request.data.body.messages`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodymessages) | +| `model` | required | `string` | +| `tools` | required | [`event:web/deepseek-search-llm-request.data.body.tools`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodytools) | + + + +### `event:web/deepseek-search-llm-request.data.body.messages` + +SHA-256: `b993441f8ce7d27b80e619e113e232ae8e62d3d5a8340f6bfe12d8c4c018e62f` + +| Position | Presence | Type | +|---|---|---| +| 0 | required | [`event:web/deepseek-search-llm-request.data.body.messages[0]`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodymessages0) | + + + +### `event:web/deepseek-search-llm-request.data.body.messages[0]` + +SHA-256: `9a2a9029f8d7ede05336980d8342737557f487b913bfd28853d0ab5214600ab5` + +Sources: [`packages/web/web-search-deepseek/src/provider.ts:65`](../packages/web/web-search-deepseek/src/provider.ts) + +| Property | Presence | Type | +|---|---|---| +| `content` | required | [`event:web/deepseek-search-llm-request.data.body.messages[0].content`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodymessages0content) | +| `role` | required | `"user"` | + + + +### `event:web/deepseek-search-llm-request.data.body.messages[0].content` + +SHA-256: `8f6d609794bf5afc01d7ccf3e03811d32f4b8047f58a2c9a98db0154c2ad7cb9` + +| Position | Presence | Type | +|---|---|---| +| 0 | required | [`event:web/deepseek-search-llm-request.data.body.messages[0].content[0]`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodymessages0content0) | + + + +### `event:web/deepseek-search-llm-request.data.body.messages[0].content[0]` + +SHA-256: `4e887768586528565381dadbddee6cef555874148089f4579e0b4b1ad096fc9c` + +Sources: [`packages/llm/llm/src/types.ts:54`](../packages/llm/llm/src/types.ts) · [`packages/web/web-search-deepseek/src/provider.ts:67`](../packages/web/web-search-deepseek/src/provider.ts) + +| Property | Presence | Type | +|---|---|---| +| `text` | required | `string` | +| `type` | required | `"text"` | + + + +### `event:web/deepseek-search-llm-request.data.body.tools` + +SHA-256: `4aed17ec726b5397c29f6cb4bcd7905dc1a12bb6140f1657f073cd24380f2af8` + +| Position | Presence | Type | +|---|---|---| +| 0 | required | [`event:web/deepseek-search-llm-request.data.body.tools[0]`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodytools0) | + + + +### `event:web/deepseek-search-llm-request.data.body.tools[0]` + +SHA-256: `2d11ca7b0d4493e244b74eba093227866b33249841e59a1e9bf56afed38604c3` + +Sources: [`packages/web/web-search-deepseek/src/provider.ts:72`](../packages/web/web-search-deepseek/src/provider.ts) + +| Property | Presence | Type | +|---|---|---| +| `max_uses` | required | `number` | +| `name` | required | `"web_search"` | +| `type` | required | `"web_search_20250305"` | + + + +### `"every"` + +SHA-256: `e42b0f40b5d0b628c6e1c44a8fb9eda0780037a81959e1b93d2dfb47b46b0e87` + +`"every"` + + + +### `"failed"` + +SHA-256: `b57a2889408de36e4516807194c73bdecd51a92f633ccd05db81bf049200f420` + +`"failed"` + + + +### `"fallback"` + +SHA-256: `d6e654b88b584fcdf290a56802b1730bc7b9847ae86baa4265ab7769eb48990c` + +`"fallback"` + + + +### `false` + +SHA-256: `0c4205180f75b9ee1e0d878bd9a9851628b5dc18448486389d9c5fdc3f1ccda3` + +`false` + + + +### `"feedback/message-delete"` + +SHA-256: `44818000e56acb97afdb5045ce235eb55e3fb1ccabc9fc7d0750bb38f36a7f8e` + +`"feedback/message-delete"` + + + +### `"feedback/message-put"` + +SHA-256: `6cda0d6d3b4336d83998fe580628426df81c2afbab5d768b8813b56d569120e8` + +`"feedback/message-put"` + + + +### `"feedback/record"` + +SHA-256: `c99e20441bf15341e9db54385e558e5c03d21dff2eb1ac08ccbf47c2d8747954` + +`"feedback/record"` + + + +### `"file"` + +SHA-256: `277b61f9c6f8170425fa6ffe06cfa14376757a896a4749cb82a687d7364c4fc9` + +`"file"` + + + +### `"finish"` + +SHA-256: `bd499eac047efe091a4df7382eadd06d25cf0cb31fee3981e8b24d85762841cd` + +`"finish"` + + + +### `"fork"` + +SHA-256: `2da725e554cd89301d829ed9dc45a572c3cd1b93b5e91d0d71f007976d8a0863` + +`"fork"` + + + +### `"fresh"` + +SHA-256: `dd2780e7e07684332f7a9ab047237798d260e7eaffb1321cb38eca695d184c3d` + +`"fresh"` + + + +### `"goal"` + +SHA-256: `b815d7b91cdd9fc6ccc7dcba05cfc5ce1d55fab5fc0c0801803cd3630cedb602` + +`"goal"` + + + +### `"goal/change"` + +SHA-256: `237c004c73b6b288246816e0d7fde16e09c63a47aa3b1227383d7afe58666163` + +`"goal/change"` + + + +### `"hook"` + +SHA-256: `dbdafceb9a900183cccc80596498d386f3bf7bddead2b8cfde68453aa4739edc` + +`"hook"` + + + +### `"hook/invoked"` + +SHA-256: `b9305f0ea75ccd6a6061c27fde19f2ab85534f9f3c1a717b573b077ebae0f611` + +`"hook/invoked"` + + + +### `"hook/result"` + +SHA-256: `a3a259a1aa5401af786ce08c4a8419681d3000d73467991b5fc563389bc4ebd2` + +`"hook/result"` + + + +### `"image"` + +SHA-256: `fb8b6a2e53cb9cf44c551176de0fb77d1ddc60037b99a891ff8a860b6d5c29a0` + +`"image"` + + + +### `"image/gif"` + +SHA-256: `de30b1fa12c75f81df6aa4096e1595296d2d04e2be14a7c29849cf8028b79048` + +`"image/gif"` + + + +### `"image/jpeg"` + +SHA-256: `3f74ba74acd10644c1ed6947a411d8088bd2cfac74f61bb8540f96d68e31f004` + +`"image/jpeg"` + + + +### `"image/png"` + +SHA-256: `58f94390b982f8a5ac1f4f235ba612557f4a1dc4a995d8b1cec63ce7639decb0` + +`"image/png"` + + + +### `"image/webp"` + +SHA-256: `ebb768d85ec87fb68e42fc14fd7d8535b5ac11791b14d04c6770180f0a719bf8` + +`"image/webp"` + + + +### `"in-history"` + +SHA-256: `f311671e07bc7fa645a0b6597de9966b97147956565296ba76a36543801b66c5` + +Sources: [`packages/llm/llm/src/types.ts:347`](../packages/llm/llm/src/types.ts) + +`"in-history"` + + + +### `"in_progress"` + +SHA-256: `94c56aeb1564ad735eb773d88636b40761d063efa1610f1308004d32923f5378` + +`"in_progress"` + + + +### `"initial"` + +SHA-256: `e7ce49f12953583fad217bf80fb9f1463acff2c3e9471a823782661675fb124c` + +`"initial"` + + + +### `"instruction-following"` + +SHA-256: `e4cac694f8b6e1d52e48c699464f1c4c26111ef87e558f7931040d00fdc8d486` + +`"instruction-following"` + + + +### `"instructions"` + +SHA-256: `fee1caf8ab6a65c1bc981f5a1730164490f22a2a89ffa3d0b76f1f432c44c8b5` + +`"instructions"` + + + +### `"interrupted"` + +SHA-256: `69d238a6e9b08d67f601b1825962963d8d3523cb69ebf6208c697dc5d058c199` + +`"interrupted"` + + + +### `"legacy"` + +SHA-256: `ad3b56aa2fc1ad4250e7399295d34a8eae592e1907e85852e2291b2e498a30bc` + +`"legacy"` + + + +### `"llm/retry"` + +SHA-256: `611cae9a3539733aa0c42f66035a7d90f19dcbbdfb5d9e3b628b13705253942d` + +`"llm/retry"` + + + +### `"llm/retry-started"` + +SHA-256: `4ae007ed190a72c92138b22d059b83208079c7551ca13fb59ae8cf5e23693964` + +`"llm/retry-started"` + + + +### `"max-tokens"` + +SHA-256: `68141cbe3994a5ffcd064c07c75996538437fd9dbf77ce8a2927e89ec577fabe` + +`"max-tokens"` + + + +### `"model"` + +SHA-256: `2765e9119edf5816ac1ab4c95c5ee32c90403f3742962eff11544f7752d59d2e` + +`"model"` + + + +### `"model/selection"` + +SHA-256: `1d5f04a03ea75fc8eeece544129eba0f49f0c82046e9c404461920390135c7b2` + +`"model/selection"` + + + +### `"negative"` + +SHA-256: `b1f62380f6746fedd629e71065e3583cef83334875b499ac9665c2eb87acfbf5` + +`"negative"` + + + +### `"never"` + +SHA-256: `1571525adc95a24e5d25b5a6440df401cd7cdd881b745178e85f5a333435cd1b` + +`"never"` + + + +### `"next-step"` + +SHA-256: `3e2ece4967cfebe95f450a93f45523dff99d6749f6269f2f60f84dfecfcf3693` + +`"next-step"` + + + +### `"next-turn"` + +SHA-256: `62ecb62dc0fe1b5e70217f8a80c39256a40ee3a1917be14e5ca899d20c35f7fd` + +`"next-turn"` + + + +### `"normal"` + +SHA-256: `3bc287e765d58dc3a22672a0495a6cfd561220dd38fbaecef80aa95b96caed79` + +`"normal"` + + + +### `"notice"` + +SHA-256: `e8de0f457fed5b9ff94dc4950b59ef75fd8ba8af665cc7899485924a62653367` + +`"notice"` + + + +### `null` + +SHA-256: `938f7c7357bc403ea3b281a9dd016939df7a0880fc8db12612585be574d0d615` + +`null` + + + +### `number` + +SHA-256: `0657d3ca5ac4e7bf79b00bcf860b1c7159d01a9da137497221ec2f1776e365f7` + +Sources: [`packages/core/session/src/types.ts:31`](../packages/core/session/src/types.ts) + +`number` + + + +### `"one-shot"` + +SHA-256: `d28765f87d6b6ab408e0b2f3e7e297791b004b806d8e302c49903ebae815feb2` + +`"one-shot"` + + + +### `"other"` + +SHA-256: `9d80129e13edfb6feb03ed327e6085c6ef60b97ad074244b2f5fd4667424a6a4` + +`"other"` + + + +### `packages/api/session-controller/src/types.ts#ModelSelection` + +SHA-256: `f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b` + +Sources: [`packages/api/session-controller/src/types.ts:86`](../packages/api/session-controller/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `model` | required | `string` | +| `provider` | required | `string` | +| `reasoningEffort` | optional | `string` | + + + +### `packages/attachment/attachment/src/types.ts#FileAttachmentRef` + +SHA-256: `f19bee84bf3d20904553cdcd57465232bc1c2a194f54c4d195292ba0487223a6` + +Sources: [`packages/attachment/attachment/src/types.ts:39`](../packages/attachment/attachment/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `attachmentId` | required | `string` | +| `bytes` | required | `number` | +| `name` | required | `string` | + + + +### `packages/attachment/attachment/src/types.ts#ImageAttachmentRef` + +SHA-256: `b0b0eb1b16490f6bee135b2377e8f8c39227974c799ad7d49ff418af23ecbc35` + +Sources: [`packages/attachment/attachment/src/types.ts:11`](../packages/attachment/attachment/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `attachmentId` | required | `string` | +| `bytes` | required | `number` | +| `height` | required | `number` | +| `mediaType` | required | [`packages/attachment/attachment/src/types.ts#ImageMediaType`](#persistence-type-packagesattachmentattachmentsrctypestsimagemediatype) | +| `name` | optional | `string` | +| `originalDimensions` | optional | [`event:agent/inbox/spliced.data.inserted[0].content[0][2].attachment.originalDimensions`](#persistence-type-eventagentinboxspliceddatainserted0content02attachmentoriginaldimensions) | +| `width` | required | `number` | + + + +### `packages/attachment/attachment/src/types.ts#ImageMediaType` + +SHA-256: `2f1e6a7ebace43d727ae138ddcd352eb3813960009e5805b8c5ac84300c97716` + +Sources: [`packages/attachment/attachment/src/types.ts:8`](../packages/attachment/attachment/src/types.ts) + +One of: + +- `"image/gif"` +- `"image/jpeg"` +- `"image/png"` +- `"image/webp"` + + + +### `packages/context/agent-instructions/src/render.ts#AgentInstructionChange` + +SHA-256: `83da72c6857601c75da11212ef847f57d457b9c99f0a82246dcdff3e26700239` + +Sources: [`packages/context/agent-instructions/src/render.ts:47`](../packages/context/agent-instructions/src/render.ts) + +| Property | Presence | Type | +|---|---|---| +| `action` | required | [`event:agent/inbox/spliced.data.inserted[0].source[17].changes[0].action`](#persistence-type-eventagentinboxspliceddatainserted0source17changes0action) | +| `digest` | optional | `string` | +| `path` | required | `string` | +| `scope` | required | `string` | + + + +### `packages/context/agent-instructions/src/state.ts#AgentInstructionSource` + +SHA-256: `6f22a9d02b9a1f1157f3b0e7bdccd7e7b12b085c4efdb709dfc74a465cd4e590` + +Sources: [`packages/context/agent-instructions/src/state.ts:37`](../packages/context/agent-instructions/src/state.ts) + +| Property | Presence | Type | +|---|---|---| +| `baseline` | optional | `true` | +| `baselineIdentity` | optional | `string` | +| `changes` | required | [`event:agent/inbox/spliced.data.inserted[0].source[17].changes`](#persistence-type-eventagentinboxspliceddatainserted0source17changes) | +| `form` | required | `"instructions"` | +| `kind` | required | `"agent-instructions"` | + + + +### `packages/context/session-reference/src/types.ts#SessionReferenceSource` + +SHA-256: `f56c5b59ca0161310c338d31dd16cf14287ccd602ff4181058df178c1c286d7f` + +Sources: [`packages/context/session-reference/src/types.ts:13`](../packages/context/session-reference/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"recall"` | +| `kind` | required | `"session-reference"` | +| `references` | required | [`event:agent/inbox/spliced.data.inserted[0].source[15].references`](#persistence-type-eventagentinboxspliceddatainserted0source15references) | +| `version` | required | `1` | + + + +### `packages/core/agent/src/types.ts#InboxTarget` + +SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c` + +Sources: [`packages/core/agent/src/types.ts:30`](../packages/core/agent/src/types.ts) + +One of: + +- `"next-step"` +- `"next-turn"` + + + +### `packages/core/session/src/types.ts#EpochHeader` + +SHA-256: `c55a891c7aac496de6ee64c91c1fb65728c977314013033e42c863428aada854` + +Sources: [`packages/core/session/src/types.ts:232`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `adapterDefaults` | optional | [`packages/llm/llm/src/call-config.ts#LlmCallConfigAdapterDefaults`](#persistence-type-packagesllmllmsrccall-configtsllmcallconfigadapterdefaults) | +| `config` | required | [`packages/llm/llm/src/call-config.ts#LlmCallConfig`](#persistence-type-packagesllmllmsrccall-configtsllmcallconfig) | +| `tools` | optional | [`event:request/header.data.header.tools`](#persistence-type-eventrequestheaderdataheadertools) | + + + +### `packages/core/session/src/types.ts#RequestContext` + +SHA-256: `b158f3ff6923714807c0d73882f0b05dc87f8fa85c9e90814852d48bbc866081` + +Sources: [`packages/core/session/src/types.ts:242`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `contextWindow` | optional | `number` | +| `model` | required | `string` | +| `provider` | required | `string` | +| `systemPromptUpdate` | optional | `"in-history"` | + + + +### `packages/core/session/src/types.ts#RequestHeaderReason` + +SHA-256: `530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6` + +Sources: [`packages/core/session/src/types.ts:261`](../packages/core/session/src/types.ts) + +One of: + +- `"change"` +- `"initial"` +- `"resume"` +- `"series"` + + + +### `packages/core/session/src/types.ts#SessionHeader` + +SHA-256: `2a8b114637e66faab5fae1c6e2fba65320db6224c80f5f36c5bf32f937154d03` + +Sources: [`packages/core/session/src/types.ts:93`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `agentPreset` | optional | `string` | +| `createdAt` | required | `number` | +| `cwd` | optional | `string` | +| `delegationDepth` | optional | `number` | +| `id` | required | `string` | +| `isSeeded` | required | `boolean` | +| `origin` | optional | `"subagent"` | +| `parentSession` | optional | `string` | +| `version` | required | `3` | + + + +### `packages/core/session/src/types.ts#SurfaceOp` + +SHA-256: `335e242de1fcc17b6ca920fc420d71bec2d76e53e37955c00948b65ab77f05c5` + +Sources: [`packages/core/session/src/types.ts:434`](../packages/core/session/src/types.ts) + +One of: + +- `"append"` +- [`SessionEventEnvelope[6].surfaceOp[1]`](#persistence-type-sessioneventenvelope6surfaceop1) + + + +### `packages/core/session/src/types.ts#TurnEndCancelCause` + +SHA-256: `1f8c126cfab3df812cd115cfc23e3256f0fa2383400f4ce91ecad313577e59b0` + +Sources: [`packages/core/session/src/types.ts:195`](../packages/core/session/src/types.ts) + +One of: + +- [`event:turn/end.data.reason[1].reason[2]`](#persistence-type-eventturnenddatareason1reason2) +- [`event:turn/end.data.reason[1].reason[3]`](#persistence-type-eventturnenddatareason1reason3) +- [`event:turn/end.data.reason[1].reason[4]`](#persistence-type-eventturnenddatareason1reason4) +- [`event:turn/end.data.reason[1].reason[1]`](#persistence-type-eventturnenddatareason1reason1) +- [`event:agent/inbox/spliced.data.inserted[0].source[0]`](#persistence-type-eventagentinboxspliceddatainserted0source0) + + + +### `packages/core/session/src/types.ts#TurnEndReason` + +SHA-256: `e3528b95cff70d83a080a96666877da3f9dc205e71955d6bc5b9b315b9e0da22` + +Sources: [`packages/core/session/src/types.ts:224`](../packages/core/session/src/types.ts) + +One of: + +- [`event:turn/end.data.reason[5]`](#persistence-type-eventturnenddatareason5) +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[2]`](#persistence-type-eventassistantattemptdatastream03chunk6reason2) +- [`event:turn/end.data.reason[3]`](#persistence-type-eventturnenddatareason3) +- [`event:turn/end.data.reason[1]`](#persistence-type-eventturnenddatareason1) +- [`event:turn/end.data.reason[2]`](#persistence-type-eventturnenddatareason2) +- [`event:turn/end.data.reason[0]`](#persistence-type-eventturnenddatareason0) + + + +### `packages/core/tools/src/index.ts#ToolRestriction` + +SHA-256: `3cfc3a56502da1f8c6153c2c56657bab4b749056968634dd0991d325ec1c919f` + +Sources: [`packages/core/tools/src/index.ts:673`](../packages/core/tools/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| `allow` | optional | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `deny` | optional | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | + + + +### `packages/core/tools/src/types.ts#PtcDispatchEventData` + +SHA-256: `1418e7f0c944a0453ce0851729b38b69c74194ff1e3ed41dc71904ca66e57bc5` + +Sources: [`packages/core/tools/src/types.ts:20`](../packages/core/tools/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `arguments` | required | `unknown` (opaque) | +| `content` | required | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `isError` | required | `boolean` | +| `name` | required | `string` | +| `parentCallId` | required | `string` | +| `rootCallId` | required | `string` | +| `subCallId` | required | `string` | + + + +### `packages/core/tools/src/types.ts#PtcDispatchStartEventData` + +SHA-256: `2fe5026daf14bd4ff2b7bfb7cf9791c302d9cbba06802178b6dfec00b892698f` + +Sources: [`packages/core/tools/src/types.ts:11`](../packages/core/tools/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `arguments` | required | `unknown` (opaque) | +| `name` | required | `string` | +| `parentCallId` | required | `string` | +| `rootCallId` | required | `string` | +| `subCallId` | required | `string` | + + + +### `packages/experimental/agent-team/src/types.ts#TeamMemberPhase` + +SHA-256: `dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd` + +Sources: [`packages/experimental/agent-team/src/types.ts:44`](../packages/experimental/agent-team/src/types.ts) + +One of: + +- `"active"` +- `"failed"` +- `"provisioning"` + + + +### `packages/experimental/agent-team/src/types.ts#TeamMemberSnapshot` + +SHA-256: `6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25` + +Sources: [`packages/experimental/agent-team/src/types.ts:47`](../packages/experimental/agent-team/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `context` | required | [`event:team/member.data.member.context`](#persistence-type-eventteammemberdatamembercontext) | +| `description` | required | `string` | +| `error` | optional | `string` | +| `id` | required | `string` | +| `name` | required | `string` | +| `phase` | required | [`packages/experimental/agent-team/src/types.ts#TeamMemberPhase`](#persistence-type-packagesexperimentalagent-teamsrctypeststeammemberphase) | +| `provider` | required | `string` | + + + +### `packages/experimental/agent-team/src/types.ts#TeamMessageSnapshot` + +SHA-256: `10ce3a084c136b5fc9969e5981b64711c35a2d9de7eac1b8443c03f73dc4f3db` + +Sources: [`packages/experimental/agent-team/src/types.ts:106`](../packages/experimental/agent-team/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `content` | required | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `id` | required | `string` | +| `senderId` | required | `string` | +| `senderName` | required | `string` | +| `targetId` | required | `string` | + + + +### `packages/experimental/agent-team/src/types.ts#TeamMessageSource` + +SHA-256: `2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0` + +Sources: [`packages/experimental/agent-team/src/types.ts:115`](../packages/experimental/agent-team/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"team-message"` | +| `messageId` | required | `string` | +| `senderId` | required | `string` | +| `senderName` | required | `string` | +| `teamId` | required | `string` | + + + +### `packages/experimental/agent-team/src/types.ts#TeamTaskSnapshot` + +SHA-256: `51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357` + +Sources: [`packages/experimental/agent-team/src/types.ts:74`](../packages/experimental/agent-team/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `blockedBy` | required | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `description` | required | `string` | +| `id` | required | `string` | +| `ownerId` | optional | `string` | +| `revision` | required | `number` | +| `status` | required | [`packages/experimental/agent-team/src/types.ts#TeamTaskStatus`](#persistence-type-packagesexperimentalagent-teamsrctypeststeamtaskstatus) | +| `subject` | required | `string` | +| `writeScopes` | required | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | + + + +### `packages/experimental/agent-team/src/types.ts#TeamTaskStatus` + +SHA-256: `b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943` + +Sources: [`packages/experimental/agent-team/src/types.ts:71`](../packages/experimental/agent-team/src/types.ts) + +One of: + +- `"completed"` +- `"deleted"` +- `"in_progress"` +- `"pending"` + + + +### `packages/feedback/command-feedback/src/types.ts#FeedbackRecord` + +SHA-256: `7d54a5a5e629e2e7783bfac72683bcc5e969e9f43426b9289826aaf294ff6344` + +Sources: [`packages/feedback/command-feedback/src/types.ts:27`](../packages/feedback/command-feedback/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `category` | optional | [`event:feedback/message-put.data.item.category`](#persistence-type-eventfeedbackmessage-putdataitemcategory) | +| `text` | optional | `string` | + + + +### `packages/feedback/message-feedback/src/types.ts#MessageFeedbackDelete` + +SHA-256: `7c6129b6961cf4b2a030f36c78d2b0a5be38617651a9b81322e054ee512bee06` + +Sources: [`packages/feedback/message-feedback/src/types.ts:46`](../packages/feedback/message-feedback/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `messageId` | required | `string` | +| `sessionId` | required | `string` | + + + +### `packages/feedback/message-feedback/src/types.ts#MessageFeedbackItem` + +SHA-256: `3d7d07060a7be77984feb651ea2bf1925d3e0477911d9d3ed86330d1e8ca6182` + +Sources: [`packages/feedback/message-feedback/src/types.ts:20`](../packages/feedback/message-feedback/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `category` | optional | [`event:feedback/message-put.data.item.category`](#persistence-type-eventfeedbackmessage-putdataitemcategory) | +| `createdAt` | required | `number` | +| `messageId` | required | `string` | +| `note` | optional | `string` | +| `rating` | required | [`packages/feedback/message-feedback/src/types.ts#MessageFeedbackRating`](#persistence-type-packagesfeedbackmessage-feedbacksrctypestsmessagefeedbackrating) | +| `updatedAt` | required | `number` | +| `version` | required | `string` | + + + +### `packages/feedback/message-feedback/src/types.ts#MessageFeedbackPut` + +SHA-256: `aa7bc2817014ca6c45719b1c193c5c131be718a0921f34345098ff250f444626` + +Sources: [`packages/feedback/message-feedback/src/types.ts:38`](../packages/feedback/message-feedback/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `item` | required | [`packages/feedback/message-feedback/src/types.ts#MessageFeedbackItem`](#persistence-type-packagesfeedbackmessage-feedbacksrctypestsmessagefeedbackitem) | +| `sessionId` | required | `string` | + + + +### `packages/feedback/message-feedback/src/types.ts#MessageFeedbackRating` + +SHA-256: `390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2` + +Sources: [`packages/feedback/message-feedback/src/types.ts:17`](../packages/feedback/message-feedback/src/types.ts) + +One of: + +- `"negative"` +- `"positive"` + + + +### `packages/fs/tool-present/src/types.ts#PresentedFile` + +SHA-256: `b8fc636a2121df9d8423c242e03c9d23b7ab7c6fdce00e45746932c60b730f97` + +Sources: [`packages/fs/tool-present/src/types.ts:5`](../packages/fs/tool-present/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `description` | optional | `string` | +| `path` | required | `string` | + + + +### `packages/goal/goal/src/domain.ts#GoalChangeMeta` + +SHA-256: `7d226b9fd95b4ba213d4237c22e930a91c3c769c95ca7f92977b9d625ba13438` + +Sources: [`packages/goal/goal/src/domain.ts:44`](../packages/goal/goal/src/domain.ts) + +One of: + +- [`packages/goal/goal/src/domain.ts#GoalClearChangeMeta`](#persistence-type-packagesgoalgoalsrcdomaintsgoalclearchangemeta) +- [`packages/goal/goal/src/domain.ts#GoalSnapshotChangeMeta`](#persistence-type-packagesgoalgoalsrcdomaintsgoalsnapshotchangemeta) + + + +### `packages/goal/goal/src/domain.ts#GoalClearChangeMeta` + +SHA-256: `cffcdc19881ab61ec0a9ce18743996f7ac44ac8889f34dfac36ae745574aa22f` + +Sources: [`packages/goal/goal/src/domain.ts:35`](../packages/goal/goal/src/domain.ts) + +| Property | Presence | Type | +|---|---|---| +| `cleared` | required | [`packages/goal/goal/src/types.ts#GoalRef`](#persistence-type-packagesgoalgoalsrctypestsgoalref) | +| `clearedAt` | required | `number` | +| `kind` | required | `"goal/change"` | +| `operation` | required | `"clear"` | +| `version` | required | `1` | + + + +### `packages/goal/goal/src/domain.ts#GoalMessageSource` + +SHA-256: `7f959241c24128b4983b559984811861dc59774b85905c8fa57c6cb0148a3dbc` + +Sources: [`packages/goal/goal/src/domain.ts:47`](../packages/goal/goal/src/domain.ts) + +| Property | Presence | Type | +|---|---|---| +| `goalId` | required | `string` | +| `kind` | required | `"goal"` | +| `revision` | required | `number` | +| `round` | required | `number` | + + + +### `packages/goal/goal/src/domain.ts#GoalSnapshotChangeMeta` + +SHA-256: `1df34a6c086866ffdc2dd9e8e5c581c3751a010cdf6b8538a7327f7a3237f0b4` + +Sources: [`packages/goal/goal/src/domain.ts:24`](../packages/goal/goal/src/domain.ts) + +| Property | Presence | Type | +|---|---|---| +| `createdAt` | required | `number` | +| `goal` | required | [`packages/goal/goal/src/types.ts#GoalSnapshot`](#persistence-type-packagesgoalgoalsrctypestsgoalsnapshot) | +| `kind` | required | `"goal/change"` | +| `operation` | required | [`event:goal/change.data[0].operation`](#persistence-type-eventgoalchangedata0operation) | +| `roundsStarted` | required | `number` | +| `updatedAt` | required | `number` | +| `version` | required | `1` | + + + +### `packages/goal/goal/src/types.ts#GoalBlockReason` + +SHA-256: `84ac8c1a66ffc697262e181f42e851259eae14038c113ca329fb4700ccdb1f7c` + +Sources: [`packages/goal/goal/src/types.ts:52`](../packages/goal/goal/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `code` | required | `string` | +| `message` | required | `string` | + + + +### `packages/goal/goal/src/types.ts#GoalPhase` + +SHA-256: `b8327af7ee5767952d1300cb4f03b7fc04197ae5ba275e13981c689ff7506172` + +Sources: [`packages/goal/goal/src/types.ts:45`](../packages/goal/goal/src/types.ts) + +One of: + +- `"active"` +- `"blocked"` +- `"complete"` +- `"paused"` + + + +### `packages/goal/goal/src/types.ts#GoalRef` + +SHA-256: `0b6d718c693fdf7f06dcccb0bcbbabe78cc63514eafc62d6b2a3899735279d6f` + +Sources: [`packages/goal/goal/src/types.ts:20`](../packages/goal/goal/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `id` | required | `string` | +| `revision` | required | `number` | + + + +### `packages/goal/goal/src/types.ts#GoalSnapshot` + +SHA-256: `6bbe793f6a93dc8d50a4d4d3fbb6f8ed15650bcb7e151d28669628f7b7c3fc25` + +Sources: [`packages/goal/goal/src/types.ts:60`](../packages/goal/goal/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `blockedReason` | optional | [`packages/goal/goal/src/types.ts#GoalBlockReason`](#persistence-type-packagesgoalgoalsrctypestsgoalblockreason) | +| `id` | required | `string` | +| `maxGoalRounds` | required | `number` | +| `objective` | required | `string` | +| `phase` | required | [`packages/goal/goal/src/types.ts#GoalPhase`](#persistence-type-packagesgoalgoalsrctypestsgoalphase) | +| `revision` | required | `number` | + + + +### `packages/hooks/hook-protocol/src/types.ts#HookDialect` + +SHA-256: `a6b41f5bede8094d7bf635be5d69b1bd7a25e006ca9cf9415bceb9322f1a7feb` + +Sources: [`packages/hooks/hook-protocol/src/types.ts:48`](../packages/hooks/hook-protocol/src/types.ts) + +One of: + +- `"claude-code"` +- `"codex"` + + + +### `packages/interaction/user-approval/src/index.ts#ApprovalPolicy` + +SHA-256: `05d2ae1e0c7f8770f83f9a09047afb39a335d8ac986df3193659aa1e581472cf` + +Sources: [`packages/interaction/user-approval/src/index.ts:60`](../packages/interaction/user-approval/src/index.ts) + +One of: + +- `"ask"` +- `"never"` + + + +### `packages/interaction/user-approval/src/types.ts#ApprovalOutcome` + +SHA-256: `930c1cd289def119d443e19dd90008916bf035f0a4d23e07241e7f050f2b89b7` + +Sources: [`packages/interaction/user-approval/src/types.ts:32`](../packages/interaction/user-approval/src/types.ts) + +One of: + +- `"allowed-once"` +- `"cancelled"` +- `"rejected"` +- `"unavailable"` + + + +### `packages/llm/llm-retry/src/types.ts#LlmRetryEventData` + +SHA-256: `35b995af35bfa087f5381a5454a2305928da037e819bfc4759d3f66d9bd55aa9` + +Sources: [`packages/llm/llm-retry/src/types.ts:16`](../packages/llm/llm-retry/src/types.ts) + +One of: + +- [`event:llm/retry.data[0]`](#persistence-type-eventllmretrydata0) +- [`event:llm/retry.data[1]`](#persistence-type-eventllmretrydata1) + + + +### `packages/llm/llm-retry/src/types.ts#LlmRetryStartedEventData` + +SHA-256: `6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20` + +Sources: [`packages/llm/llm-retry/src/types.ts:43`](../packages/llm/llm-retry/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `retry` | required | `number` | +| `retryId` | required | `string` | +| `step` | required | `number` | +| `turn` | required | `number` | + + + +### `packages/llm/llm/src/assistant-stream.ts#AssistantStreamRecord` + +SHA-256: `ecaf9469000a15d46d269ea72994ceaab3652ed31507822906fc12397422da21` + +Sources: [`packages/llm/llm/src/assistant-stream.ts:20`](../packages/llm/llm/src/assistant-stream.ts) + +One of: + +- [`event:assistant/attempt.data.stream[0][2]`](#persistence-type-eventassistantattemptdatastream02) +- [`event:assistant/attempt.data.stream[0][3]`](#persistence-type-eventassistantattemptdatastream03) +- [`event:assistant/attempt.data.stream[0][1]`](#persistence-type-eventassistantattemptdatastream01) +- [`event:assistant/attempt.data.stream[0][0]`](#persistence-type-eventassistantattemptdatastream00) + + + +### `packages/llm/llm/src/call-config.ts#LlmCallConfig` + +SHA-256: `67d5f73ceae82f5265258142ad6cf172de2c1fef93ba551b7f0454e0a93bffe0` + +Sources: [`packages/llm/llm/src/call-config.ts:23`](../packages/llm/llm/src/call-config.ts) + +| Property | Presence | Type | +|---|---|---| +| `maxTokens` | optional | `number` | +| `model` | required | `string` | +| `provider` | required | `string` | +| `reasoningEffort` | optional | `string` | +| `stop` | optional | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `temperature` | optional | `number` | + + + +### `packages/llm/llm/src/call-config.ts#LlmCallConfigAdapterDefaults` + +SHA-256: `326a9bfda27dda5882fb3cbe6e715df41dae2298fdae3d66437457268a665693` + +Sources: [`packages/llm/llm/src/call-config.ts:36`](../packages/llm/llm/src/call-config.ts) + +| Property | Presence | Type | +|---|---|---| +| `maxTokens` | optional | `true` | +| `reasoningEffort` | optional | `true` | + + + +### `packages/llm/llm/src/message.ts#AssistantMessage` + +SHA-256: `08a1293eddb9b633bfefaf0893e3b4c807dfbb49c45a447cb32bb68bfd1a8232` + +Sources: [`packages/llm/llm/src/message.ts:148`](../packages/llm/llm/src/message.ts) + +| Property | Presence | Type | +|---|---|---| +| `content` | required | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `id` | required | `string` | +| `role` | required | `"assistant"` | +| `source` | required | [`packages/llm/llm/src/message.ts#ModelMessageSource`](#persistence-type-packagesllmllmsrcmessagetsmodelmessagesource) | + + + +### `packages/llm/llm/src/message.ts#ContextSnapshotSection` + +SHA-256: `86e1e384495ee09ac2d60c9db3610f1f1fddac2d248f6f1d6000e78054680219` + +Sources: [`packages/llm/llm/src/message.ts:65`](../packages/llm/llm/src/message.ts) + +| Property | Presence | Type | +|---|---|---| +| `name` | required | `string` | +| `text` | required | `string` | + + + +### `packages/llm/llm/src/message.ts#Message` + +SHA-256: `f051068babb7ebab80e9fd4fff3a70cfe54430c2ef3ca9bb77bb54db8bc34476` + +Sources: [`packages/llm/llm/src/message.ts:131`](../packages/llm/llm/src/message.ts) + +| Property | Presence | Type | +|---|---|---| +| `content` | required | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `id` | required | `string` | +| `role` | required | [`event:session/title-llm-request.data.messages[0].role`](#persistence-type-eventsessiontitle-llm-requestdatamessages0role) | +| `source` | required | [`packages/llm/llm/src/message.ts#MessageSource`](#persistence-type-packagesllmllmsrcmessagetsmessagesource) | + + + +### `packages/llm/llm/src/message.ts#MessageSource` + +SHA-256: `4c965666d5b42116945bc356087cd5e1fd549e6dece560785ed60511059a8283` + +Sources: [`packages/llm/llm/src/message.ts:128`](../packages/llm/llm/src/message.ts) + +One of: + +- [`packages/context/agent-instructions/src/state.ts#AgentInstructionSource`](#persistence-type-packagescontextagent-instructionssrcstatetsagentinstructionsource) +- [`packages/llm/llm/src/message.ts#ToolMessageSource`](#persistence-type-packagesllmllmsrcmessagetstoolmessagesource) +- [`event:agent/inbox/spliced.data.inserted[0].source[10]`](#persistence-type-eventagentinboxspliceddatainserted0source10) +- [`event:agent/inbox/spliced.data.inserted[0].source[19]`](#persistence-type-eventagentinboxspliceddatainserted0source19) +- [`packages/skill/tool-skill/src/index.ts#SkillCatalogSource`](#persistence-type-packagesskilltool-skillsrcindextsskillcatalogsource) +- [`packages/skill/skill/src/index.ts#SkillInvocationSource`](#persistence-type-packagesskillskillsrcindextsskillinvocationsource) +- [`event:agent/inbox/spliced.data.inserted[0].source[4]`](#persistence-type-eventagentinboxspliceddatainserted0source4) +- [`event:agent/inbox/spliced.data.inserted[0].source[5]`](#persistence-type-eventagentinboxspliceddatainserted0source5) +- [`event:agent/inbox/spliced.data.inserted[0].source[6]`](#persistence-type-eventagentinboxspliceddatainserted0source6) +- [`event:agent/inbox/spliced.data.inserted[0].source[3]`](#persistence-type-eventagentinboxspliceddatainserted0source3) +- [`event:agent/inbox/spliced.data.inserted[0].source[2]`](#persistence-type-eventagentinboxspliceddatainserted0source2) +- [`event:agent/inbox/spliced.data.inserted[0].source[7]`](#persistence-type-eventagentinboxspliceddatainserted0source7) +- [`packages/context/session-reference/src/types.ts#SessionReferenceSource`](#persistence-type-packagescontextsession-referencesrctypestssessionreferencesource) +- [`packages/subagent/subagent/src/continuation-messages.ts#SubagentSettledMessageSource`](#persistence-type-packagessubagentsubagentsrccontinuation-messagestssubagentsettledmessagesource) +- [`packages/subagent/subagent/src/continuation-messages.ts#AgentMessageSource`](#persistence-type-packagessubagentsubagentsrccontinuation-messagestsagentmessagesource) +- [`packages/goal/goal/src/domain.ts#GoalMessageSource`](#persistence-type-packagesgoalgoalsrcdomaintsgoalmessagesource) +- [`packages/experimental/agent-team/src/types.ts#TeamMessageSource`](#persistence-type-packagesexperimentalagent-teamsrctypeststeammessagesource) +- [`packages/llm/llm/src/message.ts#ModelMessageSource`](#persistence-type-packagesllmllmsrcmessagetsmodelmessagesource) +- [`event:agent/inbox/spliced.data.inserted[0].source[1]`](#persistence-type-eventagentinboxspliceddatainserted0source1) +- [`event:agent/inbox/spliced.data.inserted[0].source[0]`](#persistence-type-eventagentinboxspliceddatainserted0source0) + + + +### `packages/llm/llm/src/message.ts#ModelMessageSource` + +SHA-256: `719c51f67074f97034a6c94297fe1f4b5d3c8d392c57586dab732aa5398c7a91` + +Sources: [`packages/llm/llm/src/message.ts:24`](../packages/llm/llm/src/message.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"model"` | +| `model` | required | `string` | +| `provider` | required | `string` | +| `replayState` | optional | `unknown` (opaque) | + + + +### `packages/llm/llm/src/message.ts#SystemMessage` + +SHA-256: `7973d82bd129b261f16a9a20d44df834ec60efc52a63242b4943a2515ae0894f` + +Sources: [`packages/llm/llm/src/message.ts:158`](../packages/llm/llm/src/message.ts) + +| Property | Presence | Type | +|---|---|---| +| `content` | required | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `id` | required | `string` | +| `role` | required | `"system"` | +| `source` | required | [`event:system/message.data.message.source`](#persistence-type-eventsystemmessagedatamessagesource) | + + + +### `packages/llm/llm/src/message.ts#ToolMessageSource` + +SHA-256: `d9c462b9f62fdd3efc76a44b9524dc8781f6b91cd9c7e584b17d971defafebab` + +Sources: [`packages/llm/llm/src/message.ts:29`](../packages/llm/llm/src/message.ts) + +| Property | Presence | Type | +|---|---|---| +| `callId` | required | `string` | +| `kind` | required | `"tool"` | + + + +### `packages/llm/llm/src/message.ts#ToolResultMessage` + +SHA-256: `966fad765834f62051ad6f6a100f7ea38abe5f40f7ef3ae0b6dca0e31c7549a5` + +Sources: [`packages/llm/llm/src/message.ts:164`](../packages/llm/llm/src/message.ts) + +| Property | Presence | Type | +|---|---|---| +| `content` | required | [`event:tool/result.data.message.content`](#persistence-type-eventtoolresultdatamessagecontent) | +| `id` | required | `string` | +| `role` | required | `"user"` | +| `source` | required | [`packages/llm/llm/src/message.ts#ToolMessageSource`](#persistence-type-packagesllmllmsrcmessagetstoolmessagesource) | + + + +### `packages/llm/llm/src/message.ts#UserMessage` + +SHA-256: `40390271dffbed81bffa514370ad1c3c19a13ad10c3647c9c0eae31126c95a5f` + +Sources: [`packages/llm/llm/src/message.ts:143`](../packages/llm/llm/src/message.ts) + +| Property | Presence | Type | +|---|---|---| +| `content` | required | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `id` | required | `string` | +| `role` | required | `"user"` | +| `source` | required | [`packages/llm/llm/src/message.ts#MessageSource`](#persistence-type-packagesllmllmsrcmessagetsmessagesource) | + + + +### `packages/llm/llm/src/types.ts#ContentBlock` + +SHA-256: `e1161343902a8da654d4d1bb66d729065cc93a2710ebfe2717a242a5c2ec709a` + +Sources: [`packages/llm/llm/src/types.ts:124`](../packages/llm/llm/src/types.ts) + +One of: + +- [`packages/llm/llm/src/types.ts#ReasoningBlock`](#persistence-type-packagesllmllmsrctypestsreasoningblock) +- [`event:web/deepseek-search-llm-request.data.body.messages[0].content[0]`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodymessages0content0) +- [`packages/llm/llm/src/types.ts#ToolCallBlock`](#persistence-type-packagesllmllmsrctypeststoolcallblock) +- [`packages/llm/llm/src/types.ts#ImageBlock`](#persistence-type-packagesllmllmsrctypestsimageblock) +- [`packages/llm/llm/src/types.ts#FileBlock`](#persistence-type-packagesllmllmsrctypestsfileblock) +- [`packages/llm/llm/src/types.ts#ToolResultBlock`](#persistence-type-packagesllmllmsrctypeststoolresultblock) + + + +### `packages/llm/llm/src/types.ts#ContentBlockType` + +SHA-256: `757013fff5879d73d5753d8d1120faab56110e88ac3932485ab7e034d9e9ce61` + +Sources: [`packages/llm/llm/src/types.ts:122`](../packages/llm/llm/src/types.ts) + +One of: + +- `"file"` +- `"image"` +- `"reasoning"` +- `"text"` +- `"tool-call"` +- `"tool-result"` + + + +### `packages/llm/llm/src/types.ts#FileBlock` + +SHA-256: `f2da0f27c1e6add59982c37b15e4a4912c5b33466859cce3b13d503600e24aef` + +Sources: [`packages/llm/llm/src/types.ts:84`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `attachment` | required | [`packages/attachment/attachment/src/types.ts#FileAttachmentRef`](#persistence-type-packagesattachmentattachmentsrctypestsfileattachmentref) | +| `type` | required | `"file"` | + + + +### `packages/llm/llm/src/types.ts#FinishReason` + +SHA-256: `5ed158429dfa6a8633e6ba94b8c81190616c24b65c1777bf1c60cde9dd499a2f` + +Sources: [`packages/llm/llm/src/types.ts:139`](../packages/llm/llm/src/types.ts) + +One of: + +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[3]`](#persistence-type-eventassistantattemptdatastream03chunk6reason3) +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[4]`](#persistence-type-eventassistantattemptdatastream03chunk6reason4) +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[0]`](#persistence-type-eventassistantattemptdatastream03chunk6reason0) +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[1]`](#persistence-type-eventassistantattemptdatastream03chunk6reason1) +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[2]`](#persistence-type-eventassistantattemptdatastream03chunk6reason2) + + + +### `packages/llm/llm/src/types.ts#ImageBlock` + +SHA-256: `8ef06dd236128246969d5f04a6c4a639d031fcda99a0cd13051f52f41b0ca1af` + +Sources: [`packages/llm/llm/src/types.ts:71`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `attachment` | required | [`packages/attachment/attachment/src/types.ts#ImageAttachmentRef`](#persistence-type-packagesattachmentattachmentsrctypestsimageattachmentref) | +| `type` | required | `"image"` | + + + +### `packages/llm/llm/src/types.ts#LlmFailure` + +SHA-256: `27cd736e794a0f8703a155fb697820cede894d10776586dd3df24d24c9465c0e` + +Sources: [`packages/llm/llm/src/types.ts:40`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `code` | required | `string` | +| `message` | required | `string` | +| `providerRetryAfterMs` | optional | `number` | +| `requestId` | optional | `string` | +| `status` | optional | `number` | + + + +### `packages/llm/llm/src/types.ts#ReasoningBlock` + +SHA-256: `814d9434e1ddf9078612b3b34322f26e9363b469213335007f7e9ba7fee9464e` + +Sources: [`packages/llm/llm/src/types.ts:60`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `text` | required | `string` | +| `type` | required | `"reasoning"` | + + + +### `packages/llm/llm/src/types.ts#ReplayEnvelope` + +SHA-256: `0acd94fe8794574c9ae72c7855ee16d29c546096d4af760d16d1225385764555` + +Sources: [`packages/llm/llm/src/types.ts:368`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `blocks` | optional | [`event:assistant/attempt.data.stream[0][3].chunk[6].replayState.blocks`](#persistence-type-eventassistantattemptdatastream03chunk6replaystateblocks) | +| `response` | required | `unknown` (opaque) | + + + +### `packages/llm/llm/src/types.ts#StreamChunk` + +SHA-256: `989145d2b165a1e4391d0691196ede9dec5093b09f1cf3d33b1911c24ee6c5ee` + +Sources: [`packages/llm/llm/src/types.ts:390`](../packages/llm/llm/src/types.ts) + +One of: + +- [`event:assistant/attempt.data.stream[0][3].chunk[3]`](#persistence-type-eventassistantattemptdatastream03chunk3) +- [`event:assistant/attempt.data.stream[0][3].chunk[4]`](#persistence-type-eventassistantattemptdatastream03chunk4) +- [`event:assistant/attempt.data.stream[0][3].chunk[0]`](#persistence-type-eventassistantattemptdatastream03chunk0) +- [`event:assistant/attempt.data.stream[0][3].chunk[2]`](#persistence-type-eventassistantattemptdatastream03chunk2) +- [`event:assistant/attempt.data.stream[0][3].chunk[1]`](#persistence-type-eventassistantattemptdatastream03chunk1) +- [`event:assistant/attempt.data.stream[0][3].chunk[6]`](#persistence-type-eventassistantattemptdatastream03chunk6) +- [`event:assistant/attempt.data.stream[0][3].chunk[5]`](#persistence-type-eventassistantattemptdatastream03chunk5) + + + +### `packages/llm/llm/src/types.ts#TokenUsage` + +SHA-256: `fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394` + +Sources: [`packages/llm/llm/src/types.ts:149`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `cacheReadTokens` | optional | `number` | +| `cacheWriteTokens` | optional | `number` | +| `inputTokens` | required | `number` | +| `outputTokens` | required | `number` | +| `reasoningTokens` | optional | `number` | +| `totalTokens` | optional | `number` | + + + +### `packages/llm/llm/src/types.ts#ToolCallBlock` + +SHA-256: `c1f0f17b6493b85db6453bb30e01b208d703731b25bc1e68e5d4fc72eea9cdea` + +Sources: [`packages/llm/llm/src/types.ts:91`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `arguments` | required | `string` | +| `id` | required | `string` | +| `name` | required | `string` | +| `type` | required | `"tool-call"` | + + + +### `packages/llm/llm/src/types.ts#ToolResultBlock` + +SHA-256: `9bdd77f2b39d9ae47be71e494eca8c9e950b0e99b89a4539f86c4a0e7d1398b6` + +Sources: [`packages/llm/llm/src/types.ts:101`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `content` | required | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `isError` | optional | `boolean` | +| `toolCallId` | required | `string` | +| `type` | required | `"tool-result"` | + + + +### `packages/llm/llm/src/types.ts#ToolSchema` + +SHA-256: `7f5f2a8618c2ece530a18b1358b890fb57fb8c35705e47b66bff2296f8c6e889` + +Sources: [`packages/llm/llm/src/types.ts:411`](../packages/llm/llm/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `description` | required | `string` | +| `name` | required | `string` | +| `parameters` | required | [`event:request/header.data.header.tools[0].parameters`](#persistence-type-eventrequestheaderdataheadertools0parameters) | + + + +### `packages/sandbox/sandbox/src/index.ts#SandboxMode` + +SHA-256: `fab2bb09bc5e3b9db0685e8f5500b029af1a2b33e70bc703c1d5d0f596fbc1fd` + +Sources: [`packages/sandbox/sandbox/src/index.ts:29`](../packages/sandbox/sandbox/src/index.ts) + +One of: + +- `"danger-full-access"` +- `"read-only"` +- `"workspace-write"` + + + +### `packages/schedule/schedule/src/types.ts#AfterScheduleRecord` + +SHA-256: `27e337d3d6b402b221e50c247a3f518a70d0c474cc60beee15fac3e9c5cbfcbe` + +Sources: [`packages/schedule/schedule/src/types.ts:13`](../packages/schedule/schedule/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `afterSeconds` | required | `number` | +| `id` | required | `string` | +| `kind` | required | `"after"` | +| `prompt` | required | `string` | +| `scheduledAt` | required | `string` | + + + +### `packages/schedule/schedule/src/types.ts#AtScheduleRecord` + +SHA-256: `658d895d187b26d83f3b53109a3bf77bbfba9f6f9a6d4bc4ee4315b4eb36239e` + +Sources: [`packages/schedule/schedule/src/types.ts:27`](../packages/schedule/schedule/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `id` | required | `string` | +| `kind` | required | `"at"` | +| `prompt` | required | `string` | +| `scheduledAt` | required | `string` | + + + +### `packages/schedule/schedule/src/types.ts#EveryScheduleDispatchChange` + +SHA-256: `e6063d0451dc02026fd9b54ad3f77c7b1b6392ad3e872a7737ac84e13ca72344` + +Sources: [`packages/schedule/schedule/src/types.ts:93`](../packages/schedule/schedule/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `acceptedAt` | required | `string` | +| `id` | required | `string` | +| `operation` | required | `"dispatch"` | +| `version` | required | `1` | + + + +### `packages/schedule/schedule/src/types.ts#EveryScheduleRecord` + +SHA-256: `66ece68282d8c06330fb83329a24ae54c386d5ccfab296bbf18369da8beb7d14` + +Sources: [`packages/schedule/schedule/src/types.ts:39`](../packages/schedule/schedule/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `everySeconds` | required | `number` | +| `id` | required | `string` | +| `kind` | required | `"every"` | +| `prompt` | required | `string` | +| `scheduledAt` | required | `string` | + + + +### `packages/schedule/schedule/src/types.ts#OneShotScheduleDispatchChange` + +SHA-256: `8e8e91646a5cae3fc78f7532a0013ff2088ae47077642c2edd507a508209b8af` + +Sources: [`packages/schedule/schedule/src/types.ts:86`](../packages/schedule/schedule/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `id` | required | `string` | +| `operation` | required | `"dispatch"` | +| `version` | required | `1` | + + + +### `packages/schedule/schedule/src/types.ts#ScheduleChange` + +SHA-256: `e1d0e52dcd1855ffb803efc18f8f7b776c48d1c25757e8a75c0281fa298c02e1` + +Sources: [`packages/schedule/schedule/src/types.ts:105`](../packages/schedule/schedule/src/types.ts) + +One of: + +- [`packages/schedule/schedule/src/types.ts#EveryScheduleDispatchChange`](#persistence-type-packagesscheduleschedulesrctypestseveryscheduledispatchchange) +- [`packages/schedule/schedule/src/types.ts#ScheduleDeleteChange`](#persistence-type-packagesscheduleschedulesrctypestsscheduledeletechange) +- [`packages/schedule/schedule/src/types.ts#OneShotScheduleDispatchChange`](#persistence-type-packagesscheduleschedulesrctypestsoneshotscheduledispatchchange) +- [`packages/schedule/schedule/src/types.ts#ScheduleCreateChange`](#persistence-type-packagesscheduleschedulesrctypestsschedulecreatechange) + + + +### `packages/schedule/schedule/src/types.ts#ScheduleCreateChange` + +SHA-256: `ca6425edd20afe34d0cedc6ad1abad1ad7b32e2fbc4c1d24db53939e80bf887f` + +Sources: [`packages/schedule/schedule/src/types.ts:72`](../packages/schedule/schedule/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `operation` | required | `"create"` | +| `schedule` | required | [`packages/schedule/schedule/src/types.ts#ScheduleRecord`](#persistence-type-packagesscheduleschedulesrctypestsschedulerecord) | +| `version` | required | `1` | + + + +### `packages/schedule/schedule/src/types.ts#ScheduleDeleteChange` + +SHA-256: `18dc21f12bbac8ade8377e6fbb54179e20c3d955d73fba3244c57fb6bf001df0` + +Sources: [`packages/schedule/schedule/src/types.ts:79`](../packages/schedule/schedule/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `id` | required | `string` | +| `operation` | required | `"delete"` | +| `version` | required | `1` | + + + +### `packages/schedule/schedule/src/types.ts#ScheduleRecord` + +SHA-256: `d8f868c6b3e7189de6c1ffe2ea04794ea2aa31315b6347d12536e46020afac87` + +Sources: [`packages/schedule/schedule/src/types.ts:69`](../packages/schedule/schedule/src/types.ts) + +One of: + +- [`packages/schedule/schedule/src/types.ts#AfterScheduleRecord`](#persistence-type-packagesscheduleschedulesrctypestsafterschedulerecord) +- [`packages/schedule/schedule/src/types.ts#EveryScheduleRecord`](#persistence-type-packagesscheduleschedulesrctypestseveryschedulerecord) +- [`packages/schedule/schedule/src/types.ts#AtScheduleRecord`](#persistence-type-packagesscheduleschedulesrctypestsatschedulerecord) + + + +### `packages/session/session-persistence-jsonl/src/format.ts#HeaderLine` + +SHA-256: `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` + +Sources: [`packages/session/session-persistence-jsonl/src/format.ts:82`](../packages/session/session-persistence-jsonl/src/format.ts) + +| Property | Presence | Type | +|---|---|---| +| `agentPreset` | optional | `string` | +| `createdAt` | required | `number` | +| `cwd` | optional | `string` | +| `delegationDepth` | required | `number` | +| `id` | required | `string` | +| `isSeeded` | required | `boolean` | +| `origin` | optional | `"subagent"` | +| `parentSession` | optional | `string` | +| `type` | required | `"session"` | +| `version` | required | `number` | + + + +### `packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData` + +SHA-256: `08936f7648f573f7539800e1ec2852fbbcba05e1a20487a1e5acf2a0fce1e2de` + +Sources: [`packages/session/session-title-llm/src/index.ts:27`](../packages/session/session-title-llm/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| `maxTokens` | required | `number` | +| `messageSeqs` | required | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `messages` | required | [`event:session/title-llm-request.data.messages`](#persistence-type-eventsessiontitle-llm-requestdatamessages) | +| `route` | required | [`packages/session/session-title/src/types.ts#SessionTitleModelIdentity`](#persistence-type-packagessessionsession-titlesrctypestssessiontitlemodelidentity) | +| `system` | required | `string` | +| `titleProvider` | required | `string` | + + + +### `packages/session/session-title/src/types.ts#SessionTitleEventData` + +SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4` + +Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/session-title/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `messageSeqs` | required | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `source` | required | [`packages/session/session-title/src/types.ts#SessionTitleSource`](#persistence-type-packagessessionsession-titlesrctypestssessiontitlesource) | +| `title` | required | `string` | + + + +### `packages/session/session-title/src/types.ts#SessionTitleModelIdentity` + +SHA-256: `07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235` + +Sources: [`packages/session/session-title/src/types.ts:20`](../packages/session/session-title/src/types.ts) · [`packages/subagent/tool-subagent/src/model-selection.ts:9`](../packages/subagent/tool-subagent/src/model-selection.ts) + +| Property | Presence | Type | +|---|---|---| +| `model` | required | `string` | +| `provider` | required | `string` | + + + +### `packages/session/session-title/src/types.ts#SessionTitleSource` + +SHA-256: `30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f` + +Sources: [`packages/session/session-title/src/types.ts:28`](../packages/session/session-title/src/types.ts) + +One of: + +- [`event:session/title.data.source[1]`](#persistence-type-eventsessiontitledatasource1) +- [`event:session/title.data.source[0]`](#persistence-type-eventsessiontitledatasource0) +- [`event:agent/inbox/spliced.data.inserted[0].source[0]`](#persistence-type-eventagentinboxspliceddatainserted0source0) + + + +### `packages/skill/skill/src/index.ts#SkillInvocationSource` + +SHA-256: `672c4aa2a9b736e3d6e5da6346d2e2ed5e3573f2d0baafa18e7407c9f3fee3db` + +Sources: [`packages/skill/skill/src/index.ts:146`](../packages/skill/skill/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"instructions"` | +| `kind` | required | `"skill-invocation"` | +| `name` | required | `string` | + + + +### `packages/skill/tool-skill/src/index.ts#SkillCatalogSource` + +SHA-256: `fc2ae6962f9f8801c2b1e414668e1e17cbb7544c4f39381198977a38bd10c093` + +Sources: [`packages/skill/tool-skill/src/index.ts:34`](../packages/skill/tool-skill/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| `entries` | required | [`event:agent/inbox/spliced.data.inserted[0].source[16].entries`](#persistence-type-eventagentinboxspliceddatainserted0source16entries) | +| `form` | required | `"catalog"` | +| `kind` | required | `"skill-catalog"` | +| `update` | optional | `true` | + + + +### `packages/subagent/subagent/src/catalog.ts#SubagentCatalogEvent` + +SHA-256: `3ac1196f59d5e9e0e9345122c8c403b102478b526404a2e591a38f613483321e` + +Sources: [`packages/subagent/subagent/src/catalog.ts:24`](../packages/subagent/subagent/src/catalog.ts) + +One of: + +- [`event:subagent/catalog.data[1]`](#persistence-type-eventsubagentcatalogdata1) +- [`event:subagent/catalog.data[0]`](#persistence-type-eventsubagentcatalogdata0) + + + +### `packages/subagent/subagent/src/continuation-messages.ts#AgentMessageSource` + +SHA-256: `4a9bef9f27a942520cc2279ca7dd3082556e4bff543f66b5c0333e1bbed1cd86` + +Sources: [`packages/subagent/subagent/src/continuation-messages.ts:15`](../packages/subagent/subagent/src/continuation-messages.ts) + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"relay"` | +| `kind` | required | `"agent-message"` | +| `senderSessionId` | required | `string` | + + + +### `packages/subagent/subagent/src/continuation-messages.ts#SubagentSettledMessageSource` + +SHA-256: `4e97c3d85c0fc817ee58873c38c25a2474abaaa0af4839311b50b68db3b8cf1a` + +Sources: [`packages/subagent/subagent/src/continuation-messages.ts:30`](../packages/subagent/subagent/src/continuation-messages.ts) + +| Property | Presence | Type | +|---|---|---| +| `form` | required | `"notice"` | +| `kind` | required | `"subagent-settled"` | +| `senderSessionId` | required | `string` | +| `summary` | required | `string` | + + + +### `packages/subagent/subagent/src/descriptor.ts#ContinuableSubagentDescriptorData` + +SHA-256: `6487b10ba7542f3c447b1e41a1c6a590b7bfafe0b9b66b49f34028da85bb970f` + +Sources: [`packages/subagent/subagent/src/descriptor.ts:72`](../packages/subagent/subagent/src/descriptor.ts) + +| Property | Presence | Type | +|---|---|---| +| `agentModel` | optional | `string` | +| `agentProvider` | optional | `string` | +| `agentReasoningEffort` | optional | `string` | +| `label` | required | `string` | +| `mode` | required | `"continuable"` | +| `persona` | optional | `string` | +| `provider` | required | `string` | +| `toolFilter` | optional | [`packages/core/tools/src/index.ts#ToolRestriction`](#persistence-type-packagescoretoolssrcindextstoolrestriction) | +| `version` | required | `number` | + + + +### `packages/subagent/subagent/src/descriptor.ts#OneShotSubagentDescriptorData` + +SHA-256: `5f758f3de5f00c18968a7c6d347083aac5c9ae13f6a79c78f6611b23c4973564` + +Sources: [`packages/subagent/subagent/src/descriptor.ts:61`](../packages/subagent/subagent/src/descriptor.ts) + +| Property | Presence | Type | +|---|---|---| +| `label` | optional | `string` | +| `mode` | required | `"one-shot"` | +| `provider` | required | `string` | +| `version` | required | `number` | + + + +### `packages/subagent/subagent/src/descriptor.ts#SubagentDescriptorData` + +SHA-256: `86d1d0e9cfebb240421187e3dc37fb71cc38a1501885cf32f8f9adef7b0a2bde` + +Sources: [`packages/subagent/subagent/src/descriptor.ts:89`](../packages/subagent/subagent/src/descriptor.ts) + +One of: + +- [`packages/subagent/subagent/src/descriptor.ts#ContinuableSubagentDescriptorData`](#persistence-type-packagessubagentsubagentsrcdescriptortscontinuablesubagentdescriptordata) +- [`packages/subagent/subagent/src/descriptor.ts#OneShotSubagentDescriptorData`](#persistence-type-packagessubagentsubagentsrcdescriptortsoneshotsubagentdescriptordata) + + + +### `packages/todo/tool-todo/src/types.ts#TodoItem` + +SHA-256: `a99f76fd149051c363f960bfeeb5b9509587ecaf7591a42797e4bfe810d8987a` + +Sources: [`packages/todo/tool-todo/src/types.ts:21`](../packages/todo/tool-todo/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `content` | required | `string` | +| `status` | required | [`event:todo/write.data.todos[0].status`](#persistence-type-eventtodowritedatatodos0status) | + + + +### `packages/web/web-search-deepseek/src/provider.ts#DeepSeekSearchLlmRequest` + +SHA-256: `2517ba143a271508d3ca35126d5aca8f7f1facf5aaddce4adf011832042fa2b9` + +Sources: [`packages/web/web-search-deepseek/src/provider.ts:56`](../packages/web/web-search-deepseek/src/provider.ts) + +| Property | Presence | Type | +|---|---|---| +| `apiVersion` | required | `string` | +| `body` | required | [`event:web/deepseek-search-llm-request.data.body`](#persistence-type-eventwebdeepseek-search-llm-requestdatabody) | +| `endpoint` | required | `string` | + + + +### `packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentEndData` + +SHA-256: `c0ea97bc89dfc46fa94eab956f5c3ee6af2f964aa7db6327cb18b451f207bef7` + +Sources: [`packages/workflow/tool-workflow/src/types.ts:29`](../packages/workflow/tool-workflow/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `outcome` | required | [`packages/workflow/workflow/src/types.ts#WorkflowAgentOutcome`](#persistence-type-packagesworkflowworkflowsrctypestsworkflowagentoutcome) | +| `runId` | required | `string` | +| `seq` | required | `number` | + + + +### `packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentStartData` + +SHA-256: `9a410ddf56792b2b7cd4a224a2e54dc4dee30a8e8e6b1c2b9a1d19992b217adc` + +Sources: [`packages/workflow/tool-workflow/src/types.ts:20`](../packages/workflow/tool-workflow/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `childId` | required | `string` | +| `label` | required | `string` | +| `phase` | optional | `string` | +| `runId` | required | `string` | +| `seq` | required | `number` | + + + +### `packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunEndData` + +SHA-256: `7f9365bae9fcb73e1e34061a1acfe0422c1a5fd351848cace9bebbe806f28462` + +Sources: [`packages/workflow/tool-workflow/src/types.ts:36`](../packages/workflow/tool-workflow/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `runId` | required | `string` | +| `stopReason` | required | [`packages/workflow/workflow/src/types.ts#WorkflowStopReason`](#persistence-type-packagesworkflowworkflowsrctypestsworkflowstopreason) | + + + +### `packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunStartData` + +SHA-256: `a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c` + +Sources: [`packages/workflow/tool-workflow/src/types.ts:14`](../packages/workflow/tool-workflow/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `name` | required | `string` | +| `runId` | required | `string` | + + + +### `packages/workflow/workflow/src/types.ts#WorkflowAgentOutcome` + +SHA-256: `c2deb7c9183735d4e0fffddf5c623f4e34daa1c8e96b111982e957dfcbbcf896` + +Sources: [`packages/workflow/workflow/src/types.ts:110`](../packages/workflow/workflow/src/types.ts) + +One of: + +- `"cancelled"` +- `"completed"` +- `"failed"` + + + +### `packages/workflow/workflow/src/types.ts#WorkflowStopReason` + +SHA-256: `8465ece5e9d9007c038b357015d849c15262cb0bc25abc59e47692d87ee01a15` + +Sources: [`packages/workflow/workflow/src/types.ts:63`](../packages/workflow/workflow/src/types.ts) + +One of: + +- `"cancelled"` +- `"completed"` +- `"error"` + + + +### `"parent"` + +SHA-256: `e6411b09c1d982f789bf741ca74e2bea69df56029847f86a6148e722039ee732` + +`"parent"` + + + +### `"pause"` + +SHA-256: `1d8798b1a5e8c545ee4dcd2b4e75c0b4c9ffd985e4744c6d1a1aab992f859721` + +`"pause"` + + + +### `"paused"` + +SHA-256: `c43b119ce579b3e9983a0d3adc4ba402ebb9ba773d89738a2dab01ecf5813943` + +`"paused"` + + + +### `"pending"` + +SHA-256: `234142ee4f996f38cc9fa3b8b3e3de29b03c9aefe5ebf6c224eddd564505aa85` + +`"pending"` + + + +### `"permission/preset"` + +SHA-256: `ae63d9d1080ed8945031594d93f8d96651d7101b848719cc3e1475875cd3d649` + +`"permission/preset"` + + + +### `"plan/mode"` + +SHA-256: `f634871d1e18b1eb199497ac8ab72d3964f48d03fbf611dc7521a1970cf429d9` + +`"plan/mode"` + + + +### `"plugin"` + +SHA-256: `e654d28d1e6c471147310d2fba396d00969cfe6ed9022626d3f2c2312e0b1cbb` + +`"plugin"` + + + +### `"positive"` + +SHA-256: `df5d9709b7db2a72510dc52052a23f0c38f39730af1504bab8bccabc14fae528` + +`"positive"` + + + +### `"product-interaction"` + +SHA-256: `0341d4c3dfcd53253cdc1e789d9130d3cd848b6f8b0706bf92f313a68b39ff23` + +`"product-interaction"` + + + +### `"provider"` + +SHA-256: `e31572180d6b28b73f073ad30ae19d5d82982d3daf984723984e23da1e7c459a` + +`"provider"` + + + +### `"provisioning"` + +SHA-256: `871bfd6f7bb91061246768d3377f3fd58d6b8ea7c31f44c714ae7ab215650d62` + +`"provisioning"` + + + +### `"read-only"` + +SHA-256: `12a0cfa619ac4a5fc6c3e8e359c8dae15769edc61497a575935c78d5842f8e42` + +`"read-only"` + + + +### `"reasoning"` + +SHA-256: `3c2535ca0a2062f172f15344f8040755002b5ac506cf94fc4ebaa822b15feb8c` + +`"reasoning"` + + + +### `"reasoning-chunks"` + +SHA-256: `688e853dc149f0d30b31765c12ec7e3be74169bdf1a4c6463a10b945d50cc1b8` + +`"reasoning-chunks"` + + + +### `"reasoning-delta"` + +SHA-256: `6f091fe32ab24407236090ca9d710bbaf59bb57466e9f3b8f1f6e4862bc863c6` + +`"reasoning-delta"` + + + +### `"recall"` + +SHA-256: `383cf73ae4748e5beb281ae74857ba1ffb92ae01075b9fbf51af78b666007a8f` + +`"recall"` + + + +### `"rejected"` + +SHA-256: `fab35c4520883c0d69e42d079c9f543fc5ac7a2d15b537b5572fcd99059c7018` + +`"rejected"` + + + +### `"relay"` + +SHA-256: `16d0507cb94b4cfc84a87446b1ba7652d6ec1d65ee1684789e747e30d542800e` + +`"relay"` + + + +### `"remove"` + +SHA-256: `7382c4fc95fe8c5db65ca4d9da572b115d0dbb818b7f32c22ab040318ffbb4bc` + +`"remove"` + + + +### `"replace"` + +SHA-256: `f6cb462f808309e315fea634f1ed86268b8346bbcee6774d9097ca6eed7ac1f8` + +`"replace"` + + + +### `"request/context"` + +SHA-256: `70b082fe78991edf13cf66e1b4d06cd2dcff9cacfd76f8cfb996bfb61939e7b2` + +`"request/context"` + + + +### `"request/header"` + +SHA-256: `3e0eff5946255ff05d88a10839c60305135801f97794ea48d409f8503303317e` + +`"request/header"` + + + +### `"resource-cost"` + +SHA-256: `5bd310104cfe448e530beb11422d2f791e2e32e8e95fdc528a15f9ca693dae75` + +`"resource-cost"` + + + +### `"resume"` + +SHA-256: `34d1ab5c5df378186d6054b1a1beea9a41e41965d6f336f0bc0025c441a82e66` + +`"resume"` + + + +### `"sandbox/mode"` + +SHA-256: `ca085dba2c877feceebf3c62f9d39c6c2a4d2314d97b94b3927a780fe8fe2bb2` + +`"sandbox/mode"` + + + +### `"schedule/change"` + +SHA-256: `0e7b2a97f54f1afdb8cdfad1635abe9145327db539bfb69b4045c19d8c17755d` + +`"schedule/change"` + + + +### `"security-privacy-permission"` + +SHA-256: `07eccc9ea2f9b5439d76cc77d5e062936677d43f07a23da749819b0a22ee36b7` + +`"security-privacy-permission"` + + + +### `"series"` + +SHA-256: `4f507598cf476214aa7646ce7841a7064a9dcb05d0a1eaaefaf79c2b738f160f` + +`"series"` + + + +### `"service-stability"` + +SHA-256: `7180844e1066230c5e891f1b9ccd942423b0fe48aac6ded7eef2a285b288a732` + +`"service-stability"` + + + +### `"session"` + +SHA-256: `e41bdebe246abe17154de7e7a1d81035437eee8d20e7f1edff0d91e2598ad828` + +`"session"` + + + +### `"session-log-deepseek/delivery-accepted"` + +SHA-256: `47e5d84f1a93450a78c7f3954b43990d0f629af7c034a56313e5deb63e772c1b` + +`"session-log-deepseek/delivery-accepted"` + + + +### `"session-reference"` + +SHA-256: `7f13860ee89a2d35ce09f958e85a04566cd59cb1de1d73a9619a9b631961dc71` + +`"session-reference"` + + + +### `"session/end-seed"` + +SHA-256: `1c2686c0682e7bc51d61117b2652bd4a80707e331aa276d6ef915ae32534a330` + +`"session/end-seed"` + + + +### `SessionEventEnvelope` + +SHA-256: `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` + +One of: + +- [`SessionEventEnvelope[38]`](#persistence-type-sessioneventenvelope38) +- [`SessionEventEnvelope[6]`](#persistence-type-sessioneventenvelope6) +- [`SessionEventEnvelope[0]`](#persistence-type-sessioneventenvelope0) + + + +### `SessionEventEnvelope[0]` + +SHA-256: `e3e77b26f0148755a505f5b8ea843a3be117827d4703bb5754d9dc8ddbef521f` + +| Property | Presence | Type | +|---|---|---| +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `string` | + + + +### `SessionEventEnvelope[38]` + +SHA-256: `998f22585a5d73ec005365e203f07fc3b3a029e82323fdda3fd7f13b0a9639d0` + +| Property | Presence | Type | +|---|---|---| +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `sourceEventSeqs` | optional | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `surfaceOp` | required | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | required | `number` | +| `type` | required | `string` | + + + +### `SessionEventEnvelope[38].sourceEventSeqs` + +SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78` + +Array of `number`. + + + +### `SessionEventEnvelope[6]` + +SHA-256: `7af85bf70d4eafce63e739adae38ea85501e68df3a0e22f97f3a0be90fd9e4cb` + +| Property | Presence | Type | +|---|---|---| +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `surfaceOp` | required | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | required | `number` | +| `type` | required | `string` | + + + +### `SessionEventEnvelope[6].surfaceOp[1]` + +SHA-256: `bcf0caf62d964b2fcf5404bd5c909cfa9a21c3f3c96e6b7e36d9e33565223825` + +Sources: [`packages/core/session/src/types.ts:436`](../packages/core/session/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `endSeq` | required | `number` | +| `op` | required | `"replace"` | +| `startSeq` | required | `number` | + + + +### `"session/title"` + +SHA-256: `7a651f3b672c52fb615f22c0ce9cb3cc5b22aad92e6bbeefb0808fd7b9233a50` + +`"session/title"` + + + +### `"session/title-llm-request"` + +SHA-256: `587bb11da730e06aa52b62c087d217d6f8f22459b25f31cb9e1e1baf17944e5e` + +`"session/title-llm-request"` + + + +### `"set"` + +SHA-256: `b765ee31033cea463863faf3fc6a0a1b85c47c6a2f13fe1ce833abe56105114b` + +`"set"` + + + +### `"skill-catalog"` + +SHA-256: `6ebcc2dbec9f2dabca9f668ae6d39e877899b180740570f453409db339221df3` + +`"skill-catalog"` + + + +### `"skill-invocation"` + +SHA-256: `1922cd79a70806c17758185a6ef0458bc1fb3900363906733421c16414da2509` + +`"skill-invocation"` + + + +### `"snapshot"` + +SHA-256: `6f9e00383836275e46c0a9915767f9bb561be5fc30aae490174908a93e99a912` + +`"snapshot"` + + + +### `"step/end"` + +SHA-256: `c219efbdfd53b836786202ab4532913d555798f0a8615a4e9629736e3799fb70` + +`"step/end"` + + + +### `"step/start"` + +SHA-256: `2c64d4bc7264fd3f8bed47e96ab8a593bce05a1e4e1c436dcd244a3b7e0c2a9d` + +`"step/start"` + + + +### `"stop"` + +SHA-256: `57f3f606ff8b6c84d9c443faace9d1dd10cca0354f37a4f515ef075f7c351b90` + +`"stop"` + + + +### `string` + +SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b` + +Sources: [`packages/api/session-controller/src/types.ts:375`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:19`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:10`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/webhook/webhook/src/brand.ts:12`](../packages/webhook/webhook/src/brand.ts) · [`packages/webhook/webhook/src/brand.ts:6`](../packages/webhook/webhook/src/brand.ts) · [`packages/webhook/webhook/src/brand.ts:9`](../packages/webhook/webhook/src/brand.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) + +`string` + + + +### `"subagent"` + +SHA-256: `aacda84a69739238aa782eea902d3073017d9d74b9808de2bf5a83c9738cbdc6` + +`"subagent"` + + + +### `"subagent-settled"` + +SHA-256: `bf16d2e98a7f13894ded2cffe5ce857bf473954a0e3c0ed9bc7739e2e1c7b78a` + +`"subagent-settled"` + + + +### `"subagent/catalog"` + +SHA-256: `44c8c77aa9aa100b0d74d2deee85474b29faa6eee0ac0f662f7abe9931ddd7f2` + +`"subagent/catalog"` + + + +### `"subagent/descriptor"` + +SHA-256: `5ebce1317eacd2c148456627a09668e927ee15c204f0643f5ea76b46b54a2647` + +`"subagent/descriptor"` + + + +### `"subagent/model-selection-policy"` + +SHA-256: `0a8a062bc9c3c0d97fd97742262f2ea53dd32297f834e81114870d38ac3dc6da` + +`"subagent/model-selection-policy"` + + + +### `"success"` + +SHA-256: `511801b0a6470d98566e8385d3dffddb2b67f79fcf73ed6056f46c3cc0a245a7` + +`"success"` + + + +### `"system"` + +SHA-256: `8af841fc9b265ade3441caf9fe40dd24db24226befba48150e6d0eeccc1389c3` + +`"system"` + + + +### `"system/message"` + +SHA-256: `1c5a58f4599bd4a9711712551ffe2442a5e5fcd0758bafe630cc4ead850bdaa7` + +`"system/message"` + + + +### `"task-result"` + +SHA-256: `f05fb85e715af4ab4de0b6ba111166dd0435bd8bb74a0fa0f32b9d174113fc7c` + +`"task-result"` + + + +### `"team-message"` + +SHA-256: `2e4debca12f2c449f96dc6574f3fbf2fb346193443114a241d01a236362ec48c` + +`"team-message"` + + + +### `"team/member"` + +SHA-256: `18fcdd273a2f2f9d813a3be5e71df5903a963a5c88e46b90b14de6e2db890da0` + +`"team/member"` + + + +### `"team/message/delivered"` + +SHA-256: `a8a2b9011fc067862df7ce40748868467e5778283d7ac193c13f77684a914c57` + +`"team/message/delivered"` + + + +### `"team/message/queued"` + +SHA-256: `4e011753f8263f436efaabfbb3b4db53a98fc55342c5ded2daf7208b217be159` + +`"team/message/queued"` + + + +### `"team/task"` + +SHA-256: `b57ae1b37e52bee0a21cbdace168dc6a15582bcda8c306dad962c5dcc8a8e28b` + +`"team/task"` + + + +### `"text"` + +SHA-256: `4e44c9118152f6829634bfd508e47669ab0497caffa380c94b4be91f9390d35d` + +`"text"` + + + +### `"text-chunks"` + +SHA-256: `f9cfc89e8fe085b4fcc9cd32b74b6f103608c54d7d8cd97b47c01de34f252ba3` + +`"text-chunks"` + + + +### `"text-delta"` + +SHA-256: `5abab3a27fd2736660ecddd94b28d6bbf114986dd894880153bd1be3ccacb563` + +`"text-delta"` + + + +### `"todo/write"` + +SHA-256: `af0750d32d97d47b12b7361a587db41894b17e04c9a805fe212f0114aa2248c7` + +`"todo/write"` + + + +### `"tool"` + +SHA-256: `a13d8f379222ece8a02a86efe4c8334af4f108ecb3034b38c0a42bb3046e6847` + +`"tool"` + + + +### `"tool-call"` + +SHA-256: `1f0133217eeeffd8d387baf1ec8ed9856e719bcd999dc08e48624cba96d75fd7` + +`"tool-call"` + + + +### `"tool-call-chunks"` + +SHA-256: `616f93a5047de668e1c30d57b1786abd76beeb4123599637ec1dc5290320ede5` + +`"tool-call-chunks"` + + + +### `"tool-call-delta"` + +SHA-256: `9b4fd974c124c83743efe030ba4e1250e087505f3f74588716a521fb8d453267` + +`"tool-call-delta"` + + + +### `"tool-calls"` + +SHA-256: `6c1cccb433ddc153facb380c7dda956237ba5c31f8a532a69142dc277890cc9b` + +`"tool-calls"` + + + +### `"tool-result"` + +SHA-256: `e5244812c495a80c2e78ba2c2690dca5f6b58ea8d3fe5531ff94a6f9ac5e5d66` + +`"tool-result"` + + + +### `"tool-workflow/agent-end"` + +SHA-256: `5d17f27a1ef9f7026c0232714adf7573961536d75f3303ca97973975f5af170e` + +`"tool-workflow/agent-end"` + + + +### `"tool-workflow/agent-start"` + +SHA-256: `ad92f473a03ae72599ff26c86e897c25e16269519ad2a87340f0bea946517af8` + +`"tool-workflow/agent-start"` + + + +### `"tool-workflow/run-end"` + +SHA-256: `fe792a6cd430494503837e21e79f91677704ccdd9e870c679aabd7d43ed47ddd` + +`"tool-workflow/run-end"` + + + +### `"tool-workflow/run-start"` + +SHA-256: `cdca80b8dad24d4e7b1d5a288a8475ba8d8d46be385bd2da85f122ee836f1670` + +`"tool-workflow/run-start"` + + + +### `"tool/call"` + +SHA-256: `c589e938cc0e4989df8b82abef6339de621f0dc4fe62c230d145b94996af7f92` + +`"tool/call"` + + + +### `"tool/ptc-dispatch"` + +SHA-256: `fb5e0517050d2c6d40e5b8145db1f7c5ca7044d5d50fada6ff80e0b22c6ca973` + +`"tool/ptc-dispatch"` + + + +### `"tool/ptc-dispatch-start"` + +SHA-256: `2aed07493b1c65b6d00de5586da1e743cf501684b9ad43a1765fb4007d4ea456` + +`"tool/ptc-dispatch-start"` + + + +### `"tool/result"` + +SHA-256: `570e85022434613394418ad33e77e9f1f7a05699b33d0b62eae0677a638c3833` + +`"tool/result"` + + + +### `true` + +SHA-256: `dacf3d5f3e4ff619c1320e64a48dec5e7ecd7870bece65fbd449a0e63adfa521` + +`true` + + + +### `"turn/end"` + +SHA-256: `4312134fc6f9cad507c31390bd3d147535492dd77621ec517c5e85f853d53a03` + +`"turn/end"` + + + +### `"turn/start"` + +SHA-256: `0af3ecfb8c60d416fc40a7cc4fb2c4851a8e46608b2b38ab9e510670c971aa1f` + +`"turn/start"` + + + +### `"unavailable"` + +SHA-256: `2c4c21f19f426a58f2dac914e34313aa43f8408972de857a486d1f086d5adb6c` + +`"unavailable"` + + + +### `unknown` + +SHA-256: `bbc5696e3f13c758237b316e3cf08cda0b9fd51ffe672539883457769faa185d` + +`unknown`: the declaration does not expose the stored value's internal fields. + + + +### `"usage"` + +SHA-256: `0bdaf9cb63594be4ef034b68901fffe792fe09d6d5f6bac42d7f56f6e7834fb9` + +`"usage"` + + + +### `"user"` + +SHA-256: `68f44f59e91c41395076cb77265981a601680ec8a381bbbeb6ab754ffdc94573` + +`"user"` + + + +### `"user/message"` + +SHA-256: `de25352d2dc4196309bd84b5bac6319f6d5c898ea0e82bb5ae66c5738d0d426f` + +`"user/message"` + + + +### `"web_search"` + +SHA-256: `935c8d486292b13c87b57ca280eb8c58b5b2f5239be86fcd4334c33fdeb87682` + +`"web_search"` + + + +### `"web_search_20250305"` + +SHA-256: `0116b1f89c05221d56b0ba35c83d302bf4857bc7277d247e9bbaf74b27d2c66e` + +`"web_search_20250305"` + + + +### `"web/deepseek-search-llm-request"` + +SHA-256: `6f611bed14b2106542480c4a230b4a83baf76dc0d8ac0cd13319a7cb8dae7675` + +`"web/deepseek-search-llm-request"` + + + +### `"webhook"` + +SHA-256: `db6c5246911d47a7969f6b4cb47c7d0dbbde75c402710b22ace3672fa97bebe0` + +`"webhook"` + + + +### `"workspace-write"` + +SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa` + +`"workspace-write"` diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 69e491d920..504beffabe 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -1,15 +1,83 @@ - + # 会话持久化事件目录 [English](persistence-catalog.md) | 中文 -会话持久事件日志中可能出现的所有事件类型:完整持久化的 `SessionEvent` 信封,以及可通过合并扩展的 `SessionEventMap` 中的每个成员,包括 `@deepseek-ai/dsh-session` 所属的词汇和本仓库中每个插件对 `@deepseek-ai/dsh-session/types` 的声明合并,并附有源 JSDoc、完整 payload 声明、surface 标记和声明位置。本文档是 [session.md](subsystems/session.zh.md)(surface 排序与 `deriveMessages()` 投影)、[persistence.md](subsystems/persistence.zh.md)(如何让日志持久化)和 [session.md](subsystems/session.zh.md#cordis-surface) 中生成区域(实时总线接线;日志事件**不是** cordis 事件,它通过唯一的 `session/event` emit 到达监听器)的补充。 +本目录列出仓库声明的每个持久化 Session 事件及其源码声明和解析类型,覆盖逻辑与物理 header、事件信封以及各插件的声明合并。回放规则参见 [Session](subsystems/session.zh.md),存储规则参见[持久化](subsystems/persistence.zh.md)。 -英文源文件根据源码生成(`scripts/gen-persistence-catalog.ts`),并由 `pnpm run verify-persistence-catalog`(`doc-sync`(文档同步门禁)的一部分)验证新鲜度;本中文文件作为经评审对侧通过双语配对维护。声明块保留源码声明和嵌套属性的 JSDoc,只移除其所在接口/模块带来的缩进,并使用 `ts persistence-catalog` 围栏(doc-typecheck 会跳过这些围栏,因为声明引用了其所属模块中的类型)。payload 中的类型名称会链接到记录该类型的页面。已归档的 [persistence-log-catalog 记录](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md)记载了最初的目录决策。 +运行 `pnpm run gen-persistence-catalog` 可重新生成目录的两种语言、配对记录、已知事件模块和机器 schema 目录。`pnpm run verify-persistence-catalog` 检查所有生成文件。声明围栏保留源码 JSDoc 和类型引用;解析后的定义展开其传递引用结构。 -以下信封声明组合了每个事件的 `type`、单调递增的 `seq`、以 epoch 毫秒表示的 `time`、`data`、可选的未知类型跳过标记 `ignorable`,以及条件字段 `surfaceOp`/`sourceEventSeqs`。**surface** 表示 `SurfaceEventType` 成员:它会生成一条 LLM(大语言模型)消息,并声明该事件如何加入 surface 列表。**log-only** 表示其他所有事件:这类记录可持久化、可回放,但不参与派生历史。每个 payload 均可进行 JSON 序列化(在 `Session.append` 处强制执行)。当前 writer 会写入 `SESSION_FORMAT_VERSION`;受支持的历史产物通过构建期静态相邻迁移目录进入这套当前词汇(参见[版本生命周期](subsystems/persistence.zh.md))。范围仅限本仓库中的包;下游插件可以继续合并其他当前版本事件类型,这些类型按设计不属于本目录,并且在后续格式迁移边中需要显式 disposition。 +信封包含 `type`、`seq`、`time`、`data`、可选的 `ignorable` 以及条件字段 `surfaceOp` / `sourceEventSeqs`。**surface** 事件产生模型历史,**log-only** 事件不产生模型历史。目录覆盖本仓库;外部插件类型需要独立声明,不属于本目录。 + +## 持久化类型指纹 + +[机器可读目录](persistence-schema.json)包含所有可达的规范化类型及其 SHA-256 摘要。根类型的摘要涵盖引用类型。注释、源码位置、别名、擦除的品牌标记、readonly 标记以及无语义变化的字段、联合类型或交叉类型重排不影响摘要;元组顺序、属性名称、值类型和可选性会影响摘要。摘要不变时,目录文本和源码位置仍可能产生 diff。 + +[变更记录](persistence-changes/README.zh.md)通过保存在本源码树中的快照确认精确的类型转换。按照[评审流程](cookbook/reviewing-persistence-type-changes.zh.md)分类并记录变更。这些检查覆盖已声明的类型结构;不透明载荷的内部内容和未改变类型的行为变更不在检查范围内。 + +| 根类型 | 类别 | SHA-256 | 已解析类型 | +|---|---|---|---| +| `SessionHeader` | header | `2a8b114637e66faab5fae1c6e2fba65320db6224c80f5f36c5bf32f937154d03` | [`packages/core/session/src/types.ts#SessionHeader`](#persistence-type-packagescoresessionsrctypestssessionheader) | +| `JsonlHeaderLine` | header | `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` | [`packages/session/session-persistence-jsonl/src/format.ts#HeaderLine`](#persistence-type-packagessessionsession-persistence-jsonlsrcformattsheaderline) | +| `SessionEventEnvelope` | envelope | `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` | [`SessionEventEnvelope`](#persistence-type-sessioneventenvelope) | +| `event:agent-preset/selected` | event | `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | [`event:agent-preset/selected`](#persistence-type-eventagent-presetselected) | +| `event:agent/inbox/spliced` | event | `f757322e25914d63799c6235af4ed4b4f36d7d083fef40d6873a45e94fe6ad65` | [`event:agent/inbox/spliced`](#persistence-type-eventagentinboxspliced) | +| `event:approval/asked` | event | `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` | [`event:approval/asked`](#persistence-type-eventapprovalasked) | +| `event:approval/decided` | event | `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` | [`event:approval/decided`](#persistence-type-eventapprovaldecided) | +| `event:approval/policy` | event | `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` | [`event:approval/policy`](#persistence-type-eventapprovalpolicy) | +| `event:assistant/attempt` | event | `3516f54b427775f483febe8e938e4e10f9afd3583610861547bbe2999ed4d8d9` | [`event:assistant/attempt`](#persistence-type-eventassistantattempt) | +| `event:assistant/message` | event | `a58ca1bed502d425a8b65cac02f59717498cd296de9513c6f81b83b791c20e9b` | [`event:assistant/message`](#persistence-type-eventassistantmessage) | +| `event:command/done` | event | `15196447222782e773eb943c92b18316ce96b9af0f0cfddb6e57ba8274ecc5ff` | [`event:command/done`](#persistence-type-eventcommanddone) | +| `event:command/run` | event | `37184378c6439257d105c4e2022d80fc9c3a3f7c7f6ac661b00bc9f18d871006` | [`event:command/run`](#persistence-type-eventcommandrun) | +| `event:compaction/end` | event | `b0127044ab31a702bddfd785d345f5abd7a70876746e895ce443afa3e60ddf2d` | [`event:compaction/end`](#persistence-type-eventcompactionend) | +| `event:compaction/prune` | event | `7f7fd5a6b0064f597534b29ff62ef26e786dffccf5e14f654a7d4fcea2c35f04` | [`event:compaction/prune`](#persistence-type-eventcompactionprune) | +| `event:compaction/start` | event | `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` | [`event:compaction/start`](#persistence-type-eventcompactionstart) | +| `event:compaction/summary` | event | `eb1d32dcd3d76833392f351e039dcbd40686a1ab24b252a7a5003426f9cf4c89` | [`event:compaction/summary`](#persistence-type-eventcompactionsummary) | +| `event:deliverables/presented` | event | `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | [`event:deliverables/presented`](#persistence-type-eventdeliverablespresented) | +| `event:feedback/message-delete` | event | `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` | [`event:feedback/message-delete`](#persistence-type-eventfeedbackmessage-delete) | +| `event:feedback/message-put` | event | `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` | [`event:feedback/message-put`](#persistence-type-eventfeedbackmessage-put) | +| `event:feedback/record` | event | `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` | [`event:feedback/record`](#persistence-type-eventfeedbackrecord) | +| `event:goal/change` | event | `763c8a20af487263a0080548274f7437ef4a86e0ba8769127d1ced10a72c664d` | [`event:goal/change`](#persistence-type-eventgoalchange) | +| `event:hook/invoked` | event | `8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0` | [`event:hook/invoked`](#persistence-type-eventhookinvoked) | +| `event:hook/result` | event | `e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4` | [`event:hook/result`](#persistence-type-eventhookresult) | +| `event:llm/retry` | event | `91c397f8f870e812e1dc5ac69c2745f9f105be9dc97dadc980d19ef415a65145` | [`event:llm/retry`](#persistence-type-eventllmretry) | +| `event:llm/retry-started` | event | `48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3` | [`event:llm/retry-started`](#persistence-type-eventllmretry-started) | +| `event:model/selection` | event | `35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc` | [`event:model/selection`](#persistence-type-eventmodelselection) | +| `event:permission/preset` | event | `5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b` | [`event:permission/preset`](#persistence-type-eventpermissionpreset) | +| `event:plan/mode` | event | `a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f` | [`event:plan/mode`](#persistence-type-eventplanmode) | +| `event:request/context` | event | `37cbc9cf06d494cfe5c67f078af1605eacb8e4c2c853e9e26455b73de0ee7ccf` | [`event:request/context`](#persistence-type-eventrequestcontext) | +| `event:request/header` | event | `8ea13bcd983eab596dbfd41c5c2b62a0b318b71cd2a987a6fd752c6ced7e4489` | [`event:request/header`](#persistence-type-eventrequestheader) | +| `event:sandbox/mode` | event | `516da4cdd6d2f1e5ce488e648578ca51f40e458f707b803e5b24de870e799415` | [`event:sandbox/mode`](#persistence-type-eventsandboxmode) | +| `event:schedule/change` | event | `2a7f86849ae54b3398ee49661a757c4fcb59a6192b7036ee2ff514617e13fb42` | [`event:schedule/change`](#persistence-type-eventschedulechange) | +| `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`event:session-log-deepseek/delivery-accepted`](#persistence-type-eventsession-log-deepseekdelivery-accepted) | +| `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`event:session/end-seed`](#persistence-type-eventsessionend-seed) | +| `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`event:session/title`](#persistence-type-eventsessiontitle) | +| `event:session/title-llm-request` | event | `6091f64d426ada2adc646094a6fe662a1b81d9ee5770929fe76800524e2c89a8` | [`event:session/title-llm-request`](#persistence-type-eventsessiontitle-llm-request) | +| `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`event:step/end`](#persistence-type-eventstepend) | +| `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`event:step/start`](#persistence-type-eventstepstart) | +| `event:subagent/catalog` | event | `ae1f7110feeec697b8cab42b68f7709aa7b3279764099dfb53c25890d2e5c871` | [`event:subagent/catalog`](#persistence-type-eventsubagentcatalog) | +| `event:subagent/descriptor` | event | `b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5` | [`event:subagent/descriptor`](#persistence-type-eventsubagentdescriptor) | +| `event:subagent/model-selection-policy` | event | `a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc` | [`event:subagent/model-selection-policy`](#persistence-type-eventsubagentmodel-selection-policy) | +| `event:system/message` | event | `0ae7ccdc1cc5d9d43772ebd5c7357ff509011c3a6ba188d9c274d33df2542c42` | [`event:system/message`](#persistence-type-eventsystemmessage) | +| `event:team/member` | event | `4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc` | [`event:team/member`](#persistence-type-eventteammember) | +| `event:team/message/delivered` | event | `48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb` | [`event:team/message/delivered`](#persistence-type-eventteammessagedelivered) | +| `event:team/message/queued` | event | `2ffcad547cb840f462eb1849fa5f6cb0e17eb93f5d07cc88b61d749b1b78d632` | [`event:team/message/queued`](#persistence-type-eventteammessagequeued) | +| `event:team/task` | event | `d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a` | [`event:team/task`](#persistence-type-eventteamtask) | +| `event:todo/write` | event | `b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26` | [`event:todo/write`](#persistence-type-eventtodowrite) | +| `event:tool-workflow/agent-end` | event | `babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7` | [`event:tool-workflow/agent-end`](#persistence-type-eventtool-workflowagent-end) | +| `event:tool-workflow/agent-start` | event | `5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627` | [`event:tool-workflow/agent-start`](#persistence-type-eventtool-workflowagent-start) | +| `event:tool-workflow/run-end` | event | `42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f` | [`event:tool-workflow/run-end`](#persistence-type-eventtool-workflowrun-end) | +| `event:tool-workflow/run-start` | event | `c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058` | [`event:tool-workflow/run-start`](#persistence-type-eventtool-workflowrun-start) | +| `event:tool/call` | event | `3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48` | [`event:tool/call`](#persistence-type-eventtoolcall) | +| `event:tool/ptc-dispatch` | event | `da807583a19f4ebe2bc9115c9686016e1521e405bdfaced296ae652b91e8e5f6` | [`event:tool/ptc-dispatch`](#persistence-type-eventtoolptc-dispatch) | +| `event:tool/ptc-dispatch-start` | event | `ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93` | [`event:tool/ptc-dispatch-start`](#persistence-type-eventtoolptc-dispatch-start) | +| `event:tool/result` | event | `cc3e270ab982cbafff602b753647b6f056decb7fe4fd523afd2f157a65c3d622` | [`event:tool/result`](#persistence-type-eventtoolresult) | +| `event:turn/end` | event | `84c24f1209fc3e0153de6ac85d58fd6968b851f955e0e761b92321053956609e` | [`event:turn/end`](#persistence-type-eventturnend) | +| `event:turn/start` | event | `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` | [`event:turn/start`](#persistence-type-eventturnstart) | +| `event:user/message` | event | `2e49ede7fb3a592cce8919ebe982f6ca1c1f67abe4def359ffc028c85e6d1ad8` | [`event:user/message`](#persistence-type-eventusermessage) | +| `event:web/deepseek-search-llm-request` | event | `cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331` | [`event:web/deepseek-search-llm-request`](#persistence-type-eventwebdeepseek-search-llm-request) | ## 事件信封 @@ -402,16 +470,16 @@ export type SessionEvent = { -#### `deliverables/presented` — 仅日志 +#### `deliverables/presented` — log-only ```ts persistence-catalog /** Declared filesystem files from a successful final present result, including nested calls. */ 'deliverables/presented': { turn: number; callId: ToolCallId; files: PresentedFile[] } ``` -类型: [ToolCallId](subsystems/core.zh.md) +类型:[ToolCallId](subsystems/core.zh.md) -来源: [`packages/fs/tool-present/src/types.ts:15`](../packages/fs/tool-present/src/types.ts) +来源:[`packages/fs/tool-present/src/types.ts:15`](../packages/fs/tool-present/src/types.ts) ### `feedback/*` @@ -811,7 +879,7 @@ export type SessionEvent = { -#### `subagent/model-selection-policy` — 仅日志 +#### `subagent/model-selection-policy` — log-only ```ts persistence-catalog /** @@ -1153,3 +1221,5373 @@ export type SessionEvent = { ``` 来源:[`packages/web/web-search-deepseek/src/provider.ts:83`](../packages/web/web-search-deepseek/src/provider.ts) + +## 已解析的持久化类型 + +每个类型定义仅列出一次。引用保留共享和递归关系;定义旁的摘要涵盖其完整可达结构。源码名称和位置标识声明来源,但不参与摘要计算。 + + + +### `0` + +SHA-256: `93d38436347f67c9022aacbc4472e283d722bcab8eb73fc376aaf7b6a6b9fb35` + +`0` + + + +### `1` + +SHA-256: `f88f1b7b6bb733f685f5fb6a8744a5db9199fa53c90463ea1a33bad51153671d` + +`1` + + + +### `2` + +SHA-256: `a0f906856fc06f1c6f2083eb970e5a657a82b1a84c06c095445e5d6d161cda80` + +`2` + + + +### `3` + +SHA-256: `137ac60e26bcb192210d9a32a704b73b0924ed34019323defa1b787be95bd45e` + +`3` + + + +### `"aborted"` + +SHA-256: `1e0b0b66b6f0c53fc72e52289d06b4708d776f1ca9df4c86a22bff621d403cf0` + +`"aborted"` + + + +### `"active"` + +SHA-256: `c8e6f48878cc2a2f0dc59682280d81f407943fb503f32c64bf9ff3d9393c3eff` + +`"active"` + + + +### `"after"` + +SHA-256: `5b232a9b9bfcc0fc6794dab7865bcf203a65785c5de5aa647a51228bff628413` + +`"after"` + + + +### `"agent-instructions"` + +SHA-256: `fbd2c74b346a1595b376139c587fadd23e4a21acc9c0abce342975b6cfc60986` + +`"agent-instructions"` + + + +### `"agent-message"` + +SHA-256: `82470020b5bfcb1a843e2aa05602af7d37df99e82d01f015c7c589ba054fd89d` + +`"agent-message"` + + + +### `"agent-preset/selected"` + +SHA-256: `557e148d30b79ee48645850832b30c717d7c3b64641deb5d6133b8b4e91ee0b8` + +`"agent-preset/selected"` + + + +### `"agent/inbox/spliced"` + +SHA-256: `829a2fb3be6efd4ba36aef8ed2d381a4c6fb2a0aaf50c823a6f3f8290786b809` + +`"agent/inbox/spliced"` + + + +### `"allowed-once"` + +SHA-256: `811229eedcec57979dbadca7dbea832a59f8580cff30cb217fca5e2da16314bd` + +`"allowed-once"` + + + +### `"always"` + +SHA-256: `142c37d302aa22a0a57915b5255939d3ca91c1811ca13fddcf84cf76ea00758a` + +`"always"` + + + +### `"append"` + +SHA-256: `e438a9d4fb883b3e4a8d9fc8913aa1550ed50eb384bfbf943100a5605b537498` + +`"append"` + + + +### `"approval/asked"` + +SHA-256: `cd186c309e2bef305705e1d324139240f5e681f4f6a957d6a25b0225f65560df` + +`"approval/asked"` + + + +### `"approval/decided"` + +SHA-256: `378f90f9f6022a0991928a1eaf7991094ea1920fe89a9248358a1da106e1a970` + +`"approval/decided"` + + + +### `"approval/policy"` + +SHA-256: `36d6a9a871d949ea4f3edd919d81d66aa69218bebef38899d2a1282c39420a1f` + +`"approval/policy"` + + + +### `"ask"` + +SHA-256: `9e07693fd7cd9babe0688c22c986e4d519ae1c0e40eeea13b2dc583bed2abc86` + +`"ask"` + + + +### `"assistant"` + +SHA-256: `95c0e88fcf0bafb2087f912ad9e2369de6ae6530cc0c4c2772c96205b57f7f60` + +`"assistant"` + + + +### `"assistant/attempt"` + +SHA-256: `e95f957154b2b1b58692af53d4131180b6dd19e2264869f876dbaa959991494e` + +`"assistant/attempt"` + + + +### `"assistant/message"` + +SHA-256: `6ab45ac76839722df912f3ba50f1c6b41f2d614bde11f1aabfae04117ccdc80d` + +`"assistant/message"` + + + +### `"at"` + +SHA-256: `17aae616ef020b8b9f011ec398ec935450fb06d3e2a0722c456f63aeaf5ff728` + +`"at"` + + + +### `"block"` + +SHA-256: `1e346446e183ee76f48d7994c97ecb302bddc682abb60b8a10ea0bb26223a69c` + +`"block"` + + + +### `"block-end"` + +SHA-256: `eefbea819caeeea91ccb02dfafc16c68dd7f961cf9c1b2be25a2084509cf5c5a` + +`"block-end"` + + + +### `"block-start"` + +SHA-256: `cbca49bde16b656d5b8fbe4a8a507769f7b594c6287782b809c03814941bc5e3` + +`"block-start"` + + + +### `"blocked"` + +SHA-256: `254dce209e735f9cb6f6b7aec0354712b1611759ec02af4d35a96f2081e666f6` + +`"blocked"` + + + +### `boolean` + +SHA-256: `aa93010d0e521ad7c42273f58d5c9b8bf1dfc94edf1b805a3971b364a35c1877` + +`boolean` + + + +### `"canceled"` + +SHA-256: `d4e4d575abeb4b72d616dc65c17ac0eaa943cc135609395044c38d5afcb10e31` + +`"canceled"` + + + +### `"cancelled"` + +SHA-256: `2142e391b9ccb60d487afcf373ce74db98d952f69e8289eddb352a7e16e996c5` + +`"cancelled"` + + + +### `"catalog"` + +SHA-256: `aec150797b655c5f15ebb7a501454b0c208fbaccec9ace49d7de2171fc02f47d` + +`"catalog"` + + + +### `"change"` + +SHA-256: `f789af5ab844827eb5a4f357c0afae1eeed108c6c22d8ca1a56e586b349a7c85` + +`"change"` + + + +### `"chunk"` + +SHA-256: `14230f9b8b5797bb7e8c621fa636531cfbde2ffba1ef5f1fd058216ff21952a0` + +`"chunk"` + + + +### `"claude-code"` + +SHA-256: `aff60ef947c18b012644852bdd0d5bfe72065e061dd563787c76c646b4a10f6b` + +`"claude-code"` + + + +### `"clear"` + +SHA-256: `e7e154d47d5fbc5366330fb4dade1f108c6dfe4335044d21ded14b7bf1dce084` + +`"clear"` + + + +### `"codex"` + +SHA-256: `15495614ccfa9cbdeefb0ec2abd76406435f4c6ff3dae42acae8e09ef42025c9` + +`"codex"` + + + +### `"command/done"` + +SHA-256: `8cdd3fdd295cee53035de16a1b23411716f75e9f994fe9510cac5d7fef605c76` + +`"command/done"` + + + +### `"command/run"` + +SHA-256: `f1996af2602e6f62b5ab179ca5c4af00d8753938b74cc6ef102de09e99e74a7e` + +`"command/run"` + + + +### `"compaction/end"` + +SHA-256: `1986c2664009c99155e44795676c887fa67f7c55cd2d5e6b648ff763e0ab033a` + +`"compaction/end"` + + + +### `"compaction/prune"` + +SHA-256: `10e386801903f9d424602c31d3ff837a94aef623c8b391376d8d9e253525fabe` + +`"compaction/prune"` + + + +### `"compaction/start"` + +SHA-256: `e2d0367e5716bc188435e51718ebff8ce873190625fa93b1c5331102d98758fa` + +`"compaction/start"` + + + +### `"compaction/summary"` + +SHA-256: `ef4f00923b9edbae15c7ef05320280bf61147ca17146cf59690359bfd5e5da45` + +`"compaction/summary"` + + + +### `"complete"` + +SHA-256: `d9196086e41e4dd5c2ce87f5c25529c8112a1731d258cf0728747d41a9ee3241` + +`"complete"` + + + +### `"completed"` + +SHA-256: `1ebc6f9ff3aa9e3a38f8695f49bacdab1f8b7a3832741853ca46f40af56ccd01` + +`"completed"` + + + +### `"continuable"` + +SHA-256: `e5d3828df1ec3e2a66879de0659e0f4866d9cfffc00825ee731a66f8a0c03d98` + +`"continuable"` + + + +### `"create"` + +SHA-256: `7624ca28c61ca0eaa4f015140f3cea6e0c16cce2f2960fab5f531203d253903e` + +`"create"` + + + +### `"danger-full-access"` + +SHA-256: `18fce9540bb20897a66275499faee6b8296c97e9901fa1ff45a191772448ad1a` + +`"danger-full-access"` + + + +### `"delegation"` + +SHA-256: `76632533f67879dcb346fcae640540dbc89b2472b0bce88c59f7e48fc5c0e668` + +`"delegation"` + + + +### `"delete"` + +SHA-256: `d0fbb0ca1bbba059830826f33f801da1aced35a2504376d61e020bdfec8d50c0` + +`"delete"` + + + +### `"deleted"` + +SHA-256: `e7e8f1318170b8284f16ab45a557afd4675c96071c0f2a456636481dab20b427` + +`"deleted"` + + + +### `"deliverables/presented"` + +SHA-256: `1a2718b00937b236a0d0910ef5dab2f9f8b65e4bde410cc21f15fbbf29f584d3` + +`"deliverables/presented"` + + + +### `"dispatch"` + +SHA-256: `26c879bdd130e8b8683a6ce588414fad2244783d6573ca399fa0de184a2b0bb1` + +`"dispatch"` + + + +### `"disposed"` + +SHA-256: `b38f206d59bad891fa58c89f97a0036987b3defe7685acbd318fa55eb85f9751` + +`"disposed"` + + + +### `"edit"` + +SHA-256: `ad22a5ca5bbaab2e1440779cd3648e85ddfc6f73b5207394eda699555cf31430` + +`"edit"` + + + +### `"error"` + +SHA-256: `a2be0f0cb59f0d4a06557d45847d1938aced238baa77a6da416e590b8b958553` + +`"error"` + + + +### `event:agent-preset/selected` + +SHA-256: `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:agent-preset/selected.data`](#persistence-type-eventagent-presetselecteddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"agent-preset/selected"` | + + + +### `event:agent-preset/selected.data` + +SHA-256: `6e52dd55a1d48954ed5464ec46fe34d9eb65f709baabc9561e7a279910b0c406` + +来源:[`packages/preset/agent-presets/src/session.ts:28`](../packages/preset/agent-presets/src/session.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `agentPreset` | 必需 | `string` | + + + +### `event:agent/inbox/spliced` + +SHA-256: `f757322e25914d63799c6235af4ed4b4f36d7d083fef40d6873a45e94fe6ad65` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:agent/inbox/spliced.data`](#persistence-type-eventagentinboxspliceddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"agent/inbox/spliced"` | + + + +### `event:agent/inbox/spliced.data` + +SHA-256: `7115142af1bff1ad9b52c3c785109bf5c989e77e3ddfe899014e213731352042` + +来源:[`packages/core/agent/src/types.ts:87`](../packages/core/agent/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `inserted` | 必需 | [`event:agent/inbox/spliced.data.inserted`](#persistence-type-eventagentinboxspliceddatainserted) | +| `outcome` | 可选 | `"canceled"` | +| `removedCount` | 可选 | `number` | +| `start` | 必需 | `number` | +| `target` | 必需 | [`packages/core/agent/src/types.ts#InboxTarget`](#persistence-type-packagescoreagentsrctypestsinboxtarget) | + + + +### `event:agent/inbox/spliced.data.inserted` + +SHA-256: `86560bf0779bb2d93975405fe895d9d42c1020ab49fbbd2a06e1b3c74fbbc257` + +[`packages/llm/llm/src/message.ts#UserMessage`](#persistence-type-packagesllmllmsrcmessagetsusermessage) 的数组。 + + + +### `event:agent/inbox/spliced.data.inserted[0].content` + +SHA-256: `0e560aad8fa9f338a60e76cbce6593ce93486ab30d6dd7833b8b5b07c6e6963d` + +[`packages/llm/llm/src/types.ts#ContentBlock`](#persistence-type-packagesllmllmsrctypestscontentblock) 的数组。 + + + +### `event:agent/inbox/spliced.data.inserted[0].content[0][2].attachment.originalDimensions` + +SHA-256: `0f46153645c297846a3fad3911636659ca4717a5e7d71c9f50bffacd312115ef` + +来源:[`packages/attachment/attachment/src/types.ts:28`](../packages/attachment/attachment/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `height` | 必需 | `number` | +| `width` | 必需 | `number` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[0]` + +SHA-256: `ada310bf0bdb8fed51f3b56ea63f6ea6b18bbd587f04fccb63a14ab0b2a24e05` + +来源:[`packages/core/session/src/types.ts:189`](../packages/core/session/src/types.ts) · [`packages/interaction/commands/src/types.ts:75`](../packages/interaction/commands/src/types.ts) · [`packages/interaction/commands/src/types.ts:79`](../packages/interaction/commands/src/types.ts) · [`packages/llm/llm/src/message.ts:103`](../packages/llm/llm/src/message.ts) · [`packages/session/session-title/src/types.ts:35`](../packages/session/session-title/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"user"` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[1]` + +SHA-256: `0ddab4577db37a3e0d9e3a91761735c8ef6f5629d526ab8ecaeeea19c097c8f8` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"plugin"` | +| `plugin` | 必需 | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[10]` + +SHA-256: `6a4f72e2e179e17b922f2a9392c0e1c8f8c707f32494372454850a3eb184a6e7` + +来源:[`packages/api/session-controller/src/types.ts:380`](../packages/api/session-controller/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `clientTimeZone` | 可选 | `string` | +| `kind` | 必需 | `"user"` | +| `rpcId` | 必需 | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[15].references` + +SHA-256: `df843efd87c78cf8de98f8e0d0216956cd568d0d0e56a22c853663e274aa1a18` + +[`event:agent/inbox/spliced.data.inserted[0].source[15].references[0]`](#persistence-type-eventagentinboxspliceddatainserted0source15references0) 的数组。 + + + +### `event:agent/inbox/spliced.data.inserted[0].source[15].references[0]` + +SHA-256: `4f91f187baeabef5c7950c4780d31bbe39f3345dff591a6cf26ac7e135ac1cf4` + +来源:[`packages/context/session-reference/src/types.ts:18`](../packages/context/session-reference/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `capturedFormatVersion` | 可选 | `number` | +| `capturedThroughSeq` | 必需 | [`event:compaction/end.data.turn`](#persistence-type-eventcompactionenddataturn) | +| `compacted` | 必需 | `boolean` | +| `inputIndex` | 必需 | `number` | +| `label` | 必需 | `string` | +| `omittedBytes` | 必需 | `number` | +| `omittedMessages` | 必需 | `number` | +| `originalMessages` | 必需 | `number` | +| `retainedMessages` | 必需 | `number` | +| `sessionId` | 必需 | `string` | +| `truncated` | 必需 | `boolean` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[16].entries` + +SHA-256: `aba175fb20ffc71112c55efe70458ff5d7dbfb83501892b4ba8d077f0ea5d7ba` + +[`event:agent/inbox/spliced.data.inserted[0].source[16].entries[0]`](#persistence-type-eventagentinboxspliceddatainserted0source16entries0) 的数组。 + + + +### `event:agent/inbox/spliced.data.inserted[0].source[16].entries[0]` + +SHA-256: `83079c8a3f733ac3fa603eefa0fbb4737ca4d8b69e7e2e7298b60f9ac4098693` + +来源:[`packages/skill/tool-skill/src/index.ts:40`](../packages/skill/tool-skill/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `description` | 必需 | `string` | +| `name` | 必需 | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[17].changes` + +SHA-256: `2ab12a0e345658f9342816d773fe610f049982bcad82631f43b9205abe43e5ee` + +[`packages/context/agent-instructions/src/render.ts#AgentInstructionChange`](#persistence-type-packagescontextagent-instructionssrcrendertsagentinstructionchange) 的数组。 + + + +### `event:agent/inbox/spliced.data.inserted[0].source[17].changes[0].action` + +SHA-256: `904bf499d98be4a3a5f836fcac533b6c4899569b018071e6604e1d32657d00fd` + +以下类型之一: + +- `"remove"` +- `"replace"` +- `"set"` + + + +### `event:agent/inbox/spliced.data.inserted[0].source[19]` + +SHA-256: `9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94` + +来源:[`packages/webhook/webhook/src/types.ts:74`](../packages/webhook/webhook/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `deliveryId` | 必需 | `string` | +| `form` | 必需 | `"notice"` | +| `kind` | 必需 | `"webhook"` | +| `provider` | 必需 | `string` | +| `ruleId` | 必需 | `string` | +| `source` | 必需 | `string` | +| `summary` | 必需 | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[2]` + +SHA-256: `dda6e111a8ccfc12f6e742e977a1a4e3549e8c03732a5ff6c8d7c3d4bcb7a412` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"instructions"` | +| `kind` | 必需 | `"plugin"` | +| `plugin` | 必需 | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[3]` + +SHA-256: `2f90a7b3646fea3e677bd7b4000aa96f3b8964f107b614e204e11ad4051c24fc` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"catalog"` | +| `kind` | 必需 | `"plugin"` | +| `plugin` | 必需 | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[4]` + +SHA-256: `e8d26e6993445743e6c4342c31b284c7d8f5574369abdd6021183af1a6b3538f` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"snapshot"` | +| `kind` | 必需 | `"plugin"` | +| `plugin` | 必需 | `string` | +| `sections` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].source[4].sections`](#persistence-type-eventagentinboxspliceddatainserted0source4sections) | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[4].sections` + +SHA-256: `c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262` + +[`packages/llm/llm/src/message.ts#ContextSnapshotSection`](#persistence-type-packagesllmllmsrcmessagetscontextsnapshotsection) 的数组。 + + + +### `event:agent/inbox/spliced.data.inserted[0].source[5]` + +SHA-256: `b4d298d90c798fa4d1f5b030ce3ce8031c4c41beafdb05323bec8b2a06903b43` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"notice"` | +| `kind` | 必需 | `"plugin"` | +| `plugin` | 必需 | `string` | +| `summary` | 必需 | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[6]` + +SHA-256: `2ce7ef3b3daebf95815c7ae454a751d3e67ca7a22e2e0734fe2942df393bb66c` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"relay"` | +| `kind` | 必需 | `"plugin"` | +| `plugin` | 必需 | `string` | + + + +### `event:agent/inbox/spliced.data.inserted[0].source[7]` + +SHA-256: `85e1bd563d6c80c1f377f99f91103fec4e315148a7330f3b3dae7c2d04e33b01` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"recall"` | +| `kind` | 必需 | `"plugin"` | +| `plugin` | 必需 | `string` | + + + +### `event:approval/asked` + +SHA-256: `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:approval/asked.data`](#persistence-type-eventapprovalaskeddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"approval/asked"` | + + + +### `event:approval/asked.data` + +SHA-256: `45f2a07ea94573123b6ae14db787a40062dec3834f36d8be92c523d40ba28448` + +来源:[`packages/interaction/user-approval/src/types.ts:44`](../packages/interaction/user-approval/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `callId` | 可选 | `string` | +| `id` | 必需 | `string` | +| `reason` | 可选 | `string` | +| `toolName` | 必需 | `string` | + + + +### `event:approval/decided` + +SHA-256: `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:approval/decided.data`](#persistence-type-eventapprovaldecideddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"approval/decided"` | + + + +### `event:approval/decided.data` + +SHA-256: `93cbbc5a7fd0b1aa1ff1b9a6bb5ec52b073f48e5a5200365c45c0cd54059e1b9` + +来源:[`packages/interaction/user-approval/src/types.ts:55`](../packages/interaction/user-approval/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `id` | 必需 | `string` | +| `outcome` | 必需 | [`packages/interaction/user-approval/src/types.ts#ApprovalOutcome`](#persistence-type-packagesinteractionuser-approvalsrctypestsapprovaloutcome) | + + + +### `event:approval/policy` + +SHA-256: `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:approval/policy.data`](#persistence-type-eventapprovalpolicydata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"approval/policy"` | + + + +### `event:approval/policy.data` + +SHA-256: `11f70b595aa76897fc4e25a352bfa718c659f44b799e5aa14e84e5a38fe72912` + +来源:[`packages/interaction/user-approval/src/index.ts:33`](../packages/interaction/user-approval/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `policy` | 必需 | [`packages/interaction/user-approval/src/index.ts#ApprovalPolicy`](#persistence-type-packagesinteractionuser-approvalsrcindextsapprovalpolicy) | +| `source` | 可选 | `"delegation"` | + + + +### `event:assistant/attempt` + +SHA-256: `3516f54b427775f483febe8e938e4e10f9afd3583610861547bbe2999ed4d8d9` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:assistant/attempt.data`](#persistence-type-eventassistantattemptdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"assistant/attempt"` | + + + +### `event:assistant/attempt.data` + +SHA-256: `8725743fcd3db5ddaf3e0aed550cd5d95785d8e30c9d4cbec11b7c90198d3603` + +来源:[`packages/core/session/src/types.ts:335`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `step` | 必需 | `number` | +| `stream` | 必需 | [`event:assistant/attempt.data.stream`](#persistence-type-eventassistantattemptdatastream) | +| `turn` | 必需 | `number` | + + + +### `event:assistant/attempt.data.stream` + +SHA-256: `86755151e6c4694ce5ae9511313e1e50f9fb6ead49c67d0a93c056e8650396c4` + +[`packages/llm/llm/src/assistant-stream.ts#AssistantStreamRecord`](#persistence-type-packagesllmllmsrcassistant-streamtsassistantstreamrecord) 的数组。 + + + +### `event:assistant/attempt.data.stream[0][0]` + +SHA-256: `e72269f931559e901e5e8fe1cfcbd0669fb795f43cc038ff39fba62f60403c33` + +来源:[`packages/llm/llm/src/assistant-stream.ts:21`](../packages/llm/llm/src/assistant-stream.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `dt` | 必需 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `index` | 必需 | `number` | +| `texts` | 必需 | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `time0` | 必需 | `number` | +| `type` | 必需 | `"text-chunks"` | + + + +### `event:assistant/attempt.data.stream[0][0].texts` + +SHA-256: `93c33d9687613293f8c95d46c4d922fe9ceae83b84c384beff5c3315abe005f2` + +`string` 的数组。 + + + +### `event:assistant/attempt.data.stream[0][1]` + +SHA-256: `71bb3254ec8f1e491ce63b762a7b5f18709de23aa4e8abcc8b886f4b6ce051dd` + +来源:[`packages/llm/llm/src/assistant-stream.ts:28`](../packages/llm/llm/src/assistant-stream.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `dt` | 必需 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `index` | 必需 | `number` | +| `texts` | 必需 | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `time0` | 必需 | `number` | +| `type` | 必需 | `"reasoning-chunks"` | + + + +### `event:assistant/attempt.data.stream[0][2]` + +SHA-256: `3860b4757d512a2869be833acb0faf834335b1b056bf3f83df5474dce95f8fa6` + +来源:[`packages/llm/llm/src/assistant-stream.ts:35`](../packages/llm/llm/src/assistant-stream.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `args` | 必需 | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `dt` | 必需 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `id` | 必需 | `string` | +| `index` | 必需 | `number` | +| `name` | 可选 | `string` | +| `time0` | 必需 | `number` | +| `type` | 必需 | `"tool-call-chunks"` | + + + +### `event:assistant/attempt.data.stream[0][3]` + +SHA-256: `3a896379a74576c5a527e4720be2fe6807d3225c27134e978a2b89a94068368e` + +来源:[`packages/llm/llm/src/assistant-stream.ts:44`](../packages/llm/llm/src/assistant-stream.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `chunk` | 必需 | [`packages/llm/llm/src/types.ts#StreamChunk`](#persistence-type-packagesllmllmsrctypestsstreamchunk) | +| `time` | 必需 | `number` | +| `type` | 必需 | `"chunk"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[0]` + +SHA-256: `647cb2e3aeb58e27d1d4d60011f208bb66dce257e8654193c533b47c4c22c253` + +来源:[`packages/llm/llm/src/types.ts:391`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `blockType` | 必需 | [`packages/llm/llm/src/types.ts#ContentBlockType`](#persistence-type-packagesllmllmsrctypestscontentblocktype) | +| `index` | 必需 | `number` | +| `type` | 必需 | `"block-start"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[1]` + +SHA-256: `95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65` + +来源:[`packages/llm/llm/src/types.ts:392`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `index` | 必需 | `number` | +| `text` | 必需 | `string` | +| `type` | 必需 | `"text-delta"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[2]` + +SHA-256: `2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095` + +来源:[`packages/llm/llm/src/types.ts:393`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `index` | 必需 | `number` | +| `text` | 必需 | `string` | +| `type` | 必需 | `"reasoning-delta"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[3]` + +SHA-256: `88a610b831be6d92612a464be5c7d0b88ec24d97bd299837bf74bf7672183c9a` + +来源:[`packages/llm/llm/src/types.ts:394`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `argumentsDelta` | 必需 | `string` | +| `id` | 必需 | `string` | +| `index` | 必需 | `number` | +| `name` | 可选 | `string` | +| `type` | 必需 | `"tool-call-delta"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[4]` + +SHA-256: `6326a7ecfed005f3d54b737ab9d702da4daddb75cfc635d9096e9abb4788024e` + +来源:[`packages/llm/llm/src/types.ts:395`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `block` | 必需 | [`packages/llm/llm/src/types.ts#ContentBlock`](#persistence-type-packagesllmllmsrctypestscontentblock) | +| `index` | 必需 | `number` | +| `type` | 必需 | `"block-end"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[5]` + +SHA-256: `f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515` + +来源:[`packages/llm/llm/src/types.ts:396`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `type` | 必需 | `"usage"` | +| `usage` | 必需 | [`packages/llm/llm/src/types.ts#TokenUsage`](#persistence-type-packagesllmllmsrctypeststokenusage) | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6]` + +SHA-256: `a4ed3446d32625902170fce527fb64a0283d6f4817930f54a16fdce3a482b36a` + +来源:[`packages/llm/llm/src/types.ts:397`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `reason` | 必需 | [`packages/llm/llm/src/types.ts#FinishReason`](#persistence-type-packagesllmllmsrctypestsfinishreason) | +| `replayState` | 可选 | [`packages/llm/llm/src/types.ts#ReplayEnvelope`](#persistence-type-packagesllmllmsrctypestsreplayenvelope) | +| `type` | 必需 | `"finish"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].reason[0]` + +SHA-256: `64cfc4dbd3739f0ee040689482cb27058890f1d6113abb1de756836318aeebbc` + +来源:[`packages/llm/llm/src/types.ts:134`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `failure` | 必需 | [`packages/llm/llm/src/types.ts#LlmFailure`](#persistence-type-packagesllmllmsrctypestsllmfailure) | +| `kind` | 必需 | `"aborted"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].reason[1]` + +SHA-256: `ca13866102dfc53a503fccddeebc9caf667b8a06ef27217dd65de29f0495e65d` + +来源:[`packages/llm/llm/src/types.ts:135`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `failure` | 必需 | [`packages/llm/llm/src/types.ts#LlmFailure`](#persistence-type-packagesllmllmsrctypestsllmfailure) | +| `kind` | 必需 | `"error"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].reason[2]` + +SHA-256: `ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7` + +来源:[`packages/core/session/src/types.ts:213`](../packages/core/session/src/types.ts) · [`packages/llm/llm/src/types.ts:133`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"max-tokens"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].reason[3]` + +SHA-256: `1d76406cbb2ddadb0dbbb51e9d8bc6cf678fed455cf8eb512f36f7c86d37a62f` + +来源:[`packages/llm/llm/src/types.ts:131`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"stop"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].reason[4]` + +SHA-256: `287211846beea1eccb5f9acee00d20388a38fcb2e95858fa2332d6f58529379a` + +来源:[`packages/llm/llm/src/types.ts:132`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"tool-calls"` | + + + +### `event:assistant/attempt.data.stream[0][3].chunk[6].replayState.blocks` + +SHA-256: `b1664fa89f8eab49a14e7e3168d8bc4da4aa9fd76332f6c4a053d839e5276ae7` + +`unknown`(不透明) 的数组。 + + + +### `event:assistant/message` + +SHA-256: `a58ca1bed502d425a8b65cac02f59717498cd296de9513c6f81b83b791c20e9b` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:assistant/message.data`](#persistence-type-eventassistantmessagedata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `surfaceOp` | 必需 | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | 必需 | `number` | +| `type` | 必需 | `"assistant/message"` | + + + +### `event:assistant/message.data` + +SHA-256: `a2418dd9dd938f04a3eaf1f27694b33facfb931b6e495dcfb4d5fcfa2ef82b28` + +来源:[`packages/core/session/src/types.ts:321`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `interrupted` | 可选 | `true` | +| `message` | 必需 | [`packages/llm/llm/src/message.ts#AssistantMessage`](#persistence-type-packagesllmllmsrcmessagetsassistantmessage) | +| `step` | 必需 | `number` | +| `stream` | 必需 | [`event:assistant/attempt.data.stream`](#persistence-type-eventassistantattemptdatastream) | +| `turn` | 必需 | `number` | +| `usage` | 可选 | [`packages/llm/llm/src/types.ts#TokenUsage`](#persistence-type-packagesllmllmsrctypeststokenusage) | + + + +### `event:command/done` + +SHA-256: `15196447222782e773eb943c92b18316ce96b9af0f0cfddb6e57ba8274ecc5ff` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:command/done.data`](#persistence-type-eventcommanddonedata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"command/done"` | + + + +### `event:command/done.data` + +SHA-256: `5b6fb1f226ff56402db08a6aae82af76100b14056271875f69ec526a1ae05d51` + +来源:[`packages/interaction/commands/src/types.ts:112`](../packages/interaction/commands/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `commandId` | 必需 | `string` | +| `kind` | 必需 | [`event:command/done.data.kind`](#persistence-type-eventcommanddonedatakind) | +| `sourceEventSeq` | 可选 | `number` | +| `text` | 可选 | `string` | + + + +### `event:command/done.data.kind` + +SHA-256: `1ab2ef1296f157ba96998f179bb5daa9d21a82480da2901127aeab2730797c92` + +以下类型之一: + +- `"error"` +- `"success"` + + + +### `event:command/run` + +SHA-256: `37184378c6439257d105c4e2022d80fc9c3a3f7c7f6ac661b00bc9f18d871006` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:command/run.data`](#persistence-type-eventcommandrundata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"command/run"` | + + + +### `event:command/run.data` + +SHA-256: `a853902cfab417b8f08aa51ad6855fea46f34ed4588a74e47403035111d0fb86` + +来源:[`packages/interaction/commands/src/types.ts:105`](../packages/interaction/commands/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `args` | 可选 | `string` | +| `commandId` | 必需 | `string` | +| `name` | 必需 | `string` | +| `source` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].source[0]`](#persistence-type-eventagentinboxspliceddatainserted0source0) | + + + +### `event:compaction/end` + +SHA-256: `b0127044ab31a702bddfd785d345f5abd7a70876746e895ce443afa3e60ddf2d` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:compaction/end.data`](#persistence-type-eventcompactionenddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"compaction/end"` | + + + +### `event:compaction/end.data` + +SHA-256: `ca59530d1a4f3e191b4032bc4a20eded0d827db8b5a7efb27b3af9e9b456c6f2` + +来源:[`packages/compaction/compaction/src/types.ts:72`](../packages/compaction/compaction/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `compactionId` | 必需 | `string` | +| `error` | 可选 | `string` | +| `sourceCommandId` | 可选 | `string` | +| `turn` | 必需 | [`event:compaction/end.data.turn`](#persistence-type-eventcompactionenddataturn) | + + + +### `event:compaction/end.data.turn` + +SHA-256: `3bd652ebfa8726b3ce3937a4f1bd2759e02f86b3a08a20f9e41a8513656fbc5f` + +来源:[`packages/core/session/src/types.ts:64`](../packages/core/session/src/types.ts) + +以下类型之一: + +- `null` +- `number` + + + +### `event:compaction/prune` + +SHA-256: `7f7fd5a6b0064f597534b29ff62ef26e786dffccf5e14f654a7d4fcea2c35f04` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:compaction/prune.data`](#persistence-type-eventcompactionprunedata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"compaction/prune"` | + + + +### `event:compaction/prune.data` + +SHA-256: `5dd766ccd1702a1142ad8b4bd44f2242f8e8a6752669ad3de0a5d224329b1438` + +来源:[`packages/compaction/compaction/src/types.ts:82`](../packages/compaction/compaction/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `shadowedRange` | 必需 | [`event:compaction/prune.data.shadowedRange`](#persistence-type-eventcompactionprunedatashadowedrange) | +| `shadowedSeqs` | 必需 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `shadowedTokenCount` | 必需 | `number` | + + + +### `event:compaction/prune.data.shadowedRange` + +SHA-256: `9e41386b3a0c9572b0d63078492ebb3997da7d3a830d44e0259418bd02f4bcb2` + +来源:[`packages/compaction/compaction/src/types.ts:38`](../packages/compaction/compaction/src/types.ts) · [`packages/compaction/compaction/src/types.ts:84`](../packages/compaction/compaction/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `end` | 必需 | `number` | +| `start` | 必需 | `number` | + + + +### `event:compaction/start` + +SHA-256: `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:compaction/start.data`](#persistence-type-eventcompactionstartdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"compaction/start"` | + + + +### `event:compaction/start.data` + +SHA-256: `3e4e7683b0192c9f0e6a75c59076108f017892cdb302a98032c2632fd79c356f` + +来源:[`packages/compaction/compaction/src/types.ts:24`](../packages/compaction/compaction/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `compactionId` | 必需 | `string` | +| `sourceCommandId` | 可选 | `string` | +| `turn` | 必需 | [`event:compaction/end.data.turn`](#persistence-type-eventcompactionenddataturn) | + + + +### `event:compaction/summary` + +SHA-256: `eb1d32dcd3d76833392f351e039dcbd40686a1ab24b252a7a5003426f9cf4c89` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:compaction/summary.data`](#persistence-type-eventcompactionsummarydata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"compaction/summary"` | + + + +### `event:compaction/summary.data` + +SHA-256: `0a6fae8c3249943e23a024ae6c0d9dd707df9bff0badc69786f0dc8ad9482d8e` + +以下类型之一: + +- [`event:compaction/summary.data[0]`](#persistence-type-eventcompactionsummarydata0) +- [`event:compaction/summary.data[1]`](#persistence-type-eventcompactionsummarydata1) + + + +### `event:compaction/summary.data[0]` + +SHA-256: `1e75cbc0af53f62e5685d751108dc9f6320fe835aed222e579a2a0080dc8fc59` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `compactionId` | 必需 | `string` | +| `llmStreamCall` | 必需 | `true` | +| `maxTokens` | 可选 | `number` | +| `model` | 必需 | `string` | +| `provider` | 必需 | `string` | +| `rawOutput` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `shadowedRange` | 必需 | [`event:compaction/prune.data.shadowedRange`](#persistence-type-eventcompactionprunedatashadowedrange) | +| `shadowedSeqs` | 必需 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `shadowedTokenCount` | 必需 | `number` | +| `sourceCommandId` | 可选 | `string` | +| `summary` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `usage` | 可选 | [`packages/llm/llm/src/types.ts#TokenUsage`](#persistence-type-packagesllmllmsrctypeststokenusage) | + + + +### `event:compaction/summary.data[1]` + +SHA-256: `ac5a8baa3dda8fa667b4975d6a8b40a9bde47d8d754ca8ed6b8ce923a1651828` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `compactionId` | 必需 | `string` | +| `maxTokens` | 可选 | `number` | +| `model` | 必需 | `string` | +| `provider` | 必需 | `string` | +| `rawOutput` | 可选 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `shadowedRange` | 必需 | [`event:compaction/prune.data.shadowedRange`](#persistence-type-eventcompactionprunedatashadowedrange) | +| `shadowedSeqs` | 必需 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `shadowedTokenCount` | 必需 | `number` | +| `sourceCommandId` | 可选 | `string` | +| `summary` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `usage` | 可选 | [`packages/llm/llm/src/types.ts#TokenUsage`](#persistence-type-packagesllmllmsrctypeststokenusage) | + + + +### `event:deliverables/presented` + +SHA-256: `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:deliverables/presented.data`](#persistence-type-eventdeliverablespresenteddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"deliverables/presented"` | + + + +### `event:deliverables/presented.data` + +SHA-256: `1528539c63db8b23506f0209a99ce77d8ad138adfbfcee3d4769b7382d93756c` + +来源:[`packages/fs/tool-present/src/types.ts:15`](../packages/fs/tool-present/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `callId` | 必需 | `string` | +| `files` | 必需 | [`event:deliverables/presented.data.files`](#persistence-type-eventdeliverablespresenteddatafiles) | +| `turn` | 必需 | `number` | + + + +### `event:deliverables/presented.data.files` + +SHA-256: `1d9cb3caa96100b18b1911fa3ff8c751ef6992971a03c5f4d9aefc1a0004a345` + +[`packages/fs/tool-present/src/types.ts#PresentedFile`](#persistence-type-packagesfstool-presentsrctypestspresentedfile) 的数组。 + + + +### `event:feedback/message-delete` + +SHA-256: `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/feedback/message-feedback/src/types.ts#MessageFeedbackDelete`](#persistence-type-packagesfeedbackmessage-feedbacksrctypestsmessagefeedbackdelete) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"feedback/message-delete"` | + + + +### `event:feedback/message-put` + +SHA-256: `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/feedback/message-feedback/src/types.ts#MessageFeedbackPut`](#persistence-type-packagesfeedbackmessage-feedbacksrctypestsmessagefeedbackput) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"feedback/message-put"` | + + + +### `event:feedback/message-put.data.item.category` + +SHA-256: `3305a4bb6b6c29b482ba91d59c60b47e8f98282d3dacc8c29e4a4c2712209bdd` + +以下类型之一: + +- `"instruction-following"` +- `"other"` +- `"product-interaction"` +- `"resource-cost"` +- `"security-privacy-permission"` +- `"service-stability"` +- `"task-result"` + + + +### `event:feedback/record` + +SHA-256: `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/feedback/command-feedback/src/types.ts#FeedbackRecord`](#persistence-type-packagesfeedbackcommand-feedbacksrctypestsfeedbackrecord) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"feedback/record"` | + + + +### `event:goal/change` + +SHA-256: `763c8a20af487263a0080548274f7437ef4a86e0ba8769127d1ced10a72c664d` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/goal/goal/src/domain.ts#GoalChangeMeta`](#persistence-type-packagesgoalgoalsrcdomaintsgoalchangemeta) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"goal/change"` | + + + +### `event:goal/change.data[0].operation` + +SHA-256: `62e6a429ff3f390a46ac39fb468692032f42936ca0db19b53454184189401379` + +以下类型之一: + +- `"block"` +- `"complete"` +- `"create"` +- `"edit"` +- `"pause"` +- `"resume"` + + + +### `event:hook/invoked` + +SHA-256: `8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:hook/invoked.data`](#persistence-type-eventhookinvokeddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"hook/invoked"` | + + + +### `event:hook/invoked.data` + +SHA-256: `119abe2b9c0b2f46ecb31d82b9acba4d4b30e049f655e2475e8b4ae12d5a9e25` + +来源:[`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `dialect` | 必需 | [`packages/hooks/hook-protocol/src/types.ts#HookDialect`](#persistence-type-packageshookshook-protocolsrctypestshookdialect) | +| `handlerId` | 必需 | `string` | +| `matcher` | 可选 | `string` | +| `point` | 必需 | `string` | +| `turn` | 必需 | `number` | + + + +### `event:hook/result` + +SHA-256: `e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:hook/result.data`](#persistence-type-eventhookresultdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"hook/result"` | + + + +### `event:hook/result.data` + +SHA-256: `8ad45ad053b4d89f8865c17cc9ff3b8748e86045f3f0fb816269a34af2619141` + +来源:[`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-protocol/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `decision` | 必需 | `string` | +| `durationMs` | 必需 | `number` | +| `exitCode` | 可选 | `number` | +| `handlerId` | 必需 | `string` | +| `point` | 必需 | `string` | +| `stderrSummary` | 可选 | `string` | +| `turn` | 必需 | `number` | + + + +### `event:llm/retry` + +SHA-256: `91c397f8f870e812e1dc5ac69c2745f9f105be9dc97dadc980d19ef415a65145` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/llm/llm-retry/src/types.ts#LlmRetryEventData`](#persistence-type-packagesllmllm-retrysrctypestsllmretryeventdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"llm/retry"` | + + + +### `event:llm/retry-started` + +SHA-256: `48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/llm/llm-retry/src/types.ts#LlmRetryStartedEventData`](#persistence-type-packagesllmllm-retrysrctypestsllmretrystartedeventdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"llm/retry-started"` | + + + +### `event:llm/retry.data[0]` + +SHA-256: `9096d1dd86a3da77a593e12cd95e596b2ef965fa94fcbee40e4bdcf0433924a1` + +来源:[`packages/llm/llm-retry/src/types.ts:17`](../packages/llm/llm-retry/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `delayMs` | 必需 | `number` | +| `failure` | 必需 | [`packages/llm/llm/src/types.ts#LlmFailure`](#persistence-type-packagesllmllmsrctypestsllmfailure) | +| `maxRetries` | 必需 | `number` | +| `mode` | 必需 | `"normal"` | +| `policyKey` | 必需 | `string` | +| `provider` | 必需 | `string` | +| `retry` | 必需 | `number` | +| `retryId` | 必需 | `string` | +| `step` | 必需 | `number` | +| `turn` | 必需 | `number` | + + + +### `event:llm/retry.data[1]` + +SHA-256: `c0f91c064454e9ae25a4b5caacdc487712aee3acf414b07b7d5f7fd3530a0452` + +来源:[`packages/llm/llm-retry/src/types.ts:30`](../packages/llm/llm-retry/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `delayMs` | 必需 | `number` | +| `failure` | 必需 | [`packages/llm/llm/src/types.ts#LlmFailure`](#persistence-type-packagesllmllmsrctypestsllmfailure) | +| `mode` | 必需 | `"always"` | +| `policyKey` | 必需 | `string` | +| `provider` | 必需 | `string` | +| `retry` | 必需 | `number` | +| `retryId` | 必需 | `string` | +| `step` | 必需 | `number` | +| `turn` | 必需 | `number` | + + + +### `event:model/selection` + +SHA-256: `35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/api/session-controller/src/types.ts#ModelSelection`](#persistence-type-packagesapisession-controllersrctypestsmodelselection) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"model/selection"` | + + + +### `event:permission/preset` + +SHA-256: `5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:permission/preset.data`](#persistence-type-eventpermissionpresetdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"permission/preset"` | + + + +### `event:permission/preset.data` + +SHA-256: `8a9c990773f798eac10ca8878e6bcc531ee080bc589cac956730e462cc6a09ad` + +来源:[`packages/interaction/permission-presets/src/index.ts:54`](../packages/interaction/permission-presets/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `preset` | 必需 | `string` | + + + +### `event:plan/mode` + +SHA-256: `a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:plan/mode.data`](#persistence-type-eventplanmodedata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"plan/mode"` | + + + +### `event:plan/mode.data` + +SHA-256: `20a57544bbf204dacd1ae6beb2ab29444365c3c869f2afdf489dcdcbc3d9c8c5` + +来源:[`packages/plan/plan-mode/src/index.ts:47`](../packages/plan/plan-mode/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `active` | 必需 | `boolean` | + + + +### `event:request/context` + +SHA-256: `37cbc9cf06d494cfe5c67f078af1605eacb8e4c2c853e9e26455b73de0ee7ccf` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/core/session/src/types.ts#RequestContext`](#persistence-type-packagescoresessionsrctypestsrequestcontext) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"request/context"` | + + + +### `event:request/header` + +SHA-256: `8ea13bcd983eab596dbfd41c5c2b62a0b318b71cd2a987a6fd752c6ced7e4489` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:request/header.data`](#persistence-type-eventrequestheaderdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"request/header"` | + + + +### `event:request/header.data` + +SHA-256: `2f0aefb72ab453c4bf21a6687f436775d359dc6f8f10410fd4c0615b20bd9d76` + +来源:[`packages/core/session/src/types.ts:365`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `header` | 必需 | [`packages/core/session/src/types.ts#EpochHeader`](#persistence-type-packagescoresessionsrctypestsepochheader) | +| `reason` | 必需 | [`packages/core/session/src/types.ts#RequestHeaderReason`](#persistence-type-packagescoresessionsrctypestsrequestheaderreason) | +| `startsSeries` | 可选 | `true` | + + + +### `event:request/header.data.header.tools` + +SHA-256: `6d63f25a28b70801937a46b1058be5f52d4efbaf3516141470463ae469b59c23` + +[`packages/llm/llm/src/types.ts#ToolSchema`](#persistence-type-packagesllmllmsrctypeststoolschema) 的数组。 + + + +### `event:request/header.data.header.tools[0].parameters` + +SHA-256: `0aa62ab51b36c0e64b134bd03ba8725d758744180a1a447da49beaad194f219f` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| [`string`] | 索引签名 | `unknown`(不透明) | + + + +### `event:sandbox/mode` + +SHA-256: `516da4cdd6d2f1e5ce488e648578ca51f40e458f707b803e5b24de870e799415` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:sandbox/mode.data`](#persistence-type-eventsandboxmodedata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"sandbox/mode"` | + + + +### `event:sandbox/mode.data` + +SHA-256: `9890113e97dd3541787bfe6704f2b223d3a890b5325ca0acae2b0398909b413e` + +来源:[`packages/sandbox/sandbox-policy/src/session-mode.ts:33`](../packages/sandbox/sandbox-policy/src/session-mode.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `mode` | 必需 | [`packages/sandbox/sandbox/src/index.ts#SandboxMode`](#persistence-type-packagessandboxsandboxsrcindextssandboxmode) | +| `source` | 可选 | `"delegation"` | + + + +### `event:schedule/change` + +SHA-256: `2a7f86849ae54b3398ee49661a757c4fcb59a6192b7036ee2ff514617e13fb42` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/schedule/schedule/src/types.ts#ScheduleChange`](#persistence-type-packagesscheduleschedulesrctypestsschedulechange) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"schedule/change"` | + + + +### `event:session-log-deepseek/delivery-accepted` + +SHA-256: `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:session-log-deepseek/delivery-accepted.data`](#persistence-type-eventsession-log-deepseekdelivery-accepteddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"session-log-deepseek/delivery-accepted"` | + + + +### `event:session-log-deepseek/delivery-accepted.data` + +SHA-256: `8bb8c3751a6ce64b61aba1ec7ae798c00a5803c4d868a0430873216c6569ed52` + +来源:[`packages/session/session-log-deepseek/src/types.ts:81`](../packages/session/session-log-deepseek/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `sessionFormatVersion` | 可选 | `number` | +| `sessionId` | 必需 | `string` | +| `throughSeq` | 必需 | `number` | + + + +### `event:session/end-seed` + +SHA-256: `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:session/end-seed.data`](#persistence-type-eventsessionend-seeddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"session/end-seed"` | + + + +### `event:session/end-seed.data` + +SHA-256: `17d1afb770d9941936130996da00dc86782cfef731d8d6526162c301256a4ac3` + +来源:[`packages/core/session/src/types.ts:400`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `inherited` | 可选 | `true` | + + + +### `event:session/title` + +SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/session/session-title/src/types.ts#SessionTitleEventData`](#persistence-type-packagessessionsession-titlesrctypestssessiontitleeventdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"session/title"` | + + + +### `event:session/title-llm-request` + +SHA-256: `6091f64d426ada2adc646094a6fe662a1b81d9ee5770929fe76800524e2c89a8` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData`](#persistence-type-packagessessionsession-title-llmsrcindextssessiontitlellmrequesteventdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"session/title-llm-request"` | + + + +### `event:session/title-llm-request.data.messages` + +SHA-256: `f3cfc98798d8b0e726ee22b05b13ada5cb19e9cdbe105ef3529699de99ce357a` + +[`packages/llm/llm/src/message.ts#Message`](#persistence-type-packagesllmllmsrcmessagetsmessage) 的数组。 + + + +### `event:session/title-llm-request.data.messages[0].role` + +SHA-256: `acd36e1717d2b4ef9e98a8e6fac17ca1203f3f448e5bca51677f129a8f4999e2` + +以下类型之一: + +- `"assistant"` +- `"system"` +- `"user"` + + + +### `event:session/title.data.source[0]` + +SHA-256: `1b5649238876e28811e80f10ec67a00c197ad3babd4ecc61a22b09064b88c597` + +来源:[`packages/session/session-title/src/types.ts:29`](../packages/session/session-title/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"fallback"` | + + + +### `event:session/title.data.source[1]` + +SHA-256: `b81e9de81a481b24e1e1f9adf038bfc59e57b4f35ce62f8f1e45284186f4f812` + +来源:[`packages/session/session-title/src/types.ts:30`](../packages/session/session-title/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"provider"` | +| `model` | 可选 | [`packages/session/session-title/src/types.ts#SessionTitleModelIdentity`](#persistence-type-packagessessionsession-titlesrctypestssessiontitlemodelidentity) | +| `provider` | 必需 | `string` | + + + +### `event:step/end` + +SHA-256: `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:step/end.data`](#persistence-type-eventstependdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"step/end"` | + + + +### `event:step/end.data` + +SHA-256: `ef11a3b7fa2033ae4279545ff8a8a2ee4e301b9b73e5b91654c48e6d08283545` + +来源:[`packages/core/session/src/types.ts:287`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:289`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `step` | 必需 | `number` | +| `turn` | 必需 | `number` | + + + +### `event:step/start` + +SHA-256: `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:step/end.data`](#persistence-type-eventstependdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"step/start"` | + + + +### `event:subagent/catalog` + +SHA-256: `ae1f7110feeec697b8cab42b68f7709aa7b3279764099dfb53c25890d2e5c871` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/subagent/subagent/src/catalog.ts#SubagentCatalogEvent`](#persistence-type-packagessubagentsubagentsrccatalogtssubagentcatalogevent) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"subagent/catalog"` | + + + +### `event:subagent/catalog.data[0]` + +SHA-256: `5d542ac7bdf13310c46e1a884dec9fb18ad8c6e153cff207e639cd7bf9d7e694` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `childCreatedAt` | 必需 | `number` | +| `childId` | 必需 | `string` | +| `label` | 可选 | `string` | +| `mode` | 必需 | `"one-shot"` | +| `version` | 必需 | `0` | + + + +### `event:subagent/catalog.data[1]` + +SHA-256: `0e10d7c096b47e61defe372a5fc8bd37d835199410eac44367f95c45082498bd` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `childCreatedAt` | 必需 | `number` | +| `childId` | 必需 | `string` | +| `label` | 必需 | `string` | +| `mode` | 必需 | `"continuable"` | +| `version` | 必需 | `0` | + + + +### `event:subagent/descriptor` + +SHA-256: `b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/subagent/subagent/src/descriptor.ts#SubagentDescriptorData`](#persistence-type-packagessubagentsubagentsrcdescriptortssubagentdescriptordata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"subagent/descriptor"` | + + + +### `event:subagent/model-selection-policy` + +SHA-256: `a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:subagent/model-selection-policy.data`](#persistence-type-eventsubagentmodel-selection-policydata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"subagent/model-selection-policy"` | + + + +### `event:subagent/model-selection-policy.data` + +SHA-256: `0d47a8f3d847243c4488e755ad4cab7a04ac5d4621b9c81865524cf3f4d8536f` + +来源:[`packages/subagent/tool-subagent/src/model-selection-state.ts:17`](../packages/subagent/tool-subagent/src/model-selection-state.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `allowedModels` | 必需 | [`event:subagent/model-selection-policy.data.allowedModels`](#persistence-type-eventsubagentmodel-selection-policydataallowedmodels) | + + + +### `event:subagent/model-selection-policy.data.allowedModels` + +SHA-256: `ba77b58995e36f14bb13886b0493d0a1a63d5db10b1a6cd9f6ac65a72cf832ec` + +[`packages/session/session-title/src/types.ts#SessionTitleModelIdentity`](#persistence-type-packagessessionsession-titlesrctypestssessiontitlemodelidentity) 的数组。 + + + +### `event:system/message` + +SHA-256: `0ae7ccdc1cc5d9d43772ebd5c7357ff509011c3a6ba188d9c274d33df2542c42` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:system/message.data`](#persistence-type-eventsystemmessagedata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `sourceEventSeqs` | 可选 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `surfaceOp` | 必需 | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | 必需 | `number` | +| `type` | 必需 | `"system/message"` | + + + +### `event:system/message.data` + +SHA-256: `d489162978ec04f1f76f40a68c7715326f46ec4f696edf1f321efdf88545fc18` + +来源:[`packages/core/session/src/types.ts:310`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `message` | 必需 | [`packages/llm/llm/src/message.ts#SystemMessage`](#persistence-type-packagesllmllmsrcmessagetssystemmessage) | +| `step` | 必需 | `number` | +| `turn` | 必需 | `number` | + + + +### `event:system/message.data.message.source` + +SHA-256: `23cf7e0cf3fec1283b60fa15713506169bc9c079a08e124d1cb9733baca80b40` + +以下类型之一: + +- [`event:agent/inbox/spliced.data.inserted[0].source[4]`](#persistence-type-eventagentinboxspliceddatainserted0source4) +- [`event:agent/inbox/spliced.data.inserted[0].source[5]`](#persistence-type-eventagentinboxspliceddatainserted0source5) +- [`event:agent/inbox/spliced.data.inserted[0].source[3]`](#persistence-type-eventagentinboxspliceddatainserted0source3) +- [`event:agent/inbox/spliced.data.inserted[0].source[2]`](#persistence-type-eventagentinboxspliceddatainserted0source2) +- [`event:agent/inbox/spliced.data.inserted[0].source[7]`](#persistence-type-eventagentinboxspliceddatainserted0source7) +- [`event:agent/inbox/spliced.data.inserted[0].source[6]`](#persistence-type-eventagentinboxspliceddatainserted0source6) +- [`event:agent/inbox/spliced.data.inserted[0].source[1]`](#persistence-type-eventagentinboxspliceddatainserted0source1) + + + +### `event:team/member` + +SHA-256: `4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:team/member.data`](#persistence-type-eventteammemberdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"team/member"` | + + + +### `event:team/member.data` + +SHA-256: `b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1` + +来源:[`packages/experimental/agent-team/src/types.ts:221`](../packages/experimental/agent-team/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `member` | 必需 | [`packages/experimental/agent-team/src/types.ts#TeamMemberSnapshot`](#persistence-type-packagesexperimentalagent-teamsrctypeststeammembersnapshot) | +| `teamId` | 必需 | `string` | +| `version` | 必需 | `2` | + + + +### `event:team/member.data.member.context` + +SHA-256: `50cf02d875f8a1f1411f4a6fbcffab9120ccef5f74a47331fbf29480e5f68f48` + +以下类型之一: + +- `"fork"` +- `"fresh"` + + + +### `event:team/message/delivered` + +SHA-256: `48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:team/message/delivered.data`](#persistence-type-eventteammessagedelivereddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"team/message/delivered"` | + + + +### `event:team/message/delivered.data` + +SHA-256: `0bcfaf5566d271028429ac7b562cb59053c9d78b8ae14708c35fac835108d1b1` + +来源:[`packages/experimental/agent-team/src/types.ts:227`](../packages/experimental/agent-team/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `messageId` | 必需 | `string` | +| `targetId` | 必需 | `string` | +| `teamId` | 必需 | `string` | +| `version` | 必需 | `2` | + + + +### `event:team/message/queued` + +SHA-256: `2ffcad547cb840f462eb1849fa5f6cb0e17eb93f5d07cc88b61d749b1b78d632` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:team/message/queued.data`](#persistence-type-eventteammessagequeueddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"team/message/queued"` | + + + +### `event:team/message/queued.data` + +SHA-256: `6132f7814b28287473605ebc6dacd63c7c699435452487da6152bc735b8ba8c4` + +来源:[`packages/experimental/agent-team/src/types.ts:225`](../packages/experimental/agent-team/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `message` | 必需 | [`packages/experimental/agent-team/src/types.ts#TeamMessageSnapshot`](#persistence-type-packagesexperimentalagent-teamsrctypeststeammessagesnapshot) | +| `teamId` | 必需 | `string` | +| `version` | 必需 | `2` | + + + +### `event:team/task` + +SHA-256: `d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:team/task.data`](#persistence-type-eventteamtaskdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"team/task"` | + + + +### `event:team/task.data` + +SHA-256: `a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c` + +来源:[`packages/experimental/agent-team/src/types.ts:223`](../packages/experimental/agent-team/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `task` | 必需 | [`packages/experimental/agent-team/src/types.ts#TeamTaskSnapshot`](#persistence-type-packagesexperimentalagent-teamsrctypeststeamtasksnapshot) | +| `teamId` | 必需 | `string` | +| `version` | 必需 | `2` | + + + +### `event:todo/write` + +SHA-256: `b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:todo/write.data`](#persistence-type-eventtodowritedata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"todo/write"` | + + + +### `event:todo/write.data` + +SHA-256: `79ddc907312f97cf9553d4841e8b3fc937097bd8d97900a790efad3c05255e99` + +来源:[`packages/todo/tool-todo/src/types.ts:31`](../packages/todo/tool-todo/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `todos` | 必需 | [`event:todo/write.data.todos`](#persistence-type-eventtodowritedatatodos) | + + + +### `event:todo/write.data.todos` + +SHA-256: `4b7ddaf3710b88ff9b8df66304f5f97bf03688fd19730eedc2e9535203c820ea` + +[`packages/todo/tool-todo/src/types.ts#TodoItem`](#persistence-type-packagestodotool-todosrctypeststodoitem) 的数组。 + + + +### `event:todo/write.data.todos[0].status` + +SHA-256: `fd0d91a1f9dd2efb8959bdf5e07f8f0a98b1cdfa38ce8d4732ebde0b44c3abd4` + +以下类型之一: + +- `"completed"` +- `"in_progress"` +- `"pending"` + + + +### `event:tool-workflow/agent-end` + +SHA-256: `babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentEndData`](#persistence-type-packagesworkflowtool-workflowsrctypeststoolworkflowagentenddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"tool-workflow/agent-end"` | + + + +### `event:tool-workflow/agent-start` + +SHA-256: `5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentStartData`](#persistence-type-packagesworkflowtool-workflowsrctypeststoolworkflowagentstartdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"tool-workflow/agent-start"` | + + + +### `event:tool-workflow/run-end` + +SHA-256: `42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunEndData`](#persistence-type-packagesworkflowtool-workflowsrctypeststoolworkflowrunenddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"tool-workflow/run-end"` | + + + +### `event:tool-workflow/run-start` + +SHA-256: `c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunStartData`](#persistence-type-packagesworkflowtool-workflowsrctypeststoolworkflowrunstartdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"tool-workflow/run-start"` | + + + +### `event:tool/call` + +SHA-256: `3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:tool/call.data`](#persistence-type-eventtoolcalldata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"tool/call"` | + + + +### `event:tool/call.data` + +SHA-256: `e4c18e294232c3ba6c9f1999f168263cc55956147cfe4121720899be8e52edd1` + +来源:[`packages/core/session/src/types.ts:341`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `arguments` | 必需 | `string` | +| `callId` | 必需 | `string` | +| `name` | 必需 | `string` | +| `step` | 必需 | `number` | +| `turn` | 必需 | `number` | + + + +### `event:tool/ptc-dispatch` + +SHA-256: `da807583a19f4ebe2bc9115c9686016e1521e405bdfaced296ae652b91e8e5f6` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/core/tools/src/types.ts#PtcDispatchEventData`](#persistence-type-packagescoretoolssrctypestsptcdispatcheventdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"tool/ptc-dispatch"` | + + + +### `event:tool/ptc-dispatch-start` + +SHA-256: `ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/core/tools/src/types.ts#PtcDispatchStartEventData`](#persistence-type-packagescoretoolssrctypestsptcdispatchstarteventdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"tool/ptc-dispatch-start"` | + + + +### `event:tool/result` + +SHA-256: `cc3e270ab982cbafff602b753647b6f056decb7fe4fd523afd2f157a65c3d622` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:tool/result.data`](#persistence-type-eventtoolresultdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `sourceEventSeqs` | 可选 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `surfaceOp` | 必需 | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | 必需 | `number` | +| `type` | 必需 | `"tool/result"` | + + + +### `event:tool/result.data` + +SHA-256: `a1082bd67be26461ebcc4cc9caba60ef548cd6ec625b51558d5cfa5b6b38718a` + +来源:[`packages/core/session/src/types.ts:353`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `error` | 可选 | [`event:tool/result.data.error`](#persistence-type-eventtoolresultdataerror) | +| `message` | 必需 | [`packages/llm/llm/src/message.ts#ToolResultMessage`](#persistence-type-packagesllmllmsrcmessagetstoolresultmessage) | +| `meta` | 可选 | [`event:tool/result.data.meta`](#persistence-type-eventtoolresultdatameta) | +| `step` | 必需 | `number` | +| `turn` | 必需 | `number` | + + + +### `event:tool/result.data.error` + +SHA-256: `55e3b6df99b0d19241e57618b04b84824c3df80ed102e440719139300ea703f6` + +来源:[`packages/core/session/src/types.ts:358`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `code` | 必需 | `string` | +| `name` | 必需 | `string` | + + + +### `event:tool/result.data.message.content` + +SHA-256: `5eedf09e88b5ed9ca2d5efdb5a32cd4f51352564af5ff82927c17d841a2e3d9e` + +| 位置 | 存在性 | 类型 | +|---|---|---| +| 0 | 必需 | [`packages/llm/llm/src/types.ts#ToolResultBlock`](#persistence-type-packagesllmllmsrctypeststoolresultblock) | + + + +### `event:tool/result.data.meta` + +SHA-256: `c592ce75aab73fcab19c1d7845684c72cf402b78d2e1f2833a58ecf9f3598ed6` + +来源:[`packages/util/values/src/index.ts:4`](../packages/util/values/src/index.ts) + +以下类型之一: + +- [`event:tool/result.data.meta[5]`](#persistence-type-eventtoolresultdatameta5) +- [`event:tool/result.data.meta[5][0][6]`](#persistence-type-eventtoolresultdatameta506) +- `string` +- `boolean` +- `null` +- `number` + + + +### `event:tool/result.data.meta[5]` + +SHA-256: `72ec79127a9c0d0241b1106a74cc2b24c81ce467170f1c3c93f7b71a9496d227` + +[`event:tool/result.data.meta`](#persistence-type-eventtoolresultdatameta) 的数组。 + + + +### `event:tool/result.data.meta[5][0][6]` + +SHA-256: `529e0ccd0e34079ab4ff21ed87f810cee0388246263bf22e8aba5986b56fae05` + +来源:[`packages/util/values/src/index.ts:4`](../packages/util/values/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| [`string`] | 索引签名 | [`event:tool/result.data.meta`](#persistence-type-eventtoolresultdatameta) | + + + +### `event:turn/end` + +SHA-256: `84c24f1209fc3e0153de6ac85d58fd6968b851f955e0e761b92321053956609e` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:turn/end.data`](#persistence-type-eventturnenddata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"turn/end"` | + + + +### `event:turn/end.data` + +SHA-256: `508c0856b8df8574e396880a756f087699b138630428441fefca4a4380f01a28` + +来源:[`packages/core/session/src/types.ts:285`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `reason` | 必需 | [`packages/core/session/src/types.ts#TurnEndReason`](#persistence-type-packagescoresessionsrctypeststurnendreason) | +| `turn` | 必需 | `number` | + + + +### `event:turn/end.data.reason[0]` + +SHA-256: `b6ef9c6210be8350cd465f485cf520031fffa6bf3809c61a1e713e834c5982d0` + +来源:[`packages/core/session/src/types.ts:201`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"completed"` | + + + +### `event:turn/end.data.reason[1]` + +SHA-256: `0a30c3be336cdd4f8f34e66543a3d46d611c43986edd9b3ce6cbb9b721d3b5d3` + +来源:[`packages/core/session/src/types.ts:203`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"aborted"` | +| `reason` | 必需 | [`packages/core/session/src/types.ts#TurnEndCancelCause`](#persistence-type-packagescoresessionsrctypeststurnendcancelcause) | + + + +### `event:turn/end.data.reason[1].reason[1]` + +SHA-256: `6c03546143cdf6ae88f1745ac98c5139efa1588d1415249bbc5f3104d32ab800` + +来源:[`packages/core/session/src/types.ts:190`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"parent"` | + + + +### `event:turn/end.data.reason[1].reason[2]` + +SHA-256: `6c5a37d42ebdb12db4410699259335f1e149e3a0260425684c00b153afc8990d` + +来源:[`packages/core/session/src/types.ts:191`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"hook"` | +| `reason` | 必需 | `string` | + + + +### `event:turn/end.data.reason[1].reason[3]` + +SHA-256: `4bb451330ff699f36b45539ceb0eb2606ead72084e2c5bb637c36cc80e2252db` + +来源:[`packages/core/session/src/types.ts:192`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"disposed"` | + + + +### `event:turn/end.data.reason[1].reason[4]` + +SHA-256: `eaa71be0fbf0f702df8ea6c11d0aa2f9782263d45a10d0fb14eda662f4bfe04c` + +来源:[`packages/core/session/src/types.ts:195`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"legacy"` | + + + +### `event:turn/end.data.reason[2]` + +SHA-256: `023a355b2be0d688fb6e4df8388dadb942761d46cfe96d0f499aae9631a159d4` + +来源:[`packages/core/session/src/types.ts:205`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"blocked"` | + + + +### `event:turn/end.data.reason[3]` + +SHA-256: `3dad205ad99fbda835c493ddc6bc93fe1441cef2f82774ffa2856364e2cede22` + +来源:[`packages/core/session/src/types.ts:211`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `error` | 必需 | [`packages/llm/llm/src/types.ts#LlmFailure`](#persistence-type-packagesllmllmsrctypestsllmfailure) | +| `kind` | 必需 | `"error"` | + + + +### `event:turn/end.data.reason[5]` + +SHA-256: `56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23` + +来源:[`packages/core/session/src/types.ts:220`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"interrupted"` | + + + +### `event:turn/start` + +SHA-256: `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`event:turn/start.data`](#persistence-type-eventturnstartdata) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"turn/start"` | + + + +### `event:turn/start.data` + +SHA-256: `f90eb4ab2d3897bc20c521a038e3119d581f791ec974b1a0004f63a8ab9e232d` + +来源:[`packages/core/session/src/types.ts:276`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `turn` | 必需 | `number` | + + + +### `event:user/message` + +SHA-256: `2e49ede7fb3a592cce8919ebe982f6ca1c1f67abe4def359ffc028c85e6d1ad8` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/llm/llm/src/message.ts#UserMessage`](#persistence-type-packagesllmllmsrcmessagetsusermessage) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `sourceEventSeqs` | 可选 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `surfaceOp` | 必需 | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | 必需 | `number` | +| `type` | 必需 | `"user/message"` | + + + +### `event:web/deepseek-search-llm-request` + +SHA-256: `cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`packages/web/web-search-deepseek/src/provider.ts#DeepSeekSearchLlmRequest`](#persistence-type-packageswebweb-search-deepseeksrcprovidertsdeepseeksearchllmrequest) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"web/deepseek-search-llm-request"` | + + + +### `event:web/deepseek-search-llm-request.data.body` + +SHA-256: `930a6567a10bb62ddd157bd8abdf4b182810c8c3b49d91309b3b009a5fed9731` + +来源:[`packages/web/web-search-deepseek/src/provider.ts:62`](../packages/web/web-search-deepseek/src/provider.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `max_tokens` | 必需 | `number` | +| `messages` | 必需 | [`event:web/deepseek-search-llm-request.data.body.messages`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodymessages) | +| `model` | 必需 | `string` | +| `tools` | 必需 | [`event:web/deepseek-search-llm-request.data.body.tools`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodytools) | + + + +### `event:web/deepseek-search-llm-request.data.body.messages` + +SHA-256: `b993441f8ce7d27b80e619e113e232ae8e62d3d5a8340f6bfe12d8c4c018e62f` + +| 位置 | 存在性 | 类型 | +|---|---|---| +| 0 | 必需 | [`event:web/deepseek-search-llm-request.data.body.messages[0]`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodymessages0) | + + + +### `event:web/deepseek-search-llm-request.data.body.messages[0]` + +SHA-256: `9a2a9029f8d7ede05336980d8342737557f487b913bfd28853d0ab5214600ab5` + +来源:[`packages/web/web-search-deepseek/src/provider.ts:65`](../packages/web/web-search-deepseek/src/provider.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `content` | 必需 | [`event:web/deepseek-search-llm-request.data.body.messages[0].content`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodymessages0content) | +| `role` | 必需 | `"user"` | + + + +### `event:web/deepseek-search-llm-request.data.body.messages[0].content` + +SHA-256: `8f6d609794bf5afc01d7ccf3e03811d32f4b8047f58a2c9a98db0154c2ad7cb9` + +| 位置 | 存在性 | 类型 | +|---|---|---| +| 0 | 必需 | [`event:web/deepseek-search-llm-request.data.body.messages[0].content[0]`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodymessages0content0) | + + + +### `event:web/deepseek-search-llm-request.data.body.messages[0].content[0]` + +SHA-256: `4e887768586528565381dadbddee6cef555874148089f4579e0b4b1ad096fc9c` + +来源:[`packages/llm/llm/src/types.ts:54`](../packages/llm/llm/src/types.ts) · [`packages/web/web-search-deepseek/src/provider.ts:67`](../packages/web/web-search-deepseek/src/provider.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `text` | 必需 | `string` | +| `type` | 必需 | `"text"` | + + + +### `event:web/deepseek-search-llm-request.data.body.tools` + +SHA-256: `4aed17ec726b5397c29f6cb4bcd7905dc1a12bb6140f1657f073cd24380f2af8` + +| 位置 | 存在性 | 类型 | +|---|---|---| +| 0 | 必需 | [`event:web/deepseek-search-llm-request.data.body.tools[0]`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodytools0) | + + + +### `event:web/deepseek-search-llm-request.data.body.tools[0]` + +SHA-256: `2d11ca7b0d4493e244b74eba093227866b33249841e59a1e9bf56afed38604c3` + +来源:[`packages/web/web-search-deepseek/src/provider.ts:72`](../packages/web/web-search-deepseek/src/provider.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `max_uses` | 必需 | `number` | +| `name` | 必需 | `"web_search"` | +| `type` | 必需 | `"web_search_20250305"` | + + + +### `"every"` + +SHA-256: `e42b0f40b5d0b628c6e1c44a8fb9eda0780037a81959e1b93d2dfb47b46b0e87` + +`"every"` + + + +### `"failed"` + +SHA-256: `b57a2889408de36e4516807194c73bdecd51a92f633ccd05db81bf049200f420` + +`"failed"` + + + +### `"fallback"` + +SHA-256: `d6e654b88b584fcdf290a56802b1730bc7b9847ae86baa4265ab7769eb48990c` + +`"fallback"` + + + +### `false` + +SHA-256: `0c4205180f75b9ee1e0d878bd9a9851628b5dc18448486389d9c5fdc3f1ccda3` + +`false` + + + +### `"feedback/message-delete"` + +SHA-256: `44818000e56acb97afdb5045ce235eb55e3fb1ccabc9fc7d0750bb38f36a7f8e` + +`"feedback/message-delete"` + + + +### `"feedback/message-put"` + +SHA-256: `6cda0d6d3b4336d83998fe580628426df81c2afbab5d768b8813b56d569120e8` + +`"feedback/message-put"` + + + +### `"feedback/record"` + +SHA-256: `c99e20441bf15341e9db54385e558e5c03d21dff2eb1ac08ccbf47c2d8747954` + +`"feedback/record"` + + + +### `"file"` + +SHA-256: `277b61f9c6f8170425fa6ffe06cfa14376757a896a4749cb82a687d7364c4fc9` + +`"file"` + + + +### `"finish"` + +SHA-256: `bd499eac047efe091a4df7382eadd06d25cf0cb31fee3981e8b24d85762841cd` + +`"finish"` + + + +### `"fork"` + +SHA-256: `2da725e554cd89301d829ed9dc45a572c3cd1b93b5e91d0d71f007976d8a0863` + +`"fork"` + + + +### `"fresh"` + +SHA-256: `dd2780e7e07684332f7a9ab047237798d260e7eaffb1321cb38eca695d184c3d` + +`"fresh"` + + + +### `"goal"` + +SHA-256: `b815d7b91cdd9fc6ccc7dcba05cfc5ce1d55fab5fc0c0801803cd3630cedb602` + +`"goal"` + + + +### `"goal/change"` + +SHA-256: `237c004c73b6b288246816e0d7fde16e09c63a47aa3b1227383d7afe58666163` + +`"goal/change"` + + + +### `"hook"` + +SHA-256: `dbdafceb9a900183cccc80596498d386f3bf7bddead2b8cfde68453aa4739edc` + +`"hook"` + + + +### `"hook/invoked"` + +SHA-256: `b9305f0ea75ccd6a6061c27fde19f2ab85534f9f3c1a717b573b077ebae0f611` + +`"hook/invoked"` + + + +### `"hook/result"` + +SHA-256: `a3a259a1aa5401af786ce08c4a8419681d3000d73467991b5fc563389bc4ebd2` + +`"hook/result"` + + + +### `"image"` + +SHA-256: `fb8b6a2e53cb9cf44c551176de0fb77d1ddc60037b99a891ff8a860b6d5c29a0` + +`"image"` + + + +### `"image/gif"` + +SHA-256: `de30b1fa12c75f81df6aa4096e1595296d2d04e2be14a7c29849cf8028b79048` + +`"image/gif"` + + + +### `"image/jpeg"` + +SHA-256: `3f74ba74acd10644c1ed6947a411d8088bd2cfac74f61bb8540f96d68e31f004` + +`"image/jpeg"` + + + +### `"image/png"` + +SHA-256: `58f94390b982f8a5ac1f4f235ba612557f4a1dc4a995d8b1cec63ce7639decb0` + +`"image/png"` + + + +### `"image/webp"` + +SHA-256: `ebb768d85ec87fb68e42fc14fd7d8535b5ac11791b14d04c6770180f0a719bf8` + +`"image/webp"` + + + +### `"in-history"` + +SHA-256: `f311671e07bc7fa645a0b6597de9966b97147956565296ba76a36543801b66c5` + +来源:[`packages/llm/llm/src/types.ts:347`](../packages/llm/llm/src/types.ts) + +`"in-history"` + + + +### `"in_progress"` + +SHA-256: `94c56aeb1564ad735eb773d88636b40761d063efa1610f1308004d32923f5378` + +`"in_progress"` + + + +### `"initial"` + +SHA-256: `e7ce49f12953583fad217bf80fb9f1463acff2c3e9471a823782661675fb124c` + +`"initial"` + + + +### `"instruction-following"` + +SHA-256: `e4cac694f8b6e1d52e48c699464f1c4c26111ef87e558f7931040d00fdc8d486` + +`"instruction-following"` + + + +### `"instructions"` + +SHA-256: `fee1caf8ab6a65c1bc981f5a1730164490f22a2a89ffa3d0b76f1f432c44c8b5` + +`"instructions"` + + + +### `"interrupted"` + +SHA-256: `69d238a6e9b08d67f601b1825962963d8d3523cb69ebf6208c697dc5d058c199` + +`"interrupted"` + + + +### `"legacy"` + +SHA-256: `ad3b56aa2fc1ad4250e7399295d34a8eae592e1907e85852e2291b2e498a30bc` + +`"legacy"` + + + +### `"llm/retry"` + +SHA-256: `611cae9a3539733aa0c42f66035a7d90f19dcbbdfb5d9e3b628b13705253942d` + +`"llm/retry"` + + + +### `"llm/retry-started"` + +SHA-256: `4ae007ed190a72c92138b22d059b83208079c7551ca13fb59ae8cf5e23693964` + +`"llm/retry-started"` + + + +### `"max-tokens"` + +SHA-256: `68141cbe3994a5ffcd064c07c75996538437fd9dbf77ce8a2927e89ec577fabe` + +`"max-tokens"` + + + +### `"model"` + +SHA-256: `2765e9119edf5816ac1ab4c95c5ee32c90403f3742962eff11544f7752d59d2e` + +`"model"` + + + +### `"model/selection"` + +SHA-256: `1d5f04a03ea75fc8eeece544129eba0f49f0c82046e9c404461920390135c7b2` + +`"model/selection"` + + + +### `"negative"` + +SHA-256: `b1f62380f6746fedd629e71065e3583cef83334875b499ac9665c2eb87acfbf5` + +`"negative"` + + + +### `"never"` + +SHA-256: `1571525adc95a24e5d25b5a6440df401cd7cdd881b745178e85f5a333435cd1b` + +`"never"` + + + +### `"next-step"` + +SHA-256: `3e2ece4967cfebe95f450a93f45523dff99d6749f6269f2f60f84dfecfcf3693` + +`"next-step"` + + + +### `"next-turn"` + +SHA-256: `62ecb62dc0fe1b5e70217f8a80c39256a40ee3a1917be14e5ca899d20c35f7fd` + +`"next-turn"` + + + +### `"normal"` + +SHA-256: `3bc287e765d58dc3a22672a0495a6cfd561220dd38fbaecef80aa95b96caed79` + +`"normal"` + + + +### `"notice"` + +SHA-256: `e8de0f457fed5b9ff94dc4950b59ef75fd8ba8af665cc7899485924a62653367` + +`"notice"` + + + +### `null` + +SHA-256: `938f7c7357bc403ea3b281a9dd016939df7a0880fc8db12612585be574d0d615` + +`null` + + + +### `number` + +SHA-256: `0657d3ca5ac4e7bf79b00bcf860b1c7159d01a9da137497221ec2f1776e365f7` + +来源:[`packages/core/session/src/types.ts:31`](../packages/core/session/src/types.ts) + +`number` + + + +### `"one-shot"` + +SHA-256: `d28765f87d6b6ab408e0b2f3e7e297791b004b806d8e302c49903ebae815feb2` + +`"one-shot"` + + + +### `"other"` + +SHA-256: `9d80129e13edfb6feb03ed327e6085c6ef60b97ad074244b2f5fd4667424a6a4` + +`"other"` + + + +### `packages/api/session-controller/src/types.ts#ModelSelection` + +SHA-256: `f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b` + +来源:[`packages/api/session-controller/src/types.ts:86`](../packages/api/session-controller/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `model` | 必需 | `string` | +| `provider` | 必需 | `string` | +| `reasoningEffort` | 可选 | `string` | + + + +### `packages/attachment/attachment/src/types.ts#FileAttachmentRef` + +SHA-256: `f19bee84bf3d20904553cdcd57465232bc1c2a194f54c4d195292ba0487223a6` + +来源:[`packages/attachment/attachment/src/types.ts:39`](../packages/attachment/attachment/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `attachmentId` | 必需 | `string` | +| `bytes` | 必需 | `number` | +| `name` | 必需 | `string` | + + + +### `packages/attachment/attachment/src/types.ts#ImageAttachmentRef` + +SHA-256: `b0b0eb1b16490f6bee135b2377e8f8c39227974c799ad7d49ff418af23ecbc35` + +来源:[`packages/attachment/attachment/src/types.ts:11`](../packages/attachment/attachment/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `attachmentId` | 必需 | `string` | +| `bytes` | 必需 | `number` | +| `height` | 必需 | `number` | +| `mediaType` | 必需 | [`packages/attachment/attachment/src/types.ts#ImageMediaType`](#persistence-type-packagesattachmentattachmentsrctypestsimagemediatype) | +| `name` | 可选 | `string` | +| `originalDimensions` | 可选 | [`event:agent/inbox/spliced.data.inserted[0].content[0][2].attachment.originalDimensions`](#persistence-type-eventagentinboxspliceddatainserted0content02attachmentoriginaldimensions) | +| `width` | 必需 | `number` | + + + +### `packages/attachment/attachment/src/types.ts#ImageMediaType` + +SHA-256: `2f1e6a7ebace43d727ae138ddcd352eb3813960009e5805b8c5ac84300c97716` + +来源:[`packages/attachment/attachment/src/types.ts:8`](../packages/attachment/attachment/src/types.ts) + +以下类型之一: + +- `"image/gif"` +- `"image/jpeg"` +- `"image/png"` +- `"image/webp"` + + + +### `packages/context/agent-instructions/src/render.ts#AgentInstructionChange` + +SHA-256: `83da72c6857601c75da11212ef847f57d457b9c99f0a82246dcdff3e26700239` + +来源:[`packages/context/agent-instructions/src/render.ts:47`](../packages/context/agent-instructions/src/render.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `action` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].source[17].changes[0].action`](#persistence-type-eventagentinboxspliceddatainserted0source17changes0action) | +| `digest` | 可选 | `string` | +| `path` | 必需 | `string` | +| `scope` | 必需 | `string` | + + + +### `packages/context/agent-instructions/src/state.ts#AgentInstructionSource` + +SHA-256: `6f22a9d02b9a1f1157f3b0e7bdccd7e7b12b085c4efdb709dfc74a465cd4e590` + +来源:[`packages/context/agent-instructions/src/state.ts:37`](../packages/context/agent-instructions/src/state.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `baseline` | 可选 | `true` | +| `baselineIdentity` | 可选 | `string` | +| `changes` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].source[17].changes`](#persistence-type-eventagentinboxspliceddatainserted0source17changes) | +| `form` | 必需 | `"instructions"` | +| `kind` | 必需 | `"agent-instructions"` | + + + +### `packages/context/session-reference/src/types.ts#SessionReferenceSource` + +SHA-256: `f56c5b59ca0161310c338d31dd16cf14287ccd602ff4181058df178c1c286d7f` + +来源:[`packages/context/session-reference/src/types.ts:13`](../packages/context/session-reference/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"recall"` | +| `kind` | 必需 | `"session-reference"` | +| `references` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].source[15].references`](#persistence-type-eventagentinboxspliceddatainserted0source15references) | +| `version` | 必需 | `1` | + + + +### `packages/core/agent/src/types.ts#InboxTarget` + +SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c` + +来源:[`packages/core/agent/src/types.ts:30`](../packages/core/agent/src/types.ts) + +以下类型之一: + +- `"next-step"` +- `"next-turn"` + + + +### `packages/core/session/src/types.ts#EpochHeader` + +SHA-256: `c55a891c7aac496de6ee64c91c1fb65728c977314013033e42c863428aada854` + +来源:[`packages/core/session/src/types.ts:232`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `adapterDefaults` | 可选 | [`packages/llm/llm/src/call-config.ts#LlmCallConfigAdapterDefaults`](#persistence-type-packagesllmllmsrccall-configtsllmcallconfigadapterdefaults) | +| `config` | 必需 | [`packages/llm/llm/src/call-config.ts#LlmCallConfig`](#persistence-type-packagesllmllmsrccall-configtsllmcallconfig) | +| `tools` | 可选 | [`event:request/header.data.header.tools`](#persistence-type-eventrequestheaderdataheadertools) | + + + +### `packages/core/session/src/types.ts#RequestContext` + +SHA-256: `b158f3ff6923714807c0d73882f0b05dc87f8fa85c9e90814852d48bbc866081` + +来源:[`packages/core/session/src/types.ts:242`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `contextWindow` | 可选 | `number` | +| `model` | 必需 | `string` | +| `provider` | 必需 | `string` | +| `systemPromptUpdate` | 可选 | `"in-history"` | + + + +### `packages/core/session/src/types.ts#RequestHeaderReason` + +SHA-256: `530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6` + +来源:[`packages/core/session/src/types.ts:261`](../packages/core/session/src/types.ts) + +以下类型之一: + +- `"change"` +- `"initial"` +- `"resume"` +- `"series"` + + + +### `packages/core/session/src/types.ts#SessionHeader` + +SHA-256: `2a8b114637e66faab5fae1c6e2fba65320db6224c80f5f36c5bf32f937154d03` + +来源:[`packages/core/session/src/types.ts:93`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `agentPreset` | 可选 | `string` | +| `createdAt` | 必需 | `number` | +| `cwd` | 可选 | `string` | +| `delegationDepth` | 可选 | `number` | +| `id` | 必需 | `string` | +| `isSeeded` | 必需 | `boolean` | +| `origin` | 可选 | `"subagent"` | +| `parentSession` | 可选 | `string` | +| `version` | 必需 | `3` | + + + +### `packages/core/session/src/types.ts#SurfaceOp` + +SHA-256: `335e242de1fcc17b6ca920fc420d71bec2d76e53e37955c00948b65ab77f05c5` + +来源:[`packages/core/session/src/types.ts:434`](../packages/core/session/src/types.ts) + +以下类型之一: + +- `"append"` +- [`SessionEventEnvelope[6].surfaceOp[1]`](#persistence-type-sessioneventenvelope6surfaceop1) + + + +### `packages/core/session/src/types.ts#TurnEndCancelCause` + +SHA-256: `1f8c126cfab3df812cd115cfc23e3256f0fa2383400f4ce91ecad313577e59b0` + +来源:[`packages/core/session/src/types.ts:195`](../packages/core/session/src/types.ts) + +以下类型之一: + +- [`event:turn/end.data.reason[1].reason[2]`](#persistence-type-eventturnenddatareason1reason2) +- [`event:turn/end.data.reason[1].reason[3]`](#persistence-type-eventturnenddatareason1reason3) +- [`event:turn/end.data.reason[1].reason[4]`](#persistence-type-eventturnenddatareason1reason4) +- [`event:turn/end.data.reason[1].reason[1]`](#persistence-type-eventturnenddatareason1reason1) +- [`event:agent/inbox/spliced.data.inserted[0].source[0]`](#persistence-type-eventagentinboxspliceddatainserted0source0) + + + +### `packages/core/session/src/types.ts#TurnEndReason` + +SHA-256: `e3528b95cff70d83a080a96666877da3f9dc205e71955d6bc5b9b315b9e0da22` + +来源:[`packages/core/session/src/types.ts:224`](../packages/core/session/src/types.ts) + +以下类型之一: + +- [`event:turn/end.data.reason[5]`](#persistence-type-eventturnenddatareason5) +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[2]`](#persistence-type-eventassistantattemptdatastream03chunk6reason2) +- [`event:turn/end.data.reason[3]`](#persistence-type-eventturnenddatareason3) +- [`event:turn/end.data.reason[1]`](#persistence-type-eventturnenddatareason1) +- [`event:turn/end.data.reason[2]`](#persistence-type-eventturnenddatareason2) +- [`event:turn/end.data.reason[0]`](#persistence-type-eventturnenddatareason0) + + + +### `packages/core/tools/src/index.ts#ToolRestriction` + +SHA-256: `3cfc3a56502da1f8c6153c2c56657bab4b749056968634dd0991d325ec1c919f` + +来源:[`packages/core/tools/src/index.ts:673`](../packages/core/tools/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `allow` | 可选 | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `deny` | 可选 | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | + + + +### `packages/core/tools/src/types.ts#PtcDispatchEventData` + +SHA-256: `1418e7f0c944a0453ce0851729b38b69c74194ff1e3ed41dc71904ca66e57bc5` + +来源:[`packages/core/tools/src/types.ts:20`](../packages/core/tools/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `arguments` | 必需 | `unknown`(不透明) | +| `content` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `isError` | 必需 | `boolean` | +| `name` | 必需 | `string` | +| `parentCallId` | 必需 | `string` | +| `rootCallId` | 必需 | `string` | +| `subCallId` | 必需 | `string` | + + + +### `packages/core/tools/src/types.ts#PtcDispatchStartEventData` + +SHA-256: `2fe5026daf14bd4ff2b7bfb7cf9791c302d9cbba06802178b6dfec00b892698f` + +来源:[`packages/core/tools/src/types.ts:11`](../packages/core/tools/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `arguments` | 必需 | `unknown`(不透明) | +| `name` | 必需 | `string` | +| `parentCallId` | 必需 | `string` | +| `rootCallId` | 必需 | `string` | +| `subCallId` | 必需 | `string` | + + + +### `packages/experimental/agent-team/src/types.ts#TeamMemberPhase` + +SHA-256: `dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd` + +来源:[`packages/experimental/agent-team/src/types.ts:44`](../packages/experimental/agent-team/src/types.ts) + +以下类型之一: + +- `"active"` +- `"failed"` +- `"provisioning"` + + + +### `packages/experimental/agent-team/src/types.ts#TeamMemberSnapshot` + +SHA-256: `6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25` + +来源:[`packages/experimental/agent-team/src/types.ts:47`](../packages/experimental/agent-team/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `context` | 必需 | [`event:team/member.data.member.context`](#persistence-type-eventteammemberdatamembercontext) | +| `description` | 必需 | `string` | +| `error` | 可选 | `string` | +| `id` | 必需 | `string` | +| `name` | 必需 | `string` | +| `phase` | 必需 | [`packages/experimental/agent-team/src/types.ts#TeamMemberPhase`](#persistence-type-packagesexperimentalagent-teamsrctypeststeammemberphase) | +| `provider` | 必需 | `string` | + + + +### `packages/experimental/agent-team/src/types.ts#TeamMessageSnapshot` + +SHA-256: `10ce3a084c136b5fc9969e5981b64711c35a2d9de7eac1b8443c03f73dc4f3db` + +来源:[`packages/experimental/agent-team/src/types.ts:106`](../packages/experimental/agent-team/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `content` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `id` | 必需 | `string` | +| `senderId` | 必需 | `string` | +| `senderName` | 必需 | `string` | +| `targetId` | 必需 | `string` | + + + +### `packages/experimental/agent-team/src/types.ts#TeamMessageSource` + +SHA-256: `2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0` + +来源:[`packages/experimental/agent-team/src/types.ts:115`](../packages/experimental/agent-team/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"team-message"` | +| `messageId` | 必需 | `string` | +| `senderId` | 必需 | `string` | +| `senderName` | 必需 | `string` | +| `teamId` | 必需 | `string` | + + + +### `packages/experimental/agent-team/src/types.ts#TeamTaskSnapshot` + +SHA-256: `51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357` + +来源:[`packages/experimental/agent-team/src/types.ts:74`](../packages/experimental/agent-team/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `blockedBy` | 必需 | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `description` | 必需 | `string` | +| `id` | 必需 | `string` | +| `ownerId` | 可选 | `string` | +| `revision` | 必需 | `number` | +| `status` | 必需 | [`packages/experimental/agent-team/src/types.ts#TeamTaskStatus`](#persistence-type-packagesexperimentalagent-teamsrctypeststeamtaskstatus) | +| `subject` | 必需 | `string` | +| `writeScopes` | 必需 | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | + + + +### `packages/experimental/agent-team/src/types.ts#TeamTaskStatus` + +SHA-256: `b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943` + +来源:[`packages/experimental/agent-team/src/types.ts:71`](../packages/experimental/agent-team/src/types.ts) + +以下类型之一: + +- `"completed"` +- `"deleted"` +- `"in_progress"` +- `"pending"` + + + +### `packages/feedback/command-feedback/src/types.ts#FeedbackRecord` + +SHA-256: `7d54a5a5e629e2e7783bfac72683bcc5e969e9f43426b9289826aaf294ff6344` + +来源:[`packages/feedback/command-feedback/src/types.ts:27`](../packages/feedback/command-feedback/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `category` | 可选 | [`event:feedback/message-put.data.item.category`](#persistence-type-eventfeedbackmessage-putdataitemcategory) | +| `text` | 可选 | `string` | + + + +### `packages/feedback/message-feedback/src/types.ts#MessageFeedbackDelete` + +SHA-256: `7c6129b6961cf4b2a030f36c78d2b0a5be38617651a9b81322e054ee512bee06` + +来源:[`packages/feedback/message-feedback/src/types.ts:46`](../packages/feedback/message-feedback/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `messageId` | 必需 | `string` | +| `sessionId` | 必需 | `string` | + + + +### `packages/feedback/message-feedback/src/types.ts#MessageFeedbackItem` + +SHA-256: `3d7d07060a7be77984feb651ea2bf1925d3e0477911d9d3ed86330d1e8ca6182` + +来源:[`packages/feedback/message-feedback/src/types.ts:20`](../packages/feedback/message-feedback/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `category` | 可选 | [`event:feedback/message-put.data.item.category`](#persistence-type-eventfeedbackmessage-putdataitemcategory) | +| `createdAt` | 必需 | `number` | +| `messageId` | 必需 | `string` | +| `note` | 可选 | `string` | +| `rating` | 必需 | [`packages/feedback/message-feedback/src/types.ts#MessageFeedbackRating`](#persistence-type-packagesfeedbackmessage-feedbacksrctypestsmessagefeedbackrating) | +| `updatedAt` | 必需 | `number` | +| `version` | 必需 | `string` | + + + +### `packages/feedback/message-feedback/src/types.ts#MessageFeedbackPut` + +SHA-256: `aa7bc2817014ca6c45719b1c193c5c131be718a0921f34345098ff250f444626` + +来源:[`packages/feedback/message-feedback/src/types.ts:38`](../packages/feedback/message-feedback/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `item` | 必需 | [`packages/feedback/message-feedback/src/types.ts#MessageFeedbackItem`](#persistence-type-packagesfeedbackmessage-feedbacksrctypestsmessagefeedbackitem) | +| `sessionId` | 必需 | `string` | + + + +### `packages/feedback/message-feedback/src/types.ts#MessageFeedbackRating` + +SHA-256: `390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2` + +来源:[`packages/feedback/message-feedback/src/types.ts:17`](../packages/feedback/message-feedback/src/types.ts) + +以下类型之一: + +- `"negative"` +- `"positive"` + + + +### `packages/fs/tool-present/src/types.ts#PresentedFile` + +SHA-256: `b8fc636a2121df9d8423c242e03c9d23b7ab7c6fdce00e45746932c60b730f97` + +来源:[`packages/fs/tool-present/src/types.ts:5`](../packages/fs/tool-present/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `description` | 可选 | `string` | +| `path` | 必需 | `string` | + + + +### `packages/goal/goal/src/domain.ts#GoalChangeMeta` + +SHA-256: `7d226b9fd95b4ba213d4237c22e930a91c3c769c95ca7f92977b9d625ba13438` + +来源:[`packages/goal/goal/src/domain.ts:44`](../packages/goal/goal/src/domain.ts) + +以下类型之一: + +- [`packages/goal/goal/src/domain.ts#GoalClearChangeMeta`](#persistence-type-packagesgoalgoalsrcdomaintsgoalclearchangemeta) +- [`packages/goal/goal/src/domain.ts#GoalSnapshotChangeMeta`](#persistence-type-packagesgoalgoalsrcdomaintsgoalsnapshotchangemeta) + + + +### `packages/goal/goal/src/domain.ts#GoalClearChangeMeta` + +SHA-256: `cffcdc19881ab61ec0a9ce18743996f7ac44ac8889f34dfac36ae745574aa22f` + +来源:[`packages/goal/goal/src/domain.ts:35`](../packages/goal/goal/src/domain.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `cleared` | 必需 | [`packages/goal/goal/src/types.ts#GoalRef`](#persistence-type-packagesgoalgoalsrctypestsgoalref) | +| `clearedAt` | 必需 | `number` | +| `kind` | 必需 | `"goal/change"` | +| `operation` | 必需 | `"clear"` | +| `version` | 必需 | `1` | + + + +### `packages/goal/goal/src/domain.ts#GoalMessageSource` + +SHA-256: `7f959241c24128b4983b559984811861dc59774b85905c8fa57c6cb0148a3dbc` + +来源:[`packages/goal/goal/src/domain.ts:47`](../packages/goal/goal/src/domain.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `goalId` | 必需 | `string` | +| `kind` | 必需 | `"goal"` | +| `revision` | 必需 | `number` | +| `round` | 必需 | `number` | + + + +### `packages/goal/goal/src/domain.ts#GoalSnapshotChangeMeta` + +SHA-256: `1df34a6c086866ffdc2dd9e8e5c581c3751a010cdf6b8538a7327f7a3237f0b4` + +来源:[`packages/goal/goal/src/domain.ts:24`](../packages/goal/goal/src/domain.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `createdAt` | 必需 | `number` | +| `goal` | 必需 | [`packages/goal/goal/src/types.ts#GoalSnapshot`](#persistence-type-packagesgoalgoalsrctypestsgoalsnapshot) | +| `kind` | 必需 | `"goal/change"` | +| `operation` | 必需 | [`event:goal/change.data[0].operation`](#persistence-type-eventgoalchangedata0operation) | +| `roundsStarted` | 必需 | `number` | +| `updatedAt` | 必需 | `number` | +| `version` | 必需 | `1` | + + + +### `packages/goal/goal/src/types.ts#GoalBlockReason` + +SHA-256: `84ac8c1a66ffc697262e181f42e851259eae14038c113ca329fb4700ccdb1f7c` + +来源:[`packages/goal/goal/src/types.ts:52`](../packages/goal/goal/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `code` | 必需 | `string` | +| `message` | 必需 | `string` | + + + +### `packages/goal/goal/src/types.ts#GoalPhase` + +SHA-256: `b8327af7ee5767952d1300cb4f03b7fc04197ae5ba275e13981c689ff7506172` + +来源:[`packages/goal/goal/src/types.ts:45`](../packages/goal/goal/src/types.ts) + +以下类型之一: + +- `"active"` +- `"blocked"` +- `"complete"` +- `"paused"` + + + +### `packages/goal/goal/src/types.ts#GoalRef` + +SHA-256: `0b6d718c693fdf7f06dcccb0bcbbabe78cc63514eafc62d6b2a3899735279d6f` + +来源:[`packages/goal/goal/src/types.ts:20`](../packages/goal/goal/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `id` | 必需 | `string` | +| `revision` | 必需 | `number` | + + + +### `packages/goal/goal/src/types.ts#GoalSnapshot` + +SHA-256: `6bbe793f6a93dc8d50a4d4d3fbb6f8ed15650bcb7e151d28669628f7b7c3fc25` + +来源:[`packages/goal/goal/src/types.ts:60`](../packages/goal/goal/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `blockedReason` | 可选 | [`packages/goal/goal/src/types.ts#GoalBlockReason`](#persistence-type-packagesgoalgoalsrctypestsgoalblockreason) | +| `id` | 必需 | `string` | +| `maxGoalRounds` | 必需 | `number` | +| `objective` | 必需 | `string` | +| `phase` | 必需 | [`packages/goal/goal/src/types.ts#GoalPhase`](#persistence-type-packagesgoalgoalsrctypestsgoalphase) | +| `revision` | 必需 | `number` | + + + +### `packages/hooks/hook-protocol/src/types.ts#HookDialect` + +SHA-256: `a6b41f5bede8094d7bf635be5d69b1bd7a25e006ca9cf9415bceb9322f1a7feb` + +来源:[`packages/hooks/hook-protocol/src/types.ts:48`](../packages/hooks/hook-protocol/src/types.ts) + +以下类型之一: + +- `"claude-code"` +- `"codex"` + + + +### `packages/interaction/user-approval/src/index.ts#ApprovalPolicy` + +SHA-256: `05d2ae1e0c7f8770f83f9a09047afb39a335d8ac986df3193659aa1e581472cf` + +来源:[`packages/interaction/user-approval/src/index.ts:60`](../packages/interaction/user-approval/src/index.ts) + +以下类型之一: + +- `"ask"` +- `"never"` + + + +### `packages/interaction/user-approval/src/types.ts#ApprovalOutcome` + +SHA-256: `930c1cd289def119d443e19dd90008916bf035f0a4d23e07241e7f050f2b89b7` + +来源:[`packages/interaction/user-approval/src/types.ts:32`](../packages/interaction/user-approval/src/types.ts) + +以下类型之一: + +- `"allowed-once"` +- `"cancelled"` +- `"rejected"` +- `"unavailable"` + + + +### `packages/llm/llm-retry/src/types.ts#LlmRetryEventData` + +SHA-256: `35b995af35bfa087f5381a5454a2305928da037e819bfc4759d3f66d9bd55aa9` + +来源:[`packages/llm/llm-retry/src/types.ts:16`](../packages/llm/llm-retry/src/types.ts) + +以下类型之一: + +- [`event:llm/retry.data[0]`](#persistence-type-eventllmretrydata0) +- [`event:llm/retry.data[1]`](#persistence-type-eventllmretrydata1) + + + +### `packages/llm/llm-retry/src/types.ts#LlmRetryStartedEventData` + +SHA-256: `6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20` + +来源:[`packages/llm/llm-retry/src/types.ts:43`](../packages/llm/llm-retry/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `retry` | 必需 | `number` | +| `retryId` | 必需 | `string` | +| `step` | 必需 | `number` | +| `turn` | 必需 | `number` | + + + +### `packages/llm/llm/src/assistant-stream.ts#AssistantStreamRecord` + +SHA-256: `ecaf9469000a15d46d269ea72994ceaab3652ed31507822906fc12397422da21` + +来源:[`packages/llm/llm/src/assistant-stream.ts:20`](../packages/llm/llm/src/assistant-stream.ts) + +以下类型之一: + +- [`event:assistant/attempt.data.stream[0][2]`](#persistence-type-eventassistantattemptdatastream02) +- [`event:assistant/attempt.data.stream[0][3]`](#persistence-type-eventassistantattemptdatastream03) +- [`event:assistant/attempt.data.stream[0][1]`](#persistence-type-eventassistantattemptdatastream01) +- [`event:assistant/attempt.data.stream[0][0]`](#persistence-type-eventassistantattemptdatastream00) + + + +### `packages/llm/llm/src/call-config.ts#LlmCallConfig` + +SHA-256: `67d5f73ceae82f5265258142ad6cf172de2c1fef93ba551b7f0454e0a93bffe0` + +来源:[`packages/llm/llm/src/call-config.ts:23`](../packages/llm/llm/src/call-config.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `maxTokens` | 可选 | `number` | +| `model` | 必需 | `string` | +| `provider` | 必需 | `string` | +| `reasoningEffort` | 可选 | `string` | +| `stop` | 可选 | [`event:assistant/attempt.data.stream[0][0].texts`](#persistence-type-eventassistantattemptdatastream00texts) | +| `temperature` | 可选 | `number` | + + + +### `packages/llm/llm/src/call-config.ts#LlmCallConfigAdapterDefaults` + +SHA-256: `326a9bfda27dda5882fb3cbe6e715df41dae2298fdae3d66437457268a665693` + +来源:[`packages/llm/llm/src/call-config.ts:36`](../packages/llm/llm/src/call-config.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `maxTokens` | 可选 | `true` | +| `reasoningEffort` | 可选 | `true` | + + + +### `packages/llm/llm/src/message.ts#AssistantMessage` + +SHA-256: `08a1293eddb9b633bfefaf0893e3b4c807dfbb49c45a447cb32bb68bfd1a8232` + +来源:[`packages/llm/llm/src/message.ts:148`](../packages/llm/llm/src/message.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `content` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `id` | 必需 | `string` | +| `role` | 必需 | `"assistant"` | +| `source` | 必需 | [`packages/llm/llm/src/message.ts#ModelMessageSource`](#persistence-type-packagesllmllmsrcmessagetsmodelmessagesource) | + + + +### `packages/llm/llm/src/message.ts#ContextSnapshotSection` + +SHA-256: `86e1e384495ee09ac2d60c9db3610f1f1fddac2d248f6f1d6000e78054680219` + +来源:[`packages/llm/llm/src/message.ts:65`](../packages/llm/llm/src/message.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `name` | 必需 | `string` | +| `text` | 必需 | `string` | + + + +### `packages/llm/llm/src/message.ts#Message` + +SHA-256: `f051068babb7ebab80e9fd4fff3a70cfe54430c2ef3ca9bb77bb54db8bc34476` + +来源:[`packages/llm/llm/src/message.ts:131`](../packages/llm/llm/src/message.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `content` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `id` | 必需 | `string` | +| `role` | 必需 | [`event:session/title-llm-request.data.messages[0].role`](#persistence-type-eventsessiontitle-llm-requestdatamessages0role) | +| `source` | 必需 | [`packages/llm/llm/src/message.ts#MessageSource`](#persistence-type-packagesllmllmsrcmessagetsmessagesource) | + + + +### `packages/llm/llm/src/message.ts#MessageSource` + +SHA-256: `4c965666d5b42116945bc356087cd5e1fd549e6dece560785ed60511059a8283` + +来源:[`packages/llm/llm/src/message.ts:128`](../packages/llm/llm/src/message.ts) + +以下类型之一: + +- [`packages/context/agent-instructions/src/state.ts#AgentInstructionSource`](#persistence-type-packagescontextagent-instructionssrcstatetsagentinstructionsource) +- [`packages/llm/llm/src/message.ts#ToolMessageSource`](#persistence-type-packagesllmllmsrcmessagetstoolmessagesource) +- [`event:agent/inbox/spliced.data.inserted[0].source[10]`](#persistence-type-eventagentinboxspliceddatainserted0source10) +- [`event:agent/inbox/spliced.data.inserted[0].source[19]`](#persistence-type-eventagentinboxspliceddatainserted0source19) +- [`packages/skill/tool-skill/src/index.ts#SkillCatalogSource`](#persistence-type-packagesskilltool-skillsrcindextsskillcatalogsource) +- [`packages/skill/skill/src/index.ts#SkillInvocationSource`](#persistence-type-packagesskillskillsrcindextsskillinvocationsource) +- [`event:agent/inbox/spliced.data.inserted[0].source[4]`](#persistence-type-eventagentinboxspliceddatainserted0source4) +- [`event:agent/inbox/spliced.data.inserted[0].source[5]`](#persistence-type-eventagentinboxspliceddatainserted0source5) +- [`event:agent/inbox/spliced.data.inserted[0].source[6]`](#persistence-type-eventagentinboxspliceddatainserted0source6) +- [`event:agent/inbox/spliced.data.inserted[0].source[3]`](#persistence-type-eventagentinboxspliceddatainserted0source3) +- [`event:agent/inbox/spliced.data.inserted[0].source[2]`](#persistence-type-eventagentinboxspliceddatainserted0source2) +- [`event:agent/inbox/spliced.data.inserted[0].source[7]`](#persistence-type-eventagentinboxspliceddatainserted0source7) +- [`packages/context/session-reference/src/types.ts#SessionReferenceSource`](#persistence-type-packagescontextsession-referencesrctypestssessionreferencesource) +- [`packages/subagent/subagent/src/continuation-messages.ts#SubagentSettledMessageSource`](#persistence-type-packagessubagentsubagentsrccontinuation-messagestssubagentsettledmessagesource) +- [`packages/subagent/subagent/src/continuation-messages.ts#AgentMessageSource`](#persistence-type-packagessubagentsubagentsrccontinuation-messagestsagentmessagesource) +- [`packages/goal/goal/src/domain.ts#GoalMessageSource`](#persistence-type-packagesgoalgoalsrcdomaintsgoalmessagesource) +- [`packages/experimental/agent-team/src/types.ts#TeamMessageSource`](#persistence-type-packagesexperimentalagent-teamsrctypeststeammessagesource) +- [`packages/llm/llm/src/message.ts#ModelMessageSource`](#persistence-type-packagesllmllmsrcmessagetsmodelmessagesource) +- [`event:agent/inbox/spliced.data.inserted[0].source[1]`](#persistence-type-eventagentinboxspliceddatainserted0source1) +- [`event:agent/inbox/spliced.data.inserted[0].source[0]`](#persistence-type-eventagentinboxspliceddatainserted0source0) + + + +### `packages/llm/llm/src/message.ts#ModelMessageSource` + +SHA-256: `719c51f67074f97034a6c94297fe1f4b5d3c8d392c57586dab732aa5398c7a91` + +来源:[`packages/llm/llm/src/message.ts:24`](../packages/llm/llm/src/message.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"model"` | +| `model` | 必需 | `string` | +| `provider` | 必需 | `string` | +| `replayState` | 可选 | `unknown`(不透明) | + + + +### `packages/llm/llm/src/message.ts#SystemMessage` + +SHA-256: `7973d82bd129b261f16a9a20d44df834ec60efc52a63242b4943a2515ae0894f` + +来源:[`packages/llm/llm/src/message.ts:158`](../packages/llm/llm/src/message.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `content` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `id` | 必需 | `string` | +| `role` | 必需 | `"system"` | +| `source` | 必需 | [`event:system/message.data.message.source`](#persistence-type-eventsystemmessagedatamessagesource) | + + + +### `packages/llm/llm/src/message.ts#ToolMessageSource` + +SHA-256: `d9c462b9f62fdd3efc76a44b9524dc8781f6b91cd9c7e584b17d971defafebab` + +来源:[`packages/llm/llm/src/message.ts:29`](../packages/llm/llm/src/message.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `callId` | 必需 | `string` | +| `kind` | 必需 | `"tool"` | + + + +### `packages/llm/llm/src/message.ts#ToolResultMessage` + +SHA-256: `966fad765834f62051ad6f6a100f7ea38abe5f40f7ef3ae0b6dca0e31c7549a5` + +来源:[`packages/llm/llm/src/message.ts:164`](../packages/llm/llm/src/message.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `content` | 必需 | [`event:tool/result.data.message.content`](#persistence-type-eventtoolresultdatamessagecontent) | +| `id` | 必需 | `string` | +| `role` | 必需 | `"user"` | +| `source` | 必需 | [`packages/llm/llm/src/message.ts#ToolMessageSource`](#persistence-type-packagesllmllmsrcmessagetstoolmessagesource) | + + + +### `packages/llm/llm/src/message.ts#UserMessage` + +SHA-256: `40390271dffbed81bffa514370ad1c3c19a13ad10c3647c9c0eae31126c95a5f` + +来源:[`packages/llm/llm/src/message.ts:143`](../packages/llm/llm/src/message.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `content` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `id` | 必需 | `string` | +| `role` | 必需 | `"user"` | +| `source` | 必需 | [`packages/llm/llm/src/message.ts#MessageSource`](#persistence-type-packagesllmllmsrcmessagetsmessagesource) | + + + +### `packages/llm/llm/src/types.ts#ContentBlock` + +SHA-256: `e1161343902a8da654d4d1bb66d729065cc93a2710ebfe2717a242a5c2ec709a` + +来源:[`packages/llm/llm/src/types.ts:124`](../packages/llm/llm/src/types.ts) + +以下类型之一: + +- [`packages/llm/llm/src/types.ts#ReasoningBlock`](#persistence-type-packagesllmllmsrctypestsreasoningblock) +- [`event:web/deepseek-search-llm-request.data.body.messages[0].content[0]`](#persistence-type-eventwebdeepseek-search-llm-requestdatabodymessages0content0) +- [`packages/llm/llm/src/types.ts#ToolCallBlock`](#persistence-type-packagesllmllmsrctypeststoolcallblock) +- [`packages/llm/llm/src/types.ts#ImageBlock`](#persistence-type-packagesllmllmsrctypestsimageblock) +- [`packages/llm/llm/src/types.ts#FileBlock`](#persistence-type-packagesllmllmsrctypestsfileblock) +- [`packages/llm/llm/src/types.ts#ToolResultBlock`](#persistence-type-packagesllmllmsrctypeststoolresultblock) + + + +### `packages/llm/llm/src/types.ts#ContentBlockType` + +SHA-256: `757013fff5879d73d5753d8d1120faab56110e88ac3932485ab7e034d9e9ce61` + +来源:[`packages/llm/llm/src/types.ts:122`](../packages/llm/llm/src/types.ts) + +以下类型之一: + +- `"file"` +- `"image"` +- `"reasoning"` +- `"text"` +- `"tool-call"` +- `"tool-result"` + + + +### `packages/llm/llm/src/types.ts#FileBlock` + +SHA-256: `f2da0f27c1e6add59982c37b15e4a4912c5b33466859cce3b13d503600e24aef` + +来源:[`packages/llm/llm/src/types.ts:84`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `attachment` | 必需 | [`packages/attachment/attachment/src/types.ts#FileAttachmentRef`](#persistence-type-packagesattachmentattachmentsrctypestsfileattachmentref) | +| `type` | 必需 | `"file"` | + + + +### `packages/llm/llm/src/types.ts#FinishReason` + +SHA-256: `5ed158429dfa6a8633e6ba94b8c81190616c24b65c1777bf1c60cde9dd499a2f` + +来源:[`packages/llm/llm/src/types.ts:139`](../packages/llm/llm/src/types.ts) + +以下类型之一: + +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[3]`](#persistence-type-eventassistantattemptdatastream03chunk6reason3) +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[4]`](#persistence-type-eventassistantattemptdatastream03chunk6reason4) +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[0]`](#persistence-type-eventassistantattemptdatastream03chunk6reason0) +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[1]`](#persistence-type-eventassistantattemptdatastream03chunk6reason1) +- [`event:assistant/attempt.data.stream[0][3].chunk[6].reason[2]`](#persistence-type-eventassistantattemptdatastream03chunk6reason2) + + + +### `packages/llm/llm/src/types.ts#ImageBlock` + +SHA-256: `8ef06dd236128246969d5f04a6c4a639d031fcda99a0cd13051f52f41b0ca1af` + +来源:[`packages/llm/llm/src/types.ts:71`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `attachment` | 必需 | [`packages/attachment/attachment/src/types.ts#ImageAttachmentRef`](#persistence-type-packagesattachmentattachmentsrctypestsimageattachmentref) | +| `type` | 必需 | `"image"` | + + + +### `packages/llm/llm/src/types.ts#LlmFailure` + +SHA-256: `27cd736e794a0f8703a155fb697820cede894d10776586dd3df24d24c9465c0e` + +来源:[`packages/llm/llm/src/types.ts:40`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `code` | 必需 | `string` | +| `message` | 必需 | `string` | +| `providerRetryAfterMs` | 可选 | `number` | +| `requestId` | 可选 | `string` | +| `status` | 可选 | `number` | + + + +### `packages/llm/llm/src/types.ts#ReasoningBlock` + +SHA-256: `814d9434e1ddf9078612b3b34322f26e9363b469213335007f7e9ba7fee9464e` + +来源:[`packages/llm/llm/src/types.ts:60`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `text` | 必需 | `string` | +| `type` | 必需 | `"reasoning"` | + + + +### `packages/llm/llm/src/types.ts#ReplayEnvelope` + +SHA-256: `0acd94fe8794574c9ae72c7855ee16d29c546096d4af760d16d1225385764555` + +来源:[`packages/llm/llm/src/types.ts:368`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `blocks` | 可选 | [`event:assistant/attempt.data.stream[0][3].chunk[6].replayState.blocks`](#persistence-type-eventassistantattemptdatastream03chunk6replaystateblocks) | +| `response` | 必需 | `unknown`(不透明) | + + + +### `packages/llm/llm/src/types.ts#StreamChunk` + +SHA-256: `989145d2b165a1e4391d0691196ede9dec5093b09f1cf3d33b1911c24ee6c5ee` + +来源:[`packages/llm/llm/src/types.ts:390`](../packages/llm/llm/src/types.ts) + +以下类型之一: + +- [`event:assistant/attempt.data.stream[0][3].chunk[3]`](#persistence-type-eventassistantattemptdatastream03chunk3) +- [`event:assistant/attempt.data.stream[0][3].chunk[4]`](#persistence-type-eventassistantattemptdatastream03chunk4) +- [`event:assistant/attempt.data.stream[0][3].chunk[0]`](#persistence-type-eventassistantattemptdatastream03chunk0) +- [`event:assistant/attempt.data.stream[0][3].chunk[2]`](#persistence-type-eventassistantattemptdatastream03chunk2) +- [`event:assistant/attempt.data.stream[0][3].chunk[1]`](#persistence-type-eventassistantattemptdatastream03chunk1) +- [`event:assistant/attempt.data.stream[0][3].chunk[6]`](#persistence-type-eventassistantattemptdatastream03chunk6) +- [`event:assistant/attempt.data.stream[0][3].chunk[5]`](#persistence-type-eventassistantattemptdatastream03chunk5) + + + +### `packages/llm/llm/src/types.ts#TokenUsage` + +SHA-256: `fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394` + +来源:[`packages/llm/llm/src/types.ts:149`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `cacheReadTokens` | 可选 | `number` | +| `cacheWriteTokens` | 可选 | `number` | +| `inputTokens` | 必需 | `number` | +| `outputTokens` | 必需 | `number` | +| `reasoningTokens` | 可选 | `number` | +| `totalTokens` | 可选 | `number` | + + + +### `packages/llm/llm/src/types.ts#ToolCallBlock` + +SHA-256: `c1f0f17b6493b85db6453bb30e01b208d703731b25bc1e68e5d4fc72eea9cdea` + +来源:[`packages/llm/llm/src/types.ts:91`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `arguments` | 必需 | `string` | +| `id` | 必需 | `string` | +| `name` | 必需 | `string` | +| `type` | 必需 | `"tool-call"` | + + + +### `packages/llm/llm/src/types.ts#ToolResultBlock` + +SHA-256: `9bdd77f2b39d9ae47be71e494eca8c9e950b0e99b89a4539f86c4a0e7d1398b6` + +来源:[`packages/llm/llm/src/types.ts:101`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `content` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].content`](#persistence-type-eventagentinboxspliceddatainserted0content) | +| `isError` | 可选 | `boolean` | +| `toolCallId` | 必需 | `string` | +| `type` | 必需 | `"tool-result"` | + + + +### `packages/llm/llm/src/types.ts#ToolSchema` + +SHA-256: `7f5f2a8618c2ece530a18b1358b890fb57fb8c35705e47b66bff2296f8c6e889` + +来源:[`packages/llm/llm/src/types.ts:411`](../packages/llm/llm/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `description` | 必需 | `string` | +| `name` | 必需 | `string` | +| `parameters` | 必需 | [`event:request/header.data.header.tools[0].parameters`](#persistence-type-eventrequestheaderdataheadertools0parameters) | + + + +### `packages/sandbox/sandbox/src/index.ts#SandboxMode` + +SHA-256: `fab2bb09bc5e3b9db0685e8f5500b029af1a2b33e70bc703c1d5d0f596fbc1fd` + +来源:[`packages/sandbox/sandbox/src/index.ts:29`](../packages/sandbox/sandbox/src/index.ts) + +以下类型之一: + +- `"danger-full-access"` +- `"read-only"` +- `"workspace-write"` + + + +### `packages/schedule/schedule/src/types.ts#AfterScheduleRecord` + +SHA-256: `27e337d3d6b402b221e50c247a3f518a70d0c474cc60beee15fac3e9c5cbfcbe` + +来源:[`packages/schedule/schedule/src/types.ts:13`](../packages/schedule/schedule/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `afterSeconds` | 必需 | `number` | +| `id` | 必需 | `string` | +| `kind` | 必需 | `"after"` | +| `prompt` | 必需 | `string` | +| `scheduledAt` | 必需 | `string` | + + + +### `packages/schedule/schedule/src/types.ts#AtScheduleRecord` + +SHA-256: `658d895d187b26d83f3b53109a3bf77bbfba9f6f9a6d4bc4ee4315b4eb36239e` + +来源:[`packages/schedule/schedule/src/types.ts:27`](../packages/schedule/schedule/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `id` | 必需 | `string` | +| `kind` | 必需 | `"at"` | +| `prompt` | 必需 | `string` | +| `scheduledAt` | 必需 | `string` | + + + +### `packages/schedule/schedule/src/types.ts#EveryScheduleDispatchChange` + +SHA-256: `e6063d0451dc02026fd9b54ad3f77c7b1b6392ad3e872a7737ac84e13ca72344` + +来源:[`packages/schedule/schedule/src/types.ts:93`](../packages/schedule/schedule/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `acceptedAt` | 必需 | `string` | +| `id` | 必需 | `string` | +| `operation` | 必需 | `"dispatch"` | +| `version` | 必需 | `1` | + + + +### `packages/schedule/schedule/src/types.ts#EveryScheduleRecord` + +SHA-256: `66ece68282d8c06330fb83329a24ae54c386d5ccfab296bbf18369da8beb7d14` + +来源:[`packages/schedule/schedule/src/types.ts:39`](../packages/schedule/schedule/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `everySeconds` | 必需 | `number` | +| `id` | 必需 | `string` | +| `kind` | 必需 | `"every"` | +| `prompt` | 必需 | `string` | +| `scheduledAt` | 必需 | `string` | + + + +### `packages/schedule/schedule/src/types.ts#OneShotScheduleDispatchChange` + +SHA-256: `8e8e91646a5cae3fc78f7532a0013ff2088ae47077642c2edd507a508209b8af` + +来源:[`packages/schedule/schedule/src/types.ts:86`](../packages/schedule/schedule/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `id` | 必需 | `string` | +| `operation` | 必需 | `"dispatch"` | +| `version` | 必需 | `1` | + + + +### `packages/schedule/schedule/src/types.ts#ScheduleChange` + +SHA-256: `e1d0e52dcd1855ffb803efc18f8f7b776c48d1c25757e8a75c0281fa298c02e1` + +来源:[`packages/schedule/schedule/src/types.ts:105`](../packages/schedule/schedule/src/types.ts) + +以下类型之一: + +- [`packages/schedule/schedule/src/types.ts#EveryScheduleDispatchChange`](#persistence-type-packagesscheduleschedulesrctypestseveryscheduledispatchchange) +- [`packages/schedule/schedule/src/types.ts#ScheduleDeleteChange`](#persistence-type-packagesscheduleschedulesrctypestsscheduledeletechange) +- [`packages/schedule/schedule/src/types.ts#OneShotScheduleDispatchChange`](#persistence-type-packagesscheduleschedulesrctypestsoneshotscheduledispatchchange) +- [`packages/schedule/schedule/src/types.ts#ScheduleCreateChange`](#persistence-type-packagesscheduleschedulesrctypestsschedulecreatechange) + + + +### `packages/schedule/schedule/src/types.ts#ScheduleCreateChange` + +SHA-256: `ca6425edd20afe34d0cedc6ad1abad1ad7b32e2fbc4c1d24db53939e80bf887f` + +来源:[`packages/schedule/schedule/src/types.ts:72`](../packages/schedule/schedule/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `operation` | 必需 | `"create"` | +| `schedule` | 必需 | [`packages/schedule/schedule/src/types.ts#ScheduleRecord`](#persistence-type-packagesscheduleschedulesrctypestsschedulerecord) | +| `version` | 必需 | `1` | + + + +### `packages/schedule/schedule/src/types.ts#ScheduleDeleteChange` + +SHA-256: `18dc21f12bbac8ade8377e6fbb54179e20c3d955d73fba3244c57fb6bf001df0` + +来源:[`packages/schedule/schedule/src/types.ts:79`](../packages/schedule/schedule/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `id` | 必需 | `string` | +| `operation` | 必需 | `"delete"` | +| `version` | 必需 | `1` | + + + +### `packages/schedule/schedule/src/types.ts#ScheduleRecord` + +SHA-256: `d8f868c6b3e7189de6c1ffe2ea04794ea2aa31315b6347d12536e46020afac87` + +来源:[`packages/schedule/schedule/src/types.ts:69`](../packages/schedule/schedule/src/types.ts) + +以下类型之一: + +- [`packages/schedule/schedule/src/types.ts#AfterScheduleRecord`](#persistence-type-packagesscheduleschedulesrctypestsafterschedulerecord) +- [`packages/schedule/schedule/src/types.ts#EveryScheduleRecord`](#persistence-type-packagesscheduleschedulesrctypestseveryschedulerecord) +- [`packages/schedule/schedule/src/types.ts#AtScheduleRecord`](#persistence-type-packagesscheduleschedulesrctypestsatschedulerecord) + + + +### `packages/session/session-persistence-jsonl/src/format.ts#HeaderLine` + +SHA-256: `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` + +来源:[`packages/session/session-persistence-jsonl/src/format.ts:82`](../packages/session/session-persistence-jsonl/src/format.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `agentPreset` | 可选 | `string` | +| `createdAt` | 必需 | `number` | +| `cwd` | 可选 | `string` | +| `delegationDepth` | 必需 | `number` | +| `id` | 必需 | `string` | +| `isSeeded` | 必需 | `boolean` | +| `origin` | 可选 | `"subagent"` | +| `parentSession` | 可选 | `string` | +| `type` | 必需 | `"session"` | +| `version` | 必需 | `number` | + + + +### `packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData` + +SHA-256: `08936f7648f573f7539800e1ec2852fbbcba05e1a20487a1e5acf2a0fce1e2de` + +来源:[`packages/session/session-title-llm/src/index.ts:27`](../packages/session/session-title-llm/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `maxTokens` | 必需 | `number` | +| `messageSeqs` | 必需 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `messages` | 必需 | [`event:session/title-llm-request.data.messages`](#persistence-type-eventsessiontitle-llm-requestdatamessages) | +| `route` | 必需 | [`packages/session/session-title/src/types.ts#SessionTitleModelIdentity`](#persistence-type-packagessessionsession-titlesrctypestssessiontitlemodelidentity) | +| `system` | 必需 | `string` | +| `titleProvider` | 必需 | `string` | + + + +### `packages/session/session-title/src/types.ts#SessionTitleEventData` + +SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4` + +来源:[`packages/session/session-title/src/types.ts:41`](../packages/session/session-title/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `messageSeqs` | 必需 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `source` | 必需 | [`packages/session/session-title/src/types.ts#SessionTitleSource`](#persistence-type-packagessessionsession-titlesrctypestssessiontitlesource) | +| `title` | 必需 | `string` | + + + +### `packages/session/session-title/src/types.ts#SessionTitleModelIdentity` + +SHA-256: `07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235` + +来源:[`packages/session/session-title/src/types.ts:20`](../packages/session/session-title/src/types.ts) · [`packages/subagent/tool-subagent/src/model-selection.ts:9`](../packages/subagent/tool-subagent/src/model-selection.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `model` | 必需 | `string` | +| `provider` | 必需 | `string` | + + + +### `packages/session/session-title/src/types.ts#SessionTitleSource` + +SHA-256: `30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f` + +来源:[`packages/session/session-title/src/types.ts:28`](../packages/session/session-title/src/types.ts) + +以下类型之一: + +- [`event:session/title.data.source[1]`](#persistence-type-eventsessiontitledatasource1) +- [`event:session/title.data.source[0]`](#persistence-type-eventsessiontitledatasource0) +- [`event:agent/inbox/spliced.data.inserted[0].source[0]`](#persistence-type-eventagentinboxspliceddatainserted0source0) + + + +### `packages/skill/skill/src/index.ts#SkillInvocationSource` + +SHA-256: `672c4aa2a9b736e3d6e5da6346d2e2ed5e3573f2d0baafa18e7407c9f3fee3db` + +来源:[`packages/skill/skill/src/index.ts:146`](../packages/skill/skill/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"instructions"` | +| `kind` | 必需 | `"skill-invocation"` | +| `name` | 必需 | `string` | + + + +### `packages/skill/tool-skill/src/index.ts#SkillCatalogSource` + +SHA-256: `fc2ae6962f9f8801c2b1e414668e1e17cbb7544c4f39381198977a38bd10c093` + +来源:[`packages/skill/tool-skill/src/index.ts:34`](../packages/skill/tool-skill/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `entries` | 必需 | [`event:agent/inbox/spliced.data.inserted[0].source[16].entries`](#persistence-type-eventagentinboxspliceddatainserted0source16entries) | +| `form` | 必需 | `"catalog"` | +| `kind` | 必需 | `"skill-catalog"` | +| `update` | 可选 | `true` | + + + +### `packages/subagent/subagent/src/catalog.ts#SubagentCatalogEvent` + +SHA-256: `3ac1196f59d5e9e0e9345122c8c403b102478b526404a2e591a38f613483321e` + +来源:[`packages/subagent/subagent/src/catalog.ts:24`](../packages/subagent/subagent/src/catalog.ts) + +以下类型之一: + +- [`event:subagent/catalog.data[1]`](#persistence-type-eventsubagentcatalogdata1) +- [`event:subagent/catalog.data[0]`](#persistence-type-eventsubagentcatalogdata0) + + + +### `packages/subagent/subagent/src/continuation-messages.ts#AgentMessageSource` + +SHA-256: `4a9bef9f27a942520cc2279ca7dd3082556e4bff543f66b5c0333e1bbed1cd86` + +来源:[`packages/subagent/subagent/src/continuation-messages.ts:15`](../packages/subagent/subagent/src/continuation-messages.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"relay"` | +| `kind` | 必需 | `"agent-message"` | +| `senderSessionId` | 必需 | `string` | + + + +### `packages/subagent/subagent/src/continuation-messages.ts#SubagentSettledMessageSource` + +SHA-256: `4e97c3d85c0fc817ee58873c38c25a2474abaaa0af4839311b50b68db3b8cf1a` + +来源:[`packages/subagent/subagent/src/continuation-messages.ts:30`](../packages/subagent/subagent/src/continuation-messages.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `form` | 必需 | `"notice"` | +| `kind` | 必需 | `"subagent-settled"` | +| `senderSessionId` | 必需 | `string` | +| `summary` | 必需 | `string` | + + + +### `packages/subagent/subagent/src/descriptor.ts#ContinuableSubagentDescriptorData` + +SHA-256: `6487b10ba7542f3c447b1e41a1c6a590b7bfafe0b9b66b49f34028da85bb970f` + +来源:[`packages/subagent/subagent/src/descriptor.ts:72`](../packages/subagent/subagent/src/descriptor.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `agentModel` | 可选 | `string` | +| `agentProvider` | 可选 | `string` | +| `agentReasoningEffort` | 可选 | `string` | +| `label` | 必需 | `string` | +| `mode` | 必需 | `"continuable"` | +| `persona` | 可选 | `string` | +| `provider` | 必需 | `string` | +| `toolFilter` | 可选 | [`packages/core/tools/src/index.ts#ToolRestriction`](#persistence-type-packagescoretoolssrcindextstoolrestriction) | +| `version` | 必需 | `number` | + + + +### `packages/subagent/subagent/src/descriptor.ts#OneShotSubagentDescriptorData` + +SHA-256: `5f758f3de5f00c18968a7c6d347083aac5c9ae13f6a79c78f6611b23c4973564` + +来源:[`packages/subagent/subagent/src/descriptor.ts:61`](../packages/subagent/subagent/src/descriptor.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `label` | 可选 | `string` | +| `mode` | 必需 | `"one-shot"` | +| `provider` | 必需 | `string` | +| `version` | 必需 | `number` | + + + +### `packages/subagent/subagent/src/descriptor.ts#SubagentDescriptorData` + +SHA-256: `86d1d0e9cfebb240421187e3dc37fb71cc38a1501885cf32f8f9adef7b0a2bde` + +来源:[`packages/subagent/subagent/src/descriptor.ts:89`](../packages/subagent/subagent/src/descriptor.ts) + +以下类型之一: + +- [`packages/subagent/subagent/src/descriptor.ts#ContinuableSubagentDescriptorData`](#persistence-type-packagessubagentsubagentsrcdescriptortscontinuablesubagentdescriptordata) +- [`packages/subagent/subagent/src/descriptor.ts#OneShotSubagentDescriptorData`](#persistence-type-packagessubagentsubagentsrcdescriptortsoneshotsubagentdescriptordata) + + + +### `packages/todo/tool-todo/src/types.ts#TodoItem` + +SHA-256: `a99f76fd149051c363f960bfeeb5b9509587ecaf7591a42797e4bfe810d8987a` + +来源:[`packages/todo/tool-todo/src/types.ts:21`](../packages/todo/tool-todo/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `content` | 必需 | `string` | +| `status` | 必需 | [`event:todo/write.data.todos[0].status`](#persistence-type-eventtodowritedatatodos0status) | + + + +### `packages/web/web-search-deepseek/src/provider.ts#DeepSeekSearchLlmRequest` + +SHA-256: `2517ba143a271508d3ca35126d5aca8f7f1facf5aaddce4adf011832042fa2b9` + +来源:[`packages/web/web-search-deepseek/src/provider.ts:56`](../packages/web/web-search-deepseek/src/provider.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `apiVersion` | 必需 | `string` | +| `body` | 必需 | [`event:web/deepseek-search-llm-request.data.body`](#persistence-type-eventwebdeepseek-search-llm-requestdatabody) | +| `endpoint` | 必需 | `string` | + + + +### `packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentEndData` + +SHA-256: `c0ea97bc89dfc46fa94eab956f5c3ee6af2f964aa7db6327cb18b451f207bef7` + +来源:[`packages/workflow/tool-workflow/src/types.ts:29`](../packages/workflow/tool-workflow/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `outcome` | 必需 | [`packages/workflow/workflow/src/types.ts#WorkflowAgentOutcome`](#persistence-type-packagesworkflowworkflowsrctypestsworkflowagentoutcome) | +| `runId` | 必需 | `string` | +| `seq` | 必需 | `number` | + + + +### `packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentStartData` + +SHA-256: `9a410ddf56792b2b7cd4a224a2e54dc4dee30a8e8e6b1c2b9a1d19992b217adc` + +来源:[`packages/workflow/tool-workflow/src/types.ts:20`](../packages/workflow/tool-workflow/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `childId` | 必需 | `string` | +| `label` | 必需 | `string` | +| `phase` | 可选 | `string` | +| `runId` | 必需 | `string` | +| `seq` | 必需 | `number` | + + + +### `packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunEndData` + +SHA-256: `7f9365bae9fcb73e1e34061a1acfe0422c1a5fd351848cace9bebbe806f28462` + +来源:[`packages/workflow/tool-workflow/src/types.ts:36`](../packages/workflow/tool-workflow/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `runId` | 必需 | `string` | +| `stopReason` | 必需 | [`packages/workflow/workflow/src/types.ts#WorkflowStopReason`](#persistence-type-packagesworkflowworkflowsrctypestsworkflowstopreason) | + + + +### `packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunStartData` + +SHA-256: `a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c` + +来源:[`packages/workflow/tool-workflow/src/types.ts:14`](../packages/workflow/tool-workflow/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `name` | 必需 | `string` | +| `runId` | 必需 | `string` | + + + +### `packages/workflow/workflow/src/types.ts#WorkflowAgentOutcome` + +SHA-256: `c2deb7c9183735d4e0fffddf5c623f4e34daa1c8e96b111982e957dfcbbcf896` + +来源:[`packages/workflow/workflow/src/types.ts:110`](../packages/workflow/workflow/src/types.ts) + +以下类型之一: + +- `"cancelled"` +- `"completed"` +- `"failed"` + + + +### `packages/workflow/workflow/src/types.ts#WorkflowStopReason` + +SHA-256: `8465ece5e9d9007c038b357015d849c15262cb0bc25abc59e47692d87ee01a15` + +来源:[`packages/workflow/workflow/src/types.ts:63`](../packages/workflow/workflow/src/types.ts) + +以下类型之一: + +- `"cancelled"` +- `"completed"` +- `"error"` + + + +### `"parent"` + +SHA-256: `e6411b09c1d982f789bf741ca74e2bea69df56029847f86a6148e722039ee732` + +`"parent"` + + + +### `"pause"` + +SHA-256: `1d8798b1a5e8c545ee4dcd2b4e75c0b4c9ffd985e4744c6d1a1aab992f859721` + +`"pause"` + + + +### `"paused"` + +SHA-256: `c43b119ce579b3e9983a0d3adc4ba402ebb9ba773d89738a2dab01ecf5813943` + +`"paused"` + + + +### `"pending"` + +SHA-256: `234142ee4f996f38cc9fa3b8b3e3de29b03c9aefe5ebf6c224eddd564505aa85` + +`"pending"` + + + +### `"permission/preset"` + +SHA-256: `ae63d9d1080ed8945031594d93f8d96651d7101b848719cc3e1475875cd3d649` + +`"permission/preset"` + + + +### `"plan/mode"` + +SHA-256: `f634871d1e18b1eb199497ac8ab72d3964f48d03fbf611dc7521a1970cf429d9` + +`"plan/mode"` + + + +### `"plugin"` + +SHA-256: `e654d28d1e6c471147310d2fba396d00969cfe6ed9022626d3f2c2312e0b1cbb` + +`"plugin"` + + + +### `"positive"` + +SHA-256: `df5d9709b7db2a72510dc52052a23f0c38f39730af1504bab8bccabc14fae528` + +`"positive"` + + + +### `"product-interaction"` + +SHA-256: `0341d4c3dfcd53253cdc1e789d9130d3cd848b6f8b0706bf92f313a68b39ff23` + +`"product-interaction"` + + + +### `"provider"` + +SHA-256: `e31572180d6b28b73f073ad30ae19d5d82982d3daf984723984e23da1e7c459a` + +`"provider"` + + + +### `"provisioning"` + +SHA-256: `871bfd6f7bb91061246768d3377f3fd58d6b8ea7c31f44c714ae7ab215650d62` + +`"provisioning"` + + + +### `"read-only"` + +SHA-256: `12a0cfa619ac4a5fc6c3e8e359c8dae15769edc61497a575935c78d5842f8e42` + +`"read-only"` + + + +### `"reasoning"` + +SHA-256: `3c2535ca0a2062f172f15344f8040755002b5ac506cf94fc4ebaa822b15feb8c` + +`"reasoning"` + + + +### `"reasoning-chunks"` + +SHA-256: `688e853dc149f0d30b31765c12ec7e3be74169bdf1a4c6463a10b945d50cc1b8` + +`"reasoning-chunks"` + + + +### `"reasoning-delta"` + +SHA-256: `6f091fe32ab24407236090ca9d710bbaf59bb57466e9f3b8f1f6e4862bc863c6` + +`"reasoning-delta"` + + + +### `"recall"` + +SHA-256: `383cf73ae4748e5beb281ae74857ba1ffb92ae01075b9fbf51af78b666007a8f` + +`"recall"` + + + +### `"rejected"` + +SHA-256: `fab35c4520883c0d69e42d079c9f543fc5ac7a2d15b537b5572fcd99059c7018` + +`"rejected"` + + + +### `"relay"` + +SHA-256: `16d0507cb94b4cfc84a87446b1ba7652d6ec1d65ee1684789e747e30d542800e` + +`"relay"` + + + +### `"remove"` + +SHA-256: `7382c4fc95fe8c5db65ca4d9da572b115d0dbb818b7f32c22ab040318ffbb4bc` + +`"remove"` + + + +### `"replace"` + +SHA-256: `f6cb462f808309e315fea634f1ed86268b8346bbcee6774d9097ca6eed7ac1f8` + +`"replace"` + + + +### `"request/context"` + +SHA-256: `70b082fe78991edf13cf66e1b4d06cd2dcff9cacfd76f8cfb996bfb61939e7b2` + +`"request/context"` + + + +### `"request/header"` + +SHA-256: `3e0eff5946255ff05d88a10839c60305135801f97794ea48d409f8503303317e` + +`"request/header"` + + + +### `"resource-cost"` + +SHA-256: `5bd310104cfe448e530beb11422d2f791e2e32e8e95fdc528a15f9ca693dae75` + +`"resource-cost"` + + + +### `"resume"` + +SHA-256: `34d1ab5c5df378186d6054b1a1beea9a41e41965d6f336f0bc0025c441a82e66` + +`"resume"` + + + +### `"sandbox/mode"` + +SHA-256: `ca085dba2c877feceebf3c62f9d39c6c2a4d2314d97b94b3927a780fe8fe2bb2` + +`"sandbox/mode"` + + + +### `"schedule/change"` + +SHA-256: `0e7b2a97f54f1afdb8cdfad1635abe9145327db539bfb69b4045c19d8c17755d` + +`"schedule/change"` + + + +### `"security-privacy-permission"` + +SHA-256: `07eccc9ea2f9b5439d76cc77d5e062936677d43f07a23da749819b0a22ee36b7` + +`"security-privacy-permission"` + + + +### `"series"` + +SHA-256: `4f507598cf476214aa7646ce7841a7064a9dcb05d0a1eaaefaf79c2b738f160f` + +`"series"` + + + +### `"service-stability"` + +SHA-256: `7180844e1066230c5e891f1b9ccd942423b0fe48aac6ded7eef2a285b288a732` + +`"service-stability"` + + + +### `"session"` + +SHA-256: `e41bdebe246abe17154de7e7a1d81035437eee8d20e7f1edff0d91e2598ad828` + +`"session"` + + + +### `"session-log-deepseek/delivery-accepted"` + +SHA-256: `47e5d84f1a93450a78c7f3954b43990d0f629af7c034a56313e5deb63e772c1b` + +`"session-log-deepseek/delivery-accepted"` + + + +### `"session-reference"` + +SHA-256: `7f13860ee89a2d35ce09f958e85a04566cd59cb1de1d73a9619a9b631961dc71` + +`"session-reference"` + + + +### `"session/end-seed"` + +SHA-256: `1c2686c0682e7bc51d61117b2652bd4a80707e331aa276d6ef915ae32534a330` + +`"session/end-seed"` + + + +### `SessionEventEnvelope` + +SHA-256: `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` + +以下类型之一: + +- [`SessionEventEnvelope[38]`](#persistence-type-sessioneventenvelope38) +- [`SessionEventEnvelope[6]`](#persistence-type-sessioneventenvelope6) +- [`SessionEventEnvelope[0]`](#persistence-type-sessioneventenvelope0) + + + +### `SessionEventEnvelope[0]` + +SHA-256: `e3e77b26f0148755a505f5b8ea843a3be117827d4703bb5754d9dc8ddbef521f` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `string` | + + + +### `SessionEventEnvelope[38]` + +SHA-256: `998f22585a5d73ec005365e203f07fc3b3a029e82323fdda3fd7f13b0a9639d0` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `sourceEventSeqs` | 可选 | [`SessionEventEnvelope[38].sourceEventSeqs`](#persistence-type-sessioneventenvelope38sourceeventseqs) | +| `surfaceOp` | 必需 | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | 必需 | `number` | +| `type` | 必需 | `string` | + + + +### `SessionEventEnvelope[38].sourceEventSeqs` + +SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78` + +`number` 的数组。 + + + +### `SessionEventEnvelope[6]` + +SHA-256: `7af85bf70d4eafce63e739adae38ea85501e68df3a0e22f97f3a0be90fd9e4cb` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `surfaceOp` | 必需 | [`packages/core/session/src/types.ts#SurfaceOp`](#persistence-type-packagescoresessionsrctypestssurfaceop) | +| `time` | 必需 | `number` | +| `type` | 必需 | `string` | + + + +### `SessionEventEnvelope[6].surfaceOp[1]` + +SHA-256: `bcf0caf62d964b2fcf5404bd5c909cfa9a21c3f3c96e6b7e36d9e33565223825` + +来源:[`packages/core/session/src/types.ts:436`](../packages/core/session/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `endSeq` | 必需 | `number` | +| `op` | 必需 | `"replace"` | +| `startSeq` | 必需 | `number` | + + + +### `"session/title"` + +SHA-256: `7a651f3b672c52fb615f22c0ce9cb3cc5b22aad92e6bbeefb0808fd7b9233a50` + +`"session/title"` + + + +### `"session/title-llm-request"` + +SHA-256: `587bb11da730e06aa52b62c087d217d6f8f22459b25f31cb9e1e1baf17944e5e` + +`"session/title-llm-request"` + + + +### `"set"` + +SHA-256: `b765ee31033cea463863faf3fc6a0a1b85c47c6a2f13fe1ce833abe56105114b` + +`"set"` + + + +### `"skill-catalog"` + +SHA-256: `6ebcc2dbec9f2dabca9f668ae6d39e877899b180740570f453409db339221df3` + +`"skill-catalog"` + + + +### `"skill-invocation"` + +SHA-256: `1922cd79a70806c17758185a6ef0458bc1fb3900363906733421c16414da2509` + +`"skill-invocation"` + + + +### `"snapshot"` + +SHA-256: `6f9e00383836275e46c0a9915767f9bb561be5fc30aae490174908a93e99a912` + +`"snapshot"` + + + +### `"step/end"` + +SHA-256: `c219efbdfd53b836786202ab4532913d555798f0a8615a4e9629736e3799fb70` + +`"step/end"` + + + +### `"step/start"` + +SHA-256: `2c64d4bc7264fd3f8bed47e96ab8a593bce05a1e4e1c436dcd244a3b7e0c2a9d` + +`"step/start"` + + + +### `"stop"` + +SHA-256: `57f3f606ff8b6c84d9c443faace9d1dd10cca0354f37a4f515ef075f7c351b90` + +`"stop"` + + + +### `string` + +SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b` + +来源:[`packages/api/session-controller/src/types.ts:375`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:19`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:10`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/webhook/webhook/src/brand.ts:12`](../packages/webhook/webhook/src/brand.ts) · [`packages/webhook/webhook/src/brand.ts:6`](../packages/webhook/webhook/src/brand.ts) · [`packages/webhook/webhook/src/brand.ts:9`](../packages/webhook/webhook/src/brand.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) + +`string` + + + +### `"subagent"` + +SHA-256: `aacda84a69739238aa782eea902d3073017d9d74b9808de2bf5a83c9738cbdc6` + +`"subagent"` + + + +### `"subagent-settled"` + +SHA-256: `bf16d2e98a7f13894ded2cffe5ce857bf473954a0e3c0ed9bc7739e2e1c7b78a` + +`"subagent-settled"` + + + +### `"subagent/catalog"` + +SHA-256: `44c8c77aa9aa100b0d74d2deee85474b29faa6eee0ac0f662f7abe9931ddd7f2` + +`"subagent/catalog"` + + + +### `"subagent/descriptor"` + +SHA-256: `5ebce1317eacd2c148456627a09668e927ee15c204f0643f5ea76b46b54a2647` + +`"subagent/descriptor"` + + + +### `"subagent/model-selection-policy"` + +SHA-256: `0a8a062bc9c3c0d97fd97742262f2ea53dd32297f834e81114870d38ac3dc6da` + +`"subagent/model-selection-policy"` + + + +### `"success"` + +SHA-256: `511801b0a6470d98566e8385d3dffddb2b67f79fcf73ed6056f46c3cc0a245a7` + +`"success"` + + + +### `"system"` + +SHA-256: `8af841fc9b265ade3441caf9fe40dd24db24226befba48150e6d0eeccc1389c3` + +`"system"` + + + +### `"system/message"` + +SHA-256: `1c5a58f4599bd4a9711712551ffe2442a5e5fcd0758bafe630cc4ead850bdaa7` + +`"system/message"` + + + +### `"task-result"` + +SHA-256: `f05fb85e715af4ab4de0b6ba111166dd0435bd8bb74a0fa0f32b9d174113fc7c` + +`"task-result"` + + + +### `"team-message"` + +SHA-256: `2e4debca12f2c449f96dc6574f3fbf2fb346193443114a241d01a236362ec48c` + +`"team-message"` + + + +### `"team/member"` + +SHA-256: `18fcdd273a2f2f9d813a3be5e71df5903a963a5c88e46b90b14de6e2db890da0` + +`"team/member"` + + + +### `"team/message/delivered"` + +SHA-256: `a8a2b9011fc067862df7ce40748868467e5778283d7ac193c13f77684a914c57` + +`"team/message/delivered"` + + + +### `"team/message/queued"` + +SHA-256: `4e011753f8263f436efaabfbb3b4db53a98fc55342c5ded2daf7208b217be159` + +`"team/message/queued"` + + + +### `"team/task"` + +SHA-256: `b57ae1b37e52bee0a21cbdace168dc6a15582bcda8c306dad962c5dcc8a8e28b` + +`"team/task"` + + + +### `"text"` + +SHA-256: `4e44c9118152f6829634bfd508e47669ab0497caffa380c94b4be91f9390d35d` + +`"text"` + + + +### `"text-chunks"` + +SHA-256: `f9cfc89e8fe085b4fcc9cd32b74b6f103608c54d7d8cd97b47c01de34f252ba3` + +`"text-chunks"` + + + +### `"text-delta"` + +SHA-256: `5abab3a27fd2736660ecddd94b28d6bbf114986dd894880153bd1be3ccacb563` + +`"text-delta"` + + + +### `"todo/write"` + +SHA-256: `af0750d32d97d47b12b7361a587db41894b17e04c9a805fe212f0114aa2248c7` + +`"todo/write"` + + + +### `"tool"` + +SHA-256: `a13d8f379222ece8a02a86efe4c8334af4f108ecb3034b38c0a42bb3046e6847` + +`"tool"` + + + +### `"tool-call"` + +SHA-256: `1f0133217eeeffd8d387baf1ec8ed9856e719bcd999dc08e48624cba96d75fd7` + +`"tool-call"` + + + +### `"tool-call-chunks"` + +SHA-256: `616f93a5047de668e1c30d57b1786abd76beeb4123599637ec1dc5290320ede5` + +`"tool-call-chunks"` + + + +### `"tool-call-delta"` + +SHA-256: `9b4fd974c124c83743efe030ba4e1250e087505f3f74588716a521fb8d453267` + +`"tool-call-delta"` + + + +### `"tool-calls"` + +SHA-256: `6c1cccb433ddc153facb380c7dda956237ba5c31f8a532a69142dc277890cc9b` + +`"tool-calls"` + + + +### `"tool-result"` + +SHA-256: `e5244812c495a80c2e78ba2c2690dca5f6b58ea8d3fe5531ff94a6f9ac5e5d66` + +`"tool-result"` + + + +### `"tool-workflow/agent-end"` + +SHA-256: `5d17f27a1ef9f7026c0232714adf7573961536d75f3303ca97973975f5af170e` + +`"tool-workflow/agent-end"` + + + +### `"tool-workflow/agent-start"` + +SHA-256: `ad92f473a03ae72599ff26c86e897c25e16269519ad2a87340f0bea946517af8` + +`"tool-workflow/agent-start"` + + + +### `"tool-workflow/run-end"` + +SHA-256: `fe792a6cd430494503837e21e79f91677704ccdd9e870c679aabd7d43ed47ddd` + +`"tool-workflow/run-end"` + + + +### `"tool-workflow/run-start"` + +SHA-256: `cdca80b8dad24d4e7b1d5a288a8475ba8d8d46be385bd2da85f122ee836f1670` + +`"tool-workflow/run-start"` + + + +### `"tool/call"` + +SHA-256: `c589e938cc0e4989df8b82abef6339de621f0dc4fe62c230d145b94996af7f92` + +`"tool/call"` + + + +### `"tool/ptc-dispatch"` + +SHA-256: `fb5e0517050d2c6d40e5b8145db1f7c5ca7044d5d50fada6ff80e0b22c6ca973` + +`"tool/ptc-dispatch"` + + + +### `"tool/ptc-dispatch-start"` + +SHA-256: `2aed07493b1c65b6d00de5586da1e743cf501684b9ad43a1765fb4007d4ea456` + +`"tool/ptc-dispatch-start"` + + + +### `"tool/result"` + +SHA-256: `570e85022434613394418ad33e77e9f1f7a05699b33d0b62eae0677a638c3833` + +`"tool/result"` + + + +### `true` + +SHA-256: `dacf3d5f3e4ff619c1320e64a48dec5e7ecd7870bece65fbd449a0e63adfa521` + +`true` + + + +### `"turn/end"` + +SHA-256: `4312134fc6f9cad507c31390bd3d147535492dd77621ec517c5e85f853d53a03` + +`"turn/end"` + + + +### `"turn/start"` + +SHA-256: `0af3ecfb8c60d416fc40a7cc4fb2c4851a8e46608b2b38ab9e510670c971aa1f` + +`"turn/start"` + + + +### `"unavailable"` + +SHA-256: `2c4c21f19f426a58f2dac914e34313aa43f8408972de857a486d1f086d5adb6c` + +`"unavailable"` + + + +### `unknown` + +SHA-256: `bbc5696e3f13c758237b316e3cf08cda0b9fd51ffe672539883457769faa185d` + +`unknown`:此声明未暴露存储值的内部字段。 + + + +### `"usage"` + +SHA-256: `0bdaf9cb63594be4ef034b68901fffe792fe09d6d5f6bac42d7f56f6e7834fb9` + +`"usage"` + + + +### `"user"` + +SHA-256: `68f44f59e91c41395076cb77265981a601680ec8a381bbbeb6ab754ffdc94573` + +`"user"` + + + +### `"user/message"` + +SHA-256: `de25352d2dc4196309bd84b5bac6319f6d5c898ea0e82bb5ae66c5738d0d426f` + +`"user/message"` + + + +### `"web_search"` + +SHA-256: `935c8d486292b13c87b57ca280eb8c58b5b2f5239be86fcd4334c33fdeb87682` + +`"web_search"` + + + +### `"web_search_20250305"` + +SHA-256: `0116b1f89c05221d56b0ba35c83d302bf4857bc7277d247e9bbaf74b27d2c66e` + +`"web_search_20250305"` + + + +### `"web/deepseek-search-llm-request"` + +SHA-256: `6f611bed14b2106542480c4a230b4a83baf76dc0d8ac0cd13319a7cb8dae7675` + +`"web/deepseek-search-llm-request"` + + + +### `"webhook"` + +SHA-256: `db6c5246911d47a7969f6b4cb47c7d0dbbde75c402710b22ace3672fa97bebe0` + +`"webhook"` + + + +### `"workspace-write"` + +SHA-256: `a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa` + +`"workspace-write"` diff --git a/packages/e2b/subprocess-e2b/README.i18n.yaml b/docs/persistence-changes/2026-09-11-initial.i18n.yaml similarity index 51% rename from packages/e2b/subprocess-e2b/README.i18n.yaml rename to docs/persistence-changes/2026-09-11-initial.i18n.yaml index a66bd592f1..975877b8fb 100644 --- a/packages/e2b/subprocess-e2b/README.i18n.yaml +++ b/docs/persistence-changes/2026-09-11-initial.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/e2b/subprocess-e2b/README.md -README.md: 35d20ace111442becfe496bc0ac5b152fbae5222 -README.zh.md: f2c244fd50c578064ee4c331ef3017cae3185a67 +# pnpm run verify-translation-pairing --write docs/persistence-changes/2026-09-11-initial.md +2026-09-11-initial.md: a919aba314a7e94242ab831a588e2112997edd8f +2026-09-11-initial.zh.md: e37d0c48219e8ac4d5d30d84cca441c150210a0b diff --git a/docs/persistence-changes/2026-09-11-initial.md b/docs/persistence-changes/2026-09-11-initial.md new file mode 100644 index 0000000000..a919aba314 --- /dev/null +++ b/docs/persistence-changes/2026-09-11-initial.md @@ -0,0 +1,280 @@ +--- +description: "Records a persistence type transition and its compatibility acknowledgement." +kind: persistence-change +--- + +# Initial persistence type baseline + +English | [中文](2026-09-11-initial.zh.md) + +## Summary + +This baseline records the complete persistence type inventory declared by the current source and gives later transitions a starting point stored in this tree. + +## Table of Contents + +- [Declaration](#declaration) +- [Compatibility](#compatibility) +- [Verification](#verification) +- [Dev Note](#dev-note) + + +## Declaration + +```yaml persistence-change +schemaVersion: 1 +id: 2026-09-11-initial +baseline: true +changes: + - root: "JsonlHeaderLine" + previous: null + after: "18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f" + decision: same-version + - root: "SessionEventEnvelope" + previous: null + after: "5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc" + decision: same-version + - root: "SessionHeader" + previous: null + after: "2a8b114637e66faab5fae1c6e2fba65320db6224c80f5f36c5bf32f937154d03" + decision: same-version + - root: "event:agent-preset/selected" + previous: null + after: "a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b" + decision: same-version + - root: "event:agent/inbox/spliced" + previous: null + after: "f757322e25914d63799c6235af4ed4b4f36d7d083fef40d6873a45e94fe6ad65" + decision: same-version + - root: "event:approval/asked" + previous: null + after: "3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1" + decision: same-version + - root: "event:approval/decided" + previous: null + after: "bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3" + decision: same-version + - root: "event:approval/policy" + previous: null + after: "26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78" + decision: same-version + - root: "event:assistant/attempt" + previous: null + after: "3516f54b427775f483febe8e938e4e10f9afd3583610861547bbe2999ed4d8d9" + decision: same-version + - root: "event:assistant/message" + previous: null + after: "a58ca1bed502d425a8b65cac02f59717498cd296de9513c6f81b83b791c20e9b" + decision: same-version + - root: "event:command/done" + previous: null + after: "15196447222782e773eb943c92b18316ce96b9af0f0cfddb6e57ba8274ecc5ff" + decision: same-version + - root: "event:command/run" + previous: null + after: "37184378c6439257d105c4e2022d80fc9c3a3f7c7f6ac661b00bc9f18d871006" + decision: same-version + - root: "event:compaction/end" + previous: null + after: "b0127044ab31a702bddfd785d345f5abd7a70876746e895ce443afa3e60ddf2d" + decision: same-version + - root: "event:compaction/prune" + previous: null + after: "7f7fd5a6b0064f597534b29ff62ef26e786dffccf5e14f654a7d4fcea2c35f04" + decision: same-version + - root: "event:compaction/start" + previous: null + after: "db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965" + decision: same-version + - root: "event:compaction/summary" + previous: null + after: "eb1d32dcd3d76833392f351e039dcbd40686a1ab24b252a7a5003426f9cf4c89" + decision: same-version + - root: "event:deliverables/presented" + previous: null + after: "13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176" + decision: same-version + - root: "event:feedback/message-delete" + previous: null + after: "3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796" + decision: same-version + - root: "event:feedback/message-put" + previous: null + after: "b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf" + decision: same-version + - root: "event:feedback/record" + previous: null + after: "b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8" + decision: same-version + - root: "event:goal/change" + previous: null + after: "763c8a20af487263a0080548274f7437ef4a86e0ba8769127d1ced10a72c664d" + decision: same-version + - root: "event:hook/invoked" + previous: null + after: "8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0" + decision: same-version + - root: "event:hook/result" + previous: null + after: "e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4" + decision: same-version + - root: "event:llm/retry" + previous: null + after: "91c397f8f870e812e1dc5ac69c2745f9f105be9dc97dadc980d19ef415a65145" + decision: same-version + - root: "event:llm/retry-started" + previous: null + after: "48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3" + decision: same-version + - root: "event:model/selection" + previous: null + after: "35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc" + decision: same-version + - root: "event:permission/preset" + previous: null + after: "5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b" + decision: same-version + - root: "event:plan/mode" + previous: null + after: "a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f" + decision: same-version + - root: "event:request/context" + previous: null + after: "37cbc9cf06d494cfe5c67f078af1605eacb8e4c2c853e9e26455b73de0ee7ccf" + decision: same-version + - root: "event:request/header" + previous: null + after: "8ea13bcd983eab596dbfd41c5c2b62a0b318b71cd2a987a6fd752c6ced7e4489" + decision: same-version + - root: "event:sandbox/mode" + previous: null + after: "516da4cdd6d2f1e5ce488e648578ca51f40e458f707b803e5b24de870e799415" + decision: same-version + - root: "event:schedule/change" + previous: null + after: "2a7f86849ae54b3398ee49661a757c4fcb59a6192b7036ee2ff514617e13fb42" + decision: same-version + - root: "event:session-log-deepseek/delivery-accepted" + previous: null + after: "d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9" + decision: same-version + - root: "event:session/end-seed" + previous: null + after: "5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e" + decision: same-version + - root: "event:session/title" + previous: null + after: "1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5" + decision: same-version + - root: "event:session/title-llm-request" + previous: null + after: "6091f64d426ada2adc646094a6fe662a1b81d9ee5770929fe76800524e2c89a8" + decision: same-version + - root: "event:step/end" + previous: null + after: "e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05" + decision: same-version + - root: "event:step/start" + previous: null + after: "4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986" + decision: same-version + - root: "event:subagent/catalog" + previous: null + after: "ae1f7110feeec697b8cab42b68f7709aa7b3279764099dfb53c25890d2e5c871" + decision: same-version + - root: "event:subagent/descriptor" + previous: null + after: "b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5" + decision: same-version + - root: "event:subagent/model-selection-policy" + previous: null + after: "a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc" + decision: same-version + - root: "event:system/message" + previous: null + after: "0ae7ccdc1cc5d9d43772ebd5c7357ff509011c3a6ba188d9c274d33df2542c42" + decision: same-version + - root: "event:team/member" + previous: null + after: "4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc" + decision: same-version + - root: "event:team/message/delivered" + previous: null + after: "48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb" + decision: same-version + - root: "event:team/message/queued" + previous: null + after: "2ffcad547cb840f462eb1849fa5f6cb0e17eb93f5d07cc88b61d749b1b78d632" + decision: same-version + - root: "event:team/task" + previous: null + after: "d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a" + decision: same-version + - root: "event:todo/write" + previous: null + after: "b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26" + decision: same-version + - root: "event:tool-workflow/agent-end" + previous: null + after: "babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7" + decision: same-version + - root: "event:tool-workflow/agent-start" + previous: null + after: "5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627" + decision: same-version + - root: "event:tool-workflow/run-end" + previous: null + after: "42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f" + decision: same-version + - root: "event:tool-workflow/run-start" + previous: null + after: "c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058" + decision: same-version + - root: "event:tool/call" + previous: null + after: "3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48" + decision: same-version + - root: "event:tool/ptc-dispatch" + previous: null + after: "da807583a19f4ebe2bc9115c9686016e1521e405bdfaced296ae652b91e8e5f6" + decision: same-version + - root: "event:tool/ptc-dispatch-start" + previous: null + after: "ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93" + decision: same-version + - root: "event:tool/result" + previous: null + after: "cc3e270ab982cbafff602b753647b6f056decb7fe4fd523afd2f157a65c3d622" + decision: same-version + - root: "event:turn/end" + previous: null + after: "84c24f1209fc3e0153de6ac85d58fd6968b851f955e0e761b92321053956609e" + decision: same-version + - root: "event:turn/start" + previous: null + after: "aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8" + decision: same-version + - root: "event:user/message" + previous: null + after: "2e49ede7fb3a592cce8919ebe982f6ca1c1f67abe4def359ffc028c85e6d1ad8" + decision: same-version + - root: "event:web/deepseek-search-llm-request" + previous: null + after: "cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331" + decision: same-version +``` + + +## Compatibility + +This record initializes type history without asserting relationships to earlier formats or changing the Session writer version or runtime behavior. Each root starts without a predecessor and retains its complete normalized schema; successor records reference its after schema. + + +## Verification + +Ran `pnpm exec tsx scripts/gen-persistence-catalog.ts` to generate the current inventory and `pnpm run persistence-changes --baseline 2026-09-11-initial` to create this baseline and its schema snapshot. + + +## Dev Note + +None. diff --git a/docs/persistence-changes/2026-09-11-initial.schema.json b/docs/persistence-changes/2026-09-11-initial.schema.json new file mode 100644 index 0000000000..f91e925cfa --- /dev/null +++ b/docs/persistence-changes/2026-09-11-initial.schema.json @@ -0,0 +1,13557 @@ +{ + "formatVersion": 1, + "roots": [ + { + "key": "SessionHeader", + "kind": "header", + "digest": "2a8b114637e66faab5fae1c6e2fba65320db6224c80f5f36c5bf32f937154d03", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "agentPreset", + "type": 1, + "optional": true + }, + { + "name": "createdAt", + "type": 2, + "optional": false + }, + { + "name": "cwd", + "type": 1, + "optional": true + }, + { + "name": "delegationDepth", + "type": 2, + "optional": true + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "isSeeded", + "type": 3, + "optional": false + }, + { + "name": "origin", + "type": 4, + "optional": true + }, + { + "name": "parentSession", + "type": 1, + "optional": true + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "subagent" + }, + { + "kind": "literal", + "value": 3 + } + ] + } + }, + { + "key": "JsonlHeaderLine", + "kind": "header", + "digest": "18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "agentPreset", + "type": 1, + "optional": true + }, + { + "name": "createdAt", + "type": 2, + "optional": false + }, + { + "name": "cwd", + "type": 1, + "optional": true + }, + { + "name": "delegationDepth", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "isSeeded", + "type": 3, + "optional": false + }, + { + "name": "origin", + "type": 4, + "optional": true + }, + { + "name": "parentSession", + "type": 1, + "optional": true + }, + { + "name": "type", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "subagent" + }, + { + "kind": "literal", + "value": "session" + } + ] + } + }, + { + "key": "SessionEventEnvelope", + "kind": "envelope", + "digest": "5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 10, + 11 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 4, + "optional": true + }, + { + "name": "surfaceOp", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 6, + 7 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 3, + "optional": false + }, + { + "name": "op", + "type": 8, + "optional": false + }, + { + "name": "startSeq", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "surfaceOp", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + } + ] + } + }, + { + "key": "event:agent-preset/selected", + "kind": "event", + "event": "agent-preset/selected", + "surface": false, + "digest": "a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "agentPreset", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "agent-preset/selected" + } + ] + } + }, + { + "key": "event:agent/inbox/spliced", + "kind": "event", + "event": "agent/inbox/spliced", + "surface": false, + "digest": "f757322e25914d63799c6235af4ed4b4f36d7d083fef40d6873a45e94fe6ad65", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 32, + "optional": true + }, + { + "name": "seq", + "type": 11, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 90, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "inserted", + "type": 2, + "optional": false + }, + { + "name": "outcome", + "type": 86, + "optional": true + }, + { + "name": "removedCount", + "type": 11, + "optional": true + }, + { + "name": "start", + "type": 11, + "optional": false + }, + { + "name": "target", + "type": 87, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 29, + "optional": false + }, + { + "name": "source", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 19, + 22, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "mediaType", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 17, + "optional": true + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 20, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 31, + 41, + 43, + 44, + 47, + 52, + 54, + 59, + 60, + 61, + 63, + 65, + 66, + 73, + 75, + 77, + 79, + 81, + 84, + 85 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 32, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 7, + "optional": true + }, + { + "name": "changes", + "type": 33, + "optional": false + }, + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 34 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 35, + "optional": false + }, + { + "name": "digest", + "type": 7, + "optional": true + }, + { + "name": "path", + "type": 7, + "optional": false + }, + { + "name": "scope", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 36, + 37, + 38 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 7, + "optional": true + }, + { + "name": "kind", + "type": 29, + "optional": false + }, + { + "name": "rpcId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 7, + "optional": false + }, + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "ruleId", + "type": 7, + "optional": false + }, + { + "name": "source", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 48, + "optional": false + }, + { + "name": "form", + "type": 50, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "update", + "type": 32, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 49 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 55, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + }, + { + "name": "sections", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 58 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 64, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 50, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + }, + { + "name": "references", + "type": 68, + "optional": false + }, + { + "name": "version", + "type": 72, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 69 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 11, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 70, + "optional": false + }, + { + "name": "compacted", + "type": 23, + "optional": false + }, + { + "name": "inputIndex", + "type": 11, + "optional": false + }, + { + "name": "label", + "type": 7, + "optional": false + }, + { + "name": "omittedBytes", + "type": 11, + "optional": false + }, + { + "name": "omittedMessages", + "type": 11, + "optional": false + }, + { + "name": "originalMessages", + "type": 11, + "optional": false + }, + { + "name": "retainedMessages", + "type": 11, + "optional": false + }, + { + "name": "sessionId", + "type": 7, + "optional": false + }, + { + "name": "truncated", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 11, + 71 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 74, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 64, + "optional": false + }, + { + "name": "kind", + "type": 76, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "revision", + "type": 11, + "optional": false + }, + { + "name": "round", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "messageId", + "type": 7, + "optional": false + }, + { + "name": "senderId", + "type": 7, + "optional": false + }, + { + "name": "senderName", + "type": 7, + "optional": false + }, + { + "name": "teamId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "replayState", + "type": 83, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "canceled" + }, + { + "kind": "union", + "types": [ + 88, + 89 + ] + }, + { + "kind": "literal", + "value": "next-step" + }, + { + "kind": "literal", + "value": "next-turn" + }, + { + "kind": "literal", + "value": "agent/inbox/spliced" + } + ] + } + }, + { + "key": "event:approval/asked", + "kind": "event", + "event": "approval/asked", + "surface": false, + "digest": "3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 2, + "optional": true + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "reason", + "type": 2, + "optional": true + }, + { + "name": "toolName", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "approval/asked" + } + ] + } + }, + { + "key": "event:approval/decided", + "kind": "event", + "event": "approval/decided", + "surface": false, + "digest": "bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 8, + "optional": true + }, + { + "name": "seq", + "type": 9, + "optional": false + }, + { + "name": "time", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "outcome", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6, + 7 + ] + }, + { + "kind": "literal", + "value": "allowed-once" + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "rejected" + }, + { + "kind": "literal", + "value": "unavailable" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "approval/decided" + } + ] + } + }, + { + "key": "event:approval/policy", + "kind": "event", + "event": "approval/policy", + "surface": false, + "digest": "26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 7, + "optional": false + }, + { + "name": "time", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "policy", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "ask" + }, + { + "kind": "literal", + "value": "never" + }, + { + "kind": "literal", + "value": "delegation" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "approval/policy" + } + ] + } + }, + { + "key": "event:assistant/attempt", + "kind": "event", + "event": "assistant/attempt", + "surface": false, + "digest": "3516f54b427775f483febe8e938e4e10f9afd3583610861547bbe2999ed4d8d9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 71, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 72, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "stream", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 10, + 67, + 69 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 6, + "optional": false + }, + { + "name": "dt", + "type": 8, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "literal", + "value": "tool-call-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 11, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 12, + 14, + 39, + 42, + 44, + 46, + 63 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 15, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 16, + 18, + 27, + 30, + 34, + 36 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 20, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 25, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 21, + 22, + 23, + 24 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 28, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 31, + "optional": false + }, + { + "name": "isError", + "type": 32, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 15 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 40, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 41, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 17, + 26, + 29, + 33, + 35, + 37 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 47, + "optional": false + }, + { + "name": "replayState", + "type": 59, + "optional": true + }, + { + "name": "type", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 48, + 51, + 53, + 55, + 57 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 7, + "optional": false + }, + { + "name": "message", + "type": 7, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 2, + "optional": true + }, + { + "name": "requestId", + "type": 7, + "optional": true + }, + { + "name": "status", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 60, + "optional": true + }, + { + "name": "response", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 61 + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 64, + "optional": false + }, + { + "name": "usage", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 2, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 2, + "optional": true + }, + { + "name": "inputTokens", + "type": 2, + "optional": false + }, + { + "name": "outputTokens", + "type": 2, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 2, + "optional": true + }, + { + "name": "totalTokens", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 8, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "texts", + "type": 6, + "optional": false + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 8, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "texts", + "type": 6, + "optional": false + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-chunks" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "assistant/attempt" + } + ] + } + }, + { + "key": "event:assistant/message", + "kind": "event", + "event": "assistant/message", + "surface": true, + "digest": "a58ca1bed502d425a8b65cac02f59717498cd296de9513c6f81b83b791c20e9b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 11, + "optional": false + }, + { + "name": "surfaceOp", + "type": 76, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "interrupted", + "type": 2, + "optional": true + }, + { + "name": "message", + "type": 3, + "optional": false + }, + { + "name": "step", + "type": 11, + "optional": false + }, + { + "name": "stream", + "type": 33, + "optional": false + }, + { + "name": "turn", + "type": 11, + "optional": false + }, + { + "name": "usage", + "type": 70, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 29, + "optional": false + }, + { + "name": "source", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 19, + 22, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "mediaType", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 17, + "optional": true + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 20, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 31, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "replayState", + "type": 32, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "array", + "element": 34 + }, + { + "kind": "union", + "types": [ + 35, + 39, + 72, + 74 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 36, + "optional": false + }, + { + "name": "dt", + "type": 37, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "time0", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "array", + "element": 11 + }, + { + "kind": "literal", + "value": "tool-call-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 40, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 71, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 41, + 43, + 45, + 48, + 50, + 52, + 68 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "type", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 5, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 46, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 47, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 8, + 18, + 21, + 24, + 26, + 28 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 51, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 53, + "optional": false + }, + { + "name": "replayState", + "type": 65, + "optional": true + }, + { + "name": "type", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 54, + 57, + 59, + 61, + 63 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 55, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 7, + "optional": false + }, + { + "name": "message", + "type": 7, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 11, + "optional": true + }, + { + "name": "requestId", + "type": 7, + "optional": true + }, + { + "name": "status", + "type": 11, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 55, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 66, + "optional": true + }, + { + "name": "response", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 32 + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 69, + "optional": false + }, + { + "name": "usage", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 11, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 11, + "optional": true + }, + { + "name": "inputTokens", + "type": 11, + "optional": false + }, + { + "name": "outputTokens", + "type": 11, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 11, + "optional": true + }, + { + "name": "totalTokens", + "type": 11, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 37, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "texts", + "type": 36, + "optional": false + }, + { + "name": "time0", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 73, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 37, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "texts", + "type": 36, + "optional": false + }, + { + "name": "time0", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 75, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-chunks" + }, + { + "kind": "union", + "types": [ + 77, + 79 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 11, + "optional": false + }, + { + "name": "op", + "type": 78, + "optional": false + }, + { + "name": "startSeq", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "literal", + "value": "assistant/message" + } + ] + } + }, + { + "key": "event:command/done", + "kind": "event", + "event": "command/done", + "surface": false, + "digest": "15196447222782e773eb943c92b18316ce96b9af0f0cfddb6e57ba8274ecc5ff", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "commandId", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "sourceEventSeq", + "type": 6, + "optional": true + }, + { + "name": "text", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "literal", + "value": "success" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "command/done" + } + ] + } + }, + { + "key": "event:command/run", + "kind": "event", + "event": "command/run", + "surface": false, + "digest": "37184378c6439257d105c4e2022d80fc9c3a3f7c7f6ac661b00bc9f18d871006", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 2, + "optional": true + }, + { + "name": "commandId", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "command/run" + } + ] + } + }, + { + "key": "event:compaction/end", + "kind": "event", + "event": "compaction/end", + "surface": false, + "digest": "b0127044ab31a702bddfd785d345f5abd7a70876746e895ce443afa3e60ddf2d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 2, + "optional": false + }, + { + "name": "error", + "type": 2, + "optional": true + }, + { + "name": "sourceCommandId", + "type": 2, + "optional": true + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "compaction/end" + } + ] + } + }, + { + "key": "event:compaction/prune", + "kind": "event", + "event": "compaction/prune", + "surface": false, + "digest": "7f7fd5a6b0064f597534b29ff62ef26e786dffccf5e14f654a7d4fcea2c35f04", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "shadowedRange", + "type": 2, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 4, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 3, + "optional": false + }, + { + "name": "start", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "compaction/prune" + } + ] + } + }, + { + "key": "event:compaction/start", + "kind": "event", + "event": "compaction/start", + "surface": false, + "digest": "db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 2, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 2, + "optional": true + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "compaction/start" + } + ] + } + }, + { + "key": "event:compaction/summary", + "kind": "event", + "event": "compaction/summary", + "surface": false, + "digest": "eb1d32dcd3d76833392f351e039dcbd40686a1ab24b252a7a5003426f9cf4c89", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 32 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 3, + "optional": false + }, + { + "name": "llmStreamCall", + "type": 4, + "optional": false + }, + { + "name": "maxTokens", + "type": 5, + "optional": true + }, + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "rawOutput", + "type": 6, + "optional": false + }, + { + "name": "shadowedRange", + "type": 29, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 30, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 5, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 3, + "optional": true + }, + { + "name": "summary", + "type": 6, + "optional": false + }, + { + "name": "usage", + "type": 31, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "union", + "types": [ + 8, + 11, + 13, + 15, + 17, + 26 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "isError", + "type": 9, + "optional": true + }, + { + "name": "toolCallId", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 5, + "optional": false + }, + { + "name": "height", + "type": 5, + "optional": false + }, + { + "name": "mediaType", + "type": 19, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "originalDimensions", + "type": 24, + "optional": true + }, + { + "name": "width", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 20, + 21, + 22, + 23 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 5, + "optional": false + }, + { + "name": "width", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 27, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 5, + "optional": false + }, + { + "name": "start", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 5, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 5, + "optional": true + }, + { + "name": "inputTokens", + "type": 5, + "optional": false + }, + { + "name": "outputTokens", + "type": 5, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 5, + "optional": true + }, + { + "name": "totalTokens", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 3, + "optional": false + }, + { + "name": "maxTokens", + "type": 5, + "optional": true + }, + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "rawOutput", + "type": 6, + "optional": true + }, + { + "name": "shadowedRange", + "type": 29, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 30, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 5, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 3, + "optional": true + }, + { + "name": "summary", + "type": 6, + "optional": false + }, + { + "name": "usage", + "type": 31, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "compaction/summary" + } + ] + } + }, + { + "key": "event:deliverables/presented", + "kind": "event", + "event": "deliverables/presented", + "surface": false, + "digest": "13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 2, + "optional": false + }, + { + "name": "files", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 2, + "optional": true + }, + { + "name": "path", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "deliverables/presented" + } + ] + } + }, + { + "key": "event:feedback/message-delete", + "kind": "event", + "event": "feedback/message-delete", + "surface": false, + "digest": "3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "messageId", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "feedback/message-delete" + } + ] + } + }, + { + "key": "event:feedback/message-put", + "kind": "event", + "event": "feedback/message-put", + "surface": false, + "digest": "b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 16, + "optional": true + }, + { + "name": "seq", + "type": 11, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "item", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "category", + "type": 3, + "optional": true + }, + { + "name": "createdAt", + "type": 11, + "optional": false + }, + { + "name": "messageId", + "type": 12, + "optional": false + }, + { + "name": "note", + "type": 12, + "optional": true + }, + { + "name": "rating", + "type": 13, + "optional": false + }, + { + "name": "updatedAt", + "type": 11, + "optional": false + }, + { + "name": "version", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ] + }, + { + "kind": "literal", + "value": "instruction-following" + }, + { + "kind": "literal", + "value": "other" + }, + { + "kind": "literal", + "value": "product-interaction" + }, + { + "kind": "literal", + "value": "resource-cost" + }, + { + "kind": "literal", + "value": "security-privacy-permission" + }, + { + "kind": "literal", + "value": "service-stability" + }, + { + "kind": "literal", + "value": "task-result" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "negative" + }, + { + "kind": "literal", + "value": "positive" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "feedback/message-put" + } + ] + } + }, + { + "key": "event:feedback/record", + "kind": "event", + "event": "feedback/record", + "surface": false, + "digest": "b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 11, + "optional": true + }, + { + "name": "seq", + "type": 12, + "optional": false + }, + { + "name": "time", + "type": 12, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "category", + "type": 2, + "optional": true + }, + { + "name": "text", + "type": 10, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + { + "kind": "literal", + "value": "instruction-following" + }, + { + "kind": "literal", + "value": "other" + }, + { + "kind": "literal", + "value": "product-interaction" + }, + { + "kind": "literal", + "value": "resource-cost" + }, + { + "kind": "literal", + "value": "security-privacy-permission" + }, + { + "kind": "literal", + "value": "service-stability" + }, + { + "kind": "literal", + "value": "task-result" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "feedback/record" + } + ] + } + }, + { + "key": "event:goal/change", + "kind": "event", + "event": "goal/change", + "surface": false, + "digest": "763c8a20af487263a0080548274f7437ef4a86e0ba8769127d1ced10a72c664d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 23, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 9 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "cleared", + "type": 3, + "optional": false + }, + { + "name": "clearedAt", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "operation", + "type": 7, + "optional": false + }, + { + "name": "version", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "revision", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "goal/change" + }, + { + "kind": "literal", + "value": "clear" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "createdAt", + "type": 5, + "optional": false + }, + { + "name": "goal", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "operation", + "type": 17, + "optional": false + }, + { + "name": "roundsStarted", + "type": 5, + "optional": false + }, + { + "name": "updatedAt", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "blockedReason", + "type": 11, + "optional": true + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "maxGoalRounds", + "type": 5, + "optional": false + }, + { + "name": "objective", + "type": 4, + "optional": false + }, + { + "name": "phase", + "type": 12, + "optional": false + }, + { + "name": "revision", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 4, + "optional": false + }, + { + "name": "message", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "literal", + "value": "complete" + }, + { + "kind": "literal", + "value": "paused" + }, + { + "kind": "union", + "types": [ + 15, + 18, + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "block" + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "literal", + "value": "resume" + }, + { + "kind": "literal", + "value": "edit" + }, + { + "kind": "literal", + "value": "pause" + }, + { + "kind": "literal", + "value": true + } + ] + } + }, + { + "key": "event:hook/invoked", + "kind": "event", + "event": "hook/invoked", + "surface": false, + "digest": "8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "dialect", + "type": 2, + "optional": false + }, + { + "name": "handlerId", + "type": 5, + "optional": false + }, + { + "name": "matcher", + "type": 5, + "optional": true + }, + { + "name": "point", + "type": 5, + "optional": false + }, + { + "name": "turn", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "claude-code" + }, + { + "kind": "literal", + "value": "codex" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "hook/invoked" + } + ] + } + }, + { + "key": "event:hook/result", + "kind": "event", + "event": "hook/result", + "surface": false, + "digest": "e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "decision", + "type": 2, + "optional": false + }, + { + "name": "durationMs", + "type": 3, + "optional": false + }, + { + "name": "exitCode", + "type": 3, + "optional": true + }, + { + "name": "handlerId", + "type": 2, + "optional": false + }, + { + "name": "point", + "type": 2, + "optional": false + }, + { + "name": "stderrSummary", + "type": 2, + "optional": true + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "hook/result" + } + ] + } + }, + { + "key": "event:llm/retry", + "kind": "event", + "event": "llm/retry", + "surface": false, + "digest": "91c397f8f870e812e1dc5ac69c2745f9f105be9dc97dadc980d19ef415a65145", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 9, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 7 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "delayMs", + "type": 3, + "optional": false + }, + { + "name": "failure", + "type": 4, + "optional": false + }, + { + "name": "maxRetries", + "type": 3, + "optional": false + }, + { + "name": "mode", + "type": 6, + "optional": false + }, + { + "name": "policyKey", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "retry", + "type": 3, + "optional": false + }, + { + "name": "retryId", + "type": 5, + "optional": false + }, + { + "name": "step", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 5, + "optional": false + }, + { + "name": "message", + "type": 5, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 3, + "optional": true + }, + { + "name": "requestId", + "type": 5, + "optional": true + }, + { + "name": "status", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "normal" + }, + { + "kind": "object", + "properties": [ + { + "name": "delayMs", + "type": 3, + "optional": false + }, + { + "name": "failure", + "type": 4, + "optional": false + }, + { + "name": "mode", + "type": 8, + "optional": false + }, + { + "name": "policyKey", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "retry", + "type": 3, + "optional": false + }, + { + "name": "retryId", + "type": 5, + "optional": false + }, + { + "name": "step", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "always" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "llm/retry" + } + ] + } + }, + { + "key": "event:llm/retry-started", + "kind": "event", + "event": "llm/retry-started", + "surface": false, + "digest": "48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "retry", + "type": 2, + "optional": false + }, + { + "name": "retryId", + "type": 3, + "optional": false + }, + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "llm/retry-started" + } + ] + } + }, + { + "key": "event:model/selection", + "kind": "event", + "event": "model/selection", + "surface": false, + "digest": "35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "provider", + "type": 2, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "model/selection" + } + ] + } + }, + { + "key": "event:permission/preset", + "kind": "event", + "event": "permission/preset", + "surface": false, + "digest": "5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "preset", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "permission/preset" + } + ] + } + }, + { + "key": "event:plan/mode", + "kind": "event", + "event": "plan/mode", + "surface": false, + "digest": "a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "active", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "plan/mode" + } + ] + } + }, + { + "key": "event:request/context", + "kind": "event", + "event": "request/context", + "surface": false, + "digest": "37cbc9cf06d494cfe5c67f078af1605eacb8e4c2c853e9e26455b73de0ee7ccf", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "contextWindow", + "type": 2, + "optional": true + }, + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "systemPromptUpdate", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "in-history" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "request/context" + } + ] + } + }, + { + "key": "event:request/header", + "kind": "event", + "event": "request/header", + "surface": false, + "digest": "8ea13bcd983eab596dbfd41c5c2b62a0b318b71cd2a987a6fd752c6ced7e4489", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "header", + "type": 2, + "optional": false + }, + { + "name": "reason", + "type": 13, + "optional": false + }, + { + "name": "startsSeries", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "adapterDefaults", + "type": 3, + "optional": true + }, + { + "name": "config", + "type": 5, + "optional": false + }, + { + "name": "tools", + "type": 9, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 4, + "optional": true + }, + { + "name": "reasoningEffort", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 6, + "optional": true + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 7, + "optional": true + }, + { + "name": "stop", + "type": 8, + "optional": true + }, + { + "name": "temperature", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "array", + "element": 10 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "parameters", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 7, + "value": 12 + } + ] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "union", + "types": [ + 14, + 15, + 16, + 17 + ] + }, + { + "kind": "literal", + "value": "initial" + }, + { + "kind": "literal", + "value": "resume" + }, + { + "kind": "literal", + "value": "series" + }, + { + "kind": "literal", + "value": "change" + }, + { + "kind": "literal", + "value": "request/header" + } + ] + } + }, + { + "key": "event:sandbox/mode", + "kind": "event", + "event": "sandbox/mode", + "surface": false, + "digest": "516da4cdd6d2f1e5ce488e648578ca51f40e458f707b803e5b24de870e799415", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 8, + "optional": false + }, + { + "name": "time", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "mode", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "danger-full-access" + }, + { + "kind": "literal", + "value": "read-only" + }, + { + "kind": "literal", + "value": "workspace-write" + }, + { + "kind": "literal", + "value": "delegation" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "sandbox/mode" + } + ] + } + }, + { + "key": "event:schedule/change", + "kind": "event", + "event": "schedule/change", + "surface": false, + "digest": "2a7f86849ae54b3398ee49661a757c4fcb59a6192b7036ee2ff514617e13fb42", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 19, + "optional": true + }, + { + "name": "seq", + "type": 13, + "optional": false + }, + { + "name": "time", + "type": 13, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 6, + 8, + 9 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "acceptedAt", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "operation", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "dispatch" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "operation", + "type": 7, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "delete" + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "operation", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "operation", + "type": 10, + "optional": false + }, + { + "name": "schedule", + "type": 11, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "union", + "types": [ + 12, + 15, + 17 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "afterSeconds", + "type": 13, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 14, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "after" + }, + { + "kind": "object", + "properties": [ + { + "name": "everySeconds", + "type": 13, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 16, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "every" + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 18, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "at" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "schedule/change" + } + ] + } + }, + { + "key": "event:session-log-deepseek/delivery-accepted", + "kind": "event", + "event": "session-log-deepseek/delivery-accepted", + "surface": false, + "digest": "d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "sessionFormatVersion", + "type": 2, + "optional": true + }, + { + "name": "sessionId", + "type": 3, + "optional": false + }, + { + "name": "throughSeq", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "session-log-deepseek/delivery-accepted" + } + ] + } + }, + { + "key": "event:session/end-seed", + "kind": "event", + "event": "session/end-seed", + "surface": false, + "digest": "5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "inherited", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "session/end-seed" + } + ] + } + }, + { + "key": "event:session/title", + "kind": "event", + "event": "session/title", + "surface": false, + "digest": "1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 13, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "messageSeqs", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 4, + "optional": false + }, + { + "name": "title", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 5, + 9, + 11 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": true + }, + { + "name": "provider", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "provider" + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 8, + "optional": false + }, + { + "name": "provider", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "fallback" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "session/title" + } + ] + } + }, + { + "key": "event:session/title-llm-request", + "kind": "event", + "event": "session/title-llm-request", + "surface": false, + "digest": "6091f64d426ada2adc646094a6fe662a1b81d9ee5770929fe76800524e2c89a8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 36, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 91, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 2, + "optional": false + }, + { + "name": "messageSeqs", + "type": 3, + "optional": false + }, + { + "name": "messages", + "type": 4, + "optional": false + }, + { + "name": "route", + "type": 90, + "optional": false + }, + { + "name": "system", + "type": 9, + "optional": false + }, + { + "name": "titleProvider", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 9, + "optional": false + }, + { + "name": "role", + "type": 30, + "optional": false + }, + { + "name": "source", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "union", + "types": [ + 8, + 11, + 20, + 23, + 26, + 28 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 9, + "optional": false + }, + { + "name": "id", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 12, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 9, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 13, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": true + }, + { + "name": "originalDimensions", + "type": 18, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 14, + 15, + 16, + 17 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 21, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 9, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "isError", + "type": 24, + "optional": true + }, + { + "name": "toolCallId", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "union", + "types": [ + 31, + 32, + 33 + ] + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 35, + 45, + 47, + 48, + 51, + 56, + 58, + 63, + 64, + 65, + 67, + 69, + 70, + 77, + 79, + 81, + 83, + 85, + 88, + 89 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 36, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 9, + "optional": true + }, + { + "name": "changes", + "type": 37, + "optional": false + }, + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 38 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 39, + "optional": false + }, + { + "name": "digest", + "type": 9, + "optional": true + }, + { + "name": "path", + "type": 9, + "optional": false + }, + { + "name": "scope", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 40, + 41, + 42 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 9, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 9, + "optional": true + }, + { + "name": "kind", + "type": 33, + "optional": false + }, + { + "name": "rpcId", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 9, + "optional": false + }, + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "provider", + "type": 9, + "optional": false + }, + { + "name": "ruleId", + "type": 9, + "optional": false + }, + { + "name": "source", + "type": 9, + "optional": false + }, + { + "name": "summary", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 52, + "optional": false + }, + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "update", + "type": 36, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 53 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 59, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + }, + { + "name": "sections", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 62 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 9, + "optional": false + }, + { + "name": "text", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + }, + { + "name": "summary", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 66, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 68, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 66, + "optional": false + }, + { + "name": "kind", + "type": 71, + "optional": false + }, + { + "name": "references", + "type": 72, + "optional": false + }, + { + "name": "version", + "type": 76, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 73 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 2, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 74, + "optional": false + }, + { + "name": "compacted", + "type": 24, + "optional": false + }, + { + "name": "inputIndex", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 9, + "optional": false + }, + { + "name": "omittedBytes", + "type": 2, + "optional": false + }, + { + "name": "omittedMessages", + "type": 2, + "optional": false + }, + { + "name": "originalMessages", + "type": 2, + "optional": false + }, + { + "name": "retainedMessages", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 9, + "optional": false + }, + { + "name": "truncated", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 75 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "senderSessionId", + "type": 9, + "optional": false + }, + { + "name": "summary", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 68, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "senderSessionId", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 9, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "revision", + "type": 2, + "optional": false + }, + { + "name": "round", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 84, + "optional": false + }, + { + "name": "messageId", + "type": 9, + "optional": false + }, + { + "name": "senderId", + "type": 9, + "optional": false + }, + { + "name": "senderName", + "type": 9, + "optional": false + }, + { + "name": "teamId", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 86, + "optional": false + }, + { + "name": "model", + "type": 9, + "optional": false + }, + { + "name": "provider", + "type": 9, + "optional": false + }, + { + "name": "replayState", + "type": 87, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 9, + "optional": false + }, + { + "name": "provider", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session/title-llm-request" + } + ] + } + }, + { + "key": "event:step/end", + "kind": "event", + "event": "step/end", + "surface": false, + "digest": "e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "step/end" + } + ] + } + }, + { + "key": "event:step/start", + "kind": "event", + "event": "step/start", + "surface": false, + "digest": "4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "step/start" + } + ] + } + }, + { + "key": "event:subagent/catalog", + "kind": "event", + "event": "subagent/catalog", + "surface": false, + "digest": "ae1f7110feeec697b8cab42b68f7709aa7b3279764099dfb53c25890d2e5c871", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 9, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 7 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": false + }, + { + "name": "mode", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "literal", + "value": 0 + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": true + }, + { + "name": "mode", + "type": 8, + "optional": false + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "one-shot" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "subagent/catalog" + } + ] + } + }, + { + "key": "event:subagent/descriptor", + "kind": "event", + "event": "subagent/descriptor", + "surface": false, + "digest": "b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 10, + "optional": true + }, + { + "name": "seq", + "type": 7, + "optional": false + }, + { + "name": "time", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 8 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "agentModel", + "type": 3, + "optional": true + }, + { + "name": "agentProvider", + "type": 3, + "optional": true + }, + { + "name": "agentReasoningEffort", + "type": 3, + "optional": true + }, + { + "name": "label", + "type": 3, + "optional": false + }, + { + "name": "mode", + "type": 4, + "optional": false + }, + { + "name": "persona", + "type": 3, + "optional": true + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "toolFilter", + "type": 5, + "optional": true + }, + { + "name": "version", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "object", + "properties": [ + { + "name": "allow", + "type": 6, + "optional": true + }, + { + "name": "deny", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "label", + "type": 3, + "optional": true + }, + { + "name": "mode", + "type": 9, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "version", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "one-shot" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "subagent/descriptor" + } + ] + } + }, + { + "key": "event:subagent/model-selection-policy", + "kind": "event", + "event": "subagent/model-selection-policy", + "surface": false, + "digest": "a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "allowedModels", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 4, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "subagent/model-selection-policy" + } + ] + } + }, + { + "key": "event:system/message", + "kind": "event", + "event": "system/message", + "surface": true, + "digest": "0ae7ccdc1cc5d9d43772ebd5c7357ff509011c3a6ba188d9c274d33df2542c42", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 46, + "optional": true + }, + { + "name": "seq", + "type": 10, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 47, + "optional": true + }, + { + "name": "surfaceOp", + "type": 48, + "optional": false + }, + { + "name": "time", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "step", + "type": 10, + "optional": false + }, + { + "name": "turn", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "role", + "type": 28, + "optional": false + }, + { + "name": "source", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 8, + 18, + 21, + 24, + 26 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "mediaType", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "originalDimensions", + "type": 16, + "optional": true + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 12, + 13, + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 22, + "optional": true + }, + { + "name": "toolCallId", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "union", + "types": [ + 30, + 35, + 37, + 39, + 41, + 43, + 45 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 31, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + }, + { + "name": "sections", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 34 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "text", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 36, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + }, + { + "name": "summary", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 38, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 44, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 10 + }, + { + "kind": "union", + "types": [ + 49, + 51 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 10, + "optional": false + }, + { + "name": "op", + "type": 50, + "optional": false + }, + { + "name": "startSeq", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "literal", + "value": "system/message" + } + ] + } + }, + { + "key": "event:team/member", + "kind": "event", + "event": "team/member", + "surface": false, + "digest": "4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 12, + "optional": true + }, + { + "name": "seq", + "type": 13, + "optional": false + }, + { + "name": "time", + "type": 13, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "member", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 6, + "optional": false + }, + { + "name": "version", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "context", + "type": 3, + "optional": false + }, + { + "name": "description", + "type": 6, + "optional": false + }, + { + "name": "error", + "type": 6, + "optional": true + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "phase", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "fork" + }, + { + "kind": "literal", + "value": "fresh" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 8, + 9, + 10 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "failed" + }, + { + "kind": "literal", + "value": "provisioning" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "team/member" + } + ] + } + }, + { + "key": "event:team/message/delivered", + "kind": "event", + "event": "team/message/delivered", + "surface": false, + "digest": "48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "messageId", + "type": 2, + "optional": false + }, + { + "name": "targetId", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 2, + "optional": false + }, + { + "name": "version", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "team/message/delivered" + } + ] + } + }, + { + "key": "event:team/message/queued", + "kind": "event", + "event": "team/message/queued", + "surface": false, + "digest": "2ffcad547cb840f462eb1849fa5f6cb0e17eb93f5d07cc88b61d749b1b78d632", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 29, + "optional": true + }, + { + "name": "seq", + "type": 17, + "optional": false + }, + { + "name": "time", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 7, + "optional": false + }, + { + "name": "version", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "senderId", + "type": 7, + "optional": false + }, + { + "name": "senderName", + "type": 7, + "optional": false + }, + { + "name": "targetId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 9, + 11, + 13, + 15, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 6, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "team/message/queued" + } + ] + } + }, + { + "key": "event:team/task", + "kind": "event", + "event": "team/task", + "surface": false, + "digest": "d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 12, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "task", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "blockedBy", + "type": 3, + "optional": false + }, + { + "name": "description", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "ownerId", + "type": 4, + "optional": true + }, + { + "name": "revision", + "type": 5, + "optional": false + }, + { + "name": "status", + "type": 6, + "optional": false + }, + { + "name": "subject", + "type": 4, + "optional": false + }, + { + "name": "writeScopes", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 7, + 8, + 9, + 10 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "deleted" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "team/task" + } + ] + } + }, + { + "key": "event:todo/write", + "kind": "event", + "event": "todo/write", + "surface": false, + "digest": "b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 9, + "optional": true + }, + { + "name": "seq", + "type": 10, + "optional": false + }, + { + "name": "time", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "todos", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "status", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 6, + 7, + 8 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "todo/write" + } + ] + } + }, + { + "key": "event:tool-workflow/agent-end", + "kind": "event", + "event": "tool-workflow/agent-end", + "surface": false, + "digest": "babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 8, + "optional": true + }, + { + "name": "seq", + "type": 7, + "optional": false + }, + { + "name": "time", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "outcome", + "type": 2, + "optional": false + }, + { + "name": "runId", + "type": 6, + "optional": false + }, + { + "name": "seq", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "failed" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool-workflow/agent-end" + } + ] + } + }, + { + "key": "event:tool-workflow/agent-start", + "kind": "event", + "event": "tool-workflow/agent-start", + "surface": false, + "digest": "5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "childId", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 2, + "optional": false + }, + { + "name": "phase", + "type": 2, + "optional": true + }, + { + "name": "runId", + "type": 2, + "optional": false + }, + { + "name": "seq", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool-workflow/agent-start" + } + ] + } + }, + { + "key": "event:tool-workflow/run-end", + "kind": "event", + "event": "tool-workflow/run-end", + "surface": false, + "digest": "42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 8, + "optional": false + }, + { + "name": "time", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "runId", + "type": 2, + "optional": false + }, + { + "name": "stopReason", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6 + ] + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-workflow/run-end" + } + ] + } + }, + { + "key": "event:tool-workflow/run-start", + "kind": "event", + "event": "tool-workflow/run-start", + "surface": false, + "digest": "c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "runId", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-workflow/run-start" + } + ] + } + }, + { + "key": "event:tool/call", + "kind": "event", + "event": "tool/call", + "surface": false, + "digest": "3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "callId", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "step", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool/call" + } + ] + } + }, + { + "key": "event:tool/ptc-dispatch", + "kind": "event", + "event": "tool/ptc-dispatch", + "surface": false, + "digest": "da807583a19f4ebe2bc9115c9686016e1521e405bdfaced296ae652b91e8e5f6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 28, + "optional": true + }, + { + "name": "seq", + "type": 17, + "optional": false + }, + { + "name": "time", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "parentCallId", + "type": 7, + "optional": false + }, + { + "name": "rootCallId", + "type": 7, + "optional": false + }, + { + "name": "subCallId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 9, + 11, + 13, + 15, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 6, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool/ptc-dispatch" + } + ] + } + }, + { + "key": "event:tool/ptc-dispatch-start", + "kind": "event", + "event": "tool/ptc-dispatch-start", + "surface": false, + "digest": "ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "parentCallId", + "type": 3, + "optional": false + }, + { + "name": "rootCallId", + "type": 3, + "optional": false + }, + { + "name": "subCallId", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool/ptc-dispatch-start" + } + ] + } + }, + { + "key": "event:tool/result", + "kind": "event", + "event": "tool/result", + "surface": true, + "digest": "cc3e270ab982cbafff602b753647b6f056decb7fe4fd523afd2f157a65c3d622", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 37, + "optional": true + }, + { + "name": "seq", + "type": 17, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 38, + "optional": true + }, + { + "name": "surfaceOp", + "type": 39, + "optional": false + }, + { + "name": "time", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "error", + "type": 2, + "optional": true + }, + { + "name": "message", + "type": 4, + "optional": false + }, + { + "name": "meta", + "type": 33, + "optional": true + }, + { + "name": "step", + "type": 17, + "optional": false + }, + { + "name": "turn", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "role", + "type": 30, + "optional": false + }, + { + "name": "source", + "type": 31, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 6, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "isError", + "type": 28, + "optional": true + }, + { + "name": "toolCallId", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 8 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 11, + 13, + 15, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "union", + "types": [ + 3, + 17, + 28, + 34, + 35, + 36 + ] + }, + { + "kind": "array", + "element": 33 + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 3, + "value": 33 + } + ] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 17 + }, + { + "kind": "union", + "types": [ + 40, + 41 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 17, + "optional": false + }, + { + "name": "op", + "type": 42, + "optional": false + }, + { + "name": "startSeq", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "tool/result" + } + ] + } + }, + { + "key": "event:turn/end", + "kind": "event", + "event": "turn/end", + "surface": false, + "digest": "84c24f1209fc3e0153de6ac85d58fd6968b851f955e0e761b92321053956609e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 29, + "optional": true + }, + { + "name": "seq", + "type": 14, + "optional": false + }, + { + "name": "time", + "type": 14, + "optional": false + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 5, + 7, + 9, + 11, + 16 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "interrupted" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "error", + "type": 12, + "optional": false + }, + { + "name": "kind", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 13, + "optional": false + }, + { + "name": "message", + "type": 13, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 14, + "optional": true + }, + { + "name": "requestId", + "type": 13, + "optional": true + }, + { + "name": "status", + "type": 14, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 17, + "optional": false + }, + { + "name": "reason", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "union", + "types": [ + 19, + 21, + 23, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "disposed" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "legacy" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "parent" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "reason", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hook" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "turn/end" + } + ] + } + }, + { + "key": "event:turn/start", + "kind": "event", + "event": "turn/start", + "surface": false, + "digest": "aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "turn/start" + } + ] + } + }, + { + "key": "event:user/message", + "kind": "event", + "event": "user/message", + "surface": true, + "digest": "2e49ede7fb3a592cce8919ebe982f6ca1c1f67abe4def359ffc028c85e6d1ad8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 30, + "optional": true + }, + { + "name": "seq", + "type": 9, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 84, + "optional": true + }, + { + "name": "surfaceOp", + "type": 85, + "optional": false + }, + { + "name": "time", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 88, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "role", + "type": 27, + "optional": false + }, + { + "name": "source", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 4, + 7, + 17, + 20, + 23, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "mediaType", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 15, + "optional": true + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 11, + 12, + 13, + 14 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "isError", + "type": 21, + "optional": true + }, + { + "name": "toolCallId", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 29, + 39, + 41, + 42, + 45, + 50, + 52, + 57, + 58, + 59, + 61, + 63, + 64, + 71, + 73, + 75, + 77, + 79, + 82, + 83 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 30, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 5, + "optional": true + }, + { + "name": "changes", + "type": 31, + "optional": false + }, + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 32 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 33, + "optional": false + }, + { + "name": "digest", + "type": 5, + "optional": true + }, + { + "name": "path", + "type": 5, + "optional": false + }, + { + "name": "scope", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 34, + 35, + 36 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 5, + "optional": true + }, + { + "name": "kind", + "type": 27, + "optional": false + }, + { + "name": "rpcId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 5, + "optional": false + }, + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "ruleId", + "type": 5, + "optional": false + }, + { + "name": "source", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 46, + "optional": false + }, + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + }, + { + "name": "update", + "type": 30, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 47 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 53, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "sections", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 56 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "text", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 60, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 60, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + }, + { + "name": "references", + "type": 66, + "optional": false + }, + { + "name": "version", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 67 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 9, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 68, + "optional": false + }, + { + "name": "compacted", + "type": 21, + "optional": false + }, + { + "name": "inputIndex", + "type": 9, + "optional": false + }, + { + "name": "label", + "type": 5, + "optional": false + }, + { + "name": "omittedBytes", + "type": 9, + "optional": false + }, + { + "name": "omittedMessages", + "type": 9, + "optional": false + }, + { + "name": "originalMessages", + "type": 9, + "optional": false + }, + { + "name": "retainedMessages", + "type": 9, + "optional": false + }, + { + "name": "sessionId", + "type": 5, + "optional": false + }, + { + "name": "truncated", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 9, + 69 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 72, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 74, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 76, + "optional": false + }, + { + "name": "revision", + "type": 9, + "optional": false + }, + { + "name": "round", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "messageId", + "type": 5, + "optional": false + }, + { + "name": "senderId", + "type": 5, + "optional": false + }, + { + "name": "senderName", + "type": 5, + "optional": false + }, + { + "name": "teamId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "model", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "replayState", + "type": 81, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 9 + }, + { + "kind": "union", + "types": [ + 86, + 87 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 9, + "optional": false + }, + { + "name": "op", + "type": 35, + "optional": false + }, + { + "name": "startSeq", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user/message" + } + ] + } + }, + { + "key": "event:web/deepseek-search-llm-request", + "kind": "event", + "event": "web/deepseek-search-llm-request", + "surface": false, + "digest": "cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 15, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "apiVersion", + "type": 2, + "optional": false + }, + { + "name": "body", + "type": 3, + "optional": false + }, + { + "name": "endpoint", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "max_tokens", + "type": 4, + "optional": false + }, + { + "name": "messages", + "type": 5, + "optional": false + }, + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "tools", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "tuple", + "elements": [ + { + "type": 6, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 8, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "tuple", + "elements": [ + { + "type": 12, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "max_uses", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 13, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "web_search" + }, + { + "kind": "literal", + "value": "web_search_20250305" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "web/deepseek-search-llm-request" + } + ] + } + } + ], + "types": [] +} diff --git a/docs/persistence-changes/2026-09-11-initial.zh.md b/docs/persistence-changes/2026-09-11-initial.zh.md new file mode 100644 index 0000000000..e37d0c4821 --- /dev/null +++ b/docs/persistence-changes/2026-09-11-initial.zh.md @@ -0,0 +1,280 @@ +--- +description: "记录持久化类型更改及其兼容性确认。" +kind: persistence-change +--- + +# 初始持久化类型基线 + +[English](2026-09-11-initial.md) | 中文 + +## 概述 + +此基线记录当前源码声明的完整持久化类型目录,为后续类型转换提供保存在本源码树内的起点。 + +## 目录 + +- [声明](#declaration) +- [兼容性](#compatibility) +- [验证](#verification) +- [开发备注](#dev-note) + + +## 声明 + +```yaml persistence-change +schemaVersion: 1 +id: 2026-09-11-initial +baseline: true +changes: + - root: "JsonlHeaderLine" + previous: null + after: "18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f" + decision: same-version + - root: "SessionEventEnvelope" + previous: null + after: "5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc" + decision: same-version + - root: "SessionHeader" + previous: null + after: "2a8b114637e66faab5fae1c6e2fba65320db6224c80f5f36c5bf32f937154d03" + decision: same-version + - root: "event:agent-preset/selected" + previous: null + after: "a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b" + decision: same-version + - root: "event:agent/inbox/spliced" + previous: null + after: "f757322e25914d63799c6235af4ed4b4f36d7d083fef40d6873a45e94fe6ad65" + decision: same-version + - root: "event:approval/asked" + previous: null + after: "3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1" + decision: same-version + - root: "event:approval/decided" + previous: null + after: "bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3" + decision: same-version + - root: "event:approval/policy" + previous: null + after: "26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78" + decision: same-version + - root: "event:assistant/attempt" + previous: null + after: "3516f54b427775f483febe8e938e4e10f9afd3583610861547bbe2999ed4d8d9" + decision: same-version + - root: "event:assistant/message" + previous: null + after: "a58ca1bed502d425a8b65cac02f59717498cd296de9513c6f81b83b791c20e9b" + decision: same-version + - root: "event:command/done" + previous: null + after: "15196447222782e773eb943c92b18316ce96b9af0f0cfddb6e57ba8274ecc5ff" + decision: same-version + - root: "event:command/run" + previous: null + after: "37184378c6439257d105c4e2022d80fc9c3a3f7c7f6ac661b00bc9f18d871006" + decision: same-version + - root: "event:compaction/end" + previous: null + after: "b0127044ab31a702bddfd785d345f5abd7a70876746e895ce443afa3e60ddf2d" + decision: same-version + - root: "event:compaction/prune" + previous: null + after: "7f7fd5a6b0064f597534b29ff62ef26e786dffccf5e14f654a7d4fcea2c35f04" + decision: same-version + - root: "event:compaction/start" + previous: null + after: "db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965" + decision: same-version + - root: "event:compaction/summary" + previous: null + after: "eb1d32dcd3d76833392f351e039dcbd40686a1ab24b252a7a5003426f9cf4c89" + decision: same-version + - root: "event:deliverables/presented" + previous: null + after: "13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176" + decision: same-version + - root: "event:feedback/message-delete" + previous: null + after: "3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796" + decision: same-version + - root: "event:feedback/message-put" + previous: null + after: "b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf" + decision: same-version + - root: "event:feedback/record" + previous: null + after: "b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8" + decision: same-version + - root: "event:goal/change" + previous: null + after: "763c8a20af487263a0080548274f7437ef4a86e0ba8769127d1ced10a72c664d" + decision: same-version + - root: "event:hook/invoked" + previous: null + after: "8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0" + decision: same-version + - root: "event:hook/result" + previous: null + after: "e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4" + decision: same-version + - root: "event:llm/retry" + previous: null + after: "91c397f8f870e812e1dc5ac69c2745f9f105be9dc97dadc980d19ef415a65145" + decision: same-version + - root: "event:llm/retry-started" + previous: null + after: "48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3" + decision: same-version + - root: "event:model/selection" + previous: null + after: "35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc" + decision: same-version + - root: "event:permission/preset" + previous: null + after: "5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b" + decision: same-version + - root: "event:plan/mode" + previous: null + after: "a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f" + decision: same-version + - root: "event:request/context" + previous: null + after: "37cbc9cf06d494cfe5c67f078af1605eacb8e4c2c853e9e26455b73de0ee7ccf" + decision: same-version + - root: "event:request/header" + previous: null + after: "8ea13bcd983eab596dbfd41c5c2b62a0b318b71cd2a987a6fd752c6ced7e4489" + decision: same-version + - root: "event:sandbox/mode" + previous: null + after: "516da4cdd6d2f1e5ce488e648578ca51f40e458f707b803e5b24de870e799415" + decision: same-version + - root: "event:schedule/change" + previous: null + after: "2a7f86849ae54b3398ee49661a757c4fcb59a6192b7036ee2ff514617e13fb42" + decision: same-version + - root: "event:session-log-deepseek/delivery-accepted" + previous: null + after: "d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9" + decision: same-version + - root: "event:session/end-seed" + previous: null + after: "5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e" + decision: same-version + - root: "event:session/title" + previous: null + after: "1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5" + decision: same-version + - root: "event:session/title-llm-request" + previous: null + after: "6091f64d426ada2adc646094a6fe662a1b81d9ee5770929fe76800524e2c89a8" + decision: same-version + - root: "event:step/end" + previous: null + after: "e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05" + decision: same-version + - root: "event:step/start" + previous: null + after: "4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986" + decision: same-version + - root: "event:subagent/catalog" + previous: null + after: "ae1f7110feeec697b8cab42b68f7709aa7b3279764099dfb53c25890d2e5c871" + decision: same-version + - root: "event:subagent/descriptor" + previous: null + after: "b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5" + decision: same-version + - root: "event:subagent/model-selection-policy" + previous: null + after: "a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc" + decision: same-version + - root: "event:system/message" + previous: null + after: "0ae7ccdc1cc5d9d43772ebd5c7357ff509011c3a6ba188d9c274d33df2542c42" + decision: same-version + - root: "event:team/member" + previous: null + after: "4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc" + decision: same-version + - root: "event:team/message/delivered" + previous: null + after: "48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb" + decision: same-version + - root: "event:team/message/queued" + previous: null + after: "2ffcad547cb840f462eb1849fa5f6cb0e17eb93f5d07cc88b61d749b1b78d632" + decision: same-version + - root: "event:team/task" + previous: null + after: "d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a" + decision: same-version + - root: "event:todo/write" + previous: null + after: "b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26" + decision: same-version + - root: "event:tool-workflow/agent-end" + previous: null + after: "babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7" + decision: same-version + - root: "event:tool-workflow/agent-start" + previous: null + after: "5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627" + decision: same-version + - root: "event:tool-workflow/run-end" + previous: null + after: "42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f" + decision: same-version + - root: "event:tool-workflow/run-start" + previous: null + after: "c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058" + decision: same-version + - root: "event:tool/call" + previous: null + after: "3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48" + decision: same-version + - root: "event:tool/ptc-dispatch" + previous: null + after: "da807583a19f4ebe2bc9115c9686016e1521e405bdfaced296ae652b91e8e5f6" + decision: same-version + - root: "event:tool/ptc-dispatch-start" + previous: null + after: "ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93" + decision: same-version + - root: "event:tool/result" + previous: null + after: "cc3e270ab982cbafff602b753647b6f056decb7fe4fd523afd2f157a65c3d622" + decision: same-version + - root: "event:turn/end" + previous: null + after: "84c24f1209fc3e0153de6ac85d58fd6968b851f955e0e761b92321053956609e" + decision: same-version + - root: "event:turn/start" + previous: null + after: "aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8" + decision: same-version + - root: "event:user/message" + previous: null + after: "2e49ede7fb3a592cce8919ebe982f6ca1c1f67abe4def359ffc028c85e6d1ad8" + decision: same-version + - root: "event:web/deepseek-search-llm-request" + previous: null + after: "cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331" + decision: same-version +``` + + +## 兼容性 + +该记录初始化类型历史,不声明更早版本的类型关系,也不改变 Session writer 版本或运行时行为。每个根类型从空前驱开始,并保留完整的规范化 schema;后续记录引用本记录的 after schema。 + + +## 验证 + +已运行 `pnpm exec tsx scripts/gen-persistence-catalog.ts` 生成当前目录,并运行 `pnpm run persistence-changes --baseline 2026-09-11-initial` 创建此基线及其 schema 快照。 + + +## 开发备注 + +无。 diff --git a/packages/e2b/e2b/README.i18n.yaml b/docs/persistence-changes/README.i18n.yaml similarity index 56% rename from packages/e2b/e2b/README.i18n.yaml rename to docs/persistence-changes/README.i18n.yaml index 6e6c76e337..d91bd1e868 100644 --- a/packages/e2b/e2b/README.i18n.yaml +++ b/docs/persistence-changes/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/e2b/e2b/README.md -README.md: 09c23e4540b708ada5fc6d485806058793937a09 -README.zh.md: 4bf1c6f220242b366b8f09118a6fd0fee6151d11 +# pnpm run verify-translation-pairing --write docs/persistence-changes/README.md +README.md: 99cb69ca17f7368c1f1467fd22ee7ac1594532e8 +README.zh.md: ef3198426336d98ebce1f9264b378f7dbc9e2336 diff --git a/docs/persistence-changes/README.md b/docs/persistence-changes/README.md new file mode 100644 index 0000000000..99cb69ca17 --- /dev/null +++ b/docs/persistence-changes/README.md @@ -0,0 +1,70 @@ +--- +description: "Review and maintain recorded Session persistence-type changes, their schema snapshots, and compatibility decisions." +--- + +# Persistence-type change records + +English | [中文](README.zh.md) + +## Summary + +Use this reference to inspect an acknowledged Session persistence-type change and its predecessor. Each record binds a compatibility decision to exact generated schemas. Local checks compare the current source with the recorded history using only files in the checkout. Start with the [review cookbook](../cookbook/reviewing-persistence-type-changes.md) when changing a persisted type. + +## Table of Contents + +- [Files and ownership](#files-and-ownership) +- [Compatibility rules](#compatibility-rules) +- [History and limitations](#history-and-limitations) +- [Dev Note](#dev-note) + +----- + + +## Files and ownership + +The generated [catalog](../persistence-catalog.md) provides readable declarations and digests; the [schema inventory](../persistence-schema.json) contains the normalized types. Roots cover the logical Session header, the physical JSONL header line, the event envelope, and every repository-declared event. Referenced types contribute transitively to each affected root's digest. + +Each dated record has four sibling files: + +| File | Owner | +|---|---| +| `YYYY-MM-DD-slug.md` | English acknowledgement with `kind: persistence-change`, one machine declaration, compatibility reasoning, and verification evidence | +| `YYYY-MM-DD-slug.zh.md` | Chinese counterpart with the identical machine declaration | +| `YYYY-MM-DD-slug.i18n.yaml` | Generated bilingual consistency record | +| `YYYY-MM-DD-slug.schema.json` | Generated complete after schemas for the affected roots that remain present | + +The [record template](../../.agents/skills/dsh-doc/templates/persistence-change.md) defines the authored format. Record creation accepts a bilingual prose input and generates the machine declaration, snapshots, catalog pair, and consistency records. The verifier reads the machine declaration once from the English file and checks the Chinese declaration for equality. A declaration names each affected root, its predecessor record, its after digest, and its compatibility decision. A new root has no predecessor; a deletion has no after schema and retains an explicit tombstone. + + +## Compatibility rules + +Every detected structural change requires an acknowledgement. Record creation and update infer the minimum decision from these fixed rules; an explicit `--decision` is a checked assertion. The rules apply to the complete change, so an allowed change cannot hide a simultaneous breaking change. + +| Detected change | Minimum decision | +|---|---| +| Add an optional event-body property, including its complete subtree | `same-version` | +| Make a required event-body property optional | `same-version` | +| Add an ordinary event type | `same-version` | +| Make an optional property required, add a required property, change an existing type, or remove/rename a property or event | `version-bump` | +| Change the Session header or event envelope | `version-bump` | + +A same-version explanation states why old records can omit the addition and why older readers can ignore it without changing replay. For required-to-optional changes, it explains how readers handle an absent value. The checker validates the type classification; reviewers assess the explanation. A version-bump record includes the increasing header version in the same transition and follows the [Session-format procedure](../cookbook/adding-a-session-format-version.md). + + +## History and limitations + +One baseline records the complete initial inventory. Later records use their predecessor's after schema as the before schema. The verifier rejects missing predecessors, cycles, duplicate successors for one root, digest mismatches, and current roots that disagree with their latest records. Independent roots can advance independently. Two changes to the same predecessor require a single ordered history after integration. + +Accepted records describe historical transitions; preserve their machine declarations and schema snapshots when adding a successor. An unaccepted terminal record can be refreshed explicitly; the command rejects baselines and records with dependants. The tree cannot establish review acceptance. Verification proves consistency within the tree, not that history was never rewritten. No Git ref, remote service, or released checkout supplies the baseline. + +Digests describe declared persistence types, not runtime validation or behavior. Comments, source locations, alias names, and harmless declaration reordering do not affect them. Object fields, union alternatives, intersection operands, and index signatures can be reordered when their resolved types stay the same; tuple positions and numeric enum values remain significant. Catalog text and source locations may still change, so regenerate stale artifacts without adding an acknowledgement for an unchanged digest. Opaque types such as `unknown` expose no hidden structure to compare. Behavior-only changes and structures hidden inside opaque values are outside this mechanism's scope. The [decision](../../.agents/notes/implemented/process/2026-09-11-persistence-type-history.md) records these trade-offs. + + +## Dev Note + +
+Working context for maintainers — click to expand + +None. + +
diff --git a/docs/persistence-changes/README.zh.md b/docs/persistence-changes/README.zh.md new file mode 100644 index 0000000000..ef31984263 --- /dev/null +++ b/docs/persistence-changes/README.zh.md @@ -0,0 +1,70 @@ +--- +description: "审阅和维护已记录的会话持久化类型变更、对应 schema 快照以及兼容性决策。" +--- + +# 持久化类型变更记录 + +[English](README.md) | 中文 + +## 概述 + +本参考文档用于检查已确认的会话持久化类型变更及其前驱。每条记录把兼容性决策绑定到精确的生成 schema。本地检查仅使用当前检出目录中的文件,将当前源码与记录历史比较。修改持久化类型时,从[审阅实操手册](../cookbook/reviewing-persistence-type-changes.zh.md)开始。 + +## 目录 + +- [文件与职责](#files-and-ownership) +- [兼容性规则](#compatibility-rules) +- [历史与限制](#history-and-limitations) +- [开发备注](#dev-note) + +----- + + +## 文件与职责 + +生成的[目录](../persistence-catalog.zh.md)提供可读声明和摘要;[schema 清单](../persistence-schema.json)包含规范化类型。根覆盖逻辑会话头、物理 JSONL 头行、事件封装以及每个仓库内声明的事件。被引用类型的变更会传递到每个受影响根的摘要。 + +每条带日期的记录有四个同目录文件: + +| 文件 | 职责 | +|---|---| +| `YYYY-MM-DD-slug.md` | 英文确认文档,包含 `kind: persistence-change`、一份机器声明、兼容性说明和验证证据 | +| `YYYY-MM-DD-slug.zh.md` | 中文对侧文件,包含相同的机器声明 | +| `YYYY-MM-DD-slug.i18n.yaml` | 生成的双语一致性记录 | +| `YYYY-MM-DD-slug.schema.json` | 生成的完整变更后 schema,覆盖受影响且仍然存在的根 | + +[记录模板](../../.agents/skills/dsh-doc/templates/persistence-change.md)定义人工编写的格式。创建记录时可以提供双语说明输入,由命令生成机器声明、快照、目录对和一致性记录。验证器从英文文件读取一次机器声明,并检查中文声明是否相同。声明列出每个受影响的根、其前驱记录、变更后摘要和兼容性决策。新根没有前驱;删除操作没有变更后 schema,并保留显式删除标记。 + + +## 兼容性规则 + +每个检测到的结构变更都需要确认。创建和更新记录依据这些固定规则推断最低决策;显式 `--decision` 是受检查的断言。规则应用于整个变更,因此允许的变更不能掩盖同时发生的破坏性变更。 + +| 检测到的变更 | 最低决策要求 | +|---|---| +| 添加可选事件体属性,包括其完整子树 | `same-version` | +| 将必选事件体属性改为可选 | `same-version` | +| 添加普通事件类型 | `same-version` | +| 将可选属性改为必选、添加必选属性、更改已有类型,或删除/重命名属性或事件 | `version-bump` | +| 更改会话头或事件封装 | `version-bump` | + +同版本说明须解释旧记录为何可以缺少新增内容,以及旧读取器为何可以忽略它而不改变回放。对于必选改可选的变更,说明须解释读取器如何处理缺失值。检查器验证类型分类;审阅者判断说明是否成立。升版本记录在同一转换中包含递增的头部版本,并遵循[会话格式流程](../cookbook/adding-a-session-format-version.zh.md)。 + + +## 历史与限制 + +一条基线记录完整的初始清单。后续记录使用前驱的变更后 schema 作为变更前 schema。验证器拒绝缺失前驱、环、同一根的重复后继、摘要不匹配,以及当前根与最新记录不一致的情况。独立的根可以独立演进。同一前驱上的两个变更在集成后需要形成一条有序历史。 + +已接受记录描述历史转换;添加后继时须保留其机器声明和 schema 快照。尚未接受的末端记录可以显式刷新;命令拒绝基线及被依赖的记录。目录本身无法证明审阅接受状态。验证证明目录内的一致性,不证明历史从未被改写。基线不依赖 Git 引用、远端服务或已发布版本的检出目录。 + +摘要描述声明的持久化类型,不描述运行时验证或行为。注释、源码位置、别名名称和无语义变化的声明重排不影响摘要。对象字段、联合类型分支、交叉类型操作数和索引签名可以在解析类型不变时重排;元组位置和数字枚举值仍然影响摘要。目录文本和源码位置仍可能变化,因此应重新生成陈旧产物,无需为未变的摘要添加确认记录。`unknown` 等不透明类型不提供可比较的内部结构。纯行为变更以及不透明值中隐藏的结构不在本机制范围内。[决策记录](../../.agents/notes/implemented/process/2026-09-11-persistence-type-history.zh.md)说明这些取舍。 + + +## 开发备注 + +
+维护者的工作上下文——点击展开 + +无。 + +
diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json new file mode 100644 index 0000000000..4d502ac415 --- /dev/null +++ b/docs/persistence-schema.json @@ -0,0 +1,60454 @@ +{ + "formatVersion": 1, + "roots": [ + { + "key": "SessionHeader", + "kind": "header", + "digest": "2a8b114637e66faab5fae1c6e2fba65320db6224c80f5f36c5bf32f937154d03", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "agentPreset", + "type": 1, + "optional": true + }, + { + "name": "createdAt", + "type": 2, + "optional": false + }, + { + "name": "cwd", + "type": 1, + "optional": true + }, + { + "name": "delegationDepth", + "type": 2, + "optional": true + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "isSeeded", + "type": 3, + "optional": false + }, + { + "name": "origin", + "type": 4, + "optional": true + }, + { + "name": "parentSession", + "type": 1, + "optional": true + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "subagent" + }, + { + "kind": "literal", + "value": 3 + } + ] + } + }, + { + "key": "JsonlHeaderLine", + "kind": "header", + "digest": "18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "agentPreset", + "type": 1, + "optional": true + }, + { + "name": "createdAt", + "type": 2, + "optional": false + }, + { + "name": "cwd", + "type": 1, + "optional": true + }, + { + "name": "delegationDepth", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "isSeeded", + "type": 3, + "optional": false + }, + { + "name": "origin", + "type": 4, + "optional": true + }, + { + "name": "parentSession", + "type": 1, + "optional": true + }, + { + "name": "type", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "subagent" + }, + { + "kind": "literal", + "value": "session" + } + ] + } + }, + { + "key": "SessionEventEnvelope", + "kind": "envelope", + "digest": "5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 10, + 11 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 4, + "optional": true + }, + { + "name": "surfaceOp", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 6, + 7 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 3, + "optional": false + }, + { + "name": "op", + "type": 8, + "optional": false + }, + { + "name": "startSeq", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "surfaceOp", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + } + ] + } + }, + { + "key": "event:agent-preset/selected", + "kind": "event", + "event": "agent-preset/selected", + "surface": false, + "digest": "a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "agentPreset", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "agent-preset/selected" + } + ] + } + }, + { + "key": "event:agent/inbox/spliced", + "kind": "event", + "event": "agent/inbox/spliced", + "surface": false, + "digest": "f757322e25914d63799c6235af4ed4b4f36d7d083fef40d6873a45e94fe6ad65", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 32, + "optional": true + }, + { + "name": "seq", + "type": 11, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 90, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "inserted", + "type": 2, + "optional": false + }, + { + "name": "outcome", + "type": 86, + "optional": true + }, + { + "name": "removedCount", + "type": 11, + "optional": true + }, + { + "name": "start", + "type": 11, + "optional": false + }, + { + "name": "target", + "type": 87, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 29, + "optional": false + }, + { + "name": "source", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 19, + 22, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "mediaType", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 17, + "optional": true + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 20, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 31, + 41, + 43, + 44, + 47, + 52, + 54, + 59, + 60, + 61, + 63, + 65, + 66, + 73, + 75, + 77, + 79, + 81, + 84, + 85 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 32, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 7, + "optional": true + }, + { + "name": "changes", + "type": 33, + "optional": false + }, + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 34 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 35, + "optional": false + }, + { + "name": "digest", + "type": 7, + "optional": true + }, + { + "name": "path", + "type": 7, + "optional": false + }, + { + "name": "scope", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 36, + 37, + 38 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 7, + "optional": true + }, + { + "name": "kind", + "type": 29, + "optional": false + }, + { + "name": "rpcId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 7, + "optional": false + }, + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "ruleId", + "type": 7, + "optional": false + }, + { + "name": "source", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 48, + "optional": false + }, + { + "name": "form", + "type": 50, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "update", + "type": 32, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 49 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 55, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + }, + { + "name": "sections", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 58 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 64, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 50, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + }, + { + "name": "references", + "type": 68, + "optional": false + }, + { + "name": "version", + "type": 72, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 69 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 11, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 70, + "optional": false + }, + { + "name": "compacted", + "type": 23, + "optional": false + }, + { + "name": "inputIndex", + "type": 11, + "optional": false + }, + { + "name": "label", + "type": 7, + "optional": false + }, + { + "name": "omittedBytes", + "type": 11, + "optional": false + }, + { + "name": "omittedMessages", + "type": 11, + "optional": false + }, + { + "name": "originalMessages", + "type": 11, + "optional": false + }, + { + "name": "retainedMessages", + "type": 11, + "optional": false + }, + { + "name": "sessionId", + "type": 7, + "optional": false + }, + { + "name": "truncated", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 11, + 71 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 74, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 64, + "optional": false + }, + { + "name": "kind", + "type": 76, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "revision", + "type": 11, + "optional": false + }, + { + "name": "round", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "messageId", + "type": 7, + "optional": false + }, + { + "name": "senderId", + "type": 7, + "optional": false + }, + { + "name": "senderName", + "type": 7, + "optional": false + }, + { + "name": "teamId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "replayState", + "type": 83, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "canceled" + }, + { + "kind": "union", + "types": [ + 88, + 89 + ] + }, + { + "kind": "literal", + "value": "next-step" + }, + { + "kind": "literal", + "value": "next-turn" + }, + { + "kind": "literal", + "value": "agent/inbox/spliced" + } + ] + } + }, + { + "key": "event:approval/asked", + "kind": "event", + "event": "approval/asked", + "surface": false, + "digest": "3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 2, + "optional": true + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "reason", + "type": 2, + "optional": true + }, + { + "name": "toolName", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "approval/asked" + } + ] + } + }, + { + "key": "event:approval/decided", + "kind": "event", + "event": "approval/decided", + "surface": false, + "digest": "bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 8, + "optional": true + }, + { + "name": "seq", + "type": 9, + "optional": false + }, + { + "name": "time", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "outcome", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6, + 7 + ] + }, + { + "kind": "literal", + "value": "allowed-once" + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "rejected" + }, + { + "kind": "literal", + "value": "unavailable" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "approval/decided" + } + ] + } + }, + { + "key": "event:approval/policy", + "kind": "event", + "event": "approval/policy", + "surface": false, + "digest": "26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 7, + "optional": false + }, + { + "name": "time", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "policy", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "ask" + }, + { + "kind": "literal", + "value": "never" + }, + { + "kind": "literal", + "value": "delegation" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "approval/policy" + } + ] + } + }, + { + "key": "event:assistant/attempt", + "kind": "event", + "event": "assistant/attempt", + "surface": false, + "digest": "3516f54b427775f483febe8e938e4e10f9afd3583610861547bbe2999ed4d8d9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 71, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 72, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "stream", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 10, + 67, + 69 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 6, + "optional": false + }, + { + "name": "dt", + "type": 8, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "literal", + "value": "tool-call-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 11, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 12, + 14, + 39, + 42, + 44, + 46, + 63 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 15, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 16, + 18, + 27, + 30, + 34, + 36 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 20, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 25, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 21, + 22, + 23, + 24 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 28, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 31, + "optional": false + }, + { + "name": "isError", + "type": 32, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 15 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 40, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 41, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 17, + 26, + 29, + 33, + 35, + 37 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 47, + "optional": false + }, + { + "name": "replayState", + "type": 59, + "optional": true + }, + { + "name": "type", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 48, + 51, + 53, + 55, + 57 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 7, + "optional": false + }, + { + "name": "message", + "type": 7, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 2, + "optional": true + }, + { + "name": "requestId", + "type": 7, + "optional": true + }, + { + "name": "status", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 60, + "optional": true + }, + { + "name": "response", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 61 + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 64, + "optional": false + }, + { + "name": "usage", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 2, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 2, + "optional": true + }, + { + "name": "inputTokens", + "type": 2, + "optional": false + }, + { + "name": "outputTokens", + "type": 2, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 2, + "optional": true + }, + { + "name": "totalTokens", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 8, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "texts", + "type": 6, + "optional": false + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 8, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "texts", + "type": 6, + "optional": false + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-chunks" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "assistant/attempt" + } + ] + } + }, + { + "key": "event:assistant/message", + "kind": "event", + "event": "assistant/message", + "surface": true, + "digest": "a58ca1bed502d425a8b65cac02f59717498cd296de9513c6f81b83b791c20e9b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 11, + "optional": false + }, + { + "name": "surfaceOp", + "type": 76, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "interrupted", + "type": 2, + "optional": true + }, + { + "name": "message", + "type": 3, + "optional": false + }, + { + "name": "step", + "type": 11, + "optional": false + }, + { + "name": "stream", + "type": 33, + "optional": false + }, + { + "name": "turn", + "type": 11, + "optional": false + }, + { + "name": "usage", + "type": 70, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 29, + "optional": false + }, + { + "name": "source", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 19, + 22, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "mediaType", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 17, + "optional": true + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 20, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 31, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "replayState", + "type": 32, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "array", + "element": 34 + }, + { + "kind": "union", + "types": [ + 35, + 39, + 72, + 74 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 36, + "optional": false + }, + { + "name": "dt", + "type": 37, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "time0", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "array", + "element": 11 + }, + { + "kind": "literal", + "value": "tool-call-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 40, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 71, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 41, + 43, + 45, + 48, + 50, + 52, + 68 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "type", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 5, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 46, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 47, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 8, + 18, + 21, + 24, + 26, + 28 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 51, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 53, + "optional": false + }, + { + "name": "replayState", + "type": 65, + "optional": true + }, + { + "name": "type", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 54, + 57, + 59, + 61, + 63 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 55, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 7, + "optional": false + }, + { + "name": "message", + "type": 7, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 11, + "optional": true + }, + { + "name": "requestId", + "type": 7, + "optional": true + }, + { + "name": "status", + "type": 11, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 55, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 66, + "optional": true + }, + { + "name": "response", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 32 + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 69, + "optional": false + }, + { + "name": "usage", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 11, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 11, + "optional": true + }, + { + "name": "inputTokens", + "type": 11, + "optional": false + }, + { + "name": "outputTokens", + "type": 11, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 11, + "optional": true + }, + { + "name": "totalTokens", + "type": 11, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 37, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "texts", + "type": 36, + "optional": false + }, + { + "name": "time0", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 73, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 37, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "texts", + "type": 36, + "optional": false + }, + { + "name": "time0", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 75, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-chunks" + }, + { + "kind": "union", + "types": [ + 77, + 79 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 11, + "optional": false + }, + { + "name": "op", + "type": 78, + "optional": false + }, + { + "name": "startSeq", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "literal", + "value": "assistant/message" + } + ] + } + }, + { + "key": "event:command/done", + "kind": "event", + "event": "command/done", + "surface": false, + "digest": "15196447222782e773eb943c92b18316ce96b9af0f0cfddb6e57ba8274ecc5ff", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "commandId", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "sourceEventSeq", + "type": 6, + "optional": true + }, + { + "name": "text", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "literal", + "value": "success" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "command/done" + } + ] + } + }, + { + "key": "event:command/run", + "kind": "event", + "event": "command/run", + "surface": false, + "digest": "37184378c6439257d105c4e2022d80fc9c3a3f7c7f6ac661b00bc9f18d871006", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 2, + "optional": true + }, + { + "name": "commandId", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "command/run" + } + ] + } + }, + { + "key": "event:compaction/end", + "kind": "event", + "event": "compaction/end", + "surface": false, + "digest": "b0127044ab31a702bddfd785d345f5abd7a70876746e895ce443afa3e60ddf2d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 2, + "optional": false + }, + { + "name": "error", + "type": 2, + "optional": true + }, + { + "name": "sourceCommandId", + "type": 2, + "optional": true + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "compaction/end" + } + ] + } + }, + { + "key": "event:compaction/prune", + "kind": "event", + "event": "compaction/prune", + "surface": false, + "digest": "7f7fd5a6b0064f597534b29ff62ef26e786dffccf5e14f654a7d4fcea2c35f04", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "shadowedRange", + "type": 2, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 4, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 3, + "optional": false + }, + { + "name": "start", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "compaction/prune" + } + ] + } + }, + { + "key": "event:compaction/start", + "kind": "event", + "event": "compaction/start", + "surface": false, + "digest": "db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 2, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 2, + "optional": true + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "compaction/start" + } + ] + } + }, + { + "key": "event:compaction/summary", + "kind": "event", + "event": "compaction/summary", + "surface": false, + "digest": "eb1d32dcd3d76833392f351e039dcbd40686a1ab24b252a7a5003426f9cf4c89", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 32 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 3, + "optional": false + }, + { + "name": "llmStreamCall", + "type": 4, + "optional": false + }, + { + "name": "maxTokens", + "type": 5, + "optional": true + }, + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "rawOutput", + "type": 6, + "optional": false + }, + { + "name": "shadowedRange", + "type": 29, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 30, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 5, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 3, + "optional": true + }, + { + "name": "summary", + "type": 6, + "optional": false + }, + { + "name": "usage", + "type": 31, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "union", + "types": [ + 8, + 11, + 13, + 15, + 17, + 26 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "isError", + "type": 9, + "optional": true + }, + { + "name": "toolCallId", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 5, + "optional": false + }, + { + "name": "height", + "type": 5, + "optional": false + }, + { + "name": "mediaType", + "type": 19, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "originalDimensions", + "type": 24, + "optional": true + }, + { + "name": "width", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 20, + 21, + 22, + 23 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 5, + "optional": false + }, + { + "name": "width", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 27, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 5, + "optional": false + }, + { + "name": "start", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 5, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 5, + "optional": true + }, + { + "name": "inputTokens", + "type": 5, + "optional": false + }, + { + "name": "outputTokens", + "type": 5, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 5, + "optional": true + }, + { + "name": "totalTokens", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 3, + "optional": false + }, + { + "name": "maxTokens", + "type": 5, + "optional": true + }, + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "rawOutput", + "type": 6, + "optional": true + }, + { + "name": "shadowedRange", + "type": 29, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 30, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 5, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 3, + "optional": true + }, + { + "name": "summary", + "type": 6, + "optional": false + }, + { + "name": "usage", + "type": 31, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "compaction/summary" + } + ] + } + }, + { + "key": "event:deliverables/presented", + "kind": "event", + "event": "deliverables/presented", + "surface": false, + "digest": "13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 2, + "optional": false + }, + { + "name": "files", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 2, + "optional": true + }, + { + "name": "path", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "deliverables/presented" + } + ] + } + }, + { + "key": "event:feedback/message-delete", + "kind": "event", + "event": "feedback/message-delete", + "surface": false, + "digest": "3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "messageId", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "feedback/message-delete" + } + ] + } + }, + { + "key": "event:feedback/message-put", + "kind": "event", + "event": "feedback/message-put", + "surface": false, + "digest": "b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 16, + "optional": true + }, + { + "name": "seq", + "type": 11, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "item", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "category", + "type": 3, + "optional": true + }, + { + "name": "createdAt", + "type": 11, + "optional": false + }, + { + "name": "messageId", + "type": 12, + "optional": false + }, + { + "name": "note", + "type": 12, + "optional": true + }, + { + "name": "rating", + "type": 13, + "optional": false + }, + { + "name": "updatedAt", + "type": 11, + "optional": false + }, + { + "name": "version", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ] + }, + { + "kind": "literal", + "value": "instruction-following" + }, + { + "kind": "literal", + "value": "other" + }, + { + "kind": "literal", + "value": "product-interaction" + }, + { + "kind": "literal", + "value": "resource-cost" + }, + { + "kind": "literal", + "value": "security-privacy-permission" + }, + { + "kind": "literal", + "value": "service-stability" + }, + { + "kind": "literal", + "value": "task-result" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "negative" + }, + { + "kind": "literal", + "value": "positive" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "feedback/message-put" + } + ] + } + }, + { + "key": "event:feedback/record", + "kind": "event", + "event": "feedback/record", + "surface": false, + "digest": "b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 11, + "optional": true + }, + { + "name": "seq", + "type": 12, + "optional": false + }, + { + "name": "time", + "type": 12, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "category", + "type": 2, + "optional": true + }, + { + "name": "text", + "type": 10, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + { + "kind": "literal", + "value": "instruction-following" + }, + { + "kind": "literal", + "value": "other" + }, + { + "kind": "literal", + "value": "product-interaction" + }, + { + "kind": "literal", + "value": "resource-cost" + }, + { + "kind": "literal", + "value": "security-privacy-permission" + }, + { + "kind": "literal", + "value": "service-stability" + }, + { + "kind": "literal", + "value": "task-result" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "feedback/record" + } + ] + } + }, + { + "key": "event:goal/change", + "kind": "event", + "event": "goal/change", + "surface": false, + "digest": "763c8a20af487263a0080548274f7437ef4a86e0ba8769127d1ced10a72c664d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 23, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 9 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "cleared", + "type": 3, + "optional": false + }, + { + "name": "clearedAt", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "operation", + "type": 7, + "optional": false + }, + { + "name": "version", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "revision", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "goal/change" + }, + { + "kind": "literal", + "value": "clear" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "createdAt", + "type": 5, + "optional": false + }, + { + "name": "goal", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "operation", + "type": 17, + "optional": false + }, + { + "name": "roundsStarted", + "type": 5, + "optional": false + }, + { + "name": "updatedAt", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "blockedReason", + "type": 11, + "optional": true + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "maxGoalRounds", + "type": 5, + "optional": false + }, + { + "name": "objective", + "type": 4, + "optional": false + }, + { + "name": "phase", + "type": 12, + "optional": false + }, + { + "name": "revision", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 4, + "optional": false + }, + { + "name": "message", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "literal", + "value": "complete" + }, + { + "kind": "literal", + "value": "paused" + }, + { + "kind": "union", + "types": [ + 15, + 18, + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "block" + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "literal", + "value": "resume" + }, + { + "kind": "literal", + "value": "edit" + }, + { + "kind": "literal", + "value": "pause" + }, + { + "kind": "literal", + "value": true + } + ] + } + }, + { + "key": "event:hook/invoked", + "kind": "event", + "event": "hook/invoked", + "surface": false, + "digest": "8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "dialect", + "type": 2, + "optional": false + }, + { + "name": "handlerId", + "type": 5, + "optional": false + }, + { + "name": "matcher", + "type": 5, + "optional": true + }, + { + "name": "point", + "type": 5, + "optional": false + }, + { + "name": "turn", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "claude-code" + }, + { + "kind": "literal", + "value": "codex" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "hook/invoked" + } + ] + } + }, + { + "key": "event:hook/result", + "kind": "event", + "event": "hook/result", + "surface": false, + "digest": "e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "decision", + "type": 2, + "optional": false + }, + { + "name": "durationMs", + "type": 3, + "optional": false + }, + { + "name": "exitCode", + "type": 3, + "optional": true + }, + { + "name": "handlerId", + "type": 2, + "optional": false + }, + { + "name": "point", + "type": 2, + "optional": false + }, + { + "name": "stderrSummary", + "type": 2, + "optional": true + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "hook/result" + } + ] + } + }, + { + "key": "event:llm/retry", + "kind": "event", + "event": "llm/retry", + "surface": false, + "digest": "91c397f8f870e812e1dc5ac69c2745f9f105be9dc97dadc980d19ef415a65145", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 9, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 7 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "delayMs", + "type": 3, + "optional": false + }, + { + "name": "failure", + "type": 4, + "optional": false + }, + { + "name": "maxRetries", + "type": 3, + "optional": false + }, + { + "name": "mode", + "type": 6, + "optional": false + }, + { + "name": "policyKey", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "retry", + "type": 3, + "optional": false + }, + { + "name": "retryId", + "type": 5, + "optional": false + }, + { + "name": "step", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 5, + "optional": false + }, + { + "name": "message", + "type": 5, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 3, + "optional": true + }, + { + "name": "requestId", + "type": 5, + "optional": true + }, + { + "name": "status", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "normal" + }, + { + "kind": "object", + "properties": [ + { + "name": "delayMs", + "type": 3, + "optional": false + }, + { + "name": "failure", + "type": 4, + "optional": false + }, + { + "name": "mode", + "type": 8, + "optional": false + }, + { + "name": "policyKey", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "retry", + "type": 3, + "optional": false + }, + { + "name": "retryId", + "type": 5, + "optional": false + }, + { + "name": "step", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "always" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "llm/retry" + } + ] + } + }, + { + "key": "event:llm/retry-started", + "kind": "event", + "event": "llm/retry-started", + "surface": false, + "digest": "48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "retry", + "type": 2, + "optional": false + }, + { + "name": "retryId", + "type": 3, + "optional": false + }, + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "llm/retry-started" + } + ] + } + }, + { + "key": "event:model/selection", + "kind": "event", + "event": "model/selection", + "surface": false, + "digest": "35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "provider", + "type": 2, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "model/selection" + } + ] + } + }, + { + "key": "event:permission/preset", + "kind": "event", + "event": "permission/preset", + "surface": false, + "digest": "5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "preset", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "permission/preset" + } + ] + } + }, + { + "key": "event:plan/mode", + "kind": "event", + "event": "plan/mode", + "surface": false, + "digest": "a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "active", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "plan/mode" + } + ] + } + }, + { + "key": "event:request/context", + "kind": "event", + "event": "request/context", + "surface": false, + "digest": "37cbc9cf06d494cfe5c67f078af1605eacb8e4c2c853e9e26455b73de0ee7ccf", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "contextWindow", + "type": 2, + "optional": true + }, + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "systemPromptUpdate", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "in-history" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "request/context" + } + ] + } + }, + { + "key": "event:request/header", + "kind": "event", + "event": "request/header", + "surface": false, + "digest": "8ea13bcd983eab596dbfd41c5c2b62a0b318b71cd2a987a6fd752c6ced7e4489", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "header", + "type": 2, + "optional": false + }, + { + "name": "reason", + "type": 13, + "optional": false + }, + { + "name": "startsSeries", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "adapterDefaults", + "type": 3, + "optional": true + }, + { + "name": "config", + "type": 5, + "optional": false + }, + { + "name": "tools", + "type": 9, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 4, + "optional": true + }, + { + "name": "reasoningEffort", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 6, + "optional": true + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 7, + "optional": true + }, + { + "name": "stop", + "type": 8, + "optional": true + }, + { + "name": "temperature", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "array", + "element": 10 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "parameters", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 7, + "value": 12 + } + ] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "union", + "types": [ + 14, + 15, + 16, + 17 + ] + }, + { + "kind": "literal", + "value": "initial" + }, + { + "kind": "literal", + "value": "resume" + }, + { + "kind": "literal", + "value": "series" + }, + { + "kind": "literal", + "value": "change" + }, + { + "kind": "literal", + "value": "request/header" + } + ] + } + }, + { + "key": "event:sandbox/mode", + "kind": "event", + "event": "sandbox/mode", + "surface": false, + "digest": "516da4cdd6d2f1e5ce488e648578ca51f40e458f707b803e5b24de870e799415", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 8, + "optional": false + }, + { + "name": "time", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "mode", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "danger-full-access" + }, + { + "kind": "literal", + "value": "read-only" + }, + { + "kind": "literal", + "value": "workspace-write" + }, + { + "kind": "literal", + "value": "delegation" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "sandbox/mode" + } + ] + } + }, + { + "key": "event:schedule/change", + "kind": "event", + "event": "schedule/change", + "surface": false, + "digest": "2a7f86849ae54b3398ee49661a757c4fcb59a6192b7036ee2ff514617e13fb42", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 19, + "optional": true + }, + { + "name": "seq", + "type": 13, + "optional": false + }, + { + "name": "time", + "type": 13, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 6, + 8, + 9 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "acceptedAt", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "operation", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "dispatch" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "operation", + "type": 7, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "delete" + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "operation", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "operation", + "type": 10, + "optional": false + }, + { + "name": "schedule", + "type": 11, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "union", + "types": [ + 12, + 15, + 17 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "afterSeconds", + "type": 13, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 14, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "after" + }, + { + "kind": "object", + "properties": [ + { + "name": "everySeconds", + "type": 13, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 16, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "every" + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 18, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "at" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "schedule/change" + } + ] + } + }, + { + "key": "event:session-log-deepseek/delivery-accepted", + "kind": "event", + "event": "session-log-deepseek/delivery-accepted", + "surface": false, + "digest": "d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "sessionFormatVersion", + "type": 2, + "optional": true + }, + { + "name": "sessionId", + "type": 3, + "optional": false + }, + { + "name": "throughSeq", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "session-log-deepseek/delivery-accepted" + } + ] + } + }, + { + "key": "event:session/end-seed", + "kind": "event", + "event": "session/end-seed", + "surface": false, + "digest": "5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "inherited", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "session/end-seed" + } + ] + } + }, + { + "key": "event:session/title", + "kind": "event", + "event": "session/title", + "surface": false, + "digest": "1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 13, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "messageSeqs", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 4, + "optional": false + }, + { + "name": "title", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 5, + 9, + 11 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": true + }, + { + "name": "provider", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "provider" + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 8, + "optional": false + }, + { + "name": "provider", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "fallback" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "session/title" + } + ] + } + }, + { + "key": "event:session/title-llm-request", + "kind": "event", + "event": "session/title-llm-request", + "surface": false, + "digest": "6091f64d426ada2adc646094a6fe662a1b81d9ee5770929fe76800524e2c89a8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 36, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 91, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 2, + "optional": false + }, + { + "name": "messageSeqs", + "type": 3, + "optional": false + }, + { + "name": "messages", + "type": 4, + "optional": false + }, + { + "name": "route", + "type": 90, + "optional": false + }, + { + "name": "system", + "type": 9, + "optional": false + }, + { + "name": "titleProvider", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 9, + "optional": false + }, + { + "name": "role", + "type": 30, + "optional": false + }, + { + "name": "source", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "union", + "types": [ + 8, + 11, + 20, + 23, + 26, + 28 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 9, + "optional": false + }, + { + "name": "id", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 12, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 9, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 13, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": true + }, + { + "name": "originalDimensions", + "type": 18, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 14, + 15, + 16, + 17 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 21, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 9, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "isError", + "type": 24, + "optional": true + }, + { + "name": "toolCallId", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "union", + "types": [ + 31, + 32, + 33 + ] + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 35, + 45, + 47, + 48, + 51, + 56, + 58, + 63, + 64, + 65, + 67, + 69, + 70, + 77, + 79, + 81, + 83, + 85, + 88, + 89 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 36, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 9, + "optional": true + }, + { + "name": "changes", + "type": 37, + "optional": false + }, + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 38 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 39, + "optional": false + }, + { + "name": "digest", + "type": 9, + "optional": true + }, + { + "name": "path", + "type": 9, + "optional": false + }, + { + "name": "scope", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 40, + 41, + 42 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 9, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 9, + "optional": true + }, + { + "name": "kind", + "type": 33, + "optional": false + }, + { + "name": "rpcId", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 9, + "optional": false + }, + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "provider", + "type": 9, + "optional": false + }, + { + "name": "ruleId", + "type": 9, + "optional": false + }, + { + "name": "source", + "type": 9, + "optional": false + }, + { + "name": "summary", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 52, + "optional": false + }, + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "update", + "type": 36, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 53 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 59, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + }, + { + "name": "sections", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 62 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 9, + "optional": false + }, + { + "name": "text", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + }, + { + "name": "summary", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 66, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 68, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 66, + "optional": false + }, + { + "name": "kind", + "type": 71, + "optional": false + }, + { + "name": "references", + "type": 72, + "optional": false + }, + { + "name": "version", + "type": 76, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 73 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 2, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 74, + "optional": false + }, + { + "name": "compacted", + "type": 24, + "optional": false + }, + { + "name": "inputIndex", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 9, + "optional": false + }, + { + "name": "omittedBytes", + "type": 2, + "optional": false + }, + { + "name": "omittedMessages", + "type": 2, + "optional": false + }, + { + "name": "originalMessages", + "type": 2, + "optional": false + }, + { + "name": "retainedMessages", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 9, + "optional": false + }, + { + "name": "truncated", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 75 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "senderSessionId", + "type": 9, + "optional": false + }, + { + "name": "summary", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 68, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "senderSessionId", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 9, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "revision", + "type": 2, + "optional": false + }, + { + "name": "round", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 84, + "optional": false + }, + { + "name": "messageId", + "type": 9, + "optional": false + }, + { + "name": "senderId", + "type": 9, + "optional": false + }, + { + "name": "senderName", + "type": 9, + "optional": false + }, + { + "name": "teamId", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 86, + "optional": false + }, + { + "name": "model", + "type": 9, + "optional": false + }, + { + "name": "provider", + "type": 9, + "optional": false + }, + { + "name": "replayState", + "type": 87, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 9, + "optional": false + }, + { + "name": "provider", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session/title-llm-request" + } + ] + } + }, + { + "key": "event:step/end", + "kind": "event", + "event": "step/end", + "surface": false, + "digest": "e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "step/end" + } + ] + } + }, + { + "key": "event:step/start", + "kind": "event", + "event": "step/start", + "surface": false, + "digest": "4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "step/start" + } + ] + } + }, + { + "key": "event:subagent/catalog", + "kind": "event", + "event": "subagent/catalog", + "surface": false, + "digest": "ae1f7110feeec697b8cab42b68f7709aa7b3279764099dfb53c25890d2e5c871", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 9, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 7 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": false + }, + { + "name": "mode", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "literal", + "value": 0 + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": true + }, + { + "name": "mode", + "type": 8, + "optional": false + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "one-shot" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "subagent/catalog" + } + ] + } + }, + { + "key": "event:subagent/descriptor", + "kind": "event", + "event": "subagent/descriptor", + "surface": false, + "digest": "b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 10, + "optional": true + }, + { + "name": "seq", + "type": 7, + "optional": false + }, + { + "name": "time", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 8 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "agentModel", + "type": 3, + "optional": true + }, + { + "name": "agentProvider", + "type": 3, + "optional": true + }, + { + "name": "agentReasoningEffort", + "type": 3, + "optional": true + }, + { + "name": "label", + "type": 3, + "optional": false + }, + { + "name": "mode", + "type": 4, + "optional": false + }, + { + "name": "persona", + "type": 3, + "optional": true + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "toolFilter", + "type": 5, + "optional": true + }, + { + "name": "version", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "object", + "properties": [ + { + "name": "allow", + "type": 6, + "optional": true + }, + { + "name": "deny", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "label", + "type": 3, + "optional": true + }, + { + "name": "mode", + "type": 9, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "version", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "one-shot" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "subagent/descriptor" + } + ] + } + }, + { + "key": "event:subagent/model-selection-policy", + "kind": "event", + "event": "subagent/model-selection-policy", + "surface": false, + "digest": "a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "allowedModels", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 4, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "subagent/model-selection-policy" + } + ] + } + }, + { + "key": "event:system/message", + "kind": "event", + "event": "system/message", + "surface": true, + "digest": "0ae7ccdc1cc5d9d43772ebd5c7357ff509011c3a6ba188d9c274d33df2542c42", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 46, + "optional": true + }, + { + "name": "seq", + "type": 10, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 47, + "optional": true + }, + { + "name": "surfaceOp", + "type": 48, + "optional": false + }, + { + "name": "time", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "step", + "type": 10, + "optional": false + }, + { + "name": "turn", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "role", + "type": 28, + "optional": false + }, + { + "name": "source", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 8, + 18, + 21, + 24, + 26 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "mediaType", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "originalDimensions", + "type": 16, + "optional": true + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 12, + 13, + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 22, + "optional": true + }, + { + "name": "toolCallId", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "union", + "types": [ + 30, + 35, + 37, + 39, + 41, + 43, + 45 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 31, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + }, + { + "name": "sections", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 34 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "text", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 36, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + }, + { + "name": "summary", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 38, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 44, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 10 + }, + { + "kind": "union", + "types": [ + 49, + 51 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 10, + "optional": false + }, + { + "name": "op", + "type": 50, + "optional": false + }, + { + "name": "startSeq", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "literal", + "value": "system/message" + } + ] + } + }, + { + "key": "event:team/member", + "kind": "event", + "event": "team/member", + "surface": false, + "digest": "4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 12, + "optional": true + }, + { + "name": "seq", + "type": 13, + "optional": false + }, + { + "name": "time", + "type": 13, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "member", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 6, + "optional": false + }, + { + "name": "version", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "context", + "type": 3, + "optional": false + }, + { + "name": "description", + "type": 6, + "optional": false + }, + { + "name": "error", + "type": 6, + "optional": true + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "phase", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "fork" + }, + { + "kind": "literal", + "value": "fresh" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 8, + 9, + 10 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "failed" + }, + { + "kind": "literal", + "value": "provisioning" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "team/member" + } + ] + } + }, + { + "key": "event:team/message/delivered", + "kind": "event", + "event": "team/message/delivered", + "surface": false, + "digest": "48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "messageId", + "type": 2, + "optional": false + }, + { + "name": "targetId", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 2, + "optional": false + }, + { + "name": "version", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "team/message/delivered" + } + ] + } + }, + { + "key": "event:team/message/queued", + "kind": "event", + "event": "team/message/queued", + "surface": false, + "digest": "2ffcad547cb840f462eb1849fa5f6cb0e17eb93f5d07cc88b61d749b1b78d632", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 29, + "optional": true + }, + { + "name": "seq", + "type": 17, + "optional": false + }, + { + "name": "time", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 7, + "optional": false + }, + { + "name": "version", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "senderId", + "type": 7, + "optional": false + }, + { + "name": "senderName", + "type": 7, + "optional": false + }, + { + "name": "targetId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 9, + 11, + 13, + 15, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 6, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "team/message/queued" + } + ] + } + }, + { + "key": "event:team/task", + "kind": "event", + "event": "team/task", + "surface": false, + "digest": "d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 12, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "task", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "blockedBy", + "type": 3, + "optional": false + }, + { + "name": "description", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "ownerId", + "type": 4, + "optional": true + }, + { + "name": "revision", + "type": 5, + "optional": false + }, + { + "name": "status", + "type": 6, + "optional": false + }, + { + "name": "subject", + "type": 4, + "optional": false + }, + { + "name": "writeScopes", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 7, + 8, + 9, + 10 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "deleted" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "team/task" + } + ] + } + }, + { + "key": "event:todo/write", + "kind": "event", + "event": "todo/write", + "surface": false, + "digest": "b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 9, + "optional": true + }, + { + "name": "seq", + "type": 10, + "optional": false + }, + { + "name": "time", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "todos", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "status", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 6, + 7, + 8 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "todo/write" + } + ] + } + }, + { + "key": "event:tool-workflow/agent-end", + "kind": "event", + "event": "tool-workflow/agent-end", + "surface": false, + "digest": "babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 8, + "optional": true + }, + { + "name": "seq", + "type": 7, + "optional": false + }, + { + "name": "time", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "outcome", + "type": 2, + "optional": false + }, + { + "name": "runId", + "type": 6, + "optional": false + }, + { + "name": "seq", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "failed" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool-workflow/agent-end" + } + ] + } + }, + { + "key": "event:tool-workflow/agent-start", + "kind": "event", + "event": "tool-workflow/agent-start", + "surface": false, + "digest": "5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "childId", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 2, + "optional": false + }, + { + "name": "phase", + "type": 2, + "optional": true + }, + { + "name": "runId", + "type": 2, + "optional": false + }, + { + "name": "seq", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool-workflow/agent-start" + } + ] + } + }, + { + "key": "event:tool-workflow/run-end", + "kind": "event", + "event": "tool-workflow/run-end", + "surface": false, + "digest": "42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 8, + "optional": false + }, + { + "name": "time", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "runId", + "type": 2, + "optional": false + }, + { + "name": "stopReason", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6 + ] + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-workflow/run-end" + } + ] + } + }, + { + "key": "event:tool-workflow/run-start", + "kind": "event", + "event": "tool-workflow/run-start", + "surface": false, + "digest": "c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "runId", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-workflow/run-start" + } + ] + } + }, + { + "key": "event:tool/call", + "kind": "event", + "event": "tool/call", + "surface": false, + "digest": "3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "callId", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "step", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool/call" + } + ] + } + }, + { + "key": "event:tool/ptc-dispatch", + "kind": "event", + "event": "tool/ptc-dispatch", + "surface": false, + "digest": "da807583a19f4ebe2bc9115c9686016e1521e405bdfaced296ae652b91e8e5f6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 28, + "optional": true + }, + { + "name": "seq", + "type": 17, + "optional": false + }, + { + "name": "time", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "parentCallId", + "type": 7, + "optional": false + }, + { + "name": "rootCallId", + "type": 7, + "optional": false + }, + { + "name": "subCallId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 9, + 11, + 13, + 15, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 6, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool/ptc-dispatch" + } + ] + } + }, + { + "key": "event:tool/ptc-dispatch-start", + "kind": "event", + "event": "tool/ptc-dispatch-start", + "surface": false, + "digest": "ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "parentCallId", + "type": 3, + "optional": false + }, + { + "name": "rootCallId", + "type": 3, + "optional": false + }, + { + "name": "subCallId", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool/ptc-dispatch-start" + } + ] + } + }, + { + "key": "event:tool/result", + "kind": "event", + "event": "tool/result", + "surface": true, + "digest": "cc3e270ab982cbafff602b753647b6f056decb7fe4fd523afd2f157a65c3d622", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 37, + "optional": true + }, + { + "name": "seq", + "type": 17, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 38, + "optional": true + }, + { + "name": "surfaceOp", + "type": 39, + "optional": false + }, + { + "name": "time", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "error", + "type": 2, + "optional": true + }, + { + "name": "message", + "type": 4, + "optional": false + }, + { + "name": "meta", + "type": 33, + "optional": true + }, + { + "name": "step", + "type": 17, + "optional": false + }, + { + "name": "turn", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "role", + "type": 30, + "optional": false + }, + { + "name": "source", + "type": 31, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 6, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "isError", + "type": 28, + "optional": true + }, + { + "name": "toolCallId", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 8 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 11, + 13, + 15, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "union", + "types": [ + 3, + 17, + 28, + 34, + 35, + 36 + ] + }, + { + "kind": "array", + "element": 33 + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 3, + "value": 33 + } + ] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 17 + }, + { + "kind": "union", + "types": [ + 40, + 41 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 17, + "optional": false + }, + { + "name": "op", + "type": 42, + "optional": false + }, + { + "name": "startSeq", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "tool/result" + } + ] + } + }, + { + "key": "event:turn/end", + "kind": "event", + "event": "turn/end", + "surface": false, + "digest": "84c24f1209fc3e0153de6ac85d58fd6968b851f955e0e761b92321053956609e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 29, + "optional": true + }, + { + "name": "seq", + "type": 14, + "optional": false + }, + { + "name": "time", + "type": 14, + "optional": false + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 5, + 7, + 9, + 11, + 16 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "interrupted" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "error", + "type": 12, + "optional": false + }, + { + "name": "kind", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 13, + "optional": false + }, + { + "name": "message", + "type": 13, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 14, + "optional": true + }, + { + "name": "requestId", + "type": 13, + "optional": true + }, + { + "name": "status", + "type": 14, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 17, + "optional": false + }, + { + "name": "reason", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "union", + "types": [ + 19, + 21, + 23, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "disposed" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "legacy" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "parent" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "reason", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hook" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "turn/end" + } + ] + } + }, + { + "key": "event:turn/start", + "kind": "event", + "event": "turn/start", + "surface": false, + "digest": "aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "turn/start" + } + ] + } + }, + { + "key": "event:user/message", + "kind": "event", + "event": "user/message", + "surface": true, + "digest": "2e49ede7fb3a592cce8919ebe982f6ca1c1f67abe4def359ffc028c85e6d1ad8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 30, + "optional": true + }, + { + "name": "seq", + "type": 9, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 84, + "optional": true + }, + { + "name": "surfaceOp", + "type": 85, + "optional": false + }, + { + "name": "time", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 88, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "role", + "type": 27, + "optional": false + }, + { + "name": "source", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 4, + 7, + 17, + 20, + 23, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "mediaType", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 15, + "optional": true + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 11, + 12, + 13, + 14 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "isError", + "type": 21, + "optional": true + }, + { + "name": "toolCallId", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 29, + 39, + 41, + 42, + 45, + 50, + 52, + 57, + 58, + 59, + 61, + 63, + 64, + 71, + 73, + 75, + 77, + 79, + 82, + 83 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 30, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 5, + "optional": true + }, + { + "name": "changes", + "type": 31, + "optional": false + }, + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 32 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 33, + "optional": false + }, + { + "name": "digest", + "type": 5, + "optional": true + }, + { + "name": "path", + "type": 5, + "optional": false + }, + { + "name": "scope", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 34, + 35, + 36 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 5, + "optional": true + }, + { + "name": "kind", + "type": 27, + "optional": false + }, + { + "name": "rpcId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 5, + "optional": false + }, + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "ruleId", + "type": 5, + "optional": false + }, + { + "name": "source", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 46, + "optional": false + }, + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + }, + { + "name": "update", + "type": 30, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 47 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 53, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "sections", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 56 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "text", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 60, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 60, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + }, + { + "name": "references", + "type": 66, + "optional": false + }, + { + "name": "version", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 67 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 9, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 68, + "optional": false + }, + { + "name": "compacted", + "type": 21, + "optional": false + }, + { + "name": "inputIndex", + "type": 9, + "optional": false + }, + { + "name": "label", + "type": 5, + "optional": false + }, + { + "name": "omittedBytes", + "type": 9, + "optional": false + }, + { + "name": "omittedMessages", + "type": 9, + "optional": false + }, + { + "name": "originalMessages", + "type": 9, + "optional": false + }, + { + "name": "retainedMessages", + "type": 9, + "optional": false + }, + { + "name": "sessionId", + "type": 5, + "optional": false + }, + { + "name": "truncated", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 9, + 69 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 72, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 74, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 76, + "optional": false + }, + { + "name": "revision", + "type": 9, + "optional": false + }, + { + "name": "round", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "messageId", + "type": 5, + "optional": false + }, + { + "name": "senderId", + "type": 5, + "optional": false + }, + { + "name": "senderName", + "type": 5, + "optional": false + }, + { + "name": "teamId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "model", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "replayState", + "type": 81, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 9 + }, + { + "kind": "union", + "types": [ + 86, + 87 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 9, + "optional": false + }, + { + "name": "op", + "type": 35, + "optional": false + }, + { + "name": "startSeq", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user/message" + } + ] + } + }, + { + "key": "event:web/deepseek-search-llm-request", + "kind": "event", + "event": "web/deepseek-search-llm-request", + "surface": false, + "digest": "cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 15, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "apiVersion", + "type": 2, + "optional": false + }, + { + "name": "body", + "type": 3, + "optional": false + }, + { + "name": "endpoint", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "max_tokens", + "type": 4, + "optional": false + }, + { + "name": "messages", + "type": 5, + "optional": false + }, + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "tools", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "tuple", + "elements": [ + { + "type": 6, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 8, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "tuple", + "elements": [ + { + "type": 12, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "max_uses", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 13, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "web_search" + }, + { + "kind": "literal", + "value": "web_search_20250305" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "web/deepseek-search-llm-request" + } + ] + } + } + ], + "types": [ + { + "digest": "0116b1f89c05221d56b0ba35c83d302bf4857bc7277d247e9bbaf74b27d2c66e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "web_search_20250305" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "023a355b2be0d688fb6e4df8388dadb942761d46cfe96d0f499aae9631a159d4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "blocked" + } + ] + }, + "names": [ + "event:turn/end.data.reason[2]" + ], + "sources": [ + "packages/core/session/src/types.ts:205" + ] + }, + { + "digest": "0341d4c3dfcd53253cdc1e789d9130d3cd848b6f8b0706bf92f313a68b39ff23", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "product-interaction" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "05d2ae1e0c7f8770f83f9a09047afb39a335d8ac986df3193659aa1e581472cf", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2 + ] + }, + { + "kind": "literal", + "value": "ask" + }, + { + "kind": "literal", + "value": "never" + } + ] + }, + "names": [ + "packages/interaction/user-approval/src/index.ts#ApprovalPolicy" + ], + "sources": [ + "packages/interaction/user-approval/src/index.ts:60" + ] + }, + { + "digest": "0657d3ca5ac4e7bf79b00bcf860b1c7159d01a9da137497221ec2f1776e365f7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "packages/core/session/src/types.ts#SessionSeq" + ], + "sources": [ + "packages/core/session/src/types.ts:31" + ] + }, + { + "digest": "07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 1, + "optional": false + }, + { + "name": "provider", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/session/session-title/src/types.ts#SessionTitleModelIdentity", + "packages/subagent/tool-subagent/src/model-selection.ts#AllowedModelRoute" + ], + "sources": [ + "packages/session/session-title/src/types.ts:20", + "packages/subagent/tool-subagent/src/model-selection.ts:9" + ] + }, + { + "digest": "07eccc9ea2f9b5439d76cc77d5e062936677d43f07a23da749819b0a22ee36b7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "security-privacy-permission" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "08936f7648f573f7539800e1ec2852fbbcba05e1a20487a1e5acf2a0fce1e2de", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 1, + "optional": false + }, + { + "name": "messageSeqs", + "type": 2, + "optional": false + }, + { + "name": "messages", + "type": 3, + "optional": false + }, + { + "name": "route", + "type": 89, + "optional": false + }, + { + "name": "system", + "type": 8, + "optional": false + }, + { + "name": "titleProvider", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 1 + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 8, + "optional": false + }, + { + "name": "role", + "type": 29, + "optional": false + }, + { + "name": "source", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 6 + }, + { + "kind": "union", + "types": [ + 7, + 10, + 19, + 22, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 8, + "optional": false + }, + { + "name": "id", + "type": 8, + "optional": false + }, + { + "name": "name", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 8, + "optional": false + }, + { + "name": "bytes", + "type": 1, + "optional": false + }, + { + "name": "height", + "type": 1, + "optional": false + }, + { + "name": "mediaType", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 8, + "optional": true + }, + { + "name": "originalDimensions", + "type": 17, + "optional": true + }, + { + "name": "width", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 1, + "optional": false + }, + { + "name": "width", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 20, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 8, + "optional": false + }, + { + "name": "bytes", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 5, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "union", + "types": [ + 30, + 31, + 32 + ] + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 34, + 44, + 46, + 47, + 50, + 55, + 57, + 62, + 63, + 64, + 66, + 68, + 69, + 76, + 78, + 80, + 82, + 84, + 87, + 88 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 35, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 8, + "optional": true + }, + { + "name": "changes", + "type": 36, + "optional": false + }, + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 37 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 38, + "optional": false + }, + { + "name": "digest", + "type": 8, + "optional": true + }, + { + "name": "path", + "type": 8, + "optional": false + }, + { + "name": "scope", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 39, + 40, + 41 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 8, + "optional": false + }, + { + "name": "kind", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 8, + "optional": true + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "rpcId", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 8, + "optional": false + }, + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + }, + { + "name": "provider", + "type": 8, + "optional": false + }, + { + "name": "ruleId", + "type": 8, + "optional": false + }, + { + "name": "source", + "type": 8, + "optional": false + }, + { + "name": "summary", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 51, + "optional": false + }, + { + "name": "form", + "type": 53, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "update", + "type": 35, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 52 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 8, + "optional": false + }, + { + "name": "name", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "name", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 58, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "plugin", + "type": 8, + "optional": false + }, + { + "name": "sections", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 61 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 8, + "optional": false + }, + { + "name": "text", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "plugin", + "type": 8, + "optional": false + }, + { + "name": "summary", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "plugin", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 65, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "plugin", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 67, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "plugin", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 53, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "plugin", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 65, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + }, + { + "name": "references", + "type": 71, + "optional": false + }, + { + "name": "version", + "type": 75, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 72 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 1, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 73, + "optional": false + }, + { + "name": "compacted", + "type": 23, + "optional": false + }, + { + "name": "inputIndex", + "type": 1, + "optional": false + }, + { + "name": "label", + "type": 8, + "optional": false + }, + { + "name": "omittedBytes", + "type": 1, + "optional": false + }, + { + "name": "omittedMessages", + "type": 1, + "optional": false + }, + { + "name": "originalMessages", + "type": 1, + "optional": false + }, + { + "name": "retainedMessages", + "type": 1, + "optional": false + }, + { + "name": "sessionId", + "type": 8, + "optional": false + }, + { + "name": "truncated", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 1, + 74 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 77, + "optional": false + }, + { + "name": "senderSessionId", + "type": 8, + "optional": false + }, + { + "name": "summary", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 67, + "optional": false + }, + { + "name": "kind", + "type": 79, + "optional": false + }, + { + "name": "senderSessionId", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 8, + "optional": false + }, + { + "name": "kind", + "type": 81, + "optional": false + }, + { + "name": "revision", + "type": 1, + "optional": false + }, + { + "name": "round", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 83, + "optional": false + }, + { + "name": "messageId", + "type": 8, + "optional": false + }, + { + "name": "senderId", + "type": 8, + "optional": false + }, + { + "name": "senderName", + "type": 8, + "optional": false + }, + { + "name": "teamId", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 85, + "optional": false + }, + { + "name": "model", + "type": 8, + "optional": false + }, + { + "name": "provider", + "type": 8, + "optional": false + }, + { + "name": "replayState", + "type": 86, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "plugin", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 8, + "optional": false + }, + { + "name": "provider", + "type": 8, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData" + ], + "sources": [ + "packages/session/session-title-llm/src/index.ts:27" + ] + }, + { + "digest": "08a1293eddb9b633bfefaf0893e3b4c807dfbb49c45a447cb32bb68bfd1a8232", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "role", + "type": 26, + "optional": false + }, + { + "name": "source", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "union", + "types": [ + 3, + 7, + 9, + 11, + 13, + 23 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "isError", + "type": 4, + "optional": true + }, + { + "name": "toolCallId", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 14, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 15, + "optional": false + }, + { + "name": "height", + "type": 15, + "optional": false + }, + { + "name": "mediaType", + "type": 16, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 21, + "optional": true + }, + { + "name": "width", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 17, + 18, + 19, + 20 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 15, + "optional": false + }, + { + "name": "width", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 24, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 15, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "model", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "replayState", + "type": 29, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + } + ] + }, + "names": [ + "packages/llm/llm/src/message.ts#AssistantMessage" + ], + "sources": [ + "packages/llm/llm/src/message.ts:148" + ] + }, + { + "digest": "0a30c3be336cdd4f8f34e66543a3d46d611c43986edd9b3ce6cbb9b721d3b5d3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + }, + { + "name": "reason", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "union", + "types": [ + 3, + 5, + 7, + 10, + 12 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "parent" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 8, + "optional": false + }, + { + "name": "reason", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hook" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "disposed" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "legacy" + } + ] + }, + "names": [ + "event:turn/end.data.reason[1]" + ], + "sources": [ + "packages/core/session/src/types.ts:203" + ] + }, + { + "digest": "0a6fae8c3249943e23a024ae6c0d9dd707df9bff0badc69786f0dc8ad9482d8e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 31 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 2, + "optional": false + }, + { + "name": "llmStreamCall", + "type": 3, + "optional": false + }, + { + "name": "maxTokens", + "type": 4, + "optional": true + }, + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "provider", + "type": 2, + "optional": false + }, + { + "name": "rawOutput", + "type": 5, + "optional": false + }, + { + "name": "shadowedRange", + "type": 28, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 29, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 4, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 2, + "optional": true + }, + { + "name": "summary", + "type": 5, + "optional": false + }, + { + "name": "usage", + "type": 30, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 6 + }, + { + "kind": "union", + "types": [ + 7, + 10, + 12, + 14, + 16, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 5, + "optional": false + }, + { + "name": "isError", + "type": 8, + "optional": true + }, + { + "name": "toolCallId", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 2, + "optional": false + }, + { + "name": "bytes", + "type": 4, + "optional": false + }, + { + "name": "height", + "type": 4, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 4, + "optional": false + }, + { + "name": "width", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 2, + "optional": false + }, + { + "name": "bytes", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 4, + "optional": false + }, + { + "name": "start", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 4, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 4, + "optional": true + }, + { + "name": "inputTokens", + "type": 4, + "optional": false + }, + { + "name": "outputTokens", + "type": 4, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 4, + "optional": true + }, + { + "name": "totalTokens", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 2, + "optional": false + }, + { + "name": "maxTokens", + "type": 4, + "optional": true + }, + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "provider", + "type": 2, + "optional": false + }, + { + "name": "rawOutput", + "type": 5, + "optional": true + }, + { + "name": "shadowedRange", + "type": 28, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 29, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 4, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 2, + "optional": true + }, + { + "name": "summary", + "type": 5, + "optional": false + }, + { + "name": "usage", + "type": 30, + "optional": true + } + ], + "indices": [] + } + ] + }, + "names": [ + "event:compaction/summary.data" + ], + "sources": [] + }, + { + "digest": "0a8a062bc9c3c0d97fd97742262f2ea53dd32297f834e81114870d38ac3dc6da", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "subagent/model-selection-policy" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "0aa62ab51b36c0e64b134bd03ba8725d758744180a1a447da49beaad194f219f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 1, + "value": 2 + } + ] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "opaque", + "reason": "unknown" + } + ] + }, + "names": [ + "event:request/header.data.header.tools[0].parameters" + ], + "sources": [] + }, + { + "digest": "0acd94fe8794574c9ae72c7855ee16d29c546096d4af760d16d1225385764555", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 1, + "optional": true + }, + { + "name": "response", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "opaque", + "reason": "unknown" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#ReplayEnvelope" + ], + "sources": [ + "packages/llm/llm/src/types.ts:368" + ] + }, + { + "digest": "0ae7ccdc1cc5d9d43772ebd5c7357ff509011c3a6ba188d9c274d33df2542c42", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 46, + "optional": true + }, + { + "name": "seq", + "type": 10, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 47, + "optional": true + }, + { + "name": "surfaceOp", + "type": 48, + "optional": false + }, + { + "name": "time", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "step", + "type": 10, + "optional": false + }, + { + "name": "turn", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "role", + "type": 28, + "optional": false + }, + { + "name": "source", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 8, + 18, + 21, + 24, + 26 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "mediaType", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "originalDimensions", + "type": 16, + "optional": true + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 12, + 13, + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 22, + "optional": true + }, + { + "name": "toolCallId", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "union", + "types": [ + 30, + 35, + 37, + 39, + 41, + 43, + 45 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 31, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + }, + { + "name": "sections", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 34 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "text", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 36, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + }, + { + "name": "summary", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 38, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 44, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 10 + }, + { + "kind": "union", + "types": [ + 49, + 51 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 10, + "optional": false + }, + { + "name": "op", + "type": 50, + "optional": false + }, + { + "name": "startSeq", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "literal", + "value": "system/message" + } + ] + }, + "names": [ + "event:system/message" + ], + "sources": [] + }, + { + "digest": "0af3ecfb8c60d416fc40a7cc4fb2c4851a8e46608b2b38ab9e510670c971aa1f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "turn/start" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "0b6d718c693fdf7f06dcccb0bcbbabe78cc63514eafc62d6b2a3899735279d6f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "revision", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "packages/goal/goal/src/types.ts#GoalRef" + ], + "sources": [ + "packages/goal/goal/src/types.ts:20" + ] + }, + { + "digest": "0bcfaf5566d271028429ac7b562cb59053c9d78b8ae14708c35fac835108d1b1", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "messageId", + "type": 1, + "optional": false + }, + { + "name": "targetId", + "type": 1, + "optional": false + }, + { + "name": "teamId", + "type": 1, + "optional": false + }, + { + "name": "version", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": 2 + } + ] + }, + "names": [ + "event:team/message/delivered.data" + ], + "sources": [ + "packages/experimental/agent-team/src/types.ts:227" + ] + }, + { + "digest": "0bdaf9cb63594be4ef034b68901fffe792fe09d6d5f6bac42d7f56f6e7834fb9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "usage" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "0c4205180f75b9ee1e0d878bd9a9851628b5dc18448486389d9c5fdc3f1ccda3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": false + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "0d47a8f3d847243c4488e755ad4cab7a04ac5d4621b9c81865524cf3f4d8536f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "allowedModels", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:subagent/model-selection-policy.data" + ], + "sources": [ + "packages/subagent/tool-subagent/src/model-selection-state.ts:17" + ] + }, + { + "digest": "0ddab4577db37a3e0d9e3a91761735c8ef6f5629d526ab8ecaeeea19c097c8f8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + }, + { + "name": "plugin", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[1]", + "event:system/message.data.message.source[0]" + ], + "sources": [] + }, + { + "digest": "0e10d7c096b47e61defe372a5fc8bd37d835199410eac44367f95c45082498bd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 1, + "optional": false + }, + { + "name": "childId", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 2, + "optional": false + }, + { + "name": "mode", + "type": 3, + "optional": false + }, + { + "name": "version", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "literal", + "value": 0 + } + ] + }, + "names": [ + "event:subagent/catalog.data[1]" + ], + "sources": [] + }, + { + "digest": "0e560aad8fa9f338a60e76cbce6593ce93486ab30d6dd7833b8b5b07c6e6963d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "union", + "types": [ + 2, + 5, + 7, + 9, + 19, + 22 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "mediaType", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "originalDimensions", + "type": 17, + "optional": true + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 20, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 0, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].content", + "event:agent/inbox/spliced.data.inserted[0].content[0][5].content", + "event:assistant/message.data.message.content", + "event:compaction/summary.data[0].rawOutput", + "event:compaction/summary.data[0].summary", + "event:compaction/summary.data[1].rawOutput", + "event:compaction/summary.data[1].summary", + "event:session/title-llm-request.data.messages[0].content", + "event:system/message.data.message.content", + "event:team/message/queued.data.message.content", + "event:tool/ptc-dispatch.data.content" + ], + "sources": [] + }, + { + "digest": "0e7b2a97f54f1afdb8cdfad1635abe9145327db539bfb69b4045c19d8c17755d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "schedule/change" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "0f46153645c297846a3fad3911636659ca4717a5e7d71c9f50bffacd312115ef", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 1, + "optional": false + }, + { + "name": "width", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].content[0][2].attachment.originalDimensions" + ], + "sources": [ + "packages/attachment/attachment/src/types.ts:28" + ] + }, + { + "digest": "10ce3a084c136b5fc9969e5981b64711c35a2d9de7eac1b8443c03f73dc4f3db", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "senderId", + "type": 4, + "optional": false + }, + { + "name": "senderName", + "type": 4, + "optional": false + }, + { + "name": "targetId", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "union", + "types": [ + 3, + 6, + 8, + 10, + 20, + 23 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 12, + "optional": false + }, + { + "name": "height", + "type": 12, + "optional": false + }, + { + "name": "mediaType", + "type": 13, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": true + }, + { + "name": "originalDimensions", + "type": 18, + "optional": true + }, + { + "name": "width", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 14, + 15, + 16, + 17 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 12, + "optional": false + }, + { + "name": "width", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 21, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "isError", + "type": 24, + "optional": true + }, + { + "name": "toolCallId", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + } + ] + }, + "names": [ + "packages/experimental/agent-team/src/types.ts#TeamMessageSnapshot" + ], + "sources": [ + "packages/experimental/agent-team/src/types.ts:106" + ] + }, + { + "digest": "10e386801903f9d424602c31d3ff837a94aef623c8b391376d8d9e253525fabe", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "compaction/prune" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "119abe2b9c0b2f46ecb31d82b9acba4d4b30e049f655e2475e8b4ae12d5a9e25", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "dialect", + "type": 1, + "optional": false + }, + { + "name": "handlerId", + "type": 4, + "optional": false + }, + { + "name": "matcher", + "type": 4, + "optional": true + }, + { + "name": "point", + "type": 4, + "optional": false + }, + { + "name": "turn", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 3 + ] + }, + { + "kind": "literal", + "value": "claude-code" + }, + { + "kind": "literal", + "value": "codex" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:hook/invoked.data" + ], + "sources": [ + "packages/hooks/hook-protocol/src/types.ts:19" + ] + }, + { + "digest": "11f70b595aa76897fc4e25a352bfa718c659f44b799e5aa14e84e5a38fe72912", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "policy", + "type": 1, + "optional": false + }, + { + "name": "source", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 3 + ] + }, + { + "kind": "literal", + "value": "ask" + }, + { + "kind": "literal", + "value": "never" + }, + { + "kind": "literal", + "value": "delegation" + } + ] + }, + "names": [ + "event:approval/policy.data" + ], + "sources": [ + "packages/interaction/user-approval/src/index.ts:33" + ] + }, + { + "digest": "12a0cfa619ac4a5fc6c3e8e359c8dae15769edc61497a575935c78d5842f8e42", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "read-only" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "137ac60e26bcb192210d9a32a704b73b0924ed34019323defa1b787be95bd45e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": 3 + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 2, + "optional": false + }, + { + "name": "files", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 2, + "optional": true + }, + { + "name": "path", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "deliverables/presented" + } + ] + }, + "names": [ + "event:deliverables/presented" + ], + "sources": [] + }, + { + "digest": "1418e7f0c944a0453ce0851729b38b69c74194ff1e3ed41dc71904ca66e57bc5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 1, + "optional": false + }, + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "isError", + "type": 25, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "parentCallId", + "type": 5, + "optional": false + }, + { + "name": "rootCallId", + "type": 5, + "optional": false + }, + { + "name": "subCallId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 4, + 7, + 9, + 11, + 21, + 24 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 12, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 13, + "optional": false + }, + { + "name": "height", + "type": 13, + "optional": false + }, + { + "name": "mediaType", + "type": 14, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 19, + "optional": true + }, + { + "name": "width", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 15, + 16, + 17, + 18 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 13, + "optional": false + }, + { + "name": "width", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 22, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 13, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "isError", + "type": 25, + "optional": true + }, + { + "name": "toolCallId", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + } + ] + }, + "names": [ + "packages/core/tools/src/types.ts#PtcDispatchEventData" + ], + "sources": [ + "packages/core/tools/src/types.ts:20" + ] + }, + { + "digest": "14230f9b8b5797bb7e8c621fa636531cfbde2ffba1ef5f1fd058216ff21952a0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "chunk" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "142c37d302aa22a0a57915b5255939d3ca91c1811ca13fddcf84cf76ea00758a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "always" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "15196447222782e773eb943c92b18316ce96b9af0f0cfddb6e57ba8274ecc5ff", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "commandId", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "sourceEventSeq", + "type": 6, + "optional": true + }, + { + "name": "text", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "literal", + "value": "success" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "command/done" + } + ] + }, + "names": [ + "event:command/done" + ], + "sources": [] + }, + { + "digest": "1528539c63db8b23506f0209a99ce77d8ad138adfbfcee3d4769b7382d93756c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 1, + "optional": false + }, + { + "name": "files", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 1, + "optional": true + }, + { + "name": "path", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:deliverables/presented.data" + ], + "sources": [ + "packages/fs/tool-present/src/types.ts:15" + ] + }, + { + "digest": "15495614ccfa9cbdeefb0ec2abd76406435f4c6ff3dae42acae8e09ef42025c9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "codex" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1571525adc95a24e5d25b5a6440df401cd7cdd881b745178e85f5a333435cd1b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "never" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "16d0507cb94b4cfc84a87446b1ba7652d6ec1d65ee1684789e747e30d542800e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "relay" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "17aae616ef020b8b9f011ec398ec935450fb06d3e2a0722c456f63aeaf5ff728", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "at" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "17d1afb770d9941936130996da00dc86782cfef731d8d6526162c301256a4ac3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "inherited", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + } + ] + }, + "names": [ + "event:session/end-seed.data" + ], + "sources": [ + "packages/core/session/src/types.ts:400" + ] + }, + { + "digest": "18dc21f12bbac8ade8377e6fbb54179e20c3d955d73fba3244c57fb6bf001df0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "operation", + "type": 2, + "optional": false + }, + { + "name": "version", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "delete" + }, + { + "kind": "literal", + "value": 1 + } + ] + }, + "names": [ + "packages/schedule/schedule/src/types.ts#ScheduleDeleteChange" + ], + "sources": [ + "packages/schedule/schedule/src/types.ts:79" + ] + }, + { + "digest": "18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "agentPreset", + "type": 1, + "optional": true + }, + { + "name": "createdAt", + "type": 2, + "optional": false + }, + { + "name": "cwd", + "type": 1, + "optional": true + }, + { + "name": "delegationDepth", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "isSeeded", + "type": 3, + "optional": false + }, + { + "name": "origin", + "type": 4, + "optional": true + }, + { + "name": "parentSession", + "type": 1, + "optional": true + }, + { + "name": "type", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "subagent" + }, + { + "kind": "literal", + "value": "session" + } + ] + }, + "names": [ + "packages/session/session-persistence-jsonl/src/format.ts#HeaderLine" + ], + "sources": [ + "packages/session/session-persistence-jsonl/src/format.ts:82" + ] + }, + { + "digest": "18fcdd273a2f2f9d813a3be5e71df5903a963a5c88e46b90b14de6e2db890da0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "team/member" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "18fce9540bb20897a66275499faee6b8296c97e9901fa1ff45a191772448ad1a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "danger-full-access" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1922cd79a70806c17758185a6ef0458bc1fb3900363906733421c16414da2509", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "skill-invocation" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1986c2664009c99155e44795676c887fa67f7c55cd2d5e6b648ff763e0ab033a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "compaction/end" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1a2718b00937b236a0d0910ef5dab2f9f8b65e4bde410cc21f15fbbf29f584d3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "deliverables/presented" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1ab2ef1296f157ba96998f179bb5daa9d21a82480da2901127aeab2730797c92", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2 + ] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "literal", + "value": "success" + } + ] + }, + "names": [ + "event:command/done.data.kind" + ], + "sources": [] + }, + { + "digest": "1b5649238876e28811e80f10ec67a00c197ad3babd4ecc61a22b09064b88c597", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "fallback" + } + ] + }, + "names": [ + "event:session/title.data.source[0]" + ], + "sources": [ + "packages/session/session-title/src/types.ts:29" + ] + }, + { + "digest": "1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 13, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "messageSeqs", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 4, + "optional": false + }, + { + "name": "title", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 5, + 9, + 11 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": true + }, + { + "name": "provider", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "provider" + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 8, + "optional": false + }, + { + "name": "provider", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "fallback" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "session/title" + } + ] + }, + "names": [ + "event:session/title" + ], + "sources": [] + }, + { + "digest": "1c2686c0682e7bc51d61117b2652bd4a80707e331aa276d6ef915ae32534a330", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "session/end-seed" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1c5a58f4599bd4a9711712551ffe2442a5e5fcd0758bafe630cc4ead850bdaa7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "system/message" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1d5f04a03ea75fc8eeece544129eba0f49f0c82046e9c404461920390135c7b2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "model/selection" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1d76406cbb2ddadb0dbbb51e9d8bc6cf678fed455cf8eb512f36f7c86d37a62f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[6].reason[3]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:131" + ] + }, + { + "digest": "1d8798b1a5e8c545ee4dcd2b4e75c0b4c9ffd985e4744c6d1a1aab992f859721", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "pause" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1d9cb3caa96100b18b1911fa3ff8c751ef6992971a03c5f4d9aefc1a0004a345", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 2, + "optional": true + }, + { + "name": "path", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:deliverables/presented.data.files" + ], + "sources": [] + }, + { + "digest": "1df34a6c086866ffdc2dd9e8e5c581c3751a010cdf6b8538a7327f7a3237f0b4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "createdAt", + "type": 1, + "optional": false + }, + { + "name": "goal", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 10, + "optional": false + }, + { + "name": "operation", + "type": 11, + "optional": false + }, + { + "name": "roundsStarted", + "type": 1, + "optional": false + }, + { + "name": "updatedAt", + "type": 1, + "optional": false + }, + { + "name": "version", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockedReason", + "type": 3, + "optional": true + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "maxGoalRounds", + "type": 1, + "optional": false + }, + { + "name": "objective", + "type": 4, + "optional": false + }, + { + "name": "phase", + "type": 5, + "optional": false + }, + { + "name": "revision", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 4, + "optional": false + }, + { + "name": "message", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 6, + 7, + 8, + 9 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "literal", + "value": "complete" + }, + { + "kind": "literal", + "value": "paused" + }, + { + "kind": "literal", + "value": "goal/change" + }, + { + "kind": "union", + "types": [ + 8, + 12, + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "block" + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "literal", + "value": "edit" + }, + { + "kind": "literal", + "value": "pause" + }, + { + "kind": "literal", + "value": "resume" + }, + { + "kind": "literal", + "value": 1 + } + ] + }, + "names": [ + "packages/goal/goal/src/domain.ts#GoalSnapshotChangeMeta" + ], + "sources": [ + "packages/goal/goal/src/domain.ts:24" + ] + }, + { + "digest": "1e0b0b66b6f0c53fc72e52289d06b4708d776f1ca9df4c86a22bff621d403cf0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "aborted" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1e346446e183ee76f48d7994c97ecb302bddc682abb60b8a10ea0bb26223a69c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "block" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1e75cbc0af53f62e5685d751108dc9f6320fe835aed222e579a2a0080dc8fc59", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 1, + "optional": false + }, + { + "name": "llmStreamCall", + "type": 2, + "optional": false + }, + { + "name": "maxTokens", + "type": 3, + "optional": true + }, + { + "name": "model", + "type": 1, + "optional": false + }, + { + "name": "provider", + "type": 1, + "optional": false + }, + { + "name": "rawOutput", + "type": 4, + "optional": false + }, + { + "name": "shadowedRange", + "type": 27, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 28, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 3, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 1, + "optional": true + }, + { + "name": "summary", + "type": 4, + "optional": false + }, + { + "name": "usage", + "type": 29, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 11, + 13, + 15, + 24 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "isError", + "type": 7, + "optional": true + }, + { + "name": "toolCallId", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 1, + "optional": false + }, + { + "name": "bytes", + "type": 3, + "optional": false + }, + { + "name": "height", + "type": 3, + "optional": false + }, + { + "name": "mediaType", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": true + }, + { + "name": "originalDimensions", + "type": 22, + "optional": true + }, + { + "name": "width", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 18, + 19, + 20, + 21 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 3, + "optional": false + }, + { + "name": "width", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 25, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 1, + "optional": false + }, + { + "name": "bytes", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 3, + "optional": false + }, + { + "name": "start", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 3, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 3, + "optional": true + }, + { + "name": "inputTokens", + "type": 3, + "optional": false + }, + { + "name": "outputTokens", + "type": 3, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 3, + "optional": true + }, + { + "name": "totalTokens", + "type": 3, + "optional": true + } + ], + "indices": [] + } + ] + }, + "names": [ + "event:compaction/summary.data[0]" + ], + "sources": [] + }, + { + "digest": "1ebc6f9ff3aa9e3a38f8695f49bacdab1f8b7a3832741853ca46f40af56ccd01", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "completed" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1f0133217eeeffd8d387baf1ec8ed9856e719bcd999dc08e48624cba96d75fd7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool-call" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "1f8c126cfab3df812cd115cfc23e3256f0fa2383400f4ce91ecad313577e59b0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 4, + 6, + 8, + 10 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "reason", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hook" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "disposed" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "legacy" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "parent" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + } + ] + }, + "names": [ + "packages/core/session/src/types.ts#TurnEndCancelCause" + ], + "sources": [ + "packages/core/session/src/types.ts:195" + ] + }, + { + "digest": "20a57544bbf204dacd1ae6beb2ab29444365c3c869f2afdf489dcdcbc3d9c8c5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "active", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + } + ] + }, + "names": [ + "event:plan/mode.data" + ], + "sources": [ + "packages/plan/plan-mode/src/index.ts:47" + ] + }, + { + "digest": "2142e391b9ccb60d487afcf373ce74db98d952f69e8289eddb352a7e16e996c5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "cancelled" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + }, + { + "name": "messageId", + "type": 2, + "optional": false + }, + { + "name": "senderId", + "type": 2, + "optional": false + }, + { + "name": "senderName", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/experimental/agent-team/src/types.ts#TeamMessageSource" + ], + "sources": [ + "packages/experimental/agent-team/src/types.ts:115" + ] + }, + { + "digest": "234142ee4f996f38cc9fa3b8b3e3de29b03c9aefe5ebf6c224eddd564505aa85", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "pending" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "237c004c73b6b288246816e0d7fde16e09c63a47aa3b1227383d7afe58666163", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "goal/change" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "23cf7e0cf3fec1283b60fa15713506169bc9c079a08e124d1cb9733baca80b40", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 7, + 9, + 11, + 13, + 15, + 17 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + }, + { + "name": "sections", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 6 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "text", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 8, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + }, + { + "name": "summary", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 12, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 14, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 16, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "event:system/message.data.message.source" + ], + "sources": [] + }, + { + "digest": "2517ba143a271508d3ca35126d5aca8f7f1facf5aaddce4adf011832042fa2b9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "apiVersion", + "type": 1, + "optional": false + }, + { + "name": "body", + "type": 2, + "optional": false + }, + { + "name": "endpoint", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "max_tokens", + "type": 3, + "optional": false + }, + { + "name": "messages", + "type": 4, + "optional": false + }, + { + "name": "model", + "type": 1, + "optional": false + }, + { + "name": "tools", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "tuple", + "elements": [ + { + "type": 5, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "role", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 7, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "tuple", + "elements": [ + { + "type": 11, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "max_uses", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 12, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "web_search" + }, + { + "kind": "literal", + "value": "web_search_20250305" + } + ] + }, + "names": [ + "packages/web/web-search-deepseek/src/provider.ts#DeepSeekSearchLlmRequest" + ], + "sources": [ + "packages/web/web-search-deepseek/src/provider.ts:56" + ] + }, + { + "digest": "254dce209e735f9cb6f6b7aec0354712b1611759ec02af4d35a96f2081e666f6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "blocked" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 7, + "optional": false + }, + { + "name": "time", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "policy", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "ask" + }, + { + "kind": "literal", + "value": "never" + }, + { + "kind": "literal", + "value": "delegation" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "approval/policy" + } + ] + }, + "names": [ + "event:approval/policy" + ], + "sources": [] + }, + { + "digest": "26c879bdd130e8b8683a6ce588414fad2244783d6573ca399fa0de184a2b0bb1", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "dispatch" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "2765e9119edf5816ac1ab4c95c5ee32c90403f3742962eff11544f7752d59d2e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "model" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "277b61f9c6f8170425fa6ffe06cfa14376757a896a4749cb82a687d7364c4fc9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "file" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "27cd736e794a0f8703a155fb697820cede894d10776586dd3df24d24c9465c0e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 1, + "optional": false + }, + { + "name": "message", + "type": 1, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 2, + "optional": true + }, + { + "name": "requestId", + "type": 1, + "optional": true + }, + { + "name": "status", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#LlmFailure" + ], + "sources": [ + "packages/llm/llm/src/types.ts:40" + ] + }, + { + "digest": "27e337d3d6b402b221e50c247a3f518a70d0c474cc60beee15fac3e9c5cbfcbe", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "afterSeconds", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "prompt", + "type": 2, + "optional": false + }, + { + "name": "scheduledAt", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "after" + } + ] + }, + "names": [ + "packages/schedule/schedule/src/types.ts#AfterScheduleRecord" + ], + "sources": [ + "packages/schedule/schedule/src/types.ts:13" + ] + }, + { + "digest": "287211846beea1eccb5f9acee00d20388a38fcb2e95858fa2332d6f58529379a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[6].reason[4]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:132" + ] + }, + { + "digest": "2a7f86849ae54b3398ee49661a757c4fcb59a6192b7036ee2ff514617e13fb42", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 19, + "optional": true + }, + { + "name": "seq", + "type": 13, + "optional": false + }, + { + "name": "time", + "type": 13, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 6, + 8, + 9 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "acceptedAt", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "operation", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "dispatch" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "operation", + "type": 7, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "delete" + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "operation", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "operation", + "type": 10, + "optional": false + }, + { + "name": "schedule", + "type": 11, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "union", + "types": [ + 12, + 15, + 17 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "afterSeconds", + "type": 13, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 14, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "after" + }, + { + "kind": "object", + "properties": [ + { + "name": "everySeconds", + "type": 13, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 16, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "every" + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 18, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "at" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "schedule/change" + } + ] + }, + "names": [ + "event:schedule/change" + ], + "sources": [] + }, + { + "digest": "2a8b114637e66faab5fae1c6e2fba65320db6224c80f5f36c5bf32f937154d03", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "agentPreset", + "type": 1, + "optional": true + }, + { + "name": "createdAt", + "type": 2, + "optional": false + }, + { + "name": "cwd", + "type": 1, + "optional": true + }, + { + "name": "delegationDepth", + "type": 2, + "optional": true + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "isSeeded", + "type": 3, + "optional": false + }, + { + "name": "origin", + "type": 4, + "optional": true + }, + { + "name": "parentSession", + "type": 1, + "optional": true + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "subagent" + }, + { + "kind": "literal", + "value": 3 + } + ] + }, + "names": [ + "packages/core/session/src/types.ts#SessionHeader" + ], + "sources": [ + "packages/core/session/src/types.ts:93" + ] + }, + { + "digest": "2ab12a0e345658f9342816d773fe610f049982bcad82631f43b9205abe43e5ee", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 2, + "optional": false + }, + { + "name": "digest", + "type": 6, + "optional": true + }, + { + "name": "path", + "type": 6, + "optional": false + }, + { + "name": "scope", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[17].changes" + ], + "sources": [] + }, + { + "digest": "2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 1, + "optional": false + }, + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning-delta" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[2]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:393" + ] + }, + { + "digest": "2aed07493b1c65b6d00de5586da1e743cf501684b9ad43a1765fb4007d4ea456", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool/ptc-dispatch-start" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "2c4c21f19f426a58f2dac914e34313aa43f8408972de857a486d1f086d5adb6c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "unavailable" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "2c64d4bc7264fd3f8bed47e96ab8a593bce05a1e4e1c436dcd244a3b7e0c2a9d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "step/start" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "2ce7ef3b3daebf95815c7ae454a751d3e67ca7a22e2e0734fe2942df393bb66c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[6]", + "event:system/message.data.message.source[5]" + ], + "sources": [] + }, + { + "digest": "2d11ca7b0d4493e244b74eba093227866b33249841e59a1e9bf56afed38604c3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "max_uses", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "web_search" + }, + { + "kind": "literal", + "value": "web_search_20250305" + } + ] + }, + "names": [ + "event:web/deepseek-search-llm-request.data.body.tools[0]" + ], + "sources": [ + "packages/web/web-search-deepseek/src/provider.ts:72" + ] + }, + { + "digest": "2da725e554cd89301d829ed9dc45a572c3cd1b93b5e91d0d71f007976d8a0863", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "fork" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "2e49ede7fb3a592cce8919ebe982f6ca1c1f67abe4def359ffc028c85e6d1ad8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 30, + "optional": true + }, + { + "name": "seq", + "type": 9, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 84, + "optional": true + }, + { + "name": "surfaceOp", + "type": 85, + "optional": false + }, + { + "name": "time", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 88, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "role", + "type": 27, + "optional": false + }, + { + "name": "source", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 4, + 7, + 17, + 20, + 23, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "mediaType", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 15, + "optional": true + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 11, + 12, + 13, + 14 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "isError", + "type": 21, + "optional": true + }, + { + "name": "toolCallId", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 29, + 39, + 41, + 42, + 45, + 50, + 52, + 57, + 58, + 59, + 61, + 63, + 64, + 71, + 73, + 75, + 77, + 79, + 82, + 83 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 30, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 5, + "optional": true + }, + { + "name": "changes", + "type": 31, + "optional": false + }, + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 32 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 33, + "optional": false + }, + { + "name": "digest", + "type": 5, + "optional": true + }, + { + "name": "path", + "type": 5, + "optional": false + }, + { + "name": "scope", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 34, + 35, + 36 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 5, + "optional": true + }, + { + "name": "kind", + "type": 27, + "optional": false + }, + { + "name": "rpcId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 5, + "optional": false + }, + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "ruleId", + "type": 5, + "optional": false + }, + { + "name": "source", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 46, + "optional": false + }, + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + }, + { + "name": "update", + "type": 30, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 47 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 53, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "sections", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 56 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "text", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 60, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 60, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + }, + { + "name": "references", + "type": 66, + "optional": false + }, + { + "name": "version", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 67 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 9, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 68, + "optional": false + }, + { + "name": "compacted", + "type": 21, + "optional": false + }, + { + "name": "inputIndex", + "type": 9, + "optional": false + }, + { + "name": "label", + "type": 5, + "optional": false + }, + { + "name": "omittedBytes", + "type": 9, + "optional": false + }, + { + "name": "omittedMessages", + "type": 9, + "optional": false + }, + { + "name": "originalMessages", + "type": 9, + "optional": false + }, + { + "name": "retainedMessages", + "type": 9, + "optional": false + }, + { + "name": "sessionId", + "type": 5, + "optional": false + }, + { + "name": "truncated", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 9, + 69 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 72, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 74, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 76, + "optional": false + }, + { + "name": "revision", + "type": 9, + "optional": false + }, + { + "name": "round", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "messageId", + "type": 5, + "optional": false + }, + { + "name": "senderId", + "type": 5, + "optional": false + }, + { + "name": "senderName", + "type": 5, + "optional": false + }, + { + "name": "teamId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "model", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "replayState", + "type": 81, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 9 + }, + { + "kind": "union", + "types": [ + 86, + 87 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 9, + "optional": false + }, + { + "name": "op", + "type": 35, + "optional": false + }, + { + "name": "startSeq", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user/message" + } + ] + }, + "names": [ + "event:user/message" + ], + "sources": [] + }, + { + "digest": "2e4debca12f2c449f96dc6574f3fbf2fb346193443114a241d01a236362ec48c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "team-message" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "2f0aefb72ab453c4bf21a6687f436775d359dc6f8f10410fd4c0615b20bd9d76", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "header", + "type": 1, + "optional": false + }, + { + "name": "reason", + "type": 12, + "optional": false + }, + { + "name": "startsSeries", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "adapterDefaults", + "type": 2, + "optional": true + }, + { + "name": "config", + "type": 4, + "optional": false + }, + { + "name": "tools", + "type": 8, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 3, + "optional": true + }, + { + "name": "reasoningEffort", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 5, + "optional": true + }, + { + "name": "model", + "type": 6, + "optional": false + }, + { + "name": "provider", + "type": 6, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 6, + "optional": true + }, + { + "name": "stop", + "type": 7, + "optional": true + }, + { + "name": "temperature", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 6 + }, + { + "kind": "array", + "element": 9 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "parameters", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 6, + "value": 11 + } + ] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "resume" + }, + { + "kind": "literal", + "value": "series" + }, + { + "kind": "literal", + "value": "change" + }, + { + "kind": "literal", + "value": "initial" + } + ] + }, + "names": [ + "event:request/header.data" + ], + "sources": [ + "packages/core/session/src/types.ts:365" + ] + }, + { + "digest": "2f1e6a7ebace43d727ae138ddcd352eb3813960009e5805b8c5ac84300c97716", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + } + ] + }, + "names": [ + "packages/attachment/attachment/src/types.ts#ImageMediaType" + ], + "sources": [ + "packages/attachment/attachment/src/types.ts:8" + ] + }, + { + "digest": "2f90a7b3646fea3e677bd7b4000aa96f3b8964f107b614e204e11ad4051c24fc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[3]", + "event:system/message.data.message.source[2]" + ], + "sources": [] + }, + { + "digest": "2fe5026daf14bd4ff2b7bfb7cf9791c302d9cbba06802178b6dfec00b892698f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "parentCallId", + "type": 2, + "optional": false + }, + { + "name": "rootCallId", + "type": 2, + "optional": false + }, + { + "name": "subCallId", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/core/tools/src/types.ts#PtcDispatchStartEventData" + ], + "sources": [ + "packages/core/tools/src/types.ts:11" + ] + }, + { + "digest": "2ffcad547cb840f462eb1849fa5f6cb0e17eb93f5d07cc88b61d749b1b78d632", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 29, + "optional": true + }, + { + "name": "seq", + "type": 17, + "optional": false + }, + { + "name": "time", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 7, + "optional": false + }, + { + "name": "version", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "senderId", + "type": 7, + "optional": false + }, + { + "name": "senderName", + "type": 7, + "optional": false + }, + { + "name": "targetId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 9, + 11, + 13, + 15, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 6, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "team/message/queued" + } + ] + }, + "names": [ + "event:team/message/queued" + ], + "sources": [] + }, + { + "digest": "30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 5, + 7 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "model", + "type": 3, + "optional": true + }, + { + "name": "provider", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "provider" + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 4, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "fallback" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + } + ] + }, + "names": [ + "packages/session/session-title/src/types.ts#SessionTitleSource" + ], + "sources": [ + "packages/session/session-title/src/types.ts:28" + ] + }, + { + "digest": "326a9bfda27dda5882fb3cbe6e715df41dae2298fdae3d66437457268a665693", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 1, + "optional": true + }, + { + "name": "reasoningEffort", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + } + ] + }, + "names": [ + "packages/llm/llm/src/call-config.ts#LlmCallConfigAdapterDefaults" + ], + "sources": [ + "packages/llm/llm/src/call-config.ts:36" + ] + }, + { + "digest": "3305a4bb6b6c29b482ba91d59c60b47e8f98282d3dacc8c29e4a4c2712209bdd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ] + }, + { + "kind": "literal", + "value": "instruction-following" + }, + { + "kind": "literal", + "value": "other" + }, + { + "kind": "literal", + "value": "product-interaction" + }, + { + "kind": "literal", + "value": "resource-cost" + }, + { + "kind": "literal", + "value": "security-privacy-permission" + }, + { + "kind": "literal", + "value": "service-stability" + }, + { + "kind": "literal", + "value": "task-result" + } + ] + }, + "names": [ + "event:feedback/message-put.data.item.category", + "event:feedback/record.data.category" + ], + "sources": [] + }, + { + "digest": "335e242de1fcc17b6ca920fc420d71bec2d76e53e37955c00948b65ab77f05c5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 3, + "optional": false + }, + { + "name": "op", + "type": 4, + "optional": false + }, + { + "name": "startSeq", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "replace" + } + ] + }, + "names": [ + "packages/core/session/src/types.ts#SurfaceOp" + ], + "sources": [ + "packages/core/session/src/types.ts:434" + ] + }, + { + "digest": "34d1ab5c5df378186d6054b1a1beea9a41e41965d6f336f0bc0025c441a82e66", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "resume" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "3516f54b427775f483febe8e938e4e10f9afd3583610861547bbe2999ed4d8d9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 71, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 72, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "stream", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 10, + 67, + 69 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 6, + "optional": false + }, + { + "name": "dt", + "type": 8, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "literal", + "value": "tool-call-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 11, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 12, + 14, + 39, + 42, + 44, + 46, + 63 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 15, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 16, + 18, + 27, + 30, + 34, + 36 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 20, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 25, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 21, + 22, + 23, + 24 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 28, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 31, + "optional": false + }, + { + "name": "isError", + "type": 32, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 15 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 40, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 41, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 17, + 26, + 29, + 33, + 35, + 37 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 47, + "optional": false + }, + { + "name": "replayState", + "type": 59, + "optional": true + }, + { + "name": "type", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 48, + 51, + 53, + 55, + 57 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 7, + "optional": false + }, + { + "name": "message", + "type": 7, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 2, + "optional": true + }, + { + "name": "requestId", + "type": 7, + "optional": true + }, + { + "name": "status", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 60, + "optional": true + }, + { + "name": "response", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 61 + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 64, + "optional": false + }, + { + "name": "usage", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 2, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 2, + "optional": true + }, + { + "name": "inputTokens", + "type": 2, + "optional": false + }, + { + "name": "outputTokens", + "type": 2, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 2, + "optional": true + }, + { + "name": "totalTokens", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 8, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "texts", + "type": 6, + "optional": false + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 8, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "texts", + "type": 6, + "optional": false + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-chunks" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "assistant/attempt" + } + ] + }, + "names": [ + "event:assistant/attempt" + ], + "sources": [] + }, + { + "digest": "35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "provider", + "type": 2, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "model/selection" + } + ] + }, + "names": [ + "event:model/selection" + ], + "sources": [] + }, + { + "digest": "35b995af35bfa087f5381a5454a2305928da037e819bfc4759d3f66d9bd55aa9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 6 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "delayMs", + "type": 2, + "optional": false + }, + { + "name": "failure", + "type": 3, + "optional": false + }, + { + "name": "maxRetries", + "type": 2, + "optional": false + }, + { + "name": "mode", + "type": 5, + "optional": false + }, + { + "name": "policyKey", + "type": 4, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + }, + { + "name": "retry", + "type": 2, + "optional": false + }, + { + "name": "retryId", + "type": 4, + "optional": false + }, + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 4, + "optional": false + }, + { + "name": "message", + "type": 4, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 2, + "optional": true + }, + { + "name": "requestId", + "type": 4, + "optional": true + }, + { + "name": "status", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "normal" + }, + { + "kind": "object", + "properties": [ + { + "name": "delayMs", + "type": 2, + "optional": false + }, + { + "name": "failure", + "type": 3, + "optional": false + }, + { + "name": "mode", + "type": 7, + "optional": false + }, + { + "name": "policyKey", + "type": 4, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + }, + { + "name": "retry", + "type": 2, + "optional": false + }, + { + "name": "retryId", + "type": 4, + "optional": false + }, + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "always" + } + ] + }, + "names": [ + "packages/llm/llm-retry/src/types.ts#LlmRetryEventData" + ], + "sources": [ + "packages/llm/llm-retry/src/types.ts:16" + ] + }, + { + "digest": "36d6a9a871d949ea4f3edd919d81d66aa69218bebef38899d2a1282c39420a1f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "approval/policy" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "37184378c6439257d105c4e2022d80fc9c3a3f7c7f6ac661b00bc9f18d871006", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 2, + "optional": true + }, + { + "name": "commandId", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "command/run" + } + ] + }, + "names": [ + "event:command/run" + ], + "sources": [] + }, + { + "digest": "378f90f9f6022a0991928a1eaf7991094ea1920fe89a9248358a1da106e1a970", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "approval/decided" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "37cbc9cf06d494cfe5c67f078af1605eacb8e4c2c853e9e26455b73de0ee7ccf", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "contextWindow", + "type": 2, + "optional": true + }, + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "systemPromptUpdate", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "in-history" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "request/context" + } + ] + }, + "names": [ + "event:request/context" + ], + "sources": [] + }, + { + "digest": "383cf73ae4748e5beb281ae74857ba1ffb92ae01075b9fbf51af78b666007a8f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "recall" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "3860b4757d512a2869be833acb0faf834335b1b056bf3f83df5474dce95f8fa6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 1, + "optional": false + }, + { + "name": "dt", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "index", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": true + }, + { + "name": "time0", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-call-chunks" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][2]" + ], + "sources": [ + "packages/llm/llm/src/assistant-stream.ts:35" + ] + }, + { + "digest": "390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2 + ] + }, + { + "kind": "literal", + "value": "negative" + }, + { + "kind": "literal", + "value": "positive" + } + ] + }, + "names": [ + "packages/feedback/message-feedback/src/types.ts#MessageFeedbackRating" + ], + "sources": [ + "packages/feedback/message-feedback/src/types.ts:17" + ] + }, + { + "digest": "3a896379a74576c5a527e4720be2fe6807d3225c27134e978a2b89a94068368e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 1, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 6, + 31, + 34, + 36, + 38, + 55 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "index", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 8, + 10, + 19, + 22, + 26, + 28 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 4, + "optional": false + }, + { + "name": "height", + "type": 4, + "optional": false + }, + { + "name": "mediaType", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "originalDimensions", + "type": 17, + "optional": true + }, + { + "name": "width", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 4, + "optional": false + }, + { + "name": "width", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 20, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 23, + "optional": false + }, + { + "name": "isError", + "type": 24, + "optional": true + }, + { + "name": "toolCallId", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 32, + "optional": false + }, + { + "name": "index", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 9, + 18, + 21, + 25, + 27, + 29 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 4, + "optional": false + }, + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 4, + "optional": false + }, + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 39, + "optional": false + }, + { + "name": "replayState", + "type": 51, + "optional": true + }, + { + "name": "type", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 40, + 43, + 45, + 47, + 49 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 3, + "optional": false + }, + { + "name": "message", + "type": 3, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 4, + "optional": true + }, + { + "name": "requestId", + "type": 3, + "optional": true + }, + { + "name": "status", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 52, + "optional": true + }, + { + "name": "response", + "type": 53, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 53 + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 56, + "optional": false + }, + { + "name": "usage", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 4, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 4, + "optional": true + }, + { + "name": "inputTokens", + "type": 4, + "optional": false + }, + { + "name": "outputTokens", + "type": 4, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 4, + "optional": true + }, + { + "name": "totalTokens", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3]" + ], + "sources": [ + "packages/llm/llm/src/assistant-stream.ts:44" + ] + }, + { + "digest": "3ac1196f59d5e9e0e9345122c8c403b102478b526404a2e591a38f613483321e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 6 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 2, + "optional": false + }, + { + "name": "childId", + "type": 3, + "optional": false + }, + { + "name": "label", + "type": 3, + "optional": false + }, + { + "name": "mode", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "literal", + "value": 0 + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 2, + "optional": false + }, + { + "name": "childId", + "type": 3, + "optional": false + }, + { + "name": "label", + "type": 3, + "optional": true + }, + { + "name": "mode", + "type": 7, + "optional": false + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "one-shot" + } + ] + }, + "names": [ + "packages/subagent/subagent/src/catalog.ts#SubagentCatalogEvent" + ], + "sources": [ + "packages/subagent/subagent/src/catalog.ts:24" + ] + }, + { + "digest": "3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "callId", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "step", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool/call" + } + ] + }, + "names": [ + "event:tool/call" + ], + "sources": [] + }, + { + "digest": "3bc287e765d58dc3a22672a0495a6cfd561220dd38fbaecef80aa95b96caed79", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "normal" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "3bd652ebfa8726b3ce3937a4f1bd2759e02f86b3a08a20f9e41a8513656fbc5f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:compaction/end.data.turn", + "event:compaction/start.data.turn", + "packages/core/session/src/types.ts#OptionalSessionSeq" + ], + "sources": [ + "packages/core/session/src/types.ts:64" + ] + }, + { + "digest": "3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 2, + "optional": true + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "reason", + "type": 2, + "optional": true + }, + { + "name": "toolName", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "approval/asked" + } + ] + }, + "names": [ + "event:approval/asked" + ], + "sources": [] + }, + { + "digest": "3c2535ca0a2062f172f15344f8040755002b5ac506cf94fc4ebaa822b15feb8c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "reasoning" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "3cfc3a56502da1f8c6153c2c56657bab4b749056968634dd0991d325ec1c919f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "allow", + "type": 1, + "optional": true + }, + { + "name": "deny", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/core/tools/src/index.ts#ToolRestriction" + ], + "sources": [ + "packages/core/tools/src/index.ts:673" + ] + }, + { + "digest": "3d7d07060a7be77984feb651ea2bf1925d3e0477911d9d3ed86330d1e8ca6182", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "category", + "type": 1, + "optional": true + }, + { + "name": "createdAt", + "type": 9, + "optional": false + }, + { + "name": "messageId", + "type": 10, + "optional": false + }, + { + "name": "note", + "type": 10, + "optional": true + }, + { + "name": "rating", + "type": 11, + "optional": false + }, + { + "name": "updatedAt", + "type": 9, + "optional": false + }, + { + "name": "version", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "kind": "literal", + "value": "instruction-following" + }, + { + "kind": "literal", + "value": "security-privacy-permission" + }, + { + "kind": "literal", + "value": "service-stability" + }, + { + "kind": "literal", + "value": "task-result" + }, + { + "kind": "literal", + "value": "other" + }, + { + "kind": "literal", + "value": "product-interaction" + }, + { + "kind": "literal", + "value": "resource-cost" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 12, + 13 + ] + }, + { + "kind": "literal", + "value": "negative" + }, + { + "kind": "literal", + "value": "positive" + } + ] + }, + "names": [ + "packages/feedback/message-feedback/src/types.ts#MessageFeedbackItem" + ], + "sources": [ + "packages/feedback/message-feedback/src/types.ts:20" + ] + }, + { + "digest": "3dad205ad99fbda835c493ddc6bc93fe1441cef2f82774ffa2856364e2cede22", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "error", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 2, + "optional": false + }, + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 3, + "optional": true + }, + { + "name": "requestId", + "type": 2, + "optional": true + }, + { + "name": "status", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "error" + } + ] + }, + "names": [ + "event:turn/end.data.reason[3]" + ], + "sources": [ + "packages/core/session/src/types.ts:211" + ] + }, + { + "digest": "3e0eff5946255ff05d88a10839c60305135801f97794ea48d409f8503303317e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "request/header" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "3e2ece4967cfebe95f450a93f45523dff99d6749f6269f2f60f84dfecfcf3693", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "next-step" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "3e4e7683b0192c9f0e6a75c59076108f017892cdb302a98032c2632fd79c356f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 1, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 1, + "optional": true + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 3, + 4 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:compaction/start.data" + ], + "sources": [ + "packages/compaction/compaction/src/types.ts:24" + ] + }, + { + "digest": "3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "messageId", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "feedback/message-delete" + } + ] + }, + "names": [ + "event:feedback/message-delete" + ], + "sources": [] + }, + { + "digest": "3f74ba74acd10644c1ed6947a411d8088bd2cfac74f61bb8540f96d68e31f004", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "image/jpeg" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "40390271dffbed81bffa514370ad1c3c19a13ad10c3647c9c0eae31126c95a5f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "role", + "type": 26, + "optional": false + }, + { + "name": "source", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "union", + "types": [ + 3, + 6, + 16, + 19, + 22, + 24 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 8, + "optional": false + }, + { + "name": "height", + "type": 8, + "optional": false + }, + { + "name": "mediaType", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": true + }, + { + "name": "originalDimensions", + "type": 14, + "optional": true + }, + { + "name": "width", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 10, + 11, + 12, + 13 + ] + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 8, + "optional": false + }, + { + "name": "width", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 8, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "isError", + "type": 20, + "optional": true + }, + { + "name": "toolCallId", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 28, + 38, + 40, + 41, + 44, + 49, + 51, + 56, + 57, + 58, + 60, + 62, + 63, + 70, + 72, + 74, + 76, + 78, + 81, + 82 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 29, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 4, + "optional": true + }, + { + "name": "changes", + "type": 30, + "optional": false + }, + { + "name": "form", + "type": 36, + "optional": false + }, + { + "name": "kind", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 31 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 32, + "optional": false + }, + { + "name": "digest", + "type": 4, + "optional": true + }, + { + "name": "path", + "type": 4, + "optional": false + }, + { + "name": "scope", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 33, + 34, + 35 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 4, + "optional": false + }, + { + "name": "kind", + "type": 39, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 4, + "optional": true + }, + { + "name": "kind", + "type": 26, + "optional": false + }, + { + "name": "rpcId", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 4, + "optional": false + }, + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 43, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + }, + { + "name": "ruleId", + "type": 4, + "optional": false + }, + { + "name": "source", + "type": 4, + "optional": false + }, + { + "name": "summary", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 45, + "optional": false + }, + { + "name": "form", + "type": 47, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "update", + "type": 29, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 46 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 36, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 52, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + }, + { + "name": "sections", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 55 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "text", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + }, + { + "name": "summary", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 36, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 59, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 61, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 47, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 59, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + }, + { + "name": "references", + "type": 65, + "optional": false + }, + { + "name": "version", + "type": 69, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 66 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 8, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 67, + "optional": false + }, + { + "name": "compacted", + "type": 20, + "optional": false + }, + { + "name": "inputIndex", + "type": 8, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": false + }, + { + "name": "omittedBytes", + "type": 8, + "optional": false + }, + { + "name": "omittedMessages", + "type": 8, + "optional": false + }, + { + "name": "originalMessages", + "type": 8, + "optional": false + }, + { + "name": "retainedMessages", + "type": 8, + "optional": false + }, + { + "name": "sessionId", + "type": 4, + "optional": false + }, + { + "name": "truncated", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 8, + 68 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 71, + "optional": false + }, + { + "name": "senderSessionId", + "type": 4, + "optional": false + }, + { + "name": "summary", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 61, + "optional": false + }, + { + "name": "kind", + "type": 73, + "optional": false + }, + { + "name": "senderSessionId", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 4, + "optional": false + }, + { + "name": "kind", + "type": 75, + "optional": false + }, + { + "name": "revision", + "type": 8, + "optional": false + }, + { + "name": "round", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 77, + "optional": false + }, + { + "name": "messageId", + "type": 4, + "optional": false + }, + { + "name": "senderId", + "type": 4, + "optional": false + }, + { + "name": "senderName", + "type": 4, + "optional": false + }, + { + "name": "teamId", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 79, + "optional": false + }, + { + "name": "model", + "type": 4, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + }, + { + "name": "replayState", + "type": 80, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 26, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "packages/llm/llm/src/message.ts#UserMessage" + ], + "sources": [ + "packages/llm/llm/src/message.ts:143" + ] + }, + { + "digest": "42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 8, + "optional": false + }, + { + "name": "time", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "runId", + "type": 2, + "optional": false + }, + { + "name": "stopReason", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6 + ] + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-workflow/run-end" + } + ] + }, + "names": [ + "event:tool-workflow/run-end" + ], + "sources": [] + }, + { + "digest": "4312134fc6f9cad507c31390bd3d147535492dd77621ec517c5e85f853d53a03", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "turn/end" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "44818000e56acb97afdb5045ce235eb55e3fb1ccabc9fc7d0750bb38f36a7f8e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "feedback/message-delete" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "44c8c77aa9aa100b0d74d2deee85474b29faa6eee0ac0f662f7abe9931ddd7f2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "subagent/catalog" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "step/start" + } + ] + }, + "names": [ + "event:step/start" + ], + "sources": [] + }, + { + "digest": "45f2a07ea94573123b6ae14db787a40062dec3834f36d8be92c523d40ba28448", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 1, + "optional": true + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "reason", + "type": 1, + "optional": true + }, + { + "name": "toolName", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:approval/asked.data" + ], + "sources": [ + "packages/interaction/user-approval/src/types.ts:44" + ] + }, + { + "digest": "47e5d84f1a93450a78c7f3954b43990d0f629af7c034a56313e5deb63e772c1b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "session-log-deepseek/delivery-accepted" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "retry", + "type": 2, + "optional": false + }, + { + "name": "retryId", + "type": 3, + "optional": false + }, + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "llm/retry-started" + } + ] + }, + "names": [ + "event:llm/retry-started" + ], + "sources": [] + }, + { + "digest": "48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "messageId", + "type": 2, + "optional": false + }, + { + "name": "targetId", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 2, + "optional": false + }, + { + "name": "version", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "team/message/delivered" + } + ] + }, + "names": [ + "event:team/message/delivered" + ], + "sources": [] + }, + { + "digest": "4a9bef9f27a942520cc2279ca7dd3082556e4bff543f66b5c0333e1bbed1cd86", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "senderSessionId", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/subagent/subagent/src/continuation-messages.ts#AgentMessageSource" + ], + "sources": [ + "packages/subagent/subagent/src/continuation-messages.ts:15" + ] + }, + { + "digest": "4ae007ed190a72c92138b22d059b83208079c7551ca13fb59ae8cf5e23693964", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "llm/retry-started" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "4aed17ec726b5397c29f6cb4bcd7905dc1a12bb6140f1657f073cd24380f2af8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "tuple", + "elements": [ + { + "type": 1, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "max_uses", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "web_search" + }, + { + "kind": "literal", + "value": "web_search_20250305" + } + ] + }, + "names": [ + "event:web/deepseek-search-llm-request.data.body.tools" + ], + "sources": [] + }, + { + "digest": "4b7ddaf3710b88ff9b8df66304f5f97bf03688fd19730eedc2e9535203c820ea", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "status", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + } + ] + }, + "names": [ + "event:todo/write.data.todos" + ], + "sources": [] + }, + { + "digest": "4bb451330ff699f36b45539ceb0eb2606ead72084e2c5bb637c36cc80e2252db", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "disposed" + } + ] + }, + "names": [ + "event:turn/end.data.reason[1].reason[3]" + ], + "sources": [ + "packages/core/session/src/types.ts:192" + ] + }, + { + "digest": "4c965666d5b42116945bc356087cd5e1fd549e6dece560785ed60511059a8283", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 12, + 14, + 16, + 19, + 24, + 26, + 31, + 32, + 34, + 35, + 36, + 38, + 47, + 49, + 51, + 53, + 55, + 58, + 59 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 2, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 3, + "optional": true + }, + { + "name": "changes", + "type": 4, + "optional": false + }, + { + "name": "form", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 6, + "optional": false + }, + { + "name": "digest", + "type": 3, + "optional": true + }, + { + "name": "path", + "type": 3, + "optional": false + }, + { + "name": "scope", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 7, + 8, + 9 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 3, + "optional": true + }, + { + "name": "kind", + "type": 15, + "optional": false + }, + { + "name": "rpcId", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 3, + "optional": false + }, + { + "name": "form", + "type": 17, + "optional": false + }, + { + "name": "kind", + "type": 18, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "ruleId", + "type": 3, + "optional": false + }, + { + "name": "source", + "type": 3, + "optional": false + }, + { + "name": "summary", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 20, + "optional": false + }, + { + "name": "form", + "type": 22, + "optional": false + }, + { + "name": "kind", + "type": 23, + "optional": false + }, + { + "name": "update", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 21 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 25, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 27, + "optional": false + }, + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + }, + { + "name": "sections", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 30 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "text", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 17, + "optional": false + }, + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + }, + { + "name": "summary", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 22, + "optional": false + }, + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 39, + "optional": false + }, + { + "name": "references", + "type": 40, + "optional": false + }, + { + "name": "version", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 41 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 42, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 43, + "optional": false + }, + { + "name": "compacted", + "type": 45, + "optional": false + }, + { + "name": "inputIndex", + "type": 42, + "optional": false + }, + { + "name": "label", + "type": 3, + "optional": false + }, + { + "name": "omittedBytes", + "type": 42, + "optional": false + }, + { + "name": "omittedMessages", + "type": 42, + "optional": false + }, + { + "name": "originalMessages", + "type": 42, + "optional": false + }, + { + "name": "retainedMessages", + "type": 42, + "optional": false + }, + { + "name": "sessionId", + "type": 3, + "optional": false + }, + { + "name": "truncated", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 42, + 44 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 17, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "senderSessionId", + "type": 3, + "optional": false + }, + { + "name": "summary", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "senderSessionId", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "revision", + "type": 42, + "optional": false + }, + { + "name": "round", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "messageId", + "type": 3, + "optional": false + }, + { + "name": "senderId", + "type": 3, + "optional": false + }, + { + "name": "senderName", + "type": 3, + "optional": false + }, + { + "name": "teamId", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "replayState", + "type": 57, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 15, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "packages/llm/llm/src/message.ts#MessageSource" + ], + "sources": [ + "packages/llm/llm/src/message.ts:128" + ] + }, + { + "digest": "4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/api/session-controller/src/types.ts#SessionRequestId", + "packages/attachment/attachment/src/brand.ts#AttachmentId", + "packages/compaction/compaction/src/brand.ts#CompactionId", + "packages/core/session/src/types.ts#SessionId", + "packages/experimental/agent-team/src/types.ts#TeamId", + "packages/experimental/agent-team/src/types.ts#TeamMessageId", + "packages/experimental/agent-team/src/types.ts#TeamTaskId", + "packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion", + "packages/goal/goal/src/types.ts#GoalId", + "packages/interaction/commands/src/brand.ts#CommandId", + "packages/interaction/user-approval/src/types.ts#ApprovalRequestId", + "packages/llm/llm-retry/src/brand.ts#RetryId", + "packages/llm/llm/src/brand.ts#MessageId", + "packages/llm/llm/src/brand.ts#ProviderRequestId", + "packages/llm/llm/src/brand.ts#ReasoningEffortId", + "packages/llm/llm/src/brand.ts#ToolCallId", + "packages/schedule/schedule/src/types.ts#ScheduleId", + "packages/session/session-title/src/index.ts#SessionTitleProviderId", + "packages/session/session-title/src/types.ts#SessionTitleProviderId", + "packages/webhook/webhook/src/brand.ts#WebhookDeliveryId", + "packages/webhook/webhook/src/brand.ts#WebhookRuleId", + "packages/webhook/webhook/src/brand.ts#WebhookSourceId", + "packages/workflow/workflow/src/types.ts#WorkflowRunId" + ], + "sources": [ + "packages/api/session-controller/src/types.ts:375", + "packages/attachment/attachment/src/brand.ts:6", + "packages/compaction/compaction/src/brand.ts:4", + "packages/core/session/src/types.ts:19", + "packages/experimental/agent-team/src/types.ts:20", + "packages/experimental/agent-team/src/types.ts:32", + "packages/experimental/agent-team/src/types.ts:8", + "packages/feedback/message-feedback/src/types.ts:14", + "packages/goal/goal/src/types.ts:17", + "packages/interaction/commands/src/brand.ts:31", + "packages/interaction/user-approval/src/types.ts:17", + "packages/llm/llm-retry/src/brand.ts:4", + "packages/llm/llm/src/brand.ts:16", + "packages/llm/llm/src/brand.ts:31", + "packages/llm/llm/src/brand.ts:43", + "packages/llm/llm/src/brand.ts:67", + "packages/schedule/schedule/src/types.ts:10", + "packages/session/session-title/src/index.ts:42", + "packages/session/session-title/src/types.ts:17", + "packages/webhook/webhook/src/brand.ts:12", + "packages/webhook/webhook/src/brand.ts:6", + "packages/webhook/webhook/src/brand.ts:9", + "packages/workflow/workflow/src/types.ts:13" + ] + }, + { + "digest": "4e011753f8263f436efaabfbb3b4db53a98fc55342c5ded2daf7208b217be159", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "team/message/queued" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "4e44c9118152f6829634bfd508e47669ab0497caffa380c94b4be91f9390d35d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "text" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "4e887768586528565381dadbddee6cef555874148089f4579e0b4b1ad096fc9c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "text" + } + ] + }, + "names": [ + "event:web/deepseek-search-llm-request.data.body.messages[0].content[0]", + "packages/llm/llm/src/types.ts#TextBlock" + ], + "sources": [ + "packages/llm/llm/src/types.ts:54", + "packages/web/web-search-deepseek/src/provider.ts:67" + ] + }, + { + "digest": "4e97c3d85c0fc817ee58873c38c25a2474abaaa0af4839311b50b68db3b8cf1a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "senderSessionId", + "type": 3, + "optional": false + }, + { + "name": "summary", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/subagent/subagent/src/continuation-messages.ts#SubagentSettledMessageSource" + ], + "sources": [ + "packages/subagent/subagent/src/continuation-messages.ts:30" + ] + }, + { + "digest": "4f507598cf476214aa7646ce7841a7064a9dcb05d0a1eaaefaf79c2b738f160f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "series" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "4f91f187baeabef5c7950c4780d31bbe39f3345dff591a6cf26ac7e135ac1cf4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 1, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 2, + "optional": false + }, + { + "name": "compacted", + "type": 4, + "optional": false + }, + { + "name": "inputIndex", + "type": 1, + "optional": false + }, + { + "name": "label", + "type": 5, + "optional": false + }, + { + "name": "omittedBytes", + "type": 1, + "optional": false + }, + { + "name": "omittedMessages", + "type": 1, + "optional": false + }, + { + "name": "originalMessages", + "type": 1, + "optional": false + }, + { + "name": "retainedMessages", + "type": 1, + "optional": false + }, + { + "name": "sessionId", + "type": 5, + "optional": false + }, + { + "name": "truncated", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 1, + 3 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[15].references[0]" + ], + "sources": [ + "packages/context/session-reference/src/types.ts:18" + ] + }, + { + "digest": "4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 12, + "optional": true + }, + { + "name": "seq", + "type": 13, + "optional": false + }, + { + "name": "time", + "type": 13, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "member", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 6, + "optional": false + }, + { + "name": "version", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "context", + "type": 3, + "optional": false + }, + { + "name": "description", + "type": 6, + "optional": false + }, + { + "name": "error", + "type": 6, + "optional": true + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "phase", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "fork" + }, + { + "kind": "literal", + "value": "fresh" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 8, + 9, + 10 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "failed" + }, + { + "kind": "literal", + "value": "provisioning" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "team/member" + } + ] + }, + "names": [ + "event:team/member" + ], + "sources": [] + }, + { + "digest": "508c0856b8df8574e396880a756f087699b138630428441fefca4a4380f01a28", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 1, + "optional": false + }, + { + "name": "turn", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 4, + 6, + 11, + 24, + 26 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "interrupted" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "error", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 8, + "optional": false + }, + { + "name": "message", + "type": 8, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 9, + "optional": true + }, + { + "name": "requestId", + "type": 8, + "optional": true + }, + { + "name": "status", + "type": 9, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 12, + "optional": false + }, + { + "name": "reason", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "union", + "types": [ + 14, + 16, + 18, + 20, + 22 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "disposed" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "legacy" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "parent" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 21, + "optional": false + }, + { + "name": "reason", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hook" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "completed" + } + ] + }, + "names": [ + "event:turn/end.data" + ], + "sources": [ + "packages/core/session/src/types.ts:285" + ] + }, + { + "digest": "50cf02d875f8a1f1411f4a6fbcffab9120ccef5f74a47331fbf29480e5f68f48", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2 + ] + }, + { + "kind": "literal", + "value": "fork" + }, + { + "kind": "literal", + "value": "fresh" + } + ] + }, + "names": [ + "event:team/member.data.member.context" + ], + "sources": [] + }, + { + "digest": "511801b0a6470d98566e8385d3dffddb2b67f79fcf73ed6056f46c3cc0a245a7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "success" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "516da4cdd6d2f1e5ce488e648578ca51f40e458f707b803e5b24de870e799415", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 8, + "optional": false + }, + { + "name": "time", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "mode", + "type": 2, + "optional": false + }, + { + "name": "source", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "danger-full-access" + }, + { + "kind": "literal", + "value": "read-only" + }, + { + "kind": "literal", + "value": "workspace-write" + }, + { + "kind": "literal", + "value": "delegation" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "sandbox/mode" + } + ] + }, + "names": [ + "event:sandbox/mode" + ], + "sources": [] + }, + { + "digest": "51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "blockedBy", + "type": 1, + "optional": false + }, + { + "name": "description", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "ownerId", + "type": 2, + "optional": true + }, + { + "name": "revision", + "type": 3, + "optional": false + }, + { + "name": "status", + "type": 4, + "optional": false + }, + { + "name": "subject", + "type": 2, + "optional": false + }, + { + "name": "writeScopes", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 5, + 6, + 7, + 8 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "deleted" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + } + ] + }, + "names": [ + "packages/experimental/agent-team/src/types.ts#TeamTaskSnapshot" + ], + "sources": [ + "packages/experimental/agent-team/src/types.ts:74" + ] + }, + { + "digest": "529e0ccd0e34079ab4ff21ed87f810cee0388246263bf22e8aba5986b56fae05", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 1, + "value": 2 + } + ] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 0, + 1, + 3, + 4, + 5, + 6 + ] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:tool/result.data.meta[5][0][6]", + "event:tool/result.data.meta[6]" + ], + "sources": [ + "packages/util/values/src/index.ts:4" + ] + }, + { + "digest": "530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "change" + }, + { + "kind": "literal", + "value": "initial" + }, + { + "kind": "literal", + "value": "resume" + }, + { + "kind": "literal", + "value": "series" + } + ] + }, + "names": [ + "packages/core/session/src/types.ts#RequestHeaderReason" + ], + "sources": [ + "packages/core/session/src/types.ts:261" + ] + }, + { + "digest": "557e148d30b79ee48645850832b30c717d7c3b64641deb5d6133b8b4e91ee0b8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "agent-preset/selected" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "55e3b6df99b0d19241e57618b04b84824c3df80ed102e440719139300ea703f6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:tool/result.data.error" + ], + "sources": [ + "packages/core/session/src/types.ts:358" + ] + }, + { + "digest": "56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "interrupted" + } + ] + }, + "names": [ + "event:turn/end.data.reason[5]" + ], + "sources": [ + "packages/core/session/src/types.ts:220" + ] + }, + { + "digest": "570e85022434613394418ad33e77e9f1f7a05699b33d0b62eae0677a638c3833", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool/result" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 10, + 11 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 4, + "optional": true + }, + { + "name": "surfaceOp", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 6, + 7 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 3, + "optional": false + }, + { + "name": "op", + "type": 8, + "optional": false + }, + { + "name": "startSeq", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "surfaceOp", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "SessionEventEnvelope" + ], + "sources": [] + }, + { + "digest": "57f3f606ff8b6c84d9c443faace9d1dd10cca0354f37a4f515ef075f7c351b90", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "stop" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "587bb11da730e06aa52b62c087d217d6f8f22459b25f31cb9e1e1baf17944e5e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "session/title-llm-request" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "58f94390b982f8a5ac1f4f235ba612557f4a1dc4a995d8b1cec63ce7639decb0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "image/png" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "5abab3a27fd2736660ecddd94b28d6bbf114986dd894880153bd1be3ccacb563", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "text-delta" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "5b232a9b9bfcc0fc6794dab7865bcf203a65785c5de5aa647a51228bff628413", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "after" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "5b6fb1f226ff56402db08a6aae82af76100b14056271875f69ec526a1ae05d51", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "commandId", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "sourceEventSeq", + "type": 5, + "optional": true + }, + { + "name": "text", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "literal", + "value": "success" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:command/done.data" + ], + "sources": [ + "packages/interaction/commands/src/types.ts:112" + ] + }, + { + "digest": "5bd310104cfe448e530beb11422d2f791e2e32e8e95fdc528a15f9ca693dae75", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "resource-cost" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "preset", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "permission/preset" + } + ] + }, + "names": [ + "event:permission/preset" + ], + "sources": [] + }, + { + "digest": "5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "SessionEventEnvelope[38].sourceEventSeqs", + "SessionEventEnvelope[51].sourceEventSeqs", + "SessionEventEnvelope[54].sourceEventSeqs", + "event:assistant/attempt.data.stream[0][0].dt", + "event:assistant/attempt.data.stream[0][1].dt", + "event:assistant/attempt.data.stream[0][2].dt", + "event:compaction/prune.data.shadowedSeqs", + "event:compaction/summary.data[0].shadowedSeqs", + "event:compaction/summary.data[1].shadowedSeqs", + "event:session/title-llm-request.data.messageSeqs", + "event:session/title.data.messageSeqs", + "event:system/message.sourceEventSeqs", + "event:tool/result.sourceEventSeqs", + "event:user/message.sourceEventSeqs" + ], + "sources": [] + }, + { + "digest": "5d17f27a1ef9f7026c0232714adf7573961536d75f3303ca97973975f5af170e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool-workflow/agent-end" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "5d542ac7bdf13310c46e1a884dec9fb18ad8c6e153cff207e639cd7bf9d7e694", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 1, + "optional": false + }, + { + "name": "childId", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 2, + "optional": true + }, + { + "name": "mode", + "type": 3, + "optional": false + }, + { + "name": "version", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "one-shot" + }, + { + "kind": "literal", + "value": 0 + } + ] + }, + "names": [ + "event:subagent/catalog.data[0]" + ], + "sources": [] + }, + { + "digest": "5dd766ccd1702a1142ad8b4bd44f2242f8e8a6752669ad3de0a5d224329b1438", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "shadowedRange", + "type": 1, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 3, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 2, + "optional": false + }, + { + "name": "start", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 2 + } + ] + }, + "names": [ + "event:compaction/prune.data" + ], + "sources": [ + "packages/compaction/compaction/src/types.ts:82" + ] + }, + { + "digest": "5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "inherited", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "session/end-seed" + } + ] + }, + "names": [ + "event:session/end-seed" + ], + "sources": [] + }, + { + "digest": "5ebce1317eacd2c148456627a09668e927ee15c204f0643f5ea76b46b54a2647", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "subagent/descriptor" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "5ed158429dfa6a8633e6ba94b8c81190616c24b65c1777bf1c60cde9dd499a2f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 3, + 5, + 10, + 12 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 6, + "optional": false + }, + { + "name": "kind", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 7, + "optional": false + }, + { + "name": "message", + "type": 7, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 8, + "optional": true + }, + { + "name": "requestId", + "type": 7, + "optional": true + }, + { + "name": "status", + "type": 8, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 6, + "optional": false + }, + { + "name": "kind", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#FinishReason" + ], + "sources": [ + "packages/llm/llm/src/types.ts:139" + ] + }, + { + "digest": "5eedf09e88b5ed9ca2d5efdb5a32cd4f51352564af5ff82927c17d841a2e3d9e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "tuple", + "elements": [ + { + "type": 1, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "isError", + "type": 24, + "optional": true + }, + { + "name": "toolCallId", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 1, + 4, + 7, + 9, + 11, + 21 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 12, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 13, + "optional": false + }, + { + "name": "height", + "type": 13, + "optional": false + }, + { + "name": "mediaType", + "type": 14, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 19, + "optional": true + }, + { + "name": "width", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 15, + 16, + 17, + 18 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 13, + "optional": false + }, + { + "name": "width", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 22, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 13, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + } + ] + }, + "names": [ + "event:tool/result.data.message.content" + ], + "sources": [] + }, + { + "digest": "5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "childId", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 2, + "optional": false + }, + { + "name": "phase", + "type": 2, + "optional": true + }, + { + "name": "runId", + "type": 2, + "optional": false + }, + { + "name": "seq", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool-workflow/agent-start" + } + ] + }, + "names": [ + "event:tool-workflow/agent-start" + ], + "sources": [] + }, + { + "digest": "5f758f3de5f00c18968a7c6d347083aac5c9ae13f6a79c78f6611b23c4973564", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "label", + "type": 1, + "optional": true + }, + { + "name": "mode", + "type": 2, + "optional": false + }, + { + "name": "provider", + "type": 1, + "optional": false + }, + { + "name": "version", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "one-shot" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "packages/subagent/subagent/src/descriptor.ts#OneShotSubagentDescriptorData" + ], + "sources": [ + "packages/subagent/subagent/src/descriptor.ts:61" + ] + }, + { + "digest": "6091f64d426ada2adc646094a6fe662a1b81d9ee5770929fe76800524e2c89a8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 36, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 91, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 2, + "optional": false + }, + { + "name": "messageSeqs", + "type": 3, + "optional": false + }, + { + "name": "messages", + "type": 4, + "optional": false + }, + { + "name": "route", + "type": 90, + "optional": false + }, + { + "name": "system", + "type": 9, + "optional": false + }, + { + "name": "titleProvider", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 9, + "optional": false + }, + { + "name": "role", + "type": 30, + "optional": false + }, + { + "name": "source", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "union", + "types": [ + 8, + 11, + 20, + 23, + 26, + 28 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 9, + "optional": false + }, + { + "name": "id", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 12, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 9, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 13, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": true + }, + { + "name": "originalDimensions", + "type": 18, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 14, + 15, + 16, + 17 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 21, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 9, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "isError", + "type": 24, + "optional": true + }, + { + "name": "toolCallId", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "union", + "types": [ + 31, + 32, + 33 + ] + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 35, + 45, + 47, + 48, + 51, + 56, + 58, + 63, + 64, + 65, + 67, + 69, + 70, + 77, + 79, + 81, + 83, + 85, + 88, + 89 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 36, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 9, + "optional": true + }, + { + "name": "changes", + "type": 37, + "optional": false + }, + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 38 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 39, + "optional": false + }, + { + "name": "digest", + "type": 9, + "optional": true + }, + { + "name": "path", + "type": 9, + "optional": false + }, + { + "name": "scope", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 40, + 41, + 42 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 9, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 9, + "optional": true + }, + { + "name": "kind", + "type": 33, + "optional": false + }, + { + "name": "rpcId", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 9, + "optional": false + }, + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "provider", + "type": 9, + "optional": false + }, + { + "name": "ruleId", + "type": 9, + "optional": false + }, + { + "name": "source", + "type": 9, + "optional": false + }, + { + "name": "summary", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 52, + "optional": false + }, + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "update", + "type": 36, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 53 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "name", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 59, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + }, + { + "name": "sections", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 62 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 9, + "optional": false + }, + { + "name": "text", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + }, + { + "name": "summary", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 66, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 68, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 66, + "optional": false + }, + { + "name": "kind", + "type": 71, + "optional": false + }, + { + "name": "references", + "type": 72, + "optional": false + }, + { + "name": "version", + "type": 76, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 73 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 2, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 74, + "optional": false + }, + { + "name": "compacted", + "type": 24, + "optional": false + }, + { + "name": "inputIndex", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 9, + "optional": false + }, + { + "name": "omittedBytes", + "type": 2, + "optional": false + }, + { + "name": "omittedMessages", + "type": 2, + "optional": false + }, + { + "name": "originalMessages", + "type": 2, + "optional": false + }, + { + "name": "retainedMessages", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 9, + "optional": false + }, + { + "name": "truncated", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 75 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "senderSessionId", + "type": 9, + "optional": false + }, + { + "name": "summary", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 68, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "senderSessionId", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 9, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "revision", + "type": 2, + "optional": false + }, + { + "name": "round", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 84, + "optional": false + }, + { + "name": "messageId", + "type": 9, + "optional": false + }, + { + "name": "senderId", + "type": 9, + "optional": false + }, + { + "name": "senderName", + "type": 9, + "optional": false + }, + { + "name": "teamId", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 86, + "optional": false + }, + { + "name": "model", + "type": 9, + "optional": false + }, + { + "name": "provider", + "type": 9, + "optional": false + }, + { + "name": "replayState", + "type": 87, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "plugin", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 9, + "optional": false + }, + { + "name": "provider", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session/title-llm-request" + } + ] + }, + "names": [ + "event:session/title-llm-request" + ], + "sources": [] + }, + { + "digest": "611cae9a3539733aa0c42f66035a7d90f19dcbbdfb5d9e3b628b13705253942d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "llm/retry" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "6132f7814b28287473605ebc6dacd63c7c699435452487da6152bc735b8ba8c4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "message", + "type": 1, + "optional": false + }, + { + "name": "teamId", + "type": 6, + "optional": false + }, + { + "name": "version", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "senderId", + "type": 6, + "optional": false + }, + { + "name": "senderName", + "type": 6, + "optional": false + }, + { + "name": "targetId", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 4, + 8, + 10, + 12, + 14, + 24 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "isError", + "type": 5, + "optional": true + }, + { + "name": "toolCallId", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 15, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 16, + "optional": false + }, + { + "name": "height", + "type": 16, + "optional": false + }, + { + "name": "mediaType", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "originalDimensions", + "type": 22, + "optional": true + }, + { + "name": "width", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 18, + 19, + 20, + 21 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 16, + "optional": false + }, + { + "name": "width", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 25, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 16, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": 2 + } + ] + }, + "names": [ + "event:team/message/queued.data" + ], + "sources": [ + "packages/experimental/agent-team/src/types.ts:225" + ] + }, + { + "digest": "616f93a5047de668e1c30d57b1786abd76beeb4123599637ec1dc5290320ede5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool-call-chunks" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "62e6a429ff3f390a46ac39fb468692032f42936ca0db19b53454184189401379", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3, + 4, + 5, + 6 + ] + }, + { + "kind": "literal", + "value": "block" + }, + { + "kind": "literal", + "value": "complete" + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "literal", + "value": "edit" + }, + { + "kind": "literal", + "value": "pause" + }, + { + "kind": "literal", + "value": "resume" + } + ] + }, + "names": [ + "event:goal/change.data[0].operation" + ], + "sources": [] + }, + { + "digest": "62ecb62dc0fe1b5e70217f8a80c39256a40ee3a1917be14e5ca899d20c35f7fd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "next-turn" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "6326a7ecfed005f3d54b737ab9d702da4daddb75cfc635d9096e9abb4788024e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 1, + "optional": false + }, + { + "name": "index", + "type": 15, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 7, + 9, + 11, + 13, + 23 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 4, + "optional": true + }, + { + "name": "toolCallId", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 1 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 14, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 15, + "optional": false + }, + { + "name": "height", + "type": 15, + "optional": false + }, + { + "name": "mediaType", + "type": 16, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 21, + "optional": true + }, + { + "name": "width", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 17, + 18, + 19, + 20 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 15, + "optional": false + }, + { + "name": "width", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 24, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 15, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": "block-end" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[4]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:395" + ] + }, + { + "digest": "647cb2e3aeb58e27d1d4d60011f208bb66dce257e8654193c533b47c4c22c253", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 1, + "optional": false + }, + { + "name": "index", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 3, + 4, + 5, + 6, + 7 + ] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "block-start" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[0]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:391" + ] + }, + { + "digest": "6487b10ba7542f3c447b1e41a1c6a590b7bfafe0b9b66b49f34028da85bb970f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "agentModel", + "type": 1, + "optional": true + }, + { + "name": "agentProvider", + "type": 1, + "optional": true + }, + { + "name": "agentReasoningEffort", + "type": 1, + "optional": true + }, + { + "name": "label", + "type": 1, + "optional": false + }, + { + "name": "mode", + "type": 2, + "optional": false + }, + { + "name": "persona", + "type": 1, + "optional": true + }, + { + "name": "provider", + "type": 1, + "optional": false + }, + { + "name": "toolFilter", + "type": 3, + "optional": true + }, + { + "name": "version", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "object", + "properties": [ + { + "name": "allow", + "type": 4, + "optional": true + }, + { + "name": "deny", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 1 + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "packages/subagent/subagent/src/descriptor.ts#ContinuableSubagentDescriptorData" + ], + "sources": [ + "packages/subagent/subagent/src/descriptor.ts:72" + ] + }, + { + "digest": "64cfc4dbd3739f0ee040689482cb27058890f1d6113abb1de756836318aeebbc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 2, + "optional": false + }, + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 3, + "optional": true + }, + { + "name": "requestId", + "type": 2, + "optional": true + }, + { + "name": "status", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "aborted" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[6].reason[0]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:134" + ] + }, + { + "digest": "658d895d187b26d83f3b53109a3bf77bbfba9f6f9a6d4bc4ee4315b4eb36239e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "prompt", + "type": 1, + "optional": false + }, + { + "name": "scheduledAt", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "at" + } + ] + }, + "names": [ + "packages/schedule/schedule/src/types.ts#AtScheduleRecord" + ], + "sources": [ + "packages/schedule/schedule/src/types.ts:27" + ] + }, + { + "digest": "6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "context", + "type": 1, + "optional": false + }, + { + "name": "description", + "type": 4, + "optional": false + }, + { + "name": "error", + "type": 4, + "optional": true + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "phase", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 3 + ] + }, + { + "kind": "literal", + "value": "fork" + }, + { + "kind": "literal", + "value": "fresh" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 6, + 7, + 8 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "failed" + }, + { + "kind": "literal", + "value": "provisioning" + } + ] + }, + "names": [ + "packages/experimental/agent-team/src/types.ts#TeamMemberSnapshot" + ], + "sources": [ + "packages/experimental/agent-team/src/types.ts:47" + ] + }, + { + "digest": "66ece68282d8c06330fb83329a24ae54c386d5ccfab296bbf18369da8beb7d14", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "everySeconds", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "prompt", + "type": 2, + "optional": false + }, + { + "name": "scheduledAt", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "every" + } + ] + }, + "names": [ + "packages/schedule/schedule/src/types.ts#EveryScheduleRecord" + ], + "sources": [ + "packages/schedule/schedule/src/types.ts:39" + ] + }, + { + "digest": "672c4aa2a9b736e3d6e5da6346d2e2ed5e3573f2d0baafa18e7407c9f3fee3db", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/skill/skill/src/index.ts#SkillInvocationSource" + ], + "sources": [ + "packages/skill/skill/src/index.ts:146" + ] + }, + { + "digest": "67d5f73ceae82f5265258142ad6cf172de2c1fef93ba551b7f0454e0a93bffe0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 1, + "optional": true + }, + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "provider", + "type": 2, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 2, + "optional": true + }, + { + "name": "stop", + "type": 3, + "optional": true + }, + { + "name": "temperature", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 2 + } + ] + }, + "names": [ + "packages/llm/llm/src/call-config.ts#LlmCallConfig" + ], + "sources": [ + "packages/llm/llm/src/call-config.ts:23" + ] + }, + { + "digest": "68141cbe3994a5ffcd064c07c75996538437fd9dbf77ce8a2927e89ec577fabe", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "max-tokens" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "688e853dc149f0d30b31765c12ec7e3be74169bdf1a4c6463a10b945d50cc1b8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "reasoning-chunks" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "68f44f59e91c41395076cb77265981a601680ec8a381bbbeb6ab754ffdc94573", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "user" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "69d238a6e9b08d67f601b1825962963d8d3523cb69ebf6208c697dc5d058c199", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "interrupted" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "6a4f72e2e179e17b922f2a9392c0e1c8f8c707f32494372454850a3eb184a6e7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 1, + "optional": true + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "rpcId", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "user" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[10]" + ], + "sources": [ + "packages/api/session-controller/src/types.ts:380" + ] + }, + { + "digest": "6ab45ac76839722df912f3ba50f1c6b41f2d614bde11f1aabfae04117ccdc80d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "assistant/message" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "6bbe793f6a93dc8d50a4d4d3fbb6f8ed15650bcb7e151d28669628f7b7c3fc25", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "blockedReason", + "type": 1, + "optional": true + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "maxGoalRounds", + "type": 3, + "optional": false + }, + { + "name": "objective", + "type": 2, + "optional": false + }, + { + "name": "phase", + "type": 4, + "optional": false + }, + { + "name": "revision", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 2, + "optional": false + }, + { + "name": "message", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 5, + 6, + 7, + 8 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "literal", + "value": "complete" + }, + { + "kind": "literal", + "value": "paused" + } + ] + }, + "names": [ + "packages/goal/goal/src/types.ts#GoalSnapshot" + ], + "sources": [ + "packages/goal/goal/src/types.ts:60" + ] + }, + { + "digest": "6c03546143cdf6ae88f1745ac98c5139efa1588d1415249bbc5f3104d32ab800", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "parent" + } + ] + }, + "names": [ + "event:turn/end.data.reason[1].reason[1]" + ], + "sources": [ + "packages/core/session/src/types.ts:190" + ] + }, + { + "digest": "6c1cccb433ddc153facb380c7dda956237ba5c31f8a532a69142dc277890cc9b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool-calls" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "6c5a37d42ebdb12db4410699259335f1e149e3a0260425684c00b153afc8990d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + }, + { + "name": "reason", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hook" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:turn/end.data.reason[1].reason[2]" + ], + "sources": [ + "packages/core/session/src/types.ts:191" + ] + }, + { + "digest": "6cda0d6d3b4336d83998fe580628426df81c2afbab5d768b8813b56d569120e8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "feedback/message-put" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "6d63f25a28b70801937a46b1058be5f52d4efbaf3516141470463ae469b59c23", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "parameters", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 2, + "value": 4 + } + ] + }, + { + "kind": "opaque", + "reason": "unknown" + } + ] + }, + "names": [ + "event:request/header.data.header.tools" + ], + "sources": [] + }, + { + "digest": "6e52dd55a1d48954ed5464ec46fe34d9eb65f709baabc9561e7a279910b0c406", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "agentPreset", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent-preset/selected.data" + ], + "sources": [ + "packages/preset/agent-presets/src/session.ts:28" + ] + }, + { + "digest": "6ebcc2dbec9f2dabca9f668ae6d39e877899b180740570f453409db339221df3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "skill-catalog" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "6f091fe32ab24407236090ca9d710bbaf59bb57466e9f3b8f1f6e4862bc863c6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "reasoning-delta" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "6f22a9d02b9a1f1157f3b0e7bdccd7e7b12b085c4efdb709dfc74a465cd4e590", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 1, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 2, + "optional": true + }, + { + "name": "changes", + "type": 3, + "optional": false + }, + { + "name": "form", + "type": 9, + "optional": false + }, + { + "name": "kind", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 5, + "optional": false + }, + { + "name": "digest", + "type": 2, + "optional": true + }, + { + "name": "path", + "type": 2, + "optional": false + }, + { + "name": "scope", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 6, + 7, + 8 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + } + ] + }, + "names": [ + "packages/context/agent-instructions/src/state.ts#AgentInstructionSource" + ], + "sources": [ + "packages/context/agent-instructions/src/state.ts:37" + ] + }, + { + "digest": "6f611bed14b2106542480c4a230b4a83baf76dc0d8ac0cd13319a7cb8dae7675", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "web/deepseek-search-llm-request" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "retry", + "type": 1, + "optional": false + }, + { + "name": "retryId", + "type": 2, + "optional": false + }, + { + "name": "step", + "type": 1, + "optional": false + }, + { + "name": "turn", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/llm/llm-retry/src/types.ts#LlmRetryStartedEventData" + ], + "sources": [ + "packages/llm/llm-retry/src/types.ts:43" + ] + }, + { + "digest": "6f9e00383836275e46c0a9915767f9bb561be5fc30aae490174908a93e99a912", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "snapshot" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "70b082fe78991edf13cf66e1b4d06cd2dcff9cacfd76f8cfb996bfb61939e7b2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "request/context" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "7115142af1bff1ad9b52c3c785109bf5c989e77e3ddfe899014e213731352042", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "inserted", + "type": 1, + "optional": false + }, + { + "name": "outcome", + "type": 85, + "optional": true + }, + { + "name": "removedCount", + "type": 10, + "optional": true + }, + { + "name": "start", + "type": 10, + "optional": false + }, + { + "name": "target", + "type": 86, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "role", + "type": 28, + "optional": false + }, + { + "name": "source", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 8, + 18, + 21, + 24, + 26 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "mediaType", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "originalDimensions", + "type": 16, + "optional": true + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 12, + 13, + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 22, + "optional": true + }, + { + "name": "toolCallId", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 30, + 40, + 42, + 43, + 46, + 51, + 53, + 58, + 59, + 60, + 62, + 64, + 65, + 72, + 74, + 76, + 78, + 80, + 83, + 84 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 31, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 6, + "optional": true + }, + { + "name": "changes", + "type": 32, + "optional": false + }, + { + "name": "form", + "type": 38, + "optional": false + }, + { + "name": "kind", + "type": 39, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 33 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 34, + "optional": false + }, + { + "name": "digest", + "type": 6, + "optional": true + }, + { + "name": "path", + "type": 6, + "optional": false + }, + { + "name": "scope", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 35, + 36, + 37 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 6, + "optional": false + }, + { + "name": "kind", + "type": 41, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 6, + "optional": true + }, + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "rpcId", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 6, + "optional": false + }, + { + "name": "form", + "type": 44, + "optional": false + }, + { + "name": "kind", + "type": 45, + "optional": false + }, + { + "name": "provider", + "type": 6, + "optional": false + }, + { + "name": "ruleId", + "type": 6, + "optional": false + }, + { + "name": "source", + "type": 6, + "optional": false + }, + { + "name": "summary", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 47, + "optional": false + }, + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "update", + "type": 31, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 48 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 38, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + }, + { + "name": "sections", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 57 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "text", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 44, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + }, + { + "name": "summary", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 38, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 61, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 63, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 49, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 61, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + }, + { + "name": "references", + "type": 67, + "optional": false + }, + { + "name": "version", + "type": 71, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 68 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 10, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 69, + "optional": false + }, + { + "name": "compacted", + "type": 22, + "optional": false + }, + { + "name": "inputIndex", + "type": 10, + "optional": false + }, + { + "name": "label", + "type": 6, + "optional": false + }, + { + "name": "omittedBytes", + "type": 10, + "optional": false + }, + { + "name": "omittedMessages", + "type": 10, + "optional": false + }, + { + "name": "originalMessages", + "type": 10, + "optional": false + }, + { + "name": "retainedMessages", + "type": 10, + "optional": false + }, + { + "name": "sessionId", + "type": 6, + "optional": false + }, + { + "name": "truncated", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 10, + 70 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 44, + "optional": false + }, + { + "name": "kind", + "type": 73, + "optional": false + }, + { + "name": "senderSessionId", + "type": 6, + "optional": false + }, + { + "name": "summary", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 63, + "optional": false + }, + { + "name": "kind", + "type": 75, + "optional": false + }, + { + "name": "senderSessionId", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 6, + "optional": false + }, + { + "name": "kind", + "type": 77, + "optional": false + }, + { + "name": "revision", + "type": 10, + "optional": false + }, + { + "name": "round", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 79, + "optional": false + }, + { + "name": "messageId", + "type": 6, + "optional": false + }, + { + "name": "senderId", + "type": 6, + "optional": false + }, + { + "name": "senderName", + "type": 6, + "optional": false + }, + { + "name": "teamId", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 81, + "optional": false + }, + { + "name": "model", + "type": 6, + "optional": false + }, + { + "name": "provider", + "type": 6, + "optional": false + }, + { + "name": "replayState", + "type": 82, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "plugin", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "canceled" + }, + { + "kind": "union", + "types": [ + 87, + 88 + ] + }, + { + "kind": "literal", + "value": "next-step" + }, + { + "kind": "literal", + "value": "next-turn" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data" + ], + "sources": [ + "packages/core/agent/src/types.ts:87" + ] + }, + { + "digest": "7180844e1066230c5e891f1b9ccd942423b0fe48aac6ded7eef2a285b288a732", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "service-stability" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "719c51f67074f97034a6c94297fe1f4b5d3c8d392c57586dab732aa5398c7a91", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + }, + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "provider", + "type": 2, + "optional": false + }, + { + "name": "replayState", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "opaque", + "reason": "unknown" + } + ] + }, + "names": [ + "packages/llm/llm/src/message.ts#ModelMessageSource" + ], + "sources": [ + "packages/llm/llm/src/message.ts:24" + ] + }, + { + "digest": "71bb3254ec8f1e491ce63b762a7b5f18709de23aa4e8abcc8b886f4b6ce051dd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 1, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "texts", + "type": 3, + "optional": false + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning-chunks" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][1]" + ], + "sources": [ + "packages/llm/llm/src/assistant-stream.ts:28" + ] + }, + { + "digest": "72ec79127a9c0d0241b1106a74cc2b24c81ce467170f1c3c93f7b71a9496d227", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "union", + "types": [ + 0, + 2, + 3, + 4, + 5, + 6 + ] + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 3, + "value": 1 + } + ] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:tool/result.data.meta[5]", + "event:tool/result.data.meta[5][0][5]" + ], + "sources": [] + }, + { + "digest": "7382c4fc95fe8c5db65ca4d9da572b115d0dbb818b7f32c22ab040318ffbb4bc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "remove" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "757013fff5879d73d5753d8d1120faab56110e88ac3932485ab7e034d9e9ce61", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3, + 4, + 5, + 6 + ] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "literal", + "value": "tool-result" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#ContentBlockType" + ], + "sources": [ + "packages/llm/llm/src/types.ts:122" + ] + }, + { + "digest": "7624ca28c61ca0eaa4f015140f3cea6e0c16cce2f2960fab5f531203d253903e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "create" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "763c8a20af487263a0080548274f7437ef4a86e0ba8769127d1ced10a72c664d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 23, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 9 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "cleared", + "type": 3, + "optional": false + }, + { + "name": "clearedAt", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "operation", + "type": 7, + "optional": false + }, + { + "name": "version", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "revision", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "goal/change" + }, + { + "kind": "literal", + "value": "clear" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "createdAt", + "type": 5, + "optional": false + }, + { + "name": "goal", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "operation", + "type": 17, + "optional": false + }, + { + "name": "roundsStarted", + "type": 5, + "optional": false + }, + { + "name": "updatedAt", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "blockedReason", + "type": 11, + "optional": true + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "maxGoalRounds", + "type": 5, + "optional": false + }, + { + "name": "objective", + "type": 4, + "optional": false + }, + { + "name": "phase", + "type": 12, + "optional": false + }, + { + "name": "revision", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 4, + "optional": false + }, + { + "name": "message", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "literal", + "value": "complete" + }, + { + "kind": "literal", + "value": "paused" + }, + { + "kind": "union", + "types": [ + 15, + 18, + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "block" + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "literal", + "value": "resume" + }, + { + "kind": "literal", + "value": "edit" + }, + { + "kind": "literal", + "value": "pause" + }, + { + "kind": "literal", + "value": true + } + ] + }, + "names": [ + "event:goal/change" + ], + "sources": [] + }, + { + "digest": "76632533f67879dcb346fcae640540dbc89b2472b0bce88c59f7e48fc5c0e668", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "delegation" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "7973d82bd129b261f16a9a20d44df834ec60efc52a63242b4943a2515ae0894f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "role", + "type": 26, + "optional": false + }, + { + "name": "source", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "union", + "types": [ + 3, + 6, + 16, + 19, + 22, + 24 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 8, + "optional": false + }, + { + "name": "height", + "type": 8, + "optional": false + }, + { + "name": "mediaType", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": true + }, + { + "name": "originalDimensions", + "type": 14, + "optional": true + }, + { + "name": "width", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 10, + 11, + 12, + 13 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 8, + "optional": false + }, + { + "name": "width", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 8, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "isError", + "type": 20, + "optional": true + }, + { + "name": "toolCallId", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "union", + "types": [ + 28, + 33, + 35, + 37, + 39, + 41, + 43 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 29, + "optional": false + }, + { + "name": "kind", + "type": 30, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + }, + { + "name": "sections", + "type": 31, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 32 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "text", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 30, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + }, + { + "name": "summary", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 36, + "optional": false + }, + { + "name": "kind", + "type": 30, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 38, + "optional": false + }, + { + "name": "kind", + "type": 30, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 30, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 30, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 30, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "packages/llm/llm/src/message.ts#SystemMessage" + ], + "sources": [ + "packages/llm/llm/src/message.ts:158" + ] + }, + { + "digest": "79ddc907312f97cf9553d4841e8b3fc937097bd8d97900a790efad3c05255e99", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "todos", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "status", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 5, + 6, + 7 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + } + ] + }, + "names": [ + "event:todo/write.data" + ], + "sources": [ + "packages/todo/tool-todo/src/types.ts:31" + ] + }, + { + "digest": "7a651f3b672c52fb615f22c0ce9cb3cc5b22aad92e6bbeefb0808fd7b9233a50", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "session/title" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "7af85bf70d4eafce63e739adae38ea85501e68df3a0e22f97f3a0be90fd9e4cb", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 1, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "surfaceOp", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 2, + "optional": false + }, + { + "name": "op", + "type": 6, + "optional": false + }, + { + "name": "startSeq", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "SessionEventEnvelope[6]" + ], + "sources": [] + }, + { + "digest": "7c6129b6961cf4b2a030f36c78d2b0a5be38617651a9b81322e054ee512bee06", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "messageId", + "type": 1, + "optional": false + }, + { + "name": "sessionId", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/feedback/message-feedback/src/types.ts#MessageFeedbackDelete" + ], + "sources": [ + "packages/feedback/message-feedback/src/types.ts:46" + ] + }, + { + "digest": "7d226b9fd95b4ba213d4237c22e930a91c3c769c95ca7f92977b9d625ba13438", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 8 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "cleared", + "type": 2, + "optional": false + }, + { + "name": "clearedAt", + "type": 4, + "optional": false + }, + { + "name": "kind", + "type": 5, + "optional": false + }, + { + "name": "operation", + "type": 6, + "optional": false + }, + { + "name": "version", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "revision", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "goal/change" + }, + { + "kind": "literal", + "value": "clear" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "createdAt", + "type": 4, + "optional": false + }, + { + "name": "goal", + "type": 9, + "optional": false + }, + { + "name": "kind", + "type": 5, + "optional": false + }, + { + "name": "operation", + "type": 16, + "optional": false + }, + { + "name": "roundsStarted", + "type": 4, + "optional": false + }, + { + "name": "updatedAt", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "blockedReason", + "type": 10, + "optional": true + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "maxGoalRounds", + "type": 4, + "optional": false + }, + { + "name": "objective", + "type": 3, + "optional": false + }, + { + "name": "phase", + "type": 11, + "optional": false + }, + { + "name": "revision", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 3, + "optional": false + }, + { + "name": "message", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 12, + 13, + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "literal", + "value": "complete" + }, + { + "kind": "literal", + "value": "paused" + }, + { + "kind": "union", + "types": [ + 14, + 17, + 18, + 19, + 20, + 21 + ] + }, + { + "kind": "literal", + "value": "block" + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "literal", + "value": "edit" + }, + { + "kind": "literal", + "value": "resume" + }, + { + "kind": "literal", + "value": "pause" + } + ] + }, + "names": [ + "packages/goal/goal/src/domain.ts#GoalChangeMeta" + ], + "sources": [ + "packages/goal/goal/src/domain.ts:44" + ] + }, + { + "digest": "7d54a5a5e629e2e7783bfac72683bcc5e969e9f43426b9289826aaf294ff6344", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "category", + "type": 1, + "optional": true + }, + { + "name": "text", + "type": 9, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + { + "kind": "literal", + "value": "instruction-following" + }, + { + "kind": "literal", + "value": "other" + }, + { + "kind": "literal", + "value": "product-interaction" + }, + { + "kind": "literal", + "value": "resource-cost" + }, + { + "kind": "literal", + "value": "security-privacy-permission" + }, + { + "kind": "literal", + "value": "service-stability" + }, + { + "kind": "literal", + "value": "task-result" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/feedback/command-feedback/src/types.ts#FeedbackRecord" + ], + "sources": [ + "packages/feedback/command-feedback/src/types.ts:27" + ] + }, + { + "digest": "7f13860ee89a2d35ce09f958e85a04566cd59cb1de1d73a9619a9b631961dc71", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "session-reference" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "7f5f2a8618c2ece530a18b1358b890fb57fb8c35705e47b66bff2296f8c6e889", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + }, + { + "name": "parameters", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 1, + "value": 3 + } + ] + }, + { + "kind": "opaque", + "reason": "unknown" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#ToolSchema" + ], + "sources": [ + "packages/llm/llm/src/types.ts:411" + ] + }, + { + "digest": "7f7fd5a6b0064f597534b29ff62ef26e786dffccf5e14f654a7d4fcea2c35f04", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "shadowedRange", + "type": 2, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 4, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 3, + "optional": false + }, + { + "name": "start", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "compaction/prune" + } + ] + }, + "names": [ + "event:compaction/prune" + ], + "sources": [] + }, + { + "digest": "7f9365bae9fcb73e1e34061a1acfe0422c1a5fd351848cace9bebbe806f28462", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "runId", + "type": 1, + "optional": false + }, + { + "name": "stopReason", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "error" + } + ] + }, + "names": [ + "packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunEndData" + ], + "sources": [ + "packages/workflow/tool-workflow/src/types.ts:36" + ] + }, + { + "digest": "7f959241c24128b4983b559984811861dc59774b85905c8fa57c6cb0148a3dbc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "revision", + "type": 3, + "optional": false + }, + { + "name": "round", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "packages/goal/goal/src/domain.ts#GoalMessageSource" + ], + "sources": [ + "packages/goal/goal/src/domain.ts:47" + ] + }, + { + "digest": "811229eedcec57979dbadca7dbea832a59f8580cff30cb217fca5e2da16314bd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "allowed-once" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "814d9434e1ddf9078612b3b34322f26e9363b469213335007f7e9ba7fee9464e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#ReasoningBlock" + ], + "sources": [ + "packages/llm/llm/src/types.ts:60" + ] + }, + { + "digest": "82470020b5bfcb1a843e2aa05602af7d37df99e82d01f015c7c589ba054fd89d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "agent-message" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "829a2fb3be6efd4ba36aef8ed2d381a4c6fb2a0aaf50c823a6f3f8290786b809", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "agent/inbox/spliced" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "83079c8a3f733ac3fa603eefa0fbb4737ca4d8b69e7e2e7298b60f9ac4098693", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[16].entries[0]" + ], + "sources": [ + "packages/skill/tool-skill/src/index.ts:40" + ] + }, + { + "digest": "83da72c6857601c75da11212ef847f57d457b9c99f0a82246dcdff3e26700239", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 1, + "optional": false + }, + { + "name": "digest", + "type": 5, + "optional": true + }, + { + "name": "path", + "type": 5, + "optional": false + }, + { + "name": "scope", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/context/agent-instructions/src/render.ts#AgentInstructionChange" + ], + "sources": [ + "packages/context/agent-instructions/src/render.ts:47" + ] + }, + { + "digest": "8465ece5e9d9007c038b357015d849c15262cb0bc25abc59e47692d87ee01a15", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3 + ] + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "error" + } + ] + }, + "names": [ + "packages/workflow/workflow/src/types.ts#WorkflowStopReason" + ], + "sources": [ + "packages/workflow/workflow/src/types.ts:63" + ] + }, + { + "digest": "84ac8c1a66ffc697262e181f42e851259eae14038c113ca329fb4700ccdb1f7c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 1, + "optional": false + }, + { + "name": "message", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/goal/goal/src/types.ts#GoalBlockReason" + ], + "sources": [ + "packages/goal/goal/src/types.ts:52" + ] + }, + { + "digest": "84c24f1209fc3e0153de6ac85d58fd6968b851f955e0e761b92321053956609e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 29, + "optional": true + }, + { + "name": "seq", + "type": 14, + "optional": false + }, + { + "name": "time", + "type": 14, + "optional": false + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 5, + 7, + 9, + 11, + 16 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "interrupted" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "error", + "type": 12, + "optional": false + }, + { + "name": "kind", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 13, + "optional": false + }, + { + "name": "message", + "type": 13, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 14, + "optional": true + }, + { + "name": "requestId", + "type": 13, + "optional": true + }, + { + "name": "status", + "type": 14, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 17, + "optional": false + }, + { + "name": "reason", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "union", + "types": [ + 19, + 21, + 23, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "disposed" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "legacy" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "parent" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 28, + "optional": false + }, + { + "name": "reason", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hook" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "turn/end" + } + ] + }, + "names": [ + "event:turn/end" + ], + "sources": [] + }, + { + "digest": "85e1bd563d6c80c1f377f99f91103fec4e315148a7330f3b3dae7c2d04e33b01", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[7]", + "event:system/message.data.message.source[6]" + ], + "sources": [] + }, + { + "digest": "86560bf0779bb2d93975405fe895d9d42c1020ab49fbbd2a06e1b3c74fbbc257", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "role", + "type": 27, + "optional": false + }, + { + "name": "source", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 4, + 7, + 17, + 20, + 23, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "mediaType", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 15, + "optional": true + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 11, + 12, + 13, + 14 + ] + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "isError", + "type": 21, + "optional": true + }, + { + "name": "toolCallId", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 29, + 39, + 41, + 42, + 45, + 50, + 52, + 57, + 58, + 59, + 61, + 63, + 64, + 71, + 73, + 75, + 77, + 79, + 82, + 83 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 30, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 5, + "optional": true + }, + { + "name": "changes", + "type": 31, + "optional": false + }, + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 32 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 33, + "optional": false + }, + { + "name": "digest", + "type": 5, + "optional": true + }, + { + "name": "path", + "type": 5, + "optional": false + }, + { + "name": "scope", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 34, + 35, + 36 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 5, + "optional": true + }, + { + "name": "kind", + "type": 27, + "optional": false + }, + { + "name": "rpcId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 5, + "optional": false + }, + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "ruleId", + "type": 5, + "optional": false + }, + { + "name": "source", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 46, + "optional": false + }, + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + }, + { + "name": "update", + "type": 30, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 47 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 53, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "sections", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 56 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "text", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 60, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 60, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + }, + { + "name": "references", + "type": 66, + "optional": false + }, + { + "name": "version", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 67 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 9, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 68, + "optional": false + }, + { + "name": "compacted", + "type": 21, + "optional": false + }, + { + "name": "inputIndex", + "type": 9, + "optional": false + }, + { + "name": "label", + "type": 5, + "optional": false + }, + { + "name": "omittedBytes", + "type": 9, + "optional": false + }, + { + "name": "omittedMessages", + "type": 9, + "optional": false + }, + { + "name": "originalMessages", + "type": 9, + "optional": false + }, + { + "name": "retainedMessages", + "type": 9, + "optional": false + }, + { + "name": "sessionId", + "type": 5, + "optional": false + }, + { + "name": "truncated", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 9, + 69 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 72, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 74, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 76, + "optional": false + }, + { + "name": "revision", + "type": 9, + "optional": false + }, + { + "name": "round", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "messageId", + "type": 5, + "optional": false + }, + { + "name": "senderId", + "type": 5, + "optional": false + }, + { + "name": "senderName", + "type": 5, + "optional": false + }, + { + "name": "teamId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "model", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "replayState", + "type": 81, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 27, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted" + ], + "sources": [] + }, + { + "digest": "86755151e6c4694ce5ae9511313e1e50f9fb6ead49c67d0a93c056e8650396c4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "union", + "types": [ + 2, + 8, + 65, + 67 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 3, + "optional": false + }, + { + "name": "dt", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "index", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": true + }, + { + "name": "time0", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 6 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-call-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 9, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 10, + 12, + 37, + 40, + 42, + 44, + 61 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "index", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": true + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 13, + "optional": false + }, + { + "name": "index", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 14, + 16, + 25, + 28, + 32, + 34 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 6, + "optional": false + }, + { + "name": "height", + "type": 6, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 6, + "optional": false + }, + { + "name": "width", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 29, + "optional": false + }, + { + "name": "isError", + "type": 30, + "optional": true + }, + { + "name": "toolCallId", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 31, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 13 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 38, + "optional": false + }, + { + "name": "index", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 39, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 15, + 24, + 27, + 31, + 33, + 35 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 6, + "optional": false + }, + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 41, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 6, + "optional": false + }, + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 45, + "optional": false + }, + { + "name": "replayState", + "type": 57, + "optional": true + }, + { + "name": "type", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 46, + 49, + 51, + 53, + 55 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 47, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 4, + "optional": false + }, + { + "name": "message", + "type": 4, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 6, + "optional": true + }, + { + "name": "requestId", + "type": 4, + "optional": true + }, + { + "name": "status", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 47, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 58, + "optional": true + }, + { + "name": "response", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 59 + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 62, + "optional": false + }, + { + "name": "usage", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 6, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 6, + "optional": true + }, + { + "name": "inputTokens", + "type": 6, + "optional": false + }, + { + "name": "outputTokens", + "type": 6, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 6, + "optional": true + }, + { + "name": "totalTokens", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 5, + "optional": false + }, + { + "name": "index", + "type": 6, + "optional": false + }, + { + "name": "texts", + "type": 3, + "optional": false + }, + { + "name": "time0", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 5, + "optional": false + }, + { + "name": "index", + "type": 6, + "optional": false + }, + { + "name": "texts", + "type": 3, + "optional": false + }, + { + "name": "time0", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-chunks" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream", + "event:assistant/message.data.stream" + ], + "sources": [] + }, + { + "digest": "86d1d0e9cfebb240421187e3dc37fb71cc38a1501885cf32f8f9adef7b0a2bde", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 7 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "agentModel", + "type": 2, + "optional": true + }, + { + "name": "agentProvider", + "type": 2, + "optional": true + }, + { + "name": "agentReasoningEffort", + "type": 2, + "optional": true + }, + { + "name": "label", + "type": 2, + "optional": false + }, + { + "name": "mode", + "type": 3, + "optional": false + }, + { + "name": "persona", + "type": 2, + "optional": true + }, + { + "name": "provider", + "type": 2, + "optional": false + }, + { + "name": "toolFilter", + "type": 4, + "optional": true + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "object", + "properties": [ + { + "name": "allow", + "type": 5, + "optional": true + }, + { + "name": "deny", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "label", + "type": 2, + "optional": true + }, + { + "name": "mode", + "type": 8, + "optional": false + }, + { + "name": "provider", + "type": 2, + "optional": false + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "one-shot" + } + ] + }, + "names": [ + "packages/subagent/subagent/src/descriptor.ts#SubagentDescriptorData" + ], + "sources": [ + "packages/subagent/subagent/src/descriptor.ts:89" + ] + }, + { + "digest": "86e1e384495ee09ac2d60c9db3610f1f1fddac2d248f6f1d6000e78054680219", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 1, + "optional": false + }, + { + "name": "text", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/llm/llm/src/message.ts#ContextSnapshotSection" + ], + "sources": [ + "packages/llm/llm/src/message.ts:65" + ] + }, + { + "digest": "871bfd6f7bb91061246768d3377f3fd58d6b8ea7c31f44c714ae7ab215650d62", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "provisioning" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "8725743fcd3db5ddaf3e0aed550cd5d95785d8e30c9d4cbec11b7c90198d3603", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 1, + "optional": false + }, + { + "name": "stream", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 4, + 9, + 66, + 68 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 5, + "optional": false + }, + { + "name": "dt", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "index", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "time0", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 6 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 1 + }, + { + "kind": "literal", + "value": "tool-call-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 10, + "optional": false + }, + { + "name": "time", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 11, + 13, + 38, + 41, + 43, + 45, + 62 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "index", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 14, + "optional": false + }, + { + "name": "index", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 15, + 17, + 26, + 29, + 33, + 35 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 1, + "optional": false + }, + { + "name": "height", + "type": 1, + "optional": false + }, + { + "name": "mediaType", + "type": 19, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "originalDimensions", + "type": 24, + "optional": true + }, + { + "name": "width", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 20, + 21, + 22, + 23 + ] + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 1, + "optional": false + }, + { + "name": "width", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 27, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 30, + "optional": false + }, + { + "name": "isError", + "type": 31, + "optional": true + }, + { + "name": "toolCallId", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 14 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 39, + "optional": false + }, + { + "name": "index", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 16, + 25, + 28, + 32, + 34, + 36 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 1, + "optional": false + }, + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 1, + "optional": false + }, + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 46, + "optional": false + }, + { + "name": "replayState", + "type": 58, + "optional": true + }, + { + "name": "type", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 47, + 50, + 52, + 54, + 56 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 6, + "optional": false + }, + { + "name": "message", + "type": 6, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 1, + "optional": true + }, + { + "name": "requestId", + "type": 6, + "optional": true + }, + { + "name": "status", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 48, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 53, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 59, + "optional": true + }, + { + "name": "response", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 60 + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 63, + "optional": false + }, + { + "name": "usage", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 1, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 1, + "optional": true + }, + { + "name": "inputTokens", + "type": 1, + "optional": false + }, + { + "name": "outputTokens", + "type": 1, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 1, + "optional": true + }, + { + "name": "totalTokens", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 1, + "optional": false + }, + { + "name": "texts", + "type": 5, + "optional": false + }, + { + "name": "time0", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 1, + "optional": false + }, + { + "name": "texts", + "type": 5, + "optional": false + }, + { + "name": "time0", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 69, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-chunks" + } + ] + }, + "names": [ + "event:assistant/attempt.data" + ], + "sources": [ + "packages/core/session/src/types.ts:335" + ] + }, + { + "digest": "88a610b831be6d92612a464be5c7d0b88ec24d97bd299837bf74bf7672183c9a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": true + }, + { + "name": "type", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-call-delta" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[3]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:394" + ] + }, + { + "digest": "8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 7, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "dialect", + "type": 2, + "optional": false + }, + { + "name": "handlerId", + "type": 5, + "optional": false + }, + { + "name": "matcher", + "type": 5, + "optional": true + }, + { + "name": "point", + "type": 5, + "optional": false + }, + { + "name": "turn", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "claude-code" + }, + { + "kind": "literal", + "value": "codex" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "hook/invoked" + } + ] + }, + "names": [ + "event:hook/invoked" + ], + "sources": [] + }, + { + "digest": "8a9c990773f798eac10ca8878e6bcc531ee080bc589cac956730e462cc6a09ad", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "preset", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:permission/preset.data" + ], + "sources": [ + "packages/interaction/permission-presets/src/index.ts:54" + ] + }, + { + "digest": "8ad45ad053b4d89f8865c17cc9ff3b8748e86045f3f0fb816269a34af2619141", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "decision", + "type": 1, + "optional": false + }, + { + "name": "durationMs", + "type": 2, + "optional": false + }, + { + "name": "exitCode", + "type": 2, + "optional": true + }, + { + "name": "handlerId", + "type": 1, + "optional": false + }, + { + "name": "point", + "type": 1, + "optional": false + }, + { + "name": "stderrSummary", + "type": 1, + "optional": true + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:hook/result.data" + ], + "sources": [ + "packages/hooks/hook-protocol/src/types.ts:31" + ] + }, + { + "digest": "8af841fc9b265ade3441caf9fe40dd24db24226befba48150e6d0eeccc1389c3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "system" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "8bb8c3751a6ce64b61aba1ec7ae798c00a5803c4d868a0430873216c6569ed52", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "sessionFormatVersion", + "type": 1, + "optional": true + }, + { + "name": "sessionId", + "type": 2, + "optional": false + }, + { + "name": "throughSeq", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:session-log-deepseek/delivery-accepted.data" + ], + "sources": [ + "packages/session/session-log-deepseek/src/types.ts:81" + ] + }, + { + "digest": "8cdd3fdd295cee53035de16a1b23411716f75e9f994fe9510cac5d7fef605c76", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "command/done" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "8e8e91646a5cae3fc78f7532a0013ff2088ae47077642c2edd507a508209b8af", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "operation", + "type": 2, + "optional": false + }, + { + "name": "version", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "dispatch" + }, + { + "kind": "literal", + "value": 1 + } + ] + }, + "names": [ + "packages/schedule/schedule/src/types.ts#OneShotScheduleDispatchChange" + ], + "sources": [ + "packages/schedule/schedule/src/types.ts:86" + ] + }, + { + "digest": "8ea13bcd983eab596dbfd41c5c2b62a0b318b71cd2a987a6fd752c6ced7e4489", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "header", + "type": 2, + "optional": false + }, + { + "name": "reason", + "type": 13, + "optional": false + }, + { + "name": "startsSeries", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "adapterDefaults", + "type": 3, + "optional": true + }, + { + "name": "config", + "type": 5, + "optional": false + }, + { + "name": "tools", + "type": 9, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 4, + "optional": true + }, + { + "name": "reasoningEffort", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 6, + "optional": true + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 7, + "optional": true + }, + { + "name": "stop", + "type": 8, + "optional": true + }, + { + "name": "temperature", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "array", + "element": 10 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "parameters", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 7, + "value": 12 + } + ] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "union", + "types": [ + 14, + 15, + 16, + 17 + ] + }, + { + "kind": "literal", + "value": "initial" + }, + { + "kind": "literal", + "value": "resume" + }, + { + "kind": "literal", + "value": "series" + }, + { + "kind": "literal", + "value": "change" + }, + { + "kind": "literal", + "value": "request/header" + } + ] + }, + "names": [ + "event:request/header" + ], + "sources": [] + }, + { + "digest": "8ef06dd236128246969d5f04a6c4a639d031fcda99a0cd13051f52f41b0ca1af", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 2, + "optional": false + }, + { + "name": "bytes", + "type": 3, + "optional": false + }, + { + "name": "height", + "type": 3, + "optional": false + }, + { + "name": "mediaType", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": true + }, + { + "name": "originalDimensions", + "type": 9, + "optional": true + }, + { + "name": "width", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 5, + 6, + 7, + 8 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 3, + "optional": false + }, + { + "name": "width", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#ImageBlock" + ], + "sources": [ + "packages/llm/llm/src/types.ts:71" + ] + }, + { + "digest": "8f6d609794bf5afc01d7ccf3e03811d32f4b8047f58a2c9a98db0154c2ad7cb9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "tuple", + "elements": [ + { + "type": 1, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "text" + } + ] + }, + "names": [ + "event:web/deepseek-search-llm-request.data.body.messages[0].content" + ], + "sources": [] + }, + { + "digest": "904bf499d98be4a3a5f836fcac533b6c4899569b018071e6604e1d32657d00fd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[17].changes[0].action" + ], + "sources": [] + }, + { + "digest": "9096d1dd86a3da77a593e12cd95e596b2ef965fa94fcbee40e4bdcf0433924a1", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "delayMs", + "type": 1, + "optional": false + }, + { + "name": "failure", + "type": 2, + "optional": false + }, + { + "name": "maxRetries", + "type": 1, + "optional": false + }, + { + "name": "mode", + "type": 4, + "optional": false + }, + { + "name": "policyKey", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "retry", + "type": 1, + "optional": false + }, + { + "name": "retryId", + "type": 3, + "optional": false + }, + { + "name": "step", + "type": 1, + "optional": false + }, + { + "name": "turn", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 3, + "optional": false + }, + { + "name": "message", + "type": 3, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 1, + "optional": true + }, + { + "name": "requestId", + "type": 3, + "optional": true + }, + { + "name": "status", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "normal" + } + ] + }, + "names": [ + "event:llm/retry.data[0]" + ], + "sources": [ + "packages/llm/llm-retry/src/types.ts:17" + ] + }, + { + "digest": "91c397f8f870e812e1dc5ac69c2745f9f105be9dc97dadc980d19ef415a65145", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 9, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 7 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "delayMs", + "type": 3, + "optional": false + }, + { + "name": "failure", + "type": 4, + "optional": false + }, + { + "name": "maxRetries", + "type": 3, + "optional": false + }, + { + "name": "mode", + "type": 6, + "optional": false + }, + { + "name": "policyKey", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "retry", + "type": 3, + "optional": false + }, + { + "name": "retryId", + "type": 5, + "optional": false + }, + { + "name": "step", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 5, + "optional": false + }, + { + "name": "message", + "type": 5, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 3, + "optional": true + }, + { + "name": "requestId", + "type": 5, + "optional": true + }, + { + "name": "status", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "normal" + }, + { + "kind": "object", + "properties": [ + { + "name": "delayMs", + "type": 3, + "optional": false + }, + { + "name": "failure", + "type": 4, + "optional": false + }, + { + "name": "mode", + "type": 8, + "optional": false + }, + { + "name": "policyKey", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "retry", + "type": 3, + "optional": false + }, + { + "name": "retryId", + "type": 5, + "optional": false + }, + { + "name": "step", + "type": 3, + "optional": false + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "always" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "llm/retry" + } + ] + }, + "names": [ + "event:llm/retry" + ], + "sources": [] + }, + { + "digest": "930a6567a10bb62ddd157bd8abdf4b182810c8c3b49d91309b3b009a5fed9731", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "max_tokens", + "type": 1, + "optional": false + }, + { + "name": "messages", + "type": 2, + "optional": false + }, + { + "name": "model", + "type": 6, + "optional": false + }, + { + "name": "tools", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "tuple", + "elements": [ + { + "type": 3, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "role", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 5, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "tuple", + "elements": [ + { + "type": 10, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "max_uses", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "web_search" + }, + { + "kind": "literal", + "value": "web_search_20250305" + } + ] + }, + "names": [ + "event:web/deepseek-search-llm-request.data.body" + ], + "sources": [ + "packages/web/web-search-deepseek/src/provider.ts:62" + ] + }, + { + "digest": "930c1cd289def119d443e19dd90008916bf035f0a4d23e07241e7f050f2b89b7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "allowed-once" + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "rejected" + }, + { + "kind": "literal", + "value": "unavailable" + } + ] + }, + "names": [ + "packages/interaction/user-approval/src/types.ts#ApprovalOutcome" + ], + "sources": [ + "packages/interaction/user-approval/src/types.ts:32" + ] + }, + { + "digest": "935c8d486292b13c87b57ca280eb8c58b5b2f5239be86fcd4334c33fdeb87682", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "web_search" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "938f7c7357bc403ea3b281a9dd016939df7a0880fc8db12612585be574d0d615", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "primitive", + "type": "null" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "93c33d9687613293f8c95d46c4d922fe9ceae83b84c384beff5c3315abe005f2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][0].texts", + "event:assistant/attempt.data.stream[0][1].texts", + "event:assistant/attempt.data.stream[0][2].args", + "event:request/header.data.header.config.stop", + "event:subagent/descriptor.data[1].toolFilter.allow", + "event:subagent/descriptor.data[1].toolFilter.deny", + "event:team/task.data.task.blockedBy", + "event:team/task.data.task.writeScopes" + ], + "sources": [] + }, + { + "digest": "93cbbc5a7fd0b1aa1ff1b9a6bb5ec52b073f48e5a5200365c45c0cd54059e1b9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "outcome", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5, + 6 + ] + }, + { + "kind": "literal", + "value": "allowed-once" + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "rejected" + }, + { + "kind": "literal", + "value": "unavailable" + } + ] + }, + "names": [ + "event:approval/decided.data" + ], + "sources": [ + "packages/interaction/user-approval/src/types.ts:55" + ] + }, + { + "digest": "93d38436347f67c9022aacbc4472e283d722bcab8eb73fc376aaf7b6a6b9fb35", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": 0 + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "94c56aeb1564ad735eb773d88636b40761d063efa1610f1308004d32923f5378", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "in_progress" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 1, + "optional": false + }, + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "text-delta" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[1]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:392" + ] + }, + { + "digest": "95c0e88fcf0bafb2087f912ad9e2369de6ae6530cc0c4c2772c96205b57f7f60", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "assistant" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "966fad765834f62051ad6f6a100f7ea38abe5f40f7ef3ae0b6dca0e31c7549a5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "role", + "type": 27, + "optional": false + }, + { + "name": "source", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 2, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 25, + "optional": true + }, + { + "name": "toolCallId", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 2, + 5, + 8, + 10, + 12, + 22 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 13, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 14, + "optional": false + }, + { + "name": "height", + "type": 14, + "optional": false + }, + { + "name": "mediaType", + "type": 15, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "originalDimensions", + "type": 20, + "optional": true + }, + { + "name": "width", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 16, + 17, + 18, + 19 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 14, + "optional": false + }, + { + "name": "width", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 23, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 14, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 6, + "optional": false + }, + { + "name": "kind", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + } + ] + }, + "names": [ + "packages/llm/llm/src/message.ts#ToolResultMessage" + ], + "sources": [ + "packages/llm/llm/src/message.ts:164" + ] + }, + { + "digest": "96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2 + ] + }, + { + "kind": "literal", + "value": "next-step" + }, + { + "kind": "literal", + "value": "next-turn" + } + ] + }, + "names": [ + "packages/core/agent/src/types.ts#InboxTarget" + ], + "sources": [ + "packages/core/agent/src/types.ts:30" + ] + }, + { + "digest": "9890113e97dd3541787bfe6704f2b223d3a890b5325ca0acae2b0398909b413e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "mode", + "type": 1, + "optional": false + }, + { + "name": "source", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "danger-full-access" + }, + { + "kind": "literal", + "value": "read-only" + }, + { + "kind": "literal", + "value": "workspace-write" + }, + { + "kind": "literal", + "value": "delegation" + } + ] + }, + "names": [ + "event:sandbox/mode.data" + ], + "sources": [ + "packages/sandbox/sandbox-policy/src/session-mode.ts:33" + ] + }, + { + "digest": "989145d2b165a1e4391d0691196ede9dec5093b09f1cf3d33b1911c24ee6c5ee", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 5, + 30, + 33, + 35, + 37, + 54 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "index", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": true + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 6, + "optional": false + }, + { + "name": "index", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 7, + 9, + 18, + 21, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 2, + "optional": false + }, + { + "name": "bytes", + "type": 3, + "optional": false + }, + { + "name": "height", + "type": 3, + "optional": false + }, + { + "name": "mediaType", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": true + }, + { + "name": "originalDimensions", + "type": 16, + "optional": true + }, + { + "name": "width", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 12, + 13, + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 3, + "optional": false + }, + { + "name": "width", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 2, + "optional": false + }, + { + "name": "bytes", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 22, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 6 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 31, + "optional": false + }, + { + "name": "index", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 8, + 17, + 20, + 24, + 26, + 28 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 3, + "optional": false + }, + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 3, + "optional": false + }, + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 38, + "optional": false + }, + { + "name": "replayState", + "type": 50, + "optional": true + }, + { + "name": "type", + "type": 53, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 39, + 42, + 44, + 46, + 48 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 41, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 2, + "optional": false + }, + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 3, + "optional": true + }, + { + "name": "requestId", + "type": 2, + "optional": true + }, + { + "name": "status", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 47, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 51, + "optional": true + }, + { + "name": "response", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 52 + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 55, + "optional": false + }, + { + "name": "usage", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 3, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 3, + "optional": true + }, + { + "name": "inputTokens", + "type": 3, + "optional": false + }, + { + "name": "outputTokens", + "type": 3, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 3, + "optional": true + }, + { + "name": "totalTokens", + "type": 3, + "optional": true + } + ], + "indices": [] + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#StreamChunk" + ], + "sources": [ + "packages/llm/llm/src/types.ts:390" + ] + }, + { + "digest": "998f22585a5d73ec005365e203f07fc3b3a029e82323fdda3fd7f13b0a9639d0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 1, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 3, + "optional": true + }, + { + "name": "surfaceOp", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "union", + "types": [ + 5, + 6 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 2, + "optional": false + }, + { + "name": "op", + "type": 7, + "optional": false + }, + { + "name": "startSeq", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "SessionEventEnvelope[38]", + "SessionEventEnvelope[51]", + "SessionEventEnvelope[54]" + ], + "sources": [] + }, + { + "digest": "9a2a9029f8d7ede05336980d8342737557f487b913bfd28853d0ab5214600ab5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "role", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 2, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + } + ] + }, + "names": [ + "event:web/deepseek-search-llm-request.data.body.messages[0]" + ], + "sources": [ + "packages/web/web-search-deepseek/src/provider.ts:65" + ] + }, + { + "digest": "9a410ddf56792b2b7cd4a224a2e54dc4dee30a8e8e6b1c2b9a1d19992b217adc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "childId", + "type": 1, + "optional": false + }, + { + "name": "label", + "type": 1, + "optional": false + }, + { + "name": "phase", + "type": 1, + "optional": true + }, + { + "name": "runId", + "type": 1, + "optional": false + }, + { + "name": "seq", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentStartData" + ], + "sources": [ + "packages/workflow/tool-workflow/src/types.ts:20" + ] + }, + { + "digest": "9b4fd974c124c83743efe030ba4e1250e087505f3f74588716a521fb8d453267", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool-call-delta" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "9bdd77f2b39d9ae47be71e494eca8c9e950b0e99b89a4539f86c4a0e7d1398b6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "union", + "types": [ + 0, + 3, + 6, + 8, + 10, + 20 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 12, + "optional": false + }, + { + "name": "height", + "type": 12, + "optional": false + }, + { + "name": "mediaType", + "type": 13, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": true + }, + { + "name": "originalDimensions", + "type": 18, + "optional": true + }, + { + "name": "width", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 14, + 15, + 16, + 17 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 12, + "optional": false + }, + { + "name": "width", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 21, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#ToolResultBlock" + ], + "sources": [ + "packages/llm/llm/src/types.ts:101" + ] + }, + { + "digest": "9d80129e13edfb6feb03ed327e6085c6ef60b97ad074244b2f5fd4667424a6a4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "other" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "9e07693fd7cd9babe0688c22c986e4d519ae1c0e40eeea13b2dc583bed2abc86", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "ask" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "9e41386b3a0c9572b0d63078492ebb3997da7d3a830d44e0259418bd02f4bcb2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 1, + "optional": false + }, + { + "name": "start", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:compaction/prune.data.shadowedRange", + "event:compaction/summary.data[0].shadowedRange", + "event:compaction/summary.data[1].shadowedRange" + ], + "sources": [ + "packages/compaction/compaction/src/types.ts:38", + "packages/compaction/compaction/src/types.ts:84" + ] + }, + { + "digest": "9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 1, + "optional": false + }, + { + "name": "form", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 1, + "optional": false + }, + { + "name": "ruleId", + "type": 1, + "optional": false + }, + { + "name": "source", + "type": 1, + "optional": false + }, + { + "name": "summary", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[19]" + ], + "sources": [ + "packages/webhook/webhook/src/types.ts:74" + ] + }, + { + "digest": "a0f906856fc06f1c6f2083eb970e5a657a82b1a84c06c095445e5d6d161cda80", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": 2 + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "a1082bd67be26461ebcc4cc9caba60ef548cd6ec625b51558d5cfa5b6b38718a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "error", + "type": 1, + "optional": true + }, + { + "name": "message", + "type": 3, + "optional": false + }, + { + "name": "meta", + "type": 32, + "optional": true + }, + { + "name": "step", + "type": 16, + "optional": false + }, + { + "name": "turn", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "role", + "type": 29, + "optional": false + }, + { + "name": "source", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 5, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "isError", + "type": 27, + "optional": true + }, + { + "name": "toolCallId", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "union", + "types": [ + 5, + 8, + 10, + 12, + 14, + 24 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 15, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 2, + "optional": false + }, + { + "name": "bytes", + "type": 16, + "optional": false + }, + { + "name": "height", + "type": 16, + "optional": false + }, + { + "name": "mediaType", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": true + }, + { + "name": "originalDimensions", + "type": 22, + "optional": true + }, + { + "name": "width", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 18, + 19, + 20, + 21 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 16, + "optional": false + }, + { + "name": "width", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 25, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 2, + "optional": false + }, + { + "name": "bytes", + "type": 16, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 31, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "union", + "types": [ + 2, + 16, + 27, + 33, + 34, + 35 + ] + }, + { + "kind": "array", + "element": 32 + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 2, + "value": 32 + } + ] + } + ] + }, + "names": [ + "event:tool/result.data" + ], + "sources": [ + "packages/core/session/src/types.ts:353" + ] + }, + { + "digest": "a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "agentPreset", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "agent-preset/selected" + } + ] + }, + "names": [ + "event:agent-preset/selected" + ], + "sources": [] + }, + { + "digest": "a13d8f379222ece8a02a86efe4c8334af4f108ecb3034b38c0a42bb3046e6847", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "a2418dd9dd938f04a3eaf1f27694b33facfb931b6e495dcfb4d5fcfa2ef82b28", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "interrupted", + "type": 1, + "optional": true + }, + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "step", + "type": 10, + "optional": false + }, + { + "name": "stream", + "type": 32, + "optional": false + }, + { + "name": "turn", + "type": 10, + "optional": false + }, + { + "name": "usage", + "type": 69, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "role", + "type": 28, + "optional": false + }, + { + "name": "source", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 8, + 18, + 21, + 24, + 26 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "mediaType", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "originalDimensions", + "type": 16, + "optional": true + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 12, + 13, + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 22, + "optional": true + }, + { + "name": "toolCallId", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 30, + "optional": false + }, + { + "name": "model", + "type": 6, + "optional": false + }, + { + "name": "provider", + "type": 6, + "optional": false + }, + { + "name": "replayState", + "type": 31, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "array", + "element": 33 + }, + { + "kind": "union", + "types": [ + 34, + 38, + 71, + 73 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 35, + "optional": false + }, + { + "name": "dt", + "type": 36, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "index", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "time0", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 6 + }, + { + "kind": "array", + "element": 10 + }, + { + "kind": "literal", + "value": "tool-call-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 39, + "optional": false + }, + { + "name": "time", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 40, + 42, + 44, + 47, + 49, + 51, + 67 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "index", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "type", + "type": 41, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 4, + "optional": false + }, + { + "name": "index", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 45, + "optional": false + }, + { + "name": "index", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 7, + 17, + 20, + 23, + 25, + 27 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 10, + "optional": false + }, + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 10, + "optional": false + }, + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 52, + "optional": false + }, + { + "name": "replayState", + "type": 64, + "optional": true + }, + { + "name": "type", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 53, + 56, + 58, + 60, + 62 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 6, + "optional": false + }, + { + "name": "message", + "type": 6, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 10, + "optional": true + }, + { + "name": "requestId", + "type": 6, + "optional": true + }, + { + "name": "status", + "type": 10, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 65, + "optional": true + }, + { + "name": "response", + "type": 31, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 31 + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 68, + "optional": false + }, + { + "name": "usage", + "type": 69, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 10, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 10, + "optional": true + }, + { + "name": "inputTokens", + "type": 10, + "optional": false + }, + { + "name": "outputTokens", + "type": 10, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 10, + "optional": true + }, + { + "name": "totalTokens", + "type": 10, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 36, + "optional": false + }, + { + "name": "index", + "type": 10, + "optional": false + }, + { + "name": "texts", + "type": 35, + "optional": false + }, + { + "name": "time0", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 72, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 36, + "optional": false + }, + { + "name": "index", + "type": 10, + "optional": false + }, + { + "name": "texts", + "type": 35, + "optional": false + }, + { + "name": "time0", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 74, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-chunks" + } + ] + }, + "names": [ + "event:assistant/message.data" + ], + "sources": [ + "packages/core/session/src/types.ts:321" + ] + }, + { + "digest": "a2be0f0cb59f0d4a06557d45847d1938aced238baa77a6da416e590b8b958553", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "error" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "a3a259a1aa5401af786ce08c4a8419681d3000d73467991b5fc563389bc4ebd2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "hook/result" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "workspace-write" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "task", + "type": 1, + "optional": false + }, + { + "name": "teamId", + "type": 3, + "optional": false + }, + { + "name": "version", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "blockedBy", + "type": 2, + "optional": false + }, + { + "name": "description", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "ownerId", + "type": 3, + "optional": true + }, + { + "name": "revision", + "type": 4, + "optional": false + }, + { + "name": "status", + "type": 5, + "optional": false + }, + { + "name": "subject", + "type": 3, + "optional": false + }, + { + "name": "writeScopes", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 6, + 7, + 8, + 9 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "deleted" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + }, + { + "kind": "literal", + "value": 2 + } + ] + }, + "names": [ + "event:team/task.data" + ], + "sources": [ + "packages/experimental/agent-team/src/types.ts:223" + ] + }, + { + "digest": "a4ed3446d32625902170fce527fb64a0283d6f4817930f54a16fdce3a482b36a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 1, + "optional": false + }, + { + "name": "replayState", + "type": 15, + "optional": true + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 7, + 9, + 11, + 13 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 4, + "optional": false + }, + { + "name": "message", + "type": 4, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 5, + "optional": true + }, + { + "name": "requestId", + "type": 4, + "optional": true + }, + { + "name": "status", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 16, + "optional": true + }, + { + "name": "response", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 17 + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "literal", + "value": "finish" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[6]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:397" + ] + }, + { + "digest": "a58ca1bed502d425a8b65cac02f59717498cd296de9513c6f81b83b791c20e9b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 2, + "optional": true + }, + { + "name": "seq", + "type": 11, + "optional": false + }, + { + "name": "surfaceOp", + "type": 76, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "interrupted", + "type": 2, + "optional": true + }, + { + "name": "message", + "type": 3, + "optional": false + }, + { + "name": "step", + "type": 11, + "optional": false + }, + { + "name": "stream", + "type": 33, + "optional": false + }, + { + "name": "turn", + "type": 11, + "optional": false + }, + { + "name": "usage", + "type": 70, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 29, + "optional": false + }, + { + "name": "source", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 19, + 22, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "mediaType", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 17, + "optional": true + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 20, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 31, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "replayState", + "type": 32, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "array", + "element": 34 + }, + { + "kind": "union", + "types": [ + 35, + 39, + 72, + 74 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 36, + "optional": false + }, + { + "name": "dt", + "type": 37, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "time0", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "array", + "element": 11 + }, + { + "kind": "literal", + "value": "tool-call-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 40, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 71, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 41, + 43, + 45, + 48, + 50, + 52, + 68 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "type", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 5, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 46, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 47, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 8, + 18, + 21, + 24, + 26, + 28 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 51, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 53, + "optional": false + }, + { + "name": "replayState", + "type": 65, + "optional": true + }, + { + "name": "type", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 54, + 57, + 59, + 61, + 63 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 55, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 7, + "optional": false + }, + { + "name": "message", + "type": 7, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 11, + "optional": true + }, + { + "name": "requestId", + "type": 7, + "optional": true + }, + { + "name": "status", + "type": 11, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 55, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 66, + "optional": true + }, + { + "name": "response", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 32 + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 69, + "optional": false + }, + { + "name": "usage", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 11, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 11, + "optional": true + }, + { + "name": "inputTokens", + "type": 11, + "optional": false + }, + { + "name": "outputTokens", + "type": 11, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 11, + "optional": true + }, + { + "name": "totalTokens", + "type": 11, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 37, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "texts", + "type": 36, + "optional": false + }, + { + "name": "time0", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 73, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 37, + "optional": false + }, + { + "name": "index", + "type": 11, + "optional": false + }, + { + "name": "texts", + "type": 36, + "optional": false + }, + { + "name": "time0", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 75, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-chunks" + }, + { + "kind": "union", + "types": [ + 77, + 79 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 11, + "optional": false + }, + { + "name": "op", + "type": 78, + "optional": false + }, + { + "name": "startSeq", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "literal", + "value": "assistant/message" + } + ] + }, + "names": [ + "event:assistant/message" + ], + "sources": [] + }, + { + "digest": "a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 5, + "optional": true + }, + { + "name": "seq", + "type": 6, + "optional": false + }, + { + "name": "time", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "allowedModels", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 4, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "subagent/model-selection-policy" + } + ] + }, + "names": [ + "event:subagent/model-selection-policy" + ], + "sources": [] + }, + { + "digest": "a6b41f5bede8094d7bf635be5d69b1bd7a25e006ca9cf9415bceb9322f1a7feb", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2 + ] + }, + { + "kind": "literal", + "value": "claude-code" + }, + { + "kind": "literal", + "value": "codex" + } + ] + }, + "names": [ + "packages/hooks/hook-protocol/src/types.ts#HookDialect" + ], + "sources": [ + "packages/hooks/hook-protocol/src/types.ts:48" + ] + }, + { + "digest": "a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 1, + "optional": false + }, + { + "name": "runId", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunStartData" + ], + "sources": [ + "packages/workflow/tool-workflow/src/types.ts:14" + ] + }, + { + "digest": "a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "active", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "plan/mode" + } + ] + }, + "names": [ + "event:plan/mode" + ], + "sources": [] + }, + { + "digest": "a853902cfab417b8f08aa51ad6855fea46f34ed4588a74e47403035111d0fb86", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 1, + "optional": true + }, + { + "name": "commandId", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + }, + { + "name": "source", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + } + ] + }, + "names": [ + "event:command/run.data" + ], + "sources": [ + "packages/interaction/commands/src/types.ts:105" + ] + }, + { + "digest": "a8a2b9011fc067862df7ce40748868467e5778283d7ac193c13f77684a914c57", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "team/message/delivered" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "a99f76fd149051c363f960bfeeb5b9509587ecaf7591a42797e4bfe810d8987a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "status", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + } + ] + }, + "names": [ + "packages/todo/tool-todo/src/types.ts#TodoItem" + ], + "sources": [ + "packages/todo/tool-todo/src/types.ts:21" + ] + }, + { + "digest": "aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "turn/start" + } + ] + }, + "names": [ + "event:turn/start" + ], + "sources": [] + }, + { + "digest": "aa7bc2817014ca6c45719b1c193c5c131be718a0921f34345098ff250f444626", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "item", + "type": 1, + "optional": false + }, + { + "name": "sessionId", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "category", + "type": 2, + "optional": true + }, + { + "name": "createdAt", + "type": 10, + "optional": false + }, + { + "name": "messageId", + "type": 11, + "optional": false + }, + { + "name": "note", + "type": 11, + "optional": true + }, + { + "name": "rating", + "type": 12, + "optional": false + }, + { + "name": "updatedAt", + "type": 10, + "optional": false + }, + { + "name": "version", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + { + "kind": "literal", + "value": "instruction-following" + }, + { + "kind": "literal", + "value": "resource-cost" + }, + { + "kind": "literal", + "value": "security-privacy-permission" + }, + { + "kind": "literal", + "value": "service-stability" + }, + { + "kind": "literal", + "value": "task-result" + }, + { + "kind": "literal", + "value": "other" + }, + { + "kind": "literal", + "value": "product-interaction" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 13, + 14 + ] + }, + { + "kind": "literal", + "value": "negative" + }, + { + "kind": "literal", + "value": "positive" + } + ] + }, + "names": [ + "packages/feedback/message-feedback/src/types.ts#MessageFeedbackPut" + ], + "sources": [ + "packages/feedback/message-feedback/src/types.ts:38" + ] + }, + { + "digest": "aa93010d0e521ad7c42273f58d5c9b8bf1dfc94edf1b805a3971b364a35c1877", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "primitive", + "type": "boolean" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "aacda84a69739238aa782eea902d3073017d9d74b9808de2bf5a83c9738cbdc6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "subagent" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[6].reason[2]", + "event:turn/end.data.reason[4]" + ], + "sources": [ + "packages/core/session/src/types.ts:213", + "packages/llm/llm/src/types.ts:133" + ] + }, + { + "digest": "aba175fb20ffc71112c55efe70458ff5d7dbfb83501892b4ba8d077f0ea5d7ba", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[16].entries" + ], + "sources": [] + }, + { + "digest": "ac5a8baa3dda8fa667b4975d6a8b40a9bde47d8d754ca8ed6b8ce923a1651828", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 1, + "optional": false + }, + { + "name": "maxTokens", + "type": 2, + "optional": true + }, + { + "name": "model", + "type": 1, + "optional": false + }, + { + "name": "provider", + "type": 1, + "optional": false + }, + { + "name": "rawOutput", + "type": 3, + "optional": true + }, + { + "name": "shadowedRange", + "type": 26, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 27, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 2, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 1, + "optional": true + }, + { + "name": "summary", + "type": 3, + "optional": false + }, + { + "name": "usage", + "type": 28, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 8, + 10, + 12, + 14, + 23 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 6, + "optional": true + }, + { + "name": "toolCallId", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 15, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 1, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 16, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": true + }, + { + "name": "originalDimensions", + "type": 21, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 17, + 18, + 19, + 20 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 24, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 1, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 2, + "optional": false + }, + { + "name": "start", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 2, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 2, + "optional": true + }, + { + "name": "inputTokens", + "type": 2, + "optional": false + }, + { + "name": "outputTokens", + "type": 2, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 2, + "optional": true + }, + { + "name": "totalTokens", + "type": 2, + "optional": true + } + ], + "indices": [] + } + ] + }, + "names": [ + "event:compaction/summary.data[1]" + ], + "sources": [] + }, + { + "digest": "acd36e1717d2b4ef9e98a8e6fac17ca1203f3f448e5bca51677f129a8f4999e2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3 + ] + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "literal", + "value": "user" + } + ] + }, + "names": [ + "event:session/title-llm-request.data.messages[0].role" + ], + "sources": [] + }, + { + "digest": "ad22a5ca5bbaab2e1440779cd3648e85ddfc6f73b5207394eda699555cf31430", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "edit" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "ad3b56aa2fc1ad4250e7399295d34a8eae592e1907e85852e2291b2e498a30bc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "legacy" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "ad92f473a03ae72599ff26c86e897c25e16269519ad2a87340f0bea946517af8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool-workflow/agent-start" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "ada310bf0bdb8fed51f3b56ea63f6ea6b18bbd587f04fccb63a14ab0b2a24e05", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[0]", + "event:session/title.data.source[2]", + "event:turn/end.data.reason[1].reason[0]", + "packages/interaction/commands/src/types.ts#CommandSource" + ], + "sources": [ + "packages/core/session/src/types.ts:189", + "packages/interaction/commands/src/types.ts:75", + "packages/interaction/commands/src/types.ts:79", + "packages/llm/llm/src/message.ts:103", + "packages/session/session-title/src/types.ts:35" + ] + }, + { + "digest": "ae1f7110feeec697b8cab42b68f7709aa7b3279764099dfb53c25890d2e5c871", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 9, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 7 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": false + }, + { + "name": "mode", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "literal", + "value": 0 + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": true + }, + { + "name": "mode", + "type": 8, + "optional": false + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "one-shot" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "subagent/catalog" + } + ] + }, + "names": [ + "event:subagent/catalog" + ], + "sources": [] + }, + { + "digest": "ae63d9d1080ed8945031594d93f8d96651d7101b848719cc3e1475875cd3d649", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "permission/preset" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "aec150797b655c5f15ebb7a501454b0c208fbaccec9ace49d7de2171fc02f47d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "catalog" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "af0750d32d97d47b12b7361a587db41894b17e04c9a805fe212f0114aa2248c7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "todo/write" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "aff60ef947c18b012644852bdd0d5bfe72065e061dd563787c76c646b4a10f6b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "claude-code" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "b0127044ab31a702bddfd785d345f5abd7a70876746e895ce443afa3e60ddf2d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 2, + "optional": false + }, + { + "name": "error", + "type": 2, + "optional": true + }, + { + "name": "sourceCommandId", + "type": 2, + "optional": true + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "compaction/end" + } + ] + }, + "names": [ + "event:compaction/end" + ], + "sources": [] + }, + { + "digest": "b0b0eb1b16490f6bee135b2377e8f8c39227974c799ad7d49ff418af23ecbc35", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 1, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": true + }, + { + "name": "originalDimensions", + "type": 8, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6, + 7 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "packages/attachment/attachment/src/types.ts#ImageAttachmentRef" + ], + "sources": [ + "packages/attachment/attachment/src/types.ts:11" + ] + }, + { + "digest": "b158f3ff6923714807c0d73882f0b05dc87f8fa85c9e90814852d48bbc866081", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "contextWindow", + "type": 1, + "optional": true + }, + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "provider", + "type": 2, + "optional": false + }, + { + "name": "systemPromptUpdate", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "in-history" + } + ] + }, + "names": [ + "packages/core/session/src/types.ts#RequestContext" + ], + "sources": [ + "packages/core/session/src/types.ts:242" + ] + }, + { + "digest": "b1664fa89f8eab49a14e7e3168d8bc4da4aa9fd76332f6c4a053d839e5276ae7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "opaque", + "reason": "unknown" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[6].replayState.blocks" + ], + "sources": [] + }, + { + "digest": "b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "member", + "type": 1, + "optional": false + }, + { + "name": "teamId", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "context", + "type": 2, + "optional": false + }, + { + "name": "description", + "type": 5, + "optional": false + }, + { + "name": "error", + "type": 5, + "optional": true + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "phase", + "type": 6, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "fork" + }, + { + "kind": "literal", + "value": "fresh" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 7, + 8, + 9 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "failed" + }, + { + "kind": "literal", + "value": "provisioning" + }, + { + "kind": "literal", + "value": 2 + } + ] + }, + "names": [ + "event:team/member.data" + ], + "sources": [ + "packages/experimental/agent-team/src/types.ts:221" + ] + }, + { + "digest": "b1f62380f6746fedd629e71065e3583cef83334875b499ac9665c2eb87acfbf5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "negative" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "deleted" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + } + ] + }, + "names": [ + "packages/experimental/agent-team/src/types.ts#TeamTaskStatus" + ], + "sources": [ + "packages/experimental/agent-team/src/types.ts:71" + ] + }, + { + "digest": "b38f206d59bad891fa58c89f97a0036987b3defe7685acbd318fa55eb85f9751", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "disposed" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "b4d298d90c798fa4d1f5b030ce3ce8031c4c41beafdb05323bec8b2a06903b43", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + }, + { + "name": "summary", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[5]", + "event:system/message.data.message.source[4]" + ], + "sources": [] + }, + { + "digest": "b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 16, + "optional": true + }, + { + "name": "seq", + "type": 11, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "item", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "category", + "type": 3, + "optional": true + }, + { + "name": "createdAt", + "type": 11, + "optional": false + }, + { + "name": "messageId", + "type": 12, + "optional": false + }, + { + "name": "note", + "type": 12, + "optional": true + }, + { + "name": "rating", + "type": 13, + "optional": false + }, + { + "name": "updatedAt", + "type": 11, + "optional": false + }, + { + "name": "version", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ] + }, + { + "kind": "literal", + "value": "instruction-following" + }, + { + "kind": "literal", + "value": "other" + }, + { + "kind": "literal", + "value": "product-interaction" + }, + { + "kind": "literal", + "value": "resource-cost" + }, + { + "kind": "literal", + "value": "security-privacy-permission" + }, + { + "kind": "literal", + "value": "service-stability" + }, + { + "kind": "literal", + "value": "task-result" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "negative" + }, + { + "kind": "literal", + "value": "positive" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "feedback/message-put" + } + ] + }, + "names": [ + "event:feedback/message-put" + ], + "sources": [] + }, + { + "digest": "b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 11, + "optional": true + }, + { + "name": "seq", + "type": 12, + "optional": false + }, + { + "name": "time", + "type": 12, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "category", + "type": 2, + "optional": true + }, + { + "name": "text", + "type": 10, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + { + "kind": "literal", + "value": "instruction-following" + }, + { + "kind": "literal", + "value": "other" + }, + { + "kind": "literal", + "value": "product-interaction" + }, + { + "kind": "literal", + "value": "resource-cost" + }, + { + "kind": "literal", + "value": "security-privacy-permission" + }, + { + "kind": "literal", + "value": "service-stability" + }, + { + "kind": "literal", + "value": "task-result" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "feedback/record" + } + ] + }, + "names": [ + "event:feedback/record" + ], + "sources": [] + }, + { + "digest": "b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "messageSeqs", + "type": 1, + "optional": false + }, + { + "name": "source", + "type": 3, + "optional": false + }, + { + "name": "title", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 4, + 8, + 10 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 5, + "optional": false + }, + { + "name": "model", + "type": 6, + "optional": true + }, + { + "name": "provider", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "provider" + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "fallback" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + } + ] + }, + "names": [ + "packages/session/session-title/src/types.ts#SessionTitleEventData" + ], + "sources": [ + "packages/session/session-title/src/types.ts:41" + ] + }, + { + "digest": "b57a2889408de36e4516807194c73bdecd51a92f633ccd05db81bf049200f420", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "failed" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "b57ae1b37e52bee0a21cbdace168dc6a15582bcda8c306dad962c5dcc8a8e28b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "team/task" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "b6ef9c6210be8350cd465f485cf520031fffa6bf3809c61a1e713e834c5982d0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "completed" + } + ] + }, + "names": [ + "event:turn/end.data.reason[0]" + ], + "sources": [ + "packages/core/session/src/types.ts:201" + ] + }, + { + "digest": "b765ee31033cea463863faf3fc6a0a1b85c47c6a2f13fe1ce833abe56105114b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "set" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 10, + "optional": true + }, + { + "name": "seq", + "type": 7, + "optional": false + }, + { + "name": "time", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 8 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "agentModel", + "type": 3, + "optional": true + }, + { + "name": "agentProvider", + "type": 3, + "optional": true + }, + { + "name": "agentReasoningEffort", + "type": 3, + "optional": true + }, + { + "name": "label", + "type": 3, + "optional": false + }, + { + "name": "mode", + "type": 4, + "optional": false + }, + { + "name": "persona", + "type": 3, + "optional": true + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "toolFilter", + "type": 5, + "optional": true + }, + { + "name": "version", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "object", + "properties": [ + { + "name": "allow", + "type": 6, + "optional": true + }, + { + "name": "deny", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "label", + "type": 3, + "optional": true + }, + { + "name": "mode", + "type": 9, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "version", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "one-shot" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "subagent/descriptor" + } + ] + }, + "names": [ + "event:subagent/descriptor" + ], + "sources": [] + }, + { + "digest": "b815d7b91cdd9fc6ccc7dcba05cfc5ce1d55fab5fc0c0801803cd3630cedb602", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "goal" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "b81e9de81a481b24e1e1f9adf038bfc59e57b4f35ce62f8f1e45284186f4f812", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + }, + { + "name": "model", + "type": 2, + "optional": true + }, + { + "name": "provider", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "provider" + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:session/title.data.source[1]" + ], + "sources": [ + "packages/session/session-title/src/types.ts:30" + ] + }, + { + "digest": "b8327af7ee5767952d1300cb4f03b7fc04197ae5ba275e13981c689ff7506172", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "literal", + "value": "complete" + }, + { + "kind": "literal", + "value": "paused" + } + ] + }, + "names": [ + "packages/goal/goal/src/types.ts#GoalPhase" + ], + "sources": [ + "packages/goal/goal/src/types.ts:45" + ] + }, + { + "digest": "b8fc636a2121df9d8423c242e03c9d23b7ab7c6fdce00e45746932c60b730f97", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 1, + "optional": true + }, + { + "name": "path", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/fs/tool-present/src/types.ts#PresentedFile" + ], + "sources": [ + "packages/fs/tool-present/src/types.ts:5" + ] + }, + { + "digest": "b9305f0ea75ccd6a6061c27fde19f2ab85534f9f3c1a717b573b077ebae0f611", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "hook/invoked" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 9, + "optional": true + }, + { + "name": "seq", + "type": 10, + "optional": false + }, + { + "name": "time", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "todos", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "status", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 6, + 7, + 8 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "todo/write" + } + ] + }, + "names": [ + "event:todo/write" + ], + "sources": [] + }, + { + "digest": "b993441f8ce7d27b80e619e113e232ae8e62d3d5a8340f6bfe12d8c4c018e62f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "tuple", + "elements": [ + { + "type": 1, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "role", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 3, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + } + ] + }, + "names": [ + "event:web/deepseek-search-llm-request.data.body.messages" + ], + "sources": [] + }, + { + "digest": "ba77b58995e36f14bb13886b0493d0a1a63d5db10b1a6cd9f6ac65a72cf832ec", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "provider", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:subagent/model-selection-policy.data.allowedModels" + ], + "sources": [] + }, + { + "digest": "babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 8, + "optional": true + }, + { + "name": "seq", + "type": 7, + "optional": false + }, + { + "name": "time", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "outcome", + "type": 2, + "optional": false + }, + { + "name": "runId", + "type": 6, + "optional": false + }, + { + "name": "seq", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 3, + 4, + 5 + ] + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "failed" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool-workflow/agent-end" + } + ] + }, + "names": [ + "event:tool-workflow/agent-end" + ], + "sources": [] + }, + { + "digest": "bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 8, + "optional": true + }, + { + "name": "seq", + "type": 9, + "optional": false + }, + { + "name": "time", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "outcome", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5, + 6, + 7 + ] + }, + { + "kind": "literal", + "value": "allowed-once" + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "rejected" + }, + { + "kind": "literal", + "value": "unavailable" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "approval/decided" + } + ] + }, + "names": [ + "event:approval/decided" + ], + "sources": [] + }, + { + "digest": "bbc5696e3f13c758237b316e3cf08cda0b9fd51ffe672539883457769faa185d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "opaque", + "reason": "unknown" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[8].replayState", + "event:assistant/attempt.data.stream[0][3].chunk[6].replayState.blocks[0]", + "event:assistant/attempt.data.stream[0][3].chunk[6].replayState.response", + "event:request/header.data.header.tools[0].parameters.[value]", + "event:tool/ptc-dispatch-start.data.arguments", + "event:tool/ptc-dispatch.data.arguments" + ], + "sources": [] + }, + { + "digest": "bcf0caf62d964b2fcf5404bd5c909cfa9a21c3f3c96e6b7e36d9e33565223825", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 1, + "optional": false + }, + { + "name": "op", + "type": 2, + "optional": false + }, + { + "name": "startSeq", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "replace" + } + ] + }, + "names": [ + "SessionEventEnvelope[6].surfaceOp[1]" + ], + "sources": [ + "packages/core/session/src/types.ts:436" + ] + }, + { + "digest": "bd499eac047efe091a4df7382eadd06d25cf0cb31fee3981e8b24d85762841cd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "finish" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "bf16d2e98a7f13894ded2cffe5ce857bf473954a0e3c0ed9bc7739e2e1c7b78a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "subagent-settled" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "c0ea97bc89dfc46fa94eab956f5c3ee6af2f964aa7db6327cb18b451f207bef7", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "outcome", + "type": 1, + "optional": false + }, + { + "name": "runId", + "type": 5, + "optional": false + }, + { + "name": "seq", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 3, + 4 + ] + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "failed" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentEndData" + ], + "sources": [ + "packages/workflow/tool-workflow/src/types.ts:29" + ] + }, + { + "digest": "c0f91c064454e9ae25a4b5caacdc487712aee3acf414b07b7d5f7fd3530a0452", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "delayMs", + "type": 1, + "optional": false + }, + { + "name": "failure", + "type": 2, + "optional": false + }, + { + "name": "mode", + "type": 4, + "optional": false + }, + { + "name": "policyKey", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "retry", + "type": 1, + "optional": false + }, + { + "name": "retryId", + "type": 3, + "optional": false + }, + { + "name": "step", + "type": 1, + "optional": false + }, + { + "name": "turn", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 3, + "optional": false + }, + { + "name": "message", + "type": 3, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 1, + "optional": true + }, + { + "name": "requestId", + "type": 3, + "optional": true + }, + { + "name": "status", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "always" + } + ] + }, + "names": [ + "event:llm/retry.data[1]" + ], + "sources": [ + "packages/llm/llm-retry/src/types.ts:30" + ] + }, + { + "digest": "c1f0f17b6493b85db6453bb30e01b208d703731b25bc1e68e5d4fc72eea9cdea", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#ToolCallBlock" + ], + "sources": [ + "packages/llm/llm/src/types.ts:91" + ] + }, + { + "digest": "c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "runId", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-workflow/run-start" + } + ] + }, + "names": [ + "event:tool-workflow/run-start" + ], + "sources": [] + }, + { + "digest": "c219efbdfd53b836786202ab4532913d555798f0a8615a4e9629736e3799fb70", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "step/end" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "c2deb7c9183735d4e0fffddf5c623f4e34daa1c8e96b111982e957dfcbbcf896", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3 + ] + }, + { + "kind": "literal", + "value": "cancelled" + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "failed" + } + ] + }, + "names": [ + "packages/workflow/workflow/src/types.ts#WorkflowAgentOutcome" + ], + "sources": [ + "packages/workflow/workflow/src/types.ts:110" + ] + }, + { + "digest": "c43b119ce579b3e9983a0d3adc4ba402ebb9ba773d89738a2dab01ecf5813943", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "paused" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "c55a891c7aac496de6ee64c91c1fb65728c977314013033e42c863428aada854", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "adapterDefaults", + "type": 1, + "optional": true + }, + { + "name": "config", + "type": 3, + "optional": false + }, + { + "name": "tools", + "type": 7, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 2, + "optional": true + }, + { + "name": "reasoningEffort", + "type": 2, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 4, + "optional": true + }, + { + "name": "model", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 5, + "optional": true + }, + { + "name": "stop", + "type": 6, + "optional": true + }, + { + "name": "temperature", + "type": 4, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "array", + "element": 8 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "parameters", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 5, + "value": 10 + } + ] + }, + { + "kind": "opaque", + "reason": "unknown" + } + ] + }, + "names": [ + "packages/core/session/src/types.ts#EpochHeader" + ], + "sources": [ + "packages/core/session/src/types.ts:232" + ] + }, + { + "digest": "c589e938cc0e4989df8b82abef6339de621f0dc4fe62c230d145b94996af7f92", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool/call" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "c592ce75aab73fcab19c1d7845684c72cf402b78d2e1f2833a58ecf9f3598ed6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3, + 4, + 5, + 6 + ] + }, + { + "kind": "array", + "element": 0 + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 3, + "value": 0 + } + ] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:tool/result.data.meta", + "packages/util/values/src/index.ts#JsonValue" + ], + "sources": [ + "packages/util/values/src/index.ts:4" + ] + }, + { + "digest": "c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "text", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[4].sections" + ], + "sources": [] + }, + { + "digest": "c8e6f48878cc2a2f0dc59682280d81f407943fb503f32c64bf9ff3d9393c3eff", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "active" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "c99e20441bf15341e9db54385e558e5c03d21dff2eb1ac08ccbf47c2d8747954", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "feedback/record" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "ca085dba2c877feceebf3c62f9d39c6c2a4d2314d97b94b3927a780fe8fe2bb2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "sandbox/mode" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "ca13866102dfc53a503fccddeebc9caf667b8a06ef27217dd65de29f0495e65d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 2, + "optional": false + }, + { + "name": "message", + "type": 2, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 3, + "optional": true + }, + { + "name": "requestId", + "type": 2, + "optional": true + }, + { + "name": "status", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "error" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[6].reason[1]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:135" + ] + }, + { + "digest": "ca59530d1a4f3e191b4032bc4a20eded0d827db8b5a7efb27b3af9e9b456c6f2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 1, + "optional": false + }, + { + "name": "error", + "type": 1, + "optional": true + }, + { + "name": "sourceCommandId", + "type": 1, + "optional": true + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 3, + 4 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:compaction/end.data" + ], + "sources": [ + "packages/compaction/compaction/src/types.ts:72" + ] + }, + { + "digest": "ca6425edd20afe34d0cedc6ad1abad1ad7b32e2fbc4c1d24db53939e80bf887f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "operation", + "type": 1, + "optional": false + }, + { + "name": "schedule", + "type": 2, + "optional": false + }, + { + "name": "version", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "union", + "types": [ + 3, + 7, + 9 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "afterSeconds", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "prompt", + "type": 5, + "optional": false + }, + { + "name": "scheduledAt", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "after" + }, + { + "kind": "object", + "properties": [ + { + "name": "everySeconds", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 8, + "optional": false + }, + { + "name": "prompt", + "type": 5, + "optional": false + }, + { + "name": "scheduledAt", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "every" + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 10, + "optional": false + }, + { + "name": "prompt", + "type": 5, + "optional": false + }, + { + "name": "scheduledAt", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "at" + }, + { + "kind": "literal", + "value": 1 + } + ] + }, + "names": [ + "packages/schedule/schedule/src/types.ts#ScheduleCreateChange" + ], + "sources": [ + "packages/schedule/schedule/src/types.ts:72" + ] + }, + { + "digest": "cbca49bde16b656d5b8fbe4a8a507769f7b594c6287782b809c03814941bc5e3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "block-start" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "cc3e270ab982cbafff602b753647b6f056decb7fe4fd523afd2f157a65c3d622", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 37, + "optional": true + }, + { + "name": "seq", + "type": 17, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 38, + "optional": true + }, + { + "name": "surfaceOp", + "type": 39, + "optional": false + }, + { + "name": "time", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "error", + "type": 2, + "optional": true + }, + { + "name": "message", + "type": 4, + "optional": false + }, + { + "name": "meta", + "type": 33, + "optional": true + }, + { + "name": "step", + "type": 17, + "optional": false + }, + { + "name": "turn", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "role", + "type": 30, + "optional": false + }, + { + "name": "source", + "type": 31, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 6, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "isError", + "type": 28, + "optional": true + }, + { + "name": "toolCallId", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 8 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 11, + 13, + 15, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "union", + "types": [ + 3, + 17, + 28, + 34, + 35, + 36 + ] + }, + { + "kind": "array", + "element": 33 + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 3, + "value": 33 + } + ] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 17 + }, + { + "kind": "union", + "types": [ + 40, + 41 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 17, + "optional": false + }, + { + "name": "op", + "type": 42, + "optional": false + }, + { + "name": "startSeq", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "tool/result" + } + ] + }, + "names": [ + "event:tool/result" + ], + "sources": [] + }, + { + "digest": "cd186c309e2bef305705e1d324139240f5e681f4f6a957d6a25b0225f65560df", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "approval/asked" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "cdca80b8dad24d4e7b1d5a288a8475ba8d8d46be385bd2da85f122ee836f1670", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool-workflow/run-start" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 15, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "apiVersion", + "type": 2, + "optional": false + }, + { + "name": "body", + "type": 3, + "optional": false + }, + { + "name": "endpoint", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "object", + "properties": [ + { + "name": "max_tokens", + "type": 4, + "optional": false + }, + { + "name": "messages", + "type": 5, + "optional": false + }, + { + "name": "model", + "type": 2, + "optional": false + }, + { + "name": "tools", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "tuple", + "elements": [ + { + "type": 6, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "tuple", + "elements": [ + { + "type": 8, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "tuple", + "elements": [ + { + "type": 12, + "optional": false, + "rest": false + } + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "max_uses", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 13, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "web_search" + }, + { + "kind": "literal", + "value": "web_search_20250305" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "web/deepseek-search-llm-request" + } + ] + }, + "names": [ + "event:web/deepseek-search-llm-request" + ], + "sources": [] + }, + { + "digest": "cffcdc19881ab61ec0a9ce18743996f7ac44ac8889f34dfac36ae745574aa22f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "cleared", + "type": 1, + "optional": false + }, + { + "name": "clearedAt", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 4, + "optional": false + }, + { + "name": "operation", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "revision", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "goal/change" + }, + { + "kind": "literal", + "value": "clear" + }, + { + "kind": "literal", + "value": 1 + } + ] + }, + "names": [ + "packages/goal/goal/src/domain.ts#GoalClearChangeMeta" + ], + "sources": [ + "packages/goal/goal/src/domain.ts:35" + ] + }, + { + "digest": "d0fbb0ca1bbba059830826f33f801da1aced35a2504376d61e020bdfec8d50c0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "delete" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "d28765f87d6b6ab408e0b2f3e7e297791b004b806d8e302c49903ebae815feb2", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "one-shot" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "d489162978ec04f1f76f40a68c7715326f46ec4f696edf1f321efdf88545fc18", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "message", + "type": 1, + "optional": false + }, + { + "name": "step", + "type": 9, + "optional": false + }, + { + "name": "turn", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "role", + "type": 27, + "optional": false + }, + { + "name": "source", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 4, + 7, + 17, + 20, + 23, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "mediaType", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 15, + "optional": true + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 11, + 12, + 13, + 14 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "isError", + "type": 21, + "optional": true + }, + { + "name": "toolCallId", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "union", + "types": [ + 29, + 34, + 36, + 38, + 40, + 42, + 44 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 30, + "optional": false + }, + { + "name": "kind", + "type": 31, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "sections", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 33 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "text", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 31, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 37, + "optional": false + }, + { + "name": "kind", + "type": 31, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 31, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 31, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 31, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 31, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "event:system/message.data" + ], + "sources": [ + "packages/core/session/src/types.ts:310" + ] + }, + { + "digest": "d4e4d575abeb4b72d616dc65c17ac0eaa943cc135609395044c38d5afcb10e31", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "canceled" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 12, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "task", + "type": 2, + "optional": false + }, + { + "name": "teamId", + "type": 4, + "optional": false + }, + { + "name": "version", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "blockedBy", + "type": 3, + "optional": false + }, + { + "name": "description", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "ownerId", + "type": 4, + "optional": true + }, + { + "name": "revision", + "type": 5, + "optional": false + }, + { + "name": "status", + "type": 6, + "optional": false + }, + { + "name": "subject", + "type": 4, + "optional": false + }, + { + "name": "writeScopes", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 7, + 8, + 9, + 10 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "deleted" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "team/task" + } + ] + }, + "names": [ + "event:team/task" + ], + "sources": [] + }, + { + "digest": "d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "sessionFormatVersion", + "type": 2, + "optional": true + }, + { + "name": "sessionId", + "type": 3, + "optional": false + }, + { + "name": "throughSeq", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "session-log-deepseek/delivery-accepted" + } + ] + }, + "names": [ + "event:session-log-deepseek/delivery-accepted" + ], + "sources": [] + }, + { + "digest": "d6e654b88b584fcdf290a56802b1730bc7b9847ae86baa4265ab7769eb48990c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "fallback" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "d8f868c6b3e7189de6c1ffe2ea04794ea2aa31315b6347d12536e46020afac87", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 5, + 7 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "afterSeconds", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 4, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "after" + }, + { + "kind": "object", + "properties": [ + { + "name": "everySeconds", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 6, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "every" + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "kind", + "type": 8, + "optional": false + }, + { + "name": "prompt", + "type": 3, + "optional": false + }, + { + "name": "scheduledAt", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "at" + } + ] + }, + "names": [ + "packages/schedule/schedule/src/types.ts#ScheduleRecord" + ], + "sources": [ + "packages/schedule/schedule/src/types.ts:69" + ] + }, + { + "digest": "d9196086e41e4dd5c2ce87f5c25529c8112a1731d258cf0728747d41a9ee3241", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "complete" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "d9c462b9f62fdd3efc76a44b9524dc8781f6b91cd9c7e584b17d971defafebab", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool" + } + ] + }, + "names": [ + "packages/llm/llm/src/message.ts#ToolMessageSource" + ], + "sources": [ + "packages/llm/llm/src/message.ts:29" + ] + }, + { + "digest": "da807583a19f4ebe2bc9115c9686016e1521e405bdfaced296ae652b91e8e5f6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 28, + "optional": true + }, + { + "name": "seq", + "type": 17, + "optional": false + }, + { + "name": "time", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "parentCallId", + "type": 7, + "optional": false + }, + { + "name": "rootCallId", + "type": 7, + "optional": false + }, + { + "name": "subCallId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 9, + 11, + 13, + 15, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "isError", + "type": 6, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "mediaType", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 23, + "optional": true + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 19, + 20, + 21, + 22 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 26, + "optional": false + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "tool/ptc-dispatch" + } + ] + }, + "names": [ + "event:tool/ptc-dispatch" + ], + "sources": [] + }, + { + "digest": "dacf3d5f3e4ff619c1320e64a48dec5e7ecd7870bece65fbd449a0e63adfa521", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": true + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "db6c5246911d47a7969f6b4cb47c7d0dbbde75c402710b22ace3672fa97bebe0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "webhook" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 6, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 2, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 2, + "optional": true + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "union", + "types": [ + 4, + 5 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "compaction/start" + } + ] + }, + "names": [ + "event:compaction/start" + ], + "sources": [] + }, + { + "digest": "dbdafceb9a900183cccc80596498d386f3bf7bddead2b8cfde68453aa4739edc", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "hook" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "dd2780e7e07684332f7a9ab047237798d260e7eaffb1321cb38eca695d184c3d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "fresh" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "dda6e111a8ccfc12f6e742e977a1a4e3549e8c03732a5ff6c8d7c3d4bcb7a412", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[2]", + "event:system/message.data.message.source[1]" + ], + "sources": [] + }, + { + "digest": "de25352d2dc4196309bd84b5bac6319f6d5c898ea0e82bb5ae66c5738d0d426f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "user/message" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "de30b1fa12c75f81df6aa4096e1595296d2d04e2be14a7c29849cf8028b79048", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "image/gif" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "df5d9709b7db2a72510dc52052a23f0c38f39730af1504bab8bccabc14fae528", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "positive" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "df843efd87c78cf8de98f8e0d0216956cd568d0d0e56a22c853663e274aa1a18", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 2, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 3, + "optional": false + }, + { + "name": "compacted", + "type": 5, + "optional": false + }, + { + "name": "inputIndex", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 6, + "optional": false + }, + { + "name": "omittedBytes", + "type": 2, + "optional": false + }, + { + "name": "omittedMessages", + "type": 2, + "optional": false + }, + { + "name": "originalMessages", + "type": 2, + "optional": false + }, + { + "name": "retainedMessages", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 6, + "optional": false + }, + { + "name": "truncated", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 2, + 4 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[15].references" + ], + "sources": [] + }, + { + "digest": "dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3 + ] + }, + { + "kind": "literal", + "value": "active" + }, + { + "kind": "literal", + "value": "failed" + }, + { + "kind": "literal", + "value": "provisioning" + } + ] + }, + "names": [ + "packages/experimental/agent-team/src/types.ts#TeamMemberPhase" + ], + "sources": [ + "packages/experimental/agent-team/src/types.ts:44" + ] + }, + { + "digest": "e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "step/end" + } + ] + }, + "names": [ + "event:step/end" + ], + "sources": [] + }, + { + "digest": "e1161343902a8da654d4d1bb66d729065cc93a2710ebfe2717a242a5c2ec709a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 4, + 6, + 8, + 18, + 21 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 9, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 2, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "mediaType", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": true + }, + { + "name": "originalDimensions", + "type": 16, + "optional": true + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 12, + 13, + 14, + 15 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 10, + "optional": false + }, + { + "name": "width", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 2, + "optional": false + }, + { + "name": "bytes", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 22, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 0 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#ContentBlock" + ], + "sources": [ + "packages/llm/llm/src/types.ts:124" + ] + }, + { + "digest": "e1d0e52dcd1855ffb803efc18f8f7b776c48d1c25757e8a75c0281fa298c02e1", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 5, + 7, + 8 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "acceptedAt", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "operation", + "type": 3, + "optional": false + }, + { + "name": "version", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "dispatch" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "operation", + "type": 6, + "optional": false + }, + { + "name": "version", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "delete" + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "operation", + "type": 3, + "optional": false + }, + { + "name": "version", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "operation", + "type": 9, + "optional": false + }, + { + "name": "schedule", + "type": 10, + "optional": false + }, + { + "name": "version", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "create" + }, + { + "kind": "union", + "types": [ + 11, + 14, + 16 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "afterSeconds", + "type": 12, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 13, + "optional": false + }, + { + "name": "prompt", + "type": 2, + "optional": false + }, + { + "name": "scheduledAt", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "after" + }, + { + "kind": "object", + "properties": [ + { + "name": "everySeconds", + "type": 12, + "optional": false + }, + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 15, + "optional": false + }, + { + "name": "prompt", + "type": 2, + "optional": false + }, + { + "name": "scheduledAt", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "every" + }, + { + "kind": "object", + "properties": [ + { + "name": "id", + "type": 2, + "optional": false + }, + { + "name": "kind", + "type": 17, + "optional": false + }, + { + "name": "prompt", + "type": 2, + "optional": false + }, + { + "name": "scheduledAt", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "at" + } + ] + }, + "names": [ + "packages/schedule/schedule/src/types.ts#ScheduleChange" + ], + "sources": [ + "packages/schedule/schedule/src/types.ts:105" + ] + }, + { + "digest": "e2d0367e5716bc188435e51718ebff8ce873190625fa93b1c5331102d98758fa", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "compaction/start" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e31572180d6b28b73f073ad30ae19d5d82982d3daf984723984e23da1e7c459a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "provider" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e3528b95cff70d83a080a96666877da3f9dc205e71955d6bc5b9b315b9e0da22", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 3, + 5, + 10, + 23, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "interrupted" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "error", + "type": 6, + "optional": false + }, + { + "name": "kind", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 7, + "optional": false + }, + { + "name": "message", + "type": 7, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 8, + "optional": true + }, + { + "name": "requestId", + "type": 7, + "optional": true + }, + { + "name": "status", + "type": 8, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 11, + "optional": false + }, + { + "name": "reason", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "union", + "types": [ + 13, + 15, + 17, + 19, + 21 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "disposed" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "legacy" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "parent" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 20, + "optional": false + }, + { + "name": "reason", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hook" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "blocked" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "completed" + } + ] + }, + "names": [ + "packages/core/session/src/types.ts#TurnEndReason" + ], + "sources": [ + "packages/core/session/src/types.ts:224" + ] + }, + { + "digest": "e3e77b26f0148755a505f5b8ea843a3be117827d4703bb5754d9dc8ddbef521f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "ignorable", + "type": 1, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "SessionEventEnvelope[0]", + "SessionEventEnvelope[10]", + "SessionEventEnvelope[11]", + "SessionEventEnvelope[12]", + "SessionEventEnvelope[13]", + "SessionEventEnvelope[14]", + "SessionEventEnvelope[15]", + "SessionEventEnvelope[16]", + "SessionEventEnvelope[17]", + "SessionEventEnvelope[18]", + "SessionEventEnvelope[19]", + "SessionEventEnvelope[1]", + "SessionEventEnvelope[20]", + "SessionEventEnvelope[21]", + "SessionEventEnvelope[22]", + "SessionEventEnvelope[23]", + "SessionEventEnvelope[24]", + "SessionEventEnvelope[25]", + "SessionEventEnvelope[26]", + "SessionEventEnvelope[27]", + "SessionEventEnvelope[28]", + "SessionEventEnvelope[29]", + "SessionEventEnvelope[2]", + "SessionEventEnvelope[30]", + "SessionEventEnvelope[31]", + "SessionEventEnvelope[32]", + "SessionEventEnvelope[33]", + "SessionEventEnvelope[34]", + "SessionEventEnvelope[35]", + "SessionEventEnvelope[36]", + "SessionEventEnvelope[37]", + "SessionEventEnvelope[39]", + "SessionEventEnvelope[3]", + "SessionEventEnvelope[40]", + "SessionEventEnvelope[41]", + "SessionEventEnvelope[42]", + "SessionEventEnvelope[43]", + "SessionEventEnvelope[44]", + "SessionEventEnvelope[45]", + "SessionEventEnvelope[46]", + "SessionEventEnvelope[47]", + "SessionEventEnvelope[48]", + "SessionEventEnvelope[49]", + "SessionEventEnvelope[4]", + "SessionEventEnvelope[50]", + "SessionEventEnvelope[52]", + "SessionEventEnvelope[53]", + "SessionEventEnvelope[55]", + "SessionEventEnvelope[5]", + "SessionEventEnvelope[7]", + "SessionEventEnvelope[8]", + "SessionEventEnvelope[9]" + ], + "sources": [] + }, + { + "digest": "e41bdebe246abe17154de7e7a1d81035437eee8d20e7f1edff0d91e2598ad828", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "session" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e42b0f40b5d0b628c6e1c44a8fb9eda0780037a81959e1b93d2dfb47b46b0e87", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "every" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e438a9d4fb883b3e4a8d9fc8913aa1550ed50eb384bfbf943100a5605b537498", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "append" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e4c18e294232c3ba6c9f1999f168263cc55956147cfe4121720899be8e52edd1", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 1, + "optional": false + }, + { + "name": "callId", + "type": 1, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + }, + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:tool/call.data" + ], + "sources": [ + "packages/core/session/src/types.ts:341" + ] + }, + { + "digest": "e4cac694f8b6e1d52e48c699464f1c4c26111ef87e558f7931040d00fdc8d486", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "instruction-following" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e5244812c495a80c2e78ba2c2690dca5f6b58ea8d3fe5531ff94a6f9ac5e5d66", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool-result" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e5d3828df1ec3e2a66879de0659e0f4866d9cfffc00825ee731a66f8a0c03d98", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "continuable" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e6063d0451dc02026fd9b54ad3f77c7b1b6392ad3e872a7737ac84e13ca72344", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "acceptedAt", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 1, + "optional": false + }, + { + "name": "operation", + "type": 2, + "optional": false + }, + { + "name": "version", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "dispatch" + }, + { + "kind": "literal", + "value": 1 + } + ] + }, + "names": [ + "packages/schedule/schedule/src/types.ts#EveryScheduleDispatchChange" + ], + "sources": [ + "packages/schedule/schedule/src/types.ts:93" + ] + }, + { + "digest": "e6411b09c1d982f789bf741ca74e2bea69df56029847f86a6148e722039ee732", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "parent" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e654d28d1e6c471147310d2fba396d00969cfe6ed9022626d3f2c2312e0b1cbb", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "plugin" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e72269f931559e901e5e8fe1cfcbd0669fb795f43cc038ff39fba62f60403c33", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 1, + "optional": false + }, + { + "name": "index", + "type": 2, + "optional": false + }, + { + "name": "texts", + "type": 3, + "optional": false + }, + { + "name": "time0", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "text-chunks" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][0]" + ], + "sources": [ + "packages/llm/llm/src/assistant-stream.ts:21" + ] + }, + { + "digest": "e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "decision", + "type": 2, + "optional": false + }, + { + "name": "durationMs", + "type": 3, + "optional": false + }, + { + "name": "exitCode", + "type": 3, + "optional": true + }, + { + "name": "handlerId", + "type": 2, + "optional": false + }, + { + "name": "point", + "type": 2, + "optional": false + }, + { + "name": "stderrSummary", + "type": 2, + "optional": true + }, + { + "name": "turn", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "hook/result" + } + ] + }, + "names": [ + "event:hook/result" + ], + "sources": [] + }, + { + "digest": "e7ce49f12953583fad217bf80fb9f1463acff2c3e9471a823782661675fb124c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "initial" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e7e154d47d5fbc5366330fb4dade1f108c6dfe4335044d21ded14b7bf1dce084", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "clear" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e7e8f1318170b8284f16ab45a557afd4675c96071c0f2a456636481dab20b427", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "deleted" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e8d26e6993445743e6c4342c31b284c7d8f5574369abdd6021183af1a6b3538f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "plugin", + "type": 3, + "optional": false + }, + { + "name": "sections", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "text", + "type": 3, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "event:agent/inbox/spliced.data.inserted[0].source[4]", + "event:system/message.data.message.source[3]" + ], + "sources": [] + }, + { + "digest": "e8de0f457fed5b9ff94dc4950b59ef75fd8ba8af665cc7899485924a62653367", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "notice" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "e95f957154b2b1b58692af53d4131180b6dd19e2264869f876dbaa959991494e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "assistant/attempt" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "eaa71be0fbf0f702df8ea6c11d0aa2f9782263d45a10d0fb14eda662f4bfe04c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "legacy" + } + ] + }, + "names": [ + "event:turn/end.data.reason[1].reason[4]" + ], + "sources": [ + "packages/core/session/src/types.ts:195" + ] + }, + { + "digest": "eb1d32dcd3d76833392f351e039dcbd40686a1ab24b252a7a5003426f9cf4c89", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 32 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 3, + "optional": false + }, + { + "name": "llmStreamCall", + "type": 4, + "optional": false + }, + { + "name": "maxTokens", + "type": 5, + "optional": true + }, + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "rawOutput", + "type": 6, + "optional": false + }, + { + "name": "shadowedRange", + "type": 29, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 30, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 5, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 3, + "optional": true + }, + { + "name": "summary", + "type": 6, + "optional": false + }, + { + "name": "usage", + "type": 31, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 7 + }, + { + "kind": "union", + "types": [ + 8, + 11, + 13, + 15, + 17, + 26 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 6, + "optional": false + }, + { + "name": "isError", + "type": 9, + "optional": true + }, + { + "name": "toolCallId", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 5, + "optional": false + }, + { + "name": "height", + "type": 5, + "optional": false + }, + { + "name": "mediaType", + "type": 19, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "originalDimensions", + "type": 24, + "optional": true + }, + { + "name": "width", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 20, + 21, + 22, + 23 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 5, + "optional": false + }, + { + "name": "width", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 27, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "end", + "type": 5, + "optional": false + }, + { + "name": "start", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 5, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 5, + "optional": true + }, + { + "name": "inputTokens", + "type": 5, + "optional": false + }, + { + "name": "outputTokens", + "type": 5, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 5, + "optional": true + }, + { + "name": "totalTokens", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 3, + "optional": false + }, + { + "name": "maxTokens", + "type": 5, + "optional": true + }, + { + "name": "model", + "type": 3, + "optional": false + }, + { + "name": "provider", + "type": 3, + "optional": false + }, + { + "name": "rawOutput", + "type": 6, + "optional": true + }, + { + "name": "shadowedRange", + "type": 29, + "optional": false + }, + { + "name": "shadowedSeqs", + "type": 30, + "optional": false + }, + { + "name": "shadowedTokenCount", + "type": 5, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 3, + "optional": true + }, + { + "name": "summary", + "type": 6, + "optional": false + }, + { + "name": "usage", + "type": 31, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "compaction/summary" + } + ] + }, + "names": [ + "event:compaction/summary" + ], + "sources": [] + }, + { + "digest": "ebb768d85ec87fb68e42fc14fd7d8535b5ac11791b14d04c6770180f0a719bf8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "image/webp" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 4, + "optional": true + }, + { + "name": "seq", + "type": 5, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "parentCallId", + "type": 3, + "optional": false + }, + { + "name": "rootCallId", + "type": 3, + "optional": false + }, + { + "name": "subCallId", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool/ptc-dispatch-start" + } + ] + }, + "names": [ + "event:tool/ptc-dispatch-start" + ], + "sources": [] + }, + { + "digest": "ecaf9469000a15d46d269ea72994ceaab3652ed31507822906fc12397422da21", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 7, + 64, + 66 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "args", + "type": 2, + "optional": false + }, + { + "name": "dt", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "index", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "time0", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "tool-call-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "chunk", + "type": 8, + "optional": false + }, + { + "name": "time", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 9, + 11, + 36, + 39, + 41, + 43, + 60 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "argumentsDelta", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "index", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "block", + "type": 12, + "optional": false + }, + { + "name": "index", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 13, + 15, + 24, + 27, + 31, + 33 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 3, + "optional": false + }, + { + "name": "id", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 5, + "optional": false + }, + { + "name": "height", + "type": 5, + "optional": false + }, + { + "name": "mediaType", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": true + }, + { + "name": "originalDimensions", + "type": 22, + "optional": true + }, + { + "name": "width", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 18, + 19, + 20, + 21 + ] + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 5, + "optional": false + }, + { + "name": "width", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 25, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 3, + "optional": false + }, + { + "name": "bytes", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 28, + "optional": false + }, + { + "name": "isError", + "type": 29, + "optional": true + }, + { + "name": "toolCallId", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 12 + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "block-end" + }, + { + "kind": "object", + "properties": [ + { + "name": "blockType", + "type": 37, + "optional": false + }, + { + "name": "index", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 14, + 23, + 26, + 30, + 32, + 34 + ] + }, + { + "kind": "literal", + "value": "block-start" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 5, + "optional": false + }, + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "index", + "type": 5, + "optional": false + }, + { + "name": "text", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-delta" + }, + { + "kind": "object", + "properties": [ + { + "name": "reason", + "type": 44, + "optional": false + }, + { + "name": "replayState", + "type": 56, + "optional": true + }, + { + "name": "type", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 45, + 48, + 50, + 52, + 54 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 46, + "optional": false + }, + { + "name": "kind", + "type": 47, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 3, + "optional": false + }, + { + "name": "message", + "type": 3, + "optional": false + }, + { + "name": "providerRetryAfterMs", + "type": 5, + "optional": true + }, + { + "name": "requestId", + "type": 3, + "optional": true + }, + { + "name": "status", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "aborted" + }, + { + "kind": "object", + "properties": [ + { + "name": "failure", + "type": 46, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "error" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 51, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "max-tokens" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 53, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "stop" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-calls" + }, + { + "kind": "object", + "properties": [ + { + "name": "blocks", + "type": 57, + "optional": true + }, + { + "name": "response", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 58 + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "literal", + "value": "finish" + }, + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 61, + "optional": false + }, + { + "name": "usage", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 5, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 5, + "optional": true + }, + { + "name": "inputTokens", + "type": 5, + "optional": false + }, + { + "name": "outputTokens", + "type": 5, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 5, + "optional": true + }, + { + "name": "totalTokens", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "chunk" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 4, + "optional": false + }, + { + "name": "index", + "type": 5, + "optional": false + }, + { + "name": "texts", + "type": 2, + "optional": false + }, + { + "name": "time0", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning-chunks" + }, + { + "kind": "object", + "properties": [ + { + "name": "dt", + "type": 4, + "optional": false + }, + { + "name": "index", + "type": 5, + "optional": false + }, + { + "name": "texts", + "type": 2, + "optional": false + }, + { + "name": "time0", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text-chunks" + } + ] + }, + "names": [ + "packages/llm/llm/src/assistant-stream.ts#AssistantStreamRecord" + ], + "sources": [ + "packages/llm/llm/src/assistant-stream.ts:20" + ] + }, + { + "digest": "eefbea819caeeea91ccb02dfafc16c68dd7f961cf9c1b2be25a2084509cf5c5a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "block-end" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "ef11a3b7fa2033ae4279545ff8a8a2ee4e301b9b73e5b91654c48e6d08283545", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "step", + "type": 1, + "optional": false + }, + { + "name": "turn", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:step/end.data", + "event:step/start.data" + ], + "sources": [ + "packages/core/session/src/types.ts:287", + "packages/core/session/src/types.ts:289" + ] + }, + { + "digest": "ef4f00923b9edbae15c7ef05320280bf61147ca17146cf59690359bfd5e5da45", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "compaction/summary" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "f051068babb7ebab80e9fd4fff3a70cfe54430c2ef3ca9bb77bb54db8bc34476", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "role", + "type": 26, + "optional": false + }, + { + "name": "source", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "union", + "types": [ + 3, + 6, + 16, + 19, + 22, + 24 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 8, + "optional": false + }, + { + "name": "height", + "type": 8, + "optional": false + }, + { + "name": "mediaType", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": true + }, + { + "name": "originalDimensions", + "type": 14, + "optional": true + }, + { + "name": "width", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 10, + 11, + 12, + 13 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 8, + "optional": false + }, + { + "name": "width", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 4, + "optional": false + }, + { + "name": "bytes", + "type": 8, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 1, + "optional": false + }, + { + "name": "isError", + "type": 20, + "optional": true + }, + { + "name": "toolCallId", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 25, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "union", + "types": [ + 27, + 28, + 29 + ] + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 31, + 41, + 43, + 44, + 47, + 52, + 54, + 59, + 60, + 61, + 63, + 65, + 66, + 73, + 75, + 77, + 79, + 81, + 84, + 85 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 32, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 4, + "optional": true + }, + { + "name": "changes", + "type": 33, + "optional": false + }, + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 34 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 35, + "optional": false + }, + { + "name": "digest", + "type": 4, + "optional": true + }, + { + "name": "path", + "type": 4, + "optional": false + }, + { + "name": "scope", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 36, + 37, + 38 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 4, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 4, + "optional": true + }, + { + "name": "kind", + "type": 29, + "optional": false + }, + { + "name": "rpcId", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 4, + "optional": false + }, + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + }, + { + "name": "ruleId", + "type": 4, + "optional": false + }, + { + "name": "source", + "type": 4, + "optional": false + }, + { + "name": "summary", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 48, + "optional": false + }, + { + "name": "form", + "type": 50, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "update", + "type": 32, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 49 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 4, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "name", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 55, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + }, + { + "name": "sections", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 58 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 4, + "optional": false + }, + { + "name": "text", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + }, + { + "name": "summary", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 64, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 50, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + }, + { + "name": "references", + "type": 68, + "optional": false + }, + { + "name": "version", + "type": 72, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 69 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 8, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 70, + "optional": false + }, + { + "name": "compacted", + "type": 20, + "optional": false + }, + { + "name": "inputIndex", + "type": 8, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": false + }, + { + "name": "omittedBytes", + "type": 8, + "optional": false + }, + { + "name": "omittedMessages", + "type": 8, + "optional": false + }, + { + "name": "originalMessages", + "type": 8, + "optional": false + }, + { + "name": "retainedMessages", + "type": 8, + "optional": false + }, + { + "name": "sessionId", + "type": 4, + "optional": false + }, + { + "name": "truncated", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 8, + 71 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 74, + "optional": false + }, + { + "name": "senderSessionId", + "type": 4, + "optional": false + }, + { + "name": "summary", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 64, + "optional": false + }, + { + "name": "kind", + "type": 76, + "optional": false + }, + { + "name": "senderSessionId", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 4, + "optional": false + }, + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "revision", + "type": 8, + "optional": false + }, + { + "name": "round", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "messageId", + "type": 4, + "optional": false + }, + { + "name": "senderId", + "type": 4, + "optional": false + }, + { + "name": "senderName", + "type": 4, + "optional": false + }, + { + "name": "teamId", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "model", + "type": 4, + "optional": false + }, + { + "name": "provider", + "type": 4, + "optional": false + }, + { + "name": "replayState", + "type": 83, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 29, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "packages/llm/llm/src/message.ts#Message" + ], + "sources": [ + "packages/llm/llm/src/message.ts:131" + ] + }, + { + "digest": "f05fb85e715af4ab4de0b6ba111166dd0435bd8bb74a0fa0f32b9d174113fc7c", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "task-result" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 1, + "optional": false + }, + { + "name": "provider", + "type": 1, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + } + ] + }, + "names": [ + "packages/api/session-controller/src/types.ts#ModelSelection" + ], + "sources": [ + "packages/api/session-controller/src/types.ts:86" + ] + }, + { + "digest": "f1996af2602e6f62b5ab179ca5c4af00d8753938b74cc6ef102de09e99e74a7e", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "command/run" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "f19bee84bf3d20904553cdcd57465232bc1c2a194f54c4d195292ba0487223a6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 1, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "packages/attachment/attachment/src/types.ts#FileAttachmentRef" + ], + "sources": [ + "packages/attachment/attachment/src/types.ts:39" + ] + }, + { + "digest": "f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "type", + "type": 1, + "optional": false + }, + { + "name": "usage", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "usage" + }, + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 3, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 3, + "optional": true + }, + { + "name": "inputTokens", + "type": 3, + "optional": false + }, + { + "name": "outputTokens", + "type": 3, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 3, + "optional": true + }, + { + "name": "totalTokens", + "type": 3, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:assistant/attempt.data.stream[0][3].chunk[5]" + ], + "sources": [ + "packages/llm/llm/src/types.ts:396" + ] + }, + { + "digest": "f2da0f27c1e6add59982c37b15e4a4912c5b33466859cce3b13d503600e24aef", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 1, + "optional": false + }, + { + "name": "type", + "type": 4, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 2, + "optional": false + }, + { + "name": "bytes", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "file" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#FileBlock" + ], + "sources": [ + "packages/llm/llm/src/types.ts:84" + ] + }, + { + "digest": "f311671e07bc7fa645a0b6597de9966b97147956565296ba76a36543801b66c5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "in-history" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#SystemPromptUpdate" + ], + "sources": [ + "packages/llm/llm/src/types.ts:347" + ] + }, + { + "digest": "f3cfc98798d8b0e726ee22b05b13ada5cb19e9cdbe105ef3529699de99ce357a", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "array", + "element": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "role", + "type": 27, + "optional": false + }, + { + "name": "source", + "type": 31, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 4, + 7, + 17, + 20, + 23, + 25 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 8, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "mediaType", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 15, + "optional": true + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 11, + 12, + 13, + 14 + ] + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 9, + "optional": false + }, + { + "name": "width", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 19, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 9, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "isError", + "type": 21, + "optional": true + }, + { + "name": "toolCallId", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 22, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "union", + "types": [ + 28, + 29, + 30 + ] + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 32, + 42, + 44, + 45, + 48, + 53, + 55, + 60, + 61, + 62, + 64, + 66, + 67, + 74, + 76, + 78, + 80, + 82, + 85, + 86 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 33, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 5, + "optional": true + }, + { + "name": "changes", + "type": 34, + "optional": false + }, + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 41, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 35 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 36, + "optional": false + }, + { + "name": "digest", + "type": 5, + "optional": true + }, + { + "name": "path", + "type": 5, + "optional": false + }, + { + "name": "scope", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 37, + 38, + 39 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 5, + "optional": true + }, + { + "name": "kind", + "type": 30, + "optional": false + }, + { + "name": "rpcId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 5, + "optional": false + }, + { + "name": "form", + "type": 46, + "optional": false + }, + { + "name": "kind", + "type": 47, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "ruleId", + "type": 5, + "optional": false + }, + { + "name": "source", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 49, + "optional": false + }, + { + "name": "form", + "type": 51, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "update", + "type": 33, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 50 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 56, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "sections", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 59 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "text", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 46, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 63, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 65, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 51, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 63, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + }, + { + "name": "references", + "type": 69, + "optional": false + }, + { + "name": "version", + "type": 73, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 70 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 9, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 71, + "optional": false + }, + { + "name": "compacted", + "type": 21, + "optional": false + }, + { + "name": "inputIndex", + "type": 9, + "optional": false + }, + { + "name": "label", + "type": 5, + "optional": false + }, + { + "name": "omittedBytes", + "type": 9, + "optional": false + }, + { + "name": "omittedMessages", + "type": 9, + "optional": false + }, + { + "name": "originalMessages", + "type": 9, + "optional": false + }, + { + "name": "retainedMessages", + "type": 9, + "optional": false + }, + { + "name": "sessionId", + "type": 5, + "optional": false + }, + { + "name": "truncated", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 9, + 72 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 46, + "optional": false + }, + { + "name": "kind", + "type": 75, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 65, + "optional": false + }, + { + "name": "kind", + "type": 77, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 79, + "optional": false + }, + { + "name": "revision", + "type": 9, + "optional": false + }, + { + "name": "round", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 81, + "optional": false + }, + { + "name": "messageId", + "type": 5, + "optional": false + }, + { + "name": "senderId", + "type": 5, + "optional": false + }, + { + "name": "senderName", + "type": 5, + "optional": false + }, + { + "name": "teamId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 83, + "optional": false + }, + { + "name": "model", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "replayState", + "type": 84, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "plugin", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 30, + "optional": false + } + ], + "indices": [] + } + ] + }, + "names": [ + "event:session/title-llm-request.data.messages" + ], + "sources": [] + }, + { + "digest": "f56c5b59ca0161310c338d31dd16cf14287ccd602ff4181058df178c1c286d7f", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 1, + "optional": false + }, + { + "name": "kind", + "type": 2, + "optional": false + }, + { + "name": "references", + "type": 3, + "optional": false + }, + { + "name": "version", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 5, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 6, + "optional": false + }, + { + "name": "compacted", + "type": 8, + "optional": false + }, + { + "name": "inputIndex", + "type": 5, + "optional": false + }, + { + "name": "label", + "type": 9, + "optional": false + }, + { + "name": "omittedBytes", + "type": 5, + "optional": false + }, + { + "name": "omittedMessages", + "type": 5, + "optional": false + }, + { + "name": "originalMessages", + "type": 5, + "optional": false + }, + { + "name": "retainedMessages", + "type": 5, + "optional": false + }, + { + "name": "sessionId", + "type": 9, + "optional": false + }, + { + "name": "truncated", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 5, + 7 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": 1 + } + ] + }, + "names": [ + "packages/context/session-reference/src/types.ts#SessionReferenceSource" + ], + "sources": [ + "packages/context/session-reference/src/types.ts:13" + ] + }, + { + "digest": "f634871d1e18b1eb199497ac8ab72d3964f48d03fbf611dc7521a1970cf429d9", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "plan/mode" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "f6cb462f808309e315fea634f1ed86268b8346bbcee6774d9097ca6eed7ac1f8", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "replace" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "f757322e25914d63799c6235af4ed4b4f36d7d083fef40d6873a45e94fe6ad65", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 32, + "optional": true + }, + { + "name": "seq", + "type": 11, + "optional": false + }, + { + "name": "time", + "type": 11, + "optional": false + }, + { + "name": "type", + "type": 90, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "inserted", + "type": 2, + "optional": false + }, + { + "name": "outcome", + "type": 86, + "optional": true + }, + { + "name": "removedCount", + "type": 11, + "optional": true + }, + { + "name": "start", + "type": 11, + "optional": false + }, + { + "name": "target", + "type": 87, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 29, + "optional": false + }, + { + "name": "source", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 19, + 22, + 25, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "mediaType", + "type": 12, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 17, + "optional": true + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 13, + 14, + 15, + 16 + ] + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 11, + "optional": false + }, + { + "name": "width", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 20, + "optional": false + }, + { + "name": "type", + "type": 21, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 11, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "isError", + "type": 23, + "optional": true + }, + { + "name": "toolCallId", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 24, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool-result" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 31, + 41, + 43, + 44, + 47, + 52, + 54, + 59, + 60, + 61, + 63, + 65, + 66, + 73, + 75, + 77, + 79, + 81, + 84, + 85 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 32, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 7, + "optional": true + }, + { + "name": "changes", + "type": 33, + "optional": false + }, + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "array", + "element": 34 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 35, + "optional": false + }, + { + "name": "digest", + "type": 7, + "optional": true + }, + { + "name": "path", + "type": 7, + "optional": false + }, + { + "name": "scope", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 36, + 37, + 38 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 7, + "optional": true + }, + { + "name": "kind", + "type": 29, + "optional": false + }, + { + "name": "rpcId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 7, + "optional": false + }, + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "ruleId", + "type": 7, + "optional": false + }, + { + "name": "source", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 48, + "optional": false + }, + { + "name": "form", + "type": 50, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "update", + "type": 32, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 49 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 55, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + }, + { + "name": "sections", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "plugin" + }, + { + "kind": "array", + "element": 58 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 64, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 50, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 62, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + }, + { + "name": "references", + "type": 68, + "optional": false + }, + { + "name": "version", + "type": 72, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 69 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 11, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 70, + "optional": false + }, + { + "name": "compacted", + "type": 23, + "optional": false + }, + { + "name": "inputIndex", + "type": 11, + "optional": false + }, + { + "name": "label", + "type": 7, + "optional": false + }, + { + "name": "omittedBytes", + "type": 11, + "optional": false + }, + { + "name": "omittedMessages", + "type": 11, + "optional": false + }, + { + "name": "originalMessages", + "type": 11, + "optional": false + }, + { + "name": "retainedMessages", + "type": 11, + "optional": false + }, + { + "name": "sessionId", + "type": 7, + "optional": false + }, + { + "name": "truncated", + "type": 23, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 11, + 71 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 45, + "optional": false + }, + { + "name": "kind", + "type": 74, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 64, + "optional": false + }, + { + "name": "kind", + "type": 76, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "revision", + "type": 11, + "optional": false + }, + { + "name": "round", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "messageId", + "type": 7, + "optional": false + }, + { + "name": "senderId", + "type": 7, + "optional": false + }, + { + "name": "senderName", + "type": 7, + "optional": false + }, + { + "name": "teamId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "replayState", + "type": 83, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "plugin", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "canceled" + }, + { + "kind": "union", + "types": [ + 88, + 89 + ] + }, + { + "kind": "literal", + "value": "next-step" + }, + { + "kind": "literal", + "value": "next-turn" + }, + { + "kind": "literal", + "value": "agent/inbox/spliced" + } + ] + }, + "names": [ + "event:agent/inbox/spliced" + ], + "sources": [] + }, + { + "digest": "f789af5ab844827eb5a4f357c0afae1eeed108c6c22d8ca1a56e586b349a7c85", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "change" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "f88f1b7b6bb733f685f5fb6a8744a5db9199fa53c90463ea1a33bad51153671d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": 1 + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "f90eb4ab2d3897bc20c521a038e3119d581f791ec974b1a0004f63a8ab9e232d", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "turn", + "type": 1, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "event:turn/start.data" + ], + "sources": [ + "packages/core/session/src/types.ts:276" + ] + }, + { + "digest": "f9cfc89e8fe085b4fcc9cd32b74b6f103608c54d7d8cd97b47c01de34f252ba3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "text-chunks" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "fab2bb09bc5e3b9db0685e8f5500b029af1a2b33e70bc703c1d5d0f596fbc1fd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3 + ] + }, + { + "kind": "literal", + "value": "danger-full-access" + }, + { + "kind": "literal", + "value": "read-only" + }, + { + "kind": "literal", + "value": "workspace-write" + } + ] + }, + "names": [ + "packages/sandbox/sandbox/src/index.ts#SandboxMode" + ], + "sources": [ + "packages/sandbox/sandbox/src/index.ts:29" + ] + }, + { + "digest": "fab35c4520883c0d69e42d079c9f543fc5ac7a2d15b537b5572fcd99059c7018", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "rejected" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "fb5e0517050d2c6d40e5b8145db1f7c5ca7044d5d50fada6ff80e0b22c6ca973", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool/ptc-dispatch" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "fb8b6a2e53cb9cf44c551176de0fb77d1ddc60037b99a891ff8a860b6d5c29a0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "image" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "fbd2c74b346a1595b376139c587fadd23e4a21acc9c0abce342975b6cfc60986", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "agent-instructions" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "fc2ae6962f9f8801c2b1e414668e1e17cbb7544c4f39381198977a38bd10c093", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 1, + "optional": false + }, + { + "name": "form", + "type": 4, + "optional": false + }, + { + "name": "kind", + "type": 5, + "optional": false + }, + { + "name": "update", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 3, + "optional": false + }, + { + "name": "name", + "type": 3, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "literal", + "value": true + } + ] + }, + "names": [ + "packages/skill/tool-skill/src/index.ts#SkillCatalogSource" + ], + "sources": [ + "packages/skill/tool-skill/src/index.ts:34" + ] + }, + { + "digest": "fd0d91a1f9dd2efb8959bdf5e07f8f0a98b1cdfa38ce8d4732ebde0b44c3abd4", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "union", + "types": [ + 1, + 2, + 3 + ] + }, + { + "kind": "literal", + "value": "completed" + }, + { + "kind": "literal", + "value": "in_progress" + }, + { + "kind": "literal", + "value": "pending" + } + ] + }, + "names": [ + "event:todo/write.data.todos[0].status" + ], + "sources": [] + }, + { + "digest": "fe792a6cd430494503837e21e79f91677704ccdd9e870c679aabd7d43ed47ddd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "tool-workflow/run-end" + } + ] + }, + "names": [], + "sources": [] + }, + { + "digest": "fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "cacheReadTokens", + "type": 1, + "optional": true + }, + { + "name": "cacheWriteTokens", + "type": 1, + "optional": true + }, + { + "name": "inputTokens", + "type": 1, + "optional": false + }, + { + "name": "outputTokens", + "type": 1, + "optional": false + }, + { + "name": "reasoningTokens", + "type": 1, + "optional": true + }, + { + "name": "totalTokens", + "type": 1, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + } + ] + }, + "names": [ + "packages/llm/llm/src/types.ts#TokenUsage" + ], + "sources": [ + "packages/llm/llm/src/types.ts:149" + ] + }, + { + "digest": "fee1caf8ab6a65c1bc981f5a1730164490f22a2a89ffa3d0b76f1f432c44c8b5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "literal", + "value": "instructions" + } + ] + }, + "names": [], + "sources": [] + } + ] +} diff --git a/docs/subsystems/llm-streaming.i18n.yaml b/docs/subsystems/llm-streaming.i18n.yaml index d543a5f4e7..26ddf5d641 100644 --- a/docs/subsystems/llm-streaming.i18n.yaml +++ b/docs/subsystems/llm-streaming.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/llm-streaming.md -llm-streaming.md: 28492b46435e3288609129b0dfc5cb40dd5f7296 -llm-streaming.zh.md: fe02e2ebe5b558162e849e26ff05b8d633c07ba0 +llm-streaming.md: e42d5f0c4a4e5273926ca8a58b3f060637da43dc +llm-streaming.zh.md: 727b327e31421477891955383bebbbe1a9e1b5fb diff --git a/docs/subsystems/llm-streaming.md b/docs/subsystems/llm-streaming.md index 28492b4643..e42d5f0c4a 100644 --- a/docs/subsystems/llm-streaming.md +++ b/docs/subsystems/llm-streaming.md @@ -49,7 +49,7 @@ A `Message` is one identified, immutable role/source/content value. Model-produc ```ts type-equiv /** Provider/model identity and adapter-private replay data for an assistant message. */ -interface AssistantProvenance { +interface AssistantProviderMetadata { /** Provider route that produced the message. */ provider: string /** Provider model id that produced the message. */ @@ -97,7 +97,7 @@ Producer identity and presentation form are independent. `kind` answers *who pro ```ts type-equiv /** * The kind of information in producer-supplied context, declared by the - * producer beside its provenance. + * producer in the same `MessageSource`. * * `MessageSource.kind` answers *who produced this*; `form` answers *what kind * of thing it is*, and the two axes are deliberately independent — several diff --git a/docs/subsystems/llm-streaming.zh.md b/docs/subsystems/llm-streaming.zh.md index fe02e2ebe5..727b327e31 100644 --- a/docs/subsystems/llm-streaming.zh.md +++ b/docs/subsystems/llm-streaming.zh.md @@ -49,7 +49,7 @@ interface ImageAttachmentAccess { ```ts type-equiv /** Provider/model identity and adapter-private replay data for an assistant message. */ -interface AssistantProvenance { +interface AssistantProviderMetadata { /** Provider route that produced the message. */ provider: string /** Provider model id that produced the message. */ @@ -97,7 +97,7 @@ interface MessageSourceMap { ```ts type-equiv /** * The kind of information in producer-supplied context, declared by the - * producer beside its provenance. + * producer in the same `MessageSource`. * * `MessageSource.kind` answers *who produced this*; `form` answers *what kind * of thing it is*, and the two axes are deliberately independent — several diff --git a/docs/subsystems/schedule.i18n.yaml b/docs/subsystems/schedule.i18n.yaml index 3e13e79724..58c4a9363b 100644 --- a/docs/subsystems/schedule.i18n.yaml +++ b/docs/subsystems/schedule.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/schedule.md -schedule.md: 82d221260ec4ca1411ab981801d89226753703b2 -schedule.zh.md: f60edcc53d610aa29afed36fce10b100c401e27c +schedule.md: b92b81c6f24773b8cbafea5fccd7ac16df07ccd3 +schedule.zh.md: a3e6a4e5f82223526f0ec84e60e5c752e6f65f9a diff --git a/docs/subsystems/schedule.md b/docs/subsystems/schedule.md index 82d221260e..b92b81c6f2 100644 --- a/docs/subsystems/schedule.md +++ b/docs/subsystems/schedule.md @@ -85,7 +85,7 @@ interface LocalAtInput { type AtInput = string | LocalAtInput ``` -The official Web overlay samples the browser's IANA zone for every prompt. Time-context tells the model to interpret otherwise-unqualified natural-language dates and times in that request-local zone when the open turn has one unambiguous browser zone; mixed or missing provenance tells the model to ask. That guidance is not a durable Session default: the model must still pass an offset in the string form or `time_zone` in the local form, and Schedule never reads browser, Session, process, or model context. +The official Web overlay samples the browser's IANA zone for every prompt. Time-context tells the model to interpret otherwise-unqualified natural-language dates and times in that request-local zone when the open turn has one unambiguous browser zone; mixed or missing browser-zone records tell the model to ask. That guidance is not a durable Session default: the model must still pass an offset in the string form or `time_zone` in the local form, and Schedule never reads browser, Session, process, or model context. Schedule rejects invalid offsets and zones, offset-free strings, non-future targets, and local times inside daylight-saving gaps. A daylight-saving overlap chooses its first, earlier instant. Successful creation stores only canonical UTC `scheduledAt`, so replay never depends on ambient time-zone state. diff --git a/docs/subsystems/schedule.zh.md b/docs/subsystems/schedule.zh.md index f60edcc53d..a3e6a4e5f8 100644 --- a/docs/subsystems/schedule.zh.md +++ b/docs/subsystems/schedule.zh.md @@ -85,7 +85,7 @@ interface LocalAtInput { type AtInput = string | LocalAtInput ``` -官方 Web overlay 会为每条提示词采样浏览器的 IANA 时区。当 open turn 只有一个无歧义的浏览器时区时,Time-context 会告诉模型按该请求本地时区解释未明确限定时区的自然语言日期和时间;provenance 混合或缺失时,则告诉模型询问用户。该指引不是持久 Session 默认值:模型仍必须在字符串形式中传入偏移量,或在本地形式中传入 `time_zone`;Schedule 绝不会读取浏览器、Session、进程或模型上下文。 +官方 Web overlay 会为每条提示词采样浏览器的 IANA 时区。当 open turn 只有一个无歧义的浏览器时区时,Time-context 会告诉模型按该请求本地时区解释未明确限定时区的自然语言日期和时间;浏览器时区记录混合或缺失时,则告诉模型询问用户。该指引不是持久 Session 默认值:模型仍必须在字符串形式中传入偏移量,或在本地形式中传入 `time_zone`;Schedule 绝不会读取浏览器、Session、进程或模型上下文。 Schedule 会拒绝无效偏移量与时区、不带偏移量的字符串、非未来目标,以及落在夏令时缺口内的本地时间。遇到夏令时重叠时,会选择第一次出现的较早时点。创建成功后只存储规范化后的 UTC `scheduledAt`,因此回放绝不依赖环境时区状态。 diff --git a/docs/subsystems/session-title.i18n.yaml b/docs/subsystems/session-title.i18n.yaml index 63292c20f0..623eaac62f 100644 --- a/docs/subsystems/session-title.i18n.yaml +++ b/docs/subsystems/session-title.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/session-title.md -session-title.md: 20ed32b4a08127e7193433673d22f0dea3a90c46 -session-title.zh.md: c01d5d4bf177169069bbe0a39ef533ada0f7c36a +session-title.md: 75e17f06d73e6d51db51961327f8f34f0d5e5dd5 +session-title.zh.md: 187dad8f3a8cdaef14b450635f1f308fb2b429be diff --git a/docs/subsystems/session-title.md b/docs/subsystems/session-title.md index 20ed32b4a0..75e17f06d7 100644 --- a/docs/subsystems/session-title.md +++ b/docs/subsystems/session-title.md @@ -17,7 +17,7 @@ type SessionTitleProviderId = Branded<'SessionTitleProviderId'> ```ts type-equiv /** Exact auxiliary model route that produced a title. */ -interface SessionTitleModelProvenance { +interface SessionTitleModelIdentity { /** Registered LLM provider route. */ readonly provider: string /** Provider model id. */ @@ -32,7 +32,7 @@ type SessionTitleSource = | { readonly kind: 'provider' readonly provider: SessionTitleProviderId - readonly model?: SessionTitleModelProvenance + readonly model?: SessionTitleModelIdentity } | { /** Explicit user rename: pins the title — automatic generation stops scheduling. */ @@ -74,7 +74,7 @@ interface SessionTitleLlmRequestEventData { /** Exact human `user/message` seqs represented in `messages`. */ readonly messageSeqs: SessionSeq[] /** Exact auxiliary LLM route. */ - readonly route: SessionTitleModelProvenance + readonly route: SessionTitleModelIdentity /** Exact auxiliary system prompt. */ readonly system: string /** Exact auxiliary message list. */ @@ -111,7 +111,7 @@ interface SessionTitleProviderRequest { /** All eligible human messages through this generation revision. */ readonly messages: readonly SessionTitleUserMessage[] /** Exact current logged main-request route, when one has been recorded. */ - readonly route?: SessionTitleModelProvenance + readonly route?: SessionTitleModelIdentity /** Cancellation for supersession, disposal, timeout composition, or the explicit caller. */ readonly signal: AbortSignal } @@ -125,7 +125,7 @@ interface SessionTitleProviderResult { /** Exact seqs from `request.messages` used by this result. */ readonly messageSeqs: readonly SessionSeq[] /** Auxiliary LLM route, when generation used a model. */ - readonly model?: SessionTitleModelProvenance + readonly model?: SessionTitleModelIdentity } ``` diff --git a/docs/subsystems/session-title.zh.md b/docs/subsystems/session-title.zh.md index c01d5d4bf1..187dad8f3a 100644 --- a/docs/subsystems/session-title.zh.md +++ b/docs/subsystems/session-title.zh.md @@ -17,7 +17,7 @@ type SessionTitleProviderId = Branded<'SessionTitleProviderId'> ```ts type-equiv /** Exact auxiliary model route that produced a title. */ -interface SessionTitleModelProvenance { +interface SessionTitleModelIdentity { /** Registered LLM provider route. */ readonly provider: string /** Provider model id. */ @@ -32,7 +32,7 @@ type SessionTitleSource = | { readonly kind: 'provider' readonly provider: SessionTitleProviderId - readonly model?: SessionTitleModelProvenance + readonly model?: SessionTitleModelIdentity } | { /** Explicit user rename: pins the title — automatic generation stops scheduling. */ @@ -74,7 +74,7 @@ interface SessionTitleLlmRequestEventData { /** Exact human `user/message` seqs represented in `messages`. */ readonly messageSeqs: SessionSeq[] /** Exact auxiliary LLM route. */ - readonly route: SessionTitleModelProvenance + readonly route: SessionTitleModelIdentity /** Exact auxiliary system prompt. */ readonly system: string /** Exact auxiliary message list. */ @@ -111,7 +111,7 @@ interface SessionTitleProviderRequest { /** All eligible human messages through this generation revision. */ readonly messages: readonly SessionTitleUserMessage[] /** Exact current logged main-request route, when one has been recorded. */ - readonly route?: SessionTitleModelProvenance + readonly route?: SessionTitleModelIdentity /** Cancellation for supersession, disposal, timeout composition, or the explicit caller. */ readonly signal: AbortSignal } @@ -125,7 +125,7 @@ interface SessionTitleProviderResult { /** Exact seqs from `request.messages` used by this result. */ readonly messageSeqs: readonly SessionSeq[] /** Auxiliary LLM route, when generation used a model. */ - readonly model?: SessionTitleModelProvenance + readonly model?: SessionTitleModelIdentity } ``` diff --git a/docs/subsystems/session.i18n.yaml b/docs/subsystems/session.i18n.yaml index 0efbd227eb..4e7687c92d 100644 --- a/docs/subsystems/session.i18n.yaml +++ b/docs/subsystems/session.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/session.md -session.md: 995e0c1ca892a27ca660bdf169b7affae85dcfcf -session.zh.md: 17f0daedb4758247e85d4655c2802fdbda573ba6 +session.md: 4259988f23b7c64708b78dd62b69351c85d8e21b +session.zh.md: 9540766176262f06f25b8a99e460ad14e839ca80 diff --git a/docs/subsystems/session.md b/docs/subsystems/session.md index 995e0c1ca8..4259988f23 100644 --- a/docs/subsystems/session.md +++ b/docs/subsystems/session.md @@ -277,7 +277,7 @@ type SessionEvent = { `SessionEventType = keyof SessionEventMap`. Because `SessionEventMap` is merge-extensible, switches over `SessionEvent` must NOT use `assertNever` — a plugin-added variant is a valid unknown value; handle the known cases and fall through `default`. -Every surface event requires `surfaceOp`; known log-only events forbid both surface metadata fields. Native unknown or obsolete ignorable envelopes remain opaque. `assistant/message` embeds its provider stream and forbids `sourceEventSeqs`. System, user, and tool surface events may cite a complete non-empty set of unique earlier events when their provenance or replacement operation requires it. A `tool/result` may carry `data.error` only when its tool-result block has `isError: true`; failure identity remains optional for failed results. +Every surface event requires `surfaceOp`; known log-only events forbid both surface metadata fields. Native unknown or obsolete ignorable envelopes remain opaque. `assistant/message` embeds its provider stream and forbids `sourceEventSeqs`. System, user, and tool surface events may cite a complete non-empty set of unique earlier events when source attribution or replacement coverage requires it. A `tool/result` may carry `data.error` only when its tool-result block has `isError: true`; failure identity remains optional for failed results. ## Surface types diff --git a/docs/subsystems/session.zh.md b/docs/subsystems/session.zh.md index 17f0daedb4..9540766176 100644 --- a/docs/subsystems/session.zh.md +++ b/docs/subsystems/session.zh.md @@ -277,7 +277,7 @@ type SessionEvent = { `SessionEventType = keyof SessionEventMap`。由于 `SessionEventMap` 可通过合并扩展,对 `SessionEvent` 的 switch 语句禁止使用 `assertNever`:插件添加的变体是合法的未知值;处理已知 case 后在 `default` 中放行。 -每个 surface 事件都要求 `surfaceOp`;已知仅日志事件禁止两个 surface 元数据字段。原生未知或已退役的可忽略信封保持不透明。`assistant/message` 嵌入其提供方 stream,并禁止 `sourceEventSeqs`。System、user 与 tool surface 事件可以在来源或替换操作需要时引用完整、非空且唯一的较早事件集合。`tool/result` 仅在工具结果块带有 `isError: true` 时可以携带 `data.error`;失败结果的失败身份仍可省略。 +每个 surface 事件都要求 `surfaceOp`;已知仅日志事件禁止两个 surface 元数据字段。原生未知或已退役的可忽略信封保持不透明。`assistant/message` 嵌入其提供方 stream,并禁止 `sourceEventSeqs`。System、user 与 tool surface 事件可以在来源归属或替换覆盖需要时引用完整、非空且唯一的较早事件集合。`tool/result` 仅在工具结果块带有 `isError: true` 时可以携带 `data.error`;失败结果的失败身份仍可省略。 diff --git a/docs/subsystems/spill.i18n.yaml b/docs/subsystems/spill.i18n.yaml index ac302150e2..644a193f9c 100644 --- a/docs/subsystems/spill.i18n.yaml +++ b/docs/subsystems/spill.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/spill.md -spill.md: 05e48361ae1bdd815ee88ea5233ccbbac23ddf7d -spill.zh.md: 39e49f17564644c6a554440a80b46b79d837c79f +spill.md: 577f3ff14bade2b0c03a0aeaa975d12ae96803b8 +spill.zh.md: 1bc238b8cdc0ef81e966bc5ec9c11b2dd31b24da diff --git a/docs/subsystems/spill.md b/docs/subsystems/spill.md index 05e48361ae..577f3ff14b 100644 --- a/docs/subsystems/spill.md +++ b/docs/subsystems/spill.md @@ -8,7 +8,7 @@ Source: [`packages/spill/spill/src/types.ts`](../../packages/spill/spill/src/typ ## The save request -`saveText` is the sole service operation: persist `content` verbatim, return an opaque locator, a backend-supplied retrieval hint, and the exact byte count. The request carries the save-time storage namespace (`owner`), descriptive producer provenance (`source`, never access control), and a `suggestedName` the backend may use as a naming hint, not a path. Tool provenance identifies the actual tool call; session-reference provenance identifies the captured source session, while its owner is the target session receiving the context. +`saveText` is the sole service operation: persist `content` verbatim, return an opaque locator, a backend-supplied retrieval hint, and the exact byte count. The request carries the save-time storage namespace (`owner`), descriptive producer details (`source`, never access control), and a `suggestedName` the backend may use as a naming hint, not a path. A tool source identifies the actual tool call; a session-reference source identifies the captured source session, while its owner is the target session receiving the context. ```ts type-equiv /** One request to persist text to a spill artifact. */ @@ -44,7 +44,7 @@ A retention-period cleanup may expire old locators with other old session artifa /** * Producer of a spilled artifact. Tool results carry their model-issued call id; * session references identify the captured source session instead. Descriptive - * provenance only, never access control. + * source description only, never access control. */ type SpillSource = { kind: 'tool' diff --git a/docs/subsystems/spill.zh.md b/docs/subsystems/spill.zh.md index 39e49f1756..1bc238b8cd 100644 --- a/docs/subsystems/spill.zh.md +++ b/docs/subsystems/spill.zh.md @@ -44,7 +44,7 @@ interface SpillOwner { /** * Producer of a spilled artifact. Tool results carry their model-issued call id; * session references identify the captured source session instead. Descriptive - * provenance only, never access control. + * source description only, never access control. */ type SpillSource = { kind: 'tool' diff --git a/docs/subsystems/subagent.i18n.yaml b/docs/subsystems/subagent.i18n.yaml index 8327748081..5ff51cac7f 100644 --- a/docs/subsystems/subagent.i18n.yaml +++ b/docs/subsystems/subagent.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/subagent.md -subagent.md: 2a9b5277ee33180b8b7da48999c1b075aed4c2ff +subagent.md: ed47512cc62653a8e6abf690b87798077650fcb2 subagent.zh.md: 8b9b7bed96e5252805027d8c0f4e4f0e80966914 diff --git a/docs/subsystems/subagent.md b/docs/subsystems/subagent.md index 2a9b5277ee..ed47512cc6 100644 --- a/docs/subsystems/subagent.md +++ b/docs/subsystems/subagent.md @@ -147,7 +147,7 @@ persisted Session The Agent inbox is the only queue. Every Agent message uses `Agent.steer()`: an idle target starts a turn, while a running target claims it at the nearest step boundary. The browser `subagent.prompt` Remote separately carries `delivery: 'queue' | 'steer'` through the same internal admission path; Queue opens a later FIFO turn, while Steer retains the Agent loop's best-effort nearest-step behavior and the message's human source. Successful delivery returns the accepted `MessageId`; the existing `agent/inbox/inserted`, `agent/inbox/claimed`, and `agent/inbox/discarded` events remain the message-lifecycle observations, and the continuation layer defines no second queue. -Authority comes from the exact live sender. Parent-to-child delivery requires the target's `SessionHeader.parentSession` to name the sender; child-to-parent delivery requires the sender's resident Activation to name the target. Siblings, ancestors beyond one edge, self-targets, stale Agent objects, and one-shot children are rejected. Each accepted message is framed as `Agent sent a message:` and records `AgentMessageSource`; provenance records the sender but grants no authority. +Authority comes from the exact live sender. Parent-to-child delivery requires the target's `SessionHeader.parentSession` to name the sender; child-to-parent delivery requires the sender's resident Activation to name the target. Siblings, ancestors beyond one edge, self-targets, stale Agent objects, and one-shot children are rejected. Each accepted message is framed as `Agent sent a message:` and records `AgentMessageSource`; the source records the sender but grants no authority. For `startContinuable()`, `sendMessage()`, and browser prompt delivery, the caller signal owns lookup, materialization, and admission only until inbox acceptance. Afterwards the manager owns the Activation independently: later caller cancellation neither cancels the accepted turn nor disposes the child. The public subagent service exposes no caller-selected Agent-message scheduling; browser human Queue and Steer remain internal adapter choices. @@ -199,7 +199,7 @@ interface ContinuableStart { } ``` -When a resident Activation settles, the manager delivers one notice to the child's durable direct parent describing how that epoch ended and carrying its final assistant content. That delivery is unconditional for every child whose id a caller received, happens before the ownership release that would let the parent be judged settled, and reaches a resident parent through the same waking Agent delivery as an Agent message. A parent whose own lineage is already tearing down receives it without a wake, because waking an idle Agent starts a turn rather than queueing work. Its provenance is a distinct kind so a transcript never presents a runtime account as something the child wrote. +When a resident Activation settles, the manager delivers one notice to the child's durable direct parent describing how that epoch ended and carrying its final assistant content. That delivery is unconditional for every child whose id a caller received, happens before the ownership release that would let the parent be judged settled, and reaches a resident parent through the same waking Agent delivery as an Agent message. A parent whose own lineage is already tearing down receives it without a wake, because waking an idle Agent starts a turn rather than queueing work. Its source has a distinct kind so a transcript never presents a runtime account as something the child wrote. ```ts type-equiv /** diff --git a/docs/subsystems/subprocess.i18n.yaml b/docs/subsystems/subprocess.i18n.yaml index 90672cfb7f..78c418c639 100644 --- a/docs/subsystems/subprocess.i18n.yaml +++ b/docs/subsystems/subprocess.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/subprocess.md -subprocess.md: 782623760e70648a108ac51252b610e27bffbe45 -subprocess.zh.md: 7b8e590a843899b5f00a309a913e1dcb152c5c1b +subprocess.md: b62a9c19cfdf9b557ce21cf5bcc9e7e1a01a580f +subprocess.zh.md: d9206833c14f39ed8e8bb9687b8d06c1b51b42f7 diff --git a/docs/subsystems/subprocess.md b/docs/subsystems/subprocess.md index 782623760e..b62a9c19cf 100644 --- a/docs/subsystems/subprocess.md +++ b/docs/subsystems/subprocess.md @@ -254,23 +254,6 @@ The abstract [`SubprocessRuntime`](../../packages/subprocess/subprocess/src/inde Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). - - -### `ctx.e2b` — `E2BRuntime` - -Creates one lazily consumable E2B SDK handle and deletes the sandbox at timeout or disposal. Creation begins at plugin construction; adapters await getSandbox before their first operation. - -```ts cordis-catalog -/** - * Return the shared live SDK handle. - * @returns the created sandbox after the configured cwd exists. - * @throws when E2B rejects creation or the service is disposing. - */ -async getSandbox(): Promise -``` - -Source: [`packages/e2b/e2b/src/index.ts`](../../packages/e2b/e2b/src/index.ts) - ### `ctx.subprocess` — `SubprocessRuntime` (abstract seam) diff --git a/docs/subsystems/subprocess.zh.md b/docs/subsystems/subprocess.zh.md index 7b8e590a84..d9206833c1 100644 --- a/docs/subsystems/subprocess.zh.md +++ b/docs/subsystems/subprocess.zh.md @@ -254,23 +254,6 @@ interface SubprocessOutcome { Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). - - -### `ctx.e2b` — `E2BRuntime` - -Creates one lazily consumable E2B SDK handle and deletes the sandbox at timeout or disposal. Creation begins at plugin construction; adapters await getSandbox before their first operation. - -```ts cordis-catalog -/** - * Return the shared live SDK handle. - * @returns the created sandbox after the configured cwd exists. - * @throws when E2B rejects creation or the service is disposing. - */ -async getSandbox(): Promise -``` - -Source: [`packages/e2b/e2b/src/index.ts`](../../packages/e2b/e2b/src/index.ts) - ### `ctx.subprocess` — `SubprocessRuntime` (abstract seam) diff --git a/docs/subsystems/webhook.i18n.yaml b/docs/subsystems/webhook.i18n.yaml index c27aa424bb..4d91c674b9 100644 --- a/docs/subsystems/webhook.i18n.yaml +++ b/docs/subsystems/webhook.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/webhook.md -webhook.md: 5f04c99141c5630add46db3b55c3af78e2df2bdf +webhook.md: 5ca2b722e1e0201aa5fb618cdaae6e0dfb5783dd webhook.zh.md: 2eadf03aa5f32ae6f384e6b32a95e67bc0fdab7e diff --git a/docs/subsystems/webhook.md b/docs/subsystems/webhook.md index 5f04c99141..5ca2b722e1 100644 --- a/docs/subsystems/webhook.md +++ b/docs/subsystems/webhook.md @@ -6,7 +6,7 @@ The Webhook subsystem turns authenticated external deliveries into optional ordi ## Shared values -`WebhookRuleId`, `WebhookSourceId`, and `WebhookDeliveryId` are opaque strings. A delivery id is provenance only: the runtime neither stores nor deduplicates it. +`WebhookRuleId`, `WebhookSourceId`, and `WebhookDeliveryId` are opaque strings. A delivery id records the provider's identifier only: the runtime neither stores nor deduplicates it. `WebhookEventMap` is merge-extensible by provider kind. `WebhookEventOf` selects a known provider event and otherwise admits generic lossless JSON, allowing an out-of-tree adapter without changing the runtime package. @@ -26,7 +26,7 @@ The runtime has no queue, retry, deduplication, execution status, crash replay, A non-null result is snapshotted before asynchronous preflight. The runtime validates permission and agent presets, resolves or creates the canonical Workspace, creates an Agent whose Session cwd equals the Workspace path, mounts the selected agent preset before publication, and durably attaches the Session before applying permission, title, and the initial follow-up. -The follow-up is a normal durable user-role message with `source.kind: "webhook"` and provider/source/delivery/rule provenance. Its accepted inbox insertion commits the webhook operation. The runtime does not specially flush or wait for the turn; ordinary Session persistence and Agent lifecycle apply afterward. +The follow-up is a normal durable user-role message with `source.kind: "webhook"` and the provider, source, delivery, and rule identifiers. Its accepted inbox insertion commits the webhook operation. The runtime does not specially flush or wait for the turn; ordinary Session persistence and Agent lifecycle apply afterward. Failed attachment disposes the new Agent before a prompt exists. A failure between attachment and prompt admission attempts Workspace detach and Agent disposal without replacing the original error. A Workspace automatically created during preflight remains because another concurrent caller may already use it. diff --git a/package.json b/package.json index 01e7b245e0..5f3aba5596 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "verify-md-links": "tsx scripts/verify-md-links.ts", "verify-doc-site-fragments": "tsx scripts/verify-doc-site-fragments.ts", "verify-public-repository-links": "tsx scripts/verify-public-repository-links.ts", + "verify-concrete-terms": "tsx scripts/verify-concrete-terms.ts", "verify-doc-refs": "tsx scripts/verify-doc-refs.ts", "verify-subsystem-pages": "tsx scripts/verify-subsystem-pages.ts", "verify-package-paths": "tsx scripts/verify-package-paths.ts", @@ -157,6 +158,8 @@ "verify-doc-graphs": "tsx scripts/gen-doc-graphs.ts --check", "gen-persistence-catalog": "tsx scripts/gen-persistence-catalog.ts", "verify-persistence-catalog": "tsx scripts/gen-persistence-catalog.ts --check", + "persistence-changes": "tsx scripts/persistence-changes.ts", + "verify-persistence-changes": "tsx scripts/persistence-changes.ts --check", "gen-session-format-catalog": "tsx scripts/gen-session-format-catalog.ts", "verify-session-format-catalog": "tsx scripts/gen-session-format-catalog.ts --check", "gen-third-party-notices": "tsx scripts/gen-third-party-notices.ts", diff --git a/packages/README.i18n.yaml b/packages/README.i18n.yaml index f4b9b5b2e6..121297cfb5 100644 --- a/packages/README.i18n.yaml +++ b/packages/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/README.md -README.md: 9c298d3f2426bab92a6d602495d836ef8ee9fb44 -README.zh.md: db8977a279713ac7a2f86144323a54125743a46a +README.md: 22e440b98f8440548c02d48ca3d56890718eafb6 +README.zh.md: 60f033cf01124187c123692883635b64d2bc9754 diff --git a/packages/README.md b/packages/README.md index 9c298d3f24..22e440b98f 100644 --- a/packages/README.md +++ b/packages/README.md @@ -36,7 +36,6 @@ Every package lives in exactly one group; new packages join existing groups, and | [`feedback/`](feedback/README.md) | Human feedback capture and command | | [`identity/`](identity/README.md) | Shared anonymous identity | | [`llm/`](llm/README.md) | LLM capability family: abstract service + provider adapters | -| [`e2b/`](e2b/README.md) | E2B remote-runtime providers | | [`subprocess/`](subprocess/README.md) | Subprocess capability family: Service Definition + local process-tree provider | | [`shell/`](shell/README.md) | Bash capability family: executor seam, local impl, model-facing tools | | [`terminal/`](terminal/README.md) | Persistent PTY capability family: owner-scoped sessions, local implementation, model-facing tools | @@ -83,7 +82,7 @@ Every package lives in exactly one group; new packages join existing groups, and ## Release expectations -Most groups are product — stable API. The exceptions: `e2b/` is a POC, `experimental/` is unreleased, and `test-support/`, `runtime-diagnostics/`, and `util/` are support with lower compatibility expectations. +Most groups are product — stable API. The exceptions: `experimental/` is unreleased, and `test-support/`, `runtime-diagnostics/`, and `util/` are support with lower compatibility expectations. ----- diff --git a/packages/README.zh.md b/packages/README.zh.md index db8977a279..60f033cf01 100644 --- a/packages/README.zh.md +++ b/packages/README.zh.md @@ -36,7 +36,6 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 | [`feedback/`](feedback/README.zh.md) | 人类反馈的采集与命令 | | [`identity/`](identity/README.zh.md) | 共享匿名身份 | | [`llm/`](llm/README.zh.md) | LLM(大语言模型)能力系列:抽象服务 + 提供方适配器 | -| [`e2b/`](e2b/README.zh.md) | E2B 远程运行时提供方 | | [`subprocess/`](subprocess/README.zh.md) | 子进程能力系列:Service Definition + 本地进程树提供方 | | [`shell/`](shell/README.zh.md) | Bash 能力系列:执行器 seam、本地实现、面向模型的工具 | | [`terminal/`](terminal/README.zh.md) | 持久 PTY 能力系列:限定所有者范围的会话、本地实现、面向模型的工具 | @@ -83,7 +82,7 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 ## 发布预期 -大多数组属于产品组,提供稳定 API。例外:`e2b/` 是 POC,`experimental/` 不发布,`test-support/`、`runtime-diagnostics/` 与 `util/` 是兼容性预期较低的支持组。 +大多数组属于产品组,提供稳定 API。例外:`experimental/` 不发布,`test-support/`、`runtime-diagnostics/` 与 `util/` 是兼容性预期较低的支持组。 ----- diff --git a/packages/api/session-controller/README.i18n.yaml b/packages/api/session-controller/README.i18n.yaml index 3620f28368..2e81443d71 100644 --- a/packages/api/session-controller/README.i18n.yaml +++ b/packages/api/session-controller/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/api/session-controller/README.md -README.md: 768c8704775d422c9c48d7d40a58d589f715b718 -README.zh.md: 3c1de73d13678b15af233196894b51fffa952f67 +README.md: 9b2cdeed600cd597ecfab076fe98a3a334533ba8 +README.zh.md: 04ed7808dc7ea581b489921f88349479d46d8037 diff --git a/packages/api/session-controller/README.md b/packages/api/session-controller/README.md index 768c870477..9b2cdeed60 100644 --- a/packages/api/session-controller/README.md +++ b/packages/api/session-controller/README.md @@ -26,7 +26,7 @@ English | [中文](README.zh.md) History pages and follow opening snapshots carry one `{ type: 'event', event: SessionWireEvent }` record per durable Session event. The Client retains each accepted record as one durable `SessionEventLikeEntry`; Assistant token boundaries remain inside the compact stream on `assistant/message` or `assistant/attempt`. Tool arguments, result content, failures, and `tool/result.data.meta` pass through unchanged; the controller does not resolve a Tool definition, run a presenter, or attach UI data. -The Client journal validates exact V3 event envelopes before publishing follow snapshots, live entries, or history pages. It reuses the browser-safe Session validators for required surface markers, exact replacement endpoints, earlier unique source seqs, embedded Assistant provenance, request-header omissions, and tool-error consistency. Invalid records fail without field stripping or normalization; range membership and source existence remain durable-log checks on the Host. +The Client journal validates exact V3 event envelopes before publishing follow snapshots, live entries, or history pages. It reuses the browser-safe Session validators for required surface markers, exact replacement endpoints, earlier unique source seqs, embedded Assistant provider metadata, request-header omissions, and tool-error consistency. Invalid records fail without field stripping or normalization; range membership and source existence remain durable-log checks on the Host. Each endpoint states its activation policy. List reads only stored headers and projection-cache rows: it never calls per-session stat or opens a cold Session body. A current-format cache identity may supply every list hint; a lifecycle-matching predecessor cache may supply only its version-compatible title as a stale display fact, never as an authoritative fold seed. Search, attachment, history pages, log following, skill discovery, and workspace-path opening can inspect persistence without activating an Agent; `canOpenWorkspacePath()` reports native-opening availability without addressing a Session. Queue mutation and cancellation require live state; model, rename, prompt, and file-reference operations may resolve or resume an ordinary Session. Prompt rejects content with neither non-whitespace text nor an attachment before resolving the Agent or appending Session events; queue edits accept only non-empty text content. Prompt admission consumes opaque receipts from the injected [`fileUploads`](../../client/file-upload/README.md) Host service and resolves every same-Agent receipt before sending the complete ordered content list through `ctx.attachments`. Prompt retries whose `requestId` is already queued or logged return the original acceptance without inserting another message. Create and fork are the only operations that create a new Agent directly. The service applies one preset-aware resume policy and subagent ownership fence to its own methods and to the Typert Agent and Session lookups used by other Remote namespaces. Queue mutation has one narrow exception: a live child whose current projected identity is continuable and comes from its own non-seed suffix accepts the ordinary Edit, Remove, and QueueDock Steer actions across both inbox destinations. One-shot, missing, unknown, corrupt, seed-only, or cold children remain rejected without resume. The skill catalog uses a live Agent when present or the recorded preset's standing scope when cold, so listing never starts an Agent. The authenticated delivery routes use `workspaceDesktop()` for the serving Host name and file-manager behavior. `openWorkspacePath({ path, action: "reveal" })` delegates file-manager navigation to the native adapter; omitting `action` opens the default application. @@ -37,6 +37,8 @@ The Session object also carries local submission echoes: `session.beginSubmissio The user-invocable `skills/list` metadata includes the winning provider’s optional instruction-file `path`. The composer can preview that file without loading every skill body or activating a cold Agent. +Fork copies history through the selected completed turn, including its `turn/end`. Events after that point, including queued input and model-setting changes, are excluded. An omitted or past-end anchor selects the last completed turn; an anchor inside an unfinished turn is rejected. + ## Session media references diff --git a/packages/api/session-controller/README.zh.md b/packages/api/session-controller/README.zh.md index 3c1de73d13..04ed7808dc 100644 --- a/packages/api/session-controller/README.zh.md +++ b/packages/api/session-controller/README.zh.md @@ -26,7 +26,7 @@ kind: "package-reference" 历史页与 follow opening 快照为每个持久 Session 事件携带一条 `{ type: 'event', event: SessionWireEvent }` record。Client 把每条已接受 record 保留为一个持久 `SessionEventLikeEntry`;Assistant token 边界保留在 `assistant/message` 或 `assistant/attempt` 的紧凑流内。工具参数、结果内容、失败信息和 `tool/result.data.meta` 原样通过;控制器不解析工具定义、不运行展示转换器,也不附加 UI 数据。 -Client journal 在发布 follow 快照、live entry 或历史页之前验证精确的 V3 事件 envelope。它复用浏览器安全的 Session validator,检查必需的 surface marker、精确的 replacement endpoint、更早且唯一的 source seq、内嵌 Assistant 来源、request header 可选字段的省略规则以及工具错误一致性。无效 record 直接失败,不删除字段或归一化;范围成员与来源存在性仍由 Host 的持久日志检查。 +Client journal 在发布 follow 快照、live entry 或历史页之前验证精确的 V3 事件 envelope。它复用浏览器安全的 Session validator,检查必需的 surface marker、精确的 replacement endpoint、更早且唯一的 source seq、内嵌 Assistant 提供方元数据、request header 可选字段的省略规则以及工具错误一致性。无效 record 直接失败,不删除字段或归一化;范围成员与来源存在性仍由 Host 的持久日志检查。 每个 endpoint 都声明自己的激活策略。列表只读取持久化 header 与 projection cache row,绝不调用逐 Session stat 或打开冷 Session body。当前格式 cache identity 可以提供全部列表 hint;生命周期匹配的 predecessor cache 只能提供版本兼容的 title,作为可能过时的展示事实,绝不能作为权威 fold seed。搜索、附件、历史页、日志跟随、skill 发现和工作区路径打开可以在不激活 Agent 的情况下检查 persistence;`canOpenWorkspacePath()` 无需指定 Session 即可报告原生打开能力。queue 变更与取消要求 live 状态;模型、重命名、prompt 和文件引用操作可以解析或恢复普通 Session。提示词会在解析 Agent 或追加 Session 事件前,拒绝既没有非空白文本也没有附件的 content;queue edit 只接受非空文本 content。prompt 准入从注入的 [`fileUploads`](../../client/file-upload/README.zh.md) Host 服务取得不透明凭证,在把完整有序内容列表交给 `ctx.attachments` 前解析每个属于同一 Agent 的凭证。`requestId` 已进入 queue 或日志时,prompt 重试直接返回原来的接受结果,不会重复插入消息。只有 create 与 fork 会直接创建新 Agent。该服务把同一套感知 preset 的恢复策略和 subagent ownership fence 同时用于自身方法,以及其他 Remote namespace 使用的 Typert Agent 与 Session lookup。Queue 变更只有一个狭窄例外:当前 projection identity 为 continuable 且来自自身非 seed suffix 的在线 child,可以在两个 inbox 目标上使用普通 Edit、Remove 与 QueueDock Steer action。One-shot、缺失、未知、损坏、仅含 seed identity 或冷 child 继续被拒绝,且不会恢复。skill 目录优先使用已有 live Agent,否则使用所记录 preset 的常驻 scope,因此列表查询绝不会启动 Agent。经过鉴权的文件交付路由通过 `workspaceDesktop()` 获取提供服务的 Host 名称和文件管理器行为。`openWorkspacePath({ path, action: "reveal" })` 将文件管理器导航委托给原生适配器;省略 `action` 时打开默认应用。 @@ -37,6 +37,8 @@ Session 对象还承载本地提交回显:`session.beginSubmission` 在调用 面向用户调用的 `skills/list` 元数据包含胜出提供方可选的指令文件 `path`。输入框可据此预览文件,无需加载每个 skill 的正文或激活冷态 Agent。 +分叉复制截至选中已结束轮次的历史,并包含其 `turn/end`。该位置之后的事件均被排除,包括排队输入和模型设置变更。省略锚点或锚点超出日志末尾时,选择最后一个已结束轮次;位于未结束轮次内的锚点会被拒绝。 + ## 会话媒体引用 diff --git a/packages/api/session-controller/src/client/time-zone.ts b/packages/api/session-controller/src/client/time-zone.ts index 9c2ddc4ea2..f4ba8207ff 100644 --- a/packages/api/session-controller/src/client/time-zone.ts +++ b/packages/api/session-controller/src/client/time-zone.ts @@ -1,4 +1,4 @@ -/** Browser-owned time-zone sampling for prompt RPC provenance. */ +/** Browser-owned time-zone sampling for one prompt RPC. */ /** * Resolve the current browser IANA zone for one outbound operation. diff --git a/packages/api/session-controller/src/commands.ts b/packages/api/session-controller/src/commands.ts index a564234664..f22f06fec9 100644 --- a/packages/api/session-controller/src/commands.ts +++ b/packages/api/session-controller/src/commands.ts @@ -240,10 +240,7 @@ export class SessionCommandController { { sessionId: request.sessionId }, ) } - let cut = SessionLogOffset(boundary.seq + 1) - while (cut < source.events.length && source.events[cut]?.type !== 'turn/start') { - cut = SessionLogOffset(cut + 1) - } + const cut = SessionLogOffset(boundary.seq + 1) let workspace: Workspace | undefined try { workspace = await this.forkWorkspace(source.header) diff --git a/packages/api/session-controller/tests/session-fork.host.spec.ts b/packages/api/session-controller/tests/session-fork.host.spec.ts index 1661f505bc..bc076f2868 100644 --- a/packages/api/session-controller/tests/session-fork.host.spec.ts +++ b/packages/api/session-controller/tests/session-fork.host.spec.ts @@ -4,6 +4,8 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import AgentRegistry, { agentEvents } from '@deepseek-ai/dsh-agent' import type { Agent, AgentHandle, CreateAgentOptions } from '@deepseek-ai/dsh-agent' +import { mountAgentLoopTestDependencies, mountAgentLoopTestHarness } from '@deepseek-ai/dsh-agent-loop-testkit' +import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' import { createUserMessage, ReasoningEffortId } from '@deepseek-ai/dsh-llm' import type { LlmCallConfig } from '@deepseek-ai/dsh-llm' import SessionStore, { SESSION_FORMAT_VERSION, SessionLogOffset, SessionSeq } from '@deepseek-ai/dsh-session' @@ -88,6 +90,53 @@ const remote = (ctx: Context) => createSessionTestRemote(ctx, { }) describe('sessions.fork', () => { + it.each(['message', 'turn-end', 'omitted', 'past-end'] as const)( + 'excludes the next user input when forking at %s', async (anchor) => { + const ctx = new Context() + try { + await mountAgentLoopTestDependencies(ctx) + const harness = await mountAgentLoopTestHarness(ctx) + const adapter = new MockAdapter(Array.from({ length: 4 }, () => textResponse('reply'))) + ctx.llm.registerAdapter(['mock'], adapter) + ctx.provide('workspaceRegistry', { list: () => [] } as never) + const source = await harness.create(sid('source'), { provider: 'mock', model: 'mock' }) + const message = (text: string) => createUserMessage({ + content: [{ type: 'text', text }], source: { kind: 'user' }, + }) + source.followup(message('A')) + await source.whenIdle() + const boundary = source.session.snapshotEvents().at(-1)!.seq + if (anchor === 'message' || anchor === 'turn-end') { + source.followup(message('B')) + await source.whenIdle() + } else { + source.inbox.append('next-turn', message('B')) + } + const original = source.session.snapshotEvents() + const atSeq = anchor === 'omitted' ? undefined + : anchor === 'past-end' ? source.session.seq + 1 + : anchor === 'turn-end' ? boundary + : original.find(event => event.type === 'assistant/message')!.seq + const response = await createSessionTestRemote(ctx, { + defaultModelSelection: () => ({ provider: 'mock', model: 'mock' }), cwd: '/tmp', + }).fork({ sessionId: source.id, ...(atSeq === undefined ? {} : { atSeq }) }) + if (!response.ok) throw response.error + const child = ctx.agents.get(response.value.sessionId)! + const requestCount = adapter.requests.length + child.followup(message('C')) + await child.whenIdle() + const userTexts = child.session.deriveMessages().flatMap(item => item.role === 'user' + ? item.content.flatMap(part => part.type === 'text' ? [part.text] : []) : []) + expect(userTexts).toEqual(['A', 'C']) + expect(adapter.requests.slice(requestCount)).toHaveLength(1) + expect(child.session.inheritedEventCount).toBe(boundary + 1) + expect(source.session.snapshotEvents()).toEqual(original) + } finally { + await ctx.fiber.dispose() + } + }, + ) + it('cuts at the anchored completed turn and records lineage and cwd', async () => { const ctx = await composed() const source = liveAgent(ctx, 'session-source', 2) @@ -268,9 +317,10 @@ describe('sessions.fork', () => { await ctx.fiber.dispose() }) - it('installs the latest logged model selection before the child can run', async () => { + it('inherits model selection through the completed turn and excludes later changes', async () => { const ctx = await composed() - const source = liveAgent(ctx, 'session-routed', 1) + const source = liveAgent(ctx, 'session-routed', 0) + source.append('turn/start', { turn: 1 }) source.append('request/header', { header: { config: { @@ -281,11 +331,23 @@ describe('sessions.fork', () => { }, reason: 'initial', }) - const response = await remote(ctx).fork(request({ sessionId: source.id })) + const boundary = source.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) + const inherited = source.snapshotEvents() + source.append('session/title', { + title: 'Title after the selected turn', messageSeqs: [], source: { kind: 'user' }, + }) + source.append('turn/start', { turn: 2 }) + source.append('request/header', { + header: { config: { provider: 'later-provider', model: 'later-model' } }, + reason: 'change', + }) + source.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) + const response = await remote(ctx).fork(request({ sessionId: source.id, atSeq: boundary.seq })) expect(response.ok).toBe(true) if (!response.ok) return const child = ctx.agents.get(response.value.sessionId) if (child === undefined) throw new Error('fork did not publish the child agent') + expect(child.session.snapshotEvents().slice(0, child.session.inheritedEventCount)).toEqual(inherited) const assembly = await child.ctx.systemPrompt.assemble() expect(assembly.variables).toMatchObject({ provider: 'inherited-provider', diff --git a/packages/api/session-controller/tests/transport.host.spec.ts b/packages/api/session-controller/tests/transport.host.spec.ts index ebc731769e..bac8a541d4 100644 --- a/packages/api/session-controller/tests/transport.host.spec.ts +++ b/packages/api/session-controller/tests/transport.host.spec.ts @@ -697,7 +697,7 @@ describe('SessionHistoryController', () => { expect(childSnapshot).toHaveBeenCalledWith(childSession) }) - it('keeps message-aligned pagination contiguous across replacement provenance', async () => { + it('keeps message-aligned pagination contiguous across replacement source-event references', async () => { const { ctx, transport } = await setup() const session = ctx.sessions.create(SessionId('pagination'), { meta: { cwd: '/workspace' } }) session.append('turn/start', { turn: 1 }) diff --git a/packages/boot/app-boot/README.i18n.yaml b/packages/boot/app-boot/README.i18n.yaml index ad93250809..f9371c44cc 100644 --- a/packages/boot/app-boot/README.i18n.yaml +++ b/packages/boot/app-boot/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/boot/app-boot/README.md -README.md: 10d582289707c87bd84b0eb32267ac4f5addf884 +README.md: 91ad6b67357172a0f6dea18c03deee73de391c0b README.zh.md: c8bb61e28e8ad758c757fe11a5b55135e1daf319 diff --git a/packages/boot/app-boot/README.md b/packages/boot/app-boot/README.md index 10d5822897..91ad6b6735 100644 --- a/packages/boot/app-boot/README.md +++ b/packages/boot/app-boot/README.md @@ -173,6 +173,6 @@ This Dev Note is working context for maintainers: open design questions and dire #### Open: config dump stability -`renderConfigDump` output is a loadable YAML document whose `# ==` provenance comments and `!!js`-verbatim rendering serve the `--dump-config` diagnostic. Nothing promises byte stability across package versions; decide whether the dump becomes a serialization contract before anything consumes it programmatically. +`renderConfigDump` output is a loadable YAML document whose `# ==` source comments and `!!js`-verbatim rendering serve the `--dump-config` diagnostic. Nothing promises byte stability across package versions; decide whether the dump becomes a serialization contract before anything consumes it programmatically. diff --git a/packages/boot/app-boot/src/index.ts b/packages/boot/app-boot/src/index.ts index cebe39888b..b8c96db202 100644 --- a/packages/boot/app-boot/src/index.ts +++ b/packages/boot/app-boot/src/index.ts @@ -454,7 +454,7 @@ export function renderConfigDump( } let previous = base let previousWarnings: string[] = [] - const provenance: { origin: string; patchedBy: string[] }[] = base.map(() => ({ origin: baseLabel, patchedBy: [] })) + const entryOrigins: { origin: string; patchedBy: string[] }[] = base.map(() => ({ origin: baseLabel, patchedBy: [] })) let composed = base for (let count = 1; count <= layers.length; count += 1) { const layer = layers[count - 1] @@ -467,19 +467,19 @@ export function renderConfigDump( } const before = previous.map(entry => JSON.stringify(entry)) for (let index = 0; index < composed.length; index += 1) { - if (index >= before.length) provenance.push({ origin: layer.label, patchedBy: [] }) - else if (JSON.stringify(composed[index]) !== before[index]) provenance[index]?.patchedBy.push(layer.label) + if (index >= before.length) entryOrigins.push({ origin: layer.label, patchedBy: [] }) + else if (JSON.stringify(composed[index]) !== before[index]) entryOrigins[index]?.patchedBy.push(layer.label) } previous = composed previousWarnings = warnings } - return groupedDump(composed, provenance) + return groupedDump(composed, entryOrigins) } /** Render the composed rows grouped under one source-and-patches comment per contiguous run. */ function groupedDump( composed: readonly unknown[], - provenance: readonly { origin: string; patchedBy: string[] }[], + entryOrigins: readonly { origin: string; patchedBy: string[] }[], ): string { const lines: string[] = [] let currentLabel: string | undefined @@ -491,7 +491,7 @@ function groupedDump( group = [] } for (let index = 0; index < composed.length; index += 1) { - const record = provenance[index] + const record = entryOrigins[index] /* v8 ignore next -- this array is index-aligned with composed by construction */ if (record === undefined) continue const label = record.patchedBy.length === 0 diff --git a/packages/bundle/headless/README.i18n.yaml b/packages/bundle/headless/README.i18n.yaml index db1d6102b1..791c3c026c 100644 --- a/packages/bundle/headless/README.i18n.yaml +++ b/packages/bundle/headless/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/bundle/headless/README.md -README.md: 98a9cb2294d8b05a40287c990b3ff53f755747cf -README.zh.md: 99bf90845bed1a7601ca8a70cfff98567ea364f8 +README.md: 859b39ed3bc14d7d9f85db179bcbe712918c4171 +README.zh.md: 550036e7fa0650781628e709b3429cc005b7ac66 diff --git a/packages/bundle/headless/README.md b/packages/bundle/headless/README.md index 98a9cb2294..859b39ed3b 100644 --- a/packages/bundle/headless/README.md +++ b/packages/bundle/headless/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -`dsh-headless` runs one dsh task from the command line and prints the final answer, then exits — no GUI, no server, no browser. Type `dsh --profile headless "run the tests"` and the agent works through the task with the same model, tools, and safety defaults as every other surface. It is ideal for scripts, CI, and one-off jobs: the process opens no ports and leaves nothing running behind. The exit code tells you the outcome — 0 when the task completed, 1 when it aborted or errored. The main boundary: one task per invocation, with no interactive follow-up. +`dsh-headless` runs one dsh task from the command line and prints the final answer, then exits — no GUI, no server, no browser. Type `dsh --profile headless "run the tests"` and the agent handles it with the same model, tools, and safety defaults as every other surface. It suits scripts, CI, and one-off jobs: it opens no ports and leaves nothing running behind. It also offers a JSON event stream (`--json`) and `--session-id` to resume a conversation. Exit code 0 means the task completed; 1 means it aborted or errored. The boundary: one task per invocation, no interactive follow-up. ## Table of Contents @@ -25,7 +25,7 @@ English | [中文](README.zh.md) ## Use this package -Run one task, get the final answer, and exit. The task is the command line itself, so the whole invocation is the smallest working example. +Run one task, get the final answer, and exit. The task is the command-line argument, or stdin when you omit it; the whole invocation is the smallest working example. ### Running a one-shot task @@ -33,21 +33,37 @@ Run one task, get the final answer, and exit. The task is the command line itsel dsh --profile headless "run the tests" ``` -The agent works through the task, streams each non-empty provider reasoning delta to stderr under a `dsh: reasoning:` heading, then prints the final answer on stdout and exits. Consecutive reasoning deltas stay in one section, and the runner closes that section before later output when the provider supplied no trailing newline. A successful run without reasoning keeps stderr empty; a failure exits 1 and prints `dsh: : ` to stderr. A missing or blank task is rejected before anything runs. The task text is supplied through the single `task` setting: +The agent works through the task, streams each non-empty provider reasoning delta to stderr under a `dsh: reasoning:` heading, then prints the final answer on stdout and exits. Consecutive reasoning deltas stay in one section, and the runner closes that section before later output when the provider supplied no trailing newline. A successful run without reasoning keeps stderr empty; a failure exits 1 and prints `dsh: : ` to stderr. The task comes from the positional argument, or from stdin when the argument is omitted or is a lone `-`; a blank positional argument or an empty pipe is rejected before anything runs. A positional task is used as-is and stdin is not read, so put the whole prompt in the pipe when you want piped input; a piped task is sent verbatim, its trailing newline included. + +```sh +{ echo "Summarize these changes:"; git diff --stat; } | dsh --profile headless +``` + +The task and run options are supplied through three settings: | Field | Default | Meaning | |---|---|---| -| `task` | required | The task text for the single run | +| `task` | stdin | The task text; stdin supplies it when omitted or `-` | +| `sessionId` | `session-` | Exact Session identity to adopt; an unknown id fails | +| `json` | `false` | Project the run as newline-delimited events on stdout | The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-headless) is the exhaustive source for every accepted field and its JSDoc. +### Choosing the session identity + +Every invocation defaults to a fresh `session-` identity, which `--json` reports in its opening `session` event. Pass `--session-id ` to continue that conversation: the runner adopts the persisted Session with that id, and an id with no stored Session fails before the task runs rather than quietly opening an empty history. Adoption requires the composed `sessionPersistence` and `sessionQuery` services, so a profile that omits either fails loudly instead of returning an id whose history dies with the process. An Agent already live under the requested id in this process is refused: another owner may still drive it, so the runner cannot claim an exclusive run interval over it. The identity is opaque, so the exact string is used, whitespace included. Adoption is scoped to the current working directory and refuses a Session that is a subagent or forked session, that recorded no working directory, that runs under an agent preset this profile does not compose, or whose preset record is malformed — the check reads the preset the Session log currently records, so a Session that switched preset while blank is rejected too. A supervisor therefore cannot silently drive someone else's conversation under a different composition; any mismatch fails before the task runs. + +### Machine-readable output + +`--json` replaces the final-text stdout line with a newline-delimited JSON event stream, while stderr keeps only the `dsh:` diagnostics. The stream opens with `session` (carrying the identity the run used) and closes with `final`, and carries `status`, `text`, `thinking`, `tool_call`, and `tool_result` events in between. `text` and `thinking` are projected from committed assistant messages, so a retried or discarded attempt never reaches the stream; they arrive when the step commits, not per token, and default-mode stderr reasoning remains the only live text channel. The terminal `final` event carries the same lossless answer as the default mode and is not capped; every other string and object key is capped at 8 KiB and flagged with `truncated`, and one event line, its newline included, is capped at 32 KiB — an over-long event keeps its scalar fields, drops structured ones, and at the extreme reduces to `type` and `truncated`, while a payload nested 64 levels or deeper is cut at that depth. An empty tool-argument string projects as `{}`, matching what the executor runs, while arguments that JSON cannot round-trip — an overflowing number such as `1e400` — keep their raw text rather than the `null` that `JSON.stringify` would report. A failure the runner raises outside a turn writes an `error` event and ends the stream without `final`, in addition to the `dsh:` stderr line; a profile whose own plugins fail to load exits before the runner mounts, so that case keeps only the loader's stderr diagnostics. A turn that fails in-turn still ends with a `final` event (often empty) and no `error` event, so a well-formed stream can still describe a failed run: treat exit code 1 and the `turn_end` reason as the failure signal. + ### When to use it -Use headless for scripted or automated dsh runs — CI steps, batch jobs, quick answers from a terminal. Avoid it when you need a multi-turn interactive session or a GUI; the browser surface ([dsh-web-app](../web-app/README.md)) serves that. The process stays alive only for the run, opens no listening port, and exits on its own, so it fits pipelines that wait on the process. +Use headless for scripted or automated dsh runs — CI steps, batch jobs, quick answers from a terminal. Avoid it when you need a multi-turn interactive session or a GUI; the browser surface ([dsh-web-app](../web-app/README.md)) serves that. The process stays alive only for the run, opens no listening port, and exits on its own, so it fits pipelines that wait on the process. When a supervisor needs progress rather than just the answer, `--json` gives it the event stream and `--session-id` lets a later invocation continue the same conversation. ### Help and task errors -`dsh --profile headless --help` prints the command's help text and exits without running anything. A missing or whitespace-only task is a usage error: nothing runs and the process exits 1. +`dsh --profile headless --help` prints the command's help text and exits without running anything. A whitespace-only positional task is a usage error on its own — nothing runs and the process exits 1, even when stdin is not a terminal, so an accidental blank argument never consumes a pipe. A task that is absent entirely is a usage error only when stdin is a terminal; otherwise the runner reads the task from stdin and rejects an empty result the same way. A lone `-` is the only stdin marker; mixing it with other task words is a usage error rather than a task that starts with a dash. In `--json` mode every usage error — including commander's own grammar rejections such as an unknown option or a missing option value — also writes an `error` event to stdout before the process exits, so a line-oriented supervisor sees a well-formed stream even when the runner never mounts; the event `message` carries the text without commander's `error: ` prefix. ----- @@ -57,29 +73,31 @@ Use headless for scripted or automated dsh runs — CI steps, batch jobs, quick
Implementation internals — click to expand -The runner is a direct driver over the core API carrier: it creates one fresh Agent through the registry and folds the owned durable event interval into one process-level outcome. +The runner is a direct driver over the core API carrier: it resolves the Agent identity — a fresh `session-` by default, or the persisted Session `--session-id` names — and folds the owned durable event interval into one process-level outcome. ### Run flow -The runner awaits the complete application (`ctx.get('loader')?.await()`) so the composed tools and adapters are not half-mounted, reads the shared [`agentDefaultModel`](../../core/agent-default-model/README.md) selection, creates one fresh persisted Agent with that provider and model, and submits the task as an ordinary user message. It streams that Agent's non-empty reasoning deltas to stderr, waits for quiescence, then flushes the Session and folds the owned interval (`firstSeq` onward) into the last non-empty `assistant/message` text and final `turn/end` reason. It writes the final text to stdout and requests exit. +The runner awaits the complete application (`ctx.get('loader')?.await()`) so the composed tools and adapters are not half-mounted, reads the shared [`agentDefaultModel`](../../core/agent-default-model/README.md) selection, resolves the task from config or stdin, then resolves the Agent identity: a fresh `session-` by default, or the persisted Session `--session-id` names, which it adopts through [`sessionQuery`](../../session-query/session-query/README.md) and refuses when no log exists. It submits the task as an ordinary user message. Without `--json` it streams that Agent's non-empty reasoning deltas to stderr; with `--json` it projects the run instead. It waits for quiescence, then flushes the Session and folds the owned interval (`firstSeq` onward) into the last non-empty `assistant/message` text and final `turn/end` reason. It writes the final text to stdout (or the `final` event) and requests exit. ### Patch surface over base -The patch rides over `dsh-base`: it inherits the projection cache, sets the coding persona prefix and separate cwd suffix on the base `system-prompt` row, keeps the same temporary process-wide PTC mode opt-in (`DSH_TOOLS_MODE`) as the Web surface, disables the shared HMR row, inserts PTC mode's worker as a core execution capability, and mounts the startup provider and the runner. The cache checkpoints each persisted one-shot session for later consumers; its durability barrier flushes each covered log prefix before publishing the cache row and may split otherwise coalesced JSONL runs. The startup provider ([`src/startup.ts`](src/startup.ts)) injects `ctx.cmdlineArgs` ([`dsh-cmdline`](../../boot/cmdline/README.md)), reads the positional argument, prints the app's `--help`, and provides `headlessStartup`; the runner injects that service and reads its task from lazy config. +The patch rides over `dsh-base`: it inherits the projection cache, sets the coding persona prefix and separate cwd suffix on the base `system-prompt` row, keeps the same temporary process-wide PTC mode opt-in (`DSH_TOOLS_MODE`) as the Web surface, disables the shared HMR row, inserts PTC mode's worker as a core execution capability, and mounts the startup provider and the runner. The cache checkpoints each persisted one-shot session for later consumers; its durability barrier flushes each covered log prefix before publishing the cache row and may split otherwise coalesced JSONL runs. The startup provider ([`src/startup.ts`](src/startup.ts)) injects `ctx.cmdlineArgs` ([`dsh-cmdline`](../../boot/cmdline/README.md)), reads the positional argument and the `--session-id`/`--json` options, prints the app's `--help`, and provides `headlessStartup`; the runner injects that service and reads its task and run options from lazy config. ### Exit mapping -A completed final `turn/end` exits 0; any other outcome — aborted, error, or no turn in the owned interval — exits 1. An `error` reason also writes `dsh: : ` to stderr. A direct driver failure (for example, Agent creation) writes `dsh: ` to stderr and exits 1. +A completed final `turn/end` exits 0; any other outcome — aborted, error, or no turn in the owned interval — exits 1. An `error` reason also writes `dsh: : ` to stderr. A direct driver failure (for example, Agent creation or an unusable `--session-id`) writes `dsh: ` to stderr and exits 1, and in `--json` mode also emits an `error` event. ### Source map | File | Role | |---|---| -| [`src/index.ts`](src/index.ts) | The `headless-runner` plugin: run flow, output contract, exit mapping | -| [`src/startup.ts`](src/startup.ts) | The `headless-startup` provider: task positional and `--help` | +| [`src/index.ts`](src/index.ts) | The `headless-runner` plugin: run flow, session resolution, output contract, exit mapping | +| [`src/startup.ts`](src/startup.ts) | The `headless-startup` provider: task positional, `--session-id`, `--json`, and `--help` | +| [`src/json-stream.ts`](src/json-stream.ts) | The `--json` projection: event vocabulary, commit-point emission, string bounding | | [`cordis.patch.yml`](cordis.patch.yml) | The one-shot patch over `dsh-base` | | — | No runtime invariant companion is published; the runner's observable contract (provider reasoning on stderr, final text on stdout, exit code by turn-end reason) is process-level and owned by the launcher e2e; it registers nothing and holds no mutable relation to audit inside the tree. | -| [`tests/headless.spec.ts`](tests/headless.spec.ts) | Run flow, aggregation, flush, and exit mapping | +| [`tests/headless.spec.ts`](tests/headless.spec.ts) | Run flow, aggregation, flush, session adoption, and exit mapping | +| [`tests/json-stream.spec.ts`](tests/json-stream.spec.ts) | Projection ordering, commit-point emission, bounding, and disposal | | [`tests/startup.spec.ts`](tests/startup.spec.ts) | Command-line parsing over a real Loader tree | ### Invariant ownership @@ -121,9 +139,11 @@ These limits tell you when headless does not fit and what it needs from the `dsh - **One task per run** — after the task is answered the process exits; there is no interactive follow-up, so split multi-step work into separate runs. - **Runs through the `dsh` launcher** — starting the headless profile another way fails at startup, because only the launcher can request the process exit. -- **No pre-token heartbeat** — stderr stays silent until the provider emits a non-empty reasoning delta; a delayed first token exposes no earlier progress signal. -- **Reasoning enters stderr logs** — redirection and supervisors may retain substantially more and potentially sensitive model output; route stderr to a controlled sink when needed. -- **Only reasoning and the final answer are printed** — a run without an assistant message prints an empty stdout line and exits 1; intermediate tool output is not printed. +- **No pre-token heartbeat** — in default mode stderr stays silent until the provider emits a non-empty reasoning delta; a delayed first token exposes no earlier progress signal. +- **Reasoning enters stderr logs** — in default mode, redirection and supervisors may retain substantially more and potentially sensitive model output; route stderr to a controlled sink when needed. +- **Default stdout carries only the final answer** — a run without an assistant message prints an empty stdout line and exits 1; intermediate tool output is not printed unless you opt into `--json`. +- **Adoption is cwd-, ownership-, and preset-scoped** — `--session-id` refuses a Session recorded in another working directory, one that recorded no working directory, one that is a subagent or forked session, or one that runs under an agent preset this profile does not compose or whose preset record is malformed, and requires the composed Session query and persistence services; an identity already live in the process is refused too, because the runner cannot own an exclusive run interval over it. +- **The event stream is a projection, not the log** — `--json` caps every string except the terminal `final` at 8 KiB and omits events the projection does not model, so it is not a lossless copy of the Session log. ### Dev Note diff --git a/packages/bundle/headless/README.zh.md b/packages/bundle/headless/README.zh.md index 99bf90845b..550036e7fa 100644 --- a/packages/bundle/headless/README.zh.md +++ b/packages/bundle/headless/README.zh.md @@ -9,7 +9,7 @@ kind: "package-bundle" ## 概述 -`dsh-headless` 从命令行运行一个 dsh 任务并打印最终答案,然后退出——没有 GUI、没有服务器、没有浏览器。输入 `dsh --profile headless "run the tests"`,agent(智能体)会以与所有其他表层相同的模型、工具与安全默认值完成该任务。它非常适合脚本、CI 与一次性任务:进程不打开任何端口,也不会留下任何后台运行的东西。退出码告诉你结果——任务完成时为 0,中止或出错时为 1。主要边界:每次调用只运行一个任务,没有交互式后续。 +`dsh-headless` 从命令行运行一个 dsh 任务并打印最终答案,然后退出——没有 GUI、没有服务器、没有浏览器。输入 `dsh --profile headless "run the tests"`,agent(智能体)会以与所有其他表层相同的模型、工具与安全默认值完成该任务。它非常适合脚本、CI 与一次性任务:进程不打开任何端口,也不会留下任何后台运行的东西。监督进程还可以通过按行 JSON 事件流(`--json`)驱动它,并用该事件流报告的标识(`--session-id`)在同一段对话上继续唤醒。退出码告诉你结果——任务完成时为 0,中止或出错时为 1。主要边界:每次调用只运行一个任务,没有交互式后续。 ## 目录 @@ -25,7 +25,7 @@ kind: "package-bundle" ## 使用本包 -运行一个任务,获得最终答案,然后退出。任务就是命令行本身,因此整条命令就是最小的可运行示例。 +运行一个任务,获得最终答案,然后退出。任务就是命令行参数,省略时则来自 stdin;整条命令就是最小的可运行示例。 ### 运行一次性任务 @@ -33,21 +33,37 @@ kind: "package-bundle" dsh --profile headless "run the tests" ``` -agent 会完成该任务,把提供方的每个非空推理(reasoning)增量流式写入 stderr 的 `dsh: reasoning:` 段,然后把最终答案写入 stdout 并退出。连续推理增量保持在同一段中;提供方未给尾换行时,runner 会在后续输出前结束该段。没有推理内容的成功运行保持 stderr 为空;失败时退出码为 1,并以 `dsh: : ` 向 stderr 写入错误。缺失或空白任务会在任何执行开始之前被拒绝。任务文本通过唯一的 `task` 设置提供: +agent 会完成该任务,把提供方的每个非空推理(reasoning)增量流式写入 stderr 的 `dsh: reasoning:` 段,然后把最终答案写入 stdout 并退出。连续推理增量保持在同一段中;提供方未给尾换行时,runner 会在后续输出前结束该段。没有推理内容的成功运行保持 stderr 为空;失败时退出码为 1,并以 `dsh: : ` 向 stderr 写入错误。任务来自位置参数,参数省略或为单独的 `-` 时则来自 stdin;空白位置参数或空管道会在任何执行开始之前被拒绝。位置参数会原样作为任务,stdin 不会被读取,因此想让管道内容进入提示词时,请把完整提示写进管道;管道任务会原样发送,包括结尾换行。 + +```sh +{ echo "Summarize these changes:"; git diff --stat; } | dsh --profile headless +``` + +任务与运行选项通过三个设置提供: | 字段 | 默认值 | 含义 | |---|---|---| -| `task` | 必填 | 单次运行的任务文本 | +| `task` | stdin | 任务文本;省略或传 `-` 时由 stdin 提供 | +| `sessionId` | `session-` | 要沿用的精确 Session 标识;未知 id 会失败 | +| `json` | `false` | 把本次运行投影为 stdout 上的按行 JSON 事件 | 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-headless)是所有受支持字段及其 JSDoc 的完整真源。 +### 选择 Session 标识 + +每次调用默认使用全新的 `session-` 标识,`--json` 会在开头的 `session` 事件里报告它。传入 `--session-id ` 延续这段对话:runner 沿用该 id 对应的持久化 Session,而该 id 没有持久化 Session 时会在任务运行前失败,而不是悄悄开出一段空历史。沿用要求已组合 `sessionPersistence` 与 `sessionQuery` 服务,因此缺少任一服务的 profile 会显式失败,而不会返回一个历史随进程消失的 id。本进程中已存在持有该 id 的存活 Agent 时会被拒绝:它的原 owner 可能仍在驱动它,runner 无法取得独占的运行区间。标识是不透明的,因此会原样使用调用方给出的字符串,包括空白字符。沿用被限定在当前工作目录内,并拒绝子 agent 或 fork 会话、未记录工作目录的会话、运行在本 profile 不组合的 agent preset 下的会话,以及 preset 记录畸形的会话——该检查读取 Session 日志当前记录的 preset,因此在空白期切换过 preset 的会话同样会被拒绝。因此监督进程无法在另一套组合下悄悄驱动他人的会话;任一不匹配都会在任务运行前失败。 + +### 机器可读输出 + +`--json` 用按行 JSON 事件流取代 stdout 的最终文本行,stderr 仅保留 `dsh:` 诊断信息。事件流以 `session`(携带本次运行使用的标识)开头、以 `final` 结尾,其间为 `status`、`text`、`thinking`、`tool_call` 与 `tool_result` 事件。`text` 与 `thinking` 只从已提交的 assistant 消息投影,因此被重试或丢弃的尝试不会进入事件流;它们在步骤提交时到达,而不是逐 token 到达,默认模式的 stderr 推理仍是唯一的实时文本通道。终止 `final` 事件携带与默认模式相同的无损答案,不做限长;其他每个字符串与对象键上限为 8 KiB,超出时标记 `truncated`,单条事件行(含换行)上限为 32 KiB——超长事件保留标量字段、丢弃结构化字段,极端情况下只剩 `type` 与 `truncated`,嵌套达到 64 层及以上的负载会在该深度被截断。空工具参数字符串会投影为 `{}`,与执行器实际运行的值一致;而 JSON 无法往返的参数——例如溢出为 `Infinity` 的数字 `1e400`——会保留原始文本,而不是 `JSON.stringify` 会报告的 `null`。runner 在轮次之外抛出的失败会写出 `error` 事件并在没有 `final` 的情况下结束事件流,同时向 stderr 写入 `dsh:` 行;若 profile 自身的插件加载失败,进程会在 runner 挂载前退出,该情形只保留 loader 的 stderr 诊断。轮次内失败的运行仍会以 `final` 事件(通常为空)结束且没有 `error` 事件,因此格式良好的事件流也可能描述一次失败的运行:请把退出码 1 与 `turn_end` 原因作为失败信号。 + ### 何时使用 -在脚本化或自动化的 dsh 运行中使用 headless——CI 步骤、批处理任务、从终端快速获取答案。当需要多轮交互会话或 GUI 时请避免它;浏览器表层([dsh-web-app](../web-app/README.zh.md))负责这类场景。进程只为本次运行而存活,不打开监听端口,并且自行退出,因此适合等待进程结束的流水线。 +在脚本化或自动化的 dsh 运行中使用 headless——CI 步骤、批处理任务、从终端快速获取答案。当需要多轮交互会话或 GUI 时请避免它;浏览器表层([dsh-web-app](../web-app/README.zh.md))负责这类场景。进程只为本次运行而存活,不打开监听端口,并且自行退出,因此适合等待进程结束的流水线。当监督进程需要进度而不只是答案时,`--json` 提供事件流,`--session-id` 则让后续调用继续同一段对话。 ### 帮助与任务错误 -`dsh --profile headless --help` 打印该命令的帮助文本并直接退出,不运行任何内容。缺失或只有空白的任务属于用法错误:什么都不运行,进程退出 1。 +`dsh --profile headless --help` 打印该命令的帮助文本并直接退出,不运行任何内容。只有空白的位置参数本身就属于用法错误——什么都不运行,进程退出 1,即使 stdin 不是终端也一样,因此误传的空白参数绝不会消费管道内容。完全缺失任务时,仅在 stdin 是终端时属于用法错误;stdin 不是终端时,runner 改从 stdin 读取任务,并以同样方式拒绝空结果。单独的 `-` 是唯一的 stdin 标记;把它与其他任务词混用属于用法错误,而不是以连字符开头的任务。在 `--json` 模式下,所有用法错误——包括 commander 自身的语法拒绝,例如未知选项或选项缺少取值——都会在进程退出前向 stdout 写出 `error` 事件,因此按行解析的监督进程即使 runner 从未挂载也能看到格式良好的事件流;事件 `message` 中的文本不带 commander 的 `error: ` 前缀。 ----- @@ -57,29 +73,31 @@ agent 会完成该任务,把提供方的每个非空推理(reasoning)增
实现细节——点击展开 -runner 是核心 API 载体之上的直接驱动器:它通过注册表创建一个全新的 Agent,并把所属的持久化事件区间折叠成一个进程级结果。 +runner 是核心 API 载体之上的直接驱动器:它确定 Agent 标识——默认是全新的 `session-`,或 `--session-id` 指名的持久化 Session——并把所属的持久化事件区间折叠成一个进程级结果。 ### 运行流程 -runner 等待整个应用结算(`ctx.get('loader')?.await()`),确保已组合的工具与适配器不会半挂载,读取共享的 [`agentDefaultModel`](../../core/agent-default-model/README.zh.md) 选择,用该提供方与模型创建一个全新的持久化 Agent,并把任务作为普通用户消息提交。它把该 Agent 的非空推理增量流式写入 stderr、等待完全停稳,然后对会话执行 flush,并把所属区间(从 `firstSeq` 起)折叠为最后一条非空 `assistant/message` 文本与最终 `turn/end` 原因。最后,它把最终文本写入 stdout 并请求退出。 +runner 等待整个应用结算(`ctx.get('loader')?.await()`),确保已组合的工具与适配器不会半挂载,读取共享的 [`agentDefaultModel`](../../core/agent-default-model/README.zh.md) 选择,从配置或 stdin 解析任务,然后确定 Agent 标识:默认是全新的 `session-`,或是 `--session-id` 指名的持久化 Session——通过 [`sessionQuery`](../../session-query/session-query/README.zh.md) 沿用,日志不存在时拒绝。它把任务作为普通用户消息提交。不带 `--json` 时,它把该 Agent 的非空推理增量流式写入 stderr;带 `--json` 时改为投影本次运行。它等待完全停稳,然后对会话执行 flush,并把所属区间(从 `firstSeq` 起)折叠为最后一条非空 `assistant/message` 文本与最终 `turn/end` 原因。最后,它把最终文本写入 stdout(或 `final` 事件)并请求退出。 ### 基于 base 的 patch 内容 -patch 叠加在 `dsh-base` 之上:继承投影缓存,在基础 `system-prompt` 行上设置编码 persona 前缀与独立的 cwd 后缀,保留与 Web 表层相同的临时进程级 PTC mode 开关(`DSH_TOOLS_MODE`),禁用共享的 HMR(热模块替换)行,把 PTC mode 的 worker 作为核心执行能力插入,并挂载启动提供方与 runner。缓存为每个已持久化的一次性会话写入检查点,供后续消费方使用;其持久性屏障会在发布缓存行前 flush 所覆盖的日志前缀,因此可能拆分原本会合并的 JSONL 连续段。启动提供方([`src/startup.ts`](src/startup.ts))注入 `ctx.cmdlineArgs`([`dsh-cmdline`](../../boot/cmdline/README.zh.md)),读取位置参数、打印应用自己的 `--help`,并提供 `headlessStartup`;runner 注入该服务,再从惰性配置中读取任务。 +patch 叠加在 `dsh-base` 之上:继承投影缓存,在基础 `system-prompt` 行上设置编码 persona 前缀与独立的 cwd 后缀,保留与 Web 表层相同的临时进程级 PTC mode 开关(`DSH_TOOLS_MODE`),禁用共享的 HMR(热模块替换)行,把 PTC mode 的 worker 作为核心执行能力插入,并挂载启动提供方与 runner。缓存为每个已持久化的一次性会话写入检查点,供后续消费方使用;其持久性屏障会在发布缓存行前 flush 所覆盖的日志前缀,因此可能拆分原本会合并的 JSONL 连续段。启动提供方([`src/startup.ts`](src/startup.ts))注入 `ctx.cmdlineArgs`([`dsh-cmdline`](../../boot/cmdline/README.zh.md)),读取位置参数与 `--session-id`/`--json` 选项、打印应用自己的 `--help`,并提供 `headlessStartup`;runner 注入该服务,再从惰性配置中读取任务与运行选项。 ### 退出映射 -最终 `turn/end` 完成时退出码为 0;任何其他结果——aborted、error,或所属区间内没有轮次——退出码为 1。结束原因为 `error` 时还会向 stderr 写入 `dsh: : `。直接驱动器失败(例如 Agent 创建失败)向 stderr 写入 `dsh: ` 并退出 1。 +最终 `turn/end` 完成时退出码为 0;任何其他结果——aborted、error,或所属区间内没有轮次——退出码为 1。结束原因为 `error` 时还会向 stderr 写入 `dsh: : `。直接驱动器失败(例如 Agent 创建失败或不可用的 `--session-id`)向 stderr 写入 `dsh: ` 并退出 1,且在 `--json` 模式下额外发出一个 `error` 事件。 ### 源码地图 | 文件 | 职责 | |---|---| -| [`src/index.ts`](src/index.ts) | `headless-runner` 插件:运行流程、输出约定、退出映射 | -| [`src/startup.ts`](src/startup.ts) | `headless-startup` 提供方:任务位置参数与 `--help` | +| [`src/index.ts`](src/index.ts) | `headless-runner` 插件:运行流程、Session 解析、输出约定、退出映射 | +| [`src/startup.ts`](src/startup.ts) | `headless-startup` 提供方:任务位置参数、`--session-id`、`--json` 与 `--help` | +| [`src/json-stream.ts`](src/json-stream.ts) | `--json` 投影:事件词汇、提交点发射、字符串限长 | | [`cordis.patch.yml`](cordis.patch.yml) | 叠加在 `dsh-base` 之上的一次性 patch | | — | 不发布运行时不变式伴生入口;runner 的可观察约定(stderr 中的提供方推理、stdout 中的最终文本、按轮次结束原因决定的退出码)属于进程级,并由启动器 e2e 负责;runner 不注册任何内容,树内也没有任何可变关系可审计。 | -| [`tests/headless.spec.ts`](tests/headless.spec.ts) | 运行流程、汇总、flush 与退出映射 | +| [`tests/headless.spec.ts`](tests/headless.spec.ts) | 运行流程、汇总、flush、Session 沿用与退出映射 | +| [`tests/json-stream.spec.ts`](tests/json-stream.spec.ts) | 投影顺序、提交点发射、限长与释放 | | [`tests/startup.spec.ts`](tests/startup.spec.ts) | 在真实 Loader 树上的命令行解析 | ### 不变式归属 @@ -121,9 +139,11 @@ runner 不向请求前缀添加任何内容;它只是驱动组合出的配置 - **每次运行一个任务**——任务得到回答后进程即退出;没有交互式后续,因此多步工作请拆成多次运行。 - **通过 `dsh` 启动器运行**——以其他方式启动 headless profile 会在启动时失败,因为只有启动器能请求进程退出。 -- **首个 token 前没有心跳**——提供方发出第一个非空推理增量前,stderr 保持静默;延迟首个 token 的提供方不会更早给出进度信号。 -- **推理进入 stderr 日志**——重定向与监督进程可能保留显著更多且可能敏感的模型输出;需要时应把 stderr 路由到受控位置。 -- **只打印推理和最终答案**——没有 assistant 消息的运行向 stdout 打印空行并以 1 退出;中间工具输出不会打印。 +- **首个 token 前没有心跳**——默认模式下,提供方发出第一个非空推理增量前,stderr 保持静默;延迟首个 token 的提供方不会更早给出进度信号。 +- **推理进入 stderr 日志**——默认模式下,重定向与监督进程可能保留显著更多且可能敏感的模型输出;需要时应把 stderr 路由到受控位置。 +- **默认 stdout 只承载最终答案**——没有 assistant 消息的运行向 stdout 打印空行并以 1 退出;中间工具输出不会打印,除非显式启用 `--json`。 +- **沿用受 cwd、归属与 preset 限制**——`--session-id` 会拒绝记录在其他工作目录、未记录工作目录、属于子 agent 或 fork 会话,或运行在本 profile 不组合的 agent preset 下的 Session、preset 记录畸形的 Session,并要求已组合的 Session 查询与持久化服务;本进程中已存活的身份同样会被拒绝,因为 runner 无法对它取得独占的运行区间。 +- **事件流是投影而非日志**——`--json` 除终止 `final` 外把每个字符串与对象键限制在 8 KiB,并省略投影未建模的事件,因此它不是 Session 日志的无损副本。 ### 开发备注 diff --git a/packages/bundle/headless/cordis.patch.yml b/packages/bundle/headless/cordis.patch.yml index f20194e862..1c80f91b07 100644 --- a/packages/bundle/headless/cordis.patch.yml +++ b/packages/bundle/headless/cordis.patch.yml @@ -1,8 +1,10 @@ # The dsh-headless bundle patch: one-shot task mode directly over dsh-base. # It mounts no Host, HTTP server, Web runtime, or browser plugin. An ordinary # provider plugin injects `cmdlineArgs`, parses the task positional -# (`dsh --profile headless ""`) and this app's --help, then the direct -# driver creates an Agent through the core registry and prints its durable result. +# (`dsh --profile headless ""`), the `--session-id` and `--json` options, +# and this app's --help, then the direct driver creates a fresh Agent, or adopts +# the exact Session the flag names, through the core registry and prints its +# durable result. - id: system-prompt config: @@ -23,9 +25,11 @@ - id: headless-startup name: '@deepseek-ai/dsh-headless/startup' - # Reads its task from the ordinary headlessStartup provider. + # Reads its task and run options from the ordinary headlessStartup provider. - id: headless-runner name: '@deepseek-ai/dsh-headless' inject: [headlessStartup] config: task: !!js ctx.headlessStartup.task + sessionId: !!js ctx.headlessStartup.sessionId + json: !!js ctx.headlessStartup.json diff --git a/packages/bundle/headless/package.json b/packages/bundle/headless/package.json index 5d7042c166..129c081c16 100644 --- a/packages/bundle/headless/package.json +++ b/packages/bundle/headless/package.json @@ -30,6 +30,7 @@ "lib/index.js", "lib/startup.js", "cordis.patch.yml", + "lib/json-stream-*.js", "lib/types/**/*.d.ts" ], "license": "MIT", @@ -52,7 +53,8 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-default-model": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", - "@deepseek-ai/dsh-session": "workspace:^" + "@deepseek-ai/dsh-session": "workspace:^", + "@deepseek-ai/dsh-session-query": "workspace:^" }, "devDependencies": { "@deepseek-ai/cordis": "workspace:^", @@ -62,6 +64,7 @@ "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", - "@deepseek-ai/dsh-session": "workspace:^" + "@deepseek-ai/dsh-session": "workspace:^", + "@deepseek-ai/dsh-session-query": "workspace:^" } } diff --git a/packages/bundle/headless/src/index.ts b/packages/bundle/headless/src/index.ts index 90f29b460a..16594cd440 100644 --- a/packages/bundle/headless/src/index.ts +++ b/packages/bundle/headless/src/index.ts @@ -1,9 +1,11 @@ /** * @deepseek-ai/dsh-headless — one-shot direct Agent driver. The bundle patch * rides over dsh-base without Host, HTTP, or browser plugins; this runner - * creates one Agent through the core registry, drives the task to quiescence, - * streams provider reasoning to stderr, flushes its Session, prints the final - * assistant text to stdout, and exits. + * creates one Agent through the core registry (or adopts the exact Session a + * `--session-id` names), drives the task to quiescence, streams provider + * reasoning to stderr, flushes its Session, prints the final assistant text to + * stdout, and exits. With `--json` it projects the run as newline-delimited + * events instead of the final text. * * @module @deepseek-ai/dsh-headless */ @@ -19,10 +21,15 @@ import { createUserMessage } from '@deepseek-ai/dsh-llm' import { assertNever } from '@deepseek-ai/dsh-util-values' import { SessionSeq } from '@deepseek-ai/dsh-session' import type { Session, SessionEvent, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session' -// Empty type imports carry the loader Context merge for the settlement await -// and the cmdline Context merge for the appExit host value. +import { SessionQueryError } from '@deepseek-ai/dsh-session-query' +// Empty type imports carry the loader Context merge for the settlement await, +// the cmdline Context merge for the appExit host value, and the sessionQuery +// Context merge for exact Session adoption. import type {} from '@deepseek-ai/cordis-plugin-loader' import type {} from '@deepseek-ai/dsh-cmdline' +import type {} from '@deepseek-ai/dsh-session-query' +import { internals } from './runner-internals.ts' +import { projectJsonRun, boundJsonLine } from './json-stream.ts' /** Stable Cordis plugin name. */ export const name = 'headless-runner' @@ -30,14 +37,20 @@ export const name = 'headless-runner' /** Core services required before the one-shot turn can start. */ export const inject = ['agentDefaultModel', 'agents', 'sessions'] -/** Plugin config: the task resolved from this app's injected provider service. */ +/** Plugin config: the task and run options resolved from this app's injected provider service. */ export interface Config { - /** The prompt text for the single run. */ - task: string + /** The prompt text for the single run; absent when the task arrives on stdin. */ + task?: string + /** Exact Session identity to adopt; absent for a fresh random identity. An id with no stored Session fails. */ + sessionId?: string + /** Whether stdout carries the machine-readable event stream instead of final text. */ + json?: boolean } export const Config: z = z.object({ - task: z.string().required(), + task: z.string(), + sessionId: z.string(), + json: z.boolean(), }) /** Outcome of one owned run interval. */ @@ -54,12 +67,6 @@ interface HeadlessIo { exit(code: number): void } -/** The process streams the runner writes to; tests substitute captures. */ -export const internals: { stdout: HeadlessIo['stdout']; stderr: HeadlessIo['stderr'] } = { - stdout: process.stdout, - stderr: process.stderr, -} - /** Aggregate the last assistant text and turn outcome in one owned interval. */ function summarize(session: Session, firstSeq: SessionLogOffset): RunOutcome { let started = false @@ -155,19 +162,148 @@ function streamReasoning( } } +/** The Session facts that decide whether the runner may drive it directly. */ +interface AdoptableHeader { + cwd?: string | undefined + origin?: 'subagent' | undefined + parentSession?: SessionId | undefined + agentPreset?: string | undefined +} + +/** Iterate a live Session's durable events in order. */ +function* liveEvents(session: Session): Generator { + const length = session.seq + for (let seq = 0; seq < length; seq++) { + // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. + const event = session.eventAt(SessionSeq(seq)) + if (event === undefined) { + throw new Error(`headless adoption cannot read seq ${String(seq)} below captured length ${String(length)}`) + } + yield event + } +} + +/** + * The preset a Session currently runs under: its creation header advanced by + * the last `agent-preset/selected` event. The header is only a creation fact; + * the presets plugin reconstructs a session's composition from the projection. + */ +function currentPreset(header: AdoptableHeader, events: Iterable, sessionId: SessionId): string | undefined { + let preset = header.agentPreset + for (const event of events) { + // Owned by dsh-agent-presets, which this bundle does not compose, so the + // event is read structurally rather than through its module augmentation. + const candidate = event as unknown as { type: string; data?: { agentPreset?: unknown } } + if (candidate.type !== 'agent-preset/selected') continue + const selected = candidate.data?.agentPreset + // A corrupt record must not read as "no preset": that would let the run + // continue under this bundle's composition instead of the recorded one. + if (typeof selected !== 'string' || selected === '') { + throw new Error(`session "${sessionId}" records a malformed agent-preset/selected event and cannot be adopted`) + } + preset = selected + } + return preset +} + +/** Reject a Session the one-shot runner must not adopt. */ +function assertAdoptable(header: AdoptableHeader, events: Iterable, sessionId: SessionId): void { + const preset = currentPreset(header, events, sessionId) + if (preset !== undefined) { + // This bundle composes no preset roster, so resuming the session here would + // silently run it under the headless tools and prompts instead of the + // composition its log records. + throw new Error( + `session "${sessionId}" runs under agent preset "${preset}", which the one-shot runner does not compose`, + ) + } + if (header.origin === 'subagent' || header.parentSession !== undefined) { + throw new Error(`session "${sessionId}" is a subagent or forked session and cannot be driven directly`) + } + if (header.cwd === undefined) { + throw new Error(`session "${sessionId}" recorded no working directory, so it cannot be adopted`) + } + if (header.cwd !== process.cwd()) { + throw new Error(`session "${sessionId}" was recorded in "${header.cwd}", not "${process.cwd()}"`) + } +} + +/** + * Resolve the Agent for one run: adopt the persisted Session with the requested + * id. The identity must already exist, and no Agent may be live under it; a + * first round omits the option instead, so a typo cannot pass as a brand-new + * conversation. + * @param ctx - plugin context carrying the Session query service. + * @param agents - the core Agent registry. + * @param sessionId - exact Session identity to adopt. + * @param agentOptions - provider/model pair for this run. + * @param setup - per-Agent scope setup installing the model selection. + * @returns the resumed Agent. + */ +async function resolveAgent( + ctx: Context, + agents: Context['agents'], + sessionId: SessionId, + agentOptions: { provider: string; model: string }, + setup: (agentCtx: Context) => void, +): Promise { + // Resuming promises the caller a log a later process can continue. Without a + // durable log the run would succeed, print the id, and still lose the whole + // history at exit, so a miscomposed profile fails loud before the resume. + if (ctx.get('sessionPersistence') === undefined) { + throw new Error('headless --session-id requires the sessionPersistence service; the Session would not survive this process') + } + // A later process holds no live Agent and has to find the id through the + // query service, so every --session-id run requires it. + const query = ctx.get('sessionQuery') + if (query === undefined) { + throw new Error('headless --session-id requires the sessionQuery service; dsh-base provides it') + } + const live = agents.get(sessionId) + if (live !== undefined) { + // A live Agent already has an owner that may still drive it, and `whenIdle` + // is not a single-message signal: folding its next interval into this run + // would mix that owner's events — even its final answer — into the stream. + // The runner cannot claim an exclusive interval over an Agent it did not + // create, so it refuses the identity; the adoptability rules run first so a + // real mismatch is named instead of the generic refusal. + assertAdoptable(live.session.header, liveEvents(live.session), sessionId) + throw new Error(`session "${sessionId}" is live in this process, so the one-shot runner cannot own an exclusive run interval`) + } + try { + using observation = await query.observeSession(sessionId) + assertAdoptable(observation.header, observation.events, sessionId) + const { agent } = await agents.resume({ resumeSessionId: sessionId, agentOptions, setup }) + // The observation is a snapshot: another writer may have appended a preset + // selection before this process took the write lease. Re-check the log + // resume actually attached, now that no other process can append. + assertAdoptable(agent.session.header, liveEvents(agent.session), sessionId) + return agent + } catch (error: unknown) { + if (!(error instanceof SessionQueryError) || error.code !== 'SESSION_QUERY_SESSION_NOT_FOUND') throw error + // --session-id resumes a conversation that already exists; starting a new + // one is the no-id path, which generates its own identity and reports it in + // the `session` event. Creating the requested id here would turn a typo + // into a brand-new empty history the caller believes it is continuing. + throw new Error(`session "${sessionId}" does not exist; omit --session-id to start a new Session`) + } +} + /** Report an unexpected direct-driver failure and request a failing exit. */ -function fail(io: HeadlessIo, error: unknown): void { - io.stderr.write(`dsh: ${error instanceof Error ? error.message : String(error)}\n`) +function fail(io: HeadlessIo, error: unknown, json: boolean): void { + const message = error instanceof Error ? error.message : String(error) + if (json) io.stdout.write(`${boundJsonLine({ type: 'error', message })}\n`) + io.stderr.write(`dsh: ${message}\n`) io.exit(1) } /** - * Run one task through a freshly created Agent and request process exit. + * Run one task through one Agent and request process exit. * @param ctx - plugin context carrying the Agent, default model, Session, and launcher IO services. - * @param task - one-shot task text. + * @param config - task, optional exact Session identity, and output mode. * @param io - process-facing effects. */ -async function run(ctx: Context, task: string, io: HeadlessIo): Promise { +async function run(ctx: Context, config: Config, io: HeadlessIo): Promise { // Loader siblings mount concurrently. Await the complete application before // creating an Agent so its scoped tools and adapters are not half-composed. await ctx.get('loader')?.await() @@ -177,45 +313,75 @@ async function run(ctx: Context, task: string, io: HeadlessIo): Promise { // Early process shutdown can dispose the tree while settlement is pending. if (agents === undefined || defaultModel === undefined || sessions === undefined) return + // A Cordis overlay sets the row directly and bypasses the CLI trim check, so + // the same public setting must fail here rather than become a blank identity. + if (config.sessionId !== undefined && config.sessionId.trim() === '') { + throw new Error('headless-runner: sessionId must not be blank') + } + + const task = config.task === undefined || config.task === '-' + ? await internals.readStdin() + : config.task + if (task.trim() === '') { + throw new Error('a task is required, for example: dsh --profile headless "run the tests"') + } + const selection = defaultModel.currentSelection() + const agentOptions = { provider: selection.provider, model: selection.model } // This bundle composes no preset roster, so the model-facing rows sit in the // host plane and the agent reads them from the global layer. A deployment // that DOES configure one has to join it here first // (@deepseek-ai/dsh-agent-presets README, "Composing a child agent"). - const { agent } = await agents.create({ - sessionId: brandString(`session-${randomUUID()}`), - meta: { cwd: process.cwd() }, - agentOptions: { provider: selection.provider, model: selection.model }, - setup: (agentCtx) => { - const selected: ModelSelectionRef = { current: selection, assembled: undefined } - installModelSelection(agentCtx, selected) - }, - }) + const setup = (agentCtx: Context): void => { + const selected: ModelSelectionRef = { current: selection, assembled: undefined } + installModelSelection(agentCtx, selected) + } + const sessionId = brandString(config.sessionId ?? `session-${randomUUID()}`) + const agent = config.sessionId === undefined + ? (await agents.create({ + sessionId, + meta: { cwd: process.cwd() }, + agentOptions, + setup, + })).agent + : await resolveAgent(ctx, agents, sessionId, agentOptions, setup) await agent.whenIdle() + if (config.sessionId !== undefined) { + // The resume-time check read a snapshot; an overlay can still append a + // preset selection between it and the interval this run now owns, so + // re-read the log the runner holds before submitting the task. + assertAdoptable(agent.session.header, liveEvents(agent.session), sessionId) + } const firstSeq = agent.session.seq - const stopReasoning = streamReasoning(ctx, agent, io.stderr) + const projection = config.json === true ? projectJsonRun(ctx, agent, io.stdout) : undefined + const stopReasoning = projection === undefined ? streamReasoning(ctx, agent, io.stderr) : undefined try { - agent.followup(createUserMessage({ - content: [{ type: 'text', text: task }], - source: { kind: 'user' }, - })) - await agent.whenIdle() + try { + agent.followup(createUserMessage({ + content: [{ type: 'text', text: task }], + source: { kind: 'user' }, + })) + await agent.whenIdle() + } finally { + stopReasoning?.() + } + await sessions.flush(agent.session) + const outcome = summarize(agent.session, firstSeq) + if (projection === undefined) io.stdout.write(outcome.text + '\n') + else projection.finish(outcome.text) + if (outcome.reason?.kind === 'error') { + io.stderr.write(`dsh: ${outcome.reason.error.code}: ${outcome.reason.error.message}\n`) + } + io.exit(outcome.reason?.kind === 'completed' ? 0 : 1) } finally { - stopReasoning() + projection?.dispose() } - await sessions.flush(agent.session) - const outcome = summarize(agent.session, firstSeq) - io.stdout.write(outcome.text + '\n') - if (outcome.reason?.kind === 'error') { - io.stderr.write(`dsh: ${outcome.reason.error.code}: ${outcome.reason.error.message}\n`) - } - io.exit(outcome.reason?.kind === 'completed' ? 0 : 1) } /** * Mount the one-shot direct driver. * @param ctx - plugin context carrying core services and the launcher-provided exit request. - * @param config - validated task config. + * @param config - validated task and run options. */ export function apply(ctx: Context, config: Config): void { // Read through the global service store, not the property proxy: appExit is @@ -225,5 +391,5 @@ export function apply(ctx: Context, config: Config): void { throw new Error('headless-runner: the launcher must provide ctx.appExit before the tree mounts') } const io: HeadlessIo = { stdout: internals.stdout, stderr: internals.stderr, exit } - void run(ctx, config.task, io).catch((error: unknown) => { fail(io, error) }) + void run(ctx, config, io).catch((error: unknown) => { fail(io, error, config.json === true) }) } diff --git a/packages/bundle/headless/src/json-stream.ts b/packages/bundle/headless/src/json-stream.ts new file mode 100644 index 0000000000..f02583f2ad --- /dev/null +++ b/packages/bundle/headless/src/json-stream.ts @@ -0,0 +1,332 @@ +/** + * `--json` run projection: a bounded, ordered event stream derived from one + * Agent's durable Session events. Every projected event is a commit point: + * text and reasoning come from committed `assistant/message` content, never + * from a live attempt that may still be retried or discarded, so the stream + * never carries content the durable log does not contain. + * @module @deepseek-ai/dsh-headless/json-stream + */ + +import type { Context } from '@deepseek-ai/cordis' +import type { Agent } from '@deepseek-ai/dsh-agent' +import { lastAssistantStreamChunk } from '@deepseek-ai/dsh-llm/assistant-stream' +import type { SessionEvent } from '@deepseek-ai/dsh-session' + +/** Default per-string and per-key cap applied to every bounded projected payload. */ +export const MAX_STRING_BYTES = 8 * 1024 + +/** Default cap on one projected event's serialized bytes, newline included; the terminal `final` is exempt. */ +export const MAX_EVENT_BYTES = 32 * 1024 + +/** Bytes every newline-delimited writer appends after one serialized event. */ +const LINE_TERMINATOR_BYTES = 1 + +/** The stdout sink a projection writes newline-delimited events to. */ +export interface JsonSink { + /** Write one chunk of the event stream. */ + write(chunk: string): unknown +} + +/** Tunables for {@link projectJsonRun}; every field defaults. */ +export interface JsonProjectionOptions { + /** Working directory reported by the opening `session` event. */ + cwd?: string + /** Per-string and per-key byte cap; longer values are truncated and flagged. */ + maxStringBytes?: number +} + +/** The live handle of one `--json` projection. */ +export interface JsonProjection { + /** Write the terminal `final` event carrying the run's answer text. */ + finish(text: string): void + /** Stop observing the Session. */ + dispose(): void +} + +/** Mutable truncation state threaded through one payload bound. */ +interface BoundState { + truncated: boolean +} + +/** Truncate one UTF-8 string to `maxBytes`, dropping a split trailing character. */ +function truncateUtf8(text: string, maxBytes: number): string { + const buffer = Buffer.from(text, 'utf8').subarray(0, maxBytes) + const decoded = buffer.toString('utf8') + return decoded.endsWith('\uFFFD') ? decoded.slice(0, -1) : decoded +} + +/** Cap one object key, flagging the payload when it was cut. */ +function boundKey(key: string, maxBytes: number, state: BoundState): string { + if (Buffer.byteLength(key, 'utf8') <= maxBytes) return key + state.truncated = true + return truncateUtf8(key, maxBytes) +} + +/** Maximum container depth one projected payload keeps before the tail is cut. */ +const MAX_DEPTH = 64 + +/** Recursively cap every string in one JSON-serializable value, keys included. */ +function boundValue(value: unknown, maxBytes: number, state: BoundState, depth = 0): unknown { + if (typeof value === 'string') { + if (Buffer.byteLength(value, 'utf8') <= maxBytes) return value + state.truncated = true + return truncateUtf8(value, maxBytes) + } + if (Array.isArray(value)) { + // A legal but pathologically deep argument would otherwise recurse until + // the stack overflows, and the isolated listener would silently drop the + // event; cut the tail at a depth no real tool schema reaches. + if (depth >= MAX_DEPTH) { + state.truncated = true + return '[truncated: depth]' + } + return value.map(item => boundValue(item, maxBytes, state, depth + 1)) + } + if (value !== null && typeof value === 'object') { + if (depth >= MAX_DEPTH) { + state.truncated = true + return '[truncated: depth]' + } + // A null prototype keeps a literal `__proto__` key as data instead of + // invoking the inherited setter, which would silently drop it. Two keys + // that share a truncated prefix collide last-wins; only a payload naming + // two multi-kilobyte keys can reach that. + const bounded = Object.create(null) as Record + for (const [key, item] of Object.entries(value)) { + bounded[boundKey(key, maxBytes, state)] = boundValue(item, maxBytes, state, depth + 1) + } + return bounded + } + return value +} + +/** + * Bound every string and key in one projected payload, adding `truncated: true` + * when any was cut. {@link boundJsonLine} composes this with the whole-line cap. + * @param event - the event payload to bound. + * @param maxStringBytes - per-string and per-key byte cap. + * @returns a copy with every over-long string and key truncated. + */ +function boundJsonEvent( + event: Record, + maxStringBytes: number = MAX_STRING_BYTES, +): Record { + const state: BoundState = { truncated: false } + const bounded = boundValue(event, maxStringBytes, state) as Record + if (state.truncated) bounded.truncated = true + return bounded +} + +/** + * Serialize one projected payload under both limits: every string and key is + * capped at `maxStringBytes`, and the serialized line at `maxEventBytes`. The + * line cap reserves the newline the writer appends, so the complete record + * stays within it. When the line is still too long, scalar fields survive and + * structured fields are dropped; when even those are too long, only `type` and + * `truncated` remain. + * @param event - the event payload to serialize. + * @param maxStringBytes - per-string and per-key byte cap. + * @param maxEventBytes - cap on the serialized line plus its trailing newline. + * @returns the bounded JSON line, without a trailing newline. + */ +export function boundJsonLine( + event: Record, + maxStringBytes: number = MAX_STRING_BYTES, + maxEventBytes: number = MAX_EVENT_BYTES, +): string { + const limit = maxEventBytes - LINE_TERMINATOR_BYTES + const bounded = boundJsonEvent(event, maxStringBytes) + const line = JSON.stringify(bounded) + if (Buffer.byteLength(line, 'utf8') <= limit) return line + const scalars = Object.create(null) as Record + for (const [key, value] of Object.entries(bounded)) { + if (value === null || typeof value !== 'object') scalars[key] = value + } + scalars.truncated = true + const short = JSON.stringify(scalars) + if (Buffer.byteLength(short, 'utf8') <= limit) return short + return JSON.stringify({ type: bounded.type, truncated: true }) +} + +/** + * Parse raw tool-call arguments as the executor does: empty input is `{}`, + * invalid or non-round-trippable JSON (a non-finite number) stays text. + */ +function parseArguments(raw: string): unknown { + if (raw === '') return {} + const seen = { nonFinite: false } + try { + const parsed = JSON.parse(raw, (_key, value: unknown) => { + // `1e400` is valid JSON but parses to Infinity, which JSON.stringify + // reports as null; keep the raw text rather than misdescribe the call. + if (typeof value === 'number' && !Number.isFinite(value)) seen.nonFinite = true + return value + }) as unknown + return seen.nonFinite ? raw : parsed + } catch { + return raw + } +} + +/** Join the text blocks of a tool result's model-facing content. */ +function resultText(blocks: readonly { type: string; text?: string }[]): string { + return blocks + .filter((block): block is { type: string; text: string } => + block.type === 'text' && typeof block.text === 'string') + .map(block => block.text) + .join('') +} + +/** Provider token accounting for one step, as carried by `assistant/message`. */ +type StepUsage = NonNullable['data']['usage']> + +/** Accumulated usage for one step plus whether every attempt reported a sample. */ +interface StepUsageState { + usage: StepUsage | undefined + /** False once any attempt in the step omitted a usage sample. */ + complete: boolean +} + +/** The usage a provider reported in one attempt's stream, if any. */ +function streamUsage(stream: SessionEvent<'assistant/message'>['data']['stream']): StepUsage | undefined { + return lastAssistantStreamChunk(stream, 'usage')?.usage +} + +/** + * Accumulate one step's usage across its attempts. A retried attempt keeps its + * usage only in its `assistant/attempt` stream, so the committed message alone + * would under-report billed tokens. An attempt that reports no sample makes the + * step total unknowable, so the step omits usage entirely rather than publish a + * partial sum as if it were complete; an optional bucket is summed only when + * every contribution reports it. + * @param state - usage accumulated so far and whether it is still complete. + * @param next - usage reported by the next attempt. + * @returns the updated state. + */ +function addUsage(state: StepUsageState, next: StepUsage | undefined): StepUsageState { + if (next === undefined) return { usage: state.usage, complete: false } + if (state.usage === undefined) return { usage: next, complete: state.complete } + const total = state.usage + const sum = (a: number | undefined, b: number | undefined): number | undefined => + a === undefined || b === undefined ? undefined : a + b + const totalTokens = sum(total.totalTokens, next.totalTokens) + const cacheReadTokens = sum(total.cacheReadTokens, next.cacheReadTokens) + const cacheWriteTokens = sum(total.cacheWriteTokens, next.cacheWriteTokens) + const reasoningTokens = sum(total.reasoningTokens, next.reasoningTokens) + return { + usage: { + inputTokens: total.inputTokens + next.inputTokens, + outputTokens: total.outputTokens + next.outputTokens, + ...totalTokens === undefined ? {} : { totalTokens }, + ...cacheReadTokens === undefined ? {} : { cacheReadTokens }, + ...cacheWriteTokens === undefined ? {} : { cacheWriteTokens }, + ...reasoningTokens === undefined ? {} : { reasoningTokens }, + }, + complete: state.complete, + } +} + +/** + * Project one Agent's run as newline-delimited JSON on `sink`. + * + * The opening `session` event is written before the subscription starts, so a + * caller must invoke this before submitting the task. Text and reasoning are + * emitted only when the step's `assistant/message` commits them, and the + * terminal `final` event carries the same lossless answer the default mode + * prints (it is deliberately not bounded). + * @param ctx - plugin context carrying the live Session feed. + * @param agent - the exact Agent whose events belong to this invocation. + * @param sink - stdout sink receiving one JSON object per line. + * @param options - projection tunables. + * @returns the projection handle that finishes or disposes the stream. + */ +export function projectJsonRun( + ctx: Context, + agent: Agent, + sink: JsonSink, + options: JsonProjectionOptions = {}, +): JsonProjection { + const maxStringBytes = options.maxStringBytes ?? MAX_STRING_BYTES + let disposed = false + let stepUsage: StepUsageState = { usage: undefined, complete: true } + + const write = (event: Record): void => { + sink.write(`${boundJsonLine(event, maxStringBytes)}\n`) + } + + const onSessionEvent = (session: unknown, event: SessionEvent): void => { + if (session !== agent.session) return + switch (event.type) { + case 'turn/start': + write({ type: 'status', phase: 'turn_start', turn: event.data.turn }) + return + case 'step/start': + write({ type: 'status', phase: 'step_start', turn: event.data.turn, step: event.data.step }) + return + case 'assistant/attempt': + // A retried attempt has no committed message; its billed tokens live + // only in the stream, so accumulate them into the step's total. + stepUsage = addUsage(stepUsage, streamUsage(event.data.stream)) + return + case 'assistant/message': + stepUsage = addUsage(stepUsage, event.data.usage ?? streamUsage(event.data.stream)) + for (const block of event.data.message.content) { + if (block.type === 'reasoning') write({ type: 'thinking', text: block.text }) + else if (block.type === 'text') write({ type: 'text', text: block.text }) + } + return + case 'step/end': { + const { usage, complete } = stepUsage + stepUsage = { usage: undefined, complete: true } + write({ + type: 'status', phase: 'step_end', turn: event.data.turn, step: event.data.step, + // A partial sum would read as an exact total, so omit it entirely + // when any attempt in the step failed to report usage. + ...complete && usage !== undefined ? { usage } : {}, + }) + return + } + case 'turn/end': + write({ type: 'status', phase: 'turn_end', turn: event.data.turn, reason: event.data.reason }) + return + case 'tool/call': + write({ + type: 'tool_call', + callId: event.data.callId, + tool: event.data.name, + input: parseArguments(event.data.arguments), + }) + return + case 'tool/result': { + // Compaction replaces older results in the surface; those are history, + // not this run's output, and would otherwise duplicate a callId. + if (event.surfaceOp !== 'append') return + const block = event.data.message.content[0] + write({ + type: 'tool_result', + callId: block.toolCallId, + status: block.isError === true ? 'error' : 'completed', + result: resultText(block.content), + }) + return + } + default: + return + } + } + + write({ type: 'session', sessionId: agent.id, cwd: options.cwd ?? process.cwd() }) + const stopSession = ctx.on('session/event', onSessionEvent) + + return { + finish(text: string): void { + // The answer is the lossless terminal contract, so it is not truncated. + if (disposed) return + sink.write(`${JSON.stringify({ type: 'final', text })}\n`) + }, + dispose(): void { + disposed = true + stopSession() + }, + } +} diff --git a/packages/bundle/headless/src/runner-internals.ts b/packages/bundle/headless/src/runner-internals.ts new file mode 100644 index 0000000000..1411995cd5 --- /dev/null +++ b/packages/bundle/headless/src/runner-internals.ts @@ -0,0 +1,21 @@ +/** + * Process streams the runner reads and writes, kept out of the package entry so + * substituting them in tests adds no public package API. The shape matches the + * runner's own IO carrier structurally. + * @module @deepseek-ai/dsh-headless/runner-internals + */ + +/** The process streams the runner reads and writes; tests substitute captures. */ +export const internals: { + stdout: { write(chunk: string): unknown } + stderr: { write(chunk: string): unknown } + readStdin: () => Promise +} = { + stdout: process.stdout, + stderr: process.stderr, + readStdin: async () => { + const chunks: Buffer[] = [] + for await (const chunk of process.stdin as AsyncIterable) chunks.push(chunk) + return Buffer.concat(chunks).toString('utf8') + }, +} diff --git a/packages/bundle/headless/src/startup-internals.ts b/packages/bundle/headless/src/startup-internals.ts new file mode 100644 index 0000000000..9356ec9c47 --- /dev/null +++ b/packages/bundle/headless/src/startup-internals.ts @@ -0,0 +1,14 @@ +/** + * Process facts the startup provider reads, kept out of the `./startup` entry + * so substituting them in tests adds no public package API. + * @module @deepseek-ai/dsh-headless/startup-internals + */ + +/** Process facts the provider reads; tests substitute them. */ +export const internals: { + stdinIsTty: () => boolean + stdout: { write(chunk: string): unknown } +} = { + stdinIsTty: () => process.stdin.isTTY, + stdout: process.stdout, +} diff --git a/packages/bundle/headless/src/startup.ts b/packages/bundle/headless/src/startup.ts index f1bc01125d..02886388d9 100644 --- a/packages/bundle/headless/src/startup.ts +++ b/packages/bundle/headless/src/startup.ts @@ -1,13 +1,16 @@ /** - * The one-shot app's command-line provider: it parses the task positional and - * `--help`, then publishes {@link HEADLESS_STARTUP_SERVICE}. The runner is an - * ordinary consumer whose lazy config waits for that service. + * The one-shot app's command-line provider: it parses the task positional, + * `--session-id`, `--json`, and `--help`, then publishes + * {@link HEADLESS_STARTUP_SERVICE}. The runner is an ordinary consumer whose + * lazy config waits for that service. * @module @deepseek-ai/dsh-headless/startup */ -import { Command } from 'commander' +import { Command, CommanderError } from 'commander' import type { Context } from '@deepseek-ai/cordis' import { parseCmdline } from '@deepseek-ai/dsh-cmdline' +import { boundJsonLine } from './json-stream.ts' +import { internals } from './startup-internals.ts' /** Stable Cordis plugin name. */ export const name = 'headless-startup' @@ -20,38 +23,99 @@ export const HEADLESS_STARTUP_SERVICE = 'headlessStartup' /** What the runner row reads from {@link HEADLESS_STARTUP_SERVICE}. */ export interface HeadlessStartupValues { - /** The task text this invocation asked for. */ - task: string + /** The task text this invocation asked for; absent when the runner reads stdin. */ + task: string | undefined + /** Exact Session identity to adopt; absent for a fresh random identity. */ + sessionId: string | undefined + /** Whether stdout carries the machine-readable event stream instead of final text. */ + json: boolean } /** - * This app's command: the task positional, its description, and its help text. + * This app's command: the task positional, its options, and its help text. * @returns a fresh program, so one process can parse more than once (tests). */ function headlessCommand(): Command { return new Command() .name('dsh --profile headless') - .description('Answer one task, stream reasoning to stderr, print the final assistant message, and exit.') + .description('Answer one task and exit; the answer goes to stdout and diagnostics to stderr.') .helpOption('-h, --help', 'show this help') - .argument('[task...]', 'the task text; multiple words are joined by spaces') + .option('--json', 'write newline-delimited run events to stdout instead of the final message') + .option('--session-id ', 'adopt the persisted Session with this id; an unknown id is an error') + .argument('[task...]', 'the task text; multiple words are joined by spaces, and `-` reads stdin') .addHelpText('after', ` Examples: - dsh --profile headless "run the tests" answer one task and exit + dsh --profile headless "run the tests" answer one task and exit + echo "run the tests" | dsh --profile headless read the task from stdin + dsh --profile headless --json "run the tests" emit machine-readable run events + dsh --profile headless --session-id session-… "continue" resume an existing Session `) } +/** + * Whether the raw invocation asks for the machine-readable stream. The scan + * stops at `--` and skips a `--session-id` value, so a literal `--json` used as + * an option value or a positional never installs the JSON error override. + * @param argv - the invocation's raw arguments. + * @returns whether `--json` is a real flag of this invocation. + */ +function jsonRequested(argv: readonly string[]): boolean { + for (let index = 0; index < argv.length; index += 1) { + const argument = argv[index] + if (argument === '--') return false + if (argument === '--json') return true + if (argument === '--session-id') index += 1 + } + return false +} + /** * Parse and provide the one-shot task as an ordinary Cordis service. The - * command's action publishes the task; a missing or whitespace-only task is a - * usage error, so on rejection (and on `--help`) nothing is provided. + * command's action publishes the task; a missing task on an interactive stdin + * is a usage error, so on rejection (and on `--help`) nothing is provided. * @param ctx - plugin context carrying the command line. */ export function apply(ctx: Context): void { const program = headlessCommand() + // The raw snapshot decides the JSON contract: Commander rejects a grammar + // error (an unknown option, a missing option value) before the action runs, + // and such a rejection still owes a --json caller the error event. + if (jsonRequested(ctx.get('cmdlineArgs')?.get() ?? [])) { + program.error = (message: string, errorOptions?: Parameters[1]): never => { + // The event message matches the runner's runtime errors, which carry no + // commander `error: ` prefix. + const payload = boundJsonLine({ type: 'error', message: message.replace(/^error: /, '') }) + internals.stdout.write(`${payload}\n`) + // The JSON contract keeps stderr to `dsh:` diagnostics, so commander's + // own print of this message must not run; throwing the same control-flow + // error still leaves through the launcher's exit path. + throw new CommanderError(1, errorOptions?.code ?? 'commander.error', message) + } + } program.action(() => { - const task = program.args.join(' ') - if (task.trim() === '') program.error('error: a task is required, for example: dsh --profile headless "run the tests"') - ctx.provide(HEADLESS_STARTUP_SERVICE, { task } satisfies HeadlessStartupValues) + if (program.args.length > 1 && program.args.includes('-')) { + program.error('error: `-` must be the only task argument') + } + const joined = program.args.join(' ') + if (program.args.length > 0 && joined.trim() === '') { + program.error('error: a task is required, for example: dsh --profile headless "run the tests"') + } + const task = program.args.length === 0 ? undefined : joined + if (task === undefined && internals.stdinIsTty()) { + program.error('error: a task is required, for example: dsh --profile headless "run the tests"') + } + const options = program.opts<{ json?: boolean; sessionId?: string }>() + // A SessionId is opaque, so whitespace is part of the identity: validate + // emptiness on the trimmed value but hand the runner the exact string. + const sessionId = options.sessionId + if (sessionId !== undefined && sessionId.trim() === '') { + program.error('error: --session-id requires a non-empty session id') + } + ctx.provide(HEADLESS_STARTUP_SERVICE, { + task, + sessionId, + json: options.json === true, + } satisfies HeadlessStartupValues) }) parseCmdline(ctx, program) } diff --git a/packages/bundle/headless/tests/headless.spec.ts b/packages/bundle/headless/tests/headless.spec.ts index 6f583fad95..1da176410f 100644 --- a/packages/bundle/headless/tests/headless.spec.ts +++ b/packages/bundle/headless/tests/headless.spec.ts @@ -1,16 +1,26 @@ -/** Direct one-shot Agent driving, durable aggregation, flushing, and exit mapping. */ +/** Direct one-shot Agent driving, exact Session adoption, machine-readable projection, and exit mapping. */ +import { Readable } from 'node:stream' import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' +import { brandString } from '@deepseek-ai/dsh-brand' import AgentRegistry from '@deepseek-ai/dsh-agent' -import type { Agent, AgentHandle, AssistantStreamFrame, CreateAgentOptions } from '@deepseek-ai/dsh-agent' +import type { + Agent, + AgentHandle, + AssistantStreamFrame, + CreateAgentOptions, + ResumeAgentOptions, +} from '@deepseek-ai/dsh-agent' import AgentDefaultModelConfig from '@deepseek-ai/dsh-agent-default-model' -import { LlmAttemptId, createAssistantMessage, type StreamChunk } from '@deepseek-ai/dsh-llm' +import { LlmAttemptId, ToolCallId, createAssistantMessage, createToolResultMessage, type StreamChunk } from '@deepseek-ai/dsh-llm' import SessionStore from '@deepseek-ai/dsh-session' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import type { Session, UserMessage } from '@deepseek-ai/dsh-session' +import type { Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session' +import { SessionQueryError } from '@deepseek-ai/dsh-session-query' import { createInboxStub } from '@deepseek-ai/dsh-agent-loop-testkit' -import { apply, Config, internals } from '../src/index.ts' +import { apply, Config } from '../src/index.ts' +import { internals } from '../src/runner-internals.ts' const originalInternals = { ...internals } afterEach(() => { Object.assign(internals, originalInternals) }) @@ -20,6 +30,33 @@ interface Script { afterPrompt(session: Session, message: UserMessage, agent: Agent): Promise | void } +/** Observation stub returned by the `--session-id` query path. */ +interface ObservationStub { + header: { cwd?: string; origin?: string; parentSession?: string; agentPreset?: string } + events: readonly { type: string; data: unknown }[] + [Symbol.dispose](): void +} + +/** Runner invocation options layered over the scripted Agent factory. */ +interface BenchOptions { + task?: string + useStdin?: boolean + readStdin?: () => Promise + sessionId?: string + json?: boolean + observe?: () => Promise + /** Leave the query service unmounted to exercise the fail-loud path. */ + omitSessionQuery?: boolean + /** Leave the persistence service unmounted to exercise the fail-loud path. */ + omitPersistence?: boolean + /** Register a live Agent under `sessionId` before the runner starts. */ + prelive?: boolean + /** Header facts for that pre-registered live Agent. */ + preliveMeta?: { cwd?: string; origin?: 'subagent'; agentPreset?: string } + /** Run when the runner awaits idle, e.g. to append to the attached log. */ + onWhenIdle?: (agent: Agent) => void +} + const frameStates = new WeakMap; revision: number; index: number }>() function startFrames(agent: Agent, turn = 1, step = 1): void { @@ -73,8 +110,14 @@ function appendTurn( }) } +/** Append the preset-selection event owned by dsh-agent-presets. */ +function selectPreset(session: Session, agentPreset: string): void { + const target = session as unknown as { append(type: string, data: unknown): void } + target.append('agent-preset/selected', { agentPreset }) +} + /** Mount the real registries around a small scripted Agent factory. */ -async function bench(script: Script): Promise<{ +async function bench(script: Script, options: BenchOptions = {}): Promise<{ ctx: Context output(): { out: string; err: string; order: string[] } run(): Promise<{ code: number; out: string; err: string; order: string[] }> @@ -83,42 +126,67 @@ async function bench(script: Script): Promise<{ let out = '' let err = '' const order: string[] = [] + + const mount = async ( + ownerCtx: Context, + session: Session, + createOptions: CreateAgentOptions | ResumeAgentOptions, + ): Promise => { + const inbox = createInboxStub() + let idle = Promise.resolve() + const agent: Agent = { + id: session.id, + options: createOptions.agentOptions ?? {}, + session, + inbox, + status: 'idle', + ctx: ownerCtx, + cancel: () => {}, + runMaintenance: () => Promise.reject(new Error('not used')), + send: () => {}, + followup: (message: UserMessage) => { + agent.inbox.append('next-turn', message) + idle = Promise.resolve().then(() => script.afterPrompt(session, message, agent)) + }, + steer: () => {}, + inject: () => {}, + whenIdle: () => { + options.onWhenIdle?.(agent) + return idle + }, + } + await createOptions.setup?.(ownerCtx, agent) + ctx.agents.register(agent) + return agent + } + await ctx.plugin(SessionStore) await ctx.plugin(SessionProjectionRegistry) await ctx.plugin(AgentRegistry) await ctx.plugin(AgentDefaultModelConfig, { provider: 'test-provider', model: 'test-model' }) ctx.agents.setFactory({ - async createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise { - const session = ctx.sessions.create(options.sessionId, { - ...options.meta === undefined ? {} : { meta: options.meta }, + async createAgent(ownerCtx: Context, createOptions: CreateAgentOptions): Promise { + const session = ctx.sessions.create(createOptions.sessionId, { + ...createOptions.meta === undefined ? {} : { meta: createOptions.meta }, }) - const inbox = createInboxStub() - let idle = Promise.resolve() - const agent: Agent = { - id: session.id, - options: options.agentOptions ?? {}, - session, - inbox, - status: 'idle', - ctx: ownerCtx, - cancel: () => {}, - runMaintenance: () => Promise.reject(new Error('not used')), - send: () => {}, - followup: (message: UserMessage) => { - agent.inbox.append('next-turn', message) - idle = Promise.resolve().then(() => script.afterPrompt(session, message, agent)) - }, - steer: () => {}, - inject: () => {}, - whenIdle: () => idle, - } - await options.setup?.(ownerCtx, agent) script.before?.(session) - ctx.agents.register(agent) + const agent = await mount(ownerCtx, session, createOptions) + return { agent, dispose: () => Promise.resolve() } + }, + async resume(ownerCtx: Context, resumeOptions: ResumeAgentOptions): Promise { + const session = ctx.sessions.get(resumeOptions.resumeSessionId) + if (session === undefined) throw new Error(`no attached Session ${resumeOptions.resumeSessionId}`) + const agent = await mount(ownerCtx, session, resumeOptions) return { agent, dispose: () => Promise.resolve() } }, - resume: () => Promise.reject(new Error('not used')), }) + if (options.omitSessionQuery !== true && (options.sessionId !== undefined || options.observe !== undefined)) { + const observe = options.observe ?? (() => Promise.reject(new SessionQueryError('missing', 'SESSION_QUERY_SESSION_NOT_FOUND'))) + ctx.provide('sessionQuery', { observeSession: () => observe() } as never) + } + if (options.omitPersistence !== true) { + ctx.provide('sessionPersistence', {} as never) + } return { ctx, output: () => ({ out, err, order: [...order] }), @@ -126,10 +194,21 @@ async function bench(script: Script): Promise<{ ctx.on('session/flush', () => { order.push('flush') }) internals.stdout = { write: (chunk: string) => { out += chunk; return true } } internals.stderr = { write: (chunk: string) => { err += chunk; return true } } + if (options.readStdin !== undefined) internals.readStdin = options.readStdin const exited = new Promise((resolve) => { ctx.provide('appExit', (code: number) => { order.push('exit'); resolve(code) }) }) - apply(ctx, { task: 'do the thing' }) + if (options.prelive === true || options.preliveMeta !== undefined) { + await ctx.agents.create({ + sessionId: brandString(options.sessionId ?? 'session-exact'), + meta: { cwd: process.cwd(), ...options.preliveMeta }, + }) + } + apply(ctx, { + ...options.useStdin === true ? {} : { task: options.task ?? 'do the thing' }, + ...options.sessionId === undefined ? {} : { sessionId: options.sessionId }, + ...options.json === undefined ? {} : { json: options.json }, + }) return { code: await exited, out, err, order } }, } @@ -375,6 +454,485 @@ describe('headless runner', () => { await test.ctx.fiber.dispose() }) + it('reads the task from stdin when the invocation omits one', async () => { + const test = await bench({ + afterPrompt(session, message) { appendTurn(session, 1, message, 'stdin answer', true) }, + }, { + useStdin: true, + readStdin: () => Promise.resolve('task from stdin'), + }) + expect(await test.run()).toMatchObject({ code: 0, out: 'stdin answer\n', err: '' }) + await test.ctx.fiber.dispose() + }) + + it('rejects an empty stdin task', async () => { + const test = await bench({ afterPrompt: () => {} }, { + useStdin: true, + readStdin: () => Promise.resolve(' \n'), + }) + expect(await test.run()).toMatchObject({ + code: 1, + err: 'dsh: a task is required, for example: dsh --profile headless "run the tests"\n', + }) + await test.ctx.fiber.dispose() + }) + + it('reads the default process stdin when no override is installed', async () => { + const original = Object.getOwnPropertyDescriptor(process, 'stdin') + Object.defineProperty(process, 'stdin', { + value: Readable.from([Buffer.from('piped'), Buffer.from(' task')]), + configurable: true, + }) + try { + await expect(originalInternals.readStdin()).resolves.toBe('piped task') + } finally { + if (original !== undefined) Object.defineProperty(process, 'stdin', original) + } + }) + + it('treats a bare dash positional as the stdin marker', async () => { + const test = await bench({ + afterPrompt(session, message) { appendTurn(session, 1, message, 'dash answer', true) }, + }, { + task: '-', + readStdin: () => Promise.resolve('piped dash task'), + }) + expect(await test.run()).toMatchObject({ code: 0, out: 'dash answer\n', err: '' }) + await test.ctx.fiber.dispose() + }) + + it('rejects --session-id when the query reports the id missing', async () => { + const seen: string[] = [] + const test = await bench({ + afterPrompt(session, message) { + seen.push(session.id) + appendTurn(session, 1, message, 'created', true) + }, + }, { + sessionId: 'session-exact', + observe: () => Promise.reject(new SessionQueryError('missing', 'SESSION_QUERY_SESSION_NOT_FOUND')), + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('session "session-exact" does not exist; omit --session-id to start a new Session') + expect(result.out).toBe('') + expect(seen).toEqual([]) + await test.ctx.fiber.dispose() + }) + + it('rejects --session-id when persistence is not mounted', async () => { + const test = await bench({ + afterPrompt(session, message) { appendTurn(session, 1, message, 'created', true) }, + }, { + sessionId: 'session-exact', + observe: () => Promise.reject(new SessionQueryError('missing', 'SESSION_QUERY_SESSION_NOT_FOUND')), + omitPersistence: true, + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('requires the sessionPersistence service') + expect(result.out).toBe('') + await test.ctx.fiber.dispose() + }) + + it('rejects adopting a live Session when persistence is not mounted', async () => { + const test = await bench({ + afterPrompt(session, message) { appendTurn(session, 1, message, 'live', true) }, + }, { + sessionId: 'session-exact', + prelive: true, + omitPersistence: true, + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('requires the sessionPersistence service') + expect(result.out).toBe('') + await test.ctx.fiber.dispose() + }) + + it('resumes the persisted Session when the query finds it', async () => { + const test = await bench({ + afterPrompt(session, message) { appendTurn(session, 1, message, 'resumed answer', true) }, + }, { + sessionId: 'session-exact', + observe: () => Promise.resolve({ + header: { cwd: process.cwd(), origin: 'user' }, + events: [], + [Symbol.dispose]() {}, + }), + }) + const session = test.ctx.sessions.create(brandString('session-exact'), { meta: { cwd: process.cwd() } }) + const history = { + role: 'user', content: [{ type: 'text', text: 'earlier' }], source: { kind: 'user' }, id: 'history', + } as UserMessage + appendTurn(session, 0, history, 'earlier answer', true) + const before = session.seq + expect(await test.run()).toMatchObject({ code: 0, out: 'resumed answer\n', err: '' }) + expect(session.seq).toBeGreaterThan(before) + await test.ctx.fiber.dispose() + }) + + it('rejects a persisted Session recorded in another working directory', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + observe: () => Promise.resolve({ + header: { cwd: '/somewhere/else', origin: 'user' }, + events: [], + [Symbol.dispose]() {}, + }), + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('was recorded in "/somewhere/else"') + await test.ctx.fiber.dispose() + }) + + it('rejects a persisted Session created under an agent preset', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + observe: () => Promise.resolve({ + header: { cwd: process.cwd(), agentPreset: 'minimal' }, + events: [], + [Symbol.dispose]() {}, + }), + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('runs under agent preset "minimal"') + await test.ctx.fiber.dispose() + }) + + it('rejects a persisted Session that switched to an agent preset after creation', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + observe: () => Promise.resolve({ + header: { cwd: process.cwd() }, + events: [{ type: 'agent-preset/selected', data: { agentPreset: 'minimal' } }], + [Symbol.dispose]() {}, + }), + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('runs under agent preset "minimal"') + await test.ctx.fiber.dispose() + }) + + it('rejects a persisted Session whose preset record names no preset', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + observe: () => Promise.resolve({ + header: { cwd: process.cwd() }, + events: [{ type: 'agent-preset/selected', data: {} }], + [Symbol.dispose]() {}, + }), + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('malformed agent-preset/selected event') + await test.ctx.fiber.dispose() + }) + + it('rejects a persisted Session that recorded no working directory', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + observe: () => Promise.resolve({ + header: {}, + events: [], + [Symbol.dispose]() {}, + }), + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('recorded no working directory') + await test.ctx.fiber.dispose() + }) + + it('rejects a persisted Session owned by a subagent', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + observe: () => Promise.resolve({ + header: { cwd: process.cwd(), origin: 'subagent' }, + events: [], + [Symbol.dispose]() {}, + }), + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('is a subagent or forked session') + await test.ctx.fiber.dispose() + }) + + it('requires the Session query service for an exact Session identity', async () => { + const test = await bench({ afterPrompt: () => {} }, { sessionId: 'session-exact', omitSessionQuery: true }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('requires the sessionQuery service') + await test.ctx.fiber.dispose() + }) + + it('requires the Session query service even when a live Agent holds the identity', async () => { + const test = await bench({ + afterPrompt(session, message) { appendTurn(session, 1, message, 'live', true) }, + }, { + sessionId: 'session-exact', + prelive: true, + omitSessionQuery: true, + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('requires the sessionQuery service') + expect(result.out).toBe('') + await test.ctx.fiber.dispose() + }) + + it('rejects a whitespace-only session identity from configuration', async () => { + const test = await bench({ afterPrompt: () => {} }, { sessionId: ' ' }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('sessionId must not be blank') + expect(result.out).toBe('') + await test.ctx.fiber.dispose() + }) + + it('refuses a live Agent identity it cannot own exclusively', async () => { + const test = await bench({ + afterPrompt(session, message) { appendTurn(session, 1, message, 'live answer', true) }, + }, { + sessionId: 'session-exact', + prelive: true, + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('is live in this process, so the one-shot runner cannot own an exclusive run interval') + expect(result.out).toBe('') + await test.ctx.fiber.dispose() + }) + + it('rejects a live Agent recorded in another working directory', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + preliveMeta: { cwd: '/somewhere/else' }, + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('was recorded in "/somewhere/else"') + await test.ctx.fiber.dispose() + }) + + it('rejects a live Agent owned by a subagent', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + preliveMeta: { origin: 'subagent' }, + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('is a subagent or forked session') + await test.ctx.fiber.dispose() + }) + + it('rejects a live Agent created under an agent preset', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + preliveMeta: { agentPreset: 'minimal' }, + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('runs under agent preset "minimal"') + await test.ctx.fiber.dispose() + }) + + it('rejects a live Agent that switched to an agent preset while blank', async () => { + const test = await bench({ + before(session) { + const history = { + role: 'user', content: [{ type: 'text', text: 'earlier' }], source: { kind: 'user' }, id: 'history', + } as UserMessage + appendTurn(session, 0, history, 'earlier answer', true) + selectPreset(session, 'minimal') + }, + afterPrompt: () => {}, + }, { + sessionId: 'session-exact', + prelive: true, + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('runs under agent preset "minimal"') + await test.ctx.fiber.dispose() + }) + + it('rejects a preset appended after the observation snapshot was taken', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + observe: () => Promise.resolve({ + header: { cwd: process.cwd() }, + events: [], + [Symbol.dispose]() {}, + }), + }) + const session = test.ctx.sessions.create(brandString('session-exact'), { meta: { cwd: process.cwd() } }) + selectPreset(session, 'minimal') + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('runs under agent preset "minimal"') + await test.ctx.fiber.dispose() + }) + + it('rejects a preset an overlay appends while the runner awaits idle', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + observe: () => Promise.resolve({ + header: { cwd: process.cwd() }, + events: [], + [Symbol.dispose]() {}, + }), + onWhenIdle: (agent) => { selectPreset(agent.session, 'minimal') }, + }) + test.ctx.sessions.create(brandString('session-exact'), { meta: { cwd: process.cwd() } }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('runs under agent preset "minimal"') + expect(result.out).toBe('') + await test.ctx.fiber.dispose() + }) + + it('fails when a live event below the captured Session length cannot be read', async () => { + let capturedLength = 0 + const test = await bench({ + before(session) { + const history = { + role: 'user', content: [{ type: 'text', text: 'earlier' }], source: { kind: 'user' }, id: 'history', + } as UserMessage + appendTurn(session, 0, history, 'earlier answer', true) + capturedLength = session.seq + Object.defineProperty(session, 'eventAt', { value: () => undefined }) + }, + afterPrompt: () => {}, + }, { + sessionId: 'session-exact', + prelive: true, + }) + const result = await test.run() + expect(capturedLength).toBeGreaterThan(0) + expect(result.code).toBe(1) + expect(result.err).toContain(`headless adoption cannot read seq 0 below captured length ${String(capturedLength)}`) + await test.ctx.fiber.dispose() + }) + + it('bounds the error event message in --json mode', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + json: true, + observe: () => Promise.reject(new SessionQueryError('x'.repeat(9 * 1024), 'SESSION_QUERY_CORRUPT_SESSION')), + }) + const result = await test.run() + const event = JSON.parse(result.out.trim()) as { message: string; truncated?: boolean } + expect(event.truncated).toBe(true) + expect(event.message.length).toBe(8 * 1024) + await test.ctx.fiber.dispose() + }) + + it('rejects a persisted Session linked to a parent', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + observe: () => Promise.resolve({ + header: { cwd: process.cwd(), origin: 'user', parentSession: 'parent-1' }, + events: [], + [Symbol.dispose]() {}, + }), + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toContain('is a subagent or forked session') + await test.ctx.fiber.dispose() + }) + + it('propagates a Session query failure that is not a missing log', async () => { + const test = await bench({ afterPrompt: () => {} }, { + sessionId: 'session-exact', + observe: () => Promise.reject(new SessionQueryError('log is corrupt', 'SESSION_QUERY_CORRUPT_SESSION')), + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(result.err).toBe('dsh: log is corrupt\n') + await test.ctx.fiber.dispose() + }) + + it('projects the run as ordered newline-delimited events in --json mode', async () => { + const test = await bench({ + afterPrompt(session, message, agent) { + session.append('turn/start', { turn: 1 }) + session.append('step/start', { turn: 1, step: 1 }) + session.append('user/message', message, { surfaceOp: 'append' }) + startFrames(agent) + // A live attempt that never commits must not reach the projection. + emitChunk(agent, { type: 'reasoning-delta', index: 0, text: 'discarded attempt' }) + emitChunk(agent, { type: 'text-delta', index: 0, text: 'discarded answer' }) + session.append('assistant/message', { + stream: [], + turn: 1, + step: 1, + usage: { inputTokens: 3, outputTokens: 4 }, + message: createAssistantMessage({ + content: [ + { type: 'reasoning', text: 'thinking hard' }, + { type: 'text', text: 'answer' }, + { type: 'tool-call', id: ToolCallId('call-1'), name: 'bash', arguments: '{"command":"ls"}' }, + ], + source: { provider: 'test-provider', model: 'test-model' }, + }), + }, { surfaceOp: 'append' }) + session.append('tool/call', { + turn: 1, step: 1, callId: ToolCallId('call-1'), name: 'bash', arguments: '{"command":"ls"}', + }) + session.append('tool/result', { + turn: 1, + step: 1, + message: createToolResultMessage({ + callId: ToolCallId('call-1'), + content: [{ type: 'text', text: 'a.txt' }], + isError: false, + }), + }, { surfaceOp: 'append' }) + session.append('step/end', { turn: 1, step: 1 }) + session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) + }, + }, { json: true }) + const result = await test.run() + const events = result.out.trim().split('\n').map(line => JSON.parse(line) as Record) + expect(events.map(event => event.type)).toEqual([ + 'session', 'status', 'status', 'thinking', 'text', + 'tool_call', 'tool_result', 'status', 'status', 'final', + ]) + expect(events[0]).toMatchObject({ type: 'session', cwd: process.cwd() }) + expect(typeof events[0]?.sessionId).toBe('string') + expect(events[1]).toMatchObject({ type: 'status', phase: 'turn_start', turn: 1 }) + expect(events[3]).toMatchObject({ type: 'thinking', text: 'thinking hard' }) + expect(events[4]).toMatchObject({ type: 'text', text: 'answer' }) + expect(result.out).not.toContain('discarded') + expect(events[5]).toMatchObject({ type: 'tool_call', callId: 'call-1', tool: 'bash', input: { command: 'ls' } }) + expect(events[6]).toMatchObject({ type: 'tool_result', callId: 'call-1', status: 'completed', result: 'a.txt' }) + expect(events[7]).toMatchObject({ type: 'status', phase: 'step_end', usage: { inputTokens: 3, outputTokens: 4 } }) + expect(events[8]).toMatchObject({ type: 'status', phase: 'turn_end', reason: { kind: 'completed' } }) + expect(events[9]).toMatchObject({ type: 'final', text: 'answer' }) + expect(result.err).toBe('') + expect(result.code).toBe(0) + await test.ctx.fiber.dispose() + }) + + it('reports a direct failure as an error event in --json mode', async () => { + const test = await bench({ afterPrompt: () => {} }, { + useStdin: true, + readStdin: () => Promise.resolve(''), + json: true, + }) + const result = await test.run() + expect(result.code).toBe(1) + expect(JSON.parse(result.out.trim())).toMatchObject({ type: 'error' }) + expect(result.err).toContain('a task is required') + await test.ctx.fiber.dispose() + }) + it('reports a direct Agent creation failure', async () => { const ctx = new Context() let err = '' @@ -442,8 +1000,9 @@ describe('headless runner', () => { expect(() => { apply(ctx, { task: 't' }) }).toThrow('must provide ctx.appExit') }) - it('validates config: the task is required', () => { - expect(() => new Config({} as never)).toThrow() - expect(new Config({ task: 'x' })).toEqual({ task: 'x' }) + it('validates config: the task and run options are optional', () => { + expect(new Config({})).toEqual({}) + expect(new Config({ task: 'x', sessionId: 'session-x', json: true })) + .toEqual({ task: 'x', sessionId: 'session-x', json: true }) }) }) diff --git a/packages/bundle/headless/tests/json-stream.spec.ts b/packages/bundle/headless/tests/json-stream.spec.ts new file mode 100644 index 0000000000..a9332ee894 --- /dev/null +++ b/packages/bundle/headless/tests/json-stream.spec.ts @@ -0,0 +1,421 @@ +/** The `--json` run projection: commit-point emission, ordering, bounding, and disposal. */ + +import { describe, expect, it } from 'vitest' +import type { Context } from '@deepseek-ai/cordis' +import type { Agent } from '@deepseek-ai/dsh-agent' +import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' +import { boundJsonLine, MAX_STRING_BYTES, projectJsonRun, type JsonProjectionOptions } from '../src/json-stream.ts' + +interface ProjectionHarness { + readonly lines: string[] + readonly projection: ReturnType + readonly agent: Agent + readonly session: Session + readonly parsed: () => Record[] + emitSession(event: SessionEvent): void + emitRawSession(session: unknown, event: SessionEvent): void +} + +/** One committed assistant message carrying the given content blocks. */ +function assistantMessage(content: unknown[], usage?: unknown): SessionEvent { + return { + type: 'assistant/message', + data: { + stream: [], + turn: 1, + step: 1, + ...usage === undefined ? {} : { usage }, + message: { + role: 'assistant', + content, + source: { kind: 'model', provider: 'p', model: 'm' }, + }, + }, + } as unknown as SessionEvent +} + +/** One discarded attempt whose stream reports the given usage sample. */ +function attemptWithUsage(usage: unknown): SessionEvent { + return { + type: 'assistant/attempt', + data: { turn: 1, step: 1, stream: [{ type: 'chunk', chunk: { type: 'usage', usage } }] }, + } as unknown as SessionEvent +} + +/** One step boundary event closing the accumulated usage window. */ +function stepEnd(): SessionEvent { + return { type: 'step/end', data: { turn: 1, step: 1 } } as unknown as SessionEvent +} + +/** One tool result event with the given surface placement. */ +function toolResult( + callId: string, + content: unknown[], + surfaceOp: 'append' | { op: 'replace'; startSeq: number; endSeq: number } = 'append', +): SessionEvent { + return { + type: 'tool/result', + surfaceOp, + data: { + turn: 1, + step: 1, + message: { content: [{ type: 'tool-result', toolCallId: callId, content, isError: false }] }, + }, + } as unknown as SessionEvent +} + +/** Drive the projector through a minimal Context and Agent double. */ +function harness( + options: JsonProjectionOptions = {}, + agentId = 'session-1', + cwd: string | null = '/', +): ProjectionHarness { + const lines: string[] = [] + const sessionListeners = new Set<(session: unknown, event: SessionEvent) => void>() + const ctx = { + on(name: string, handler: unknown) { + if (name === 'session/event') sessionListeners.add(handler as never) + return () => { sessionListeners.delete(handler as never) } + }, + } as unknown as Context + const session = {} as Session + const agent = { id: agentId, session } as unknown as Agent + const projection = projectJsonRun(ctx, agent, { + write: (chunk: string) => { lines.push(chunk); return true }, + }, { ...options, ...cwd === null ? {} : { cwd } }) + return { + lines, + projection, + agent, + session, + parsed: () => lines.map(line => JSON.parse(line) as Record), + emitSession: (event) => { for (const listener of sessionListeners) listener(session, event) }, + emitRawSession: (rawSession, event) => { for (const listener of sessionListeners) listener(rawSession, event) }, + } +} + +describe('--json projection', () => { + it('opens with the session event before any observed event', () => { + const test = harness() + expect(test.parsed()).toEqual([{ type: 'session', sessionId: 'session-1', cwd: '/' }]) + }) + + it('defaults the reported cwd and the per-string cap', () => { + const test = harness({}, 'session-1', null) + expect(test.parsed()[0]).toEqual({ type: 'session', sessionId: 'session-1', cwd: process.cwd() }) + test.emitSession(assistantMessage([{ type: 'text', text: 'x'.repeat(9000) }])) + expect(test.parsed()[1]?.truncated).toBe(true) + expect((test.parsed()[1]?.text as string).length).toBe(8 * 1024) + }) + + it('reports turn and step boundaries with the turn-end reason', () => { + const test = harness() + test.emitSession({ type: 'turn/start', data: { turn: 1 } } as unknown as SessionEvent) + test.emitSession({ type: 'step/start', data: { turn: 1, step: 1 } } as unknown as SessionEvent) + test.emitSession({ + type: 'turn/end', + data: { turn: 1, reason: { kind: 'completed' } }, + } as unknown as SessionEvent) + expect(test.parsed().slice(1)).toEqual([ + { type: 'status', phase: 'turn_start', turn: 1 }, + { type: 'status', phase: 'step_start', turn: 1, step: 1 }, + { type: 'status', phase: 'turn_end', turn: 1, reason: { kind: 'completed' } }, + ]) + }) + + it('projects committed reasoning and text in content order', () => { + const test = harness() + test.emitSession(assistantMessage([ + { type: 'reasoning', text: 'think' }, + { type: 'tool-call', id: 'c1', name: 'bash', arguments: '{}' }, + { type: 'text', text: 'answer' }, + ])) + expect(test.parsed().slice(1)).toEqual([ + { type: 'thinking', text: 'think' }, + { type: 'text', text: 'answer' }, + ]) + }) + + it('ignores a discarded attempt so retried content never reaches the stream', () => { + const test = harness() + test.emitSession({ + type: 'assistant/attempt', + data: { turn: 1, step: 1, stream: [] }, + } as unknown as SessionEvent) + test.emitSession({ type: 'session/title', data: { title: 'ignored' } } as unknown as SessionEvent) + expect(test.parsed().map(event => event.type)).toEqual(['session']) + }) + + it('attaches step usage to step_end and omits it when absent', () => { + const test = harness() + test.emitSession({ type: 'step/end', data: { turn: 1, step: 1 } } as unknown as SessionEvent) + test.emitSession(assistantMessage([{ type: 'text', text: 'x' }], { inputTokens: 3, outputTokens: 4 })) + test.emitSession({ type: 'step/end', data: { turn: 1, step: 2 } } as unknown as SessionEvent) + const events = test.parsed() + expect(events[1]).toEqual({ type: 'status', phase: 'step_end', turn: 1, step: 1 }) + expect(events[3]).toEqual({ + type: 'status', phase: 'step_end', turn: 1, step: 2, + usage: { inputTokens: 3, outputTokens: 4 }, + }) + }) + + it('reports tool results as completed or errored and keeps the final event last', () => { + const test = harness() + test.emitSession(toolResult('c1', [{ type: 'text', text: 'a.txt' }])) + test.emitSession({ + type: 'tool/result', + surfaceOp: 'append', + data: { + turn: 1, + step: 1, + message: { + content: [{ + type: 'tool-result', + toolCallId: 'c2', + content: [{ type: 'image' }, { type: 'text' }, { type: 'text', text: 'boom' }], + isError: true, + }], + }, + }, + } as unknown as SessionEvent) + test.projection.finish('done') + const events = test.parsed() + expect(events[1]).toEqual({ type: 'tool_result', callId: 'c1', status: 'completed', result: 'a.txt' }) + expect(events[2]).toEqual({ type: 'tool_result', callId: 'c2', status: 'error', result: 'boom' }) + expect(events.at(-1)).toEqual({ type: 'final', text: 'done' }) + }) + + it('skips a compaction replacement of an older tool result', () => { + const test = harness() + test.emitSession(toolResult('old', [{ type: 'text', text: 'history' }], { op: 'replace', startSeq: 1, endSeq: 2 })) + expect(test.parsed().map(event => event.type)).toEqual(['session']) + }) + + it('keeps non-JSON tool arguments raw and bounds nested values', () => { + const test = harness({ maxStringBytes: 11 }, 's1') + test.emitSession({ + type: 'tool/call', + data: { turn: 1, step: 1, callId: 'raw', name: 'bash', arguments: 'not json' }, + } as unknown as SessionEvent) + test.emitSession({ + type: 'tool/call', + data: { + turn: 1, + step: 1, + callId: 'nested', + name: 'bash', + arguments: JSON.stringify({ items: ['éééééé', null, true], n: 1 }), + }, + } as unknown as SessionEvent) + const events = test.parsed() + expect(events[1]).toEqual({ type: 'tool_call', callId: 'raw', tool: 'bash', input: 'not json' }) + expect(events[2]).toEqual({ + type: 'tool_call', + callId: 'nested', + tool: 'bash', + input: { items: ['ééééé', null, true], n: 1 }, + truncated: true, + }) + }) + + it('keeps raw arguments that JSON cannot round-trip, such as an overflowing number', () => { + const test = harness({}, 's1') + test.emitSession({ + type: 'tool/call', + data: { turn: 1, step: 1, callId: 'inf', name: 'bash', arguments: '{"n":1e400}' }, + } as unknown as SessionEvent) + expect(test.parsed()[1]).toEqual({ type: 'tool_call', callId: 'inf', tool: 'bash', input: '{"n":1e400}' }) + }) + + it('keeps a literal __proto__ key and bounds over-long object keys', () => { + const proto = harness({ maxStringBytes: 32 }, 's1') + proto.emitSession({ + type: 'tool/call', + data: { + turn: 1, + step: 1, + callId: 'p', + name: 'bash', + arguments: '{"__proto__":{"polluted":true},"a":1}', + }, + } as unknown as SessionEvent) + const protoInput = proto.parsed()[1]?.input as Record + expect(Object.keys(protoInput)).toEqual(['__proto__', 'a']) + expect(protoInput['__proto__']).toEqual({ polluted: true }) + + const longKey = harness({ maxStringBytes: 8 }, 's1') + longKey.emitSession({ + type: 'tool/call', + data: { + turn: 1, + step: 1, + callId: 'k', + name: 'bash', + arguments: JSON.stringify({ ['k'.repeat(20)]: 1 }), + }, + } as unknown as SessionEvent) + const longEvent = longKey.parsed()[1] as { input: Record; truncated?: boolean } + expect(Object.keys(longEvent.input)).toEqual(['kkkkkkkk']) + expect(longEvent.truncated).toBe(true) + }) + + it('drops a split trailing multibyte character when truncating', () => { + const test = harness({ maxStringBytes: 5 }, 's1') + test.emitSession(assistantMessage([{ type: 'text', text: 'ééé' }])) + expect(test.parsed()[1]).toEqual({ type: 'text', text: 'éé', truncated: true }) + }) + + it('normalizes empty tool arguments to an empty object like the executor', () => { + const test = harness({}, 's1') + test.emitSession({ + type: 'tool/call', + data: { turn: 1, step: 1, callId: 'empty', name: 'bash', arguments: '' }, + } as unknown as SessionEvent) + expect(test.parsed()[1]).toEqual({ type: 'tool_call', callId: 'empty', tool: 'bash', input: {} }) + }) + + it('bounds one whole event line, dropping structured fields before scalars', () => { + const input = Array.from({ length: 20_000 }, (_, index) => index) + const line = boundJsonLine({ type: 'tool_call', callId: 'c', tool: 'bash', input }, MAX_STRING_BYTES, 1024) + expect(Buffer.byteLength(line, 'utf8')).toBeLessThanOrEqual(1024) + expect(JSON.parse(line)).toEqual({ type: 'tool_call', callId: 'c', tool: 'bash', truncated: true }) + }) + + it('reduces a payload to its type when even its scalars exceed the line cap', () => { + const line = boundJsonLine({ type: 'text', text: 'x'.repeat(2000) }, 4096, 64) + expect(JSON.parse(line)).toEqual({ type: 'text', truncated: true }) + expect(JSON.parse(boundJsonLine({ type: 'text', text: 'ok' }))).toEqual({ type: 'text', text: 'ok' }) + }) + + it('caps an error line even when control characters expand under JSON escaping', () => { + const line = boundJsonLine({ type: 'error', message: '\u0000'.repeat(MAX_STRING_BYTES) }) + expect(Buffer.byteLength(line, 'utf8') + 1).toBeLessThanOrEqual(32 * 1024) + expect(JSON.parse(line)).toEqual({ type: 'error', truncated: true }) + }) + + it('reserves the trailing newline inside the whole-line cap', () => { + // A 64-byte line exactly fills a 64-byte cap; the writer's newline must + // force the scalar fallback rather than write 65 bytes. + const line = boundJsonLine({ type: 'text', text: 'x'.repeat(39) }, 4096, 64) + expect(Buffer.byteLength(line, 'utf8') + 1).toBeLessThanOrEqual(64) + expect(JSON.parse(line)).toEqual({ type: 'text', truncated: true }) + }) + + it('cuts a payload that nests past the depth budget instead of overflowing the stack', () => { + let deepArray: unknown = 'leaf' + for (let level = 0; level < 200; level += 1) deepArray = [deepArray] + expect(JSON.parse(boundJsonLine({ type: 'tool_call', input: deepArray }))) + .toMatchObject({ type: 'tool_call', truncated: true }) + + let deepObject: unknown = 'leaf' + for (let level = 0; level < 200; level += 1) deepObject = { next: deepObject } + expect(JSON.parse(boundJsonLine({ type: 'tool_call', input: deepObject }))) + .toMatchObject({ type: 'tool_call', truncated: true }) + }) + + it('sums retried attempt usage into the step total and drops an unshared bucket', () => { + const test = harness() + test.emitSession(attemptWithUsage({ inputTokens: 10, outputTokens: 2, totalTokens: 12, cacheReadTokens: 4 })) + test.emitSession(assistantMessage( + [{ type: 'text', text: 'ok' }], + { inputTokens: 3, outputTokens: 1, totalTokens: 4 }, + )) + test.emitSession(stepEnd()) + expect(test.parsed().at(-1)).toEqual({ + type: 'status', phase: 'step_end', turn: 1, step: 1, + usage: { inputTokens: 13, outputTokens: 3, totalTokens: 16 }, + }) + }) + + it('sums every optional bucket both attempts report', () => { + const test = harness() + const sample = { + inputTokens: 2, outputTokens: 1, totalTokens: 3, + cacheReadTokens: 1, cacheWriteTokens: 1, reasoningTokens: 1, + } + test.emitSession(attemptWithUsage(sample)) + test.emitSession(attemptWithUsage(sample)) + test.emitSession(stepEnd()) + expect(test.parsed().at(-1)).toMatchObject({ + usage: { + inputTokens: 4, outputTokens: 2, totalTokens: 6, + cacheReadTokens: 2, cacheWriteTokens: 2, reasoningTokens: 2, + }, + }) + }) + + it('drops a bucket only the later attempt reports', () => { + const test = harness() + test.emitSession(attemptWithUsage({ inputTokens: 1, outputTokens: 1 })) + test.emitSession(attemptWithUsage({ inputTokens: 1, outputTokens: 1, cacheWriteTokens: 2 })) + test.emitSession(stepEnd()) + const usage = (test.parsed().at(-1) as { usage: Record }).usage + expect(usage).toEqual({ inputTokens: 2, outputTokens: 2 }) + expect(usage).not.toHaveProperty('cacheWriteTokens') + }) + + it('reads a message usage sample from its stream when the field is absent', () => { + const test = harness() + test.emitSession({ + type: 'assistant/message', + data: { + turn: 1, + step: 1, + stream: [{ type: 'chunk', chunk: { type: 'usage', usage: { inputTokens: 5, outputTokens: 1 } } }], + message: { + role: 'assistant', + content: [{ type: 'text', text: 'hi' }], + source: { kind: 'model', provider: 'p', model: 'm' }, + }, + }, + } as unknown as SessionEvent) + test.emitSession(stepEnd()) + expect(test.parsed().at(-1)).toMatchObject({ usage: { inputTokens: 5, outputTokens: 1 } }) + }) + + it('omits the step total when a later attempt reports no usage sample', () => { + const test = harness() + test.emitSession(attemptWithUsage({ inputTokens: 7, outputTokens: 3 })) + test.emitSession(assistantMessage([{ type: 'text', text: 'ok' }])) + test.emitSession(stepEnd()) + expect(test.parsed().at(-1)).toEqual({ type: 'status', phase: 'step_end', turn: 1, step: 1 }) + }) + + it('omits the step total when an earlier attempt reports no usage sample', () => { + const test = harness() + test.emitSession({ + type: 'assistant/attempt', + data: { turn: 1, step: 1, stream: [] }, + } as unknown as SessionEvent) + test.emitSession(assistantMessage([{ type: 'text', text: 'ok' }], { inputTokens: 1, outputTokens: 1 })) + test.emitSession(stepEnd()) + expect(test.parsed().at(-1)).toEqual({ type: 'status', phase: 'step_end', turn: 1, step: 1 }) + }) + + it('omits the step total when no attempt reports a usage sample', () => { + const test = harness() + test.emitSession(stepEnd()) + expect(test.parsed().at(-1)).toEqual({ type: 'status', phase: 'step_end', turn: 1, step: 1 }) + }) + + it('writes the terminal final event without bounding its answer', () => { + const test = harness({ maxStringBytes: 4 }) + test.projection.finish('abcdefgh') + expect(test.parsed().at(-1)).toEqual({ type: 'final', text: 'abcdefgh' }) + }) + + it('ignores events from another Session and stops writing after dispose', () => { + const test = harness() + test.emitRawSession({}, assistantMessage([{ type: 'text', text: 'foreign' }])) + test.projection.dispose() + test.emitSession(assistantMessage([{ type: 'text', text: 'late' }])) + test.projection.finish('ignored') + expect(test.parsed().map(event => event.type)).toEqual(['session']) + }) + + it('bounds one projected payload through the line writer', () => { + expect(JSON.parse(boundJsonLine({ type: 'error', message: 'x'.repeat(20) }, 8, 4096))) + .toEqual({ type: 'error', message: 'xxxxxxxx', truncated: true }) + expect(JSON.parse(boundJsonLine({ type: 'error', message: 'ok' }))).toEqual({ type: 'error', message: 'ok' }) + }) +}) diff --git a/packages/bundle/headless/tests/startup.spec.ts b/packages/bundle/headless/tests/startup.spec.ts index 29c5a7666b..36965a74b7 100644 --- a/packages/bundle/headless/tests/startup.spec.ts +++ b/packages/bundle/headless/tests/startup.spec.ts @@ -1,7 +1,7 @@ /** * The one-shot app's ordinary command-line provider over a real Loader tree: - * the task becomes injected runner config, while help and usage errors leave - * the consumer pending. + * the task, exact Session identity, and output mode become injected runner + * config, while help and interactive usage errors leave the consumer pending. */ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs' @@ -11,38 +11,53 @@ import { pathToFileURL } from 'node:url' import { Context } from '@deepseek-ai/cordis' import Loader from '@deepseek-ai/cordis-plugin-loader' import Include from '@deepseek-ai/cordis-plugin-include' -import { internals, provideCmdline } from '@deepseek-ai/dsh-cmdline' +import { internals as cmdlineInternals, provideCmdline } from '@deepseek-ai/dsh-cmdline' import { afterEach, describe, expect, it } from 'vitest' -import { apply, HEADLESS_STARTUP_SERVICE, type HeadlessStartupValues } from '../src/startup.ts' +import { + apply, + HEADLESS_STARTUP_SERVICE, + type HeadlessStartupValues, +} from '../src/startup.ts' +import { internals as startupInternals } from '../src/startup-internals.ts' /** What one boot of the fixture tree observed. */ interface Observed { exits: number[] out: string + err: string runnerConfig?: unknown } const disposers: (() => Promise)[] = [] +/** The real process facts captured before any test substitutes them. */ +const originalInternals = { ...startupInternals } + /** Fixture tree roots, removed after their booted tree has been disposed. */ const tempDirs: string[] = [] afterEach(async () => { for (const dispose of disposers.splice(0)) await dispose() for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true }) - internals.stdout = process.stdout - internals.stderr = process.stderr + cmdlineInternals.stdout = process.stdout + cmdlineInternals.stderr = process.stderr + startupInternals.stdinIsTty = () => process.stdin.isTTY + startupInternals.stdout = process.stdout }) /** * Mount the real provider over a runner stand-in. * @param args - the invocation's inner arguments. + * @param options - process facts the provider reads. * @returns the resolved service value and observed runner/process effects. */ -async function bootStartup(args: string[]): Promise<{ task: HeadlessStartupValues | undefined; observed: Observed }> { +async function bootStartup( + args: string[], + options: { stdinIsTty?: boolean } = {}, +): Promise<{ task: HeadlessStartupValues | undefined; observed: Observed }> { const dir = mkdtempSync(join(tmpdir(), 'dsh-headless-startup-')) tempDirs.push(dir) - const observed: Observed = { exits: [], out: '' } + const observed: Observed = { exits: [], out: '', err: '' } writeFileSync(join(dir, 'row.mjs'), 'export function apply(_ctx, config) { globalThis.__headlessStartupObserved.runnerConfig = config }\n') // Loader imports through Node's resolver, so this fixture delegates to the // source-plane plugin already imported by the test. @@ -58,13 +73,26 @@ export const apply = ctx => globalThis.__headlessStartupApply(ctx) ` inject: [${HEADLESS_STARTUP_SERVICE}]`, ' config:', ' task: !!js ctx.headlessStartup.task', + ' sessionId: !!js ctx.headlessStartup.sessionId', + ' json: !!js ctx.headlessStartup.json', '- id: headless-startup', ` name: ${pathToFileURL(join(dir, 'startup.mjs')).href}`, '', ].join('\n')) const observing = { write: (chunk: string) => { observed.out += chunk; return true } } - internals.stdout = observing - internals.stderr = observing + // Commander's own output keeps landing in `out` so existing assertions see + // the full transcript, while `err` isolates what stderr actually carried. + const observingErr = { + write: (chunk: string) => { + observed.out += chunk + observed.err += chunk + return true + }, + } + cmdlineInternals.stdout = observing + cmdlineInternals.stderr = observingErr + startupInternals.stdinIsTty = () => options.stdinIsTty === true + startupInternals.stdout = observing const globals = globalThis as unknown as { __headlessStartupApply: typeof apply __headlessStartupObserved: Observed @@ -88,23 +116,125 @@ export const apply = ctx => globalThis.__headlessStartupApply(ctx) describe('headless command-line provider', () => { it('joins the task positional into the runner config', async () => { const { task, observed } = await bootStartup(['run', 'the', 'tests']) - expect(task).toEqual({ task: 'run the tests' }) - expect(observed.runnerConfig).toEqual({ task: 'run the tests' }) + expect(task).toEqual({ task: 'run the tests', sessionId: undefined, json: false }) + expect(observed.runnerConfig).toMatchObject({ task: 'run the tests', json: false }) expect(observed.exits).toEqual([]) }) - it.each([{ args: [] }, { args: [' '] }])('rejects an invocation with no non-whitespace task ($args)', async ({ args }) => { - const { task, observed } = await bootStartup(args) + it('publishes the machine-readable output mode and the exact Session identity', async () => { + const { task, observed } = await bootStartup(['--json', '--session-id', 'session-exact', 'do', 'it']) + expect(task).toEqual({ task: 'do it', sessionId: 'session-exact', json: true }) + expect(observed.runnerConfig).toMatchObject({ task: 'do it', sessionId: 'session-exact', json: true }) + }) + + it('keeps the stdin marker as the task so the runner reads the pipe', async () => { + const { task } = await bootStartup(['-'], { stdinIsTty: false }) + expect(task).toEqual({ task: '-', sessionId: undefined, json: false }) + }) + + it('defers an absent task to stdin when stdin is not a terminal', async () => { + const { task, observed } = await bootStartup([], { stdinIsTty: false }) + expect(task).toEqual({ task: undefined, sessionId: undefined, json: false }) + expect(observed.runnerConfig).toMatchObject({ json: false }) + }) + + it.each([{ args: [] as string[] }, { args: [' '] }])('rejects an interactive invocation with no task ($args)', async ({ args }) => { + const { task, observed } = await bootStartup(args, { stdinIsTty: true }) expect(observed.out).toContain('a task is required') expect(task).toBeUndefined() expect(observed.runnerConfig).toBeUndefined() expect(observed.exits).toEqual([1]) }) + it('rejects an explicitly empty Session identity', async () => { + const { task, observed } = await bootStartup(['--session-id', '', 'do', 'it']) + expect(observed.out).toContain('--session-id requires a non-empty session id') + expect(task).toBeUndefined() + expect(observed.exits).toEqual([1]) + }) + + it('keeps the caller-provided exact Session identity verbatim', async () => { + const { task } = await bootStartup(['--session-id', ' session-x ', 'do', 'it']) + expect(task).toEqual({ task: 'do it', sessionId: ' session-x ', json: false }) + }) + + it('rejects a lone stdin marker mixed with other task words', async () => { + const { task, observed } = await bootStartup(['-', 'do', 'it']) + expect(observed.out).toContain('`-` must be the only task argument') + expect(task).toBeUndefined() + expect(observed.exits).toEqual([1]) + }) + + it('writes the JSON error event for a --json usage error', async () => { + const { task, observed } = await bootStartup(['--json'], { stdinIsTty: true }) + const first = JSON.parse(observed.out.trim().split('\n')[0] ?? '{}') as { type: string; message: string } + expect(first).toEqual({ + type: 'error', + message: 'a task is required, for example: dsh --profile headless "run the tests"', + }) + expect(task).toBeUndefined() + expect(observed.err).toBe('') + expect(observed.exits).toEqual([1]) + }) + + it('writes the JSON error event for an empty Session identity in --json mode', async () => { + const { observed } = await bootStartup(['--json', '--session-id', '', 'do', 'it']) + const first = JSON.parse(observed.out.trim().split('\n')[0] ?? '{}') as { type: string; message: string } + expect(first.type).toBe('error') + expect(first.message).toContain('--session-id requires a non-empty session id') + expect(observed.err).toBe('') + expect(observed.exits).toEqual([1]) + }) + + it('writes the JSON error event for a commander grammar rejection in --json mode', async () => { + const { task, observed } = await bootStartup(['--json', '--bogus', 'do', 'it']) + const first = JSON.parse(observed.out.trim().split('\n')[0] ?? '{}') as { type: string; message: string } + expect(first).toEqual({ type: 'error', message: "unknown option '--bogus'" }) + expect(task).toBeUndefined() + expect(observed.err).toBe('') + expect(observed.exits).toEqual([1]) + }) + + it('does not install the JSON error override for a --json option value', async () => { + const { observed } = await bootStartup(['--session-id', '--json'], { stdinIsTty: true }) + expect(observed.out).toContain('a task is required') + expect(observed.out).not.toContain('"type":"error"') + expect(observed.exits).toEqual([1]) + }) + + it('does not install the JSON error override for a --json positional after --', async () => { + const { task, observed } = await bootStartup(['--', '--json'], { stdinIsTty: false }) + expect(task).toEqual({ task: '--json', sessionId: undefined, json: false }) + expect(observed.out).not.toContain('"type":"error"') + }) + + it('rejects a blank positional task instead of reading stdin', async () => { + const { task, observed } = await bootStartup([' '], { stdinIsTty: false }) + expect(observed.out).toContain('a task is required') + expect(task).toBeUndefined() + expect(observed.runnerConfig).toBeUndefined() + expect(observed.exits).toEqual([1]) + }) + + it('reports the real process stdin terminal state by default', () => { + const original = Object.getOwnPropertyDescriptor(process, 'stdin') + Object.defineProperty(process, 'stdin', { value: { isTTY: true }, configurable: true }) + try { + expect(originalInternals.stdinIsTty()).toBe(true) + } finally { + if (original !== undefined) Object.defineProperty(process, 'stdin', original) + } + }) + + it('fails loud without the launcher command line and exit request', () => { + expect(() => { apply(new Context()) }).toThrow('the launcher must provide ctx.cmdlineArgs and ctx.appExit') + }) + it('prints its own help and leaves the runner pending', async () => { const { task, observed } = await bootStartup(['--help']) expect(observed.out).toContain('dsh --profile headless') - expect(observed.out).toContain('stream reasoning to stderr') + expect(observed.out).toContain('the answer goes to stdout and diagnostics to stderr') + expect(observed.out).toContain('--session-id') expect(task).toBeUndefined() expect(observed.runnerConfig).toBeUndefined() expect(observed.exits).toEqual([0]) diff --git a/packages/bundle/headless/tsconfig.json b/packages/bundle/headless/tsconfig.json index 0c60d69aa7..b86afc4103 100644 --- a/packages/bundle/headless/tsconfig.json +++ b/packages/bundle/headless/tsconfig.json @@ -31,6 +31,9 @@ }, { "path": "../../boot/cmdline" + }, + { + "path": "../../session-query/session-query" } ] } diff --git a/packages/client/connection/README.i18n.yaml b/packages/client/connection/README.i18n.yaml index 8ed7cbac92..9793a90129 100644 --- a/packages/client/connection/README.i18n.yaml +++ b/packages/client/connection/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/connection/README.md -README.md: 865c894403d2177e15085d0616289cf8950b555d -README.zh.md: d3d0a62035b1770ecc1b9f81aa775dad0c86cb11 +README.md: 606f27f37a80c11c105a1431858efae7b8e9d86a +README.zh.md: d7885ad873fb1d7d82c7a613c8f66bc7356b2542 diff --git a/packages/client/connection/README.md b/packages/client/connection/README.md index 865c894403..606f27f37a 100644 --- a/packages/client/connection/README.md +++ b/packages/client/connection/README.md @@ -27,6 +27,8 @@ The package carries browser-to-Host Remote calls, exact Fetch responses, and con The browser uses HTTP POST for Remote unary calls. API Gateway owns the `/api/remote.mux` WebSocket and its logical streams; shell-owned compositions provide equivalent Remote streams through `connection.rpc.open` without opening a WebSocket. The Host half always provides the carrier-neutral RPC and exact `GET`/`HEAD`/`POST` route registries. When a Web carrier is present it also owns the sole `/api` route, Fetch bridge, browser authentication, and Host/Origin checks; a shell-owned carrier dispatches the shared Fetch handler directly. Each exact route declares buffered or streaming request-body handling before the bridge reads any bytes. Typert Gateway claims generated Remote endpoints, feature packages register non-JSON responses such as Session-log downloads and raw file uploads, and unclaimed requests return 404. Loopback hostname classification remains package-internal to the browser-facing Client state. Browser raw-body transfer is provided by [`dsh-client-file-upload`](../file-upload/README.md). +The browser fixture follows the [Session Controller fork semantics](../../api/session-controller/README.md#use-this-package). + ----- diff --git a/packages/client/connection/README.zh.md b/packages/client/connection/README.zh.md index d3d0a62035..d7885ad873 100644 --- a/packages/client/connection/README.zh.md +++ b/packages/client/connection/README.zh.md @@ -27,6 +27,8 @@ kind: "package-reference" 浏览器通过 HTTP POST 执行 Remote 一元调用;API Gateway 自己拥有 `/api/remote.mux` WebSocket 及其逻辑流。由 shell 持有的组合通过 `connection.rpc.open` 提供等价的 Remote 流,不打开 WebSocket。Host half 始终提供与载体无关的 RPC 注册表和精确 `GET`/`HEAD`/`POST` 路由注册表。存在 Web 载体时,它还持有唯一 `/api` route、Fetch bridge、浏览器认证与 Host/Origin 校验;由 shell 持有的载体则直接分派共享 Fetch handler。每条精确路由会在 bridge 读取任何字节前声明缓冲或流式请求体处理方式。Typert Gateway 认领生成的 Remote endpoint,功能包注册 Session 日志下载、原始文件上传等非 JSON 响应,未认领的请求返回 404。Loopback hostname 判定只供浏览器侧当前页面状态使用,留在包内。浏览器原始请求体传输由 [`dsh-client-file-upload`](../file-upload/README.zh.md) 提供。 +浏览器 fixture 遵循 [Session Controller 的 fork 语义](../../api/session-controller/README.zh.md#use-this-package)。 + ----- diff --git a/packages/client/connection/src/client/fixture.ts b/packages/client/connection/src/client/fixture.ts index a27749f61a..103ca7dcdd 100644 --- a/packages/client/connection/src/client/fixture.ts +++ b/packages/client/connection/src/client/fixture.ts @@ -3249,8 +3249,7 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld { details: { sessionId }, }) } - let cut = boundary.seq + 1 - while (cut < log.length && log[cut]?.type !== 'turn/start') cut++ + const cut = boundary.seq + 1 const child: FixtureSessionSummary = { sessionId: sid(`fx-${nextSession++}`), updatedAt: Date.now(), running: false, blank: false, parentSessionId: sessionId, diff --git a/packages/client/connection/tests/fixture.client.spec.ts b/packages/client/connection/tests/fixture.client.spec.ts index 88f661754c..eff5431592 100644 --- a/packages/client/connection/tests/fixture.client.spec.ts +++ b/packages/client/connection/tests/fixture.client.spec.ts @@ -1635,6 +1635,35 @@ describe('fixture Connection RPC', () => { vi.unstubAllGlobals() }) + it.each(['turn-end', 'omitted', 'past-end'] as const)('forks through the completed turn for a %s anchor', async (anchor) => { + vi.stubGlobal('__fxTiming', (globalThis as Record).__fxTiming) + const { rpc } = createFixtureFaces() + const sessions = createSessionClient(rpc) + const sourceId = sid('fx-alpha') + const initial = await sessions.history({ sessionId: sourceId, maxMessages: 1000 }) + if (!initial.result.ok) throw new Error('source history failed') + const events = historyEvents(initial.result.value.records) + const boundary = events.findLast(event => event.type === 'turn/end') + if (boundary === undefined) throw new Error('source has no completed turn') + expect(initial.result.value.hasMore).toBe(false) + expect((await sessions.rename({ sessionId: sourceId, title: 'Title after the selected turn' })).result.ok).toBe(true) + const source = await sessions.history({ sessionId: sourceId, maxMessages: 1000 }) + if (!source.result.ok) throw new Error('source history after rename failed') + const fork = await rpc.call('/api', 'session/fork', { + args: { request: { + sessionId: sourceId, + ...(anchor === 'omitted' ? {} : { atSeq: anchor === 'turn-end' ? boundary.seq : events.length + 100 }), + } }, + }) + if (!fork.ok) throw new Error('fork failed') + const childId = (fork.value as { sessionId: SessionId }).sessionId + const child = await sessions.history({ sessionId: childId, maxMessages: 1000 }) + if (!child.result.ok) throw new Error('child history failed') + expect(child.result.value.hasMore).toBe(false) + expect(historyEvents(child.result.value.records)).toEqual(events.slice(0, boundary.seq + 1)) + expect(await sessions.history({ sessionId: sourceId, maxMessages: 1000 })).toMatchObject({ result: source.result }) + }) + it('covers the migrated Remote dispatch table', async () => { const rpc = createFixtureConnectionRpc() const sessions = createSessionClient(rpc) diff --git a/packages/client/ui-chat/src/client/chat/ContextInjectionRow.tsx b/packages/client/ui-chat/src/client/chat/ContextInjectionRow.tsx index 37f08dc2f5..c905035119 100644 --- a/packages/client/ui-chat/src/client/chat/ContextInjectionRow.tsx +++ b/packages/client/ui-chat/src/client/chat/ContextInjectionRow.tsx @@ -10,7 +10,7 @@ export interface ContextInjectionRowProps { content: ContextMessageNode['content'] source: ContextMessageNode['source'] /** Role and producer name projected from the durable source. */ - provenance: ContextMessageNode['provenance'] + producer: ContextMessageNode['producer'] /** Producer-declared information form; null renders the opaque body. */ form: ContextMessageNode['form'] /** The owning view's locale seat, passed down as a plain prop. */ @@ -28,7 +28,7 @@ export interface ContextInjectionRowProps { * @param props - Durable content, its projected producer role/name and form, and the locale seat. * @returns A collapsed context row with a bounded, form-specific body. */ -export function ContextInjectionRow({ content, source, provenance, form, t }: ContextInjectionRowProps) { +export function ContextInjectionRow({ content, source, producer, form, t }: ContextInjectionRowProps) { const [open, setOpen] = useState(false) // Resolved rather than declared: a form whose fields are unreadable renders // the opaque body, and the marker must say what the row actually shows. @@ -37,18 +37,18 @@ export function ContextInjectionRow({ content, source, provenance, form, t }: Co return ( : } chevronClassName={css.chevron} - title={t(provenance.role === 'recall' ? 'message.contextRecall' : 'message.contextInjection')} - collapsedContent={provenance.label === null ? undefined : ( + title={t(producer.role === 'recall' ? 'message.contextRecall' : 'message.contextInjection')} + collapsedContent={producer.label === null ? undefined : ( /* ToolRow's separator shape: an aria-hidden dot, so the accessible name stays the two readable parts and the two disclosure rows expose one name shape. A source that names no producer drops the dot with it. */ <> - {provenance.label} + {producer.label} {summary !== null && ( <> diff --git a/packages/client/ui-chat/src/client/chat/MessageItem.tsx b/packages/client/ui-chat/src/client/chat/MessageItem.tsx index 0666fe7115..e1875f5ce2 100644 --- a/packages/client/ui-chat/src/client/chat/MessageItem.tsx +++ b/packages/client/ui-chat/src/client/chat/MessageItem.tsx @@ -344,7 +344,7 @@ export const ContextMessageNodeView = memo(function ContextMessageNodeView({ nod diff --git a/packages/client/ui-chat/src/client/contract/snapshot.ts b/packages/client/ui-chat/src/client/contract/snapshot.ts index 0d02198811..80f8654e7c 100644 --- a/packages/client/ui-chat/src/client/contract/snapshot.ts +++ b/packages/client/ui-chat/src/client/contract/snapshot.ts @@ -5,7 +5,7 @@ import type { ChatConversationViewNode } from './chat-nodes.ts' import type { TurnProcessSpec } from './turn-process.ts' export type { - AssistantBlock, AssistantMessageNode, AssistantProvenanceView, AssistantRequestConfig, + AssistantBlock, AssistantMessageNode, AssistantProviderMetadataView, AssistantRequestConfig, AssistantTiming, CommandNode, CompactionSummaryNode, ContextMessageNode, ConversationNode, ModelRetryNode, PartialAssistant, RunningToolCall, SteeringMessageNode, TodoItem, ToolCallBlock, ToolResultNode, TurnErrorNode, TurnMaxTokensNode, UnknownSurfaceNode, diff --git a/packages/client/ui-chat/src/client/conversation-nodes/event-projection.ts b/packages/client/ui-chat/src/client/conversation-nodes/event-projection.ts index 1695a467fe..5995638194 100644 --- a/packages/client/ui-chat/src/client/conversation-nodes/event-projection.ts +++ b/packages/client/ui-chat/src/client/conversation-nodes/event-projection.ts @@ -2,7 +2,7 @@ import type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm/types' import type { - AssistantBlock, ContextProvenanceView, KnownContextForm, + AssistantBlock, ContextProducerView, KnownContextForm, } from '@deepseek-ai/dsh-client-ui-conversation/client' /* jscpd:ignore-start -- Chat and Trajectory own independent event-to-view projections. */ @@ -57,7 +57,7 @@ export function contextForm(source: unknown): KnownContextForm | null { * @param source - Logged `user/message` source. * @returns Role and label rendered by Chat. */ -export function contextProvenance(source: unknown): ContextProvenanceView { +export function contextProducer(source: unknown): ContextProducerView { const record = asRecord(source) const kind = record === null ? null : readString(record, 'kind') if (record === null || kind === null) return { role: 'inject', label: null } diff --git a/packages/client/ui-chat/src/client/conversation-nodes/message.ts b/packages/client/ui-chat/src/client/conversation-nodes/message.ts index 3cd6538bd5..823bbe3c93 100644 --- a/packages/client/ui-chat/src/client/conversation-nodes/message.ts +++ b/packages/client/ui-chat/src/client/conversation-nodes/message.ts @@ -5,7 +5,7 @@ import type { import { isAppendSurfaceEvent, isReplacementSurfaceEvent } from '@deepseek-ai/dsh-session/surface' import type { InboxState } from './inbox.ts' import { chatNode } from './common.ts' -import { contextForm, contextProvenance } from './event-projection.ts' +import { contextForm, contextProducer } from './event-projection.ts' interface ReferencedUserMessageNode extends UserMessageNode { /** Labels cited by the immediately following session-reference context. */ @@ -59,7 +59,7 @@ export const messageDefinition: ConversationNodeDefinition = { time: event.time, content: event.data.content, source: event.data.source, - provenance: contextProvenance(event.data.source), + producer: contextProducer(event.data.source), form: contextForm(event.data.source), } } diff --git a/packages/client/ui-chat/src/client/index.ts b/packages/client/ui-chat/src/client/index.ts index 450ce00fce..9c2fe3a5d0 100644 --- a/packages/client/ui-chat/src/client/index.ts +++ b/packages/client/ui-chat/src/client/index.ts @@ -14,7 +14,7 @@ export type {} from './conversation-nodes/turn-process.ts' export type {} from './conversation-nodes/turn-tail.ts' export type { - AssistantBlock, AssistantMessageNode, AssistantProvenanceView, AssistantRequestConfig, + AssistantBlock, AssistantMessageNode, AssistantProviderMetadataView, AssistantRequestConfig, AssistantTiming, ChatLocationNodeIndex, ChatNodeProcessSource, ChatNodeSource, ChatNodeStore, ChatSnapshot, ChatTurnNavigationIndex, ChatTurnProcessPresentation, CommandNode, CompactionSummaryNode, ContextMessageNode, ConversationNode, @@ -42,7 +42,7 @@ export type { export type { ChatKey } from './locale.ts' export type { ConversationContext, ConversationContextOriginKind } from './model/conversation-context.ts' export type { - ContextProvenanceView, ContextRole, KnownContextForm, + ContextProducerView, ContextRole, KnownContextForm, } from '@deepseek-ai/dsh-client-ui-conversation/client' export type { ConversationPromptSnapshot, RequestInspectionSnapshot, RequestPromptChange, RequestView, diff --git a/packages/client/ui-chat/tests/chat-branch-tails.client.spec.tsx b/packages/client/ui-chat/tests/chat-branch-tails.client.spec.tsx index d24a0a3446..56ed85a3e9 100644 --- a/packages/client/ui-chat/tests/chat-branch-tails.client.spec.tsx +++ b/packages/client/ui-chat/tests/chat-branch-tails.client.spec.tsx @@ -332,7 +332,7 @@ describe('MessageItem arms', () => { seq: 3, content: [{ type: 'text', text: 'line one\n\nline two' }], source: { kind: 'plugin', plugin: 'fixture', empty: {}, list: [] }, - provenance: { role: 'inject', label: 'fixture' }, + producer: { role: 'inject', label: 'fixture' }, form: null, } as never} />, @@ -373,7 +373,7 @@ describe('MessageItem arms', () => { { action: 'replace', scope: '.\u0000AGENTS.md', path: 'AGENTS.md' }, ], }, - provenance: { role: 'inject', label: 'AGENTS.md, sub/AGENTS.md' }, + producer: { role: 'inject', label: 'AGENTS.md, sub/AGENTS.md' }, form: 'instructions', } as never} />, @@ -401,7 +401,7 @@ describe('MessageItem arms', () => { { action: 'replace', scope: 'b', path: 'old/AGENTS.md' }, ], }, - provenance: { role: 'inject', label: 'new/AGENTS.md, old/AGENTS.md' }, + producer: { role: 'inject', label: 'new/AGENTS.md, old/AGENTS.md' }, form: 'instructions', } as never} />, @@ -422,7 +422,7 @@ describe('MessageItem arms', () => { { type: 'text', text: 'after' }, ], source: null, - provenance: { role: 'inject', label: null }, + producer: { role: 'inject', label: null }, form: null, } as never} />, @@ -444,7 +444,7 @@ describe('MessageItem arms', () => { form: 'catalog', entries: [{ name: 'a-skill', description: 'Does A' }, { name: 'b-skill', description: 'Does B' }], }, - provenance: { role: 'inject', label: 'skill-catalog' }, + producer: { role: 'inject', label: 'skill-catalog' }, form: 'catalog', } as never} />, @@ -468,7 +468,7 @@ describe('MessageItem arms', () => { update: true, entries: [{ name: 'a-skill', description: 'Does A' }], }, - provenance: { role: 'inject', label: 'skill-catalog' }, + producer: { role: 'inject', label: 'skill-catalog' }, form: 'catalog', } as never} />, @@ -490,7 +490,7 @@ describe('MessageItem arms', () => { form: 'catalog', entries: [{ name: 'a-skill', description: 'Does A' }, { name: 'b-skill' }], }, - provenance: { role: 'inject', label: 'skill-catalog' }, + producer: { role: 'inject', label: 'skill-catalog' }, form: 'catalog', } as never} />, @@ -510,7 +510,7 @@ describe('MessageItem arms', () => { seq: 3, content: [{ type: 'text', text: 'instruction prose' }], source: { kind: 'agent-instructions', form: 'instructions', changes: [{ action: 'set' }] }, - provenance: { role: 'inject', label: 'agent-instructions' }, + producer: { role: 'inject', label: 'agent-instructions' }, form: 'instructions', } as never} />, @@ -530,7 +530,7 @@ describe('MessageItem arms', () => { seq: 3, content: [{ type: 'text', text: 'first' }, { type: 'text', text: 'second' }], source: null, - provenance: { role: 'inject', label: null }, + producer: { role: 'inject', label: null }, form: null, } as never} />, @@ -546,7 +546,7 @@ describe('MessageItem arms', () => { seq: 3, content: [{ type: 'text', text: 'short' }], source: { kind: 'plugin', note: 'y'.repeat(21_000) }, - provenance: { role: 'inject', label: 'plugin' }, + producer: { role: 'inject', label: 'plugin' }, form: null, } as never} />, @@ -565,7 +565,7 @@ describe('MessageItem arms', () => { seq: 3, content: [{ type: 'text', text: 'catalog prose' }], source: { kind: 'skill-catalog', form: 'catalog', update: true, entries: [] }, - provenance: { role: 'inject', label: 'skill-catalog' }, + producer: { role: 'inject', label: 'skill-catalog' }, form: 'catalog', } as never} />, @@ -584,7 +584,7 @@ describe('MessageItem arms', () => { seq: 3, content: [{ type: 'text', text: 'catalog prose' }], source: { kind: 'skill-catalog', form: 'catalog', entries: 'not-a-list' }, - provenance: { role: 'inject', label: 'skill-catalog' }, + producer: { role: 'inject', label: 'skill-catalog' }, form: 'catalog', } as never} />, @@ -600,7 +600,7 @@ describe('MessageItem arms', () => { , @@ -617,7 +617,7 @@ describe('MessageItem arms', () => { seq: 3, content: [{ type: 'text', text: 'prose' }, { type: 'future-block', payload: 1 }], source: { kind: 'skill-catalog', form: 'catalog', entries: [{ name: 'a', description: 'b' }] }, - provenance: { role: 'inject', label: 'skill-catalog' }, + producer: { role: 'inject', label: 'skill-catalog' }, form: 'catalog', } as never} />, @@ -635,7 +635,7 @@ describe('MessageItem arms', () => { seq: 3, content: [{ type: 'text', text: 'instruction prose' }], source: { kind: 'agent-instructions', form: 'instructions', changes: [{ action: 'merge', path: 'A.md' }] }, - provenance: { role: 'inject', label: 'agent-instructions' }, + producer: { role: 'inject', label: 'agent-instructions' }, form: 'instructions', } as never} />, @@ -651,7 +651,7 @@ describe('MessageItem arms', () => { , @@ -673,7 +673,7 @@ describe('MessageItem arms', () => { form: 'snapshot', sections: [{ name: 'sandbox:policy', text: 'workspace-write' }, { name: 'workspace', text: '/repo' }], }, - provenance: { role: 'inject', label: '@deepseek-ai/dsh-system-prompt' }, + producer: { role: 'inject', label: '@deepseek-ai/dsh-system-prompt' }, form: 'snapshot', } as never} />, @@ -691,7 +691,7 @@ describe('MessageItem arms', () => { seq: 3, content: [{ type: 'text', text: 'background job bash-1 finished.' }], source: { kind: 'plugin', plugin: 'tool-jobs', form: 'notice', summary: 'bash pnpm test [status: completed]' }, - provenance: { role: 'inject', label: 'tool-jobs' }, + producer: { role: 'inject', label: 'tool-jobs' }, form: 'notice', } as never} />, @@ -706,7 +706,7 @@ describe('MessageItem arms', () => { , @@ -729,7 +729,7 @@ describe('MessageItem arms', () => { const view = render( , ) @@ -745,7 +745,7 @@ describe('MessageItem arms', () => { , @@ -762,7 +762,7 @@ describe('MessageItem arms', () => { seq: 3, content: [{ type: 'text', text: 'child report body' }], source: { kind: 'agent-message', form: 'relay', senderSessionId: 'child-7' }, - provenance: { role: 'inject', label: 'agent-message' }, + producer: { role: 'inject', label: 'agent-message' }, form: 'relay', } as never} />, @@ -789,7 +789,7 @@ describe('MessageItem arms', () => { { label: '修 CI', retainedMessages: 3, omittedMessages: 0, truncated: false }, ], }, - provenance: { role: 'recall', label: '重构 loader, 修 CI' }, + producer: { role: 'recall', label: '重构 loader, 修 CI' }, form: 'recall', } as never} />, diff --git a/packages/client/ui-chat/tests/chat-view.client.spec.tsx b/packages/client/ui-chat/tests/chat-view.client.spec.tsx index c724bf7ba6..4f43dccec5 100644 --- a/packages/client/ui-chat/tests/chat-view.client.spec.tsx +++ b/packages/client/ui-chat/tests/chat-view.client.spec.tsx @@ -150,7 +150,7 @@ const reasoningAssistant = (seq: number, text: string, turn = 1, step = 1): Assi }) const context = (seq: number, text: string, turn?: number): ContextMessageNode & { turn?: number } => ({ kind: 'context', seq, time: seq * 1_000, content: [{ type: 'text', text }], source: null, - provenance: { role: 'inject', label: null }, form: null, + producer: { role: 'inject', label: null }, form: null, ...(turn === undefined ? {} : { turn }), }) const steering = (seq: number, text: string, turn: number): SteeringMessageNode & { turn: number } => ({ @@ -1250,7 +1250,7 @@ describe('ChatView', () => { const nextRetry = { ...retry(3), turn: 2, retry: 2 } const context = { kind: 'context', seq: 4, time: 4_000, content: [], source: null, - provenance: { role: 'inject', label: null }, + producer: { role: 'inject', label: null }, form: null, } as const satisfies ConversationNode const h = makeHarness({ nodes: [user(1, 'try'), retryNode] }, { running: true }) diff --git a/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts b/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts index acb8cc2b7f..3e8983fc46 100644 --- a/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts +++ b/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts @@ -1440,7 +1440,7 @@ describe('built-in conversation node Definitions', () => { expect(node(snapshot(value), 'context')?.data).toMatchObject({ kind: 'context', - provenance: { role: 'inject', label: 'demo-skill' }, + producer: { role: 'inject', label: 'demo-skill' }, form: 'instructions', }) }) diff --git a/packages/client/ui-chat/tests/event-projection.client.spec.ts b/packages/client/ui-chat/tests/event-projection.client.spec.ts index aa333c4cba..f1818da09c 100644 Binary files a/packages/client/ui-chat/tests/event-projection.client.spec.ts and b/packages/client/ui-chat/tests/event-projection.client.spec.ts differ diff --git a/packages/client/ui-conversation/src/client/contract/context-provenance.ts b/packages/client/ui-conversation/src/client/contract/context-producer.ts similarity index 96% rename from packages/client/ui-conversation/src/client/contract/context-provenance.ts rename to packages/client/ui-conversation/src/client/contract/context-producer.ts index db732bf59c..567ce167f6 100644 --- a/packages/client/ui-conversation/src/client/contract/context-provenance.ts +++ b/packages/client/ui-conversation/src/client/contract/context-producer.ts @@ -11,7 +11,7 @@ export type ContextRole = 'inject' | 'recall' /** Role and producer name presented for one logged non-user message. */ -export interface ContextProvenanceView { +export interface ContextProducerView { /** The role this context plays in the model-facing conversation. */ role: ContextRole /** diff --git a/packages/client/ui-conversation/src/client/contract/records.ts b/packages/client/ui-conversation/src/client/contract/records.ts index 53230a1240..9da0900192 100644 --- a/packages/client/ui-conversation/src/client/contract/records.ts +++ b/packages/client/ui-conversation/src/client/contract/records.ts @@ -8,7 +8,7 @@ import type { ContentBlock } from '@deepseek-ai/dsh-llm/types' import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment' import type { LlmRetryEventData } from '@deepseek-ai/dsh-llm-retry/types' import type { TodoItem } from '@deepseek-ai/dsh-tool-todo/client' -import type { ContextProvenanceView, KnownContextForm } from './context-provenance.ts' +import type { ContextProducerView, KnownContextForm } from './context-producer.ts' export type { TodoItem } /** Request configuration recorded for one provider call. */ @@ -24,7 +24,7 @@ export interface AssistantRequestConfig { } /** Stable provider/model identity reported for one completed request. */ -export interface AssistantProvenanceView { +export interface AssistantProviderMetadataView { provider: string model: string } @@ -73,7 +73,7 @@ export interface AssistantMessageNode { step: number blocks: readonly AssistantBlock[] usage?: unknown - provenance?: AssistantProvenanceView + providerMetadata?: AssistantProviderMetadataView requestConfig?: AssistantRequestConfig /** Timing derived from the recorded step/chunk/message event sequence. */ timing?: AssistantTiming @@ -104,7 +104,7 @@ export interface ContextMessageNode { content: readonly ContentBlock[] source: unknown /** Role and producer name projected from `source` by the target. */ - provenance: ContextProvenanceView + producer: ContextProducerView /** Producer-declared information form supported by the target; null presents as opaque. */ form: KnownContextForm | null } diff --git a/packages/client/ui-conversation/src/client/contract/request-inspection.ts b/packages/client/ui-conversation/src/client/contract/request-inspection.ts index a59b803459..3f0d785b8e 100644 --- a/packages/client/ui-conversation/src/client/contract/request-inspection.ts +++ b/packages/client/ui-conversation/src/client/contract/request-inspection.ts @@ -1,11 +1,11 @@ import type { ContentBlock, ToolSchema } from '@deepseek-ai/dsh-llm/types' import type { SessionEvent } from '@deepseek-ai/dsh-session/types' import type { - AssistantProvenanceView, AssistantRequestConfig, + AssistantProviderMetadataView, AssistantRequestConfig, } from './records.ts' export type { - AssistantProvenanceView, AssistantRequestConfig, + AssistantProviderMetadataView, AssistantRequestConfig, } from './records.ts' /** @@ -133,7 +133,7 @@ interface RequestViewBase { error?: string /** Stable provider code for localized presentation of known failures. */ errorCode?: string - provenance?: AssistantProvenanceView + providerMetadata?: AssistantProviderMetadataView requestConfig?: AssistantRequestConfig usage?: unknown /** Assistant message or compaction summary sequence produced by this request. */ diff --git a/packages/client/ui-conversation/src/client/index.ts b/packages/client/ui-conversation/src/client/index.ts index 52c801ae4d..2b94139936 100644 --- a/packages/client/ui-conversation/src/client/index.ts +++ b/packages/client/ui-conversation/src/client/index.ts @@ -21,15 +21,15 @@ export type { ConversationPhase, ConversationSnapshot, } from './contract/snapshot.ts' export type { - AssistantBlock, AssistantMessageNode, AssistantProvenanceView, AssistantRequestConfig, + AssistantBlock, AssistantMessageNode, AssistantProviderMetadataView, AssistantRequestConfig, AssistantTiming, CommandNode, CompactionSummaryNode, ContextMessageNode, ConversationNode, ModelRetryNode, PartialAssistant, RunningToolCall, SteeringMessageNode, TodoItem, ToolCallBlock, ToolResultNode, TurnErrorNode, TurnMaxTokensNode, UnknownSurfaceNode, UserMessageNode, } from './contract/records.ts' export type { - ContextProvenanceView, ContextRole, KnownContextForm, -} from './contract/context-provenance.ts' + ContextProducerView, ContextRole, KnownContextForm, +} from './contract/context-producer.ts' export type { ConversationPromptSnapshot, RequestInspectionSnapshot, RequestPromptChange, RequestPromptInspection, RequestPromptInspector, RequestView, SystemPromptNode, diff --git a/packages/client/ui-conversation/src/client/skeleton/ConversationContent.tsx b/packages/client/ui-conversation/src/client/skeleton/ConversationContent.tsx new file mode 100644 index 0000000000..d1a888ca31 --- /dev/null +++ b/packages/client/ui-conversation/src/client/skeleton/ConversationContent.tsx @@ -0,0 +1,285 @@ +import { useCallback, useEffect, useRef, useState } from 'react' +import clsx from 'clsx' +import type { SessionSnapshot } from '@deepseek-ai/dsh-api-session-controller/client' +import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types' +import type { ConversationSlotProps, InputZone } from '../contract/slots.ts' +import { HeroShell, WorkspaceChip, workspaceLabel } from './EmptyHero.tsx' +import css from './ConversationRoot.module.css' + +type ConversationContentProps = Omit & { + session: SessionSnapshot | undefined + phase: 'settling' | 'hero' | 'active' + hero: boolean + onHandleStart: () => number + onHandleDrag: (width: number) => void + onHandleCommit: (width: number) => void + onHandleEnd: () => void +} + +/** One transcript width handle: pointer capture + rAF-throttled symmetric + * resize (both sides write the one centered width, so outward travel widens + * by 2× the pointer distance). pointermove publishes the pointer's Y as a CSS + * variable so the glow indicator rides it. Mirrors ui-layout AppFrame's + * DragHandle capture model. */ +function WidthHandle(props: { + side: 'left' | 'right' + onStart: () => number + onDrag: (width: number) => void + onCommit: (width: number) => void + onEnd: () => void +}) { + const [dragging, setDragging] = useState(false) + const base = useRef(0) + const origin = useRef(0) + const latest = useRef(0) + const frame = useRef(null) + const callbacks = useRef(props) + callbacks.current = props + + const outwardWidth = () => { + const dx = latest.current - origin.current + const outward = callbacks.current.side === 'right' ? dx : -dx + return base.current + outward * 2 + } + const cancelFrame = () => { + if (frame.current !== null) { cancelAnimationFrame(frame.current); frame.current = null } + } + const onPointerDown = useCallback((e: React.PointerEvent) => { + e.preventDefault() + e.currentTarget.setPointerCapture(e.pointerId) + origin.current = e.clientX + latest.current = e.clientX + base.current = callbacks.current.onStart() + setDragging(true) + }, []) + const onPointerMove = useCallback((e: React.PointerEvent) => { + const box = e.currentTarget.getBoundingClientRect() + e.currentTarget.style.setProperty('--dsh-width-handle-pointer-y', `${e.clientY - box.top}px`) + if (!e.currentTarget.hasPointerCapture(e.pointerId)) return + latest.current = e.clientX + frame.current ??= requestAnimationFrame(() => { + frame.current = null + callbacks.current.onDrag(outwardWidth()) + }) + }, []) + const onPointerUp = useCallback((e: React.PointerEvent) => { + if (!e.currentTarget.hasPointerCapture(e.pointerId)) return + e.currentTarget.releasePointerCapture(e.pointerId) + cancelFrame() + latest.current = e.clientX + // Only a gesture with actual travel commits: a press-and-release on a + // window-clamped width must not overwrite the wider stored preference + // with the clamped display value. + if (latest.current !== origin.current) callbacks.current.onCommit(outwardWidth()) + setDragging(false) + callbacks.current.onEnd() + }, []) + // Releasing the button outside the window delivers pointercancel (or drops + // the capture silently) instead of pointerup; without this the glow's + // data-dragging state sticks on. The gesture is abandoned uncommitted — + // onEnd republishes the stored preference. releasePointerCapture inside + // onPointerUp also fires lostpointercapture, so this runs (idempotently) + // after every normal drag end too; keep both paths. + const onPointerCancel = useCallback(() => { + cancelFrame() + setDragging(false) + callbacks.current.onEnd() + }, []) + + return ( +
+ ) +} + +/** + * Render the existing Conversation body, Composer, and width handles. + * @param props - original Conversation seats plus MainPanel-derived phase and width callbacks. + * @returns the unchanged Conversation body subtree. + */ +export function ConversationContent({ + sessionId, session, phase, hero, useSessions, useSessionPendingInteraction, + useWorkspaces, useInput, useComposerBlock, renderSlot, renderSlotChain, + selectWorkspace, t, onHandleStart, onHandleDrag, onHandleCommit, onHandleEnd, +}: ConversationContentProps) { + const pendingInteraction = useSessionPendingInteraction(snapshot => + sessionId === undefined ? undefined : snapshot.get(sessionId)) + const inputState = useInput(s => s) + const cwd = useSessions(s => sessionId === undefined ? undefined : s.byId[sessionId]?.cwd) + const workspaces = useWorkspaces(s => s) + // A plugin this package cannot import (ui-model-selection) says this session cannot + // send; its reason is already localized by whoever raised it. + const composerBlock = useComposerBlock(block => block) + + const [pickerOpen, setPickerOpen] = useState(false) + const [pendingWorkspaceId, setPendingWorkspaceId] = useState() + const pickerAnchor = useRef(null) + + // Publishes the two live measurements floating View chrome reads off the + // scroll body: the seat's height as --dsh-composer-height, so controls clear + // the composer as it grows, and the scrollport's own height as + // --dsh-conversation-viewport-height, so a control can sit in the band the + // seat leaves visible. Callback ref, not an effect; stable identity prevents + // observer churn while the first blank session fills the resident body + // outlet. + const seatObserver = useRef(null) + const seatResizeRef = useCallback((seat: HTMLDivElement | null): void => { + seatObserver.current?.disconnect() + seatObserver.current = null + const scroller = seat?.parentElement ?? null + if (seat === null || scroller === null) return + seatObserver.current = new ResizeObserver(() => { + scroller.style.setProperty('--dsh-composer-height', `${seat.offsetHeight}px`) + scroller.style.setProperty( + '--dsh-conversation-viewport-height', + `${scroller.clientHeight}px`, + ) + }) + seatObserver.current.observe(seat) + seatObserver.current.observe(scroller) + }, []) + + const sessionWorkspace = sessionId === undefined + ? undefined + : workspaces.items.find(workspace => workspace.sessionIds.includes(sessionId)) + const pendingWorkspace = workspaces.items.find( + workspace => workspace.workspaceId === pendingWorkspaceId, + ) + + // Clear the pending pick once the session lands in it, or when the picked + // workspace disappears from a ready list (deleted from the sidebar). + useEffect(() => { + if (pendingWorkspaceId === undefined) return + if (sessionWorkspace?.workspaceId === pendingWorkspaceId + || (workspaces.phase === 'ready' && pendingWorkspace === undefined)) { + setPendingWorkspaceId(undefined) + } + }, [pendingWorkspaceId, sessionWorkspace?.workspaceId, workspaces.phase, pendingWorkspace]) + + const zone: InputZone | undefined = + session === undefined || inputState === undefined ? undefined : { session, input: inputState } + + // The chip is a selector; label resolution walks the flow top-down: + // 1. a just-picked workspace (pending) → its title; + // 2. cold start, no session yet → placeholder ("Choose workspace"); + // 3. the blank session's workspace is in the list → its title; + // 4. list still loading → cwd folder name bridges so the title does not + // flash on refresh (empty cwd → placeholder); + // 5. list ready but no owning workspace (deleted from the sidebar) → + // placeholder, never the deleted folder's name via cwd. + const chipTitle = pendingWorkspace?.title + ?? (sessionId === undefined + ? undefined + : sessionWorkspace?.title + ?? (workspaces.phase === 'ready' || cwd === undefined || cwd === '' + ? undefined + : workspaceLabel(cwd))) + + const heroWorkspaceRow = ( +
+ { setPickerOpen(open => !open) }} + t={t} + /> + {renderSlot('conversation.hero.workspace', { + open: pickerOpen, + anchorRef: pickerAnchor, + selectedId: pendingWorkspaceId ?? sessionWorkspace?.workspaceId, + onPick: (workspaceId) => { + setPickerOpen(false) + setPendingWorkspaceId(workspaceId) + void selectWorkspace(workspaceId).catch(() => { + setPendingWorkspaceId(current => current === workspaceId ? undefined : current) + }) + }, + onClose: () => { setPickerOpen(false) }, + })} + {renderSlot('conversation.hero.agentPreset', {})} +
+ ) + + // The placeholder chip ("Choose workspace") and the Workspace-trigger input travel + // together: no workspace picked yet (cold start, no session at all), or a + // blank session whose workspace vanished (deleted from the sidebar). The + // bar is ONE session-maybe slot rendered unconditionally — inert is a prop, + // not a different tree, so the textarea DOM survives the transition. + const inert = sessionId === undefined || (hero && chipTitle === undefined) + // A raised block is the same inert posture with the blocker's own reason: + // one disabled textarea, never a second tree. The no-workspace state wins + // when both hold — picking a workspace is the earlier prerequisite. + const blocked = !inert && composerBlock !== undefined + const inputBar = renderSlot('conversation.composer.bar', { + variant: hero ? 'hero' : 'composer', + ...(inert + ? { + disabled: true, + placeholder: t('placeholder.workspace'), + workspacePickerOpen: pickerOpen, + onRequestWorkspace: () => { setPickerOpen(true) }, + } + : blocked + // `blocked`, not `disabled`: the bar refuses input either way, but a + // block keeps the model seat live because choosing a model is how the + // user clears it. + ? { blocked: composerBlock, placeholder: composerBlock.reason } + : hero ? { placeholder: t('placeholder.hero') } : {}), + }) + + const composerBar = ( +
+ {hero && } + {hero && heroWorkspaceRow} + {zone !== undefined && renderSlot('conversation.input.dock', zone)} + {inputBar} +
+ ) + + const composer = renderSlotChain( + 'conversation.composer', + { sessionId, session, pendingInteraction }, + { fallback: composerBar, fallbackOnly: sessionId === undefined, overlay: true }, + ) + + // Sticky wraps the whole chain output (fallback + elected overlay), not + // only `.composerStack`: overlay:true renders those as siblings, and sticky + // on the fallback alone would leave a business-owned takeover at the content + // end off-screen when the user is not pinned to the floor. + const composerSeat = ( +
+ {composer} +
+ ) + + return ( +
+
+ {sessionId === undefined ? null : renderSlot('conversation.session', {})} + {composerSeat} +
+ {/* Width handles only while a transcript is on screen; the hero has no + content column to size. */} + {phase === 'active' && (['left', 'right'] as const).map(side => ( + + ))} +
+ ) +} diff --git a/packages/client/ui-conversation/src/client/skeleton/ConversationMainPanel.tsx b/packages/client/ui-conversation/src/client/skeleton/ConversationMainPanel.tsx new file mode 100644 index 0000000000..e6a91eff58 --- /dev/null +++ b/packages/client/ui-conversation/src/client/skeleton/ConversationMainPanel.tsx @@ -0,0 +1,146 @@ +import { useCallback, useRef } from 'react' +import type { ConversationSlotProps } from '../contract/slots.ts' +import { conversationPhase } from '../contract/snapshot.ts' +import { ConversationContent } from './ConversationContent.tsx' +import css from './ConversationRoot.module.css' + +/** localStorage key for the dragged transcript width preference (px). */ +const WIDTH_PREF_KEY = 'dsh.conversation.contentWidth' +/** Floor for a dragged content width; matches the layout center-column minimum. */ +const CONTENT_MIN = 640 +/** Column budget the content must leave free: 88px per side keeps the width + * handles fully placeable (24px inset + 40px strip + 24px safe zone) — a + * larger dragged width would push its own handles off the column and leave no + * way to drag back. */ +const CONTENT_EDGE_BUDGET = 176 + +/** Reads the persisted width preference; durable-storage boundary, so a + * missing or corrupt value resolves to "no preference". + * @returns the stored width in px, or null when unset or invalid. */ +function readWidthPreference(): number | null { + const raw = localStorage.getItem(WIDTH_PREF_KEY) + if (raw === null) return null + const value = Number(raw) + return Number.isFinite(value) && value > 0 ? value : null +} + +/** Resolves the content width the CSS axis would show for a column width. + * @param columnWidth - the conversation column's rendered width in px. + * @param preference - the dragged preference, or null for the adaptive clamp. + * @returns the resolved content width in px (mirrors the CSS clamp). */ +function resolveContentWidth(columnWidth: number, preference: number | null): number { + const max = Math.max(CONTENT_MIN, columnWidth - CONTENT_EDGE_BUDGET) + if (preference !== null) return Math.min(Math.max(preference, CONTENT_MIN), max) + return Math.max(680, Math.min(columnWidth * 0.64, 920)) +} + +/** + * Render the existing main Conversation frame around the extracted content. + * @param props - the original `main.conversation` Slot props. + * @returns the unchanged root, Header, content, and width-control subtree. + */ +export function ConversationMainPanel(props: ConversationSlotProps) { + const { sessionId, useSession, useSessions, useConversation, renderSlot } = props + const session = useSession(s => s) + const conversation = useConversation(s => s) + const shellPhase = session === undefined || conversation === undefined + ? 'blank' + : conversationPhase(session, conversation) + const openState = session?.openState + const summaryBlank = useSessions(s => sessionId === undefined ? undefined : s.byId[sessionId]?.blank) + + // Publishes the column's live width as --dsh-conversation-column-width so + // the shared width axis can adapt (see the .root CSS), and re-clamps a + // dragged preference against the shrunken column WITHOUT rewriting the + // stored preference — widening the window restores it (the AppFrame + // sidebar-drag rule). Same callback-ref pattern as the seat observer. + const rootEl = useRef(null) + const rootObserver = useRef(null) + const publishWidths = useCallback((root: HTMLDivElement): void => { + const column = root.offsetWidth + root.style.setProperty('--dsh-conversation-column-width', `${column}px`) + const preference = readWidthPreference() + if (preference === null) { + root.style.removeProperty('--dsh-chat-user-width') + } else { + root.style.setProperty('--dsh-chat-user-width', `${resolveContentWidth(column, preference)}px`) + } + }, []) + const rootResizeRef = useCallback((root: HTMLDivElement | null): void => { + rootObserver.current?.disconnect() + rootObserver.current = null + rootEl.current = root + if (root === null) return + rootObserver.current = new ResizeObserver(() => { publishWidths(root) }) + rootObserver.current.observe(root) + publishWidths(root) + }, [publishWidths]) + + // Drag plumbing for the two width handles: onStart snapshots the resolved + // width (grabbing a clamped column must not jump back to the raw stored + // preference), onDrag publishes only the live clamped style, onCommit + // persists the width of a gesture that actually travelled, and onEnd + // republishes from storage — an uncommitted press leaves the stored + // preference untouched. + const onHandleStart = useCallback((): number => { + const root = rootEl.current + /* v8 ignore next -- handles render inside the root, so the ref is always attached. */ + if (root === null) return 680 + return resolveContentWidth(root.offsetWidth, readWidthPreference()) + }, []) + const onHandleDrag = useCallback((width: number): void => { + const root = rootEl.current + /* v8 ignore next -- handles render inside the root, so the ref is always attached. */ + if (root === null) return + const clamped = resolveContentWidth(root.offsetWidth, width) + root.style.setProperty('--dsh-chat-user-width', `${clamped}px`) + }, []) + const onHandleCommit = useCallback((width: number): void => { + const root = rootEl.current + /* v8 ignore next -- handles render inside the root, so the ref is always attached. */ + if (root === null) return + localStorage.setItem(WIDTH_PREF_KEY, `${resolveContentWidth(root.offsetWidth, width)}`) + }, []) + const onHandleEnd = useCallback((): void => { + const root = rootEl.current + if (root !== null) publishWidths(root) + }, [publishWidths]) + + // While a session is still replaying (loading + blank) the hero/docked + // choice is unknowable — render the composer hidden instead of flashing + // the centered hero and snapping to the docked bar (or vice versa). + // Exemption: a session the list summary already proves blank can only + // land on the hero, so hiding would blank the column for the whole + // history round-trip (the startup auto-selection flash) for nothing. + // The exemption is deliberately open-state-wide, not loading-only: a + // summary-blank session is the hero before its open starts (`cold`) and + // after one fails (`error`) for the same reason — there is no history. + // A restored continuable subagent also stays settled until its eagerly + // loaded parent catalog establishes availability. This keeps the composer + // hidden instead of briefly rendering the parent-offline takeover. + const parentAvailabilityPending = session?.subagent?.address.mode === 'continuable' + && session.subagent.parentAvailable === undefined + const settling = sessionId !== undefined && ( + (shellPhase === 'blank' && openState === 'loading' && summaryBlank !== true) + || parentAvailabilityPending + ) + const hero = sessionId === undefined + || (shellPhase === 'blank' && (openState === 'open' || summaryBlank === true)) + const phase = settling ? 'settling' : hero ? 'hero' : 'active' + + return ( +
+ {sessionId === undefined ? null : renderSlot('conversation.session.header', {})} + +
+ ) +} diff --git a/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx b/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx index dfb515bde6..c57fe69936 100644 --- a/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx @@ -2,394 +2,12 @@ // chain, AND the composer bar (session-maybe slot) stay mounted across // no-session/session transitions — the bar renders inert via owner props. -import { useCallback, useEffect, useRef, useState } from 'react' -import clsx from 'clsx' -import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types' -import type { ConversationSlotProps, InputZone } from '../contract/slots.ts' -import { conversationPhase } from '../contract/snapshot.ts' -import { HeroShell, WorkspaceChip, workspaceLabel } from './EmptyHero.tsx' -import css from './ConversationRoot.module.css' +import type { ConversationSlotProps } from '../contract/slots.ts' +import { ConversationMainPanel } from './ConversationMainPanel.tsx' /** Full props composed from the slot contract. */ export type ConversationRootProps = ConversationSlotProps -/** localStorage key for the dragged transcript width preference (px). */ -const WIDTH_PREF_KEY = 'dsh.conversation.contentWidth' -/** Floor for a dragged content width; matches the layout center-column minimum. */ -const CONTENT_MIN = 640 -/** Column budget the content must leave free: 88px per side keeps the width - * handles fully placeable (24px inset + 40px strip + 24px safe zone) — a - * larger dragged width would push its own handles off the column and leave no - * way to drag back. */ -const CONTENT_EDGE_BUDGET = 176 - -/** Reads the persisted width preference; durable-storage boundary, so a - * missing or corrupt value resolves to "no preference". - * @returns the stored width in px, or null when unset or invalid. */ -function readWidthPreference(): number | null { - const raw = localStorage.getItem(WIDTH_PREF_KEY) - if (raw === null) return null - const value = Number(raw) - return Number.isFinite(value) && value > 0 ? value : null -} - -/** Resolves the content width the CSS axis would show for a column width. - * @param columnWidth - the conversation column's rendered width in px. - * @param preference - the dragged preference, or null for the adaptive clamp. - * @returns the resolved content width in px (mirrors the CSS clamp). */ -function resolveContentWidth(columnWidth: number, preference: number | null): number { - const max = Math.max(CONTENT_MIN, columnWidth - CONTENT_EDGE_BUDGET) - if (preference !== null) return Math.min(Math.max(preference, CONTENT_MIN), max) - return Math.max(680, Math.min(columnWidth * 0.64, 920)) -} - -/** One transcript width handle: pointer capture + rAF-throttled symmetric - * resize (both sides write the one centered width, so outward travel widens - * by 2× the pointer distance). pointermove publishes the pointer's Y as a CSS - * variable so the glow indicator rides it. Mirrors ui-layout AppFrame's - * DragHandle capture model. */ -function WidthHandle(props: { - side: 'left' | 'right' - onStart: () => number - onDrag: (width: number) => void - onCommit: (width: number) => void - onEnd: () => void -}) { - const [dragging, setDragging] = useState(false) - const base = useRef(0) - const origin = useRef(0) - const latest = useRef(0) - const frame = useRef(null) - const callbacks = useRef(props) - callbacks.current = props - - const outwardWidth = () => { - const dx = latest.current - origin.current - const outward = callbacks.current.side === 'right' ? dx : -dx - return base.current + outward * 2 - } - const cancelFrame = () => { - if (frame.current !== null) { cancelAnimationFrame(frame.current); frame.current = null } - } - const onPointerDown = useCallback((e: React.PointerEvent) => { - e.preventDefault() - e.currentTarget.setPointerCapture(e.pointerId) - origin.current = e.clientX - latest.current = e.clientX - base.current = callbacks.current.onStart() - setDragging(true) - }, []) - const onPointerMove = useCallback((e: React.PointerEvent) => { - const box = e.currentTarget.getBoundingClientRect() - e.currentTarget.style.setProperty('--dsh-width-handle-pointer-y', `${e.clientY - box.top}px`) - if (!e.currentTarget.hasPointerCapture(e.pointerId)) return - latest.current = e.clientX - frame.current ??= requestAnimationFrame(() => { - frame.current = null - callbacks.current.onDrag(outwardWidth()) - }) - }, []) - const onPointerUp = useCallback((e: React.PointerEvent) => { - if (!e.currentTarget.hasPointerCapture(e.pointerId)) return - e.currentTarget.releasePointerCapture(e.pointerId) - cancelFrame() - latest.current = e.clientX - // Only a gesture with actual travel commits: a press-and-release on a - // window-clamped width must not overwrite the wider stored preference - // with the clamped display value. - if (latest.current !== origin.current) callbacks.current.onCommit(outwardWidth()) - setDragging(false) - callbacks.current.onEnd() - }, []) - // Releasing the button outside the window delivers pointercancel (or drops - // the capture silently) instead of pointerup; without this the glow's - // data-dragging state sticks on. The gesture is abandoned uncommitted — - // onEnd republishes the stored preference. releasePointerCapture inside - // onPointerUp also fires lostpointercapture, so this runs (idempotently) - // after every normal drag end too; keep both paths. - const onPointerCancel = useCallback(() => { - cancelFrame() - setDragging(false) - callbacks.current.onEnd() - }, []) - - return ( -
- ) -} - -export function ConversationRoot({ - sessionId, useSession, useSessions, useSessionPendingInteraction, - useWorkspaces, useConversation, useInput, useComposerBlock, - renderSlot, renderSlotChain, selectWorkspace, t, -}: ConversationRootProps) { - const session = useSession(s => s) - const pendingInteraction = useSessionPendingInteraction(snapshot => - sessionId === undefined ? undefined : snapshot.get(sessionId)) - const conversation = useConversation(s => s) - const shellPhase = session === undefined || conversation === undefined - ? 'blank' - : conversationPhase(session, conversation) - const openState = session?.openState - const inputState = useInput(s => s) - const cwd = useSessions(s => sessionId === undefined ? undefined : s.byId[sessionId]?.cwd) - const summaryBlank = useSessions(s => sessionId === undefined ? undefined : s.byId[sessionId]?.blank) - const workspaces = useWorkspaces(s => s) - // A plugin this package cannot import (ui-model-selection) says this session cannot - // send; its reason is already localized by whoever raised it. - const composerBlock = useComposerBlock(block => block) - - const [pickerOpen, setPickerOpen] = useState(false) - const [pendingWorkspaceId, setPendingWorkspaceId] = useState() - const pickerAnchor = useRef(null) - - // Publishes the two live measurements floating View chrome reads off the - // scroll body: the seat's height as --dsh-composer-height, so controls clear - // the composer as it grows, and the scrollport's own height as - // --dsh-conversation-viewport-height, so a control can sit in the band the - // seat leaves visible. Callback ref, not an effect; stable identity prevents - // observer churn while the first blank session fills the resident body - // outlet. - const seatObserver = useRef(null) - const seatResizeRef = useCallback((seat: HTMLDivElement | null): void => { - seatObserver.current?.disconnect() - seatObserver.current = null - const scroller = seat?.parentElement ?? null - if (seat === null || scroller === null) return - seatObserver.current = new ResizeObserver(() => { - scroller.style.setProperty('--dsh-composer-height', `${seat.offsetHeight}px`) - scroller.style.setProperty( - '--dsh-conversation-viewport-height', - `${scroller.clientHeight}px`, - ) - }) - seatObserver.current.observe(seat) - seatObserver.current.observe(scroller) - }, []) - - // Publishes the column's live width as --dsh-conversation-column-width so - // the shared width axis can adapt (see the .root CSS), and re-clamps a - // dragged preference against the shrunken column WITHOUT rewriting the - // stored preference — widening the window restores it (the AppFrame - // sidebar-drag rule). Same callback-ref pattern as the seat observer. - const rootEl = useRef(null) - const rootObserver = useRef(null) - const publishWidths = useCallback((root: HTMLDivElement): void => { - const column = root.offsetWidth - root.style.setProperty('--dsh-conversation-column-width', `${column}px`) - const preference = readWidthPreference() - if (preference === null) { - root.style.removeProperty('--dsh-chat-user-width') - } else { - root.style.setProperty('--dsh-chat-user-width', `${resolveContentWidth(column, preference)}px`) - } - }, []) - const rootResizeRef = useCallback((root: HTMLDivElement | null): void => { - rootObserver.current?.disconnect() - rootObserver.current = null - rootEl.current = root - if (root === null) return - rootObserver.current = new ResizeObserver(() => { publishWidths(root) }) - rootObserver.current.observe(root) - publishWidths(root) - }, [publishWidths]) - - // Drag plumbing for the two width handles: onStart snapshots the resolved - // width (grabbing a clamped column must not jump back to the raw stored - // preference), onDrag publishes only the live clamped style, onCommit - // persists the width of a gesture that actually travelled, and onEnd - // republishes from storage — an uncommitted press leaves the stored - // preference untouched. - const onHandleStart = useCallback((): number => { - const root = rootEl.current - /* v8 ignore next -- handles render inside the root, so the ref is always attached. */ - if (root === null) return 680 - return resolveContentWidth(root.offsetWidth, readWidthPreference()) - }, []) - const onHandleDrag = useCallback((width: number): void => { - const root = rootEl.current - /* v8 ignore next -- handles render inside the root, so the ref is always attached. */ - if (root === null) return - const clamped = resolveContentWidth(root.offsetWidth, width) - root.style.setProperty('--dsh-chat-user-width', `${clamped}px`) - }, []) - const onHandleCommit = useCallback((width: number): void => { - const root = rootEl.current - /* v8 ignore next -- handles render inside the root, so the ref is always attached. */ - if (root === null) return - localStorage.setItem(WIDTH_PREF_KEY, `${resolveContentWidth(root.offsetWidth, width)}`) - }, []) - const onHandleEnd = useCallback((): void => { - const root = rootEl.current - if (root !== null) publishWidths(root) - }, [publishWidths]) - - const sessionWorkspace = sessionId === undefined - ? undefined - : workspaces.items.find(workspace => workspace.sessionIds.includes(sessionId)) - const pendingWorkspace = workspaces.items.find( - workspace => workspace.workspaceId === pendingWorkspaceId, - ) - - // Clear the pending pick once the session lands in it, or when the picked - // workspace disappears from a ready list (deleted from the sidebar). - useEffect(() => { - if (pendingWorkspaceId === undefined) return - if (sessionWorkspace?.workspaceId === pendingWorkspaceId - || (workspaces.phase === 'ready' && pendingWorkspace === undefined)) { - setPendingWorkspaceId(undefined) - } - }, [pendingWorkspaceId, sessionWorkspace?.workspaceId, workspaces.phase, pendingWorkspace]) - - // While a session is still replaying (loading + blank) the hero/docked - // choice is unknowable — render the composer hidden instead of flashing - // the centered hero and snapping to the docked bar (or vice versa). - // Exemption: a session the list summary already proves blank can only - // land on the hero, so hiding would blank the column for the whole - // history round-trip (the startup auto-selection flash) for nothing. - // The exemption is deliberately open-state-wide, not loading-only: a - // summary-blank session is the hero before its open starts (`cold`) and - // after one fails (`error`) for the same reason — there is no history. - // A restored continuable subagent also stays settled until its eagerly - // loaded parent catalog establishes availability. This keeps the composer - // hidden instead of briefly rendering the parent-offline takeover. - const parentAvailabilityPending = session?.subagent?.address.mode === 'continuable' - && session.subagent.parentAvailable === undefined - const settling = sessionId !== undefined && ( - (shellPhase === 'blank' && openState === 'loading' && summaryBlank !== true) - || parentAvailabilityPending - ) - const hero = sessionId === undefined - || (shellPhase === 'blank' && (openState === 'open' || summaryBlank === true)) - const zone: InputZone | undefined = - session === undefined || inputState === undefined ? undefined : { session, input: inputState } - - // The chip is a selector; label resolution walks the flow top-down: - // 1. a just-picked workspace (pending) → its title; - // 2. cold start, no session yet → placeholder ("Choose workspace"); - // 3. the blank session's workspace is in the list → its title; - // 4. list still loading → cwd folder name bridges so the title does not - // flash on refresh (empty cwd → placeholder); - // 5. list ready but no owning workspace (deleted from the sidebar) → - // placeholder, never the deleted folder's name via cwd. - const chipTitle = pendingWorkspace?.title - ?? (sessionId === undefined - ? undefined - : sessionWorkspace?.title - ?? (workspaces.phase === 'ready' || cwd === undefined || cwd === '' - ? undefined - : workspaceLabel(cwd))) - - const heroWorkspaceRow = ( -
- { setPickerOpen(open => !open) }} - t={t} - /> - {renderSlot('conversation.hero.workspace', { - open: pickerOpen, - anchorRef: pickerAnchor, - selectedId: pendingWorkspaceId ?? sessionWorkspace?.workspaceId, - onPick: (workspaceId) => { - setPickerOpen(false) - setPendingWorkspaceId(workspaceId) - void selectWorkspace(workspaceId).catch(() => { - setPendingWorkspaceId(current => current === workspaceId ? undefined : current) - }) - }, - onClose: () => { setPickerOpen(false) }, - })} - {renderSlot('conversation.hero.agentPreset', {})} -
- ) - - // The placeholder chip ("Choose workspace") and the Workspace-trigger input travel - // together: no workspace picked yet (cold start, no session at all), or a - // blank session whose workspace vanished (deleted from the sidebar). The - // bar is ONE session-maybe slot rendered unconditionally — inert is a prop, - // not a different tree, so the textarea DOM survives the transition. - const inert = sessionId === undefined || (hero && chipTitle === undefined) - // A raised block is the same inert posture with the blocker's own reason: - // one disabled textarea, never a second tree. The no-workspace state wins - // when both hold — picking a workspace is the earlier prerequisite. - const blocked = !inert && composerBlock !== undefined - const inputBar = renderSlot('conversation.composer.bar', { - variant: hero ? 'hero' : 'composer', - ...(inert - ? { - disabled: true, - placeholder: t('placeholder.workspace'), - workspacePickerOpen: pickerOpen, - onRequestWorkspace: () => { setPickerOpen(true) }, - } - : blocked - // `blocked`, not `disabled`: the bar refuses input either way, but a - // block keeps the model seat live because choosing a model is how the - // user clears it. - ? { blocked: composerBlock, placeholder: composerBlock.reason } - : hero ? { placeholder: t('placeholder.hero') } : {}), - }) - - const composerBar = ( -
- {hero && } - {hero && heroWorkspaceRow} - {zone !== undefined && renderSlot('conversation.input.dock', zone)} - {inputBar} -
- ) - - const phase = settling ? 'settling' : hero ? 'hero' : 'active' - const composer = renderSlotChain( - 'conversation.composer', - { sessionId, session, pendingInteraction }, - { fallback: composerBar, fallbackOnly: sessionId === undefined, overlay: true }, - ) - - // Sticky wraps the whole chain output (fallback + elected overlay), not - // only `.composerStack`: overlay:true renders those as siblings, and sticky - // on the fallback alone would leave a business-owned takeover at the content - // end off-screen when the user is not pinned to the floor. - const composerSeat = ( -
- {composer} -
- ) - - return ( -
- {sessionId === undefined ? null : renderSlot('conversation.session.header', {})} -
-
- {sessionId === undefined ? null : renderSlot('conversation.session', {})} - {composerSeat} -
- {/* Width handles only while a transcript is on screen; the hero has no - content column to size. */} - {phase === 'active' && (['left', 'right'] as const).map(side => ( - - ))} -
-
- ) +export function ConversationRoot(props: ConversationRootProps) { + return } diff --git a/packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx b/packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx index 803c53fc9a..f17da377a3 100644 --- a/packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx @@ -1,6 +1,5 @@ /** Strict per-session header/body content inserted into the resident conversation layout. */ -import { useEffect } from 'react' import clsx from 'clsx' import type { SessionListState, SessionSummary } from '@deepseek-ai/dsh-api-session-controller/client' import type { SessionId } from '@deepseek-ai/dsh-session/types' @@ -9,6 +8,7 @@ import type { } from '../contract/slots.ts' import { conversationPhase } from '../contract/snapshot.ts' import { resolveActiveView } from '../view-selection.ts' +import { DefaultConversationViews } from './DefaultConversationViews.tsx' import css from './ConversationRoot.module.css' /** Full props composed from the strict session body contract. */ @@ -165,35 +165,6 @@ export function ConversationSessionHeader({ * @param props - Strict Session input/store, view ledger, and render shares. * @returns the active view area, or null while the Session remains blank. */ -export function ConversationSession({ - useSession, useConversation, useConversationViews, useInput, inputActions, useStore, actions, - renderSlot, bindDraftMirror, openView, -}: ConversationSessionProps) { - const tabs = useConversationViews(value => value) - const selectedId = useStore(s => s.view) - const active = resolveActiveView(tabs, selectedId) - const session = useSession(s => s) - const conversation = useConversation(s => s) - const inputState = useInput(s => s) - const storedDraft = useStore(s => s.draft) - const viewRequest = useStore(s => s.viewRequest ?? null) - - useEffect(() => { - if (inputState.draft === '' && storedDraft !== '') inputActions.setDraft(storedDraft) - const unmirror = bindDraftMirror(actions.setDraft) - return () => { unmirror() } - // Mount-only (deps pinned to inputActions): later store writes come from - // the machine mirror, not this seed effect. - }, [inputActions]) - - if (session.blank && conversationPhase(session, conversation) === 'blank') return null - return ( -
- {active !== undefined && renderSlot('conversation.view', { - viewRequest, - openView, - completeViewRequest: actions.completeViewRequest, - }, { only: active.id })} -
- ) +export function ConversationSession(props: ConversationSessionProps) { + return } diff --git a/packages/client/ui-conversation/src/client/skeleton/DefaultConversationViews.tsx b/packages/client/ui-conversation/src/client/skeleton/DefaultConversationViews.tsx new file mode 100644 index 0000000000..640ca316f1 --- /dev/null +++ b/packages/client/ui-conversation/src/client/skeleton/DefaultConversationViews.tsx @@ -0,0 +1,44 @@ +import { useEffect } from 'react' +import type { ConversationSessionSlotProps } from '../contract/slots.ts' +import { conversationPhase } from '../contract/snapshot.ts' +import { resolveActiveView } from '../view-selection.ts' +import css from './ConversationRoot.module.css' + +/** + * Renders the active Session view inside the resident scrollport and keeps + * the input draft mirrored while blank Hero chrome is visible. + * @param props - Strict Session input/store, view ledger, and render shares. + * @returns the active view area, or null while the Session remains blank. + */ +export function DefaultConversationViews({ + useSession, useConversation, useConversationViews, useInput, inputActions, useStore, actions, + renderSlot, bindDraftMirror, openView, +}: ConversationSessionSlotProps) { + const tabs = useConversationViews(value => value) + const selectedId = useStore(s => s.view) + const active = resolveActiveView(tabs, selectedId) + const session = useSession(s => s) + const conversation = useConversation(s => s) + const inputState = useInput(s => s) + const storedDraft = useStore(s => s.draft) + const viewRequest = useStore(s => s.viewRequest ?? null) + + useEffect(() => { + if (inputState.draft === '' && storedDraft !== '') inputActions.setDraft(storedDraft) + const unmirror = bindDraftMirror(actions.setDraft) + return () => { unmirror() } + // Mount-only (deps pinned to inputActions): later store writes come from + // the machine mirror, not this seed effect. + }, [inputActions]) + + if (session.blank && conversationPhase(session, conversation) === 'blank') return null + return ( +
+ {active !== undefined && renderSlot('conversation.view', { + viewRequest, + openView, + completeViewRequest: actions.completeViewRequest, + }, { only: active.id })} +
+ ) +} diff --git a/packages/client/ui-settings-plugin-inventory/README.i18n.yaml b/packages/client/ui-settings-plugin-inventory/README.i18n.yaml index d437829dd5..c41ba66d5d 100644 --- a/packages/client/ui-settings-plugin-inventory/README.i18n.yaml +++ b/packages/client/ui-settings-plugin-inventory/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-settings-plugin-inventory/README.md -README.md: d56e7bcd2bc8a1590910045319db5cd3b6307e85 +README.md: 40b7e41cd2d6953fdf3b9155fd6adff9d9d65391 README.zh.md: 45f3444ea4fa56fe4a55517eb5d981913c790b15 diff --git a/packages/client/ui-settings-plugin-inventory/README.md b/packages/client/ui-settings-plugin-inventory/README.md index d56e7bcd2b..40b7e41cd2 100644 --- a/packages/client/ui-settings-plugin-inventory/README.md +++ b/packages/client/ui-settings-plugin-inventory/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -The **Plugin list** tab lets Web users inspect plugins without changing their configuration. It presents agent presets first and collapses the global inventory until needed. Cards retain the package name as the primary title, identify instances by stable entry id, and expose enablement, provenance, runtime status, disabled conditions, and discovery failures; preset-provided global entries name their presets. Search covers both groups and points to matches in other presets. The tab handles loading, empty, no-match, failure, and retry states without exposing transport details, and still shows the global inventory without a preset roster. +The **Plugin list** tab lets Web users inspect plugins without changing their configuration. It presents agent presets first and collapses the global inventory until needed. Cards retain the package name as the primary title, identify instances by stable entry id, and expose enablement, source details, runtime status, disabled conditions, and discovery failures; preset-provided global entries name their presets. Search covers both groups and points to matches in other presets. The tab handles loading, empty, no-match, failure, and retry states without exposing transport details, and still shows the global inventory without a preset roster. ## Table of Contents diff --git a/packages/client/ui-settings-plugin-inventory/tests/components.client.spec.tsx b/packages/client/ui-settings-plugin-inventory/tests/components.client.spec.tsx index f441987ba1..eeb9d620da 100644 --- a/packages/client/ui-settings-plugin-inventory/tests/components.client.spec.tsx +++ b/packages/client/ui-settings-plugin-inventory/tests/components.client.spec.tsx @@ -114,7 +114,7 @@ describe('PluginInventorySettingsTab', () => { expect(view.container.querySelector('[data-plugin-count]')?.getAttribute('data-plugin-count')).toBe('7') expect(screen.getByText(`1 ${en.failedCountLabel}`)).toBeTruthy() - // A preset row expands into its provenance facts. + // A preset row expands into its source facts. fireEvent.click(screen.getByRole('button', { name: 'pwsh, pwsh, Conditional' })) expect(screen.getByText(en.fromPreset)).toBeTruthy() expect(screen.getByText('标准模式')).toBeTruthy() @@ -213,7 +213,7 @@ describe('PluginInventorySettingsTab', () => { pickPreset('ptc') expect(view.container.querySelector('[data-preset-plugin-count]')?.getAttribute('data-preset-plugin-count')).toBe('3') fireEvent.click(screen.getByRole('button', { name: 'tool-bash, bash, Enabled' })) - // An unnamed preset labels provenance by its id. + // An unnamed preset labels its source by id. expect(screen.getByText(en.fromPreset).nextElementSibling?.textContent).toBe('ptc') pickPreset('坏预设 (failed to load)') diff --git a/packages/client/ui-trajectory/src/client/TrajectoryView.tsx b/packages/client/ui-trajectory/src/client/TrajectoryView.tsx index 4f8bc63d9c..4120779d9d 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryView.tsx +++ b/packages/client/ui-trajectory/src/client/TrajectoryView.tsx @@ -250,8 +250,8 @@ export function TrajectoryView({ const turn = request?.turn ?? node?.turn const step = request?.step ?? node?.step if (turn === undefined || step === undefined) continue - const provider = request?.provenance?.provider ?? node?.provenance?.provider - const model = request?.provenance?.model ?? node?.provenance?.model + const provider = request?.providerMetadata?.provider ?? node?.providerMetadata?.provider + const model = request?.providerMetadata?.model ?? node?.providerMetadata?.model const requestConfig = request?.requestConfig ?? node?.requestConfig numbered.push({ seq: entry.seq, @@ -292,12 +292,12 @@ export function TrajectoryView({ ...(request.error === undefined ? {} : { error: request.error }), ...(request.errorCode === undefined ? {} : { errorCode: request.errorCode }), resultSeq: request.startSeq, - ...(request.provenance?.provider === undefined + ...(request.providerMetadata?.provider === undefined ? {} - : { provider: request.provenance.provider }), - ...(request.provenance?.model === undefined + : { provider: request.providerMetadata.provider }), + ...(request.providerMetadata?.model === undefined ? {} - : { model: request.provenance.model }), + : { model: request.providerMetadata.model }), ...(request.requestConfig === undefined ? {} : { requestConfig: request.requestConfig }), ...(usage === undefined ? {} : { usage }), ...(cumulativeUsage === undefined ? {} : { cumulativeUsage }), diff --git a/packages/client/ui-trajectory/src/client/trajectory-assistant-definition.ts b/packages/client/ui-trajectory/src/client/trajectory-assistant-definition.ts index 2bc68039d6..6b1d6cd004 100644 --- a/packages/client/ui-trajectory/src/client/trajectory-assistant-definition.ts +++ b/packages/client/ui-trajectory/src/client/trajectory-assistant-definition.ts @@ -252,7 +252,7 @@ function finalNode( step: state.step, blocks: toAssistantBlocks(event.data.message.content), usage: event.data.usage, - provenance: { + providerMetadata: { provider: event.data.message.source.provider, model: event.data.message.source.model, }, @@ -309,7 +309,7 @@ function assistantRequest( ? {} : { resultSeq: node.seq, - ...(node.provenance === undefined ? {} : { provenance: node.provenance }), + ...(node.providerMetadata === undefined ? {} : { providerMetadata: node.providerMetadata }), }), ...(state.usage === undefined ? {} : { usage: state.usage }), } diff --git a/packages/client/ui-trajectory/src/client/trajectory-compaction-definition.ts b/packages/client/ui-trajectory/src/client/trajectory-compaction-definition.ts index c5487bbb91..32916af141 100644 --- a/packages/client/ui-trajectory/src/client/trajectory-compaction-definition.ts +++ b/packages/client/ui-trajectory/src/client/trajectory-compaction-definition.ts @@ -64,7 +64,7 @@ function requestFromState( resultSeq: summary.seq, summary: summary.data.summary, ...(summary.data.rawOutput === undefined ? {} : { rawOutput: summary.data.rawOutput }), - provenance: { provider: summary.data.provider, model: summary.data.model }, + providerMetadata: { provider: summary.data.provider, model: summary.data.model }, requestConfig: { provider: summary.data.provider, model: summary.data.model, diff --git a/packages/client/ui-trajectory/src/client/trajectory-event-projection.ts b/packages/client/ui-trajectory/src/client/trajectory-event-projection.ts index d97aa40789..c132e88747 100644 --- a/packages/client/ui-trajectory/src/client/trajectory-event-projection.ts +++ b/packages/client/ui-trajectory/src/client/trajectory-event-projection.ts @@ -2,7 +2,7 @@ import type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm/types' import type { - AssistantBlock, ContextProvenanceView, KnownContextForm, + AssistantBlock, ContextProducerView, KnownContextForm, } from '@deepseek-ai/dsh-client-ui-conversation/client' /* jscpd:ignore-start -- Chat and Trajectory own independent event-to-view projections. */ @@ -57,7 +57,7 @@ export function contextForm(source: unknown): KnownContextForm | null { * @param source - Logged `user/message` source. * @returns Role and label rendered by Trajectory. */ -export function contextProvenance(source: unknown): ContextProvenanceView { +export function contextProducer(source: unknown): ContextProducerView { const record = asRecord(source) const kind = record === null ? null : readString(record, 'kind') if (record === null || kind === null) return { role: 'inject', label: null } diff --git a/packages/client/ui-trajectory/src/client/trajectory-message-definitions.ts b/packages/client/ui-trajectory/src/client/trajectory-message-definitions.ts index deb4097406..1faaeb3547 100644 --- a/packages/client/ui-trajectory/src/client/trajectory-message-definitions.ts +++ b/packages/client/ui-trajectory/src/client/trajectory-message-definitions.ts @@ -5,7 +5,7 @@ import type { } from '@deepseek-ai/dsh-client-ui-conversation/client' import type {} from '@deepseek-ai/dsh-agent/types' import { trajectoryNode } from './trajectory-definition-common.ts' -import { contextForm, contextProvenance } from './trajectory-event-projection.ts' +import { contextForm, contextProducer } from './trajectory-event-projection.ts' /* jscpd:ignore-start -- Target-owned Definitions intentionally keep their event * state machines independent; see ../../../../../.agents/notes/implemented/ @@ -151,7 +151,7 @@ const trajectoryMessageDefinition: ConversationNodeDefinition = { time: event.time, content: event.data.content, source: event.data.source, - provenance: contextProvenance(event.data.source), + producer: contextProducer(event.data.source), form: contextForm(event.data.source), } } diff --git a/packages/client/ui-trajectory/tests/conversation-definitions.client.spec.ts b/packages/client/ui-trajectory/tests/conversation-definitions.client.spec.ts index f3d00f5dc3..a3a0623be2 100644 --- a/packages/client/ui-trajectory/tests/conversation-definitions.client.spec.ts +++ b/packages/client/ui-trajectory/tests/conversation-definitions.client.spec.ts @@ -397,7 +397,7 @@ describe('Trajectory conversation Definitions', () => { purpose: 'assistant', resultSeq: 3, status: 'error', - provenance: { provider: 'test', model: 'test' }, + providerMetadata: { provider: 'test', model: 'test' }, }]) }) diff --git a/packages/client/ui-trajectory/tests/event-projection.client.spec.ts b/packages/client/ui-trajectory/tests/event-projection.client.spec.ts index 9d2d8ff433..0405cd9cc5 100644 --- a/packages/client/ui-trajectory/tests/event-projection.client.spec.ts +++ b/packages/client/ui-trajectory/tests/event-projection.client.spec.ts @@ -1,27 +1,27 @@ import { describe, expect, it } from 'vitest' import type { ContentBlock } from '@deepseek-ai/dsh-llm/types' import { - contextForm, contextProvenance, displayFailure, emptyAssistantBlock, isTokenDelta, + contextForm, contextProducer, displayFailure, emptyAssistantBlock, isTokenDelta, toAssistantBlock, toAssistantBlocks, } from '../src/client/trajectory-event-projection.ts' describe('Trajectory event projection', () => { it('projects known, unknown, and unreadable context sources', () => { - expect(contextProvenance({ kind: 'session-reference', references: [{ label: 'A' }, { label: 'A' }] })) + expect(contextProducer({ kind: 'session-reference', references: [{ label: 'A' }, { label: 'A' }] })) .toEqual({ role: 'recall', label: 'A' }) - expect(contextProvenance({ kind: 'session-reference', references: [] })) + expect(contextProducer({ kind: 'session-reference', references: [] })) .toEqual({ role: 'recall', label: 'session-reference' }) - expect(contextProvenance({ kind: 'agent-instructions', changes: [{ path: 'AGENTS.md' }, null] })) + expect(contextProducer({ kind: 'agent-instructions', changes: [{ path: 'AGENTS.md' }, null] })) .toEqual({ role: 'inject', label: 'AGENTS.md' }) - expect(contextProvenance({ kind: 'agent-instructions', changes: 'bad' }).label) + expect(contextProducer({ kind: 'agent-instructions', changes: 'bad' }).label) .toBe('agent-instructions') - expect(contextProvenance({ kind: 'plugin', plugin: 'p' }).label).toBe('p') - expect(contextProvenance({ kind: 'plugin', plugin: 1 }).label).toBe('plugin') - expect(contextProvenance({ kind: 'skill-invocation', name: 's' }).label).toBe('s') - expect(contextProvenance({ kind: 'future' }).label).toBe('future') - expect(contextProvenance(null)).toEqual({ role: 'inject', label: null }) - expect(contextProvenance([])).toEqual({ role: 'inject', label: null }) - expect(contextProvenance({ kind: '' })).toEqual({ role: 'inject', label: null }) + expect(contextProducer({ kind: 'plugin', plugin: 'p' }).label).toBe('p') + expect(contextProducer({ kind: 'plugin', plugin: 1 }).label).toBe('plugin') + expect(contextProducer({ kind: 'skill-invocation', name: 's' }).label).toBe('s') + expect(contextProducer({ kind: 'future' }).label).toBe('future') + expect(contextProducer(null)).toEqual({ role: 'inject', label: null }) + expect(contextProducer([])).toEqual({ role: 'inject', label: null }) + expect(contextProducer({ kind: '' })).toEqual({ role: 'inject', label: null }) }) it('accepts only forms supported by the target', () => { diff --git a/packages/client/ui-workspace/README.i18n.yaml b/packages/client/ui-workspace/README.i18n.yaml index db2cd9bd89..76c160e3b1 100644 --- a/packages/client/ui-workspace/README.i18n.yaml +++ b/packages/client/ui-workspace/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-workspace/README.md -README.md: d504ad03e6153a987f94357bb939e75c592f7773 -README.zh.md: 10c4acd2dfa3c04f9e1d0e2027451907b0ab8fea +README.md: 7317573add877fce9bdfdf359219053ae2452566 +README.zh.md: 1b803e3b91030171829d8a7f6f03270fa1aa566f diff --git a/packages/client/ui-workspace/README.md b/packages/client/ui-workspace/README.md index d504ad03e6..7317573add 100644 --- a/packages/client/ui-workspace/README.md +++ b/packages/client/ui-workspace/README.md @@ -29,7 +29,7 @@ Use the sidebar to browse Workspaces and their Sessions, reorder them, and start ### Reordering and view options -View options combine grouping with one browser-persisted Session order per account: **Manual** and **Last updated** apply in either presentation. Entering Last updated performs a complete recency sort and later user prompts or steers promote their Session once; entering Manual preserves every current position and disables later promotion. Dragging edits the current order in either mode; Manual-mode drags for real Workspaces also update the Host Session account, while Ungrouped and flat-list orders remain browser-local. In a collapsed group, drag boundaries follow rendered rows and place the source before intervening hidden rows, so a drag cannot hide its source. Workspace drag order is Host-durable in either Session order mode. +**Last updated** orders ordinary Sessions by their latest user prompt or steer time, newest first, in both grouped and flat views. **Manual** freezes the current displayed order and holds positions when activity changes; newly discovered ordinary Sessions append to the end, newest first when several arrive together. Returning to Last updated discards every manual position, and entering Manual again freezes the then-current recency order. The browser defaults to Last updated and remembers the selected mode across reloads. Dragging an ordinary Session applies the move locally and selects Manual. The selected blank **New Session** is always pinned first and cannot be dragged; after its first prompt it becomes an ordinary draggable row, retaining its first position in Manual or following its current timestamp in Last updated. In a collapsed group, drag boundaries follow rendered rows and place the source before intervening hidden rows, so a drag cannot hide its source. Session display orders for real Workspaces, Ungrouped, and the flat list are browser-local; Workspace group drag order remains Host-durable. ### Search @@ -67,7 +67,7 @@ Each registration declares a **directory-flow child hole** (`single` kind: `conv ### View state -Once the Workspace list baseline is ready, browser-persisted expansion and Session-order records retain only current Workspace ids plus Ungrouped and the flat-list account. Real Workspaces initialize from `WorkspaceView.sessionIds`, while Ungrouped and the cross-Workspace flat list initialize from recency. The shared sidebar projection hides rows whose durable Session summary has `origin: 'subagent'`, and each visible ordinary row inherits the blue activity indicator while any descendant reached through uninterrupted subagent-origin lineage is running. The same pure derivation reads the Schedule key from list projection values for grouped, flat, and search nodes; the package uses only the type-only `@deepseek-ai/dsh-schedule/client` dependency and does not import the Schedule runtime or `ui-schedule`. +Once the Workspace list baseline is ready, browser-persisted expansion and manual Session-order records retain only current Workspace ids plus Ungrouped and the flat-list account. `WorkspaceView.sessionIds` supplies real-Workspace membership, not Session display order. View actions require the current account orders explicitly. Flat-list membership and ordering use Session ids; row rendering adds status indicators once. Entering Manual snapshots every active account from the current display; reconciliation retains saved members that still belong to the account, removes departed members, and appends newly known members by recency. A new membership entry without a Session summary is omitted until that summary arrives, while an already saved slot survives a temporarily missing summary. During Workspace reconnection, Manual records an observed blank Session at the front of its saved flat and known group orders without removing other saved members; full membership reconciliation waits for the Workspace baseline. This reconciliation remains mounted while the sidebar is a rail or search replaces its body. Last updated derives directly from each current list snapshot without reading or writing saved positions; equal timestamps use Session ids as a stable tie-break. The shared sidebar projection hides rows whose durable Session summary has `origin: 'subagent'`, and each visible ordinary row inherits the blue activity indicator while any descendant reached through uninterrupted subagent-origin lineage is running. The same pure derivation reads the Schedule key from list projection values for grouped, flat, and search nodes; the package uses only the type-only `@deepseek-ai/dsh-schedule/client` dependency and does not import the Schedule runtime or `ui-schedule`. ### Hover cards @@ -85,7 +85,7 @@ These pages cover the sidebar host, the hero surface, and the picking backends. - [ui-sidebar](../ui-sidebar/README.md) — the sidebar shell hosting the `sidebar.workspaces` hole. - [ui-conversation](../ui-conversation/README.md) — the chat surface hosting the Session Intent hero's picker hole. - [directory-picker-native](../../host/directory-picker-native/README.md) — the OS-chooser backend filling the directory-flow hole. -- [Workspace Controller](../../api/workspace-controller/README.md) — the Host mutations and framework-neutral Client projection that own workspaces and ordering. +- [Workspace Controller](../../api/workspace-controller/README.md) — the Host mutations and framework-neutral Client projection that own Workspaces, membership, and Workspace group order. ----- diff --git a/packages/client/ui-workspace/README.zh.md b/packages/client/ui-workspace/README.zh.md index 10c4acd2df..1b803e3b91 100644 --- a/packages/client/ui-workspace/README.zh.md +++ b/packages/client/ui-workspace/README.zh.md @@ -29,7 +29,7 @@ kind: "package-reference" ### 重排序与视图选项 -视图选项把分组方式和每个记账各自的一份浏览器持久化 Session 顺序放在一起:**手动排序**和**最近更新**在两种呈现方式下都可用。进入最近更新时会执行一次完整的时间排序,后续用户提示词或 steering(中途引导)会将对应 Session 置顶一次;进入手动排序则保留所有当前位置并停用后续置顶。两种模式下的拖拽都会编辑当前顺序;真实 Workspace 在手动模式下的拖拽还会更新 Host Session 记账,而 Ungrouped 和单列表的顺序始终只保存在浏览器本地。折叠分组的拖拽边界按渲染行确定,并把来源行放在中间隐藏行之前,因此拖拽不会隐藏来源行。无论采用哪种 Session 顺序,Workspace 拖拽顺序都由 Host 持久化。 +**最近更新**在分组和单列表视图中都按普通 Session 的最近一次用户提示词或 steering(中途引导)时间降序排列。**手动排序**会冻结当前显示顺序,并在活动变化时保持已有位置;新发现的普通 Session 追加到末尾,多条同时到达时按最近更新时间降序排列。返回最近更新会丢弃所有手动位置;再次进入手动排序时,则冻结当时的最近更新顺序。浏览器默认采用最近更新,并在重新加载后记住所选模式。拖拽普通 Session 会在浏览器本地应用移动并选中手动排序。当前选中的空白**新会话**始终固定在首位且无法拖拽;首条提示词落地后,它成为可拖拽的普通行,在手动排序中保留首位,在最近更新中按当前时间戳排列。折叠分组的拖拽边界按渲染行确定,并把来源行放在中间隐藏行之前,因此拖拽不会隐藏来源行。真实 Workspace、Ungrouped 与单列表的 Session 显示顺序都保留在浏览器本地;Workspace 分组的拖拽顺序仍由 Host 持久化。 ### 搜索 @@ -67,7 +67,7 @@ Session 行渲染运行时的实时 `pendingInteraction` 分类:审批显示** ### 视图状态 -Workspace 列表基线就绪后,浏览器持久化的展开状态与 Session 顺序记录只保留当前 Workspace id、Ungrouped 与单列表记账。真实 Workspace 从 `WorkspaceView.sessionIds` 初始化,Ungrouped 与跨 Workspace 单列表从最近更新时间顺序初始化。共享侧边栏投影会隐藏持久化 Session 摘要中带有 `origin: 'subagent'` 的行;每个可见普通行都会在经不间断的 subagent 谱系可达的任一后代运行时继承蓝色活动指示器。同一项纯派生逻辑还会为分组、平铺与搜索节点读取列表 projection value 中的 Schedule key;本包只使用纯类型依赖 `@deepseek-ai/dsh-schedule/client`,不会导入 Schedule 运行时或 `ui-schedule`。 +Workspace 列表基线就绪后,浏览器持久化的展开状态和手动 Session 顺序记录只保留当前 Workspace id、Ungrouped 和单列表记账。`WorkspaceView.sessionIds` 提供真实 Workspace 的成员关系,而不提供 Session 显示顺序。视图操作要求显式传入当前各记账的顺序。单列表的成员筛选和排序使用 Session id,行渲染只计算一次状态指示。进入手动排序会从当前显示结果一次性记录每个有效记账;对账会保留仍属于该记账的已保存成员、移除已经离开的成员,并按最近更新时间追加新发现的成员。尚无 Session 摘要的新成员会等摘要到达后再加入,而已经保存的位置在摘要暂时缺失时仍会保留。Workspace 重连期间,手动排序会将已观察到的空白 Session 记录到已保存的单列表及已知分组顺序首位,不移除其他已保存成员;完整成员对账等待 Workspace 基线到齐。即使侧边栏收成窄栏或搜索替代列表主体,这项对账也保持挂载。最近更新直接从每份当前列表快照派生,不读取或写入已保存位置;时间相同时按 Session id 稳定排序。共享侧边栏投影会隐藏持久化 Session 摘要中带有 `origin: 'subagent'` 的行;每个可见普通行都会在经不间断的 subagent 谱系可达的任一后代运行时继承蓝色活动指示器。同一项纯派生逻辑还会为分组、平铺与搜索节点读取列表 projection value 中的 Schedule key;本包只使用纯类型依赖 `@deepseek-ai/dsh-schedule/client`,不会导入 Schedule 运行时或 `ui-schedule`。 ### 悬浮卡片 @@ -85,7 +85,7 @@ Workspace 与 Session 悬浮卡片会复制对应行被截断的值:激活 Wor - [ui-sidebar](../ui-sidebar/README.zh.md)——承载 `sidebar.workspaces` 子 slot 的侧边栏外壳。 - [ui-conversation](../ui-conversation/README.zh.md)——承载 Session Intent 主视觉区选择器子 slot 的聊天界面。 - [directory-picker-native](../../host/directory-picker-native/README.zh.md)——填充目录流子 slot 的 OS 选择器后端。 -- [Workspace Controller](../../api/workspace-controller/README.zh.md)——负责 Workspace 与排序的 Host 变更和框架无关 Client 投影。 +- [Workspace Controller](../../api/workspace-controller/README.zh.md)——负责 Workspace、成员关系与 Workspace 分组顺序的 Host 变更和框架无关 Client 投影。 ----- diff --git a/packages/client/ui-workspace/src/client/contract/slots.ts b/packages/client/ui-workspace/src/client/contract/slots.ts index df17251c38..baa263b1a6 100644 --- a/packages/client/ui-workspace/src/client/contract/slots.ts +++ b/packages/client/ui-workspace/src/client/contract/slots.ts @@ -134,12 +134,6 @@ export type WorkspaceBrowserInjected = { * session clears the selection into the New Session view state. */ archiveSession: (sessionId: SessionId) => Promise - /** - * Reorder a session inside its Workspace account (DOM-insertBefore - * semantics: omitted anchor appends to the end). The view refreshes from - * the Host response/changed frame; failures leave the order unchanged. - */ - insertSessionBefore: (workspaceId: WorkspaceId, sessionId: SessionId, beforeSessionId?: SessionId) => Promise /** Adopt a picked host directory as a real Workspace before targeting a Session. */ createWorkspace: (input: { path: string }) => Promise } diff --git a/packages/client/ui-workspace/src/client/index.ts b/packages/client/ui-workspace/src/client/index.ts index 9204fe7774..f7136526c0 100644 --- a/packages/client/ui-workspace/src/client/index.ts +++ b/packages/client/ui-workspace/src/client/index.ts @@ -126,9 +126,6 @@ export function apply(ctx: Context): void { await workspaces.insertBefore(workspaceId, beforeWorkspaceId) }, archiveSession: async (sessionId) => { await uiWorkspace.archiveSession(sessionId) }, - insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => { - await workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId) - }, createWorkspace: input => workspaces.create(input), hooks: { directoryFlow: browserFlowSource, hostInfo }, }) diff --git a/packages/client/ui-workspace/src/client/rows/Rows.tsx b/packages/client/ui-workspace/src/client/rows/Rows.tsx index 0c2101bd0a..a015d31de5 100644 --- a/packages/client/ui-workspace/src/client/rows/Rows.tsx +++ b/packages/client/ui-workspace/src/client/rows/Rows.tsx @@ -371,7 +371,7 @@ export function SearchResultItem({ result, currentId, onOpen, t }: { * @param props.onFork - fork a session at its last completed turn. * @param props.onArchive - archive a session by id. * @param props.onReveal - scroll this row into view after search navigation, then acknowledge it. - * @param props.drag - optional draggable-row wiring. + * @param props.drag - optional row-drag target wiring; blank rows cannot start a drag. * @param props.flat - omit the empty status slot in the hierarchy-free flat list. * @param props.t - the browser root's locale seat. * @returns the session row. @@ -391,7 +391,7 @@ export function SessionNodeItem({ onArchive: (id: SessionNode['id']) => void /** Scroll this row into view after search navigation, then acknowledge it. */ onReveal?: (() => void) | undefined - /** Present only on draggable rows (workspace-group sessions outside search). */ + /** Present on reorderable-list rows so every row can remain a drop target. */ drag?: RowDragProps | undefined /** The row is rendered without a parent Workspace header. */ flat?: boolean | undefined @@ -403,6 +403,7 @@ export function SessionNodeItem({ const statuses = sessionStatuses(node, t) const primaryStatus = statuses[0] const showStatus = primaryStatus.state !== 'done' || row.completed + const draggable = drag !== undefined && !row.blank const [menuOpen, setMenuOpen] = useState(false) const rowRef = useRef(null) useEffect(() => { @@ -431,15 +432,15 @@ export function SessionNodeItem({ role="treeitem" aria-selected={selected} onClick={() => { onOpen(node.id) }} - draggable={drag !== undefined} - onDragStart={drag === undefined + draggable={draggable} + onDragStart={drag === undefined || row.blank ? undefined : (e) => { e.dataTransfer.effectAllowed = 'move' e.dataTransfer.setData('text/plain', node.id) drag.start() }} - onDragEnd={drag?.end} + onDragEnd={drag === undefined || row.blank ? undefined : drag.end} onDragOver={drag === undefined ? undefined : (e) => { diff --git a/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx b/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx index 585e9c8766..19966e451c 100644 --- a/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx +++ b/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx @@ -23,7 +23,8 @@ import type { SessionId } from '@deepseek-ai/dsh-session/types' import type { WorkspaceBrowserProps } from '../contract/slots.ts' import type { SessionNode, SessionOrderBy } from '../tree.ts' import { - deriveFlat, deriveGroups, deriveSearchResults, owningGroupKey, UNGROUPED_KEY, + deriveFlat, deriveGroups, deriveSearchResults, orderByRecency, owningGroupKey, + pinCurrentBlank, reconcileManualOrder, UNGROUPED_KEY, visibleSessionIds, } from '../tree.ts' import { ProjectRowItem, SearchResultItem, SessionNodeItem } from './Rows.tsx' import { FLAT_SESSION_ORDER_KEY } from '../stores.ts' @@ -95,73 +96,6 @@ function useNativeDragAcceptance(active: boolean): void { }, [active]) } -/** Reconcile a stored view order with the Workspace's current session account. */ -function reconciledSessionOrder(sessionIds: readonly SessionId[], stored: readonly string[] | undefined): SessionId[] { - if (stored === undefined) return [...sessionIds] - const byId = new Map(sessionIds.map(id => [id as string, id])) - const ordered: SessionId[] = [] - const included = new Set() - for (const key of stored) { - const id = byId.get(key) - if (id === undefined || included.has(key)) continue - ordered.push(id) - included.add(key) - } - for (const id of sessionIds) { - if (included.has(id)) continue - ordered.push(id) - } - return ordered -} - -/** Newest update first with stable Session identity as the tie-break. */ -function compareSessionRecency(a: SessionId, b: SessionId, byId: SessionListState['byId']): number { - const aUpdatedAt = byId[a]?.updatedAt ?? Number.NEGATIVE_INFINITY - const bUpdatedAt = byId[b]?.updatedAt ?? Number.NEGATIVE_INFINITY - if (aUpdatedAt !== bUpdatedAt) return bUpdatedAt - aUpdatedAt - return a < b ? -1 : 1 -} - -/** Reconcile one editable order account and apply its activity-promotion policy. */ -function nextSessionOrderAccount({ - sessionIds, previousOrder, previousUpdatedAt, list, orderBy, sortByRecency, -}: { - sessionIds: readonly SessionId[] - previousOrder: readonly string[] | undefined - previousUpdatedAt: Readonly> - list: SessionListState - orderBy: SessionOrderBy - sortByRecency: boolean -}): { order: SessionId[]; updatedAt: Record; changed: boolean } { - let order = reconciledSessionOrder(sessionIds, previousOrder) - if (sortByRecency) { - order.sort((a, b) => compareSessionRecency(a, b, list.byId)) - } else if (orderBy === 'updated') { - const promoted = sessionIds - .filter((id) => { - const session = list.byId[id] - return session !== undefined - && (previousUpdatedAt[id] === undefined || session.updatedAt > previousUpdatedAt[id]) - }) - .sort((a, b) => compareSessionRecency(a, b, list.byId)) - if (promoted.length > 0) { - const promotedIds = new Set(promoted) - order = [...promoted, ...order.filter(id => !promotedIds.has(id))] - } - } - const updatedAt: Record = {} - for (const id of sessionIds) { - const session = list.byId[id] - if (session !== undefined) updatedAt[id] = session.updatedAt - } - const orderChanged = previousOrder === undefined - || order.length !== previousOrder.length - || order.some((id, index) => id !== previousOrder[index]) - const timestampsChanged = Object.keys(updatedAt).length !== Object.keys(previousUpdatedAt).length - || Object.entries(updatedAt).some(([id, timestamp]) => previousUpdatedAt[id] !== timestamp) - return { order, updatedAt, changed: orderChanged || timestampsChanged } -} - /** Grouping and ordering menu; own open state so it resets with the wide chrome. */ function ViewOptionsMenu({ groupBy, orderBy, onGroupPick, onOrderPick, t }: { groupBy: 'workspace' | 'flat' @@ -234,26 +168,25 @@ function workspaceGroupHalf(e: { clientY: number; currentTarget: HTMLElement }): type SessionTreeProps = Pick< WorkspaceBrowserProps, - 'useSessions' | 'useSessionPendingInteraction' | 'startSession' | 'open' | 'forkSession' - | 'insertWorkspaceBefore' | 'insertSessionBefore' | 't' | 'usePanelInfo' + 'useSessionPendingInteraction' | 'startSession' | 'open' | 'forkSession' + | 'insertWorkspaceBefore' | 't' | 'usePanelInfo' > & { + /** Always-mounted Session list snapshot. */ + list: SessionListState /** Host account home for POSIX hover-path abbreviation. */ home?: string | undefined + /** Workspaces in Host group order with browser-projected Session order. */ workspaces: readonly WorkspaceView[] + /** Browser-projected order for Sessions outside every Workspace. */ + ungroupedSessionIds: readonly SessionId[] /** Whether the current Workspace stream has a complete Host baseline. */ workspaceReady: boolean /** Explicit persisted zero-or-five-session state by Workspace group. */ groupExpansion: Readonly> /** Persist one Workspace group's zero-or-five-session state. */ setGroupExpanded: (key: string, expanded: boolean) => void - /** Shared editable orders used by Workspace groups and the flat-list account. */ - sessionOrderByAccount: Readonly> - /** Last update timestamps observed for one-time recent-update promotions. */ - sessionUpdatedAtByAccount: Readonly>>> - /** Replace one shared order and its observed timestamps. */ - syncSessionOrderAccount: (accountKey: string, order: string[], updatedAt: Record) => void - /** Apply a drag to one shared order. */ - setSessionOrder: (accountKey: string, order: string[]) => void + /** Save a drag order and select Manual. */ + setSessionOrder: (accountKey: string, order: readonly string[]) => void /** Registry-global archive set (hidden rows). */ archivedSessionIds: readonly SessionNode['id'][] /** Open the browser-owned rename dialog for a real Workspace group. */ @@ -264,8 +197,6 @@ type SessionTreeProps = Pick< onSessionRename: (sessionId: SessionNode['id'], currentTitle: string) => void /** Archive a session (row menu action; the row disappears on the state echo). */ onSessionArchive: (sessionId: SessionNode['id']) => void - /** Session order behavior: fixed after edits, or additionally promoted by user activity. */ - orderBy: SessionOrderBy /** One Session chosen from search that must be exposed and scrolled into view. */ revealSessionId?: SessionId | undefined /** Acknowledge that the chosen Session row has been revealed. */ @@ -274,16 +205,16 @@ type SessionTreeProps = Pick< /** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */ function SessionTree({ - useSessions, useSessionPendingInteraction, startSession, open, forkSession, workspaces, archivedSessionIds, + list, useSessionPendingInteraction, startSession, open, forkSession, workspaces, ungroupedSessionIds, + archivedSessionIds, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, - insertWorkspaceBefore, insertSessionBefore, orderBy, + insertWorkspaceBefore, groupExpansion, setGroupExpanded, - sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, home, t, + setSessionOrder, home, t, revealSessionId, onSessionRevealed, }: SessionTreeProps) { const panelActive = usePanelInfo(info => info.activePanelId !== null) - const list = useSessions(s => s) const pendingInteractions = useSessionPendingInteraction(s => s) const current = panelActive ? undefined : list.current const revealGroup = revealSessionId === undefined || !workspaceReady @@ -295,7 +226,6 @@ function SessionTree({ const sessionDropCommitted = useRef(false) const [workspaceDrag, setWorkspaceDrag] = useState(null) const workspaceDropCommitted = useRef(false) - const previousOrderBy = useRef(orderBy) const nativeDragActive = drag !== null || workspaceDrag !== null useNativeDragAcceptance(nativeDragActive) const currentGroup = current === undefined || !workspaceReady @@ -309,56 +239,12 @@ function SessionTree({ () => Object.entries(groupExpansion).filter(([, expanded]) => expanded).map(([key]) => key), [groupExpansion], ) - const ungroupedSessionIds = useMemo(() => { - const accounted = new Set(workspaces.flatMap(workspace => workspace.sessionIds)) - return list.ids.filter((id: SessionId) => list.byId[id] !== undefined && !accounted.has(id)) - }, [list, workspaces]) - useEffect(() => { - if (list.phase !== 'ready') return - const switchedToUpdated = previousOrderBy.current !== 'updated' && orderBy === 'updated' - previousOrderBy.current = orderBy - const accounts = [ - ...workspaces.map(workspace => ({ - key: workspace.workspaceId as string, - sessionIds: workspace.sessionIds.filter(id => list.byId[id] !== undefined), - })), - { key: UNGROUPED_KEY, sessionIds: ungroupedSessionIds }, - ] - for (const { key, sessionIds } of accounts) { - const previousOrder = sessionOrderByAccount[key] - const previousUpdatedAt = sessionUpdatedAtByAccount[key] ?? {} - const next = nextSessionOrderAccount({ - sessionIds, - previousOrder, - previousUpdatedAt, - list, - orderBy, - sortByRecency: orderBy === 'updated' && (previousOrder === undefined || switchedToUpdated), - }) - if (next.changed) { - syncSessionOrderAccount(key, next.order.map(id => id as string), next.updatedAt) - } - } - }, [list, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, ungroupedSessionIds, workspaces]) - const orderedWorkspaces = useMemo(() => { - return workspaces.map((workspace) => { - const stored = sessionOrderByAccount[workspace.workspaceId as string] - const sessionIds = reconciledSessionOrder(workspace.sessionIds, stored) - return { ...workspace, sessionIds } - }) - }, [sessionOrderByAccount, workspaces]) - const orderedUngroupedSessionIds = useMemo( - () => reconciledSessionOrder(ungroupedSessionIds, sessionOrderByAccount[UNGROUPED_KEY]), - [sessionOrderByAccount, ungroupedSessionIds], - ) const groups = useMemo( - () => deriveGroups(list, orderedWorkspaces, archivedSessionIds, pendingInteractions, { + () => deriveGroups(list, workspaces, archivedSessionIds, pendingInteractions, { expandedGroups, - ...(sessionOrderByAccount[UNGROUPED_KEY] === undefined - ? {} - : { ungroupedOrder: sessionOrderByAccount[UNGROUPED_KEY] }), + ungroupedOrder: ungroupedSessionIds, }), - [list, orderedWorkspaces, archivedSessionIds, pendingInteractions, expandedGroups, sessionOrderByAccount], + [list, workspaces, archivedSessionIds, pendingInteractions, expandedGroups, ungroupedSessionIds], ) useEffect(() => { if (revealGroup === undefined || groupExpansion[revealGroup] === true) return @@ -390,16 +276,15 @@ function SessionTree({ const visibleInsertAt = over.half === 'before' ? targetWithoutSourceIndex : targetWithoutSourceIndex + 1 if (sourceIndex !== -1 && visibleInsertAt === sourceIndex) return const accountSessionIds = activeDrag.accountKey === UNGROUPED_KEY - ? orderedUngroupedSessionIds - : orderedWorkspaces.find(workspace => workspace.workspaceId === activeDrag.accountKey)?.sessionIds - if (accountSessionIds === undefined) return + ? ungroupedSessionIds + : workspaces.find(workspace => workspace.workspaceId === activeDrag.accountKey)?.sessionIds + if (accountSessionIds === undefined || !accountSessionIds.includes(activeDrag.sessionId)) return const nextOrder = accountSessionIds.filter(id => id !== activeDrag.sessionId) let anchor: SessionId | undefined if (sessionsExpanded) { anchor = over.half === 'before' ? over.id : renderedSessions[targetIndex + 1]?.id } else { - // A collapsed group may render the blank row after hidden ordinary rows. - // Place the source at the visible boundary before those hidden account members. + // Place the source at the visible boundary before hidden account members. const previousVisible = withoutSource[visibleInsertAt - 1]?.id if (previousVisible === undefined) { anchor = nextOrder[0] @@ -419,11 +304,8 @@ function SessionTree({ }) if (!collapsedSessionRows(nextGroup).rows.some(node => node.id === activeDrag.sessionId)) return } - setSessionOrder(activeDrag.accountKey, nextOrder.map(id => id as string)) - if (orderBy === 'updated' || activeDrag.accountKey === UNGROUPED_KEY) return - insertSessionBefore(activeDrag.accountKey as WorkspaceId, activeDrag.sessionId, anchor).catch((reason: unknown) => { - console.warn('session reorder rejected:', reason) - }) + const currentBlank = group.sessions.find(node => node.blank)?.id + setSessionOrder(activeDrag.accountKey, pinCurrentBlank(nextOrder, currentBlank)) } const commitWorkspaceDrag = ( activeDrag: WorkspaceDragState, @@ -553,9 +435,10 @@ function SessionTree({ ? group.sessions : collapsed.rows ).map((node) => { - // Session drag never leaves its group. Ungrouped writes only the - // browser-local account; real Workspaces may also write Host order. + // Session drag never leaves its browser-local account. const sameGroupDrag = drag !== null && drag.accountKey === group.key + const normalizeHalf = (half: 'before' | 'after'): 'before' | 'after' => + node.blank ? 'after' : half const dragProps = { start: () => { sessionDropCommitted.current = false @@ -565,12 +448,14 @@ function SessionTree({ marker: sameGroupDrag && drag.over?.id === node.id ? drag.over.half : null, hover: (half: 'before' | 'after') => { /* v8 ignore next -- narrowing guard: Rows gates hover on `active`, which is false while the drag state is null. */ - setDrag(d => (d === null ? d : { ...d, over: { id: node.id, half } })) + setDrag(d => (d === null ? d : { + ...d, over: { id: node.id, half: normalizeHalf(half) }, + })) }, drop: (half: 'before' | 'after') => { /* v8 ignore next -- narrowing guard: Rows gates drop on `active`, which is false while the drag state is null. */ if (drag === null) return - commitSessionDrag(drag, { id: node.id, half }) + commitSessionDrag(drag, { id: node.id, half: normalizeHalf(half) }) }, end: () => { if (drag?.over !== null && drag?.over !== undefined) commitSessionDrag(drag, drag.over) @@ -619,64 +504,31 @@ function SessionTree({ /** The flat "In one list" body: every session is one draggable top-level row. */ function FlatList({ - useSessions, useSessionPendingInteraction, open, forkSession, onSessionRename, onSessionArchive, - archivedSessionIds, usePanelInfo, - orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, + list, sessionIds, useSessionPendingInteraction, open, forkSession, onSessionRename, onSessionArchive, + usePanelInfo, setSessionOrder, revealSessionId, onSessionRevealed, t, }: Pick< SessionTreeProps, - | 'useSessions' | 'useSessionPendingInteraction' | 'open' | 'forkSession' | 'onSessionRename' | 'onSessionArchive' - | 'archivedSessionIds' | 'usePanelInfo' - | 'orderBy' - | 'sessionOrderByAccount' - | 'sessionUpdatedAtByAccount' - | 'syncSessionOrderAccount' | 'setSessionOrder' | 'revealSessionId' | 'onSessionRevealed' | 't' ->) { +> & { + list: SessionListState + sessionIds: readonly SessionId[] +}) { const panelActive = usePanelInfo(info => info.activePanelId !== null) - const list = useSessions(s => s) const pendingInteractions = useSessionPendingInteraction(s => s) - const baseRows = useMemo( - () => deriveFlat(list, archivedSessionIds, pendingInteractions), - [list, archivedSessionIds, pendingInteractions], + const rows = useMemo( + () => deriveFlat(list, sessionIds, pendingInteractions), + [list, sessionIds, pendingInteractions], ) - const sessionIds = useMemo(() => baseRows.map(row => row.id), [baseRows]) - const previousOrderBy = useRef(orderBy) - useEffect(() => { - if (list.phase !== 'ready') return - const previousOrder = sessionOrderByAccount[FLAT_SESSION_ORDER_KEY] - const previousUpdatedAt = sessionUpdatedAtByAccount[FLAT_SESSION_ORDER_KEY] ?? {} - const switchedToUpdated = previousOrderBy.current !== 'updated' && orderBy === 'updated' - previousOrderBy.current = orderBy - const next = nextSessionOrderAccount({ - sessionIds, - previousOrder, - previousUpdatedAt, - list, - orderBy, - sortByRecency: orderBy === 'updated' && (previousOrder === undefined || switchedToUpdated), - }) - if (next.changed) { - syncSessionOrderAccount(FLAT_SESSION_ORDER_KEY, next.order.map(id => id as string), next.updatedAt) - } - }, [list, orderBy, sessionOrderByAccount, sessionUpdatedAtByAccount, sessionIds, syncSessionOrderAccount]) - const rows = useMemo(() => { - const byId = new Map(baseRows.map(row => [row.id, row])) - return reconciledSessionOrder(sessionIds, sessionOrderByAccount[FLAT_SESSION_ORDER_KEY]) - .flatMap((id) => { - const row = byId.get(id) - return row === undefined ? [] : [row] - }) - }, [baseRows, sessionOrderByAccount, sessionIds]) const [drag, setDrag] = useState(null) const dropCommitted = useRef(false) useNativeDragAcceptance(drag !== null) @@ -694,7 +546,8 @@ function FlatList({ const nextOrder = rows.map(row => row.id).filter(id => id !== activeDrag.sessionId) const insertAt = anchor === undefined ? nextOrder.length : nextOrder.indexOf(anchor) nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId) - setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder.map(id => id as string)) + const currentBlank = rows.find(node => node.blank)?.id + setSessionOrder(FLAT_SESSION_ORDER_KEY, pinCurrentBlank(nextOrder, currentBlank)) } const now = Date.now() return ( @@ -705,6 +558,8 @@ function FlatList({ )} {rows.map((node) => { const active = drag !== null + const normalizeHalf = (half: 'before' | 'after'): 'before' | 'after' => + node.blank ? 'after' : half return ( { - setDrag(current => current === null ? current : { ...current, over: { id: node.id, half } }) + setDrag(current => current === null ? current : { + ...current, over: { id: node.id, half: normalizeHalf(half) }, + }) }, drop: (half) => { - if (drag !== null) commitDrag(drag, { id: node.id, half }) + if (drag !== null) commitDrag(drag, { id: node.id, half: normalizeHalf(half) }) }, end: () => { if (drag?.over !== null && drag?.over !== undefined) commitDrag(drag, drag.over) @@ -767,7 +624,7 @@ function SearchResults({ resultLimit, usePanelInfo, t, -}: Pick & { +}: Pick & { workspaces: readonly WorkspaceView[] archivedSessionIds: readonly SessionNode['id'][] query: string @@ -853,7 +710,6 @@ export function WorkspaceBrowser({ deleteWorkspace, insertWorkspaceBefore, archiveSession, - insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, @@ -863,6 +719,8 @@ export function WorkspaceBrowser({ t, }: WorkspaceBrowserProps) { const home = useHostInfo(info => info.home) + // Ordering remains live while the rail or search replaces the list body. + const list = useSessions(state => state) const workspaces = useWorkspaces(state => state.items) const workspacePhase = useWorkspaces(state => state.phase) const workspaceStreamState = useWorkspaces(state => state.state) @@ -874,33 +732,54 @@ export function WorkspaceBrowser({ const orderBy = useStore(s => s.orderBy) const groupExpansion = useStore(s => s.groupExpansion) const sessionOrderByAccount = useStore(s => s.sessionOrderByAccount) - const sessionUpdatedAtByAccount = useStore(s => s.sessionUpdatedAtByAccount) - const currentBlankSessionId = useSessions((state) => { - const current = state.current - return current !== undefined && state.byId[current]?.blank === true ? current : undefined - }) - const currentBlankAccount = currentBlankSessionId === undefined - || workspacePhase !== 'ready' - ? undefined - : owningGroupKey(workspaces, currentBlankSessionId) - const promotedBlank = useRef<{ sessionId: SessionId; accountKey: string } | undefined>(undefined) - useEffect(() => { - if (currentBlankSessionId === undefined || currentBlankAccount === undefined) { - promotedBlank.current = undefined - return + const workspaceReady = workspacePhase === 'ready' && workspaceStreamState !== 'loading' + const currentBlank = list.current !== undefined && list.byId[list.current]?.blank === true + ? list.current + : undefined + const ungroupedMemberIds = useMemo(() => { + const accounted = new Set(workspaces.flatMap(workspace => workspace.sessionIds)) + return list.ids.filter(id => list.byId[id] !== undefined && !accounted.has(id)) + }, [list, workspaces]) + const flatMemberIds = useMemo( + () => visibleSessionIds(list, archivedSessionIds), + [archivedSessionIds, list], + ) + const orderedWorkspaces = useMemo(() => workspaces.map((workspace) => { + const memberIds = workspace.sessionIds + const baseOrder = orderBy === 'updated' + ? orderByRecency(memberIds, list.byId) + : reconcileManualOrder(memberIds, sessionOrderByAccount[workspace.workspaceId], list.byId) + return { + ...workspace, + sessionIds: pinCurrentBlank( + baseOrder, + currentBlank !== undefined && memberIds.includes(currentBlank) ? currentBlank : undefined, + ), } - const promoted = promotedBlank.current - if (promoted !== undefined && promoted.sessionId === currentBlankSessionId - && promoted.accountKey === currentBlankAccount) return - promotedBlank.current = { sessionId: currentBlankSessionId, accountKey: currentBlankAccount } - for (const accountKey of new Set([currentBlankAccount, FLAT_SESSION_ORDER_KEY])) { - const previous = sessionOrderByAccount[accountKey] ?? [] - actions.setSessionOrder(accountKey, [ - currentBlankSessionId, - ...previous.filter(id => id !== currentBlankSessionId), - ]) - } - }, [actions.setSessionOrder, currentBlankAccount, currentBlankSessionId, sessionOrderByAccount]) + }), [currentBlank, list.byId, orderBy, sessionOrderByAccount, workspaces]) + const orderedUngroupedSessionIds = useMemo(() => { + const baseOrder = orderBy === 'updated' + ? orderByRecency(ungroupedMemberIds, list.byId) + : reconcileManualOrder(ungroupedMemberIds, sessionOrderByAccount[UNGROUPED_KEY], list.byId) + return pinCurrentBlank( + baseOrder, + currentBlank !== undefined && ungroupedMemberIds.includes(currentBlank) ? currentBlank : undefined, + ) + }, [currentBlank, list.byId, orderBy, sessionOrderByAccount, ungroupedMemberIds]) + const orderedFlatSessionIds = useMemo(() => { + const baseOrder = orderBy === 'updated' + ? orderByRecency(flatMemberIds, list.byId) + : reconcileManualOrder(flatMemberIds, sessionOrderByAccount[FLAT_SESSION_ORDER_KEY], list.byId) + return pinCurrentBlank( + baseOrder, + currentBlank !== undefined && flatMemberIds.includes(currentBlank) ? currentBlank : undefined, + ) + }, [currentBlank, flatMemberIds, list.byId, orderBy, sessionOrderByAccount]) + const activeSessionOrders = useMemo>>(() => Object.fromEntries([ + ...orderedWorkspaces.map(workspace => [workspace.workspaceId as string, workspace.sessionIds] as const), + [UNGROUPED_KEY, orderedUngroupedSessionIds] as const, + [FLAT_SESSION_ORDER_KEY, orderedFlatSessionIds] as const, + ]), [orderedFlatSessionIds, orderedUngroupedSessionIds, orderedWorkspaces]) useEffect(() => { if (workspacePhase !== 'ready') return actions.retainAccountKeys([ @@ -909,6 +788,46 @@ export function WorkspaceBrowser({ ...workspaces.map(workspace => workspace.workspaceId as string), ]) }, [actions.retainAccountKeys, workspacePhase, workspaces]) + useEffect(() => { + if (list.phase !== 'ready' || workspaceReady || orderBy !== 'manual' || currentBlank === undefined) return + // A first prompt can end blank pinning before the Workspace baseline arrives. + // Preserve saved members until that baseline can establish departures. + const changed: Record = {} + for (const [key, ids] of Object.entries(activeSessionOrders)) { + if (key !== FLAT_SESSION_ORDER_KEY && workspacePhase !== 'ready') continue + const saved = sessionOrderByAccount[key] ?? [] + if (ids[0] !== currentBlank || saved[0] === currentBlank) continue + changed[key] = [currentBlank, ...saved.filter(id => id !== currentBlank)] + } + if (Object.keys(changed).length > 0) actions.syncSessionOrders(changed) + }, [ + actions.syncSessionOrders, + activeSessionOrders, + currentBlank, + list.phase, + orderBy, + sessionOrderByAccount, + workspacePhase, + workspaceReady, + ]) + useEffect(() => { + if (list.phase !== 'ready' || !workspaceReady || orderBy !== 'manual') return + const changed = Object.fromEntries(Object.entries(activeSessionOrders).filter(([key, ids]) => { + const saved = sessionOrderByAccount[key] + return saved === undefined || saved.length !== ids.length || ids.some((id, index) => id !== saved[index]) + })) + if (Object.keys(changed).length > 0) actions.syncSessionOrders(changed) + }, [ + actions.syncSessionOrders, + activeSessionOrders, + list.phase, + orderBy, + sessionOrderByAccount, + workspaceReady, + ]) + const saveSessionOrder = (accountKey: string, order: readonly string[]): void => { + actions.setSessionOrder(accountKey, order, activeSessionOrders) + } // The query outlives the tree and the input (both wide-only) so collapsing // does not silently drop an in-progress filter. const [query, setQuery] = useState('') @@ -1191,7 +1110,7 @@ export function WorkspaceBrowser({ groupBy={groupBy} orderBy={orderBy} onGroupPick={(mode) => { actions.setGroupBy(mode) }} - onOrderPick={(mode) => { actions.setOrderBy(mode) }} + onOrderPick={(mode) => { actions.setOrderBy(mode, activeSessionOrders) }} t={t} /> )} @@ -1273,15 +1192,12 @@ export function WorkspaceBrowser({ ? ( - /** Shared editable order per Workspace group plus the browser-local flat-list account. */ + /** Saved manual order per Workspace group plus the browser-local flat-list account. */ sessionOrderByAccount: Record - /** Last observed update timestamps per order account for one-time promotion events. */ - sessionUpdatedAtByAccount: Record> } /** @@ -33,16 +31,30 @@ type WorkspaceViewState = { */ type WorkspaceViewActions = { setGroupBy: (draft: WorkspaceViewState, mode: SessionGroupBy) => void - setOrderBy: (draft: WorkspaceViewState, mode: SessionOrderBy) => void + setOrderBy: ( + draft: WorkspaceViewState, + mode: SessionOrderBy, + initialOrders: Readonly>, + ) => void setGroupExpanded: (draft: WorkspaceViewState, key: string, expanded: boolean) => void retainAccountKeys: (draft: WorkspaceViewState, workspaceKeys: readonly string[]) => void - syncSessionOrderAccount: ( + syncSessionOrders: ( + draft: WorkspaceViewState, + orders: Readonly>, + ) => void + setSessionOrder: ( draft: WorkspaceViewState, accountKey: string, - order: string[], - updatedAt: Record, + order: readonly string[], + initialOrders: Readonly>, ) => void - setSessionOrder: (draft: WorkspaceViewState, accountKey: string, order: string[]) => void +} + +/** Copy read-only projections into the persisted mutable store representation. */ +function copySessionOrders( + orders: Readonly>, +): Record { + return Object.fromEntries(Object.entries(orders).map(([key, order]) => [key, [...order]])) } /** @@ -56,12 +68,15 @@ export function createWorkspaceViewStore(): EngineStoreHandle { d.groupBy = mode }, - setOrderBy: (d, mode: SessionOrderBy) => { d.orderBy = mode }, + setOrderBy: (d, mode: SessionOrderBy, initialOrders) => { + if (mode === d.orderBy) return + d.sessionOrderByAccount = mode === 'manual' ? copySessionOrders(initialOrders) : {} + d.orderBy = mode + }, setGroupExpanded: (d, key: string, expanded: boolean) => { d.groupExpansion[key] = expanded }, retainAccountKeys: (d, workspaceKeys: readonly string[]) => { const retained = new Set(workspaceKeys) @@ -71,16 +86,16 @@ export function createWorkspaceViewStore(): EngineStoreHandle retained.has(key)), ) - d.sessionUpdatedAtByAccount = Object.fromEntries( - Object.entries(d.sessionUpdatedAtByAccount).filter(([key]) => retained.has(key)), - ) + delete (d as WorkspaceViewState & { sessionUpdatedAtByAccount?: unknown }).sessionUpdatedAtByAccount }, - syncSessionOrderAccount: (d, accountKey: string, order: string[], updatedAt: Record) => { - d.sessionOrderByAccount[accountKey] = order - d.sessionUpdatedAtByAccount[accountKey] = updatedAt + syncSessionOrders: (d, orders) => { + if (d.orderBy !== 'manual') return + Object.assign(d.sessionOrderByAccount, copySessionOrders(orders)) }, - setSessionOrder: (d, accountKey: string, order: string[]) => { - d.sessionOrderByAccount[accountKey] = order + setSessionOrder: (d, accountKey, order, initialOrders) => { + if (d.orderBy === 'updated') d.sessionOrderByAccount = copySessionOrders(initialOrders) + d.orderBy = 'manual' + d.sessionOrderByAccount[accountKey] = [...order] }, }, }) diff --git a/packages/client/ui-workspace/src/client/tree.ts b/packages/client/ui-workspace/src/client/tree.ts index 37c6d50e38..8a82361f77 100644 --- a/packages/client/ui-workspace/src/client/tree.ts +++ b/packages/client/ui-workspace/src/client/tree.ts @@ -1,7 +1,7 @@ /** - * Derives the workspace browser tree from Host Workspace order and membership. - * Unassigned Sessions trail under Ungrouped; only the selected blank Session - * remains visible. + * Derives the workspace browser tree from caller-projected Workspace and + * Session order. Unassigned Sessions trail under Ungrouped; only the selected + * blank Session remains visible. */ import { type SessionListState, type SessionSearchResultItem, type SessionSummary, @@ -130,10 +130,68 @@ export function workspaceLabel(cwd: string | undefined): string { return base !== '' ? base : cwd } -/** Recency comparator: newest first, id as the deterministic tiebreak (ids are unique per group). */ -function byRecency(a: SessionSummary, b: SessionSummary): number { - if (b.updatedAt !== a.updatedAt) return b.updatedAt - a.updatedAt - return a.id < b.id ? -1 : 1 +/** + * Project known account members by current Session recency. + * @param sessionIds - authoritative account membership. + * @param summaries - current Session summaries; members without a summary are omitted until it arrives. + * @returns known members newest first, with Session identity as the deterministic tie-break. + */ +export function orderByRecency( + sessionIds: readonly SessionId[], + summaries: SessionListState['byId'], +): SessionId[] { + return sessionIds.flatMap((id) => { + const summary = summaries[id] + return summary === undefined ? [] : [{ id, updatedAt: summary.updatedAt }] + }) + .sort((a, b) => { + if (a.updatedAt !== b.updatedAt) return b.updatedAt - a.updatedAt + return a.id < b.id ? -1 : 1 + }) + .map(member => member.id) +} + +/** + * Reconcile a browser-local manual order with current account membership. + * @param memberIds - authoritative account membership. + * @param savedOrder - previously saved browser-local order. + * @param summaries - current Session summaries used to append newly known members by recency. + * @returns retained saved slots followed by newly known members; departed members and unknown new members are omitted. + */ +export function reconcileManualOrder( + memberIds: readonly SessionId[], + savedOrder: readonly string[] | undefined, + summaries: SessionListState['byId'], +): SessionId[] { + const members = new Map(memberIds.map(id => [id as string, id])) + const included = new Set() + const ordered: SessionId[] = [] + for (const key of savedOrder ?? []) { + const id = members.get(key) + if (id === undefined || included.has(key)) continue + ordered.push(id) + included.add(key) + } + for (const id of orderByRecency(memberIds, summaries)) { + if (included.has(id)) continue + ordered.push(id) + included.add(id) + } + return ordered +} + +/** + * Keep the selected provisional New Session ahead of either base order. + * @param order - recency or reconciled manual order. + * @param currentBlank - selected blank Session in this account, when present. + * @returns a copy with the selected blank first and no duplicate slot. + */ +export function pinCurrentBlank( + order: readonly SessionId[], + currentBlank: SessionId | undefined, +): SessionId[] { + if (currentBlank === undefined) return [...order] + return [currentBlank, ...order.filter(id => id !== currentBlank)] } /** @@ -170,38 +228,32 @@ function buildGroup( createdAt: number | undefined, label: string, members: readonly SessionSummary[], - order: 'account' | 'recency', ): Group { - const sessions = [...members] - // Real Workspace order comes from sessionIds. Ungrouped falls back to - // recency until the browser supplies its persisted local order. - if (order === 'recency') sessions.sort(byRecency) - return { key, workspaceId, cwd, createdAt, label, sessions } + return { key, workspaceId, cwd, createdAt, label, sessions: [...members] } } /** Apply a stored Ungrouped order and append newly loose Sessions by recency. */ -function orderedUngrouped(members: readonly SessionSummary[], stored: readonly string[]): SessionSummary[] { +function orderedUngrouped( + members: readonly SessionSummary[], + stored: readonly string[] | undefined, + summaries: SessionListState['byId'], +): SessionSummary[] { const byId = new Map(members.map(session => [session.id as string, session])) - const included = new Set() - const ordered: SessionSummary[] = [] - for (const key of stored) { - const session = byId.get(key) - if (session === undefined || included.has(key)) continue - ordered.push(session) - included.add(key) - } - for (const session of [...members].sort(byRecency)) { - if (included.has(session.id)) continue - ordered.push(session) - } - return ordered + const ids = stored === undefined + ? orderByRecency(members.map(session => session.id), summaries) + : reconcileManualOrder(members.map(session => session.id), stored, summaries) + return ids.flatMap((id) => { + const session = byId.get(id) + /* v8 ignore next -- ids are projected exclusively from the members used to build byId. */ + return session === undefined ? [] : [session] + }) } /** - * Group Sessions by Host Workspace: one group per entity in stable Host - * order, with members resolved from sessionIds in their stored order. Sessions - * outside every Workspace trail in the browser-local Ungrouped order, which - * falls back to recency before that order is initialized. + * Group Sessions by Workspace: one group per caller-ordered entity, with + * members resolved from caller-ordered sessionIds. Sessions outside every + * Workspace trail in the browser-local Ungrouped order, which falls back to + * recency before that order is initialized. */ function groupByWorkspace( list: SessionListState, @@ -222,7 +274,7 @@ function groupByWorkspace( } groups.push(buildGroup( workspace.workspaceId, workspace.workspaceId, workspace.path, - Date.parse(workspace.createdAt), workspace.title, members, 'account', + Date.parse(workspace.createdAt), workspace.title, members, )) } const stray = list.ids @@ -236,8 +288,7 @@ function groupByWorkspace( undefined, undefined, '', - ungroupedOrder === undefined ? stray : orderedUngrouped(stray, ungroupedOrder), - ungroupedOrder === undefined ? 'recency' : 'account', + orderedUngrouped(stray, ungroupedOrder, list.byId), )) } return groups @@ -283,7 +334,7 @@ function sessionNode( * Content search lives outside this derivation * (see {@link deriveSearchResults}). * @param list - sessions list snapshot (`current` feeds containsCurrent). - * @param workspaces - real workspaces in stable Host order. + * @param workspaces - real Workspaces in Host group order with caller-projected Session order. * @param archivedSessionIds - registry-global archive set. * @param pendingInteractions - pending UI interactions by Session. * @param view - local expansion arrays. @@ -323,30 +374,37 @@ export function deriveGroups( } /** - * Derive the flat session list ("In one list" mode): every session — fork - * children included — as a top-level row, strictly newest-first. No grouping, - * no parent/child adjacency. Content search lives outside this derivation - * (see {@link deriveSearchResults}). + * Select flat-list members without deriving row presentation or ordering. * @param list - sessions list snapshot. * @param archivedSessionIds - registry-global archive set. + * @returns known visible Session ids in list order, including ordinary forks and only the current blank. + */ +export function visibleSessionIds( + list: SessionListState, + archivedSessionIds: readonly SessionId[], +): SessionId[] { + const archived = new Set(archivedSessionIds) + return list.ids.filter((id) => { + const s = list.byId[id] + return s !== undefined && sessionVisible(s, list.current, archived) + }) +} + +/** + * Derive flat rows from the browser's ordered visible Session ids. + * @param list - sessions list snapshot used to select the ids. + * @param sessionIds - known visible members in render order, including any pinned blank. * @param pendingInteractions - pending UI interactions by Session. - * @returns flat rows in render order. + * @returns flat rows in the supplied order with current status indicators. */ export function deriveFlat( list: SessionListState, - archivedSessionIds: readonly SessionId[], + sessionIds: readonly SessionId[], pendingInteractions: SessionPendingInteractions, ): SessionNode[] { - const archived = new Set(archivedSessionIds) const descendants = indexSubagentDescendants(list.byId) - const rows: SessionSummary[] = [] - for (const id of list.ids) { - const s = list.byId[id] - if (s === undefined || !sessionVisible(s, list.current, archived)) continue - rows.push(s) - } - rows.sort(byRecency) - return rows.map(session => sessionNode(session, descendants, pendingInteractions)) + return sessionIds + .map(id => sessionNode(list.byId[id] as SessionSummary, descendants, pendingInteractions)) } /** @@ -402,7 +460,9 @@ export function deriveSearchResults( local.push(summary) } } - local.sort(byRecency) + const localById = new Map(local.map(summary => [summary.id, summary])) + const orderedLocal = orderByRecency(local.map(summary => summary.id), list.byId) + .map(id => localById.get(id) as SessionSummary) const ordered: SessionSummary[] = [] const included = new Set() @@ -411,7 +471,7 @@ export function deriveSearchResults( included.add(summary.id) ordered.push(summary) } - for (const summary of local) include(summary) + for (const summary of orderedLocal) include(summary) for (const item of content.items) { const summary = list.byId[item.sessionId] if (summary !== undefined && !summary.blank && sessionVisible(summary, list.current, archived)) include(summary) diff --git a/packages/client/ui-workspace/tests/apply.client.spec.ts b/packages/client/ui-workspace/tests/apply.client.spec.ts index 8035b099a1..101dd0d266 100644 --- a/packages/client/ui-workspace/tests/apply.client.spec.ts +++ b/packages/client/ui-workspace/tests/apply.client.spec.ts @@ -18,7 +18,6 @@ async function bench() { title: 'new', sessionIds: [], createdAt: '0', updatedAt: '0', })) const rename = vi.fn(async () => ({})) - const insertSessionBefore = vi.fn(async () => ({})) const open = vi.fn() const clear = vi.fn() const selectPanel = vi.fn() @@ -43,7 +42,7 @@ async function bench() { delete: vi.fn(async () => undefined), insertBefore: vi.fn(async () => undefined), archiveSession: vi.fn(async () => undefined), - insertSessionBefore, + insertSessionBefore: vi.fn(async () => ({})), } as never) ctx.provide('sessions', { list: { @@ -73,7 +72,7 @@ async function bench() { ctx.provide('locale', locale) return { ctx, slots: ctx.get('slots') as SlotRegistry, locale, create, rename, - insertSessionBefore, open, clear, selectPanel, search, renameSession, binding, fork, pickDirectory, + open, clear, selectPanel, search, renameSession, binding, fork, pickDirectory, } } @@ -145,8 +144,6 @@ describe('ui-workspace apply', () => { expect(b.fork).toHaveBeenCalledWith({ sessionId: 'session', increaseTitle: true }) await browser.renameWorkspace('ws' as never, 'renamed') expect(b.rename).toHaveBeenCalledWith('ws', 'renamed') - await browser.insertSessionBefore('ws' as never, 's1' as never, 's2' as never) - expect(b.insertSessionBefore).toHaveBeenCalledWith('ws', 's1', 's2') await browser.createWorkspace({ path: '/tmp/browser-project' }) expect(b.create).toHaveBeenCalledWith({ path: '/tmp/browser-project' }) diff --git a/packages/client/ui-workspace/tests/tree.client.spec.ts b/packages/client/ui-workspace/tests/tree.client.spec.ts index 8f5326134b..0443c291ab 100644 --- a/packages/client/ui-workspace/tests/tree.client.spec.ts +++ b/packages/client/ui-workspace/tests/tree.client.spec.ts @@ -5,8 +5,8 @@ import type { SessionPendingInteractionBase } from '@deepseek-ai/dsh-client-ui-s import type { ScheduleId, ScheduleRecord } from '@deepseek-ai/dsh-schedule/client' import type { SessionId } from '@deepseek-ai/dsh-session/types' import { - deriveFlat, deriveGroups, deriveSearchResults, owningGroupKey, workspaceLabel, - UNGROUPED_KEY, + deriveFlat, deriveGroups, deriveSearchResults, orderByRecency, owningGroupKey, + pinCurrentBlank, reconcileManualOrder, visibleSessionIds, workspaceLabel, UNGROUPED_KEY, } from '../src/client/tree.ts' import { createWorkspaceViewStore } from '../src/client/stores.ts' @@ -48,8 +48,36 @@ describe('owningGroupKey', () => { }) }) +describe('Session ordering', () => { + it('orders known members by recency with a stable identity tie-break', () => { + const summaries = list(summary('tie-b', 20), summary('older', 10), summary('tie-a', 20)).byId + expect(orderByRecency( + [sid('unknown'), sid('tie-b'), sid('older'), sid('tie-a')], + summaries, + )).toEqual([sid('tie-a'), sid('tie-b'), sid('older')]) + }) + + it('reconciles retained manual slots and appends newly known members by recency', () => { + const summaries = list(summary('kept', 1), summary('newer', 30), summary('older', 20)).byId + expect(reconcileManualOrder( + [sid('saved-without-summary'), sid('kept'), sid('newer'), sid('older'), sid('new-without-summary')], + ['departed', 'saved-without-summary', 'kept'], + summaries, + )).toEqual([ + sid('saved-without-summary'), sid('kept'), sid('newer'), sid('older'), + ]) + }) + + it('pins only the selected blank without changing the base order', () => { + expect(pinCurrentBlank([sid('newer'), sid('blank'), sid('older')], sid('blank'))) + .toEqual([sid('blank'), sid('newer'), sid('older')]) + expect(pinCurrentBlank([sid('newer'), sid('older')], undefined)) + .toEqual([sid('newer'), sid('older')]) + }) +}) + describe('deriveGroups', () => { - it('keeps Host Workspace and sessionIds order without Client recency sorting', () => { + it('keeps caller-supplied Workspace and sessionIds order', () => { const sessions = list(summary('newer', 20), summary('older', 10)) const workspaces = [workspace('first', ['older', 'newer']), workspace('empty', [])] const groups = deriveGroups(sessions, workspaces, noArchive, noAttention, view(['first'])) @@ -68,7 +96,7 @@ describe('deriveGroups', () => { sessions, [workspace('project', ['awaiting'])], noArchive, attention, view(['project']), ) expect(grouped[0]!.sessions[0]).toMatchObject({ pendingInteraction: 'plan-review', running: true }) - expect(deriveFlat(sessions, noArchive, attention)[0]) + expect(deriveFlat(sessions, visibleSessionIds(sessions, noArchive), attention)[0]) .toMatchObject({ pendingInteraction: 'plan-review', running: true }) }) @@ -81,7 +109,7 @@ describe('deriveGroups', () => { { key: `${kind}:1`, kind, sessionId: awaiting.id }, ]]) - expect(deriveFlat(list(awaiting), noArchive, attention)[0]?.pendingInteraction).toBe(kind) + expect(deriveFlat(list(awaiting), [awaiting.id], attention)[0]?.pendingInteraction).toBe(kind) }, ) @@ -147,7 +175,8 @@ describe('deriveGroups', () => { const plainNode = groups[0]!.sessions.find(session => session.id === plain.id)! expect(doneNode.completed).toBe(true) expect(plainNode.completed).toBe(false) - expect(deriveFlat(sessions, noArchive, noAttention).find(node => node.id === done.id)!.completed).toBe(true) + expect(deriveFlat(sessions, visibleSessionIds(sessions, noArchive), noAttention) + .find(node => node.id === done.id)!.completed).toBe(true) const search = deriveSearchResults( sessions, [workspace('first', ['done', 'plain'])], 'done', noArchive, noAttention, { items: [], hasMore: false }, 10, @@ -178,7 +207,7 @@ describe('deriveGroups', () => { expect(deriveGroups( sessions, workspaces, noArchive, noAttention, view(['project']), )[0]!.sessions.map(node => [node.id, node.hasActiveSchedule])).toEqual(expected) - expect(deriveFlat(sessions, noArchive, noAttention) + expect(deriveFlat(sessions, visibleSessionIds(sessions, noArchive), noAttention) .map(node => [node.id, node.hasActiveSchedule])).toEqual(expected) expect(deriveSearchResults( sessions, workspaces, 'project', noArchive, noAttention, { items: [], hasMore: false }, 10, @@ -210,8 +239,9 @@ describe('deriveGroups', () => { expect(groups[0]!.sessionCount).toBe(2) expect(groups[0]!.sessions[0]).toMatchObject({ running: false, runningSubagentCount: 2 }) expect(groups[0]!.sessions[1]).toMatchObject({ running: false, runningSubagentCount: 1 }) - expect(deriveFlat(sessions, noArchive, noAttention).map(node => [node.id, node.runningSubagentCount])).toEqual([ - [fork.id, 1], [parent.id, 2], + expect(deriveFlat(sessions, visibleSessionIds(sessions, noArchive), noAttention) + .map(node => [node.id, node.runningSubagentCount])).toEqual([ + [parent.id, 2], [fork.id, 1], ]) expect(deriveSearchResults( sessions, [workspace('first', ['parent', 'fork'])], 'parent', noArchive, @@ -294,40 +324,36 @@ describe('deriveGroups', () => { }) describe('deriveFlat', () => { - it('flattens every session — fork children included — newest-first with id tiebreak', () => { + it('renders ordinary forks in the supplied order regardless of timestamps', () => { const parent = summary('parent', 10) const child = { ...summary('child', 30), parentId: parent.id } const tieB = summary('tie-b', 20) const tieA = summary('tie-a', 20) - const rows = deriveFlat(list(parent, child, tieB, tieA), noArchive, noAttention) - expect(rows.map(row => row.id)).toEqual([sid('child'), sid('tie-a'), sid('tie-b'), sid('parent')]) + const rows = deriveFlat(list(parent, child, tieB, tieA), [parent.id, tieA.id, child.id, tieB.id], noAttention) + expect(rows.map(row => row.id)).toEqual([parent.id, tieA.id, child.id, tieB.id]) }) it('hides subagent-origin rows but keeps ordinary forks', () => { const parent = summary('parent', 1) const fork = { ...summary('fork', 2), parentId: parent.id } const subagent = { ...summary('subagent', 3), parentId: parent.id, origin: 'subagent' as const } - const rows = deriveFlat( - { ...list(parent, fork, subagent), current: subagent.id }, - noArchive, - noAttention, - ) - expect(rows.map(row => row.id)).toEqual([fork.id, parent.id]) + const ids = visibleSessionIds({ ...list(parent, fork, subagent), current: subagent.id }, noArchive) + expect(ids).toEqual([parent.id, fork.id]) }) it('tolerates ids whose summary has not landed yet', () => { const partial: SessionListState = { ...list(summary('present', 1)), ids: [sid('ghost'), sid('present')] } - expect(deriveFlat(partial, noArchive, noAttention).map(row => row.id)).toEqual([sid('present')]) + expect(visibleSessionIds(partial, noArchive)).toEqual([sid('present')]) }) it('shows only the current blank session and excludes blanks from search', () => { const currentBlank = { ...summary('current-blank', 9), blank: true } const staleBlank = { ...summary('stale-blank', 8), blank: true } const sessions = { - ...list(summary('real', 1), currentBlank, staleBlank), + ...list(currentBlank, summary('real', 1), staleBlank), current: currentBlank.id, } - const rows = deriveFlat(sessions, noArchive, noAttention) + const rows = deriveFlat(sessions, visibleSessionIds(sessions, noArchive), noAttention) expect(rows.map(row => row.id)).toEqual([currentBlank.id, sid('real')]) expect(rows.map(row => row.title)).toEqual(['', 'real']) expect(rows.map(row => row.blank)).toEqual([true, false]) @@ -336,7 +362,7 @@ describe('deriveFlat', () => { it('hides archived sessions in flat mode', () => { const kept = summary('kept', 1) const gone = summary('gone', 2) - expect(deriveFlat(list(kept, gone), archived('gone'), noAttention).map(row => row.id)).toEqual([kept.id]) + expect(visibleSessionIds(list(kept, gone), archived('gone'))).toEqual([kept.id]) }) }) @@ -490,38 +516,64 @@ describe('deriveSearchResults', () => { }) describe('createWorkspaceViewStore', () => { - it('stores grouping, ordering, Workspace expansion, and recent-session view order', () => { + it('stores view preferences and selects Manual when saving a drag order', () => { const store = createWorkspaceViewStore().create() expect(store.getSnapshot().groupBy).toBe('workspace') expect(store.getSnapshot().orderBy).toBe('updated') store.actions.setGroupBy('flat') - store.actions.setOrderBy('updated') + store.actions.setOrderBy('updated', {}) store.actions.setGroupExpanded('alpha', true) - store.actions.syncSessionOrderAccount('alpha', ['two', 'one'], { one: 1, two: 2 }) - store.actions.setSessionOrder('alpha', ['one', 'two']) + store.actions.setSessionOrder('alpha', ['one', 'two'], {}) expect(store.getSnapshot().groupBy).toBe('flat') expect(store.getSnapshot()).toMatchObject({ - orderBy: 'updated', + orderBy: 'manual', groupExpansion: { alpha: true }, sessionOrderByAccount: { alpha: ['one', 'two'] }, - sessionUpdatedAtByAccount: { alpha: { one: 1, two: 2 } }, }) }) + it('retains positions when reselecting Manual and snapshots the supplied order on mode switches', () => { + const store = createWorkspaceViewStore().create() + store.actions.setSessionOrder('alpha', ['two', 'one'], {}) + store.actions.setOrderBy('manual', {}) + expect(store.getSnapshot()).toMatchObject({ orderBy: 'manual', sessionOrderByAccount: { alpha: ['two', 'one'] } }) + store.actions.setOrderBy('updated', {}) + expect(store.getSnapshot()).toMatchObject({ orderBy: 'updated', sessionOrderByAccount: {} }) + store.actions.setOrderBy('manual', { alpha: ['one', 'two'] }) + expect(store.getSnapshot().sessionOrderByAccount).toEqual({ alpha: ['one', 'two'] }) + }) + + it('snapshots every account when a recency drag selects Manual', () => { + const store = createWorkspaceViewStore().create() + store.actions.setSessionOrder('alpha', ['two', 'one'], { + alpha: ['one', 'two'], + beta: ['three'], + }) + expect(store.getSnapshot()).toMatchObject({ + orderBy: 'manual', + sessionOrderByAccount: { alpha: ['two', 'one'], beta: ['three'] }, + }) + }) + + it('ignores reconciliation writes outside Manual', () => { + const store = createWorkspaceViewStore().create() + store.actions.syncSessionOrders({ alpha: ['one'] }) + expect(store.getSnapshot().sessionOrderByAccount).toEqual({}) + }) + it('removes view state outside the retained Workspace key set', () => { const store = createWorkspaceViewStore().create() store.actions.setGroupExpanded('', true) store.actions.setGroupExpanded('alpha', true) store.actions.setGroupExpanded('deleted', true) - store.actions.syncSessionOrderAccount('alpha', ['alpha-session'], { 'alpha-session': 2 }) - store.actions.syncSessionOrderAccount('deleted', ['deleted-session'], { 'deleted-session': 1 }) + store.actions.setSessionOrder('alpha', ['alpha-session'], {}) + store.actions.setSessionOrder('deleted', ['deleted-session'], {}) store.actions.retainAccountKeys(['', 'alpha']) const snapshot = store.getSnapshot() expect(snapshot.groupExpansion).toEqual({ '': true, alpha: true }) expect(snapshot.sessionOrderByAccount).toEqual({ alpha: ['alpha-session'] }) - expect(snapshot.sessionUpdatedAtByAccount).toEqual({ alpha: { 'alpha-session': 2 } }) }) }) diff --git a/packages/client/ui-workspace/tests/workspace-browser.client.spec.tsx b/packages/client/ui-workspace/tests/workspace-browser.client.spec.tsx index b25b32e166..ae62df5324 100644 --- a/packages/client/ui-workspace/tests/workspace-browser.client.spec.tsx +++ b/packages/client/ui-workspace/tests/workspace-browser.client.spec.tsx @@ -25,7 +25,7 @@ afterEach(cleanup) const scrollIntoView = vi.fn() beforeEach(() => { localStorage.clear() - createWorkspaceViewStore().create().actions.setOrderBy('manual') + createWorkspaceViewStore().create().actions.setOrderBy('manual', {}) Element.prototype.scrollIntoView = scrollIntoView scrollIntoView.mockClear() }) @@ -94,7 +94,6 @@ function mount(overrides: Partial = {}) { deleteWorkspace: vi.fn(async () => {}), archiveSession: vi.fn(async () => {}), insertWorkspaceBefore: vi.fn(async () => {}), - insertSessionBefore: vi.fn(async () => {}), createWorkspace: vi.fn(async () => workspace('created', [])), useDirectoryFlow: bindSnapshotSelector({ getSnapshot: () => true, subscribe: () => () => {} }), useHostInfo: selector => selector({ home: undefined, isLoopback: true }), @@ -113,6 +112,108 @@ function rerender(b: ReturnType, overrides: Partial { + it.each(['workspace', 'flat', 'ungrouped'] as const)('keeps %s recency independent of arrival order and saved manual positions', (mode) => { + localStorage.clear() + const preferences = createWorkspaceViewStore().create() + preferences.actions.setGroupBy(mode === 'flat' ? 'flat' : 'workspace') + const account = mode === 'ungrouped' ? UNGROUPED_KEY : 'alpha' + preferences.actions.setSessionOrder(account, ['older', 'newer'], {}) + preferences.actions.setSessionOrder(UNGROUPED_KEY, ['older', 'newer'], {}) + preferences.actions.setSessionOrder(FLAT_SESSION_ORDER_KEY, ['older', 'newer'], {}) + preferences.actions.setGroupExpanded(account, true) + preferences.actions.setGroupExpanded(UNGROUPED_KEY, true) + localStorage.setItem('dsh.workspace.view.v5', JSON.stringify({ ...preferences.getSnapshot(), orderBy: 'updated' })) + const b = mount({ + useSessions: hook(sessionState([summary('newer', 100)])), + useWorkspaces: hook(workspaceState(mode === 'ungrouped' ? [] : [workspace(account, ['older', 'newer'])])), + }) + const names = () => screen.getAllByRole('treeitem') + .filter(row => row.getAttribute('aria-expanded') === null) + .map(row => row.textContent?.includes('newer') ? 'newer' : 'older') + expect(names()).toEqual(['newer']) + rerender(b, { useSessions: hook(sessionState([summary('older', 20), summary('newer', 100)])) }) + expect(names()).toEqual(['newer', 'older']) + rerender(b, { useSessions: hook(sessionState([summary('older', 80), summary('newer', 100)])) }) + expect(names()).toEqual(['newer', 'older']) + rerender(b, { useSessions: hook(sessionState([summary('older', 120), summary('newer', 100)])) }) + expect(names()).toEqual(['older', 'newer']) + rerender(b, { useSessions: hook(sessionState([summary('older', 80), summary('newer', 100)])) }) + expect(names()).toEqual(['newer', 'older']) + expect(b.store.getSnapshot().sessionOrderByAccount[account]).toEqual(['older', 'newer']) + b.view.unmount() + const restored = mount({ + useSessions: hook(sessionState([summary('older', 80), summary('newer', 100)])), + useWorkspaces: b.props.useWorkspaces, + }) + expect(names()).toEqual(['newer', 'older']) + act(() => { restored.store.actions.setOrderBy('manual', {}) }) + expect(names()).toEqual(['newer', 'older']) + }) + + it.each(['workspace', 'flat', 'ungrouped'] as const)('starts Manual from the current %s order and forgets discarded layouts', (mode) => { + localStorage.clear() + const preferences = createWorkspaceViewStore().create() + preferences.actions.setGroupBy(mode === 'flat' ? 'flat' : 'workspace') + const account = mode === 'flat' ? FLAT_SESSION_ORDER_KEY : mode === 'ungrouped' ? UNGROUPED_KEY : 'alpha' + preferences.actions.setGroupExpanded(account, true) + const b = mount({ + useSessions: hook(sessionState([summary('a', 30), summary('b', 20), summary('c', 10)])), + useWorkspaces: hook(workspaceState(mode === 'ungrouped' ? [] : [workspace('alpha', ['c', 'a', 'b'])])), + }) + const names = () => screen.getAllByRole('treeitem').filter(row => row.getAttribute('aria-expanded') === null) + .map(row => row.querySelector('[class*="title"]')?.textContent) + const pick = (name: string) => { + fireEvent.click(screen.getByRole('button', { name: '视图选项' })) + fireEvent.click(screen.getByRole('menuitem', { name })) + } + expect(names()).toEqual(['a', 'b', 'c']) + pick('手动排序') + expect(names()).toEqual(['a', 'b', 'c']) + const source = screen.getByText('c').closest('[role="treeitem"]') as HTMLElement + const target = screen.getByText('a').closest('[role="treeitem"]') as HTMLElement + target.getBoundingClientRect = () => ({ + top: 100, bottom: 134, left: 0, right: 200, width: 200, height: 34, x: 0, y: 100, toJSON: () => ({}), + }) + fireEvent.dragStart(source, { dataTransfer: dragData() }) + fireDrag(target, 'drop', 102) + expect(names()).toEqual(['c', 'a', 'b']) + rerender(b, { useSessions: hook(sessionState([summary('a', 30), summary('b', 40), summary('c', 10)])) }) + expect(names()).toEqual(['c', 'a', 'b']) + b.view.unmount() + const restored = mount({ useSessions: b.props.useSessions, useWorkspaces: b.props.useWorkspaces }) + expect(names()).toEqual(['c', 'a', 'b']) + expect(restored.store.getSnapshot().orderBy).toBe('manual') + pick('最近更新') + expect(names()).toEqual(['b', 'a', 'c']) + pick('手动排序') + expect(names()).toEqual(['b', 'a', 'c']) + }) + + it.each(['workspace', 'flat'] as const)('keeps the provisional blank first and time ties stable in %s recency', (groupBy) => { + localStorage.clear() + const preferences = createWorkspaceViewStore().create() + preferences.actions.setGroupBy(groupBy) + preferences.actions.setGroupExpanded('alpha', true) + const b = mount({ + useSessions: hook(sessionState([ + summary('tie-b', 100), summary('blank', 1, { blank: true }), summary('tie-a', 100), + ], { current: sid('blank') })), + useWorkspaces: hook(workspaceState([workspace('alpha', ['missing', 'tie-b', 'blank', 'tie-a'])])), + }) + const rows = () => screen.getAllByRole('treeitem').filter(row => row.getAttribute('aria-expanded') === null) + expect(rows().map(row => row.textContent)).toEqual([ + expect.stringContaining('新会话'), expect.stringContaining('tie-a'), expect.stringContaining('tie-b'), + ]) + expect(b.store.getSnapshot().sessionOrderByAccount).toEqual({}) + rerender(b, { useSessions: hook(sessionState([ + summary('tie-b', 100), summary('blank', 1), summary('tie-a', 100), + ], { current: sid('blank') })) }) + expect(rows().map(row => row.textContent)).toEqual([ + expect.stringContaining('tie-a'), expect.stringContaining('tie-b'), expect.stringContaining('blank'), + ]) + expect(b.store.getSnapshot().orderBy).toBe('updated') + }) + it('moves focus into Workspace controls without selecting a Session while a main panel is active', () => { const panelInfo = { activePanelId: 'panel-a' as MainPanelId } const b = mount({ @@ -165,15 +266,111 @@ describe('WorkspaceBrowser', () => { const b = mount({ useWorkspaces: hook(pending) }) act(() => { b.store.actions.setGroupExpanded('deleted', true) - b.store.actions.syncSessionOrderAccount('deleted', ['session'], { session: 1 }) + b.store.actions.setSessionOrder('deleted', ['session'], {}) }) expect(b.store.getSnapshot().groupExpansion).toEqual({ deleted: true }) rerender(b, { useWorkspaces: hook(workspaceState([])) }) await waitFor(() => { expect(b.store.getSnapshot().groupExpansion).toEqual({}) - expect(b.store.getSnapshot().sessionOrderByAccount).toEqual({ [UNGROUPED_KEY]: [] }) - expect(b.store.getSnapshot().sessionUpdatedAtByAccount).toEqual({ [UNGROUPED_KEY]: {} }) + expect(b.store.getSnapshot().sessionOrderByAccount).toEqual({ + [UNGROUPED_KEY]: [], + [FLAT_SESSION_ORDER_KEY]: [], + }) + }) + }) + + it.each(['workspace', 'flat', 'ungrouped'] as const)('retains the new Session position through a %s Workspace reconnect', (mode) => { + const account = mode === 'flat' ? FLAT_SESSION_ORDER_KEY : mode === 'ungrouped' ? UNGROUPED_KEY : 'alpha' + const old = summary('old', 10) + const absent = summary('absent', 5) + const blank = summary('blank', 1, { blank: true }) + const groups = (ids: string[]) => workspaceState(mode === 'ungrouped' ? [] : [workspace('alpha', ids)]) + const preferences = createWorkspaceViewStore().create() + preferences.actions.setGroupBy(mode === 'flat' ? 'flat' : 'workspace') + preferences.actions.setGroupExpanded(account, true) + preferences.actions.setSessionOrder(account, ['old', 'absent'], {}) + const b = mount({ + useSessions: hook(sessionState([old, absent])), + useWorkspaces: hook(groups(['old', 'absent'])), + }) + const names = () => screen.getAllByRole('treeitem') + .filter(row => row.getAttribute('aria-expanded') === null) + .map(row => row.textContent) + rerender(b, { + useSessions: hook(sessionState([old, blank], { current: blank.id })), + useWorkspaces: hook({ ...groups(['old', 'blank']), state: 'loading' }), + }) + expect(names()).toEqual([expect.stringContaining('新会话'), expect.stringContaining('old')]) + expect(b.store.getSnapshot().sessionOrderByAccount[account]).toEqual(['blank', 'old', 'absent']) + + rerender(b, { + useSessions: hook(sessionState([old, { ...blank, blank: false, updatedAt: 20 }], { current: blank.id })), + }) + expect(names()).toEqual([expect.stringContaining('blank'), expect.stringContaining('old')]) + expect(b.store.getSnapshot().sessionOrderByAccount[account]).toEqual(['blank', 'old', 'absent']) + + rerender(b, { + useSessions: hook(sessionState([old, absent, { ...blank, blank: false, updatedAt: 20 }], { current: blank.id })), + useWorkspaces: hook(groups(['old', 'absent', 'blank'])), + }) + expect(names()).toEqual([ + expect.stringContaining('blank'), expect.stringContaining('old'), expect.stringContaining('absent'), + ]) + expect(b.store.getSnapshot().sessionOrderByAccount[account]).toEqual(['blank', 'old', 'absent']) + rerender(b, { + useSessions: hook(sessionState([old, { ...blank, blank: false, updatedAt: 20 }], { current: blank.id })), + useWorkspaces: hook(groups(['old', 'blank'])), + }) + expect(b.store.getSnapshot().sessionOrderByAccount[account]).toEqual(['blank', 'old']) + }) + + it('reconciles a late blank and its first prompt while the sidebar is collapsed', async () => { + const old = summary('old', 10) + const blank = summary('blank', 1, { blank: true }) + const b = mount({ + wide: false, + useSessions: hook(sessionState([old])), + useWorkspaces: hook(workspaceState([workspace('alpha', ['old', 'blank'])])), + }) + await waitFor(() => { + expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['old']) + }) + + rerender(b, { + useSessions: hook(sessionState([old, blank], { current: blank.id })), + }) + await waitFor(() => { + expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['blank', 'old']) + }) + + rerender(b, { + useSessions: hook(sessionState([old, { ...blank, blank: false, updatedAt: 20 }], { current: blank.id })), + }) + await waitFor(() => { + expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['blank', 'old']) + }) + + rerender(b, { wide: true }) + expect(screen.getAllByRole('treeitem').slice(1).map(row => row.textContent)).toEqual([ + expect.stringContaining('blank'), + expect.stringContaining('old'), + ]) + }) + + it('drops the obsolete timestamp ledger from persisted viewing state', async () => { + localStorage.clear() + localStorage.setItem('dsh.workspace.view.v5', JSON.stringify({ + groupBy: 'workspace', + orderBy: 'manual', + groupExpansion: {}, + sessionOrderByAccount: {}, + sessionUpdatedAtByAccount: { alpha: { old: 10 } }, + })) + mount() + await waitFor(() => { + const persisted = JSON.parse(localStorage.getItem('dsh.workspace.view.v5') ?? '{}') as Record + expect(persisted).not.toHaveProperty('sessionUpdatedAtByAccount') }) }) @@ -219,7 +416,6 @@ describe('WorkspaceBrowser', () => { }) it('persists flat-list drag order locally and applies Last updated within that account', async () => { - const insertSessionBefore = vi.fn(async () => {}) const sessions = sessionState([summary('one', 3), summary('two', 2), summary('three', 1)]) const workspaces = workspaceState([ workspace('alpha', ['one']), @@ -228,7 +424,6 @@ describe('WorkspaceBrowser', () => { const b = mount({ useSessions: hook(sessions), useWorkspaces: hook(workspaces), - insertSessionBefore, }) fireEvent.click(screen.getByRole('button', { name: '视图选项' })) fireEvent.click(screen.getByRole('menuitem', { name: '单列表' })) @@ -247,13 +442,14 @@ describe('WorkspaceBrowser', () => { fireDrag(three, 'drop', 180) expect(b.store.getSnapshot().sessionOrderByAccount[FLAT_SESSION_ORDER_KEY]) .toEqual(['two', 'three', 'one']) - expect(insertSessionBefore).not.toHaveBeenCalled() fireEvent.click(screen.getByRole('button', { name: '视图选项' })) fireEvent.click(screen.getByRole('menuitem', { name: '最近更新' })) await waitFor(() => { - expect(b.store.getSnapshot().sessionOrderByAccount[FLAT_SESSION_ORDER_KEY]) - .toEqual(['one', 'two', 'three']) + expect(screen.getAllByRole('treeitem').map(row => row.textContent)).toEqual([ + expect.stringContaining('one'), expect.stringContaining('two'), expect.stringContaining('three'), + ]) + expect(b.store.getSnapshot().sessionOrderByAccount).toEqual({}) }) fireEvent.click(screen.getByRole('button', { name: '视图选项' })) @@ -336,55 +532,38 @@ describe('WorkspaceBrowser', () => { expect(screen.getByRole('button', { name: '展开其余 2 个会话' })).toBeTruthy() }) - it('anchors collapsed drags before hidden rows so the source stays visible', async () => { + it('pins the blank while collapsed drags keep an ordinary source visible', async () => { const ordinary = Array.from({ length: 6 }, (_, index) => summary(`session-${index + 1}`, 6 - index)) const blank = summary('blank', 7, { blank: true }) - const insertSessionBefore = vi.fn(async () => {}) const b = mount({ useSessions: hook(sessionState([blank, ...ordinary], { current: blank.id })), useWorkspaces: hook(workspaceState([workspace('alpha', [blank.id, ...ordinary.map(item => item.id)])])), - insertSessionBefore, }) await waitFor(() => { expect(b.store.getSnapshot().sessionOrderByAccount.alpha) .toEqual(['blank', 'session-1', 'session-2', 'session-3', 'session-4', 'session-5', 'session-6']) }) - fireEvent.click(screen.getByRole('button', { name: '展开其余 1 个会话' })) const blankRow = screen.getByText('新会话').closest('[role="treeitem"]') as HTMLElement - const session6 = screen.getByText('session-6').closest('[role="treeitem"]') as HTMLElement - session6.getBoundingClientRect = () => ({ - top: 200, bottom: 234, left: 0, right: 200, width: 200, height: 34, - x: 0, y: 200, toJSON: () => ({}), - }) + expect(blankRow.draggable).toBe(false) fireEvent.dragStart(blankRow, { dataTransfer: dragData() }) - fireDrag(session6, 'drop', 230) expect(b.store.getSnapshot().sessionOrderByAccount.alpha) - .toEqual(['session-1', 'session-2', 'session-3', 'session-4', 'session-5', 'session-6', 'blank']) + .toEqual(['blank', 'session-1', 'session-2', 'session-3', 'session-4', 'session-5', 'session-6']) - insertSessionBefore.mockClear() - fireEvent.click(screen.getByRole('button', { name: '收起' })) - const collapsedBlank = screen.getByText('新会话').closest('[role="treeitem"]') as HTMLElement - collapsedBlank.getBoundingClientRect = () => ({ + blankRow.getBoundingClientRect = () => ({ top: 200, bottom: 234, left: 0, right: 200, width: 200, height: 34, x: 0, y: 200, toJSON: () => ({}), }) const session5 = screen.getByText('session-5').closest('[role="treeitem"]') as HTMLElement fireEvent.dragStart(session5, { dataTransfer: dragData() }) - fireDrag(collapsedBlank, 'drop', 205) - expect(insertSessionBefore).not.toHaveBeenCalled() - - const session4 = screen.getByText('session-4').closest('[role="treeitem"]') as HTMLElement - fireEvent.dragStart(session4, { dataTransfer: dragData() }) - fireDrag(collapsedBlank, 'drop', 205) + fireDrag(blankRow, 'drop', 205) expect(b.store.getSnapshot().sessionOrderByAccount.alpha) - .toEqual(['session-1', 'session-2', 'session-3', 'session-5', 'session-4', 'session-6', 'blank']) - expect(insertSessionBefore).toHaveBeenCalledWith(wid('alpha'), sid('session-4'), sid('session-6')) - expect(screen.getByText('session-4')).toBeTruthy() + .toEqual(['blank', 'session-5', 'session-1', 'session-2', 'session-3', 'session-4', 'session-6']) + expect(screen.getByText('session-5')).toBeTruthy() expect(screen.queryByText('session-6')).toBeNull() }) - it('shares one editable order across modes and promotes only while Last updated is active', async () => { + it('discards manual positions on recency selection and switches to Manual on drag', async () => { const initial = sessionState([summary('one', 3), summary('two', 2)]) const b = mount({ useSessions: hook(initial), @@ -404,37 +583,33 @@ describe('WorkspaceBrowser', () => { top: 150, bottom: 184, left: 0, right: 200, width: 200, height: 34, x: 0, y: 150, toJSON: () => ({}), }) fireEvent.dragStart(one, { dataTransfer: dragData() }) + fireDrag(one, 'drop', 180) + expect(b.store.getSnapshot().orderBy).toBe('updated') + fireEvent.dragStart(one, { dataTransfer: dragData() }) fireDrag(two, 'drop', 180) + expect(b.store.getSnapshot().orderBy).toBe('manual') expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['two', 'one']) fireEvent.click(screen.getByRole('button', { name: '视图选项' })) fireEvent.click(screen.getByRole('menuitem', { name: '手动排序' })) expect(screen.getAllByRole('treeitem').slice(1)[0]?.textContent).toContain('two') - // User activity updates the timestamp baseline in Manual mode without - // changing the shared visual order. const updated = sessionState([summary('one', 4), summary('two', 2)]) rerender(b, { useSessions: hook(updated) }) - await waitFor(() => { - expect(b.store.getSnapshot().sessionUpdatedAtByAccount.alpha).toEqual({ one: 4, two: 2 }) - }) expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['two', 'one']) expect(screen.getAllByRole('treeitem').slice(1)[0]?.textContent).toContain('two') - // Entering Last updated performs one complete recency sort. fireEvent.click(screen.getByRole('button', { name: '视图选项' })) fireEvent.click(screen.getByRole('menuitem', { name: '最近更新' })) await waitFor(() => { - expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['one', 'two']) + expect(b.store.getSnapshot().sessionOrderByAccount).toEqual({}) expect(screen.getAllByRole('treeitem').slice(1)[0]?.textContent).toContain('one') }) - // A later user activity timestamp promotes that Session once while the - // mode remains active. const promoted = sessionState([summary('one', 4), summary('two', 5)]) rerender(b, { useSessions: hook(promoted) }) await waitFor(() => { - expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['two', 'one']) + expect(b.store.getSnapshot().sessionOrderByAccount).toEqual({}) expect(screen.getAllByRole('treeitem').slice(1)[0]?.textContent).toContain('two') }) @@ -443,7 +618,7 @@ describe('WorkspaceBrowser', () => { useSessions: hook(promoted), useWorkspaces: hook(workspaceState([workspace('alpha', ['two', 'one'])])), }) - expect(restored.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['two', 'one']) + expect(restored.store.getSnapshot().sessionOrderByAccount).toEqual({}) expect(screen.getAllByRole('treeitem').slice(1)[0]?.textContent).toContain('two') }) @@ -579,70 +754,70 @@ describe('WorkspaceBrowser', () => { expect(screen.queryByText('新会话')).toBeNull() }) - it('promotes the blank selected by New Session in its grouped and flat orders', async () => { - const items = [ - summary('old', 100), - summary('blank', 150, { blank: true }), - summary('mid', 200), - ] - const startSession = vi.fn() + it.each(['workspace', 'flat', 'ungrouped'] as const)('keeps a new blank first across %s mode switches and enables drag after the first prompt', (mode) => { + localStorage.clear() + const preferences = createWorkspaceViewStore().create() + preferences.actions.setGroupBy(mode === 'flat' ? 'flat' : 'workspace') + const account = mode === 'flat' ? FLAT_SESSION_ORDER_KEY : mode === 'ungrouped' ? UNGROUPED_KEY : 'alpha' + preferences.actions.setGroupExpanded(account, true) + const groups = (ids: string[]) => hook(workspaceState(mode === 'ungrouped' ? [] : [workspace('alpha', ids)])) const b = mount({ - useSessions: hook(sessionState(items)), - useWorkspaces: hook(workspaceState([workspace('alpha', ['old', 'blank', 'mid'])])), - startSession, + useSessions: hook(sessionState([summary('old', 100), summary('mid', 200)])), + useWorkspaces: groups(['old', 'mid']), }) - await waitFor(() => { - expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['old', 'blank', 'mid']) - }) - startSession.mockImplementation(() => { - rerender(b, { useSessions: hook(sessionState(items, { current: sid('blank') })) }) - }) - fireEvent.click(screen.getByRole('button', { name: '在“alpha”中新建会话' })) - expect(startSession).toHaveBeenCalledWith(wid('alpha')) - await waitFor(() => { - expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['blank', 'old', 'mid']) - expect(b.store.getSnapshot().sessionOrderByAccount[FLAT_SESSION_ORDER_KEY]).toEqual(['blank']) - }) - b.store.actions.setGroupBy('flat') - await waitFor(() => { - expect(b.store.getSnapshot().sessionOrderByAccount[FLAT_SESSION_ORDER_KEY]).toEqual(['blank', 'mid', 'old']) - }) - }) - - it('does not repeat blank promotion after a manual drag or the first prompt', async () => { - const insertSessionBefore = vi.fn(async () => {}) - const b = mount({ + const names = () => screen.getAllByRole('treeitem').filter(row => row.getAttribute('aria-expanded') === null) + .map(row => row.querySelector('[class*="title"]')?.textContent) + const pick = (name: string) => { + fireEvent.click(screen.getByRole('button', { name: '视图选项' })) + fireEvent.click(screen.getByRole('menuitem', { name })) + } + pick('手动排序') + pick('最近更新') + rerender(b, { useSessions: hook(sessionState([ - summary('old', 100), - summary('blank', 150, { blank: true }), - summary('mid', 200), + summary('old', 100), summary('mid', 200), summary('blank', 1, { blank: true }), ], { current: sid('blank') })), - useWorkspaces: hook(workspaceState([workspace('alpha', ['old', 'blank', 'mid'])])), - insertSessionBefore, - }) - await waitFor(() => { - expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['blank', 'old', 'mid']) + useWorkspaces: groups(['old', 'mid', 'blank']), }) + expect(names()).toEqual(['新会话', 'mid', 'old']) + expect(b.store.getSnapshot().orderBy).toBe('updated') + pick('手动排序') + expect(names()).toEqual(['新会话', 'mid', 'old']) + pick('最近更新') const blank = screen.getByText('新会话').closest('[role="treeitem"]') as HTMLElement - const mid = screen.getByText('mid').closest('[role="treeitem"]') as HTMLElement - mid.getBoundingClientRect = () => ({ + expect(blank.draggable).toBe(false) + const old = screen.getByText('old').closest('[role="treeitem"]') as HTMLElement + old.getBoundingClientRect = () => ({ top: 150, bottom: 184, left: 0, right: 200, width: 200, height: 34, x: 0, y: 150, toJSON: () => ({}), }) fireEvent.dragStart(blank, { dataTransfer: dragData() }) - fireDrag(mid, 'drop', 180) - expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['old', 'mid', 'blank']) - expect(insertSessionBefore).toHaveBeenCalledWith(wid('alpha'), sid('blank'), undefined) - - rerender(b, { + fireDrag(old, 'drop', 180) + expect(names()).toEqual(['新会话', 'mid', 'old']) + b.view.unmount() + const restored = mount({ useSessions: b.props.useSessions, useWorkspaces: b.props.useWorkspaces }) + expect(names()).toEqual(['新会话', 'mid', 'old']) + rerender(restored, { useSessions: hook(sessionState([ + summary('old', 100), summary('mid', 200), summary('blank', 300), + ], { current: sid('blank') })) }) + expect(names()).toEqual(['blank', 'mid', 'old']) + const ordinaryBlank = screen.getByText('blank').closest('[role="treeitem"]') as HTMLElement + expect(ordinaryBlank.draggable).toBe(true) + const restoredOld = screen.getByText('old').closest('[role="treeitem"]') as HTMLElement + restoredOld.getBoundingClientRect = () => old.getBoundingClientRect() + fireEvent.dragStart(ordinaryBlank, { dataTransfer: dragData() }) + fireDrag(restoredOld, 'drop', 180) + expect(names()).toEqual(['mid', 'old', 'blank']) + rerender(restored, { useSessions: hook(sessionState([ - summary('old', 100), - summary('blank', 150), - summary('mid', 200), - ], { current: sid('blank') })), - }) - await waitFor(() => { - expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['old', 'mid', 'blank']) + summary('old', 100), summary('mid', 200), summary('blank', 300), summary('new-blank', 1, { blank: true }), + ], { current: sid('new-blank') })), + useWorkspaces: groups(['old', 'mid', 'blank', 'new-blank']), }) + expect(names()).toEqual(['新会话', 'mid', 'old', 'blank']) + pick('最近更新') + expect(names()).toEqual(['新会话', 'blank', 'mid', 'old']) + pick('手动排序') + expect(names()).toEqual(['新会话', 'blank', 'mid', 'old']) }) it('shows local metadata matches immediately, then clears back to the grouped tree', async () => { @@ -1168,13 +1343,11 @@ describe('WorkspaceBrowser', () => { expect(insertWorkspaceBefore).toHaveBeenCalledWith(wid('tail'), wid('beta')) }) - it('drag reorder reports the anchor to insertSessionBefore and skips no-op drops', () => { - const insertSessionBefore = vi.fn(async () => {}) + it('persists a grouped drag locally and skips no-op drops', () => { const sessions = sessionState([summary('one', 3), summary('two', 2), summary('three', 1)]) - mount({ + const b = mount({ useSessions: hook(sessions), useWorkspaces: hook(workspaceState([workspace('alpha', ['one', 'two', 'three'])])), - insertSessionBefore, }) fireEvent.click(screen.getByText('alpha')) const rows = screen.getAllByRole('treeitem').slice(1) // drop the group header @@ -1187,7 +1360,7 @@ describe('WorkspaceBrowser', () => { // Drop on the top half of "three": insert one before three. fireDrag(three, 'dragOver', 205) fireDrag(three, 'drop', 205) - expect(insertSessionBefore).toHaveBeenCalledWith(wid('alpha'), sid('one'), sid('three')) + expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['two', 'one', 'three']) // Dropping right back onto its own position is a no-op — top half // (anchor = itself) and bottom half (anchor = the next root) alike. @@ -1197,19 +1370,17 @@ describe('WorkspaceBrowser', () => { }) fireDrag(one, 'dragOver', 105) fireDrag(one, 'drop', 105) - expect(insertSessionBefore).toHaveBeenCalledTimes(1) + expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['two', 'one', 'three']) fireEvent.dragStart(one, { dataTransfer }) fireDrag(one, 'drop', 130) - expect(insertSessionBefore).toHaveBeenCalledTimes(1) + expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['two', 'one', 'three']) }) - it('persists Ungrouped drag order in both modes without writing a Host Workspace account', async () => { - const insertSessionBefore = vi.fn(async () => {}) + it('persists Ungrouped drag order in both modes', async () => { const sessions = sessionState([summary('one', 3), summary('two', 2), summary('three', 1)]) const b = mount({ useSessions: hook(sessions), useWorkspaces: hook(workspaceState([])), - insertSessionBefore, }) fireEvent.click(screen.getByText('未分组')) @@ -1227,22 +1398,22 @@ describe('WorkspaceBrowser', () => { expect(b.store.getSnapshot().sessionOrderByAccount[UNGROUPED_KEY]).toEqual(['two', 'three', 'one']) dragAfter('two', 'one') expect(b.store.getSnapshot().sessionOrderByAccount[UNGROUPED_KEY]).toEqual(['three', 'one', 'two']) - expect(insertSessionBefore).not.toHaveBeenCalled() fireEvent.click(screen.getByRole('button', { name: '视图选项' })) fireEvent.click(screen.getByRole('menuitem', { name: '最近更新' })) await waitFor(() => { - expect(b.store.getSnapshot().sessionOrderByAccount[UNGROUPED_KEY]).toEqual(['one', 'two', 'three']) + expect(screen.getAllByRole('treeitem').slice(1).map(row => row.textContent)).toEqual([ + expect.stringContaining('one'), expect.stringContaining('two'), expect.stringContaining('three'), + ]) + expect(b.store.getSnapshot().sessionOrderByAccount).toEqual({}) }) dragAfter('one', 'three') expect(b.store.getSnapshot().sessionOrderByAccount[UNGROUPED_KEY]).toEqual(['two', 'three', 'one']) - expect(insertSessionBefore).not.toHaveBeenCalled() b.view.unmount() const restored = mount({ useSessions: hook(sessions), useWorkspaces: hook(workspaceState([])), - insertSessionBefore, }) expect(restored.store.getSnapshot().sessionOrderByAccount[UNGROUPED_KEY]).toEqual(['two', 'three', 'one']) expect(screen.getAllByRole('treeitem').slice(1).map(row => row.textContent)).toEqual([ @@ -1252,13 +1423,11 @@ describe('WorkspaceBrowser', () => { ]) }) - it('still sends the reorder when the dragged row left the group mid-drag', () => { - const insertSessionBefore = vi.fn(async () => {}) + it('ignores a drag whose source left the group in flight', () => { const sessions = sessionState([summary('one', 2), summary('two', 1)]) const b = mount({ useSessions: hook(sessions), useWorkspaces: hook(workspaceState([workspace('alpha', ['one', 'two'])])), - insertSessionBefore, }) fireEvent.click(screen.getByText('alpha')) const one = screen.getByText('one').closest('[role="treeitem"]') as HTMLElement @@ -1271,16 +1440,14 @@ describe('WorkspaceBrowser', () => { top: 150, bottom: 184, left: 0, right: 200, width: 200, height: 34, x: 0, y: 150, toJSON: () => ({}), }) fireDrag(two, 'drop', 155) - expect(insertSessionBefore).toHaveBeenCalledWith(wid('alpha'), sid('one'), sid('two')) + expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['two']) }) it('drag end without a drop clears markers; bottom-half drop appends past the last row', () => { - const insertSessionBefore = vi.fn(async () => {}) const sessions = sessionState([summary('one', 2), summary('two', 1)]) - mount({ + const b = mount({ useSessions: hook(sessions), useWorkspaces: hook(workspaceState([workspace('alpha', ['one', 'two'])])), - insertSessionBefore, }) fireEvent.click(screen.getByText('alpha')) const [one, two] = screen.getAllByRole('treeitem').slice(1) as [HTMLElement, HTMLElement] @@ -1292,21 +1459,19 @@ describe('WorkspaceBrowser', () => { fireEvent.dragEnd(one) // The drag ended: rows no longer accept drops. fireDrag(two, 'drop', 180) - expect(insertSessionBefore).not.toHaveBeenCalled() + expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['one', 'two']) // Bottom half of the last row: append (anchor omitted). fireEvent.dragStart(one, { dataTransfer }) fireDrag(two, 'dragOver', 180) fireDrag(two, 'drop', 180) - expect(insertSessionBefore).toHaveBeenCalledWith(wid('alpha'), sid('one'), undefined) + expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['two', 'one']) }) it('accepts a document-level drop and commits the last Session marker on drag end', () => { - const insertSessionBefore = vi.fn(async () => {}) - mount({ + const b = mount({ useSessions: hook(sessionState([summary('one', 2), summary('two', 1)])), useWorkspaces: hook(workspaceState([workspace('alpha', ['one', 'two'])])), - insertSessionBefore, }) fireEvent.click(screen.getByText('alpha')) const [one, two] = screen.getAllByRole('treeitem').slice(1) as [HTMLElement, HTMLElement] @@ -1320,31 +1485,7 @@ describe('WorkspaceBrowser', () => { fireEvent(document.body, outsideDrop) expect(outsideDrop.defaultPrevented).toBe(true) fireEvent.dragEnd(one) - expect(insertSessionBefore).toHaveBeenCalledWith(wid('alpha'), sid('one'), undefined) - }) - - it('logs and keeps the order when the reorder call rejects', async () => { - const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) - try { - const insertSessionBefore = vi.fn(async () => { throw new Error('stale anchor') }) - const sessions = sessionState([summary('one', 2), summary('two', 1)]) - mount({ - useSessions: hook(sessions), - useWorkspaces: hook(workspaceState([workspace('alpha', ['one', 'two'])])), - insertSessionBefore, - }) - fireEvent.click(screen.getByText('alpha')) - const [one, two] = screen.getAllByRole('treeitem').slice(1) as [HTMLElement, HTMLElement] - two.getBoundingClientRect = () => ({ - top: 150, bottom: 184, left: 0, right: 200, width: 200, height: 34, x: 0, y: 150, toJSON: () => ({}), - }) - const dataTransfer = dragData() - fireEvent.dragStart(one, { dataTransfer }) - fireDrag(two, 'drop', 180) - await waitFor(() => { expect(warn).toHaveBeenCalledWith('session reorder rejected:', expect.any(Error)) }) - } finally { - warn.mockRestore() - } + expect(b.store.getSnapshot().sessionOrderByAccount.alpha).toEqual(['two', 'one']) }) it('renames a workspace through the row menu dialog', async () => { diff --git a/packages/compaction/command-compact/tests/command-compact.spec.ts b/packages/compaction/command-compact/tests/command-compact.spec.ts index bd312d2a99..cf4fc3eb3a 100644 --- a/packages/compaction/command-compact/tests/command-compact.spec.ts +++ b/packages/compaction/command-compact/tests/command-compact.spec.ts @@ -64,13 +64,13 @@ class StubCompactionEngine extends CompactionEngine { result: CompactionResult, sourceCommandId: Parameters[2], ): CompactionResult { - const provenance = { + const operationIds = { compactionId: result.compactionId, ...sourceCommandId === undefined ? {} : { sourceCommandId }, } - agent.session.append('compaction/start', { ...provenance, turn: null }) + agent.session.append('compaction/start', { ...operationIds, turn: null }) agent.session.append('compaction/summary', { - ...provenance, + ...operationIds, summary: result.summary, shadowedRange: result.shadowedRange, shadowedSeqs: result.shadowedSeqs, @@ -78,8 +78,8 @@ class StubCompactionEngine extends CompactionEngine { provider: 'command-test', model: 'command-test', }) - agent.session.append('compaction/end', { ...provenance, turn: null }) - return { ...result, ...provenance } + agent.session.append('compaction/end', { ...operationIds, turn: null }) + return { ...result, ...operationIds } } } diff --git a/packages/compaction/command-compact/tests/loader-composition.spec.ts b/packages/compaction/command-compact/tests/loader-composition.spec.ts index 57367b2c7b..d37448d5ad 100644 --- a/packages/compaction/command-compact/tests/loader-composition.spec.ts +++ b/packages/compaction/command-compact/tests/loader-composition.spec.ts @@ -50,13 +50,13 @@ class LoaderCompactionEngine extends CompactionEngine { _signal: AbortSignal, sourceCommandId?: Parameters[2], ): Promise { - const provenance = { + const operationIds = { compactionId: RESULT.compactionId, ...sourceCommandId === undefined ? {} : { sourceCommandId }, } - agent.session.append('compaction/start', { ...provenance, turn: null }) + agent.session.append('compaction/start', { ...operationIds, turn: null }) agent.session.append('compaction/summary', { - ...provenance, + ...operationIds, summary: RESULT.summary, shadowedRange: RESULT.shadowedRange, shadowedSeqs: RESULT.shadowedSeqs, @@ -64,8 +64,8 @@ class LoaderCompactionEngine extends CompactionEngine { provider: 'loader-test', model: 'loader-test', }) - agent.session.append('compaction/end', { ...provenance, turn: null }) - return Promise.resolve({ ...RESULT, ...provenance }) + agent.session.append('compaction/end', { ...operationIds, turn: null }) + return Promise.resolve({ ...RESULT, ...operationIds }) } } diff --git a/packages/compaction/compaction-basic/src/region.ts b/packages/compaction/compaction-basic/src/region.ts index e8efef2b12..048c732c8d 100644 --- a/packages/compaction/compaction-basic/src/region.ts +++ b/packages/compaction/compaction-basic/src/region.ts @@ -470,7 +470,7 @@ function commitCompactionBody( usage, checkpointMessage, } = summarized - const callProvenance = summarized.llmStreamCall === true + const callRecord = summarized.llmStreamCall === true ? { rawOutput: summarized.rawOutput, llmStreamCall: true as const } : summarized.rawOutput === undefined ? {} : { rawOutput: summarized.rawOutput } const summaryEvent = session.append('compaction/summary', { @@ -479,7 +479,7 @@ function commitCompactionBody( ? {} : { sourceCommandId: startEvent.data.sourceCommandId }, summary, - ...callProvenance, + ...callRecord, shadowedRange: { start, end }, shadowedSeqs: [...shadowedSeqs], shadowedTokenCount, diff --git a/packages/compaction/compaction/src/checkpoint.ts b/packages/compaction/compaction/src/checkpoint.ts index 116ff18aa5..395a70c1f6 100644 --- a/packages/compaction/compaction/src/checkpoint.ts +++ b/packages/compaction/compaction/src/checkpoint.ts @@ -1,5 +1,5 @@ /** - * Compaction checkpoint provenance: the correlated source constructor and type + * Compaction checkpoint source: the correlated constructor and type * every backend uses for its replacement user message, plus the predicate that * recognizes persisted checkpoints. * @@ -18,14 +18,14 @@ import type { CompactionId } from './brand.ts' const COMPACT_CHECKPOINT_MARKER = Object.freeze({ kind: 'plugin', plugin: 'compact' } as const) -/** Message provenance carried by a concrete compaction checkpoint. */ +/** Message source carried by a concrete compaction checkpoint. */ export type CompactionCheckpointSource = typeof COMPACT_CHECKPOINT_MARKER & { readonly compactionId: CompactionId readonly sourceCommandId?: CommandId } /** - * Create checkpoint provenance correlated with one compaction transaction. + * Create a checkpoint source correlated with one compaction transaction. * @param compactionId - owning compaction identity. * @param sourceCommandId - initiating manual command, when present. * @returns immutable checkpoint source. diff --git a/packages/compaction/compaction/tests/invariant.spec.ts b/packages/compaction/compaction/tests/invariant.spec.ts index ff49a3400e..20269e558e 100644 --- a/packages/compaction/compaction/tests/invariant.spec.ts +++ b/packages/compaction/compaction/tests/invariant.spec.ts @@ -334,7 +334,7 @@ describe('compaction invariants', () => { })).toThrow(/compaction checkpoint id .* does not match compaction\/start id/) }) - it('requires checkpoint provenance to name an open transaction', async () => { + it('requires a checkpoint source to name an open transaction', async () => { const ctx = await setup() const withoutStart = ctx.sessions.create() const original = withoutStart.append('user/message', createUserMessage({ diff --git a/packages/context/session-reference/README.i18n.yaml b/packages/context/session-reference/README.i18n.yaml index 2436daf6f1..7bd0b1d5f8 100644 --- a/packages/context/session-reference/README.i18n.yaml +++ b/packages/context/session-reference/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/context/session-reference/README.md -README.md: a7da16668c32ae50568e4012221bbaf6b7da7d2d +README.md: 533a1282da920b6daa47ef5be98e49c691a8cb84 README.zh.md: e7b43c9a56cd5b4062770144ccea0bf125153b40 diff --git a/packages/context/session-reference/README.md b/packages/context/session-reference/README.md index a7da16668c..533a1282da 100644 --- a/packages/context/session-reference/README.md +++ b/packages/context/session-reference/README.md @@ -68,7 +68,7 @@ This section explains the design of the service; the observable behavior is cove Preparation reads each referenced session's current surface exactly once, when the target message reaches `agent/pre-step`. Both preview and spill use that same captured projection: direct-user text, assistant text, and user checkpoints carrying the canonical compaction marker; tools, reasoning, and other injected context are excluded. This prevents recursive reference propagation and prevents a later source mutation from changing the saved transcript. Preview JSON escapes every `<` as `\u003c`, so source text cannot spell the `` framing tag. -The resolver discovers optional storage through `ctx.get("spillStore")` and saves only truncated references. Storage ownership is the target session; provenance identifies the referenced source session and label, without a fabricated tool call. Cancellation is checked after the asynchronous save and prevents publication even if an artifact was written. Artifact expiry remains the backend's existing policy. +The resolver discovers optional storage through `ctx.get("spillStore")` and saves only truncated references. Storage ownership is the target session; the source descriptor identifies the referenced session and label, without a fabricated tool call. Cancellation is checked after the asynchronous save and prevents publication even if an artifact was written. Artifact expiry remains the backend's existing policy. The budget uses the provider and model captured after `system-prompt/assemble` completes for the target agent. Direct `prepare` calls before any assembly use agent options; session headers do not select the budget model. Diagnostic assemblies without an agent do not affect captured routes. diff --git a/packages/context/time-context/README.i18n.yaml b/packages/context/time-context/README.i18n.yaml index a1e84a7b81..1368cfe310 100644 --- a/packages/context/time-context/README.i18n.yaml +++ b/packages/context/time-context/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/context/time-context/README.md -README.md: 007cfeb69c5b0434603ab8f75a8e989c76583b66 +README.md: ea6f480b51dddc72f2be1a480053b47045a3f60b README.zh.md: 600a8870860ee1558758d2eaf71220dbcabf9588 diff --git a/packages/context/time-context/README.md b/packages/context/time-context/README.md index 007cfeb69c..ea6f480b51 100644 --- a/packages/context/time-context/README.md +++ b/packages/context/time-context/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -`dsh-time-context` gives the model a clock: on eligible steps it appends a durable, source-attributed reading with the current time, the browser zone attached to the open request, and the elapsed time since the preceding model-visible message. It helps the model interpret otherwise-unqualified dates and times in the user's browser zone, and tells it to ask when zone provenance is mixed or missing. The plugin is opt-in: default compositions leave it disabled, and the Schedule Web overlay mounts it. A positive `refreshIntervalMs` reduces how often readings accumulate; omission or `0` injects at every eligible step. +`dsh-time-context` gives the model a clock: on eligible steps it appends a durable, source-attributed reading with the current time, the browser zone attached to the open request, and the elapsed time since the preceding model-visible message. It helps the model interpret otherwise-unqualified dates and times in the user's browser zone, and tells it to ask when current-turn browser zones are mixed or missing. The plugin is opt-in: default compositions leave it disabled, and the Schedule Web overlay mounts it. A positive `refreshIntervalMs` reduces how often readings accumulate; omission or `0` injects at every eligible step. ## Table of Contents @@ -50,7 +50,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a ### Choosing the zone -When the open turn contains exactly one Host-validated browser zone, the timestamp is formatted in that request-local zone. With missing or mixed browser provenance, the configured `timeZone` formats the display; omitting it resolves the Node process zone once at plugin load, and every explicit fallback is validated through `Intl.DateTimeFormat`. The resolved instruction tells the model to interpret unqualified dates and times in the chosen zone, and to ask the user to clarify when provenance is mixed or unavailable. +When the open turn contains exactly one Host-validated browser zone, the timestamp is formatted in that request-local zone. With missing or mixed browser-zone records, the configured `timeZone` formats the display; omitting it resolves the Node process zone once at plugin load, and every explicit fallback is validated through `Intl.DateTimeFormat`. The resolved instruction tells the model to interpret unqualified dates and times in the chosen zone, and to ask the user to clarify when those records are mixed or unavailable. ----- @@ -134,9 +134,9 @@ Append-only; newly visible content follows the reusable request prefix and does These limits define when clock context is a poor fit. They are current package constraints. -- **Prompt provenance only** — browser-zone context guides natural-language interpretation but does not silently supply another tool's required zone field. +- **Prompt guidance only** — browser-zone context guides natural-language interpretation but does not silently supply another tool's required zone field. - **Mixed turns ask** — if one open turn contains prompts from different browser zones, the model is told to clarify rather than guess which one owns an unqualified time. -- **Fallback is not user authority** — the configured or process zone formats the clock when browser provenance is missing or mixed, but the model-facing policy still says to clarify. +- **Fallback is not user authority** — the configured or process zone formats the clock when current-turn browser zones are missing or mixed, but the model-facing policy still says to clarify. - **Whole-second display** — timestamps and durations omit sub-second precision even though durable event times retain milliseconds. - **History cost between compactions** — omission or `0` retains one reading for every eligible attempt; a positive interval reduces but does not eliminate this cost and may leave a later request without fresh browser-zone guidance. diff --git a/packages/context/time-context/tests/invariant.spec.ts b/packages/context/time-context/tests/invariant.spec.ts index 51ebd42629..74ab8b5241 100644 --- a/packages/context/time-context/tests/invariant.spec.ts +++ b/packages/context/time-context/tests/invariant.spec.ts @@ -102,7 +102,7 @@ describe('time-context invariants', () => { }).not.toThrow() }) - it('requires browser-zone policy and timestamp to match current-turn request provenance', async () => { + it('requires browser-zone policy and timestamp to match current-turn request sources', async () => { const ctx = await setup() const policy = 'Browser time zone for this request: Asia/Shanghai. ' + 'Interpret otherwise-unqualified dates and times in this zone.' @@ -150,7 +150,7 @@ describe('time-context invariants', () => { } }) - it('rejects invalid browser provenance loaded across the durable boundary', async () => { + it('rejects invalid browser-zone data loaded across the durable boundary', async () => { const ctx = await setup() const timeZone = 'Not/A_Real_Zone' const policy = `Browser time zone for this request: ${timeZone}. ` @@ -283,7 +283,7 @@ describe('time-context invariants', () => { }).toThrow(message) }) - it('requires exact snapshot provenance without copied request authority', async () => { + it('requires the exact snapshot source without copied request authority', async () => { const ctx = await setup() const base = event(reading()) for (const source of [ diff --git a/packages/core/agent-loop/README.i18n.yaml b/packages/core/agent-loop/README.i18n.yaml index 2936b69d34..cff528aef5 100644 --- a/packages/core/agent-loop/README.i18n.yaml +++ b/packages/core/agent-loop/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/agent-loop/README.md -README.md: cff0eb77b8464d49908bbd7b0e7a1b0658ed2f3c -README.zh.md: 94fb5406193300abc3077462407fdc8b1dd742a0 +README.md: 780fed31a34113e01d7c98313bd88b5c322a8332 +README.zh.md: 766ff4a2e7287f6987b4443c19f3164d7e2cd2f3 diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index cff0eb77b8..780fed31a3 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -92,7 +92,7 @@ The package is the one concrete implementation of the public `Agent` contract. I After `agent/request`, `ctx.llm.prepareCall()` validates adapter-owned fields and resolves reasoning-effort and output-token defaults under the active turn signal. The loop retains that exact adapter through resolution, `request/header` logging, and dispatch. It writes a full header for the first request, a changed envelope (config or tools — the prompt is not part of the header), an explicit message-series start, a request after surface replacement (an in-place prompt replacement or compaction), and resume; unchanged steps, retries, and ordinary later turns in the same series inherit the latest header, and an in-history prompt append is not a replacement, so the request that follows it inherits the header too. Beside the header, the loop logs `request/context` — provider, model, `contextWindow`, and the route's `systemPromptUpdate` mode from `prepareCall()` — only when one of those differs from the latest snapshot. Before the next waterfall, the loop removes adapter-default fields so the current route resolves them again, while explicit settings persist. An unhandled route still fails with `NO_ADAPTER`. -The loop deep-freezes each derived message identity on its first request and reuses that proof only within the same agent. Restored messages keep their identity; request construction does not freeze their containing event wrappers. Each request freezes its local canonical header, fresh message array, and envelope while leaving the cancellation signal live. The [request-freeze decision](../../../.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.md) explains ownership and measurement. +The loop deep-freezes each derived message identity on its first request and reuses that proof only within the same agent. Restored messages keep their identity; request construction does not freeze their containing event wrappers. Each request freezes its local canonical header, fresh message array, and envelope while leaving the cancellation signal live. The [request-freeze decision](../../../.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md) explains ownership and measurement. ### Source map diff --git a/packages/core/agent-loop/README.zh.md b/packages/core/agent-loop/README.zh.md index 94fb540619..766ff4a2e7 100644 --- a/packages/core/agent-loop/README.zh.md +++ b/packages/core/agent-loop/README.zh.md @@ -92,7 +92,7 @@ const handle = await ctx.agents.create({ `agent/request` 返回后,`ctx.llm.prepareCall()` 会在活跃轮次信号下校验适配器持有的字段,并解析推理强度和输出 token 默认值。循环会在解析、`request/header` 记录与分派期间保留同一个适配器。循环会为首次请求、变化的 envelope(配置或工具——提示词不属于 header)、显式消息序列起点、surface 替换(原地替换提示词或压缩(compaction))后的请求及恢复写入完整 header;同一序列内内容未变的步骤、重试与普通后续轮次继承最新 header,历史内追加提示词不是替换,因此紧随其后的请求同样继承 header。在 header 之外,循环还会记录 `request/context`——提供方、模型、`contextWindow` 以及来自 `prepareCall()` 的路由 `systemPromptUpdate` 模式——且仅在其中任何一项与最新快照不同时记录。下一次 waterfall 分发前,循环移除适配器默认字段,使当前路由重新解析它们;显式设置则保留。未处理的路由仍以 `NO_ADAPTER` 失败。 -循环在每个派生消息对象首次进入请求时执行深冻结,并且仅在同一 agent 内复用该证明。恢复的消息保留对象身份;构造请求不会冻结包含消息的事件包装对象。每个请求都会冻结本地规范化 header、新消息数组和请求封装,同时保留取消信号的可变性。[请求冻结决策](../../../.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-provenance.zh.md)解释了所有权与测量依据。 +循环在每个派生消息对象首次进入请求时执行深冻结,并且仅在同一 agent 内复用该证明。恢复的消息保留对象身份;构造请求不会冻结包含消息的事件包装对象。每个请求都会冻结本地规范化 header、新消息数组和请求封装,同时保留取消信号的可变性。[请求冻结决策](../../../.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.zh.md)解释了所有权与测量依据。 ### 源码地图 diff --git a/packages/core/session/README.i18n.yaml b/packages/core/session/README.i18n.yaml index e4ccbdc3ec..c74112c5a6 100644 --- a/packages/core/session/README.i18n.yaml +++ b/packages/core/session/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/session/README.md -README.md: a18fd0de73a6aa7dc881f1472d991c133f81ad55 +README.md: 2368e20df012b16ed12f35de327db62c71ee3269 README.zh.md: 84cc0aa35da11a5a782e1949e32852785fb681c4 diff --git a/packages/core/session/README.md b/packages/core/session/README.md index a18fd0de73..2368e20df0 100644 --- a/packages/core/session/README.md +++ b/packages/core/session/README.md @@ -135,7 +135,7 @@ The package-level contract is enough for most consumers; read these when you nee #### What the model sees -The model receives the complete messages from `system/message`, `user/message`, `assistant/message`, and `tool/result` surface entries verbatim, the system prompt first — identities, roles, sources, and content blocks are the same values established at creation, and projections never mint identities. Direct prompts and injected context remain separate `user/message` events whose sources preserve their provenance. Embedded streams, `assistant/attempt`, boundaries, and other log-only facts add no message. +The model receives the complete messages from `system/message`, `user/message`, `assistant/message`, and `tool/result` surface entries verbatim, the system prompt first — identities, roles, sources, and content blocks are the same values established at creation, and projections never mint identities. Direct prompts and injected context remain separate `user/message` events whose sources preserve their attribution. Embedded streams, `assistant/attempt`, boundaries, and other log-only facts add no message. #### Token effect diff --git a/packages/core/session/src/surface.ts b/packages/core/session/src/surface.ts index 5d8ce74fe2..a702f9618a 100644 --- a/packages/core/session/src/surface.ts +++ b/packages/core/session/src/surface.ts @@ -266,7 +266,7 @@ function surfaceOpOf(event: SessionEvent): SurfaceOp | undefined { } /** Validate cited source-event seqs against prior log entries and the replacement range. */ -function assertProvenance( +function assertSourceEventReferences( event: SessionEvent, shadowedSeqs: readonly SessionSeq[], ): void { @@ -316,7 +316,7 @@ export function validateSurfaceMetadata(event: SessionEvent): SurfaceOp | undefi && (op.startSeq >= event.seq || op.endSeq >= event.seq)) { throw new Error(`surface replace at seq ${event.seq}: startSeq and endSeq must reference earlier events`) } - if (op !== undefined) assertProvenance(event, []) + if (op !== undefined) assertSourceEventReferences(event, []) return op } @@ -434,7 +434,7 @@ function planSurfaceEvent( return { kind: 'append', seq: event.seq } } const range = replacementRange(state, surfaceOp) - assertProvenance(event, range.shadowedSeqs) + assertSourceEventReferences(event, range.shadowedSeqs) assertToolResultRewrite(event, range.shadowedSeqs, events, baseSeq) assertSystemHeadRewrite(event, state, range.startIdx, range.shadowedSeqs, events, baseSeq) return { diff --git a/packages/core/session/tests/canonical-envelopes.spec.ts b/packages/core/session/tests/canonical-envelopes.spec.ts index 123852762d..ec78041a8b 100644 --- a/packages/core/session/tests/canonical-envelopes.spec.ts +++ b/packages/core/session/tests/canonical-envelopes.spec.ts @@ -252,7 +252,7 @@ describe('canonical event-local surface metadata', () => { expect(() => accept({ ...userEvent(), surfaceOp } as unknown as SessionEvent)).toThrow(/invalid replace surfaceOp/) }) - it(path + ' requires markers and forbids non-surface and assistant provenance', () => { + it(path + ' requires markers and forbids non-surface and assistant source-event references', () => { const { surfaceOp: _op, ...markerless } = userEvent() expect(() => accept(markerless as SessionEvent)).toThrow(/requires a surfaceOp marker/) expect(() => accept({ type: 'turn/start', seq: SessionSeq(0), time: 1, data: { turn: 1 }, surfaceOp: 'append' } as never)) @@ -293,7 +293,7 @@ describe('canonical event-local surface metadata', () => { } }) - it('requires system placement and preserves system data and provenance on head replacements', () => { + it('requires system placement and preserves system data and source-event references on head replacements', () => { const session = Session.create(id) const data = { turn: 1, step: 1, message: createSystemMessage('head', 'fixture'), extra: { nested: true } } const head = session.append('system/message', data, { surfaceOp: 'append' }) @@ -319,7 +319,7 @@ describe('canonical event-local surface metadata', () => { expect(snapshotSessionEvent(event)).toEqual(event) }) - it('requires surface intent on event variants and forbids assistant provenance in types', () => { + it('requires surface intent on event variants and forbids assistant source-event references in types', () => { expectTypeOf().toEqualTypeOf>() expectTypeOf, 'surfaceOp'>>().not.toExtend>() expectTypeOf<{ surfaceOp: 'append'; sourceEventSeqs: SessionSeq[] }>().not.toExtend>() diff --git a/packages/core/session/tests/seq-ranges.spec.ts b/packages/core/session/tests/seq-ranges.spec.ts index cace9b53c3..89a05dc50a 100644 --- a/packages/core/session/tests/seq-ranges.spec.ts +++ b/packages/core/session/tests/seq-ranges.spec.ts @@ -21,7 +21,7 @@ describe('sourceEventSeqs ranges', () => { expect(encodeSeqRanges(seqs([3, 2]))).toEqual([3, 2]) }) - it('does not impose a persistence-only provenance length limit', () => { + it('does not impose a persistence-only source-event length limit', () => { const values = Array.from({ length: 1_000_001 }, (_, index) => SessionSeq(index)) expect(encodeSeqRanges(values)).toEqual([[0, 1_000_000]]) }) diff --git a/packages/core/session/tests/surface.spec.ts b/packages/core/session/tests/surface.spec.ts index 6d10e22335..c861e17b3b 100644 --- a/packages/core/session/tests/surface.spec.ts +++ b/packages/core/session/tests/surface.spec.ts @@ -64,7 +64,7 @@ function surfaceSession(): Session { return s } -function provenanceEvent(seq: SessionSeq, sourceEventSeqs: unknown): SessionEvent { +function sourceEventReferenceEvent(seq: SessionSeq, sourceEventSeqs: unknown): SessionEvent { return { type: 'user/message', seq, @@ -104,10 +104,10 @@ function toolResultEvent( describe('foldSurface source-event references', () => { it('accepts absent or valid source-event references and complete replacement coverage', () => { const events = [ - provenanceEvent(SessionSeq(0), undefined), - provenanceEvent(SessionSeq(1), undefined), + sourceEventReferenceEvent(SessionSeq(0), undefined), + sourceEventReferenceEvent(SessionSeq(1), undefined), { - ...provenanceEvent(SessionSeq(2), [0, 1]), + ...sourceEventReferenceEvent(SessionSeq(2), [0, 1]), surfaceOp: surfaceOp({ op: 'replace', startSeq: 0, endSeq: 1 }), }, ] as SessionEvent[] @@ -125,7 +125,7 @@ describe('foldSurface source-event references', () => { expect(() => foldSurface([event])).toThrow(/cannot carry sourceEventSeqs/) }) - it('rejects obsolete source-event provenance on an assistant message', () => { + it('rejects obsolete Assistant chunk references on an assistant message', () => { const event = { type: 'assistant/message', seq: SessionSeq(0), @@ -150,19 +150,19 @@ describe('foldSurface source-event references', () => { }) it.each([ - ['a non-array', [{ ...provenanceEvent(SessionSeq(0), undefined), sourceEventSeqs: 'invalid' }], /must be an array/], - ['an empty array', [provenanceEvent(SessionSeq(0), [])], /must not be empty/], - ['duplicates', [provenanceEvent(SessionSeq(0), undefined), provenanceEvent(SessionSeq(1), [0, 0])], /must not contain duplicates/], - ['a sparse array', [provenanceEvent(SessionSeq(0), Array(1))], /densely contain/], - ['a non-number', [{ ...provenanceEvent(SessionSeq(0), undefined), sourceEventSeqs: ['0'] }], /non-negative safe integers/], - ['a fractional number', [provenanceEvent(SessionSeq(0), [0.5])], /non-negative safe integers/], - ['a negative number', [provenanceEvent(SessionSeq(0), [-1])], /non-negative safe integers/], - ['a self reference', [provenanceEvent(SessionSeq(0), [0])], /must reference earlier events/], - ['a non-contiguous event seq', [provenanceEvent(SessionSeq(0), undefined), provenanceEvent(SessionSeq(2), [1])], /seq 2 is not contiguous; expected 1/], + ['a non-array', [{ ...sourceEventReferenceEvent(SessionSeq(0), undefined), sourceEventSeqs: 'invalid' }], /must be an array/], + ['an empty array', [sourceEventReferenceEvent(SessionSeq(0), [])], /must not be empty/], + ['duplicates', [sourceEventReferenceEvent(SessionSeq(0), undefined), sourceEventReferenceEvent(SessionSeq(1), [0, 0])], /must not contain duplicates/], + ['a sparse array', [sourceEventReferenceEvent(SessionSeq(0), Array(1))], /densely contain/], + ['a non-number', [{ ...sourceEventReferenceEvent(SessionSeq(0), undefined), sourceEventSeqs: ['0'] }], /non-negative safe integers/], + ['a fractional number', [sourceEventReferenceEvent(SessionSeq(0), [0.5])], /non-negative safe integers/], + ['a negative number', [sourceEventReferenceEvent(SessionSeq(0), [-1])], /non-negative safe integers/], + ['a self reference', [sourceEventReferenceEvent(SessionSeq(0), [0])], /must reference earlier events/], + ['a non-contiguous event seq', [sourceEventReferenceEvent(SessionSeq(0), undefined), sourceEventReferenceEvent(SessionSeq(2), [1])], /seq 2 is not contiguous; expected 1/], ['incomplete replacement coverage', [ - provenanceEvent(SessionSeq(0), undefined), - provenanceEvent(SessionSeq(1), undefined), - { ...provenanceEvent(SessionSeq(2), [0]), surfaceOp: { op: 'replace', startSeq: 0, endSeq: 1 } }, + sourceEventReferenceEvent(SessionSeq(0), undefined), + sourceEventReferenceEvent(SessionSeq(1), undefined), + { ...sourceEventReferenceEvent(SessionSeq(2), [0]), surfaceOp: { op: 'replace', startSeq: 0, endSeq: 1 } }, ], /missing 1/], ] as const)( 'rejects %s', @@ -175,8 +175,8 @@ describe('foldSurface source-event references', () => { describe('foldSurface tool-result rewrites', () => { it('rejects a replacement spanning multiple current nodes', () => { const events = [ - provenanceEvent(SessionSeq(0), undefined), - provenanceEvent(SessionSeq(1), undefined), + sourceEventReferenceEvent(SessionSeq(0), undefined), + sourceEventReferenceEvent(SessionSeq(1), undefined), toolResultEvent(SessionSeq(2), 'rewrite', { op: 'replace', startSeq: 0, endSeq: 1 }, [0, 1]), ] expect(() => foldSurface(events)).toThrow(/must rewrite exactly one current node/) @@ -184,7 +184,7 @@ describe('foldSurface tool-result rewrites', () => { it('rejects a replacement targeting a non-result node', () => { const events = [ - provenanceEvent(SessionSeq(0), undefined), + sourceEventReferenceEvent(SessionSeq(0), undefined), toolResultEvent(SessionSeq(1), 'rewrite', { op: 'replace', startSeq: 0, endSeq: 0 }, [0]), ] expect(() => foldSurface(events)).toThrow(/must target a current tool\/result/) @@ -267,10 +267,10 @@ describe('SurfaceManager', () => { it('folds a contiguous window without materializing earlier event sequences', () => { const baseSeq = 400_000 const events = [ - provenanceEvent(SessionSeq(baseSeq), undefined), - provenanceEvent(SessionSeq(baseSeq + 1), undefined), + sourceEventReferenceEvent(SessionSeq(baseSeq), undefined), + sourceEventReferenceEvent(SessionSeq(baseSeq + 1), undefined), { - ...provenanceEvent(SessionSeq(baseSeq + 2), [baseSeq]), + ...sourceEventReferenceEvent(SessionSeq(baseSeq + 2), [baseSeq]), surfaceOp: surfaceOp({ op: 'replace', startSeq: baseSeq, endSeq: baseSeq }), }, ] as SessionEvent[] @@ -300,9 +300,9 @@ describe('SurfaceManager', () => { it('rejects a replacement that crosses a loaded window head', () => { const baseSeq = 400_000 const events = [ - provenanceEvent(SessionSeq(baseSeq), undefined), + sourceEventReferenceEvent(SessionSeq(baseSeq), undefined), { - ...provenanceEvent(SessionSeq(baseSeq + 1), [baseSeq - 1, baseSeq]), + ...sourceEventReferenceEvent(SessionSeq(baseSeq + 1), [baseSeq - 1, baseSeq]), surfaceOp: surfaceOp({ op: 'replace', startSeq: baseSeq - 1, endSeq: baseSeq }), }, ] as SessionEvent[] @@ -361,9 +361,9 @@ describe('SurfaceManager', () => { it('foldSurface reports the same invalid replacement failures as the incremental manager', () => { const events = [ - provenanceEvent(SessionSeq(0), undefined), + sourceEventReferenceEvent(SessionSeq(0), undefined), { type: 'turn/start', seq: SessionSeq(1), time: 1, data: { turn: 1 } }, - { ...provenanceEvent(SessionSeq(2), [0]), surfaceOp: { op: 'replace', startSeq: 1, endSeq: 0 } }, + { ...sourceEventReferenceEvent(SessionSeq(2), [0]), surfaceOp: { op: 'replace', startSeq: 1, endSeq: 0 } }, ] as SessionEvent[] expect(() => foldSurface(events)).toThrow(/start seq 1 not found/) @@ -373,7 +373,7 @@ describe('SurfaceManager', () => { it('rejects negative-zero replacement event sequences', () => { const event = { - ...provenanceEvent(SessionSeq(0), [0]), + ...sourceEventReferenceEvent(SessionSeq(0), [0]), surfaceOp: { op: 'replace', startSeq: -0, endSeq: -0 }, } as SessionEvent diff --git a/packages/e2b/README.md b/packages/e2b/README.md deleted file mode 100644 index d4be373067..0000000000 --- a/packages/e2b/README.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -description: "The E2B remote-runtime group map: file and command work inside one remote Linux sandbox, for users and maintainers of the E2B family." -kind: "package-group" ---- - -# packages/e2b - -English | [中文](README.zh.md) - -## Summary - -The E2B family lets agents read and edit files, run shell commands, and use terminals inside one remote Linux sandbox instead of on the host machine. It keeps filesystem work separate from command and terminal execution while both use the same sandbox. Existing shell, terminal, and language-server features continue to work without E2B-specific tools. The harness, model calls, and session state remain local; the sandbox is ephemeral, experimental, and absent from shipped compositions by default. - -## Table of Contents - -- [Packages](#packages) -- [Related documentation](#related-documentation) -- [Dev Note](#dev-note) - ------ - - -## Packages - -| Package | Role | ctx key | -|---|---|---| -| [`e2b`](e2b/README.md) | One shared remote Linux sandbox that file and command work runs in | `ctx.e2b` | -| [`fs-e2b`](fs-e2b/README.md) | File reads, writes, edits, and listings inside the remote sandbox | `ctx.fs` | -| [`subprocess-e2b`](subprocess-e2b/README.md) | Shell commands and interactive terminals inside the remote sandbox | `ctx.subprocess` | - ------ - - -## Related documentation - -- [Portable execution-world decision](../../.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md) — why the execution world can move without moving the harness, and what stays local. -- [Subprocess subsystem](../../docs/subsystems/subprocess.md) — the subprocess seam contract and the generated Cordis surface, including `ctx.e2b`. -- [Filesystem subsystem](../../docs/subsystems/filesystem.md) — the filesystem seam contract and the generated Cordis surface. - ------ - - -## Dev Note - -
-Working context for maintainers — click to expand - -None. - -
diff --git a/packages/e2b/README.zh.md b/packages/e2b/README.zh.md deleted file mode 100644 index 3b4267cd1c..0000000000 --- a/packages/e2b/README.zh.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -description: "E2B 远程运行时组映射:把文件与命令工作放进一个远程 Linux 沙箱,供 E2B 家族的用户与维护者浏览。" -kind: "package-group" ---- - -# packages/e2b - -[English](README.md) | 中文 - -## 概述 - -E2B 家族让 agent(智能体)在一个远程 Linux 沙箱中读取和编辑文件、运行 shell 命令并使用终端,而不是在主机上执行这些工作。文件系统工作与命令和终端执行保持分离,但两者使用同一个沙箱。现有的 shell、终端与语言服务器功能无需 E2B 专用工具即可继续工作。harness 进程、模型调用与会话状态仍在本地;沙箱是临时性的实验环境,且默认不包含在已发布的组合中。 - -## 目录 - -- [包](#packages) -- [相关文档](#related-documentation) -- [开发备注](#dev-note) - ------ - - -## 包 - -| 包(package) | 职责 | ctx 键 | -|---|---|---| -| [`e2b`](e2b/README.zh.md) | 承载文件操作与命令执行的共享远程 Linux 沙箱 | `ctx.e2b` | -| [`fs-e2b`](fs-e2b/README.zh.md) | 远程沙箱内的文件读取、写入、编辑与列表 | `ctx.fs` | -| [`subprocess-e2b`](subprocess-e2b/README.zh.md) | 远程沙箱内的 shell 命令与交互式终端 | `ctx.subprocess` | - ------ - - -## 相关文档 - -- [可移植执行世界决策](../../.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md)——执行世界为何可以在不移动 harness 的情况下迁移,以及哪些内容留在本地。 -- [子进程子系统](../../docs/subsystems/subprocess.zh.md)——子进程 seam 约定与生成的 Cordis 接口,包括 `ctx.e2b`。 -- [文件系统子系统](../../docs/subsystems/filesystem.zh.md)——文件系统 seam 约定与生成的 Cordis 接口。 - ------ - - -## 开发备注 - -
-维护者的工作上下文——点击展开 - -无。 - -
diff --git a/packages/e2b/e2b/README.md b/packages/e2b/e2b/README.md deleted file mode 100644 index 09c23e4540..0000000000 --- a/packages/e2b/e2b/README.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -description: "One shared remote Linux sandbox for E2B-backed file and command work: configuration, lifetime, and what happens at startup and shutdown." -kind: "package-reference" ---- - -# @deepseek-ai/dsh-e2b - -English | [中文](README.zh.md) - -## Summary - -`dsh-e2b` runs the agent's file operations, shell commands, and terminals in one shared remote Linux sandbox instead of on your machine. The sandbox is created at startup and deleted when its configured lifetime expires or the app shuts down, so everything it holds is ephemeral. Configure an API key, an absolute remote working directory, and the sandbox lifetime. Use it with `dsh-fs-e2b` and `dsh-subprocess-e2b`; by itself it adds no user-visible capability. It sends nothing to the model, and no shipped composition enables E2B by default. - -## Table of Contents - -- [Use this package](#use-this-package) -- [Understand the implementation](#understand-the-implementation) -- [Further Exploration](#further-exploration) -- [Model Experience](#model-experience) -- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) -- [Dev Note](#dev-note) - ------ - - -## Use this package - -Use this package when you want the agent's file and command work to run in a remote Linux sandbox rather than on your machine. It is the foundation of the E2B family: with the filesystem and subprocess packages mounted, all of that work shares one remote working directory and process world. - -### When to choose it - -Choose the E2B family when work should be isolated from the host machine — for example, when you want the agent's file edits and command runs to happen somewhere disposable. Choose the local filesystem and subprocess packages when running on the host is fine. This package is invisible to the model and adds no request cost. - -### Minimal configuration - -Three settings matter: an API key (or the `E2B_API_KEY` environment variable), an absolute remote working directory, and the sandbox lifetime. A bad key, a relative working directory, or an invalid lifetime rejects startup before any remote work happens. - -```yaml -- name: '@deepseek-ai/dsh-e2b' - config: - apiKey: - cwd: /home/user/workspace - timeoutMs: 300000 - -- name: '@deepseek-ai/dsh-subprocess-e2b' -- name: '@deepseek-ai/dsh-fs-e2b' -``` - -| Field | Default | Meaning | -|---|---|---| -| `apiKey` | `E2B_API_KEY` | API key for the host SDK connection; never installed in the sandbox | -| `cwd` | `/home/user/workspace` | Remote working directory the family shares; absolute POSIX path | -| `timeoutMs` | `300,000` | Sandbox lifetime in milliseconds; the sandbox is deleted when it expires | - -The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-e2b) is the exhaustive source for every accepted field and its JSDoc. - -### What you get - -With this package mounted, file reads and writes, shell commands, and terminals all operate inside the sandbox's working directory, so the agent sees one consistent remote world: what it writes with the file features is what its commands can read, and vice versa. The remote working directory is created if it does not exist yet. - -### Starting and stopping the sandbox - -Loading the plugin starts the sandbox in the background; the filesystem and subprocess features are ready once it is up. The sandbox lives for the configured lifetime (default five minutes) unless the app stops first — in both cases it is deleted, so save anything you still need before then. If the sandbox disappears while running (expired or removed elsewhere), the family treats that as a clean end rather than an error. - ------ - - -## Understand the implementation - -
-Implementation internals — click to expand - -This section explains the design decisions behind the owner and points at the code that realizes them; the observable behavior is fully covered in [Use this package](#use-this-package). - -### Design philosophy - -- **One sandbox, one handle.** All adapters await the same `getSandbox()` promise, so filesystem and process operations share one remote Linux world. -- **Secure by construction.** The sandbox is created with `secure: true` and `lifecycle: { onTimeout: 'kill' }`, so expiry always deletes it. -- **Isolated control shells.** `e2bControlEnvs()` gives every internal command shell a fresh randomized `HOME`, and `quoteE2BShellArg()` preserves opaque arguments through the SDK's unavoidable `/bin/bash -l -c` layer. - -### Source map - -| File | Role | -|---|---| -| [`src/index.ts`](src/index.ts) | Plugin entry: `E2BRuntime` service, `Config` schema, validation, sandbox open and teardown | -| — | No runtime invariant companion is published; sandbox creation and teardown have one SDK promise and no independent event or mutable-data relationship to cross-check. | - -### Lifecycle - -`open()` creates the sandbox, prepares `cwd` and the private runtime root, rejects a non-directory or symlink runtime root, and applies `chmod 700`. Disposal prevents new handle acquisition, awaits setup, and deletes the sandbox, accepting `SandboxNotFoundError` as quiescence. `getSandbox()` re-checks the disposed flag after awaiting readiness, so disposal racing readiness still rejects acquisition; an eager connection failure stays observed but does not reject plugin load, and `getSandbox()` surfaces it. - -### Setup failure handling - -Any directory-setup failure makes one deletion attempt and preserves the original error; a failed rollback is bounded by E2B's configured sandbox timeout (see the Dev Note). Provider plugins must load after this owner and dispose before it, because every adapter awaits the same handle. - -
- ------ - - -## Further Exploration - -Read these pages when the package-level contract is not enough. They move from the family composition to the subprocess seam surface and the decision evidence behind the remote execution world. - -- [E2B provider family map](../README.md) — the three packages and the opt-in composition. -- [Subprocess subsystem](../../../docs/subsystems/subprocess.md) — the subprocess seam contract and the generated Cordis surface, including `ctx.e2b`. -- [Portable execution-world decision](../../../.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md) — why consumers delegate to `ctx.fs` and `ctx.subprocess`, and what stays in the host. -- [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-e2b) — every accepted config field and its source declaration. - ------ - - -## Model Experience - -None, as the shared remote-runtime owner registers no model context; provider adapters and consumers own rendered effects. - -#### KV Cache effect - -No direct invalidation: the owner contributes no request tokens and never mutates a request prefix, so provider cache reuse is unaffected. - -## Known Limitations and Deferred Work - - - - -These limits define when the E2B family is a poor fit or needs special operational care. They are current package constraints, not a task backlog. - -- **Not a whole-harness runtime** — Cordis services, agent/session state, session logs, LLM requests, skills, and SDK-side buffers stay in the host process. -- **Sandbox state is ephemeral** — disposal and timeout delete the sandbox; reconnect, pause/leave retention, templates, volumes, and snapshots are outside this POC. -- **No deployment platform is configured** — network policy, host-workspace synchronization, and sandbox discovery are outside this POC. -- **`cwd` is a resolution convention, not containment** — adapters and commands can address other sandbox paths; E2B network access retains the base image's policy. - - -### Dev Note - -
-Working context for maintainers — click to expand - -This Dev Note is working context for maintainers: open questions and directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above and the package code. - -#### Open: sandbox setup rollback - -The `open()` failure path makes a single deletion attempt and preserves the original setup failure. Retry state stays deferred unless a real double failure outlives E2B's configured sandbox timeout (TODO(e2b-setup-rollback)). - -
diff --git a/packages/e2b/e2b/README.zh.md b/packages/e2b/e2b/README.zh.md deleted file mode 100644 index 4bf1c6f220..0000000000 --- a/packages/e2b/e2b/README.zh.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -description: "E2B 文件与命令工作的共享远程 Linux 沙箱:配置、生命周期,以及启动与关闭时会发生什么。" -kind: "package-reference" ---- - -# @deepseek-ai/dsh-e2b - -[English](README.md) | 中文 - -## 概述 - -`dsh-e2b` 让 agent(智能体)的文件操作、shell 命令与终端在一个共享的远程 Linux 沙箱内运行,而不是在你的机器上。应用启动时会创建沙箱,并在配置的生命周期到期或应用关闭时删除它,因此其中保存的一切都是短暂的。请配置 API 密钥、绝对远程工作目录与沙箱生命周期。请与 `dsh-fs-e2b`、`dsh-subprocess-e2b` 一起使用;单独使用它不会带来任何用户可见的能力。它不会向模型发送任何内容,而且任何已发布的组合都不会默认启用 E2B。 - -## 目录 - -- [使用本包](#use-this-package) -- [理解实现](#understand-the-implementation) -- [进一步探索](#further-exploration) -- [模型体验](#model-experience) -- [已知限制与延期工作](#known-limitations-and-deferred-work) -- [开发备注](#dev-note) - ------ - - -## 使用本包 - -当你希望 agent 的文件操作与命令执行在远程 Linux 沙箱而非你的机器上进行时,使用本包。它是 E2B 家族的基础:挂载文件系统与子进程包之后,所有这些工作都会共享同一个远程工作目录与进程环境。 - -### 何时选择 - -当工作应与宿主机器隔离时——例如你希望 agent 的文件编辑与命令运行发生在某个可丢弃的环境中——选择 E2B 家族。当在宿主上运行没有问题的时候,选择本地的文件系统与子进程包。本包对模型不可见,也不增加任何请求成本。 - -### 最小配置 - -三个设置很重要:API 密钥(或 `E2B_API_KEY` 环境变量)、绝对远程工作目录与沙箱生命周期。密钥错误、相对工作目录或无效生命周期都会在任何远程工作开始前拒绝启动。 - -```yaml -- name: '@deepseek-ai/dsh-e2b' - config: - apiKey: - cwd: /home/user/workspace - timeoutMs: 300000 - -- name: '@deepseek-ai/dsh-subprocess-e2b' -- name: '@deepseek-ai/dsh-fs-e2b' -``` - -| 字段 | 默认值 | 含义 | -|---|---|---| -| `apiKey` | `E2B_API_KEY` | 宿主 SDK 连接的 API 密钥;绝不会安装进沙箱 | -| `cwd` | `/home/user/workspace` | 家族共享的远程工作目录;必须是绝对 POSIX 路径 | -| `timeoutMs` | `300,000` | 沙箱生命周期(毫秒);到期后沙箱被删除 | - -生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-e2b)完整列出了每个受支持字段及其 JSDoc,是这些信息的真源。 - -### 你能得到什么 - -挂载本包后,文件读写、shell 命令与终端都会在沙箱的工作目录内运行,因此 agent 看到的是一个一致的远程世界:它用文件功能写入的内容,正是它的命令能够读取的内容,反之亦然。远程工作目录若不存在,会自动创建。 - -### 沙箱的启动与停止 - -加载插件会在后台启动沙箱;文件系统与子进程功能在其就绪后即可使用。沙箱存活时间为配置的生命周期(默认五分钟),除非应用先停止——两种情况下沙箱都会被删除,因此请在此之前保存你仍需要的内容。如果运行期间沙箱消失(到期或被别处删除),家族会将其视为正常终止,而不是错误。 - ------ - - -## 理解实现 - -
-实现细节——点击展开 - -本节解释所有者背后的设计决策,并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。 - -### 设计理念 - -- **一个沙箱,一个句柄。** 所有适配器都等待同一个 `getSandbox()` promise,因此文件系统与进程操作共享同一个远程 Linux 世界。 -- **构造即安全。** 沙箱以 `secure: true` 和 `lifecycle: { onTimeout: 'kill' }` 创建,因此超时必定删除它。 -- **隔离的控制 shell。** `e2bControlEnvs()` 为每个内部命令 shell 提供全新随机生成的 `HOME`,`quoteE2BShellArg()` 则通过 SDK 不可避免的 `/bin/bash -l -c` 层保留不透明参数。 - -### 源码索引 - -| 文件 | 职责 | -|---|---| -| [`src/index.ts`](src/index.ts) | 插件入口:`E2BRuntime` 服务、`Config` schema、校验、沙箱创建与拆除 | -| — | 不发布运行时不变式伴生入口;沙箱创建与拆除只有一个 SDK promise,没有可交叉核对的独立事件或可变数据关系。 | - -### 生命周期 - -`open()` 创建沙箱、准备 `cwd` 与私有运行时根目录、拒绝非目录或符号链接的运行时根目录,并执行 `chmod 700`。dispose(资源释放)会阻止新的句柄获取、等待初始化完成并删除沙箱,把 `SandboxNotFoundError` 视为完全停稳。`getSandbox()` 在等待就绪后重新检查已释放标志,因此与就绪发生竞态的资源释放仍会拒绝获取句柄;预先发起的连接即使失败也会保持可观察状态,但不会导致插件加载失败;`getSandbox()` 会将该失败暴露给调用方。 - -### 初始化失败处理 - -任何目录初始化失败都会尝试删除一次并保留原始错误;回滚失败由 E2B 配置的沙箱超时约束(见开发备注)。提供方插件必须在该所有者之后加载、并在其之前 dispose,因为每个适配器都等待同一个句柄。 - -
- ------ - - -## 进一步探索 - -当包级约定不够用时阅读以下页面。它们从家族组合逐步进入子进程 seam 表面,以及远程执行世界背后的决策证据。 - -- [E2B 提供方家族地图](../README.zh.md)——三个包与可选组合。 -- [子进程子系统](../../../docs/subsystems/subprocess.zh.md)——子进程 seam 约定与生成的 Cordis 表面,包括 `ctx.e2b`。 -- [可移植执行世界决策](../../../.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md)——消费方为何委托给 `ctx.fs` 与 `ctx.subprocess`,以及留在宿主中的内容。 -- [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-e2b)——每个受支持配置字段及其源声明。 - ------ - - -## 模型体验 - -无。本共享远程运行时所有者不注册任何模型上下文;提供方适配器与消费方拥有所有渲染效果。 - -#### KV Cache 影响 - -不会直接失效:所有者不贡献任何请求 token,也从不改变请求前缀,因此提供方缓存复用不受影响。 - -## 已知限制与延期工作 - - - - -这些限制说明 E2B 家族何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。 - -- **不是完整的 harness 运行时**:Cordis 服务、agent/会话状态、会话日志、LLM(大语言模型)请求、skill(技能)和 SDK 侧缓冲仍留在宿主进程中。 -- **沙箱状态是短暂的**:dispose 与超时都会删除沙箱;重新连接、pause/leave 保留、模板、卷和快照均不在本 POC 范围内。 -- **没有配置部署平台**:网络策略、宿主工作区同步与沙箱发现均不在本 POC 范围内。 -- **`cwd` 是解析约定,而不是包含边界**:适配器与命令可以访问沙箱中的其他路径;E2B 网络访问也继续采用基础镜像的策略。 - - -### 开发备注 - -
-维护者的工作上下文——点击展开 - -本开发备注是维护者的工作上下文:开放问题与尚未决定的探索方向。它明确不具权威性——已交付的行为、限制与既定理由以上文和包代码为准。 - -#### 开放:沙箱初始化回滚 - -`open()` 的失败路径只会尝试删除一次,并保留原始初始化失败。除非真实的双重失败超出 E2B 配置的沙箱超时,否则重试状态保持延后(TODO(e2b-setup-rollback))。 - -
diff --git a/packages/e2b/e2b/package.json b/packages/e2b/e2b/package.json deleted file mode 100644 index 9a2bd625fc..0000000000 --- a/packages/e2b/e2b/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "@deepseek-ai/dsh-e2b", - "description": "Shared E2B sandbox lifecycle for DeepSeek Harness provider adapters", - "version": "0.1.5-rc.2", - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", - "directory": "packages/e2b/e2b" - }, - "type": "module", - "main": "lib/index.js", - "types": "lib/types/index.d.ts", - "exports": { - ".": { - "types": "./lib/types/index.d.ts", - "default": "./lib/index.js" - }, - "./src/*": "./src/*", - "./package.json": "./package.json" - }, - "files": [ - "lib/index.js", - "lib/types/**/*.d.ts" - ], - "license": "MIT", - "peerDependencies": { - "@deepseek-ai/cordis": "workspace:^", - "@deepseek-ai/dsh-http-proxy": "workspace:^" - }, - "dependencies": { - "@deepseek-ai/schemastery": "workspace:^", - "e2b": "2.29.1" - }, - "devDependencies": { - "@deepseek-ai/cordis": "workspace:^", - "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", - "@deepseek-ai/dsh-app-boot": "workspace:^", - "@deepseek-ai/dsh-bash-local": "workspace:^", - "@deepseek-ai/dsh-fs-e2b": "workspace:^", - "@deepseek-ai/dsh-loader-smoke": "workspace:^", - "@deepseek-ai/dsh-lsp": "workspace:^", - "@deepseek-ai/dsh-lsp-stdio": "workspace:^", - "@deepseek-ai/dsh-sandbox-policy": "workspace:^", - "@deepseek-ai/dsh-session": "workspace:^", - "@deepseek-ai/dsh-session-projection": "workspace:^", - "@deepseek-ai/dsh-subprocess-e2b": "workspace:^", - "@deepseek-ai/dsh-terminal": "workspace:^", - "@deepseek-ai/dsh-terminal-bash": "workspace:^", - "@deepseek-ai/dsh-http-proxy": "workspace:^", - "@deepseek-ai/dsh-launch-environment": "workspace:^" - } -} diff --git a/packages/e2b/e2b/src/api-url.ts b/packages/e2b/e2b/src/api-url.ts deleted file mode 100644 index 6d113ea581..0000000000 --- a/packages/e2b/e2b/src/api-url.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * The E2B control-plane URL, derived the way the SDK derives it. - * @module @deepseek-ai/dsh-e2b/src/api-url.ts - */ - -/** The SDK's own default control-plane domain; `E2B_DOMAIN` overrides it there and here alike. */ -const E2B_DEFAULT_DOMAIN = 'e2b.app' - -/** The debug control plane the SDK substitutes, on loopback and plain HTTP. */ -const E2B_DEBUG_API_URL = 'http://localhost:3000' - -/** - * The control-plane URL the SDK will actually call, derived the way the SDK derives it: an explicit - * `E2B_API_URL` first, then the debug substitute, then the domain default. Choosing a proxy for - * anything else would pick the wrong scheme's proxy, ignore a bypass entry naming the real host, and - * — for the loopback debug plane — hand a proxy the control-plane traffic and its API key. - * - * @param env - the process environment to read; overridable so tests need no ambient state. - * @returns the absolute control-plane URL. - */ -export function e2bApiUrl(env: NodeJS.ProcessEnv = process.env): string { - const explicit = env.E2B_API_URL - if (explicit !== undefined && explicit !== '') return explicit - if ((env.E2B_DEBUG ?? 'false').toLowerCase() === 'true') return E2B_DEBUG_API_URL - return `https://api.${env.E2B_DOMAIN ?? E2B_DEFAULT_DOMAIN}` -} diff --git a/packages/e2b/e2b/src/index.ts b/packages/e2b/e2b/src/index.ts deleted file mode 100644 index 4333fdc431..0000000000 --- a/packages/e2b/e2b/src/index.ts +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Shared ownership of one E2B sandbox. Capability adapters await the same SDK - * handle, so filesystem and process operations inhabit one remote Linux world. - * @module @deepseek-ai/dsh-e2b - */ - -import { randomUUID } from 'node:crypto' -import { posix } from 'node:path' -import { Context, Service } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -import { FileType, Sandbox, SandboxNotFoundError } from 'e2b' -import { proxyRouteFor } from '@deepseek-ai/dsh-http-proxy' -import { e2bApiUrl } from './api-url.ts' - -export { - CommandExitError, - FileNotFoundError, - FileType, - Sandbox, - SandboxNotFoundError, -} from 'e2b' -export type { CommandHandle, CommandResult, EntryInfo } from 'e2b' - -/** - * Quote one opaque argument for the SDK's unavoidable `/bin/bash -l -c` layer. - * @param value - Exact argument value to preserve. - * @returns A single shell word with no interpolation. - */ -export function quoteE2BShellArg(value: string): string { - return `'${value.replaceAll('\'', "'\"'\"'")}'` -} - -/** - * Isolate E2B's hard-coded login shell behind a fresh randomized home path. - * @param overrides - Additional environment entries for the internal command. - * @returns A fresh mutable map that the E2B SDK may extend. - */ -export function e2bControlEnvs( - overrides: Readonly> = {}, -): Record { - return { ...overrides, HOME: `/.dsh-e2b-control-${randomUUID()}` } -} - -/** Configuration for the shared E2B sandbox owner. */ -export interface Config { - /** API key; omission reads `E2B_API_KEY`. It is never forwarded into the sandbox. */ - apiKey?: string - /** Shared remote working directory, created before adapters receive the sandbox. */ - cwd?: string - /** E2B sandbox lifetime in milliseconds; expiry always deletes the sandbox. */ - timeoutMs?: number -} - -interface ResolvedConfig { - apiKey: string - cwd: string - timeoutMs: number -} - -interface SchemaResolvedConfig extends Config { - cwd: string - timeoutMs: number -} - -declare module '@deepseek-ai/cordis' { - interface Context { - e2b: E2BRuntime - } -} - - -/** - * Creates one lazily consumable E2B SDK handle and deletes the sandbox at - * timeout or disposal. Creation begins at plugin construction; adapters await - * {@link getSandbox} before their first operation. - */ -export class E2BRuntime extends Service { - static Config: z = z.object({ - apiKey: z.string(), - cwd: z.string().default('/home/user/workspace'), - timeoutMs: z.number().default(300_000), - }) - - /** Validated remote working directory shared by provider adapters. */ - readonly cwd: string - /** Remote directory reserved for adapter-owned process and terminal state. */ - readonly runtimeRoot: string - - private readonly config: ResolvedConfig - private readonly ready: Promise - private disposed = false - - constructor(ctx: Context, config: Config) { - super(ctx, 'e2b') - // Schemastery fills these fields before construction; the type does not encode that step. - const resolved = config as SchemaResolvedConfig - const apiKey = config.apiKey ?? process.env.E2B_API_KEY - this.config = { - apiKey: apiKey ?? '', - cwd: resolved.cwd, - timeoutMs: resolved.timeoutMs, - } - this.validate() - this.cwd = this.config.cwd - this.runtimeRoot = posix.join(this.cwd, '.dsh-e2b') - this.ready = this.open() - // A deployment may load the owner before any adapter uses it. Keep a - // failed eager connection observed; getSandbox() still returns the error. - void this.ready.catch(() => {}) - - ctx.effect(() => async () => { - this.disposed = true - let sandbox: Sandbox - try { - sandbox = await this.ready - } catch (_sandboxSetupFailure) { - // open() either acquired no sandbox or already made the POC's one rollback attempt. - return - } - try { - await sandbox.kill() - } catch (error: unknown) { - if (!(error instanceof SandboxNotFoundError)) throw error - } - }, 'e2b sandbox teardown') - } - - /** - * Return the shared live SDK handle. - * @returns the created sandbox after the configured cwd exists. - * @throws when E2B rejects creation or the service is disposing. - */ - async getSandbox(): Promise { - if (this.disposed) throw new Error('E2B sandbox service is disposing') - const sandbox = await this.ready - // Disposal can race the awaited sandbox readiness despite the synchronous precheck. - // oxlint-disable-next-line typescript/no-unnecessary-condition -- Awaiting readiness yields to disposal. - if (this.disposed) throw new Error('E2B sandbox service is disposing') - return sandbox - } - - private validate(): void { - if (this.config.apiKey.length === 0) { - throw new Error('dsh-e2b: configure apiKey or set E2B_API_KEY') - } - if (!posix.isAbsolute(this.config.cwd)) { - throw new Error(`dsh-e2b: cwd must be an absolute Linux path: ${this.config.cwd}`) - } - if (!Number.isFinite(this.config.timeoutMs) || this.config.timeoutMs <= 0) { - throw new Error('dsh-e2b: timeoutMs must be a positive finite number') - } - } - - private async open(): Promise { - // The SDK builds its own undici dispatcher, so the global one never reaches it; it takes a proxy - // URL instead and reads no environment of its own. The decision is made against the URL the SDK - // will really call, so a bypass entry naming that host is honored and a loopback debug plane - // stays direct. - const route = proxyRouteFor(new URL(e2bApiUrl())) - const sandbox = await Sandbox.create({ - apiKey: this.config.apiKey, - timeoutMs: this.config.timeoutMs, - secure: true, - lifecycle: { onTimeout: 'kill' }, - ...route.proxied ? { proxy: route.proxy } : {}, - }) - try { - await sandbox.files.makeDir(this.cwd) - await sandbox.files.makeDir(this.runtimeRoot) - const runtimeRoot = await sandbox.files.getInfo(this.runtimeRoot) - if (runtimeRoot.type !== FileType.DIR || runtimeRoot.symlinkTarget !== undefined) { - throw new Error(`dsh-e2b: runtime root must be a real directory: ${this.runtimeRoot}`) - } - await sandbox.commands.run( - `chmod 700 -- ${quoteE2BShellArg(this.runtimeRoot)}`, - { envs: e2bControlEnvs() }, - ) - return sandbox - } catch (error: unknown) { - try { - await sandbox.kill() - } catch (_sandboxSetupRollbackFailure) { - // TODO(e2b-setup-rollback): Add retry state only if a real double failure - // outlives E2B's configured sandbox timeout. - } - throw error - } - } -} - -export default E2BRuntime diff --git a/packages/e2b/e2b/tests/composition.e2e.ts b/packages/e2b/e2b/tests/composition.e2e.ts deleted file mode 100644 index de6f192bf3..0000000000 --- a/packages/e2b/e2b/tests/composition.e2e.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { access } from 'node:fs/promises' -import { join, posix } from 'node:path' -import { fileURLToPath } from 'node:url' -import { Context } from '@deepseek-ai/cordis' -import { describe, expect, it } from 'vitest' -import type { Agent } from '@deepseek-ai/dsh-agent' -import { runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke' -import { - FileNotFoundError, - Sandbox, - SandboxNotFoundError, -} from '@deepseek-ai/dsh-e2b' -import TerminalSessionService, { TerminalSessionId } from '@deepseek-ai/dsh-terminal' -import { BashTerminalBackend } from '@deepseek-ai/dsh-terminal-bash' -import SandboxPolicyService from '@deepseek-ai/dsh-sandbox-policy' -import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import { Session, SessionId } from '@deepseek-ai/dsh-session' -import E2BSubprocessRuntime from '@deepseek-ai/dsh-subprocess-e2b' -import { unsupportedInbox } from '@deepseek-ai/dsh-agent-loop-testkit' - -const fixtureRoot = fileURLToPath(new URL('./fixtures/composition/', import.meta.url)) -const binScript = join(fixtureRoot, 'bin.ts') -const configPath = join(fixtureRoot, 'cordis.yml') -const tsconfigPath = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)) - -describe.skipIf(!process.env.E2B_API_KEY)('E2B live Loader composition', () => { - it('scrubs credentials before actual E2B command and PTY login shells', async () => { - const apiKey = process.env.E2B_API_KEY - if (apiKey === undefined) throw new Error('E2B_API_KEY disappeared before the PTY environment test') - const sandbox = await Sandbox.create({ - apiKey, - envs: { NPM_TOKEN: 'sentinel-secret', DSH_STALE: 'sentinel-stale', KEEP: 'visible' }, - timeoutMs: 60_000, - secure: true, - lifecycle: { onTimeout: 'kill' }, - }) - try { - const profileLeakPath = '/home/user/dsh-e2b-bootstrap-profile-leak' - const hostileProfile = [ - 'if [[ "${NPM_TOKEN-}" == "sentinel-secret" ]]; then', - ` printf leaked > ${profileLeakPath}`, - 'fi', - '', - ].join('\n') - await sandbox.files.write([ - { path: '/home/user/.bash_profile', data: hostileProfile }, - { path: '/home/user/.profile', data: hostileProfile }, - { path: '/home/user/.bashrc', data: hostileProfile }, - ]) - const ctx = new Context() - ctx.provide('e2b', { - cwd: '/home/user', - runtimeRoot: '/home/user/.dsh-e2b', - getSandbox: async () => sandbox, - } as never) - await ctx.plugin(SessionProjectionRegistry) - const sandboxPolicyFiber = await ctx.plugin(SandboxPolicyService, { - mode: 'danger-full-access', - workspaceRoot: '/home/user', - }) - const ptyFiber = await ctx.plugin(TerminalSessionService) - const subprocessFiber = await ctx.plugin(E2BSubprocessRuntime) - const node = await ctx.subprocess.resolveExecutable('node') - const relativeNodePath = posix.relative(ctx.e2b.cwd, posix.dirname(node)) || '.' - await expect(ctx.subprocess.resolveExecutable('node', { PATH: relativeNodePath })).resolves.toBe(node) - await expect(sandbox.files.read(profileLeakPath)).rejects.toBeInstanceOf(FileNotFoundError) - const environmentProbe = ctx.subprocess.spawn({ - argv: ['/bin/bash', '-c', [ - 'dsh_leak=0', - 'for dsh_pid in "$PPID" $(ps -o pid= --ppid "$PPID"); do', - ' [[ "$dsh_pid" == "$$" ]] && continue', - ' if tr "\\0" "\\n" < "/proc/$dsh_pid/environ" 2>/dev/null | grep -Fqx "NPM_TOKEN=sentinel-secret"; then dsh_leak=1; fi', - 'done', - 'printf "DIRECT=<%s> LEAK=<%s>\\n" "${NPM_TOKEN-}" "$dsh_leak"', - ].join('\n')], - cwd: '/home/user', - stdio: { stdin: 'ignore', stdout: { maxBytes: 1_024 }, stderr: { maxBytes: 1_024 } }, - graceMs: 500, - env: {}, - }) - await expect(environmentProbe.done).resolves.toEqual({ exitCode: 0, signal: null }) - expect(environmentProbe.collected.stdout?.readFrom(0).text).toBe('DIRECT=<> LEAK=<0>\n') - await expect(sandbox.files.read(profileLeakPath)).rejects.toBeInstanceOf(FileNotFoundError) - const ownerId = SessionId('e2b-pty-env-owner') - const ownerSession = Session.create(ownerId) - const owner: Agent = { - id: ownerId, - options: {}, - session: ownerSession, - inbox: unsupportedInbox(), - status: 'idle', - ctx, - send() {}, - followup() {}, - steer() {}, - inject() {}, - cancel() {}, - runMaintenance: task => task(new AbortController().signal), - whenIdle: () => Promise.resolve(), - } - const backend = new BashTerminalBackend(ctx, { - backendType: 'shell', shellDialect: 'bash', shellPath: '/bin/bash', shellArgs: ['--noprofile', '--norc', '-i'], - rows: 24, cols: 80, - scrollbackLines: 100, scrollbackMaxBytes: 65_536, maxReadBytes: 16_384, - pollIntervalMs: 25, exactProbeAfterMs: 150, idleSilenceMs: 1_000, - handoffGraceMs: 500, timeoutMs: 5_000, disposeGraceMs: 1_000, - }) - const session = await backend.spawn({ sessionId: TerminalSessionId('env'), owner, type: 'shell' }) - const result = await session.startSend({ - text: "printf 'NPM=<%s> DSH=<%s> KEEP=<%s>\\n' \"$NPM_TOKEN\" \"$DSH_STALE\" \"$KEEP\"", - submit: true, - }).done - expect(result.viewport).toContain('NPM=<> DSH=<> KEEP=') - expect(result.viewport).not.toContain('sentinel-secret') - expect(result.viewport).not.toContain('sentinel-stale') - await expect(sandbox.files.read(profileLeakPath)).rejects.toBeInstanceOf(FileNotFoundError) - await session.close('environment test complete') - await subprocessFiber.dispose() - await ptyFiber.dispose() - await sandboxPolicyFiber.dispose() - - } finally { - await sandbox.kill().catch(() => false) - } - }, 70_000) - - it('runs FS, Bash, PTY, and LSP in one sandbox and deletes it', async () => { - const { stdout, stderr } = await runLoaderSmoke({ - label: 'E2B composition', - tempDirPrefix: 'dsh-e2b-composition-', - binScript, - libBinScript: binScript, - configPath, - tsconfigPath, - env: { - NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '), - }, - processTimeoutMs: 180_000, - inspect: async (cwd) => { - for (const name of ['from-fs.txt', 'from-bash.txt', 'multibyte # file.ts', 'fixture-lsp.mjs']) { - await expect(access(join(cwd, name))).rejects.toMatchObject({ code: 'ENOENT' }) - } - }, - }) - - expect(stderr).toBe('') - const output = JSON.parse(stdout) as Record - expect(output).toMatchObject({ - bashRead: 'versioned-by-fs\n', - fsRead: 'written-by-bash\n', - explicitEnvironment: true, - splitUtf8Output: '你好', - hover: { - kind: 'hover', - hover: { contents: '**remote hover** 你好 café' }, - }, - definition: { - kind: 'locations', - locations: [{ range: { start: { line: 0, character: 6 }, end: { line: 0, character: 10 } } }], - }, - terminal: { - echo: { waitReason: 'stdin_read', sessionStatus: { kind: 'running' } }, - signal: { delivered: true }, - interrupted: { sessionStatus: { kind: 'running' } }, - treeCleanup: true, - }, - }) - const terminalMotd = (output.terminal as { motd: string }).motd - expect(terminalMotd.length).toBeGreaterThan(0) - expect(terminalMotd).not.toContain('exec /bin/bash') - expect(terminalMotd).not.toContain('.dsh-e2b/terminals/') - expect((output.terminal as { echo: { viewport: string } }).echo.viewport).toContain('PTY-你好') - expect((output.terminal as { scrollback: string }).scrollback).toContain('PTY-你好') - expect((output.terminal as { signal: { targetPgid: number } }).signal.targetPgid).toBeGreaterThan(0) - expect(['stdin_read', 'inferred_idle']).toContain( - (output.terminal as { interrupted: { waitReason: string } }).interrupted.waitReason, - ) - const apiKey = process.env.E2B_API_KEY - if (apiKey === undefined) throw new Error('E2B_API_KEY disappeared during the live composition test') - await expect(Sandbox.getInfo(String(output.sandboxId), { apiKey })).rejects.toBeInstanceOf(SandboxNotFoundError) - await expect.poll(async () => { - const sandboxes = await Sandbox.list({ apiKey }).nextItems() - return sandboxes.some(sandbox => sandbox.sandboxId === output.sandboxId) - }, { interval: 250, timeout: 5_000 }).toBe(false) - }, 195_000) -}) diff --git a/packages/e2b/e2b/tests/e2b.spec.ts b/packages/e2b/e2b/tests/e2b.spec.ts deleted file mode 100644 index e1007dfbef..0000000000 --- a/packages/e2b/e2b/tests/e2b.spec.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { Mock } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import type { Sandbox as SandboxType } from 'e2b' -import E2BRuntime, { - e2bControlEnvs, - FileType, - SandboxNotFoundError, - quoteE2BShellArg, -} from '@deepseek-ai/dsh-e2b' - -const sdk = vi.hoisted(() => ({ - create: vi.fn(), -})) - -vi.mock('e2b', async (importOriginal) => { - const actual = await importOriginal() - // The mock replaces only the SDK's static factory surface and is never constructed. - // oxlint-disable-next-line typescript/no-extraneous-class -- The SDK contract is a class with a static factory. - class FakeSandbox { - static create(...args: unknown[]): unknown { - return sdk.create(...args) - } - } - return { ...actual, Sandbox: FakeSandbox } -}) - -interface SandboxFixture { - sandbox: SandboxType - makeDir: ReturnType - getInfo: ReturnType - run: Mock - kill: ReturnType -} - -type RunCommand = ( - command: string, - options?: { envs?: Record }, -) => Promise<{ exitCode: number; stdout: string; stderr: string }> - -function fakeSandbox(id = 'sandbox-1'): SandboxFixture { - const makeDir = vi.fn().mockResolvedValue(true) - const getInfo = vi.fn().mockResolvedValue({ type: FileType.DIR }) - const run = vi.fn().mockResolvedValue({ exitCode: 0, stdout: '', stderr: '' }) - const kill = vi.fn().mockResolvedValue(undefined) - const sandbox = { - sandboxId: id, - files: { makeDir, getInfo }, - commands: { run }, - kill, - } as unknown as SandboxType - return { sandbox, makeDir, getInfo, run, kill } -} - -beforeEach(() => { - sdk.create.mockReset() - vi.unstubAllEnvs() -}) - -describe('E2BRuntime', () => { - it('gives each SDK login shell a fresh non-overridable control home', () => { - const first = e2bControlEnvs({ HOME: '/hostile', NPM_TOKEN: '' }) - const second = e2bControlEnvs() - - expect(first.HOME).toMatch(/^\/\.dsh-e2b-control-/) - expect(first).toEqual({ HOME: first.HOME, NPM_TOKEN: '' }) - expect(first.HOME).not.toBe(second.HOME) - }) - - it('creates one protected shared sandbox and kills it on default disposal', async () => { - const fixture = fakeSandbox() - sdk.create.mockResolvedValue(fixture.sandbox) - const ctx = new Context() - const fiber = await ctx.plugin(E2BRuntime, { apiKey: 'test-key' }) - - const service = ctx.e2b - await expect(service.getSandbox()).resolves.toBe(fixture.sandbox) - expect(service.cwd).toBe('/home/user/workspace') - expect(service.runtimeRoot).toBe('/home/user/workspace/.dsh-e2b') - expect(sdk.create).toHaveBeenCalledWith({ - apiKey: 'test-key', - timeoutMs: 300_000, - secure: true, - lifecycle: { onTimeout: 'kill' }, - }) - expect(fixture.makeDir).toHaveBeenNthCalledWith(1, '/home/user/workspace') - expect(fixture.makeDir).toHaveBeenNthCalledWith(2, '/home/user/workspace/.dsh-e2b') - expect(fixture.getInfo).toHaveBeenCalledWith('/home/user/workspace/.dsh-e2b') - const runOptions = fixture.run.mock.calls[0]?.[1] - expect(runOptions?.envs?.HOME).toMatch(/^\/\.dsh-e2b-control-/) - expect(fixture.run).toHaveBeenCalledWith( - "chmod 700 -- '/home/user/workspace/.dsh-e2b'", - { envs: { HOME: runOptions?.envs?.HOME } }, - ) - - await fiber.dispose() - expect(fixture.kill).toHaveBeenCalledOnce() - await expect(service.getSandbox()).rejects.toThrow(/disposing/) - }) - - it('rejects handle acquisition when disposal starts during setup', async () => { - const fixture = fakeSandbox() - const opening = Promise.withResolvers() - sdk.create.mockReturnValue(opening.promise) - const ctx = new Context() - const fiber = await ctx.plugin(E2BRuntime, { apiKey: 'test-key' }) - - const acquisition = ctx.e2b.getSandbox() - const disposing = fiber.dispose() - opening.resolve(fixture.sandbox) - - await expect(acquisition).rejects.toThrow(/disposing/) - await expect(disposing).resolves.toBeUndefined() - expect(fixture.kill).toHaveBeenCalledOnce() - }) - - it('reads the key from the environment and honors the configured cwd and lifetime', async () => { - vi.stubEnv('E2B_API_KEY', 'environment-key') - const fixture = fakeSandbox('configured-sandbox') - sdk.create.mockResolvedValue(fixture.sandbox) - const ctx = new Context() - const fiber = await ctx.plugin(E2BRuntime, { - cwd: '/workspace/project', - timeoutMs: 60_000, - }) - await ctx.e2b.getSandbox() - - expect(sdk.create).toHaveBeenCalledWith({ - apiKey: 'environment-key', - timeoutMs: 60_000, - secure: true, - lifecycle: { onTimeout: 'kill' }, - }) - expect(ctx.e2b.cwd).toBe('/workspace/project') - await fiber.dispose() - expect(fixture.kill).toHaveBeenCalledOnce() - }) - - it('accepts a missing sandbox when disposal itself requests deletion', async () => { - const fixture = fakeSandbox() - fixture.kill.mockRejectedValue(new SandboxNotFoundError('already deleted')) - sdk.create.mockResolvedValue(fixture.sandbox) - const ctx = new Context() - const errors: unknown[] = [] - ctx.logger.error = ((error: unknown) => { errors.push(error) }) as typeof ctx.logger.error - const fiber = await ctx.plugin(E2BRuntime, { apiKey: 'test-key' }) - await ctx.e2b.getSandbox() - - await fiber.dispose() - expect(fixture.kill).toHaveBeenCalledOnce() - expect(errors).toEqual([]) - }) - - it('does not classify other disposal failures as an already-gone sandbox', async () => { - const fixture = fakeSandbox() - const failure = new Error('disposition unknown') - fixture.kill.mockRejectedValue(failure) - sdk.create.mockResolvedValue(fixture.sandbox) - const ctx = new Context() - const errors: unknown[] = [] - ctx.logger.error = ((error: unknown) => { errors.push(error) }) as typeof ctx.logger.error - const fiber = await ctx.plugin(E2BRuntime, { apiKey: 'test-key' }) - await ctx.e2b.getSandbox() - await expect(fiber.dispose()).resolves.toBeUndefined() - expect(fixture.kill).toHaveBeenCalledOnce() - expect(errors).toContain(failure) - }) - - it('kills a newly created sandbox when remote directory setup fails', async () => { - const fixture = fakeSandbox() - fixture.makeDir.mockRejectedValueOnce(new Error('setup failed')) - sdk.create.mockResolvedValue(fixture.sandbox) - const ctx = new Context() - const fiber = await ctx.plugin(E2BRuntime, { apiKey: 'test-key' }) - - await expect(ctx.e2b.getSandbox()).rejects.toThrow('setup failed') - expect(fixture.kill).toHaveBeenCalledOnce() - await fiber.dispose() - }) - - it('preserves the setup failure after its one rollback attempt fails', async () => { - const fixture = fakeSandbox() - fixture.run.mockRejectedValueOnce(new Error('chmod failed')) - fixture.kill.mockRejectedValueOnce(new Error('cleanup failed')) - sdk.create.mockResolvedValue(fixture.sandbox) - const ctx = new Context() - const fiber = await ctx.plugin(E2BRuntime, { apiKey: 'test-key' }) - await expect(ctx.e2b.getSandbox()).rejects.toThrow('chmod failed') - expect(fixture.kill).toHaveBeenCalledOnce() - - await fiber.dispose() - expect(fixture.kill).toHaveBeenCalledOnce() - }) - - it.each([ - ['symbolic link', { type: FileType.DIR, symlinkTarget: '/tmp/redirected' }], - ['regular file', { type: FileType.FILE }], - ])('rejects a reserved runtime root that is a %s', async (_label, info) => { - const fixture = fakeSandbox() - fixture.getInfo.mockResolvedValueOnce(info) - sdk.create.mockResolvedValue(fixture.sandbox) - const ctx = new Context() - await ctx.plugin(E2BRuntime, { apiKey: 'test-key' }) - - await expect(ctx.e2b.getSandbox()).rejects.toThrow('runtime root must be a real directory') - expect(fixture.run).not.toHaveBeenCalled() - expect(fixture.kill).toHaveBeenCalledOnce() - }) - - it.each([ - [{ apiKey: '' }, /configure apiKey/], - [{ apiKey: 'x', cwd: 'relative' }, /absolute Linux path/], - [{ apiKey: 'x', timeoutMs: 0 }, /positive finite/], - ] as const)('fails self-contained configuration before opening E2B: %j', async (config, message) => { - vi.stubEnv('E2B_API_KEY', '') - const ctx = new Context() - await expect(ctx.plugin(E2BRuntime, config)).rejects.toThrow(message) - expect(sdk.create).not.toHaveBeenCalled() - }) - - it('requires a key when both config and the environment omit it', async () => { - const original = process.env.E2B_API_KEY - delete process.env.E2B_API_KEY - try { - const ctx = new Context() - await expect(ctx.plugin(E2BRuntime, {})).rejects.toThrow(/configure apiKey/) - } finally { - if (original === undefined) delete process.env.E2B_API_KEY - else process.env.E2B_API_KEY = original - } - }) -}) - -describe('E2B helpers', () => { - it('quotes opaque shell arguments without interpolation', () => { - expect(quoteE2BShellArg("a'b $HOME")).toBe("'a'\"'\"'b $HOME'") - }) -}) diff --git a/packages/e2b/e2b/tests/egress.spec.ts b/packages/e2b/e2b/tests/egress.spec.ts deleted file mode 100644 index 0bbaca9aa7..0000000000 --- a/packages/e2b/e2b/tests/egress.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { createServer, type Server } from 'node:http' -import type { AddressInfo } from 'node:net' -import { afterAll, beforeAll, describe, expect, it } from 'vitest' -import { installProxyFromEnvironment } from '@deepseek-ai/dsh-http-proxy' - -let seen: string[] = [] -let proxy: Server -let proxyUrl: string - -beforeAll(async () => { - proxy = createServer((request, response) => { - seen.push(`REQ ${request.url ?? ''}`) - response.writeHead(502); response.end('fake-proxy') - }) - proxy.on('connect', (request, socket) => { - seen.push(`CONNECT ${request.url ?? ''}`) - socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n'); socket.end() - }) - const a = await new Promise((r) => { proxy.listen(0, '127.0.0.1', () => { r(proxy.address() as AddressInfo) }) }) - proxyUrl = `http://127.0.0.1:${String(a.port)}` -}) -afterAll(async () => { await new Promise((r) => { proxy.close(() => { r() }) }) }) - -/** The launch environment of a user who exported one proxy for both schemes. */ -function proxyEnv(): { get(name: string): { value: string } | undefined } { - return { get: name => (name === 'HTTP_PROXY' || name === 'HTTPS_PROXY' ? { value: proxyUrl } : undefined) } -} -async function observe(run: () => Promise): Promise { - seen = [] - const dispose = await installProxyFromEnvironment(proxyEnv(), () => undefined) - try { await run().catch(() => undefined) } finally { await dispose() } - return seen -} -import { Context } from '@deepseek-ai/cordis' -import E2bRuntime from '../src/index.ts' - -describe('e2b egress', () => { - it('reaches the control plane through the proxy', async () => { - const observed = await observe(async () => { - const ctx = new Context() - const fiber = await ctx.plugin(E2bRuntime, { apiKey: `e2b_${'0'.repeat(40)}`, cwd: '/home/user', timeoutMs: 5_000 }) - await ctx.e2b.getSandbox().catch(() => undefined) - await fiber.dispose() - }) - expect(observed.join('|')).toContain('api.e2b.app:443') - }) -}) - -describe('e2b control-plane URL', () => { - it('follows the SDK precedence so the proxy decision matches the real target', async () => { - const { e2bApiUrl } = await import('../src/api-url.ts') - expect(e2bApiUrl({})).toBe('https://api.e2b.app') - expect(e2bApiUrl({ E2B_DOMAIN: 'e2b.dev' })).toBe('https://api.e2b.dev') - expect(e2bApiUrl({ E2B_DEBUG: 'TRUE' })).toBe('http://localhost:3000') - expect(e2bApiUrl({ E2B_API_URL: 'https://api.internal.example', E2B_DEBUG: 'true' })) - .toBe('https://api.internal.example') - }) - - it('keeps the loopback debug plane direct instead of sending its API key to a proxy', async () => { - const { e2bApiUrl } = await import('../src/api-url.ts') - const { proxyRouteFor } = await import('@deepseek-ai/dsh-http-proxy') - const { createLaunchEnvironmentSnapshot } = await import('@deepseek-ai/dsh-launch-environment') - // A real launch installs from the environment, and the resolved policy always bypasses loopback. - const dispose = await installProxyFromEnvironment( - createLaunchEnvironmentSnapshot([{ source: 'process', values: { HTTP_PROXY: proxyUrl } }]), - () => undefined, - ) - try { - expect(proxyRouteFor(new URL(e2bApiUrl({ E2B_DEBUG: 'true' })))).toEqual({ proxied: false }) - expect(proxyRouteFor(new URL(e2bApiUrl({})))).toMatchObject({ proxied: true, proxy: proxyUrl }) - } finally { - await dispose() - } - }) -}) diff --git a/packages/e2b/e2b/tests/fixtures/composition/bin.ts b/packages/e2b/e2b/tests/fixtures/composition/bin.ts deleted file mode 100644 index d762a2dda7..0000000000 --- a/packages/e2b/e2b/tests/fixtures/composition/bin.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { readFile } from 'node:fs/promises' -import { resolve } from 'node:path' -import { boot } from '@deepseek-ai/dsh-app-boot' -import { type Agent } from '@deepseek-ai/dsh-agent' -import { Session, SessionId } from '@deepseek-ai/dsh-session' -import type {} from '@deepseek-ai/dsh-fs-e2b' -import type {} from '@deepseek-ai/dsh-bash-local' -import type {} from '@deepseek-ai/dsh-lsp-stdio' -import type {} from '@deepseek-ai/dsh-terminal-bash' - -const configPath = process.argv[2] -if (configPath === undefined) throw new Error('usage: bin.ts ') - -const ctx = await boot('e2b-composition', resolve(configPath)) -const ownerFiber = ctx.plugin(() => {}) -const ownerId = SessionId('e2b-live-owner') -const session = Session.create(ownerId) -const unsupportedInboxMutation = (): never => { - throw new Error('the E2B composition owner does not support Inbox mutations') -} -const owner: Agent = { - id: ownerId, - options: {}, - session, - inbox: { - nextTurn: [], - nextStep: [], - clear: unsupportedInboxMutation, - append: unsupportedInboxMutation, - prepend: unsupportedInboxMutation, - replace: unsupportedInboxMutation, - remove: unsupportedInboxMutation, - splice: unsupportedInboxMutation, - }, - status: 'idle', - ctx: ownerFiber.ctx, - send() {}, - followup() {}, - steer() {}, - inject() {}, - cancel() {}, - runMaintenance: task => task(new AbortController().signal), - whenIdle: () => Promise.resolve(), -} -const unregisterOwner = ctx.agents.register(owner) -let terminalId: Awaited>['sessionId'] | undefined -try { - const sandbox = await ctx.e2b.getSandbox() - const fromFs = await ctx.fs.resolve('from-fs.txt') - const written = await ctx.fs.writeText(fromFs, 'written-by-fs\n', { kind: 'createIfAbsent' }) - const observed = await ctx.fs.stat(fromFs) - if (observed?.version !== written.version) { - throw new Error(`E2B rename did not preserve version metadata: ${JSON.stringify({ written, observed })}`) - } - await ctx.fs.editText( - fromFs, - { oldString: 'written-by-fs', newString: 'versioned-by-fs', replaceAll: false }, - { version: observed.version }, - ) - const bashRead = await ctx.shell.run(ctx.shell.resolve({ command: 'cat from-fs.txt' })) - if (bashRead.exitCode !== 0 || bashRead.stdout.text !== 'versioned-by-fs\n') { - throw new Error(`E2B Bash could not read the FS write: ${JSON.stringify(bashRead)}`) - } - - const bashWrite = await ctx.shell.run(ctx.shell.resolve({ command: "printf 'written-by-bash\\n' > from-bash.txt" })) - if (bashWrite.exitCode !== 0) { - throw new Error(`E2B Bash could not write the shared filesystem: ${JSON.stringify(bashWrite)}`) - } - const fromBash = await ctx.fs.resolve('from-bash.txt') - const fsRead = await ctx.fs.readText(fromBash) - - const environmentHandle = ctx.subprocess.spawn({ - argv: ['env'], - cwd: process.cwd(), - stdio: { stdin: 'ignore', stdout: { maxBytes: 65_536 }, stderr: { maxBytes: 4_096 } }, - graceMs: 500, - env: { - 'FOO-BAR': 'hyphen-value', - DSH_EXPLICIT: 'managed-value', - TOKEN_EXPLICIT: 'credential-value', - }, - }) - const environmentOutcome = await environmentHandle.done - const environmentText = environmentHandle.collected.stdout?.readFrom(0).text - if (environmentOutcome.exitCode !== 0 || environmentText === undefined) { - throw new Error(`E2B subprocess environment probe failed: ${JSON.stringify(environmentOutcome)}`) - } - const environmentLines = new Set(environmentText.trimEnd().split('\n')) - const explicitEnvironment = [ - 'FOO-BAR=hyphen-value', - 'DSH_EXPLICIT=managed-value', - 'TOKEN_EXPLICIT=credential-value', - ].every(entry => environmentLines.has(entry)) - if (!explicitEnvironment) throw new Error(`E2B subprocess dropped an explicit environment entry: ${environmentText}`) - - const splitUtf8Handle = ctx.subprocess.spawn({ - argv: ['bash', '-c', "printf '\\344'; sleep 0.05; printf '\\275'; sleep 0.05; printf '\\240'; sleep 0.05; printf '\\345'; sleep 0.05; printf '\\245'; sleep 0.05; printf '\\275'"], - cwd: process.cwd(), - stdio: { stdin: 'ignore', stdout: { maxBytes: 32 }, stderr: { maxBytes: 4_096 } }, - graceMs: 500, - env: {}, - }) - const splitUtf8Outcome = await splitUtf8Handle.done - const splitUtf8Output = splitUtf8Handle.collected.stdout?.readFrom(0).text - if (splitUtf8Outcome.exitCode !== 0 || splitUtf8Output !== '你好') { - throw new Error(`E2B subprocess corrupted split UTF-8 output: ${JSON.stringify({ splitUtf8Outcome, splitUtf8Output })}`) - } - - const outputDrainStarted = Date.now() - const outputDrainHandle = ctx.subprocess.spawn({ - argv: ['bash', '-c', "bash -c 'exec -a dsh-output-drain-descendant sleep 30' & printf 'leader-done\\n'"], - cwd: process.cwd(), - stdio: { stdin: 'ignore', stdout: { maxBytes: 64 }, stderr: { maxBytes: 4_096 } }, - graceMs: 250, - env: {}, - }) - const outputDrainOutcome = await outputDrainHandle.done - const outputDrainText = outputDrainHandle.collected.stdout?.readFrom(0).text - const outputDrainElapsedMs = Date.now() - outputDrainStarted - outputDrainHandle.terminate() - const outputDrainExited = await outputDrainHandle.waitForExit(AbortSignal.timeout(5_000)) - const outputDrainProcesses = await sandbox.commands.list() - const outputDrainClean = !outputDrainProcesses.some(processInfo => - JSON.stringify([processInfo.cmd, processInfo.args]).includes('dsh-output-drain-descendant'), - ) - if (outputDrainOutcome.exitCode !== 0 || outputDrainText !== 'leader-done\n' - || outputDrainElapsedMs >= 10_000 || !outputDrainExited || !outputDrainClean) { - throw new Error(`E2B subprocess did not bound descendant-held output: ${JSON.stringify({ - outputDrainOutcome, outputDrainText, outputDrainElapsedMs, outputDrainExited, outputDrainClean, - })}`) - } - - const lspFixture = await readFile(new URL('./fixture-lsp.mjs', import.meta.url), 'utf8') - const remoteLspFixture = await ctx.fs.resolve('fixture-lsp.mjs') - await ctx.fs.writeText(remoteLspFixture, lspFixture, { kind: 'createIfAbsent' }) - const remoteSource = await ctx.fs.resolve('multibyte # file.ts') - await ctx.fs.writeText(remoteSource, 'const café = "你好"\nconsole.log(café)\n', { kind: 'createIfAbsent' }) - const hover = await ctx.lsp.query({ - operation: 'hover', - filePath: 'multibyte # file.ts', - position: { line: 0, character: 7 }, - workspaceRoot: process.cwd(), - }) - const definition = await ctx.lsp.query({ - operation: 'goToDefinition', - filePath: 'multibyte # file.ts', - position: { line: 0, character: 7 }, - workspaceRoot: process.cwd(), - }) - - const terminal = await ctx.terminals.spawn(owner, { type: 'shell' }) - terminalId = terminal.sessionId - const terminalEcho = await ctx.terminals.startSend(owner, terminal.sessionId, { - text: "printf 'PTY-你好\\n'", - submit: true, - }).done - const sleeping = ctx.terminals.startSend(owner, terminal.sessionId, { - text: "printf 'DSH_SLEEP_%s\\n' READY; sleep 30", - submit: true, - }) - let sleepReadyOutput = '' - const sleepReadyDeadline = Date.now() + 5_000 - while (!sleepReadyOutput.includes('DSH_SLEEP_READY\n')) { - sleepReadyOutput += sleeping.readOutput().delta - if (sleepReadyOutput.includes('DSH_SLEEP_READY\n')) break - const settled = await Promise.race([ - sleeping.done.then(result => ({ result })), - new Promise(resolveDelay => setTimeout(() => { resolveDelay(undefined) }, 25)), - ]) - if (settled !== undefined) { - throw new Error(`E2B PTY successor settled before executing: ${JSON.stringify(settled.result)}`) - } - if (Date.now() >= sleepReadyDeadline) throw new Error(`E2B PTY successor did not execute: ${sleepReadyOutput}`) - } - const terminalSignal = await ctx.terminals.signal(owner, terminal.sessionId, 'SIGINT') - const interrupted = await sleeping.done - const stubborn = await ctx.terminals.startSend(owner, terminal.sessionId, { - text: "bash -c 'trap \"\" TERM; exec sleep 30' & printf 'DSH_STUBBORN_PID=%s\\n' \"$!\"", - submit: true, - }).done - const stubbornMatch = /DSH_STUBBORN_PID=([1-9][0-9]*)/.exec(stubborn.viewport) - if (stubbornMatch?.[1] === undefined) throw new Error(`E2B PTY did not report its stubborn child: ${stubborn.viewport}`) - const stubbornPid = Number(stubbornMatch[1]) - const terminalScrollback = ctx.terminals.read(owner, terminal.sessionId, { count: 50 }) - await ctx.terminals.kill(owner, terminal.sessionId, 'live E2B composition complete') - terminalId = undefined - const stubbornProbe = await sandbox.commands.run(`if kill -0 ${stubbornPid} 2>/dev/null; then printf alive; else printf gone; fi`) - const terminalTreeCleanup = stubbornProbe.stdout === 'gone' - if (!terminalTreeCleanup) throw new Error(`E2B PTY left process ${stubbornPid} alive after close`) - - process.stdout.write(`${JSON.stringify({ - sandboxId: (await ctx.e2b.getSandbox()).sandboxId, - bashRead: bashRead.stdout.text, - fsRead, - explicitEnvironment, - splitUtf8Output, - hover, - definition, - terminal: { - motd: terminal.motd, - echo: terminalEcho, - signal: terminalSignal, - interrupted, - treeCleanup: terminalTreeCleanup, - scrollback: terminalScrollback.text, - }, - })}\n`) -} finally { - if (terminalId !== undefined) await ctx.terminals.kill(owner, terminalId, 'fixture cleanup').catch(() => false) - unregisterOwner() - await ownerFiber.dispose() - await ctx.fiber.dispose() -} diff --git a/packages/e2b/e2b/tests/fixtures/composition/cordis.yml b/packages/e2b/e2b/tests/fixtures/composition/cordis.yml deleted file mode 100644 index 6177630e25..0000000000 --- a/packages/e2b/e2b/tests/fixtures/composition/cordis.yml +++ /dev/null @@ -1,60 +0,0 @@ -# One-world invariant for the E2B package composition: -# e2b.cwd and sandbox-policy.workspaceRoot must name the same remote directory, -# which is also bash-local's implicit default workdir. -- id: e2b - name: '@deepseek-ai/dsh-e2b' - config: - cwd: !!js process.cwd() - timeoutMs: 180000 - -- id: subprocess-e2b - name: '@deepseek-ai/dsh-subprocess-e2b' - -- id: bash - name: '@deepseek-ai/dsh-bash-local' - config: - timeoutMs: 30000 - -- id: fs-e2b - name: '@deepseek-ai/dsh-fs-e2b' - -- id: agents - name: '@deepseek-ai/dsh-agent' - -- id: session-projection - name: '@deepseek-ai/dsh-session-projection' - -- id: sandbox-policy - name: '@deepseek-ai/dsh-sandbox-policy' - config: - mode: danger-full-access - workspaceRoot: !!js process.cwd() - -- id: pty - name: '@deepseek-ai/dsh-terminal' - -- id: terminal-bash - name: '@deepseek-ai/dsh-terminal-bash' - config: - pollIntervalMs: 25 - exactProbeAfterMs: 150 - idleSilenceMs: 2000 - handoffGraceMs: 500 - timeoutMs: 5000 - disposeGraceMs: 1000 - -- id: lsp - name: '@deepseek-ai/dsh-lsp' - -- id: lsp-stdio - name: '@deepseek-ai/dsh-lsp-stdio' - config: - servers: - fixture: - command: node - args: - - !!js process.cwd() + '/fixture-lsp.mjs' - extensionToLanguage: - .ts: typescript - shutdownTimeoutMs: 1000 - killGraceMs: 500 diff --git a/packages/e2b/e2b/tests/fixtures/composition/fixture-lsp.mjs b/packages/e2b/e2b/tests/fixtures/composition/fixture-lsp.mjs deleted file mode 100644 index dc4f7bb219..0000000000 --- a/packages/e2b/e2b/tests/fixtures/composition/fixture-lsp.mjs +++ /dev/null @@ -1,85 +0,0 @@ -import { Buffer } from 'node:buffer' - -let pending = Buffer.alloc(0) -let source = '' -let sourceUri = '' - -function send(message) { - const body = Buffer.from(JSON.stringify(message)) - process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`) - process.stdout.write(body) -} - -function respond(id, result) { - send({ jsonrpc: '2.0', id, result }) -} - -function dispatch(message) { - switch (message.method) { - case 'initialize': - respond(message.id, { - capabilities: { - positionEncoding: 'utf-16', - textDocumentSync: { openClose: true, change: 1 }, - definitionProvider: true, - referencesProvider: true, - implementationProvider: true, - hoverProvider: true, - }, - }) - return - case 'textDocument/didOpen': - source = message.params.textDocument.text - sourceUri = message.params.textDocument.uri - return - case 'textDocument/didClose': - source = '' - sourceUri = '' - return - case 'textDocument/hover': - if (!source.includes('const café = "你好"')) { - send({ jsonrpc: '2.0', id: message.id, error: { code: -32000, message: 'multibyte source was corrupted' } }) - return - } - respond(message.id, { - contents: { kind: 'markdown', value: '**remote hover** 你好 café' }, - range: { start: { line: 0, character: 6 }, end: { line: 0, character: 10 } }, - }) - return - case 'textDocument/definition': - case 'textDocument/references': - case 'textDocument/implementation': - respond(message.id, [{ - uri: sourceUri, - range: { start: { line: 0, character: 6 }, end: { line: 0, character: 10 } }, - }]) - return - case 'shutdown': - respond(message.id, null) - return - case 'exit': - process.exit(0) - return - } -} - -function drain() { - for (;;) { - const headerEnd = pending.indexOf('\r\n\r\n') - if (headerEnd < 0) return - const header = pending.subarray(0, headerEnd).toString('ascii') - const match = /(?:^|\r\n)Content-Length: ([0-9]+)(?:\r\n|$)/i.exec(header) - if (!match) throw new Error('missing Content-Length') - const length = Number(match[1]) - const bodyStart = headerEnd + 4 - if (pending.length < bodyStart + length) return - const body = pending.subarray(bodyStart, bodyStart + length) - pending = pending.subarray(bodyStart + length) - dispatch(JSON.parse(body.toString('utf8'))) - } -} - -process.stdin.on('data', chunk => { - pending = Buffer.concat([pending, chunk]) - drain() -}) diff --git a/packages/e2b/e2b/tsconfig.json b/packages/e2b/e2b/tsconfig.json deleted file mode 100644 index 49a7dbb012..0000000000 --- a/packages/e2b/e2b/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "lib/types" - }, - "include": [ - "src" - ], - "references": [ - { - "path": "../../../vendor/cosmokit" - }, - { - "path": "../../../vendor/cordis" - }, - { - "path": "../../../vendor/schemastery" - }, - { - "path": "../../util/brand" - }, - { - "path": "../../util/http-proxy" - }, - { - "path": "../../util/launch-environment" - } - ] -} diff --git a/packages/e2b/fs-e2b/README.i18n.yaml b/packages/e2b/fs-e2b/README.i18n.yaml deleted file mode 100644 index b1b96d8ae1..0000000000 --- a/packages/e2b/fs-e2b/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/e2b/fs-e2b/README.md -README.md: 4f81902a8d36f0e7612eb7d34e411d0ddebda697 -README.zh.md: e20d01314497648e755026b7d9b84c72e24a3b37 diff --git a/packages/e2b/fs-e2b/README.md b/packages/e2b/fs-e2b/README.md deleted file mode 100644 index 4f81902a8d..0000000000 --- a/packages/e2b/fs-e2b/README.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -description: "File operations inside the shared remote sandbox: what the agent can do with files there, when to use it, and what to expect — for deployers and maintainers of the E2B family." -kind: "package-reference" ---- - -# @deepseek-ai/dsh-fs-e2b - -English | [中文](README.zh.md) - -## Summary - -`dsh-fs-e2b` runs the agent's file operations inside the remote sandbox: the agent can read files, list directories, write new files, overwrite or edit existing ones, and get accurate metadata — all in the same remote world where its commands run. It needs no configuration; mounting it moves file work off the host machine. Use it together with `dsh-e2b` and `dsh-subprocess-e2b` so files and commands share one remote working directory. The host machine's files are never touched, and results look to the model exactly like local file results. Choose the local filesystem package instead when files should live on the host. - -## Table of Contents - -- [Use this package](#use-this-package) -- [Understand the implementation](#understand-the-implementation) -- [Further Exploration](#further-exploration) -- [Model Experience](#model-experience) -- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) -- [Dev Note](#dev-note) - ------ - - -## Use this package - -Use this package when the agent's file work — reading, writing, editing, listing — should happen inside the remote sandbox rather than on your machine. It is the filesystem half of the E2B family: what the agent writes here is what its commands can read in the same sandbox. - -### When to choose it - -Choose it when a composition already uses the E2B sandbox and you want file operations to run there. Choose the local filesystem package when files should stay on the host. There is no configuration to tune. - -### Mounting it - -Load the sandbox owner first, then this package; after that, file features operate on the sandbox: - -```yaml -- name: '@deepseek-ai/dsh-e2b' -- name: '@deepseek-ai/dsh-fs-e2b' -``` - -Mounting it does not copy or mirror your local files — the sandbox's working directory starts empty and fills as the agent works. - -### Reading files - -The agent can read a file's whole contents, stream large files, or read raw bytes up to a size cap or as one byte window. Binary files and files that are not valid UTF-8 text are refused with a clear message instead of being garbled; reads past the size cap fail with a message naming the limit. - -### Writing and editing files - -The agent can create files, overwrite them, or edit them by replacing a literal piece of text (optionally every occurrence), and can ask for a file to be created only if it does not exist yet. A write lands completely or not at all — a failed write never leaves a partial file. If the file changed since the agent last read it, the write is refused rather than clobbering the newer content, so two processes cannot overwrite each other's work unseen. - -### Paths in the sandbox - -Relative paths resolve against the caller's working directory or the sandbox's shared working directory, and are reported as the POSIX paths they are in the sandbox — what the agent reads and writes matches exactly what its commands see. - ------ - - -## Understand the implementation - -
-Implementation internals — click to expand - -This section explains the design decisions behind the provider and points at the code that realizes them; the observable behavior is fully covered in [Use this package](#use-this-package). - -### Design philosophy - -- **One remote world.** Paths and contents stay in the sandbox; the host workspace is never copied, mounted, or reconciled. -- **Atomic publication.** Every mutation commits through same-filesystem rename or a guarded link, and the returned version comes from the committed entry, so no fallible metadata request follows the commit point. -- **Strict transport framing.** Canonical paths and contents cross the SDK as ASCII base64 with NUL framing, so newline and multibyte data survive arbitrary decoding boundaries. - -### Source map - -| File | Role | -|---|---| -| [`src/index.ts`](src/index.ts) | Plugin entry: `E2BFileSystem` provider, canonicalization, reads, atomic writes, error mapping | -| — | No runtime invariant companion is published; each operation returns the E2B controller's committed result directly, with no independent event or cache to cross-check. | - -### Canonical paths and transport framing - -Relative paths resolve as POSIX paths against the caller `cwd` or `ctx.e2b.cwd`; GNU `realpath -mz` supplies canonical target identity without requiring the final file to exist, and ASCII base64 plus strict NUL framing preserves newline and multibyte paths across the decoded SDK transport. `stat`, no-follow `lstat`, and stable one-level directory listings project E2B metadata into the seam; canonical targets expose absolute POSIX process paths, percent-encoded `file:` URIs, and provider-owned containment checks. - -### Write path - -Writes create a random sibling staging directory, set it to mode `0700` before uploading content, and preserve an existing file's POSIX mode; replacements publish through E2B's same-filesystem atomic rename, and a guarded `createIfAbsent` publishes with `ln -T` so the commit is atomically no-replace even when a directory appears at the destination. The `dsh-version` extended attribute plus the committed entry's metadata form the returned version; literal edits LF-normalize for matching and restore the dominant CRLF style, and mutations serialize per canonical target. Staging-cleanup failures after commit never turn a successful write into a failure. - -### Failure and cancellation - -E2B not-found, permission, abort, and other controller failures map to the existing `FsError` codes (`FS_NOT_FOUND`, `FS_PERMISSION_DENIED`, `FS_ABORTED`, `FS_IO_ERROR`), while text and byte reads add `FS_NOT_TEXT` and `FS_TOO_LARGE`. Cancellation is checked at SDK request boundaries and immediately before publication, but the signal is never forwarded into the rename or guarded-link commit, so cancellation cannot interrupt atomic publication or turn a committed write into a reported failure. - -
- ------ - - -## Further Exploration - -Read these pages when the package-level contract is not enough. They move from the family composition to the filesystem seam surface and the tools that render it. - -- [E2B provider family map](../README.md) — the sandbox owner and the three-package composition. -- [Filesystem subsystem](../../../docs/subsystems/filesystem.md) — the filesystem seam contract and the generated Cordis surface. -- [Filesystem provider contract](../../fs/fs/README.md) — the `FileSystem` interface this provider implements. -- [File tools](../../fs/tool-fs/README.md) — the tools that render filesystem results to the model. - ------ - - -## Model Experience - -Indirectly, through [`dsh-tool-fs`](../../fs/tool-fs/README.md), which renders remote UTF-8 content, directory results, mutation acknowledgements, and provider errors while E2B identity and transport remain internal. - -#### KV Cache effect - -No direct invalidation: `dsh-tool-fs` owns any request-prefix changes; the E2B transport never reaches a request. - -## Known Limitations and Deferred Work - - - - -These limits define when the provider is a poor fit or needs special operational care. They are current package constraints, not a task backlog. - -- **No host synchronization** — an empty E2B cwd stays empty until a tool, command, or external process populates it; local files are neither uploaded nor reflected back. -- **Mutation coordination is host-process-local** — `createIfAbsent` preserves a remote creator racing publication, but another harness connection or command can still race replacement; version guards detect only metadata changes represented by E2B. -- **Reads reopen canonical targets by path** — a concurrent remote path replacement between resolution and stream opening is not fenced by a stable file handle; no observed product defect justifies a provider-specific bounded-read protocol in this POC. -- **Whole-file mutation costs remain** — overwrite diffs and literal edits read complete files into host memory, and every operation incurs E2B controller latency. -- **The POC targets E2B's default Linux image** — it relies on GNU `realpath`/`base64`/`chmod`, same-filesystem rename, streaming reads, and metadata extended attributes; custom templates are outside this POC. - - -### Dev Note - -
-Working context for maintainers — click to expand - -None. - -
diff --git a/packages/e2b/fs-e2b/README.zh.md b/packages/e2b/fs-e2b/README.zh.md deleted file mode 100644 index e20d013144..0000000000 --- a/packages/e2b/fs-e2b/README.zh.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -description: "共享远程沙箱内的文件操作:agent(智能体)可以在那里对文件做什么、何时使用,以及可以期待什么——面向 E2B 家族的部署方与维护者。" -kind: "package-reference" ---- - -# @deepseek-ai/dsh-fs-e2b - -[English](README.md) | 中文 - -## 概述 - -`dsh-fs-e2b` 让 agent 的文件操作在远程沙箱内运行:agent 可以读取文件、列出目录、写入新文件、覆盖或编辑现有文件,并获得准确的元数据——这些操作都发生在其命令运行的同一远程环境中。它不需要任何配置;挂载它就把文件工作从宿主机器上移走。请与 `dsh-e2b`、`dsh-subprocess-e2b` 一起使用,让文件与命令共享同一个远程工作目录。宿主机器上的文件永远不会被触及,模型看到的结果与本地文件结果完全一致。当文件应留在宿主上时,请选择本地的文件系统包。 - -## 目录 - -- [使用本包](#use-this-package) -- [理解实现](#understand-the-implementation) -- [进一步探索](#further-exploration) -- [模型体验](#model-experience) -- [已知限制与延期工作](#known-limitations-and-deferred-work) -- [开发备注](#dev-note) - ------ - - -## 使用本包 - -当 agent 的文件工作——读取、写入、编辑、列出——应在远程沙箱内而非你的机器上进行时,使用本包。它是 E2B 家族中的文件系统部分:agent 在这里写入的内容,正是它的命令能在同一个沙箱中读取的内容。 - -### 何时选择 - -当组合已经使用 E2B 沙箱且希望文件操作在其中运行时,选择本包。当文件应留在宿主上时,选择本地文件系统包。这里没有需要调优的配置。 - -### 挂载 - -先加载沙箱所有者,再加载本包;之后文件功能就会作用于沙箱: - -```yaml -- name: '@deepseek-ai/dsh-e2b' -- name: '@deepseek-ai/dsh-fs-e2b' -``` - -挂载它不会复制或镜像你的本地文件——沙箱的工作目录从空开始,并随 agent 的工作逐渐被填充。 - -### 读取文件 - -agent 可以读取文件的完整内容、流式读取大文件,或在大小上限内或按字节窗口读取原始字节。二进制文件与不是有效 UTF-8 文本的文件会被明确拒绝而不是乱码显示;超过大小上限的读取会以指明上限的消息失败。 - -### 写入与编辑文件 - -agent 可以创建文件、覆盖文件,或通过替换一段字面量文本(可选地替换所有出现处)来编辑文件,也可以要求仅在文件尚不存在时创建它。一次写入要么完整落地,要么完全不落地——失败的写入绝不会留下不完整的文件。如果文件在 agent 上次读取之后发生了变化,写入会被拒绝而不是覆盖较新的内容,因此两个进程不会在互不知情的情况下覆盖彼此的工作。 - -### 沙箱中的路径 - -相对路径会针对调用方的工作目录或沙箱共享工作目录解析,并按其在沙箱中的 POSIX 路径报告——agent 读写的内容与它的命令看到的内容完全一致。 - ------ - - -## 理解实现 - -
-实现细节——点击展开 - -本节解释提供方背后的设计决策,并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。 - -### 设计理念 - -- **同一远程环境。** 路径与内容都留在沙箱内;宿主工作区永远不会被复制、挂载或协调。 -- **原子发布。** 每次变更都通过同一文件系统 rename 或受防护链接提交,返回的版本来自已提交条目,因此提交点之后不会再有可能失败的元数据请求。 -- **严格的传输分帧。** 规范化路径与内容以带 NUL 分帧的 ASCII base64 形式跨越 SDK,因此换行与多字节数据能经受任意解码边界。 - -### 源码地图 - -| 文件 | 职责 | -|---|---| -| [`src/index.ts`](src/index.ts) | 插件入口:`E2BFileSystem` 提供方、规范化、读取、原子写入、错误映射 | -| — | 不发布运行时不变式配套项;每个操作都直接返回 E2B 控制器的已提交结果,没有可用于交叉核验的独立事件或缓存。 | - -### 规范化路径与传输分帧 - -相对路径以调用方 `cwd` 或 `ctx.e2b.cwd` 为基准,按照 POSIX 路径解析;GNU `realpath -mz` 提供规范化目标身份,且不要求最终文件存在;ASCII base64 加严格 NUL 分帧会在已解码的 SDK 传输中保留含换行符和多字节字符的路径。`stat`、不跟随链接的 `lstat` 与稳定的单层目录列表会把 E2B 元数据投影到 seam;规范化目标公开绝对 POSIX 进程路径、百分号编码的 `file:` URI,以及由提供方负责的包含关系检查。 - -### 写入路径 - -写入会创建随机的同级暂存目录,在上传内容前将其 mode 设为 `0700`,并保留现有文件的 POSIX mode;替换操作通过 E2B 的同一文件系统原子重命名发布,带防护的 `createIfAbsent` 改用 `ln -T` 发布,即使目标位置出现目录,也能使提交具备原子且不替换的语义。`dsh-version` 扩展属性与已提交条目的元数据共同构成返回的版本;字面量编辑匹配时会规范化为 LF,并恢复占主导的 CRLF 风格,变更按规范化目标串行执行。提交后的暂存清理失败绝不会把一次成功的写入变成失败。 - -### 失败与取消 - -E2B 的未找到、权限、中止及其他控制器故障会映射到现有 `FsError` 错误码(`FS_NOT_FOUND`、`FS_PERMISSION_DENIED`、`FS_ABORTED`、`FS_IO_ERROR`),文本与字节读取则增加 `FS_NOT_TEXT` 与 `FS_TOO_LARGE`。取消在 SDK 请求边界与发布前立即检查,但信号永远不会传入 rename 或受防护链接提交,因此取消无法中断原子发布,也不会把已提交的写入报告为失败。 - -
- ------ - - -## 进一步探索 - -当包级约定不够用时阅读以下页面。这些页面从家族组合讲起,逐步深入到文件系统 seam 接口及其渲染工具。 - -- [E2B 提供方家族地图](../README.zh.md)——沙箱所有者与三包组合。 -- [文件系统子系统](../../../docs/subsystems/filesystem.zh.md)——文件系统 seam 约定与生成的 Cordis 接口。 -- [文件系统提供方约定](../../fs/fs/README.zh.md)——本提供方实现的 `FileSystem` 接口。 -- [文件工具](../../fs/tool-fs/README.zh.md)——向模型渲染文件系统结果的工具。 - ------ - - -## 模型体验 - -通过 [`dsh-tool-fs`](../../fs/tool-fs/README.zh.md) 间接影响模型;该工具渲染远程 UTF-8 内容、目录结果、变更确认与提供方错误,而 E2B 身份与传输保持内部实现。 - -#### KV Cache 影响 - -不会直接失效:请求前缀变更由 `dsh-tool-fs` 负责;E2B 传输永远不会进入请求。 - -## 已知限制与延期工作 - - - - -这些限制说明本提供方何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是待办事项清单。 - -- **不提供宿主同步**:空的 E2B cwd 会一直为空,直到工具、命令或外部进程填充它;本地文件既不会上传,也不会同步回本地。 -- **变更协调仅限宿主进程内**:`createIfAbsent` 会保留与发布发生竞态的远程创建者所写入的文件,但另一个 harness 连接或命令仍可能与替换操作发生竞态;版本防护只能检测 E2B 元数据所体现的变更。 -- **读取会按路径重新打开规范化目标**:在解析与打开流之间若并发替换远程路径,该操作没有稳定文件句柄提供围栏;在该 POC 中,没有已观察到的产品缺陷能够证明提供方专用的有界读取协议值得引入。 -- **仍需承担完整文件变更成本**:覆盖差异与字面量编辑会把完整文件读入宿主内存,每项操作也都会产生 E2B 控制器延迟。 -- **该 POC 面向 E2B 默认 Linux 镜像**:它依赖 GNU `realpath`/`base64`/`chmod`、同一文件系统内的 rename、流式读取与元数据扩展属性;自定义模板不在该 POC 范围内。 - - -### 开发备注 - -
-维护者的工作上下文——点击展开 - -无。 - -
diff --git a/packages/e2b/fs-e2b/package.json b/packages/e2b/fs-e2b/package.json deleted file mode 100644 index 4381fe7e86..0000000000 --- a/packages/e2b/fs-e2b/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@deepseek-ai/dsh-fs-e2b", - "description": "E2B filesystem implementation for DeepSeek Harness", - "version": "0.1.5-rc.2", - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", - "directory": "packages/e2b/fs-e2b" - }, - "type": "module", - "main": "lib/index.js", - "types": "lib/types/index.d.ts", - "exports": { - ".": { - "types": "./lib/types/index.d.ts", - "default": "./lib/index.js" - }, - "./src/*": "./src/*", - "./package.json": "./package.json" - }, - "files": [ - "lib/index.js", - "lib/types/**/*.d.ts" - ], - "license": "MIT", - "peerDependencies": { - "@deepseek-ai/dsh-e2b": "workspace:^", - "@deepseek-ai/dsh-fs": "workspace:^", - "@deepseek-ai/cordis": "workspace:^" - }, - "devDependencies": { - "@deepseek-ai/dsh-e2b": "workspace:^", - "@deepseek-ai/dsh-fs": "workspace:^", - "@deepseek-ai/cordis": "workspace:^" - } -} diff --git a/packages/e2b/fs-e2b/src/index.ts b/packages/e2b/fs-e2b/src/index.ts deleted file mode 100644 index 2d879adb0c..0000000000 --- a/packages/e2b/fs-e2b/src/index.ts +++ /dev/null @@ -1,628 +0,0 @@ -/** - * E2B provider for the filesystem capability seam. Paths, contents, and - * atomic staging files remain inside the shared remote sandbox. - * @module @deepseek-ai/dsh-fs-e2b - */ - -import { createHash, randomUUID } from 'node:crypto' -import { Buffer } from 'node:buffer' -import { posix } from 'node:path' -import { FileSystem, FsError, FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs' -import type { - FsDirEntry, - FsEditOutcome, - FsEditRequest, - FsInfo, - FsPathInfo, - FsTarget, - FsWriteIntent, - FsWriteOutcome, -} from '@deepseek-ai/dsh-fs' -import { - CommandExitError, - e2bControlEnvs, - FileNotFoundError, - FileType, - quoteE2BShellArg, -} from '@deepseek-ai/dsh-e2b' -import type { EntryInfo, Sandbox } from '@deepseek-ai/dsh-e2b' - -const VERSION_METADATA_KEY = 'dsh-version' -const BINARY_SAMPLE_BYTES = 8192 -const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/ - -function assertNotAborted(signal: AbortSignal | undefined, operation: string): void { - if (signal?.aborted === true) throw new FsError(`${operation} aborted`, 'FS_ABORTED') -} - -function normalizeLineEndings(value: string): string { - return value.replaceAll('\r\n', '\n') -} - -function detectsCrlf(value: string): boolean { - const sample = value.slice(0, 4096) - const crlf = sample.split('\r\n').length - 1 - const lf = sample.split('\n').length - 1 - crlf - return crlf > lf -} - -function restoreLineEndings(value: string, crlf: boolean): string { - return crlf ? normalizeLineEndings(value).replaceAll('\n', '\r\n') : value -} - -function decodeText(bytes: Uint8Array, displayPath: string, binarySampleBytes: number): string { - if (bytes.subarray(0, binarySampleBytes).includes(0)) { - throw new FsError(`cannot read "${displayPath}": binary file`, 'FS_NOT_TEXT') - } - try { - return new TextDecoder('utf-8', { fatal: true }).decode(bytes) - } catch (error: unknown) { - throw new FsError(`cannot read "${displayPath}": invalid UTF-8 text`, 'FS_NOT_TEXT', { cause: error }) - } -} - -function decodeCanonicalPath(encoded: string): string { - if (encoded.length === 0 || !BASE64.test(encoded)) { - throw new Error('fs-e2b: canonical path transport returned invalid base64') - } - const framed = Buffer.from(encoded, 'base64') - if (framed.toString('base64') !== encoded - || framed.length < 2 - || framed.at(-1) !== 0 - || framed.subarray(0, -1).includes(0)) { - throw new Error('fs-e2b: canonical path transport returned invalid NUL framing') - } - let path: string - try { - path = new TextDecoder('utf-8', { fatal: true }).decode(framed.subarray(0, -1)) - } catch (error: unknown) { - throw new Error('fs-e2b: canonical path is not valid UTF-8', { cause: error }) - } - if (!posix.isAbsolute(path)) throw new Error('fs-e2b: canonical path is not absolute') - return path -} - -function signalOpts(signal: AbortSignal | undefined): { signal?: AbortSignal } { - return signal === undefined ? {} : { signal } -} - -function commandOpts(signal: AbortSignal | undefined): { envs: Record; signal?: AbortSignal } { - return { envs: e2bControlEnvs(), ...signalOpts(signal) } -} - -async function openReadStream( - sandbox: Sandbox, - target: FsTarget, - signal: AbortSignal | undefined, -): Promise> { - try { - // The pinned SDK's stream overload lies for empty files: content-length 0 - // returns '' instead of a ReadableStream. - const read = await sandbox.files.read(String(target.targetKey), { format: 'stream', ...signalOpts(signal) }) as - ReadableStream | string - return typeof read === 'string' - ? new ReadableStream({ start(controller) { controller.close() } }) - : read - } catch (error: unknown) { - throw mapError(error, 'read', target.displayPath, signal) - } -} - -function entryType(entry: EntryInfo): FsInfo['type'] { - switch (entry.type) { - case FileType.FILE: - return 'file' - case FileType.DIR: - return 'directory' - default: - return 'other' - } -} - -function entryVersion(entry: EntryInfo): ReturnType { - const facts = JSON.stringify([ - entry.metadata?.[VERSION_METADATA_KEY], - entry.path, - entry.type, - entry.size, - entry.mode, - entry.modifiedTime?.toISOString(), - entry.symlinkTarget, - ]) - return FsVersion(`e2b:${createHash('sha256').update(facts).digest('hex')}`) -} - -function mapError(error: unknown, operation: string, displayPath: string, signal?: AbortSignal): FsError { - if (error instanceof FsError) return error - if (signal?.aborted === true || (error instanceof DOMException && error.name === 'AbortError')) { - return new FsError(`${operation} aborted`, 'FS_ABORTED', { cause: error }) - } - if (error instanceof FileNotFoundError) { - return new FsError(`cannot ${operation} "${displayPath}": not found`, 'FS_NOT_FOUND', { cause: error }) - } - if (/permission denied|operation not permitted/i.test(String(error))) { - return new FsError(`cannot ${operation} "${displayPath}": permission denied`, 'FS_PERMISSION_DENIED', { cause: error }) - } - return new FsError(`cannot ${operation} "${displayPath}": ${String(error)}`, 'FS_IO_ERROR', { cause: error }) -} - -function literalEdit(content: string, request: FsEditRequest, displayPath: string): string { - const oldString = normalizeLineEndings(request.oldString) - const newString = normalizeLineEndings(request.newString) - if (oldString.length === 0) { - throw new FsError(`cannot edit "${displayPath}": old_string must be non-empty`, 'FS_EDIT_NOT_FOUND') - } - let matches = 0 - let offset = 0 - while (true) { - const found = content.indexOf(oldString, offset) - if (found < 0) break - matches += 1 - offset = found + oldString.length - } - if (matches === 0) throw new FsError(`cannot edit "${displayPath}": old_string was not found`, 'FS_EDIT_NOT_FOUND') - if (!request.replaceAll && matches !== 1) { - throw new FsError(`cannot edit "${displayPath}": old_string matched ${matches} times`, 'FS_AMBIGUOUS_EDIT') - } - return request.replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString) -} - -/** Remote filesystem backend sharing the sandbox owned by `ctx.e2b`. */ -export class E2BFileSystem extends FileSystem { - static inject = ['e2b'] - - private readonly locks = new Map>() - - override async resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise { - assertNotAborted(opts?.signal, 'resolve') - if (path.trim().length === 0) throw new FsError('file_path must be a non-empty string', 'FS_NOT_FOUND') - const displayPath = posix.resolve(opts?.cwd ?? this.ctx.e2b.cwd, path) - try { - const sandbox = await this.ctx.e2b.getSandbox() - const targetKey = await this.canonicalPath(sandbox, displayPath, opts?.signal) - assertNotAborted(opts?.signal, 'resolve') - return { targetKey: FsTargetKey(targetKey), displayPath } - } catch (error: unknown) { - throw mapError(error, 'resolve', displayPath, opts?.signal) - } - } - - override processPath(target: FsTarget): string { - return String(target.targetKey) - } - - override fileUrl(target: FsTarget): string { - const path = this.processPath(target) - if (!posix.isAbsolute(path)) throw new Error(`fs-e2b: expected an absolute process path: ${JSON.stringify(path)}`) - return `file://${path.split('/').map(segment => encodeURIComponent(segment)).join('/')}` - } - - override contains(parent: FsTarget, child: FsTarget): boolean { - const relative = posix.relative(this.processPath(parent), this.processPath(child)) - return relative === '' || (relative !== '..' && !relative.startsWith('../') && !posix.isAbsolute(relative)) - } - - override async stat(target: FsTarget, signal?: AbortSignal): Promise { - assertNotAborted(signal, 'stat') - const entry = await this.probe(String(target.targetKey), target.displayPath, signal) - if (entry === undefined) return undefined - return { - version: entryVersion(entry), - type: entryType(entry), - ...(entry.type === FileType.FILE ? { size: entry.size } : {}), - } - } - - override async lstat(path: string, opts?: { cwd?: string }, signal?: AbortSignal): Promise { - assertNotAborted(signal, 'lstat') - if (path.trim().length === 0) throw new FsError('file_path must be a non-empty string', 'FS_NOT_FOUND') - const displayPath = posix.resolve(opts?.cwd ?? this.ctx.e2b.cwd, path) - const entry = await this.probe(displayPath, displayPath, signal) - if (entry === undefined) return undefined - const type = entry.symlinkTarget !== undefined - ? 'symlink' as const - : entry.type === FileType.FILE - ? 'file' as const - : entry.type === FileType.DIR - ? 'directory' as const - : 'other' as const - return { - version: entryVersion(entry), - type, - ...(entry.type === FileType.FILE ? { size: entry.size } : {}), - } - } - - override async readText(target: FsTarget, signal?: AbortSignal): Promise { - const sandbox = await this.ctx.e2b.getSandbox() - await this.requireRegular(target, signal) - try { - const bytes = await sandbox.files.read(String(target.targetKey), { format: 'bytes', ...signalOpts(signal) }) - assertNotAborted(signal, 'read') - return decodeText(bytes, target.displayPath, BINARY_SAMPLE_BYTES) - } catch (error: unknown) { - throw mapError(error, 'read', target.displayPath, signal) - } - } - - override async readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise { - const sandbox = await this.ctx.e2b.getSandbox() - const info = await this.requireRegular(target, signal) - if (info.size !== undefined && info.size > maxBytes) { - throw new FsError(`cannot read "${target.displayPath}": ${info.size} bytes exceeds the ${maxBytes}-byte limit`, 'FS_TOO_LARGE') - } - const stream = await openReadStream(sandbox, target, signal) - const reader = stream.getReader() - const chunks: Uint8Array[] = [] - let bytes = 0 - let completed = false - try { - while (true) { - assertNotAborted(signal, 'read') - const next = await reader.read() - if (next.done) break - // The stat preflight covers the at-rest case; this streamed bound stops - // a post-stat grower without transferring past the first overflowing chunk. - bytes += next.value.byteLength - if (bytes > maxBytes) { - throw new FsError(`cannot read "${target.displayPath}": content exceeds the ${maxBytes}-byte limit`, 'FS_TOO_LARGE') - } - chunks.push(next.value) - } - completed = true - } catch (error: unknown) { - throw mapError(error, 'read', target.displayPath, signal) - } finally { - if (!completed) { - try { - await reader.cancel() - } catch (_streamCancellationFailure) { - // The read already failed; a cancellation failure on the abandoned - // remote stream adds nothing actionable for the caller. - } - } - reader.releaseLock() - } - const whole = new Uint8Array(bytes) - let offset = 0 - for (const chunk of chunks) { - whole.set(chunk, offset) - offset += chunk.byteLength - } - return whole - } - - override async readByteRange(target: FsTarget, range: { offset: number; length: number }, signal?: AbortSignal): Promise { - const sandbox = await this.ctx.e2b.getSandbox() - await this.requireRegular(target, signal) - if (range.length === 0) return new Uint8Array(0) - // The SDK streams only from the file's start: skip to `offset`, keep - // `length` bytes, and cancel the stream there, so no more than the window - // beyond the skipped prefix is ever transferred. - const stream = await openReadStream(sandbox, target, signal) - const reader = stream.getReader() - const window = new Uint8Array(range.length) - const end = range.offset + range.length - let position = 0 - let filled = 0 - let drained = false - try { - while (filled < range.length) { - assertNotAborted(signal, 'read') - const next = await reader.read() - if (next.done) { - drained = true - break - } - const from = Math.max(range.offset, position) - const to = Math.min(end, position + next.value.byteLength) - if (to > from) { - window.set(next.value.subarray(from - position, to - position), filled) - filled += to - from - } - position += next.value.byteLength - } - } catch (error: unknown) { - throw mapError(error, 'read', target.displayPath, signal) - } finally { - if (!drained) { - try { - await reader.cancel() - } catch (_streamCancellationFailure) { - // The window is complete or the read already failed; a cancellation - // failure on the abandoned remote stream adds nothing actionable. - } - } - reader.releaseLock() - } - return filled === range.length ? window : window.subarray(0, filled) - } - - override async streamText(target: FsTarget, signal?: AbortSignal): Promise> { - const sandbox = await this.ctx.e2b.getSandbox() - await this.requireRegular(target, signal) - const stream = await openReadStream(sandbox, target, signal) - const displayPath = target.displayPath - return { - async *[Symbol.asyncIterator](): AsyncGenerator { - const reader = stream.getReader() - const decoder = new TextDecoder('utf-8', { fatal: true }) - let sampledBytes = 0 - let completed = false - try { - while (true) { - assertNotAborted(signal, 'read') - const next = await reader.read() - if (next.done) break - if (sampledBytes < BINARY_SAMPLE_BYTES) { - const sample = next.value.subarray(0, BINARY_SAMPLE_BYTES - sampledBytes) - if (sample.includes(0)) throw new FsError(`cannot read "${displayPath}": binary file`, 'FS_NOT_TEXT') - sampledBytes += sample.length - } - let text: string - try { - text = decoder.decode(next.value, { stream: true }) - } catch (error: unknown) { - throw new FsError(`cannot read "${displayPath}": invalid UTF-8 text`, 'FS_NOT_TEXT', { cause: error }) - } - if (text.length > 0) yield text - } - try { - decoder.decode() - } catch (error: unknown) { - throw new FsError(`cannot read "${displayPath}": invalid UTF-8 text`, 'FS_NOT_TEXT', { cause: error }) - } - completed = true - } catch (error: unknown) { - throw mapError(error, 'read', displayPath, signal) - } finally { - if (!completed) { - try { - await reader.cancel() - } catch (_streamCancellationFailure) { - // The primary read outcome owns the result; cancellation is best-effort after early stop. - } - } - reader.releaseLock() - } - }, - } - } - - override async listDir(target: FsTarget, signal?: AbortSignal): Promise { - const info = await this.stat(target, signal) - if (info === undefined) throw new FsError(`cannot list "${target.displayPath}": not found`, 'FS_NOT_FOUND') - if (info.type !== 'directory') throw new FsError(`cannot list "${target.displayPath}": not a directory`, 'FS_NOT_DIRECTORY') - try { - const sandbox = await this.ctx.e2b.getSandbox() - const listed = await sandbox.files.list(String(target.targetKey), { depth: 1, ...signalOpts(signal) }) - const entries: FsDirEntry[] = [] - for (const entry of listed) { - const displayPath = posix.join(target.displayPath, entry.name) - const canonical = entry.symlinkTarget === undefined - ? entry.path - : await this.canonicalPath(sandbox, entry.path, signal) - const resolved = entry.symlinkTarget === undefined - ? entry - : await this.probe(canonical, displayPath, signal) - entries.push({ - name: entry.name, - type: resolved === undefined ? 'other' : entryType(resolved), - target: { targetKey: FsTargetKey(canonical), displayPath }, - ...(resolved !== undefined ? { version: entryVersion(resolved) } : {}), - ...(resolved?.type === FileType.FILE ? { size: resolved.size } : {}), - }) - } - return entries.sort((left, right) => left.name.localeCompare(right.name)) - } catch (error: unknown) { - throw mapError(error, 'list', target.displayPath, signal) - } - } - - override async writeText( - target: FsTarget, - content: string, - expected?: FsWriteIntent, - signal?: AbortSignal, - ): Promise { - return this.withLock(String(target.targetKey), async () => { - const existing = await this.probe(String(target.targetKey), target.displayPath, signal) - if (existing !== undefined && entryType(existing) !== 'file') { - throw new FsError(`cannot write "${target.displayPath}": not a regular file`, 'FS_NOT_REGULAR_FILE') - } - this.checkWriteIntent(existing, expected, target) - const before = existing === undefined ? null : await this.readForDiff(target, signal) - const version = await this.writeAtomic( - target, - content, - existing, - expected?.kind === 'createIfAbsent', - signal, - ) - return { - operation: existing === undefined ? 'create' : 'update', - version, - before, - after: normalizeLineEndings(content), - } - }) - } - - override async editText( - target: FsTarget, - edit: FsEditRequest, - expected?: { version: ReturnType }, - signal?: AbortSignal, - ): Promise { - return this.withLock(String(target.targetKey), async () => { - const existing = await this.probe(String(target.targetKey), target.displayPath, signal) - if (existing === undefined) { - throw new FsError(`cannot edit "${target.displayPath}": file changed since it was read`, 'FS_STALE_VERSION') - } - if (entryType(existing) !== 'file') { - throw new FsError(`cannot edit "${target.displayPath}": not a regular file`, 'FS_NOT_REGULAR_FILE') - } - if (expected !== undefined && entryVersion(existing) !== expected.version) { - throw new FsError(`cannot edit "${target.displayPath}": file changed since it was read`, 'FS_STALE_VERSION') - } - const raw = await this.readForEdit(target, signal) - const before = normalizeLineEndings(raw) - const after = literalEdit(before, edit, target.displayPath) - const storage = restoreLineEndings(after, detectsCrlf(raw)) - const version = await this.writeAtomic(target, storage, existing, false, signal) - return { version, before, after } - }) - } - - private async withLock(targetKey: string, operation: () => Promise): Promise { - const prior = this.locks.get(targetKey) ?? Promise.resolve() - const run = prior.then(operation, operation) - const tail = run.then(() => undefined, () => undefined) - this.locks.set(targetKey, tail) - try { - return await run - } finally { - if (this.locks.get(targetKey) === tail) this.locks.delete(targetKey) - } - } - - private async canonicalPath(sandbox: Sandbox, path: string, signal?: AbortSignal): Promise { - try { - const result = await sandbox.commands.run( - `set -o pipefail; realpath -mz -- ${quoteE2BShellArg(path)} | base64 -w0`, - commandOpts(signal), - ) - return decodeCanonicalPath(result.stdout) - } catch (error: unknown) { - if (error instanceof CommandExitError) throw new Error(error.stderr || error.message, { cause: error }) - throw error - } - } - - private async probe(path: string, displayPath: string, signal?: AbortSignal): Promise { - assertNotAborted(signal, 'stat') - try { - const sandbox = await this.ctx.e2b.getSandbox() - const entry = await sandbox.files.getInfo(path, signalOpts(signal)) - assertNotAborted(signal, 'stat') - return entry - } catch (error: unknown) { - if (error instanceof FileNotFoundError) return undefined - throw mapError(error, 'stat', displayPath, signal) - } - } - - private async requireRegular(target: FsTarget, signal?: AbortSignal): Promise { - const info = await this.stat(target, signal) - if (info === undefined) throw new FsError(`cannot read "${target.displayPath}": not found`, 'FS_NOT_FOUND') - if (info.type !== 'file') throw new FsError(`cannot read "${target.displayPath}": not a regular file`, 'FS_NOT_REGULAR_FILE') - return info - } - - private checkWriteIntent(existing: EntryInfo | undefined, expected: FsWriteIntent | undefined, target: FsTarget): void { - if (expected?.kind === 'createIfAbsent' && existing !== undefined) { - throw new FsError(`cannot overwrite existing "${target.displayPath}" without reading it first`, 'FS_NOT_OBSERVED') - } - if (expected?.kind === 'replaceIfVersion') { - if (existing === undefined || entryVersion(existing) !== expected.version) { - throw new FsError(`cannot write "${target.displayPath}": file changed since it was read`, 'FS_STALE_VERSION') - } - } - } - - private async readForDiff(target: FsTarget, signal?: AbortSignal): Promise { - try { - const sandbox = await this.ctx.e2b.getSandbox() - const bytes = await sandbox.files.read(String(target.targetKey), { format: 'bytes', ...signalOpts(signal) }) - assertNotAborted(signal, 'read') - return normalizeLineEndings(decodeText(bytes, target.displayPath, bytes.length)) - } catch (error: unknown) { - if (error instanceof FsError && error.code === 'FS_NOT_TEXT') return null - throw mapError(error, 'read', target.displayPath, signal) - } - } - - private async readForEdit(target: FsTarget, signal?: AbortSignal): Promise { - try { - const sandbox = await this.ctx.e2b.getSandbox() - const bytes = await sandbox.files.read(String(target.targetKey), { format: 'bytes', ...signalOpts(signal) }) - assertNotAborted(signal, 'edit') - return decodeText(bytes, target.displayPath, bytes.length) - } catch (error: unknown) { - throw mapError(error, 'edit', target.displayPath, signal) - } - } - - private async writeAtomic( - target: FsTarget, - content: string, - existing: EntryInfo | undefined, - createIfAbsent: boolean, - signal?: AbortSignal, - ): Promise> { - assertNotAborted(signal, 'write') - const sandbox = await this.ctx.e2b.getSandbox() - const targetPath = String(target.targetKey) - const versionId = randomUUID() - const stagingDirectory = posix.join(posix.dirname(targetPath), `.dsh-${randomUUID()}.tmp`) - const temporary = posix.join(stagingDirectory, 'content') - let stagingDirectoryCreated = false - try { - const created = await sandbox.files.makeDir(stagingDirectory, signalOpts(signal)) - if (!created) throw new Error('private staging directory already exists') - stagingDirectoryCreated = true - await sandbox.commands.run(`chmod 700 -- ${quoteE2BShellArg(stagingDirectory)}`, commandOpts(signal)) - assertNotAborted(signal, 'write') - await sandbox.files.write(temporary, content, { - metadata: { [VERSION_METADATA_KEY]: versionId }, - ...signalOpts(signal), - }) - assertNotAborted(signal, 'write') - const mode = existing === undefined ? 0o600 : existing.mode & 0o777 - await sandbox.commands.run( - `chmod ${mode.toString(8)} -- ${quoteE2BShellArg(temporary)}`, - commandOpts(signal), - ) - assertNotAborted(signal, 'write') - let committed: EntryInfo - if (createIfAbsent) { - const staged = await sandbox.files.getInfo(temporary, signalOpts(signal)) - assertNotAborted(signal, 'write') - const targetArg = quoteE2BShellArg(targetPath) - const publication = await sandbox.commands.run( - `if ln -T -- ${quoteE2BShellArg(temporary)} ${targetArg}; then printf created; elif test -e ${targetArg} || test -L ${targetArg}; then printf exists; else exit 1; fi`, - commandOpts(undefined), - ) - if (publication.stdout === 'exists') { - throw new FsError( - `cannot overwrite existing "${target.displayPath}" without reading it first`, - 'FS_NOT_OBSERVED', - ) - } - if (publication.stdout !== 'created') { - throw new Error('guarded create returned an invalid publication result') - } - committed = { ...staged, name: posix.basename(targetPath), path: targetPath } - } else { - committed = await sandbox.files.rename(temporary, targetPath) - } - try { - await sandbox.files.remove(stagingDirectory) - } catch (_committedStagingCleanupFailure) { - // The target is already committed; an empty private directory cannot turn that write into a failure. - } - return entryVersion(committed) - } catch (error: unknown) { - if (stagingDirectoryCreated) { - try { - await sandbox.files.remove(stagingDirectory) - } catch (_stagingDirectoryAlreadyAbsentOrCleanupFailed) { - // Only the private staging directory is swallowed; the original failure owns the operation. - } - } - throw mapError(error, 'write', target.displayPath, signal) - } - } -} - -export default E2BFileSystem diff --git a/packages/e2b/fs-e2b/tests/filesystem.spec.ts b/packages/e2b/fs-e2b/tests/filesystem.spec.ts deleted file mode 100644 index 09b0a8e26b..0000000000 --- a/packages/e2b/fs-e2b/tests/filesystem.spec.ts +++ /dev/null @@ -1,851 +0,0 @@ -import { Buffer } from 'node:buffer' -import { dirname, posix } from 'node:path' -import { Context } from '@deepseek-ai/cordis' -import { - CommandExitError, - FileNotFoundError, - FileType, - type EntryInfo, - type Sandbox, -} from '@deepseek-ai/dsh-e2b' -import type E2BRuntime from '@deepseek-ai/dsh-e2b' -import { FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs' -import E2BFileSystem from '@deepseek-ai/dsh-fs-e2b' -import { describe, expect, it, vi } from 'vitest' - -interface RemoteNode { - type: FileType - data: Uint8Array - mode: number - modified: number - metadata?: Record - symlinkTarget?: string -} - -function bytes(value: string | readonly number[]): Uint8Array { - return typeof value === 'string' ? new TextEncoder().encode(value) : Uint8Array.from(value) -} - -function commandError(exitCode: number, stderr = ''): CommandExitError { - return new CommandExitError({ exitCode, stdout: '', stderr, error: stderr }) -} - -class FakeRemote { - readonly nodes = new Map() - readonly writes: Array<{ path: string; data: string; metadata?: Record }> = [] - readonly writeParentModes: number[] = [] - readonly renames: Array<{ from: string; to: string }> = [] - readonly links: Array<{ from: string; to: string }> = [] - readonly removals: string[] = [] - readonly commands: string[] = [] - readonly reads: Array<{ path: string; format: 'bytes' | 'stream' }> = [] - streamChunks: Uint8Array[] | undefined - streamKeepOpen = false - readonly streamCancel = vi.fn() - nextCommandError: unknown - nextMakeDirResult: boolean | undefined - nextInfoError: unknown - nextListError: unknown - nextReadError: unknown - nextRenameError: unknown - nextRemoveError: unknown - canonicalOutput: string | undefined - abortAfterRename: AbortController | undefined - competitorBeforeLink: - | { path: string; kind: 'file'; data: string } - | { path: string; kind: 'directory' } - | undefined - guardedLinkOutput: string | undefined - disappearOnInfo = new Set() - private clock = 1 - - constructor() { - this.dir('/') - this.dir('/workspace') - } - - dir(path: string): void { - this.nodes.set(path, { type: FileType.DIR, data: bytes(''), mode: 0o755, modified: this.clock++ }) - } - - file(path: string, data: string | readonly number[], mode = 0o644): void { - this.nodes.set(path, { type: FileType.FILE, data: bytes(data), mode, modified: this.clock++ }) - } - - other(path: string): void { - this.nodes.set(path, { type: 'other' as FileType, data: bytes(''), mode: 0o600, modified: this.clock++ }) - } - - symlink(path: string, target: string): void { - this.nodes.set(path, { - type: FileType.FILE, - data: bytes(''), - mode: 0o777, - modified: this.clock++, - symlinkTarget: target, - }) - } - - mutate(path: string, data: string): void { - const node = this.required(path) - node.data = bytes(data) - node.modified = this.clock++ - } - - private required(path: string): RemoteNode { - const node = this.nodes.get(path) - if (node === undefined) throw new FileNotFoundError(`missing: ${path}`) - return node - } - - private followed(path: string): { path: string; node: RemoteNode; link?: RemoteNode } { - const node = this.required(path) - if (node.symlinkTarget === undefined) return { path, node } - return { path: node.symlinkTarget, node: this.required(node.symlinkTarget), link: node } - } - - private info(path: string): EntryInfo { - if (this.disappearOnInfo.delete(path)) throw new FileNotFoundError(`missing: ${path}`) - return this.rawInfo(path) - } - - private rawInfo(path: string): EntryInfo { - const followed = this.followed(path) - const node = followed.node - return { - name: posix.basename(path), - path, - type: node.type, - size: node.data.byteLength, - mode: node.mode, - permissions: 'rw-------', - owner: 'user', - group: 'user', - modifiedTime: new Date(node.modified), - ...(node.metadata !== undefined ? { metadata: { ...node.metadata } } : {}), - ...(followed.link?.symlinkTarget !== undefined ? { symlinkTarget: followed.link.symlinkTarget } : {}), - } - } - - private checkAbort(options: { signal?: AbortSignal } | undefined): void { - if (options?.signal?.aborted === true) throw new DOMException('aborted', 'AbortError') - } - - readonly sandbox = { - sandboxId: 'fake', - files: { - makeDir: async (path: string, options?: { signal?: AbortSignal }): Promise => { - this.checkAbort(options) - if (this.nextMakeDirResult !== undefined) { - const result = this.nextMakeDirResult - this.nextMakeDirResult = undefined - return result - } - if (this.nodes.has(path)) return false - this.dir(path) - return true - }, - getInfo: async (path: string, options?: { signal?: AbortSignal }): Promise => { - this.checkAbort(options) - if (this.nextInfoError !== undefined) { - const error = this.nextInfoError - this.nextInfoError = undefined - throw error - } - return this.info(path) - }, - read: async (path: string, options: { format: 'bytes' | 'stream'; signal?: AbortSignal }): Promise | string> => { - this.checkAbort(options) - this.reads.push({ path, format: options.format }) - if (this.nextReadError !== undefined) { - const error = this.nextReadError - this.nextReadError = undefined - throw error - } - const data = this.followed(path).node.data - if (options.format === 'bytes') return data.slice() - // Pinned-SDK fidelity: a content-length-0 response returns '' even in stream format. - if (data.length === 0 && this.streamChunks === undefined) return '' - const chunks = this.streamChunks ?? [data.slice()] - return new ReadableStream({ - start: (controller) => { - for (const chunk of chunks) controller.enqueue(chunk) - if (!this.streamKeepOpen) controller.close() - // SDK fidelity: an abort of the request signal fails the open stream. - options.signal?.addEventListener('abort', () => { controller.error(new DOMException('aborted', 'AbortError')) }, { once: true }) - }, - cancel: () => { this.streamCancel() }, - }) - }, - list: async (path: string, options?: { depth?: number; signal?: AbortSignal }): Promise => { - this.checkAbort(options) - if (this.nextListError !== undefined) { - const error = this.nextListError - this.nextListError = undefined - throw error - } - this.required(path) - return [...this.nodes.keys()] - .filter(candidate => candidate !== path && dirname(candidate) === path) - .map(candidate => this.rawInfo(candidate)) - }, - write: async (path: string, data: string, options?: { metadata?: Record; signal?: AbortSignal }): Promise => { - this.checkAbort(options) - const parent = dirname(path) - if (!this.nodes.has(parent)) this.dir(parent) - this.writeParentModes.push(this.required(parent).mode) - this.nodes.set(path, { - type: FileType.FILE, - data: bytes(data), - mode: 0o644, - modified: this.clock++, - ...(options?.metadata !== undefined ? { metadata: { ...options.metadata } } : {}), - }) - this.writes.push({ path, data, ...(options?.metadata !== undefined ? { metadata: options.metadata } : {}) }) - return {} - }, - rename: async (from: string, to: string, options?: { signal?: AbortSignal }): Promise => { - this.checkAbort(options) - if (this.nextRenameError !== undefined) { - const error = this.nextRenameError - this.nextRenameError = undefined - throw error - } - const node = this.required(from) - this.nodes.delete(from) - this.nodes.set(to, node) - this.renames.push({ from, to }) - this.abortAfterRename?.abort('after commit') - this.checkAbort(options) - return this.info(to) - }, - remove: async (path: string): Promise => { - this.removals.push(path) - if (this.nextRemoveError !== undefined) { - const error = this.nextRemoveError - this.nextRemoveError = undefined - throw error - } - for (const candidate of this.nodes.keys()) { - if (candidate === path || candidate.startsWith(`${path}/`)) this.nodes.delete(candidate) - } - }, - }, - commands: { - run: async ( - command: string, - options?: { envs?: Record; signal?: AbortSignal }, - ): Promise<{ exitCode: number; stdout: string; stderr: string }> => { - this.checkAbort(options) - const home = options?.envs?.HOME - expect(home).toMatch(/^\/\.dsh-e2b-control-/) - expect(options?.envs).toEqual({ HOME: home }) - this.commands.push(command) - if (this.nextCommandError !== undefined) { - const error = this.nextCommandError - this.nextCommandError = undefined - throw error - } - const realpathPrefix = 'set -o pipefail; realpath -mz -- ' - const realpathSuffix = ' | base64 -w0' - if (command.startsWith(realpathPrefix) && command.endsWith(realpathSuffix)) { - const quoted = command.slice(realpathPrefix.length, -realpathSuffix.length) - const input = quoted.slice(1, -1).replaceAll(String.raw`'"'"'`, '\'') - const node = this.nodes.get(input) - const canonical = `${node?.symlinkTarget ?? input}\0` - return { - exitCode: 0, - stdout: this.canonicalOutput ?? Buffer.from(canonical).toString('base64'), - stderr: '', - } - } - const chmod = /^chmod ([0-7]+) -- '([^']+)'$/.exec(command) - if (chmod !== null) this.required(chmod[2]!).mode = Number.parseInt(chmod[1]!, 8) - const guardedLink = new RegExp( - "^if ln -T -- '([^']+)' '([^']+)'; then printf created; " - + "elif test -e '[^']+' \\|\\| test -L '[^']+'; then printf exists; else exit 1; fi$", - ).exec(command) - if (guardedLink !== null) { - const from = guardedLink[1]! - const to = guardedLink[2]! - if (this.guardedLinkOutput !== undefined) { - const stdout = this.guardedLinkOutput - this.guardedLinkOutput = undefined - return { exitCode: 0, stdout, stderr: '' } - } - if (this.competitorBeforeLink?.path === to) { - if (this.competitorBeforeLink.kind === 'directory') this.dir(to) - else this.file(to, this.competitorBeforeLink.data) - this.competitorBeforeLink = undefined - } - if (this.nodes.has(to)) return { exitCode: 0, stdout: 'exists', stderr: '' } - this.nodes.set(to, this.required(from)) - this.links.push({ from, to }) - this.abortAfterRename?.abort('after commit') - return { exitCode: 0, stdout: 'created', stderr: '' } - } - const move = /^mv -f -- '([^']+)' '([^']+)'$/.exec(command) - if (move !== null) { - if (this.nextRenameError !== undefined) { - const error = this.nextRenameError - this.nextRenameError = undefined - throw error - } - const node = this.required(move[1]!) - this.nodes.delete(move[1]!) - this.nodes.set(move[2]!, node) - this.renames.push({ from: move[1]!, to: move[2]! }) - this.abortAfterRename?.abort('after commit') - } - return { exitCode: 0, stdout: '', stderr: '' } - }, - }, - } as unknown as Sandbox -} - -async function setup(remote = new FakeRemote()): Promise<{ ctx: Context; fs: E2BFileSystem; remote: FakeRemote }> { - const ctx = new Context() - const runtime = { - cwd: '/workspace', - runtimeRoot: '/workspace/.dsh-e2b', - getSandbox: async () => remote.sandbox, - } as unknown as E2BRuntime - ctx.provide('e2b', runtime) - await ctx.plugin(E2BFileSystem) - return { ctx, fs: ctx.fs as E2BFileSystem, remote } -} - -async function expectCode(promise: Promise, code: string): Promise { - await expect(promise).rejects.toMatchObject({ code }) -} - -describe('E2BFileSystem identity, metadata, and reads', () => { - it('resolves remote paths, reports symlinks, and lists direct children in stable order', async () => { - const remote = new FakeRemote() - remote.file('/workspace/z.txt', 'z') - remote.file('/workspace/a.txt', 'a') - remote.dir('/workspace/dir') - remote.other('/workspace/special') - remote.file('/workspace/dir/nested.txt', 'nested') - remote.symlink('/workspace/link.txt', '/workspace/a.txt') - const { fs } = await setup(remote) - - const link = await fs.resolve('link.txt') - expect(link).toEqual({ targetKey: '/workspace/a.txt', displayPath: '/workspace/link.txt' }) - await expect(fs.lstat('link.txt')).resolves.toMatchObject({ type: 'symlink', size: 1 }) - await expect(fs.lstat('a.txt')).resolves.toMatchObject({ type: 'file', size: 1 }) - await expect(fs.lstat('dir')).resolves.toEqual(expect.objectContaining({ type: 'directory' })) - await expect(fs.lstat('special')).resolves.toEqual(expect.objectContaining({ type: 'other' })) - await expect(fs.lstat('missing')).resolves.toBeUndefined() - await expect(fs.stat(link)).resolves.toMatchObject({ type: 'file', size: 1 }) - const directory = await fs.resolve('.') - const listed = await fs.listDir(directory) - expect(listed.map(entry => entry.name)).toEqual(['a.txt', 'dir', 'link.txt', 'special', 'z.txt']) - expect(listed.find(entry => entry.name === 'dir')).toMatchObject({ type: 'directory' }) - expect(listed.find(entry => entry.name === 'link.txt')).toMatchObject({ - type: 'file', - target: { targetKey: '/workspace/a.txt', displayPath: '/workspace/link.txt' }, - }) - expect(listed.some(entry => entry.name === 'nested.txt')).toBe(false) - }) - - it('projects canonical process paths, file URLs, and containment', async () => { - const remote = new FakeRemote() - remote.dir('/workspace/nested') - remote.file('/workspace/nested/multibyte # file.ts', 'text') - remote.file('/outside.ts', 'outside') - const { fs } = await setup(remote) - const workspace = await fs.resolve('/workspace') - const nested = await fs.resolve('/workspace/nested/multibyte # file.ts') - const outside = await fs.resolve('/outside.ts') - - expect(fs.processPath(nested)).toBe('/workspace/nested/multibyte # file.ts') - expect(fs.processPathFromHostPath('/Users/alice/.dsh/attachments/object')).toBeUndefined() - expect(fs.fileUrl(nested)).toBe('file:///workspace/nested/multibyte%20%23%20file.ts') - expect(fs.contains(workspace, workspace)).toBe(true) - expect(fs.contains(workspace, nested)).toBe(true) - expect(fs.contains(nested, workspace)).toBe(false) - expect(fs.contains(workspace, outside)).toBe(false) - expect(() => fs.fileUrl({ targetKey: FsTargetKey('relative'), displayPath: 'relative' })) - .toThrow('expected an absolute process path') - }) - - it('preserves newline and multibyte canonical paths through strict ASCII framing', async () => { - const remote = new FakeRemote() - const path = '/workspace/你好\nfile.ts' - remote.file(path, 'text') - const { fs } = await setup(remote) - - await expect(fs.resolve(path)).resolves.toEqual({ targetKey: path, displayPath: path }) - }) - - it.each([ - ['invalid base64', '!!!!'], - ['missing terminator', Buffer.from('/workspace/file').toString('base64')], - ['multiple records', Buffer.from('/workspace/file\0/other\0').toString('base64')], - ['invalid UTF-8', Buffer.from([47, 0xff, 0]).toString('base64')], - ['relative path', Buffer.from('workspace/file\0').toString('base64')], - ])('rejects %s from canonical path transport', async (_label, output) => { - const remote = new FakeRemote() - remote.canonicalOutput = output - const { fs } = await setup(remote) - await expectCode(fs.resolve('file'), 'FS_IO_ERROR') - }) - - it('reads whole and streamed UTF-8 across chunk boundaries', async () => { - const remote = new FakeRemote() - remote.file('/workspace/text.txt', 'A€B') - remote.streamChunks = [bytes([65, 0xe2]), bytes([0x82, 0xac, 66])] - const { fs } = await setup(remote) - const target = await fs.resolve('text.txt') - await expect(fs.readText(target)).resolves.toBe('A€B') - let streamed = '' - for await (const chunk of await fs.streamText(target)) streamed += chunk - expect(streamed).toBe('A€B') - - remote.streamChunks = [bytes([0xe2]), bytes([0x82, 0xac])] - let initiallyBuffered = '' - for await (const chunk of await fs.streamText(target)) initiallyBuffered += chunk - expect(initiallyBuffered).toBe('€') - }) - - it('streams an empty file even though the pinned SDK returns a non-stream value', async () => { - const remote = new FakeRemote() - remote.file('/workspace/empty.txt', '') - const { fs } = await setup(remote) - let streamed = '' - for await (const chunk of await fs.streamText(await fs.resolve('empty.txt'))) streamed += chunk - expect(streamed).toBe('') - }) - - it('cancels a remote stream when its consumer stops early', async () => { - const remote = new FakeRemote() - remote.file('/workspace/text.txt', 'ab') - remote.streamChunks = [bytes('a'), bytes('b')] - remote.streamKeepOpen = true - const { fs } = await setup(remote) - const stream = await fs.streamText(await fs.resolve('text.txt')) - - for await (const chunk of stream) { - expect(chunk).toBe('a') - break - } - - expect(remote.streamCancel).toHaveBeenCalledOnce() - }) - - it('matches local binary sampling while edits still reject any NUL byte', async () => { - const remote = new FakeRemote() - remote.file('/workspace/late-nul.txt', `${'a'.repeat(8192)}\0tail`) - const { fs } = await setup(remote) - const target = await fs.resolve('late-nul.txt') - await expect(fs.readText(target)).resolves.toContain('\0tail') - remote.streamChunks = [bytes('a'.repeat(8192)), bytes([0, 116])] - let streamed = '' - for await (const chunk of await fs.streamText(target)) streamed += chunk - expect(streamed).toBe(`${'a'.repeat(8192)}\0t`) - await expectCode(fs.editText(target, { oldString: 'tail', newString: 'end', replaceAll: false }), 'FS_NOT_TEXT') - }) - - it('maps binary, invalid UTF-8, missing, and non-regular read failures', async () => { - const remote = new FakeRemote() - remote.file('/workspace/binary', [0, 1]) - remote.file('/workspace/invalid', [0xff]) - remote.dir('/workspace/directory') - const { fs } = await setup(remote) - await expectCode(fs.readText(await fs.resolve('binary')), 'FS_NOT_TEXT') - await expectCode(fs.readText(await fs.resolve('invalid')), 'FS_NOT_TEXT') - await expectCode(fs.readText(await fs.resolve('missing')), 'FS_NOT_FOUND') - await expectCode(fs.readText(await fs.resolve('directory')), 'FS_NOT_REGULAR_FILE') - - remote.streamChunks = [bytes([0xff])] - const invalid = await fs.streamText(await fs.resolve('invalid')) - await expect((async () => { for await (const _chunk of invalid) void _chunk })()).rejects.toMatchObject({ code: 'FS_NOT_TEXT' }) - remote.streamChunks = [bytes([0])] - const binary = await fs.streamText(await fs.resolve('binary')) - await expect((async () => { for await (const _chunk of binary) void _chunk })()).rejects.toMatchObject({ code: 'FS_NOT_TEXT' }) - - remote.streamChunks = [bytes([0xe2])] - const incomplete = await fs.streamText(await fs.resolve('invalid')) - await expect((async () => { for await (const _chunk of incomplete) void _chunk })()).rejects.toMatchObject({ code: 'FS_NOT_TEXT' }) - - const raced = await fs.resolve('invalid') - remote.nextReadError = new FileNotFoundError('gone after stat') - await expectCode(fs.streamText(raced), 'FS_NOT_FOUND') - }) - - it('readBytes returns raw content, enforces the byte cap, and maps failures', async () => { - const remote = new FakeRemote() - remote.file('/workspace/img.bin', [0x89, 0, 0xff, 0x47]) - remote.dir('/workspace/directory') - const { fs } = await setup(remote) - const target = await fs.resolve('img.bin') - expect(Array.from(await fs.readBytes(target, undefined, 4))).toEqual([0x89, 0, 0xff, 0x47]) - expect(remote.reads).toEqual([{ path: '/workspace/img.bin', format: 'stream' }]) - remote.reads.length = 0 - await expectCode(fs.readBytes(target, undefined, 3), 'FS_TOO_LARGE') - expect(remote.reads).toEqual([]) - await expectCode(fs.readBytes(await fs.resolve('missing'), undefined, 4), 'FS_NOT_FOUND') - await expectCode(fs.readBytes(await fs.resolve('directory'), undefined, 4), 'FS_NOT_REGULAR_FILE') - - const live = new AbortController() - expect((await fs.readBytes(target, live.signal, 4)).byteLength).toBe(4) - remote.nextReadError = new DOMException('aborted', 'AbortError') - await expectCode(fs.readBytes(target, undefined, 4), 'FS_ABORTED') - }) - - it('readBytes bounds a post-stat grower mid-stream and reads an empty file through the SDK quirk', async () => { - const remote = new FakeRemote() - remote.file('/workspace/grow.bin', [1, 1, 1, 1]) - remote.file('/workspace/empty.bin', '') - const { fs } = await setup(remote) - - remote.streamChunks = [bytes([1, 1, 1]), bytes([1, 2, 2])] - remote.streamKeepOpen = true - await expectCode(fs.readBytes(await fs.resolve('grow.bin'), undefined, 4), 'FS_TOO_LARGE') - expect(remote.streamCancel).toHaveBeenCalledOnce() - - remote.streamChunks = undefined - remote.streamKeepOpen = false - expect((await fs.readBytes(await fs.resolve('empty.bin'), undefined, 4)).byteLength).toBe(0) - }) - - it('readByteRange skips to the offset, keeps the window, and cancels the stream there', async () => { - const remote = new FakeRemote() - remote.file('/workspace/ramp.bin', [1, 2, 3, 4, 5, 6, 7, 8, 9]) - const { fs } = await setup(remote) - const target = await fs.resolve('ramp.bin') - remote.streamChunks = [bytes([1, 2, 3]), bytes([4, 5, 6]), bytes([7, 8, 9])] - remote.streamKeepOpen = true - expect(Array.from(await fs.readByteRange(target, { offset: 4, length: 3 }))).toEqual([5, 6, 7]) - expect(remote.reads).toEqual([{ path: '/workspace/ramp.bin', format: 'stream' }]) - expect(remote.streamCancel).toHaveBeenCalledOnce() - }) - - it('readByteRange shortens at the end, empties past it, and skips the read for length 0', async () => { - const remote = new FakeRemote() - remote.file('/workspace/ramp.bin', [1, 2, 3, 4, 5, 6, 7, 8, 9]) - remote.dir('/workspace/directory') - const { fs } = await setup(remote) - const target = await fs.resolve('ramp.bin') - remote.streamChunks = [bytes([1, 2, 3]), bytes([4, 5, 6]), bytes([7, 8, 9])] - expect(Array.from(await fs.readByteRange(target, { offset: 7, length: 10 }))).toEqual([8, 9]) - expect(remote.streamCancel).not.toHaveBeenCalled() - expect((await fs.readByteRange(target, { offset: 9, length: 2 })).byteLength).toBe(0) - remote.reads.length = 0 - expect((await fs.readByteRange(target, { offset: 0, length: 0 })).byteLength).toBe(0) - expect(remote.reads).toEqual([]) - await expectCode(fs.readByteRange(await fs.resolve('missing'), { offset: 0, length: 1 }), 'FS_NOT_FOUND') - await expectCode(fs.readByteRange(await fs.resolve('directory'), { offset: 0, length: 1 }), 'FS_NOT_REGULAR_FILE') - }) - - it('readByteRange maps a failing open, an abort mid-stream, and tolerates a failing cancel', async () => { - const remote = new FakeRemote() - remote.file('/workspace/ramp.bin', [1, 2, 3, 4]) - const { fs } = await setup(remote) - const target = await fs.resolve('ramp.bin') - remote.nextReadError = new DOMException('aborted', 'AbortError') - await expectCode(fs.readByteRange(target, { offset: 0, length: 2 }), 'FS_ABORTED') - - // The window wants more than the one chunk delivered; the abort fails the open stream. - remote.streamChunks = [bytes([1])] - remote.streamKeepOpen = true - const controller = new AbortController() - const pending = fs.readByteRange(target, { offset: 0, length: 4 }, controller.signal) - await new Promise((resolve) => { setTimeout(resolve, 0) }) - controller.abort() - await expectCode(pending, 'FS_ABORTED') - - remote.streamChunks = [bytes([1, 2, 3, 4])] - remote.streamCancel.mockRejectedValueOnce(new Error('cancel failed')) - expect(Array.from(await fs.readByteRange(target, { offset: 1, length: 2 }))).toEqual([2, 3]) - }) - - it('honors aborts before and during remote reads', async () => { - const remote = new FakeRemote() - remote.file('/workspace/a', 'a') - const { fs } = await setup(remote) - await expectCode(fs.resolve('a', { signal: AbortSignal.abort() }), 'FS_ABORTED') - await expectCode(fs.lstat('a', undefined, AbortSignal.abort()), 'FS_ABORTED') - await expectCode(fs.stat(await fs.resolve('a'), AbortSignal.abort()), 'FS_ABORTED') - remote.nextReadError = new DOMException('aborted', 'AbortError') - await expectCode(fs.readText(await fs.resolve('a')), 'FS_ABORTED') - }) - - it('rejects empty paths and directory-listing type errors', async () => { - const remote = new FakeRemote() - remote.file('/workspace/file', 'x') - const { fs } = await setup(remote) - await expectCode(fs.resolve(' '), 'FS_NOT_FOUND') - await expectCode(fs.lstat(''), 'FS_NOT_FOUND') - await expectCode(fs.listDir(await fs.resolve('missing')), 'FS_NOT_FOUND') - await expectCode(fs.listDir(await fs.resolve('/workspace/file')), 'FS_NOT_DIRECTORY') - remote.nextListError = new Error('listing transport failed') - await expectCode(fs.listDir(await fs.resolve('/workspace')), 'FS_IO_ERROR') - }) -}) - -describe('E2BFileSystem atomic writes and edits', () => { - it('creates owner-only files and returns metadata after the committed move', async () => { - const { fs, remote } = await setup() - const target = await fs.resolve('new.txt') - const outcome = await fs.writeText(target, 'one\r\ntwo\rthree', { kind: 'createIfAbsent' }) - expect(outcome).toMatchObject({ operation: 'create', before: null, after: 'one\ntwo\rthree' }) - expect(remote.nodes.get('/workspace/new.txt')?.mode).toBe(0o600) - expect(remote.nodes.get('/workspace/new.txt')?.metadata?.['dsh-version']).toBeDefined() - expect(remote.writeParentModes).toEqual([0o700]) - expect(remote.links).toHaveLength(1) - const stagingDirectory = posix.dirname(remote.writes[0]!.path) - expect(posix.dirname(stagingDirectory)).toBe('/workspace') - expect(remote.removals).toContain(stagingDirectory) - await expect(fs.stat(target)).resolves.toMatchObject({ version: outcome.version, size: 14 }) - }) - - it('preserves replacement mode, normalizes only CRLF for diffs, and changes version on external writes', async () => { - const remote = new FakeRemote() - remote.file('/workspace/file.txt', 'old\r\nline\rlone', 0o640) - const { fs } = await setup(remote) - const target = await fs.resolve('file.txt') - const before = (await fs.stat(target))!.version - const outcome = await fs.writeText(target, 'new', { kind: 'replaceIfVersion', version: before }) - expect(outcome).toMatchObject({ operation: 'update', before: 'old\nline\rlone', after: 'new' }) - expect(remote.nodes.get('/workspace/file.txt')?.mode).toBe(0o640) - const committed = outcome.version - remote.mutate('/workspace/file.txt', 'external') - expect((await fs.stat(target))!.version).not.toBe(committed) - }) - - it('returns null as the overwrite diff basis for binary or invalid prior content', async () => { - const remote = new FakeRemote() - remote.file('/workspace/file.txt', [0xff]) - const { fs } = await setup(remote) - const target = await fs.resolve('file.txt') - await expect(fs.writeText(target, 'valid')).resolves.toMatchObject({ before: null, after: 'valid' }) - }) - - it('fails an overwrite when reading its text diff basis fails for another reason', async () => { - const remote = new FakeRemote() - remote.file('/workspace/file.txt', 'prior') - const { fs } = await setup(remote) - const target = await fs.resolve('file.txt') - remote.nextReadError = new Error('read transport failed') - await expectCode(fs.writeText(target, 'replacement'), 'FS_IO_ERROR') - expect(new TextDecoder().decode(remote.nodes.get('/workspace/file.txt')?.data)).toBe('prior') - }) - - it('enforces create and version intents before publication', async () => { - const remote = new FakeRemote() - remote.file('/workspace/file.txt', 'v1') - const { fs } = await setup(remote) - const target = await fs.resolve('file.txt') - const version = (await fs.stat(target))!.version - await expectCode(fs.writeText(target, 'blind', { kind: 'createIfAbsent' }), 'FS_NOT_OBSERVED') - remote.mutate('/workspace/file.txt', 'v2') - await expectCode(fs.writeText(target, 'stale', { kind: 'replaceIfVersion', version }), 'FS_STALE_VERSION') - await expectCode(fs.writeText(await fs.resolve('missing'), 'stale', { kind: 'replaceIfVersion', version }), 'FS_STALE_VERSION') - remote.dir('/workspace/dir') - await expectCode(fs.writeText(await fs.resolve('dir'), 'x'), 'FS_NOT_REGULAR_FILE') - }) - - it('preserves a competitor created after the guarded-create probe', async () => { - const remote = new FakeRemote() - remote.competitorBeforeLink = { path: '/workspace/race.txt', kind: 'file', data: 'competitor' } - const { fs } = await setup(remote) - - await expectCode( - fs.writeText(await fs.resolve('race.txt'), 'ours', { kind: 'createIfAbsent' }), - 'FS_NOT_OBSERVED', - ) - expect(new TextDecoder().decode(remote.nodes.get('/workspace/race.txt')?.data)).toBe('competitor') - expect(remote.links).toHaveLength(0) - expect(remote.removals).toHaveLength(1) - }) - - it('preserves a competing directory during guarded-create publication', async () => { - const remote = new FakeRemote() - remote.competitorBeforeLink = { path: '/workspace/race-dir', kind: 'directory' } - const { fs } = await setup(remote) - - await expectCode( - fs.writeText(await fs.resolve('race-dir'), 'ours', { kind: 'createIfAbsent' }), - 'FS_NOT_OBSERVED', - ) - expect(remote.nodes.get('/workspace/race-dir')?.type).toBe(FileType.DIR) - expect(remote.nodes.has('/workspace/race-dir/content')).toBe(false) - expect(remote.links).toHaveLength(0) - expect(remote.removals).toHaveLength(1) - }) - - it('rejects an invalid guarded-create publication response before claiming success', async () => { - const remote = new FakeRemote() - remote.guardedLinkOutput = 'unexpected' - const { fs } = await setup(remote) - - await expectCode( - fs.writeText(await fs.resolve('invalid.txt'), 'ours', { kind: 'createIfAbsent' }), - 'FS_IO_ERROR', - ) - expect(remote.nodes.has('/workspace/invalid.txt')).toBe(false) - expect(remote.removals).toHaveLength(1) - }) - - it('does not turn an abort observed after a successful move into a failed write', async () => { - const remote = new FakeRemote() - const controller = new AbortController() - remote.abortAfterRename = controller - const { fs } = await setup(remote) - await expect(fs.writeText(await fs.resolve('committed'), 'yes', undefined, controller.signal)) - .resolves.toMatchObject({ operation: 'create' }) - expect(controller.signal.aborted).toBe(true) - }) - - it('does not turn an abort observed after a guarded create into a failed write', async () => { - const remote = new FakeRemote() - const controller = new AbortController() - remote.abortAfterRename = controller - const { fs } = await setup(remote) - await expect(fs.writeText( - await fs.resolve('committed-create'), - 'yes', - { kind: 'createIfAbsent' }, - controller.signal, - )).resolves.toMatchObject({ operation: 'create' }) - expect(controller.signal.aborted).toBe(true) - }) - - it('does not turn post-commit staging cleanup failure into a failed write', async () => { - const remote = new FakeRemote() - remote.nextRemoveError = new Error('empty staging cleanup failed') - const { fs } = await setup(remote) - await expect(fs.writeText(await fs.resolve('committed'), 'yes')) - .resolves.toMatchObject({ operation: 'create' }) - expect(new TextDecoder().decode(remote.nodes.get('/workspace/committed')?.data)).toBe('yes') - }) - - it('returns committed rename metadata without a fallible post-commit lookup', async () => { - const remote = new FakeRemote() - const getInfo = vi.spyOn(remote.sandbox.files, 'getInfo') - const { fs } = await setup(remote) - - await expect(fs.writeText(await fs.resolve('committed'), 'yes')) - .resolves.toMatchObject({ operation: 'create' }) - expect(getInfo).toHaveBeenCalledTimes(1) - expect(remote.renames).toHaveLength(1) - }) - - it('cleans staging files and maps command, permission, and abort failures', async () => { - const remote = new FakeRemote() - const { fs } = await setup(remote) - const commandTarget = await fs.resolve('command') - remote.nextCommandError = commandError(1, 'chmod failed') - await expectCode(fs.writeText(commandTarget, 'x'), 'FS_IO_ERROR') - expect(remote.removals).toHaveLength(1) - - remote.nextRenameError = new Error('permission denied') - await expectCode(fs.writeText(await fs.resolve('permission'), 'x'), 'FS_PERMISSION_DENIED') - remote.nextRemoveError = new Error('cleanup also failed') - remote.nextRenameError = new DOMException('aborted', 'AbortError') - await expectCode(fs.writeText(await fs.resolve('abort'), 'x'), 'FS_ABORTED') - - const removalsBeforeCollision = remote.removals.length - remote.nextMakeDirResult = false - await expectCode(fs.writeText(await fs.resolve('collision'), 'x'), 'FS_IO_ERROR') - expect(remote.removals).toHaveLength(removalsBeforeCollision) - }) - - it('applies literal edits atomically and restores the detected CRLF style', async () => { - const remote = new FakeRemote() - remote.file('/workspace/file.txt', 'one\r\ntwo\r\nthree\n') - const { fs } = await setup(remote) - const target = await fs.resolve('file.txt') - const version = (await fs.stat(target))!.version - const outcome = await fs.editText( - target, - { oldString: 'two\r\n', newString: 'TWO\r\n', replaceAll: false }, - { version }, - ) - expect(outcome).toMatchObject({ before: 'one\ntwo\nthree\n', after: 'one\nTWO\nthree\n' }) - expect(new TextDecoder().decode(remote.nodes.get('/workspace/file.txt')?.data)).toBe('one\r\nTWO\r\nthree\r\n') - }) - - it('reports stale and literal-match failures with stable codes', async () => { - const remote = new FakeRemote() - remote.file('/workspace/file.txt', 'a a') - remote.dir('/workspace/dir') - const { fs } = await setup(remote) - const target = await fs.resolve('file.txt') - await expectCode(fs.editText(target, { oldString: '', newString: 'x', replaceAll: false }), 'FS_EDIT_NOT_FOUND') - await expectCode(fs.editText(target, { oldString: 'z', newString: 'x', replaceAll: false }), 'FS_EDIT_NOT_FOUND') - await expectCode(fs.editText(target, { oldString: 'a', newString: 'x', replaceAll: false }), 'FS_AMBIGUOUS_EDIT') - await expect(fs.editText(target, { oldString: 'a', newString: 'x', replaceAll: true })) - .resolves.toMatchObject({ after: 'x x' }) - await expectCode(fs.editText(target, { oldString: 'x', newString: 'y', replaceAll: false }, { version: FsVersion('stale') }), 'FS_STALE_VERSION') - await expectCode(fs.editText(await fs.resolve('missing'), { oldString: 'x', newString: 'y', replaceAll: false }), 'FS_STALE_VERSION') - await expectCode(fs.editText(await fs.resolve('dir'), { oldString: 'x', newString: 'y', replaceAll: false }), 'FS_NOT_REGULAR_FILE') - }) - - it('serializes guarded mutations so only one stale version can win', async () => { - const remote = new FakeRemote() - remote.file('/workspace/file.txt', 'base') - const { fs } = await setup(remote) - const target = await fs.resolve('file.txt') - const version = (await fs.stat(target))!.version - const results = await Promise.allSettled([ - fs.writeText(target, 'one', { kind: 'replaceIfVersion', version }), - fs.editText(target, { oldString: 'base', newString: 'two', replaceAll: false }, { version }), - ]) - expect(results.filter(result => result.status === 'fulfilled')).toHaveLength(1) - expect(results.filter(result => result.status === 'rejected')).toHaveLength(1) - }) -}) - -describe('E2B filesystem adapter integration edges', () => { - it('maps canonicalization, permission, and generic provider failures', async () => { - const remote = new FakeRemote() - const { fs } = await setup(remote) - remote.nextCommandError = commandError(1, 'not a directory') - await expectCode(fs.resolve('bad'), 'FS_IO_ERROR') - remote.nextCommandError = commandError(1) - await expectCode(fs.resolve('bad-again'), 'FS_IO_ERROR') - remote.nextCommandError = new Error('canonical transport failed') - await expectCode(fs.resolve('bad-transport'), 'FS_IO_ERROR') - remote.file('/workspace/a', 'a') - const target = await fs.resolve('a') - remote.nextInfoError = new Error('metadata transport failed') - await expectCode(fs.stat(target), 'FS_IO_ERROR') - remote.nextReadError = new Error('operation not permitted') - await expectCode(fs.readText(target), 'FS_PERMISSION_DENIED') - remote.nextReadError = 'transport vanished' - await expectCode(fs.readText(target), 'FS_IO_ERROR') - }) - - it('uses listing metadata directly and canonicalizes only symbolic links', async () => { - const remote = new FakeRemote() - remote.file('/workspace/a', 'a') - remote.file('/workspace/target', 'target') - remote.file('/workspace/gone', 'gone') - remote.symlink('/workspace/link', '/workspace/target') - remote.symlink('/workspace/vanished-link', '/workspace/gone') - remote.disappearOnInfo.add('/workspace/gone') - const { fs } = await setup(remote) - const directory = await fs.resolve('/workspace') - const commandsBefore = remote.commands.length - const getInfo = vi.spyOn(remote.sandbox.files, 'getInfo') - - const listed = await fs.listDir(directory) - - expect(listed.find(entry => entry.name === 'a')).toMatchObject({ - type: 'file', target: { targetKey: '/workspace/a' }, size: 1, - }) - expect(listed.find(entry => entry.name === 'link')).toMatchObject({ - type: 'file', target: { targetKey: '/workspace/target' }, size: 6, - }) - expect(listed.find(entry => entry.name === 'vanished-link')).toEqual({ - name: 'vanished-link', - type: 'other', - target: { targetKey: '/workspace/gone', displayPath: '/workspace/vanished-link' }, - }) - expect(remote.commands.slice(commandsBefore)).toHaveLength(2) - expect(getInfo).toHaveBeenCalledTimes(3) - }) -}) diff --git a/packages/e2b/fs-e2b/tsconfig.json b/packages/e2b/fs-e2b/tsconfig.json deleted file mode 100644 index e8d52a194c..0000000000 --- a/packages/e2b/fs-e2b/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "lib/types" - }, - "include": ["src"], - "references": [ - { - "path": "../../../vendor/cosmokit" - }, - { - "path": "../../../vendor/cordis" - }, - { - "path": "../e2b" - }, - { - "path": "../../fs/fs" - } - ] -} diff --git a/packages/e2b/subprocess-e2b/README.md b/packages/e2b/subprocess-e2b/README.md deleted file mode 100644 index 35d20ace11..0000000000 --- a/packages/e2b/subprocess-e2b/README.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -description: "Shell commands and terminals inside the shared remote sandbox: what the agent can run there, how output is handled, and what to expect — for deployments and maintainers of the E2B family." -kind: "package-reference" ---- - -# @deepseek-ai/dsh-subprocess-e2b - -English | [中文](README.zh.md) - -## Summary - -`dsh-subprocess-e2b` runs the agent's shell commands and interactive terminals inside an E2B remote sandbox instead of the host. Existing command, terminal, and language-server workflows continue without E2B-specific tools. Host environment variables and secrets are excluded; only explicitly requested environment entries enter the sandbox. Use it with `dsh-e2b` and `dsh-fs-e2b` so commands, terminals, and files share one sandbox. Remote execution adds latency because each command requires asynchronous setup. - -## Table of Contents - -- [Use this package](#use-this-package) -- [Understand the implementation](#understand-the-implementation) -- [Further Exploration](#further-exploration) -- [Model Experience](#model-experience) -- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) -- [Dev Note](#dev-note) - ------ - - -## Use this package - -Use this package when the agent's shell commands and terminals should run inside the remote sandbox rather than on your machine. It is the command half of the E2B family: commands, terminals, and files share one remote world. - -### When to choose it - -Choose it when a composition already uses the E2B sandbox and you want commands and terminals to run there. Choose the local subprocess package for host execution. - -### Configuration - -The only setting is how often the package checks a running command's status; the default suits most deployments, and raising it reduces remote requests at the cost of slightly slower exit detection. - -| Field | Default | Meaning | -|---|---|---| -| `pollMs` | `20` | How often the package checks a running command's status, in milliseconds | - -The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-subprocess-e2b) is the exhaustive source for every accepted field and its JSDoc. - -### Running commands - -The agent can run a command in the sandbox with a working directory and environment, choose how its output is delivered (streamed live, captured up to a size cap, or routed to the app's own output), and stop it if it hangs — a stop first asks the command to exit politely, then force-kills it after a short grace, so a stuck command cannot leak. Very large output can be saved to a file in the sandbox so the agent can read it later. The command's exit code is reported normally; if the sandbox vanishes while a command runs, the command is treated as ended rather than erroring. - -### Using terminals - -The agent can open an interactive terminal in the sandbox, send input, read output, and signal programs running in it — prompts, interactive tools, and full-screen programs behave as they do locally. Terminal features like scrollback and readiness detection are provided by the terminal tooling, which works unchanged. - -### Keeping the environment clean - -Commands run with a clean, sandbox-native environment: host variables and values that look like credentials are not passed in implicitly, and only entries the agent explicitly requests are set. This keeps secrets out of the sandbox. - -### If the sandbox disappears - -The sandbox is ephemeral: if it is deleted while commands or terminals are running — through expiry, shutdown, or removal elsewhere — the affected commands are treated as ended cleanly. Do not rely on work surviving the sandbox. - -The default sandbox image ships with the runtime and utilities command work needs: `node`, `bash`, `setsid`, `ps`, `awk`, `tr`, `env`, `base64`, `chmod`, `tee`, `head`, `rm`, `kill`, `id`, and `getent`. - ------ - - -## Understand the implementation - -
-Implementation internals — click to expand - -This section explains the design decisions behind the provider and points at the code that realizes them; the observable behavior is fully covered in [Use this package](#use-this-package). - -### Design philosophy - -- **Provider-private remote identity.** The synchronous seam never blocks on the network. Private wrapper files asynchronously publish a process-group identity for stdin, observation, termination, and quiescence checks, together with the direct exit code and spill validity; that identity is not the requested target PID. -- **One teardown ladder.** Termination, rollback, and disposal share one process-group signal path — `SIGTERM`, then `SIGKILL` plus the SDK kill fallback — and treat proven quiescence as final. -- **Environment is explicit.** Nothing from the host and nothing credential-shaped enters the sandbox implicitly; every ambient value is scrubbed and every `spec.env` entry is an explicit opt-in. - -### Source map - -| File | Role | -|---|---| -| [`src/index.ts`](src/index.ts) | Plugin entry: `E2BSubprocessRuntime`, `Config`, spawn and spawnTerminal, disposal | -| [`src/process.ts`](src/process.ts) | `E2BSubprocessHandle`: remote wrapper, publication, termination, output projection | -| [`src/terminal.ts`](src/terminal.ts) | `E2BTerminalHandle`: PTY allocation, session teardown | -| [`src/environment.ts`](src/environment.ts) | Remote environment probe, scrubbing, serialization | -| [`src/output.ts`](src/output.ts) | Base64 decoder and bounded output readers | -| [`src/remote.ts`](src/remote.ts) | Shared control-shell helpers: option shaping, poll ticks, group signalling | -| — | No runtime invariant companion is published; live remote handles are private state owned by teardown logic, and the E2B command event stream is the sole outcome authority. | - -### Remote wrapper - -The bootstrap resolves its own tools from the sandbox PATH, refuses any missing or non-executable path, execs through `env -i` and `setsid --wait`, publishes the process-group id and exit code to private files beneath `ctx.e2b.runtimeRoot/processes`, and redirects stdout and stderr through base64 encoders that emit a reserved completion frame; `tee` and `head -c` bound optional spill files. - -### Private process identity and publication - -The synchronous seam returns a handle immediately while the command starts asynchronously. The wrapper publishes a private process-group ID for stdin, observation, termination, and quiescence checks, but that ID is not the requested target PID. A startup signal aborts environment and private-state preparation before allocation; once allocation begins, cancellation waits for a provisional SDK handle it can clean. - -### Environment boundary - -One trusted control-shell probe resolves the sandbox user's login home from its passwd entry and transports the sandbox environment as base64 ASCII for one strict UTF-8 decode; the wrapper then removes ambient `DSH_*` and credential-shaped (`*KEY*`, `*SECRET*`, `*TOKEN*`) names and restores every valid `spec.env` entry as an explicit caller opt-in. Empty names, `=`, and NUL framing violations reject before launch; subsequent command and PTY login shells receive a fresh randomized root-level `HOME` plus empty overrides for every scrubbed ambient name before user profiles can run. Private environment files are removed after consumption. - -### Output handling - -The remote wrapper branches raw bytes into optional bounded spill files and frames each live chunk as newline-delimited base64 ASCII; the host restores bytes across arbitrary SDK callback boundaries. Pipe mode writes to host Node streams, inherit mode to the harness process streams, and collect mode retains a bounded host tail with offset reads. For collect or inherit output, an incomplete SDK stream is disconnected after `graceMs` with its partial spill withheld; natural raw-pipe completion awaits lossless transport and preserves backpressure. Batch and streaming stdin use the SDK handle. - -### Termination ladder - -Termination and rollback share one tolerant signal path (`signalRemoteGroups`), escalate `SIGTERM` to `SIGKILL` on grace expiry, use the SDK kill as a fallback, and prove quiescence with a bounded process-table probe before reporting success; zombie-only groups count as empty, and a `SandboxNotFoundError` is treated as quiescence. - -
- ------ - - -## Further Exploration - -Read these pages when the package-level contract is not enough. They move from the family composition to the subprocess seam surface and the consumers that render it. - -- [E2B provider family map](../README.md) — the sandbox owner and the three-package composition. -- [Subprocess subsystem](../../../docs/subsystems/subprocess.md) — the subprocess seam contract and the generated Cordis surface. -- [Subprocess seam package](../../subprocess/subprocess/README.md) — the abstract contract this provider implements. -- [Bash executor](../../shell/bash-local/README.md) — the consumer that renders spawned commands to the model. -- [PTY terminal backend](../../terminal/terminal-bash/README.md) — the consumer that renders terminal sessions. -- [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-subprocess-e2b) — every accepted config field and its source declaration. - ------ - - -## Model Experience - -Indirectly, through consumer seams such as the bash executor family, which render remote output, exit facts, background deltas, and spill paths. - -#### KV Cache effect - -No direct invalidation: the consumer seams own any request-prefix changes; this backend's transport never reaches a request. - -## Known Limitations and Deferred Work - - - - -These limits define when the provider is a poor fit or needs special operational care. They are current package constraints, not a task backlog. - -- **The SDK still retains complete command output in host memory** — E2B `CommandHandle.stdout` and `.stderr` accumulate the base64 transport even when this adapter exposes bounded raw-byte tails, so the subprocess seam's normal host-memory bound is not achieved and transport retention is larger than the source stream. -- **Private state lives for the sandbox lifetime** — process directories and valid spill files remain under `.dsh-e2b` until the owner deletes the sandbox; this POC supplies no in-sandbox sweep. -- **Control state shares the sandbox user's UID** — E2B runs every command as the same default user, so `0700`/`0600` modes cannot isolate `.dsh-e2b` control files from concurrently running sandbox processes; real isolation needs an E2B per-command user or an out-of-band control channel. -- **Numeric process identities are not reuse-fenced** — E2B exposes numeric PID/PGID input, signalling, and cleanup operations but no atomic identity-bound alternative; replacement is deferred until E2B adds an identity primitive or a failure demonstrates a narrower protocol. -- **The initial environment probe inherits sandbox defaults** — E2B merges command overrides with default environment entries, so the probe cannot blank unknown credential-shaped names before enumerating them; this POC therefore does not support secrets in sandbox-default environment variables. -- **E2B exposes no signal fact** — an adapter-requested `SIGTERM` or `SIGKILL` is reported only when no wrapper-published direct exit code wins; every unrequested SDK exit remains an exit code, including values equal to `128 + signal`. -- **Exact terminal stdin-wait inspection is unavailable** — E2B exposes the foreground process group but not the syscall evidence needed to prove it is waiting on fd 0, so the generic PTY backend falls back to controlled prompt markers and bounded silence. -- **Linux utility and E2B transport semantics are assumed** — there is no Windows, escaped-session recovery, or network-partition fidelity layer. - - -### Dev Note - -
-Working context for maintainers — click to expand - -This Dev Note is working context for maintainers: open questions and directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above and the package code. - -#### Open: numeric process identities - -E2B exposes numeric PID/PGID input, signalling, and cleanup operations without an atomic identity-bound alternative. The adapter minimizes host round trips and defers a replacement until E2B adds an identity primitive or a failure demonstrates a narrower protocol (TODO(e2b-pgid-identity)). - -#### Open: replacement environments and status observation - -The initial environment probe inherits sandbox defaults because E2B merges command overrides, and collect/inherit command status needs control-plane polling because E2B cannot observe direct-command exit independently of descendant-held output. Both close only with new E2B primitives (TODO(e2b-replace-environment), TODO(e2b-status-watch)). - -
diff --git a/packages/e2b/subprocess-e2b/README.zh.md b/packages/e2b/subprocess-e2b/README.zh.md deleted file mode 100644 index f2c244fd50..0000000000 --- a/packages/e2b/subprocess-e2b/README.zh.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -description: "共享远程沙箱内的 shell 命令与终端:agent(智能体)可以在那里运行什么、输出如何处理,以及可以期待什么——面向 E2B 家族的部署方与维护者。" -kind: "package-reference" ---- - -# @deepseek-ai/dsh-subprocess-e2b - -[English](README.md) | 中文 - -## 概述 - -`dsh-subprocess-e2b` 让 agent 的 shell 命令与交互式终端在 E2B 远程沙箱而非宿主中运行。现有的命令、终端与语言服务器工作流无需 E2B 专用工具即可继续使用。宿主环境变量与密钥不会传入沙箱;只有显式请求的环境条目会进入沙箱。请与 `dsh-e2b`、`dsh-fs-e2b` 一起使用,让命令、终端与文件共享同一个沙箱。远程执行会增加延迟,因为每条命令都需要异步初始化。 - -## 目录 - -- [使用本包](#use-this-package) -- [理解实现](#understand-the-implementation) -- [进一步探索](#further-exploration) -- [模型体验](#model-experience) -- [已知限制与延期工作](#known-limitations-and-deferred-work) -- [开发备注](#dev-note) - ------ - - -## 使用本包 - -当 agent 的 shell 命令与终端应在远程沙箱内而非你的机器上运行时,使用本包。它负责 E2B 家族中的命令执行部分:命令、终端与文件共享同一个远程执行环境。 - -### 何时选择 - -当组合已经使用 E2B 沙箱且希望命令与终端在其中运行时,选择本包。宿主执行请选择本地子进程包。 - -### 配置 - -唯一设置是包检查运行中命令状态的频率;默认值适合大多数部署,调大它可以减少远程请求,代价是退出检测略慢。 - -| 字段 | 默认值 | 含义 | -|---|---|---| -| `pollMs` | `20` | 包检查运行中命令状态的频率(毫秒) | - -生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-subprocess-e2b)是每个受支持字段及其 JSDoc 的穷尽式真源。 - -### 运行命令 - -agent 可以在沙箱中按指定的工作目录与环境运行命令,选择输出的交付方式(实时流式、在大小上限内捕获,或路由到应用自身的输出),并在命令卡住时停止它——停止会先礼貌地请求命令退出,短暂宽限期后再强制终止,因此卡住的命令不会残留。非常大的输出可以保存到沙箱中的文件里,供 agent 稍后读取。命令的退出码会正常报告;如果命令运行期间沙箱消失,该命令会被视为已结束而不是报错。 - -### 使用终端 - -agent 可以在沙箱中打开交互式终端、发送输入、读取输出,并向其中运行的程序发送信号——提示符、交互式工具与全屏程序的行为与本地完全一致。scrollback 与就绪检测等终端功能由终端工具提供,无需改动即可工作。 - -### 保持环境干净 - -命令在干净、沙箱原生的环境中运行:宿主变量与形似凭据的值不会被隐式传入,只有 agent 显式请求的条目才会被设置。这使密钥不会进入沙箱。 - -### 如果沙箱消失 - -沙箱是短暂的:如果命令或终端运行期间沙箱被删除——无论是到期、关闭还是被别处移除——受影响的命令会被视为干净地结束。不要指望任何工作在沙箱删除后继续存在。 - -默认沙箱镜像自带命令工作所需的运行时与工具:`node`、`bash`、`setsid`、`ps`、`awk`、`tr`、`env`、`base64`、`chmod`、`tee`、`head`、`rm`、`kill`、`id` 与 `getent`。 - ------ - - -## 理解实现 - -
-实现细节——点击展开 - -本节解释提供方背后的设计决策,并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。 - -### 设计理念 - -- **提供方私有的远程身份。** 同步 seam 从不阻塞在网络请求上。包装层的私有文件会异步发布进程组身份,供 stdin、观察、终止与完全停稳检查使用,同时发布直接退出码与 spill 有效性;该身份不是请求目标的 PID。 -- **单一终止阶梯。** 终止、回滚与资源释放共享同一条进程组信号路径——先 `SIGTERM`,再 `SIGKILL` 加 SDK kill 回退——并把已证明的完全停稳视为最终状态。 -- **环境必须显式。** 宿主内容与形似凭据的内容都不会隐式进入沙箱;每个环境值都会被清理,每个 `spec.env` 条目都是显式选择。 - -### 源码地图 - -| 文件 | 职责 | -|---|---| -| [`src/index.ts`](src/index.ts) | 插件入口:`E2BSubprocessRuntime`、`Config`、spawn 与 spawnTerminal、资源释放 | -| [`src/process.ts`](src/process.ts) | `E2BSubprocessHandle`:远程包装层、发布、终止、输出投影 | -| [`src/terminal.ts`](src/terminal.ts) | `E2BTerminalHandle`:PTY 分配、会话拆除 | -| [`src/environment.ts`](src/environment.ts) | 远程环境探测、清理、序列化 | -| [`src/output.ts`](src/output.ts) | base64 解码器与有界输出读取器 | -| [`src/remote.ts`](src/remote.ts) | 共享控制 shell 辅助:选项构造、轮询 tick、进程组信号 | -| — | 不发布运行时不变式伴生入口;存活的远程句柄是拆除逻辑持有的私有状态,E2B 命令事件流是判定结果的唯一权威来源。 | - -### 远程包装层 - -引导脚本会从沙箱 PATH 解析自身所需的工具,拒绝任何缺失或不可执行的路径,通过 `env -i` 与 `setsid --wait` 执行 exec,把进程组 ID 与退出码发布到 `ctx.e2b.runtimeRoot/processes` 下的私有文件,并把 stdout 与 stderr 重定向到带保留完成帧的 base64 编码器;`tee` 与 `head -c` 约束可选 spill 文件的大小。 - -### 私有进程身份与发布 - -同步 seam 会立即返回句柄,同时命令异步启动。包装层会发布私有进程组 ID,供 stdin、观察、终止与完全停稳检查使用,但该 ID 不是请求目标的 PID。启动信号会在分配前中止环境与私有状态准备;分配开始后,取消会等待可清理的临时 SDK 句柄。 - -### 环境边界 - -一次受信任的控制 shell 探测会从 passwd 条目解析沙箱用户的登录主目录,以 base64 ASCII 传输沙箱环境,再进行一次严格 UTF-8 解码;随后包装层移除环境中的 `DSH_*` 与形似凭据的名称(`*KEY*`、`*SECRET*`、`*TOKEN*`),并把每个有效的 `spec.env` 条目恢复为调用方显式选择。空名称、`=` 与违反 NUL 分帧规则的条目会在启动前被拒绝;在用户 profile 脚本运行前,此后的命令与 PTY 登录 shell 会获得位于根目录下、全新随机生成的 `HOME`,并为每个被清理的环境变量名设置空值覆盖。私有环境文件在使用后会被删除。 - -### 输出处理 - -远程包装层先把原始字节分流到可选的有界 spill 文件,再把每个实时分片编码为换行分隔的 base64 ASCII 帧;宿主会跨任意 SDK 回调边界增量恢复字节。pipe 模式把字节写入宿主 Node 流,inherit 模式写入 harness 进程流,collect 模式保留有界的宿主尾部并支持偏移读取。对于 collect 或 inherit 输出,超过 `graceMs` 后适配器会断开未完成的 SDK 流并扣留其不完整的 spill;原始 pipe 自然完成时则会等待无损传输并保留背压。批量与流式 stdin 都使用 SDK 句柄。 - -### 终止阶梯 - -终止与回滚共享同一条容错信号路径(`signalRemoteGroups`),在宽限期满时从 `SIGTERM` 升级到 `SIGKILL`,以 SDK kill 作为回退,并在报告成功前用有界进程表探测证明完全停稳;仅含僵尸进程的进程组视为空,`SandboxNotFoundError` 视为完全停稳。 - -
- ------ - - -## 进一步探索 - -当包级约定不够用时阅读以下页面。它们从家族组合逐步进入子进程 seam 表面,以及渲染它的消费方。 - -- [E2B 提供方家族地图](../README.zh.md)——沙箱所有者与三包组合。 -- [子进程子系统](../../../docs/subsystems/subprocess.zh.md)——子进程 seam 约定与生成的 Cordis 表面。 -- [子进程 seam 包](../../subprocess/subprocess/README.zh.md)——本提供方实现的抽象约定。 -- [Bash 执行器](../../shell/bash-local/README.zh.md)——向模型渲染所启动命令的消费方。 -- [PTY 终端后端](../../terminal/terminal-bash/README.zh.md)——渲染终端会话的消费方。 -- [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-subprocess-e2b)——每个受支持配置字段及其源声明。 - ------ - - -## 模型体验 - -通过消费方 seam 间接影响模型,例如 bash 执行器家族;它们渲染远程输出、退出事实、后台增量与 spill 路径。 - -#### KV Cache 影响 - -不会直接失效:请求前缀变更由消费方 seam 负责;本后端的传输永远不会进入请求。 - -## 已知限制与延期工作 - - - - -这些限制说明本提供方何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。 - -- **SDK 仍会在宿主内存中保留完整命令输出**:即使本适配器公开的是有界原始字节尾部,E2B `CommandHandle.stdout` 与 `.stderr` 仍会累积 base64 传输内容,因此无法达到子进程 seam 通常提供的宿主内存边界,而且传输保留量大于源数据流。 -- **私有状态随沙箱生命周期存在**:进程目录与有效的 spill 文件会留在 `.dsh-e2b` 下,直到所有者删除沙箱;本 POC 不提供沙箱内清理。 -- **控制状态与沙箱用户同 UID**:E2B 以同一默认用户运行每条命令,因此 `0700`/`0600` 权限无法把 `.dsh-e2b` 控制文件与并发运行的沙箱进程隔离开;真正的隔离需要 E2B 提供按命令用户或带外控制通道。 -- **数值进程身份没有复用围栏**:E2B 公开基于数值 PID/PGID 的输入、信号发送与清理操作,却没有与身份原子绑定的替代方案;在 E2B 新增身份原语,或实际故障证明需要更窄的协议之前,替代方案继续延后。 -- **初始环境探测会继承沙箱默认值**:E2B 会把命令覆盖与默认环境条目合并,因此探测无法在枚举未知且形似凭据的名称之前将它们置空;因此,该 POC 不支持把 secret 放入沙箱默认环境变量。 -- **E2B 不公开信号事实**:适配器请求的 `SIGTERM` 或 `SIGKILL` 只有在包装层发布的直接退出码没有胜出时才报告为信号;其他未请求的 SDK 退出始终保留为退出码,包括等于 `128 + signal` 的值。 -- **无法精确检查终端 stdin 等待状态**:E2B 会公开前台进程组,但不提供证明其正在等待 fd 0 所需的 syscall 证据,因此通用 PTY 后端会回退到受控提示符标记与有界静默机制。 -- **依赖 Linux 工具与 E2B 传输语义**:没有 Windows、逃逸会话恢复或网络分区的保真层。 - - -### 开发备注 - -
-维护者的工作上下文——点击展开 - -本开发备注是维护者的工作上下文:开放问题与尚未决定的探索方向。它明确不具权威性——已交付的行为、限制与既定理由以上文和包代码为准。 - -#### 开放:数值进程身份 - -E2B 公开基于数值 PID/PGID 的输入、信号发送与清理操作,却没有与身份原子绑定的替代方案。适配器会尽量减少宿主往返,并在 E2B 新增身份原语或实际故障证明需要更窄的协议之前,继续延后替代方案(TODO(e2b-pgid-identity))。 - -#### 开放:替换环境与状态观察 - -由于 E2B 会合并命令覆盖,初始环境探测会继承沙箱默认值;又因为 E2B 无法独立于后代持有的输出观察直接命令的退出,collect/inherit 命令状态需要控制面轮询。两者都只能靠 E2B 的新原语来弥合(TODO(e2b-replace-environment)、TODO(e2b-status-watch))。 - -
diff --git a/packages/e2b/subprocess-e2b/package.json b/packages/e2b/subprocess-e2b/package.json deleted file mode 100644 index f5824b362c..0000000000 --- a/packages/e2b/subprocess-e2b/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@deepseek-ai/dsh-subprocess-e2b", - "description": "E2B subprocess implementation for DeepSeek Harness", - "version": "0.1.5-rc.2", - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", - "directory": "packages/e2b/subprocess-e2b" - }, - "type": "module", - "main": "lib/index.js", - "types": "lib/types/index.d.ts", - "exports": { - ".": { - "types": "./lib/types/index.d.ts", - "default": "./lib/index.js" - }, - "./src/*": "./src/*", - "./package.json": "./package.json" - }, - "files": [ - "lib/index.js", - "lib/types/**/*.d.ts" - ], - "license": "MIT", - "peerDependencies": { - "@deepseek-ai/dsh-e2b": "workspace:^", - "@deepseek-ai/dsh-subprocess": "workspace:^", - "@deepseek-ai/dsh-timeout": "workspace:^", - "@deepseek-ai/cordis": "workspace:^" - }, - "dependencies": { - "@deepseek-ai/schemastery": "workspace:^" - }, - "devDependencies": { - "@deepseek-ai/dsh-e2b": "workspace:^", - "@deepseek-ai/dsh-subprocess": "workspace:^", - "@deepseek-ai/dsh-timeout": "workspace:^", - "@deepseek-ai/cordis": "workspace:^" - } -} diff --git a/packages/e2b/subprocess-e2b/src/environment.ts b/packages/e2b/subprocess-e2b/src/environment.ts deleted file mode 100644 index 7cdb1b7fbf..0000000000 --- a/packages/e2b/subprocess-e2b/src/environment.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** Shared remote-environment scrubbing for E2B process and terminal launchers. */ - -import { Buffer } from 'node:buffer' -import { posix } from 'node:path' -import { e2bControlEnvs } from '@deepseek-ai/dsh-e2b' -import type { Sandbox } from '@deepseek-ai/dsh-e2b' -import { SENSITIVE_ENV_PATTERN } from '@deepseek-ai/dsh-subprocess' - -const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/ - -function remoteEnvironmentEntries(raw: string): Array { - const entries: Array = [] - for (const entry of raw.split('\0')) { - if (entry.length === 0) continue - const separator = entry.indexOf('=') - if (separator <= 0) continue - entries.push([entry.slice(0, separator), entry.slice(separator + 1)]) - } - return entries -} - -/** - * Read the remote environment through ASCII base64 so SDK callback chunking cannot corrupt UTF-8. - * @param sandbox - shared E2B execution world. - * @param signal - optional cancellation for the control-plane request. - * @returns the complete NUL-delimited UTF-8 environment. - */ -export async function readRemoteEnvironment(sandbox: Sandbox, signal?: AbortSignal): Promise { - // TODO(e2b-replace-environment): Remove this ambient probe when E2B can start - // a command with a replacement environment instead of merged overrides. - const result = await sandbox.commands.run( - 'set -o pipefail; dsh_e2b_passwd="$(getent passwd "$(id -u)")"; IFS=: read -r _ _ _ _ _ dsh_e2b_home _ <<<"$dsh_e2b_passwd"; test -n "$dsh_e2b_home" -a -d "$dsh_e2b_home"; printf \'%s\' "$dsh_e2b_home" | base64 -w 0; printf \'\\n\'; env -0 | base64 -w 0', - { envs: e2bControlEnvs(), ...(signal === undefined ? {} : { signal }) }, - ) - const lines = result.stdout.trim().split('\n') - if (lines.length !== 2 || !lines.every(line => BASE64.test(line))) { - throw new Error('subprocess-e2b: remote environment transport returned invalid base64') - } - const [encodedHome, encodedEnvironment] = lines as [string, string] - let home: string - let raw: string - try { - const decoder = new TextDecoder('utf-8', { fatal: true }) - home = decoder.decode(Buffer.from(encodedHome, 'base64')) - raw = decoder.decode(Buffer.from(encodedEnvironment, 'base64')) - } catch (error: unknown) { - throw new Error('subprocess-e2b: remote environment is not valid UTF-8', { cause: error }) - } - if (!posix.isAbsolute(home) || home.includes('\0')) { - throw new Error(`subprocess-e2b: remote login home is invalid: ${JSON.stringify(home)}`) - } - const environment = new Map(remoteEnvironmentEntries(raw)) - environment.set('HOME', home) - return [...environment].map(([name, value]) => `${name}=${value}\0`).join('') -} - -/** - * Parse an E2B NUL-delimited environment while removing harness-private and credential-shaped names. - * @param raw - The complete NUL-delimited remote environment. - * @returns Mutable retained entries for the caller to overlay and serialize. - */ -export function scrubRemoteEnvironment(raw: string): Map { - const environment = new Map() - for (const [name, value] of remoteEnvironmentEntries(raw)) { - if (name.startsWith('DSH_') || SENSITIVE_ENV_PATTERN.test(name)) continue - environment.set(name, value) - } - return environment -} - -/** - * Isolate E2B's fixed login-shell bootstrap from user profiles and ambient credentials. - * @param raw - The complete NUL-delimited remote environment. - * @returns Explicit E2B command or PTY overrides for bootstrap-shell startup. - */ -export function bootstrapEnvironment(raw: string): Record { - const environment: Record = { TERM: 'dumb' } - for (const [name] of remoteEnvironmentEntries(raw)) { - if (name.startsWith('DSH_') || SENSITIVE_ENV_PATTERN.test(name)) environment[name] = '' - } - return environment -} - -/** - * Overlay explicit entries and serialize one validated E2B environment. - * @param raw - The complete NUL-delimited remote environment. - * @param explicit - Deliberate caller overrides applied after ambient scrubbing; an `undefined` tombstone removes an ambient entry. - * @returns NUL-delimited `name=value` entries accepted by `env -i`. - */ -export function serializeRemoteEnvironment( - raw: string, - explicit: Readonly | undefined, -): string { - const environment = scrubRemoteEnvironment(raw) - for (const [name, value] of Object.entries(explicit ?? {})) { - if (name.length === 0 || name.includes('=') || name.includes('\0') || value?.includes('\0') === true) { - throw new Error('subprocess-e2b: environment entries require non-empty NUL-free names without = and NUL-free values') - } - // An explicit undefined is the seam's tombstone: remove the ambient entry. - if (value === undefined) environment.delete(name) - else environment.set(name, value) - } - return [...environment].map(([name, value]) => `${name}=${value}\0`).join('') -} diff --git a/packages/e2b/subprocess-e2b/src/index.ts b/packages/e2b/subprocess-e2b/src/index.ts deleted file mode 100644 index 62a8802d79..0000000000 --- a/packages/e2b/subprocess-e2b/src/index.ts +++ /dev/null @@ -1,231 +0,0 @@ -/** - * E2B Service Provider for the subprocess capability seam. Each handle starts through the - * shared sandbox and retains command output/status paths in that remote world. - * @module @deepseek-ai/dsh-subprocess-e2b - */ - -import { randomUUID } from 'node:crypto' -import { posix } from 'node:path' -import { inspect } from 'node:util' -import { Context } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -import { SubprocessRuntime } from '@deepseek-ai/dsh-subprocess' -import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout' -import type { - SubprocessHandle, - SubprocessSpawnSpec, - SubprocessTerminalHandle, - SubprocessTerminalSpawnSpec, -} from '@deepseek-ai/dsh-subprocess' -import { e2bControlEnvs, quoteE2BShellArg } from '@deepseek-ai/dsh-e2b' -import { E2BSubprocessHandle } from './process.ts' -import { asError, signalOpts } from './remote.ts' -import { spawnE2BTerminal } from './terminal.ts' - -/** Configuration for the E2B subprocess adapter. */ -export interface Config { - /** Remote status/liveness poll cadence in milliseconds; each tick is one control-plane request. */ - pollMs?: number -} - -interface SchemaResolvedConfig extends Config { - pollMs: number -} - -interface TerminalSetup { - done: Promise - controller: AbortController -} - -/** - * Enforce the seam's documented grace bound (positive, finite, one Node timer), - * matching subprocess-local's spawn-time check; an unbounded grace would make - * the remote force-escalation deadline unreachable. - * @param graceMs - The spec's cleanup grace in milliseconds. - */ -function requireRepresentableGrace(graceMs: number): void { - if (!Number.isFinite(graceMs) || graceMs <= 0 || graceMs > MAX_TIMER_DELAY_MS) { - throw new Error(`subprocess graceMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`) - } -} - -function validateNoNullByte(subject: string, value: string): void { - if (!value.includes('\0')) return - const error = new TypeError(`${subject} must be a string without null bytes. Received ${inspect(value)}`) - Object.assign(error, { code: 'ERR_INVALID_ARG_VALUE' }) - throw error -} - -/** E2B command manager registered as `ctx.subprocess`. */ -export class E2BSubprocessRuntime extends SubprocessRuntime { - static inject = ['e2b'] - - static Config: z = z.object({ - pollMs: z.number().default(20), - }) - - private readonly live = new Set() - private readonly terminals = new Set() - private readonly terminalSetups = new Set() - private readonly pollMs: number - private disposing = false - - /** Create the E2B subprocess service and bind its disposal policy. */ - constructor(ctx: Context, config: Config) { - super(ctx) - // Schemastery fills pollMs before construction; the type does not encode that step. - const { pollMs } = config as SchemaResolvedConfig - if (!Number.isSafeInteger(pollMs) || pollMs <= 0) { - throw new Error('subprocess-e2b: pollMs must be a positive safe integer') - } - this.pollMs = pollMs - ctx.effect(() => async () => { - this.disposing = true - for (const setup of this.terminalSetups) { - setup.controller.abort(new Error('subprocess-e2b: service disposed during terminal setup')) - } - await Promise.all([...this.terminalSetups].map(setup => setup.done)) - const handles = [...this.live] - const terminals = [...this.terminals] - const pending: Promise[] = [] - for (const handle of handles) { - handle.terminate() - pending.push(handle.waitForExit().then(async () => { - await handle.done.catch(() => undefined) - this.live.delete(handle) - })) - } - for (const terminal of terminals) { - pending.push(terminal.terminate().then(() => { this.terminals.delete(terminal) })) - } - const outcomes = await Promise.allSettled(pending) - const failures = outcomes.flatMap(outcome => outcome.status === 'rejected' - ? [outcome.reason as unknown] - : []) - if (failures.length === 1) throw asError(failures[0]) - if (failures.length > 1) throw new AggregateError(failures, 'subprocess-e2b: teardown failed') - }, 'e2b subprocess teardown') - } - - /** @inheritdoc */ - async resolveExecutable( - command: string, - env?: Readonly>, - signal?: AbortSignal, - ): Promise { - if (command.length === 0) throw new Error('subprocess-e2b: executable name must be non-empty') - signal?.throwIfAborted() - const sandbox = await this.ctx.e2b.getSandbox() - if (posix.isAbsolute(command)) { - await sandbox.commands.run( - `test -f ${quoteE2BShellArg(command)} -a -x ${quoteE2BShellArg(command)}`, - { envs: e2bControlEnvs(), ...signalOpts(signal) }, - ) - signal?.throwIfAborted() - return command - } - if (command.includes('/')) { - throw new Error( - `subprocess-e2b: command ${JSON.stringify(command)} is a relative path; use an absolute path or a bare PATH name`, - ) - } - const path = env?.PATH - const prefix = path === undefined ? '' : `PATH=${quoteE2BShellArg(path)} ` - const result = await sandbox.commands.run( - `${prefix}command -v -- ${quoteE2BShellArg(command)}`, - { cwd: this.ctx.e2b.cwd, envs: e2bControlEnvs(), ...signalOpts(signal) }, - ) - signal?.throwIfAborted() - const executable = result.stdout.trim() - if (executable.includes('\n') || (!posix.isAbsolute(executable) && !executable.includes('/'))) { - throw new Error(`subprocess-e2b: executable ${JSON.stringify(command)} did not resolve to one absolute path`) - } - // A relative result comes from a relative PATH entry; the lookup ran with the shared cwd. - return posix.resolve(this.ctx.e2b.cwd, executable) - } - - /** @inheritdoc */ - spawn(spec: SubprocessSpawnSpec): SubprocessHandle { - if (this.disposing) throw new Error('subprocess-e2b: service is disposing') - const program = spec.argv[0] - if (program === undefined || program.length === 0) { - throw new Error('invalid argv: expected a non-empty program name at argv[0]') - } - requireRepresentableGrace(spec.graceMs) - if (spec.signal?.aborted === true) { - let reason = 'aborted' - try { - reason = String(spec.signal.reason ?? reason) - } catch { - // Arbitrary caller-owned reasons cannot escape the stable Error boundary. - } - throw new Error(`aborted before spawn: ${reason}`) - } - spec.argv.forEach((value, index) => { - validateNoNullByte(index === 0 ? "The argument 'file'" : `The argument 'args[${String(index - 1)}]'`, value) - }) - validateNoNullByte("The property 'options.cwd'", spec.cwd) - for (const [key, value] of Object.entries(spec.env ?? {})) { - if (value === undefined) continue - validateNoNullByte(`The property 'options.env['${key}']'`, key) - validateNoNullByte(`The property 'options.env['${key}']'`, value) - } - const stateDir = posix.join(this.ctx.e2b.runtimeRoot, 'processes', randomUUID()) - const handle = new E2BSubprocessHandle(this.ctx.e2b, spec, stateDir, this.pollMs) - this.live.add(handle) - const release = async (): Promise => { - await handle.waitForExit() - this.live.delete(handle) - } - void handle.done.then(release, release).catch((_automaticReleaseFailure: unknown) => { - // Retain the handle so service disposal can retry its cleanup transaction. - }) - return handle - } - - /** @inheritdoc */ - async spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise { - if (this.disposing) throw new Error('subprocess-e2b: service is disposing') - const program = spec.argv[0] - if (program === undefined || program.length === 0) { - throw new Error('subprocess-e2b: terminal argv must contain a program') - } - requireRepresentableGrace(spec.graceMs) - spec.signal?.throwIfAborted() - const stateDir = posix.join(this.ctx.e2b.runtimeRoot, 'terminals', randomUUID()) - const done = Promise.withResolvers() - const setup: TerminalSetup = { done: done.promise, controller: new AbortController() } - const setupSignal = spec.signal === undefined - ? setup.controller.signal - : AbortSignal.any([spec.signal, setup.controller.signal]) - this.terminalSetups.add(setup) - try { - const terminal = await spawnE2BTerminal( - this.ctx.e2b, - { ...spec, signal: setupSignal }, - stateDir, - this.pollMs, - ) - this.terminals.add(terminal) - // oxlint-disable-next-line typescript/no-unnecessary-condition -- Remote allocation yields to disposal. - if (this.disposing) { - await terminal.terminate() - this.terminals.delete(terminal) - throw new Error('subprocess-e2b: service disposed during terminal setup') - } - const release = async (): Promise => { - await terminal.terminate() - this.terminals.delete(terminal) - } - void terminal.done.then(release, release).catch((_automaticReleaseFailure: unknown) => { - // Retain the terminal so service disposal can retry its cleanup transaction. - }) - return terminal - } finally { - this.terminalSetups.delete(setup) - done.resolve() - } - } -} - -export default E2BSubprocessRuntime diff --git a/packages/e2b/subprocess-e2b/src/output.ts b/packages/e2b/subprocess-e2b/src/output.ts deleted file mode 100644 index ba1dc1a716..0000000000 --- a/packages/e2b/subprocess-e2b/src/output.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** Bounded host-side projection of a complete output file retained in E2B. */ - -import { Buffer } from 'node:buffer' -import type { SubprocessOutputRead, SubprocessOutputReader } from '@deepseek-ai/dsh-subprocess' - -const BASE64_TEXT = /^[A-Za-z0-9+/]+={0,2}$/u - -/** Reserved non-base64 frame proving that one remote encoder reached clean EOF. */ -export const E2B_OUTPUT_COMPLETE_FRAME = '!dsh-e2b-output-complete!' - -/** Incrementally decode newline-delimited base64 frames emitted by one remote encoder. */ -export class E2BBase64Decoder { - private pending = '' - private complete = false - - /** - * Decode every complete newline-delimited frame in one arbitrarily split SDK callback. - * @param text - ASCII base64 frames from E2B's decoded callback. - * @returns the complete raw bytes made available by this callback. - */ - push(text: string): Buffer { - if (text.length === 0) return Buffer.alloc(0) - this.pending += text - const decoded: Buffer[] = [] - for (;;) { - const boundary = this.pending.indexOf('\n') - if (boundary < 0) break - const frame = this.pending.slice(0, boundary) - this.pending = this.pending.slice(boundary + 1) - if (frame === E2B_OUTPUT_COMPLETE_FRAME) { - if (this.complete) throw new Error('subprocess-e2b: duplicate output transport completion') - this.complete = true - continue - } - if (this.complete) throw new Error('subprocess-e2b: output transport continued after completion') - if (!BASE64_TEXT.test(frame)) { - throw new Error('subprocess-e2b: invalid base64 output transport') - } - const bytes = Buffer.from(frame, 'base64') - if (bytes.toString('base64') !== frame) { - throw new Error('subprocess-e2b: invalid base64 output transport') - } - decoded.push(bytes) - } - return Buffer.concat(decoded) - } - - /** - * Validate clean encoder completion, or discard an interrupted trailing frame after requested termination. - * @param requireComplete - Whether natural completion requires the reserved EOF frame. - */ - finish(requireComplete = true): void { - if (!requireComplete) { - this.pending = '' - return - } - if (this.pending.length > 0) { - throw new Error('subprocess-e2b: truncated base64 output transport') - } - if (!this.complete) throw new Error('subprocess-e2b: incomplete output transport') - } -} - -/** Offset reader used for one collect-mode E2B stream. */ -export class E2BOutputReader implements SubprocessOutputReader { - private chunks: Buffer[] = [] - private retainedBytes = 0 - private totalBytes = 0 - private spillValid = true - - /** - * Create a bounded reader over one remote spill path. - * @param maxBytes - In-memory tail cap. - * @param maxSpillBytes - Maximum complete remote file size the caller accepts. - * @param spillPath - Remote full-output path. - */ - constructor( - private readonly maxBytes: number, - private readonly maxSpillBytes: number | undefined, - private readonly spillPath: string, - ) {} - - /** Total bytes observed from the SDK stream. */ - get size(): number { - return this.totalBytes - } - - /** Stop advertising a remote spill whose writer did not reach clean EOF. */ - invalidateSpill(): void { - this.spillValid = false - } - - /** - * Append one byte-faithful decoded transport event. - * @param bytes - Raw command bytes recovered from the ASCII SDK transport. - */ - push(bytes: Uint8Array): void { - if (bytes.length === 0) return - const chunk = Buffer.from(bytes) - this.totalBytes += chunk.length - this.chunks.push(chunk) - this.retainedBytes += chunk.length - while (this.retainedBytes > this.maxBytes) { - const head = this.chunks[0] as Buffer - const excess = this.retainedBytes - this.maxBytes - if (head.length <= excess) { - this.chunks.shift() - this.retainedBytes -= head.length - } else { - this.chunks[0] = head.subarray(excess) - this.retainedBytes -= excess - } - } - } - - /** @inheritdoc */ - readFrom(fromByte: number): SubprocessOutputRead { - const retained = Buffer.concat(this.chunks, this.retainedBytes) - const firstRetained = this.totalBytes - this.retainedBytes - const lossy = fromByte < firstRetained - const start = lossy ? 0 : Math.min(retained.length, Math.max(0, fromByte - firstRetained)) - return { - text: retained.subarray(start).toString('utf8'), - nextOffset: this.totalBytes, - lossy, - ...(lossy && this.spillValid && this.maxSpillBytes !== undefined && this.totalBytes <= this.maxSpillBytes - ? { spillPath: this.spillPath } - : {}), - } - } -} diff --git a/packages/e2b/subprocess-e2b/src/process.ts b/packages/e2b/subprocess-e2b/src/process.ts deleted file mode 100644 index ce4e91c268..0000000000 --- a/packages/e2b/subprocess-e2b/src/process.ts +++ /dev/null @@ -1,693 +0,0 @@ -/** One asynchronously-started E2B command projected onto the subprocess seam. */ - -import { Buffer } from 'node:buffer' -import { PassThrough, Writable } from 'node:stream' -import { posix } from 'node:path' -import { - CommandExitError, - e2bControlEnvs, - FileNotFoundError, - SandboxNotFoundError, - quoteE2BShellArg, -} from '@deepseek-ai/dsh-e2b' -import type { CommandHandle, CommandResult, Sandbox } from '@deepseek-ai/dsh-e2b' -import type { - SubprocessCollect, - SubprocessHandle, - SubprocessOutcome, - SubprocessOutputMode, - SubprocessSpawnSpec, -} from '@deepseek-ai/dsh-subprocess' -import type E2BRuntime from '@deepseek-ai/dsh-e2b' -import { bootstrapEnvironment, readRemoteEnvironment, serializeRemoteEnvironment } from './environment.ts' -import { E2BBase64Decoder, E2B_OUTPUT_COMPLETE_FRAME, E2BOutputReader } from './output.ts' -import { asError, commandOpts, signalRemoteGroups, waitTick } from './remote.ts' - -const OUTPUT_ENCODER_SOURCE = [ - '(async () => {', - ' for await (const chunk of process.stdin) {', - " if (!process.stdout.write(chunk.toString('base64') + '\\n')) {", - " await new Promise(resolve => process.stdout.once('drain', resolve))", - ' }', - ' }', - ` if (!process.stdout.write(${JSON.stringify(E2B_OUTPUT_COMPLETE_FRAME)} + '\\n')) {`, - " await new Promise(resolve => process.stdout.once('drain', resolve))", - ' }', - '})().catch(() => { process.exitCode = 1 })', -].join('\n') - -function isCollect(mode: SubprocessOutputMode): mode is SubprocessCollect { - return mode !== 'pipe' && mode !== 'inherit' -} - -function hasSpill(mode: SubprocessOutputMode): mode is SubprocessCollect & { spill: { maxBytes: number } } { - return isCollect(mode) && mode.spill !== undefined -} - -function isValidProcessId(value: number): boolean { - return Number.isSafeInteger(value) && value > 0 -} - -class DeferredStdin extends Writable { - constructor(private readonly ready: Promise) { - super({ decodeStrings: false }) - } - - override _write(chunk: string | Buffer, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void { - void this.ready.then(handle => handle.sendStdin(chunk)).then( - () => { callback() }, - (error: unknown) => { callback(asError(error)) }, - ) - } - - override _final(callback: (error?: Error | null) => void): void { - void this.ready.then(handle => handle.closeStdin()).then( - () => { callback() }, - (error: unknown) => { callback(asError(error)) }, - ) - } -} - -interface RemotePaths { - pid: string - status: string - environment: string - stdout: string - stderr: string -} - -type CommandSettlement = - | { kind: 'result'; result: CommandResult } - | { kind: 'error'; error: unknown } - -function withinMs(settlement: Promise, timeoutMs: number): Promise { - return new Promise((resolve) => { - const timer = setTimeout(() => { resolve(undefined) }, timeoutMs) - void settlement.then((value) => { - clearTimeout(timer) - resolve(value) - }) - }) -} - -function commandText(spec: SubprocessSpawnSpec, paths: RemotePaths): string { - const encoder = `"$dsh_e2b_env_bin" -i "$dsh_e2b_node" -e ${quoteE2BShellArg(OUTPUT_ENCODER_SOURCE)}` - const stdoutRedirect = hasSpill(spec.stdio.stdout) - ? `> >("$dsh_e2b_tee" --output-error=warn-nopipe >("$dsh_e2b_head" -c ${spec.stdio.stdout.spill.maxBytes} > ${quoteE2BShellArg(paths.stdout)}) | ${encoder} 2>/dev/null)` - : `> >(${encoder} 2>/dev/null)` - const stderrRedirect = hasSpill(spec.stdio.stderr) - ? `2> >("$dsh_e2b_tee" --output-error=warn-nopipe >("$dsh_e2b_head" -c ${spec.stdio.stderr.spill.maxBytes} > ${quoteE2BShellArg(paths.stderr)}) | ${encoder} >&2 2>/dev/null)` - : `2> >(${encoder} >&2 2>/dev/null)` - const inner = [ - 'set +e', - 'dsh_e2b_env_bin=$1', - 'dsh_e2b_node=$2', - 'dsh_e2b_ps=$3', - 'dsh_e2b_tr=$4', - 'dsh_e2b_tee=$5', - 'dsh_e2b_head=$6', - 'dsh_e2b_rm=$7', - 'shift 7', - 'dsh_e2b_pgid="$("$dsh_e2b_ps" -o pgid= -p "$$" | "$dsh_e2b_tr" -d " ")"', - `printf '%s\\n' "$dsh_e2b_pgid" > ${quoteE2BShellArg(paths.pid)}`, - `mapfile -d '' -t dsh_e2b_env < ${quoteE2BShellArg(paths.environment)}`, - `"$dsh_e2b_rm" -f -- ${quoteE2BShellArg(paths.environment)}`, - `"$dsh_e2b_env_bin" -i -- "\${dsh_e2b_env[@]}" "$@" ${stdoutRedirect} ${stderrRedirect}`.trimEnd(), - 'dsh_e2b_status=$?', - `printf '%s\\n' "$dsh_e2b_status" > ${quoteE2BShellArg(paths.status)}`, - 'wait', - 'exit "$dsh_e2b_status"', - ].join('\n') - const argv = spec.argv.map(quoteE2BShellArg).join(' ') - const bootstrap = [ - `mapfile -d '' -t dsh_e2b_env < ${quoteE2BShellArg(paths.environment)}`, - 'dsh_e2b_env_bin="$(command -v env)"', - 'dsh_e2b_setsid="$(command -v setsid)"', - 'dsh_e2b_bash="$(command -v bash)"', - 'dsh_e2b_node="$(command -v node)"', - 'dsh_e2b_ps="$(command -v ps)"', - 'dsh_e2b_tr="$(command -v tr)"', - 'dsh_e2b_tee="$(command -v tee)"', - 'dsh_e2b_head="$(command -v head)"', - 'dsh_e2b_rm="$(command -v rm)"', - 'for dsh_e2b_tool in "$dsh_e2b_env_bin" "$dsh_e2b_setsid" "$dsh_e2b_bash" "$dsh_e2b_node" "$dsh_e2b_ps" "$dsh_e2b_tr" "$dsh_e2b_tee" "$dsh_e2b_head" "$dsh_e2b_rm"; do', - ' [[ "$dsh_e2b_tool" == /* && -x "$dsh_e2b_tool" ]] || exit 125', - 'done', - `exec "$dsh_e2b_env_bin" -i -- "\${dsh_e2b_env[@]}" "$dsh_e2b_setsid" --wait -- "$dsh_e2b_bash" -c ${quoteE2BShellArg(inner)} dsh-e2b "$dsh_e2b_env_bin" "$dsh_e2b_node" "$dsh_e2b_ps" "$dsh_e2b_tr" "$dsh_e2b_tee" "$dsh_e2b_head" "$dsh_e2b_rm" ${argv}`, - ].join('\n') - return bootstrap -} - -const WAIT_ABORTED = Symbol('wait aborted') - -function waitWithSignal(promise: Promise, signal: AbortSignal | undefined): Promise { - if (signal === undefined) return promise - if (signal.aborted) return Promise.resolve(WAIT_ABORTED) - return new Promise((resolve) => { - const onAbort = (): void => { cleanup(); resolve(WAIT_ABORTED) } - const cleanup = (): void => { signal.removeEventListener('abort', onAbort) } - signal.addEventListener('abort', onAbort, { once: true }) - if (signal.aborted) { - onAbort() - return - } - void promise.then((value) => { cleanup(); resolve(value) }) - }) -} - -/** E2B-backed subprocess handle with private remote process-group tracking. */ -export class E2BSubprocessHandle implements SubprocessHandle { - readonly stdin: Writable | undefined - readonly stdout: PassThrough | undefined - readonly stderr: PassThrough | undefined - readonly collected: SubprocessHandle['collected'] - readonly done: Promise - - private readonly commandState = Promise.withResolvers() - private readonly readyState = Promise.withResolvers() - private readonly stdoutDecoder = new E2BBase64Decoder() - private readonly stderrDecoder = new E2BBase64Decoder() - private readonly terminationController = new AbortController() - /** Releases output waits that survive the command outcome, so blocked SDK callbacks settle. */ - private readonly outputReleased = new AbortController() - private readonly stdoutReader: E2BOutputReader | undefined - private readonly stderrReader: E2BOutputReader | undefined - private readonly paths: RemotePaths - private controlEnvs: Record = {} - private remoteProcessGroupId: number | undefined - private outputTransportError: Error | undefined - private outputDrainExpired = false - private stateDirectoryCreated = false - private quiescenceProven = false - private terminationAttempt: Promise | undefined - private terminationFailure: Error | undefined - private terminationSignal: NodeJS.Signals | null = null - - /** - * Begin an E2B command without blocking the synchronous subprocess spawn call. - * @param runtime - Shared E2B sandbox owner. - * @param spec - Fully resolved subprocess request. - * @param stateDir - Remote directory retaining process identity, status, and valid spills. - * @param pollMs - Remote status/liveness poll cadence. - */ - constructor( - private readonly runtime: E2BRuntime, - private readonly spec: SubprocessSpawnSpec, - readonly stateDir: string, - private readonly pollMs: number, - ) { - this.paths = { - pid: posix.join(stateDir, 'pid'), - status: posix.join(stateDir, 'exit-code'), - environment: posix.join(stateDir, 'environment'), - stdout: posix.join(stateDir, 'stdout.log'), - stderr: posix.join(stateDir, 'stderr.log'), - } - const outMode = spec.stdio.stdout - const errMode = spec.stdio.stderr - this.stdout = outMode === 'pipe' ? new PassThrough() : undefined - this.stderr = errMode === 'pipe' ? new PassThrough() : undefined - this.stdoutReader = isCollect(outMode) - ? new E2BOutputReader(outMode.maxBytes, outMode.spill?.maxBytes, this.paths.stdout) - : undefined - this.stderrReader = isCollect(errMode) - ? new E2BOutputReader(errMode.maxBytes, errMode.spill?.maxBytes, this.paths.stderr) - : undefined - this.collected = { - ...(this.stdoutReader !== undefined ? { stdout: this.stdoutReader } : {}), - ...(this.stderrReader !== undefined ? { stderr: this.stderrReader } : {}), - } - this.stdin = spec.stdio.stdin === 'pipe' ? new DeferredStdin(this.readyState.promise) : undefined - void this.readyState.promise.catch(() => {}) - spec.signal?.addEventListener('abort', this.onAbort, { once: true }) - this.done = this.run() - void this.done.catch(() => {}) - if (spec.signal?.aborted === true) this.terminate() - } - - /** @inheritdoc */ - terminate(): void { - if (this.quiescenceProven || this.terminationAttempt !== undefined) return - this.terminationController.abort(new Error('subprocess-e2b: command terminated')) - this.stdout?.destroy() - this.stderr?.destroy() - this.terminationFailure = undefined - const attempt = this.terminateRemote() - this.terminationAttempt = attempt - void attempt.then( - () => { this.terminationAttempt = undefined }, - (error: unknown) => { - if (!this.quiescenceProven) this.terminationFailure = asError(error) - this.terminationAttempt = undefined - }, - ) - } - - /** @inheritdoc */ - async waitForExit(signal?: AbortSignal): Promise { - if (this.quiescenceProven) return true - let handle: CommandHandle | undefined - if (this.terminationController.signal.aborted) { - const observed = await waitWithSignal(this.commandState.promise, signal) - if (observed === WAIT_ABORTED) return false - handle = observed - if (handle === undefined) { - this.markQuiescent() - return true - } - if (this.remoteProcessGroupId === undefined) { - const attempt = this.terminationAttempt - if (attempt !== undefined && await waitWithSignal(attempt.catch(() => undefined), signal) === WAIT_ABORTED) { - return false - } - this.throwTerminationFailure() - // Successful pre-publication termination records quiescence; its only other outcome is the failure above. - return true - } - } else { - const observed = await waitWithSignal( - this.readyState.promise.catch(() => this.commandState.promise), - signal, - ) - if (observed === WAIT_ABORTED) return false - handle = observed - if (handle === undefined) { - this.markQuiescent() - return true - } - } - this.throwTerminationFailure() - let sandbox: Sandbox - try { - sandbox = await this.runtime.getSandbox() - } catch (error: unknown) { - if (signal?.aborted === true) return false - if (error instanceof SandboxNotFoundError) { - this.markQuiescent() - return true - } - throw error - } - const processGroupId = this.remoteProcessGroupId ?? handle.pid - while (await this.groupAlive(sandbox, processGroupId, signal)) { - this.throwTerminationFailure() - if (!await waitTick(this.pollMs, signal)) return false - } - this.throwTerminationFailure() - if (signal?.aborted === true) return false - this.markQuiescent() - return true - } - - private readonly onAbort = (): void => { this.terminate() } - - private markQuiescent(): void { - this.quiescenceProven = true - this.terminationFailure = undefined - } - - private async run(): Promise { - let sandbox: Sandbox | undefined - let preparing = true - try { - sandbox = await this.runtime.getSandbox() - await this.prepareState(sandbox) - preparing = false - const handle = await sandbox.commands.run( - commandText(this.spec, this.paths), - { - background: true, - cwd: this.spec.cwd, - envs: e2bControlEnvs(this.controlEnvs), - stdin: this.spec.stdio.stdin !== 'ignore', - timeoutMs: 0, - onStdout: async (data) => { await this.dispatchOutput('stdout', data) }, - onStderr: async (data) => { await this.dispatchOutput('stderr', data) }, - }, - ) - const completion = handle.wait() - void completion.catch(() => {}) - if (!isValidProcessId(handle.pid)) { - const invalidPid = new Error(`subprocess-e2b: E2B returned invalid command pid ${handle.pid}`) - try { - await handle.kill() - this.markQuiescent() - } catch (cleanupError: unknown) { - this.terminationFailure = asError(cleanupError) - this.commandState.resolve(handle) - throw new AggregateError( - [invalidPid, cleanupError], - 'subprocess-e2b: invalid command pid rollback did not reach quiescence', - ) - } - throw invalidPid - } - this.commandState.resolve(handle) - try { - this.remoteProcessGroupId = await this.waitForProcessGroupId(sandbox, completion) - } catch (error: unknown) { - try { - await this.rollbackUnpublishedGroup(sandbox, handle) - } catch (cleanupError: unknown) { - throw new AggregateError( - [error, cleanupError], - 'subprocess-e2b: process-group publication failed and rollback did not reach quiescence', - ) - } - throw error - } - this.readyState.resolve(handle) - await this.writeBatchStdin(handle) - const outcome = await this.waitForCommand(sandbox, handle, completion) - if (this.outputTransportError !== undefined) throw this.outputTransportError - const requireCompleteOutput = this.terminationSignal === null && !this.outputDrainExpired - this.stdoutDecoder.finish(requireCompleteOutput) - this.stderrDecoder.finish(requireCompleteOutput) - await this.finalizeSpills(sandbox) - return outcome - } catch (error: unknown) { - const canceledPreparation = preparing && this.terminationController.signal.aborted - let failure = await this.rollbackPublishedFailure(error) - if (sandbox !== undefined && this.stateDirectoryCreated) { - try { - await this.removeFailedState(sandbox) - } catch (cleanupError: unknown) { - failure = new AggregateError( - [failure, cleanupError], - 'subprocess-e2b: command failed and private state cleanup failed', - ) - } - } - this.commandState.resolve(undefined) - this.readyState.reject(failure) - if (canceledPreparation && failure === error) return { exitCode: null, signal: 'SIGTERM' } - throw failure - } finally { - this.spec.signal?.removeEventListener('abort', this.onAbort) - this.stdout?.end() - this.stderr?.end() - } - } - - private async prepareState(sandbox: Sandbox): Promise { - const signal = this.terminationController.signal - const ambient = await readRemoteEnvironment(sandbox, signal) - this.controlEnvs = bootstrapEnvironment(ambient) - // Own the directory before the request: a cancellation racing a committed - // creation must still enter cleanup (removal tolerates an absent path). - this.stateDirectoryCreated = true - await sandbox.files.makeDir(this.stateDir, { signal }) - await sandbox.commands.run( - `chmod 700 -- ${quoteE2BShellArg(this.stateDir)}`, - commandOpts(this.controlEnvs, signal), - ) - const files = [ - { path: this.paths.pid, data: '' }, - { path: this.paths.status, data: '' }, - { path: this.paths.environment, data: serializeRemoteEnvironment(ambient, this.spec.env) }, - ...(hasSpill(this.spec.stdio.stdout) ? [{ path: this.paths.stdout, data: '' }] : []), - ...(hasSpill(this.spec.stdio.stderr) ? [{ path: this.paths.stderr, data: '' }] : []), - ] - await sandbox.files.write(files, { signal }) - await sandbox.commands.run( - `chmod 600 -- ${files.map(file => quoteE2BShellArg(file.path)).join(' ')}`, - commandOpts(this.controlEnvs, signal), - ) - signal.throwIfAborted() - } - - private async writeBatchStdin(handle: CommandHandle): Promise { - if (typeof this.spec.stdio.stdin !== 'object') return - try { - await handle.sendStdin(this.spec.stdio.stdin.data) - await handle.closeStdin() - } catch (_processClosedItsInput) { - // Like the local adapter, batch stdin is best-effort; exit and output remain authoritative. - } - } - - private async dispatchOutput(stream: 'stdout' | 'stderr', data: string): Promise { - let bytes: Buffer - try { - bytes = stream === 'stdout' ? this.stdoutDecoder.push(data) : this.stderrDecoder.push(data) - } catch (error: unknown) { - this.outputTransportError ??= asError(error) - const target = stream === 'stdout' ? this.stdout : this.stderr - target?.destroy(this.outputTransportError) - return - } - try { - if (stream === 'stdout') { - this.stdoutReader?.push(bytes) - await this.writeOutput(this.stdout, this.spec.stdio.stdout === 'inherit' ? process.stdout : undefined, bytes) - return - } - this.stderrReader?.push(bytes) - await this.writeOutput(this.stderr, this.spec.stdio.stderr === 'inherit' ? process.stderr : undefined, bytes) - } catch (error: unknown) { - const target = stream === 'stdout' ? this.stdout : this.stderr - target?.destroy(asError(error)) - } - } - - private async writeOutput(pipe: PassThrough | undefined, inherited: NodeJS.WriteStream | undefined, data: Uint8Array): Promise { - const target = pipe ?? inherited - if (target === undefined || data.length === 0 || this.terminationController.signal.aborted) return - if (target.destroyed) throw new Error('subprocess output stream is closed') - if (target.write(data)) return - await new Promise((resolve, reject) => { - const onDrain = (): void => { cleanup(); resolve() } - const onClose = (): void => { cleanup(); resolve() } - const onRelease = (): void => { cleanup(); resolve() } - const onError = (error: Error): void => { cleanup(); reject(error) } - const cleanup = (): void => { - target.removeListener('drain', onDrain) - target.removeListener('close', onClose) - target.removeListener('error', onError) - this.terminationController.signal.removeEventListener('abort', onRelease) - this.outputReleased.signal.removeEventListener('abort', onRelease) - } - target.once('drain', onDrain) - target.once('close', onClose) - target.once('error', onError) - this.terminationController.signal.addEventListener('abort', onRelease, { once: true }) - this.outputReleased.signal.addEventListener('abort', onRelease, { once: true }) - if (this.terminationController.signal.aborted || this.outputReleased.signal.aborted) onRelease() - }) - } - - private async waitForProcessGroupId(sandbox: Sandbox, completion: Promise): Promise { - const commandSettled = completion.then( - () => true, - () => true, - ) - while (true) { - // TODO(e2b-publication-cancel): Join cancellation to the existing - // termination transaction before aborting an in-flight SDK file read. - const raw = await sandbox.files.read(this.paths.pid) - const value = raw.trim() - if (value.length > 0) { - const pid = Number(value) - if (!/^[1-9][0-9]*$/.test(value) || !Number.isSafeInteger(pid)) { - throw new Error(`subprocess-e2b: remote wrapper published invalid process-group id ${JSON.stringify(value)}`) - } - // A same-UID sandbox process can rewrite this file; refuse ids whose - // negative form addresses every process (`kill -- -1`) or init's group. - if (pid <= 1) { - throw new Error(`subprocess-e2b: unsafe published process-group id ${pid}`) - } - return pid - } - const settled = await Promise.race([commandSettled, waitTick(this.pollMs).then(() => false)]) - if (settled) throw new Error('subprocess-e2b: remote command exited before publishing its process-group id') - } - } - - private async waitForCommand( - sandbox: Sandbox, - handle: CommandHandle, - completion: Promise, - ): Promise { - const settlement = completion.then( - result => ({ kind: 'result', result }), - (error: unknown) => ({ kind: 'error', error }), - ) - const hasPipeOutput = this.spec.stdio.stdout === 'pipe' || this.spec.stdio.stderr === 'pipe' - let completed = hasPipeOutput ? await settlement : undefined - while (true) { - const rawStatus = (await sandbox.files.read(this.paths.status)).trim() - if (rawStatus.length > 0) { - const exitCode = Number(rawStatus) - if (!/^(?:0|[1-9][0-9]*)$/.test(rawStatus) || !Number.isSafeInteger(exitCode) || exitCode > 255) { - throw new Error(`subprocess-e2b: remote wrapper published invalid exit code ${JSON.stringify(rawStatus)}`) - } - if (completed !== undefined) return this.commandOutcome(completed, exitCode) - const drained = await withinMs(settlement, this.spec.graceMs) - if (drained !== undefined) return this.commandOutcome(drained, exitCode) - this.outputDrainExpired = true - this.stdoutReader?.invalidateSpill() - this.stderrReader?.invalidateSpill() - // Release inherited-output waits so a callback blocked on host - // backpressure cannot keep the disconnected SDK settlement pending. - this.outputReleased.abort(new Error('subprocess-e2b: output drain grace expired')) - await handle.disconnect() - return { exitCode, signal: null } - } - if (completed !== undefined) return this.commandOutcome(completed) - // TODO(e2b-status-watch): Replace collect/inherit control-plane polling - // when E2B can observe direct-command exit independently of descendant-held output. - completed = await Promise.race([settlement, waitTick(this.pollMs).then(() => undefined)]) - } - } - - private commandOutcome(settlement: CommandSettlement, publishedExitCode?: number): SubprocessOutcome { - if (settlement.kind === 'result') { - return { exitCode: publishedExitCode ?? settlement.result.exitCode, signal: null } - } - if (settlement.error instanceof CommandExitError) { - if (publishedExitCode !== undefined) return { exitCode: publishedExitCode, signal: null } - return this.terminationSignal === null - ? { exitCode: settlement.error.exitCode, signal: null } - : { exitCode: null, signal: this.terminationSignal } - } - throw settlement.error - } - - private async rollbackPublishedFailure(error: unknown): Promise { - if (this.remoteProcessGroupId === undefined || this.quiescenceProven) return error - this.terminate() - try { - await this.waitForExit() - return error - } catch (cleanupError: unknown) { - return new AggregateError( - [asError(error), asError(cleanupError)], - 'subprocess-e2b: command monitoring failed and process-group rollback did not reach quiescence', - ) - } - } - - private async rollbackUnpublishedGroup(sandbox: Sandbox, handle: CommandHandle): Promise { - // The bootstrap ends in an exec chain through the scrubbed environment and - // `setsid`, so E2B's command PID is the provisional group id even before the - // private publication file can be trusted. Kill that group before the SDK-PID - // fallback, then prove no group member survived before rejecting startup. - await this.forceKillGroup(sandbox, handle, handle.pid) - this.markQuiescent() - } - - private async terminateRemote(): Promise { - try { - await this.terminateRemoteInSandbox() - } catch (error: unknown) { - if (error instanceof SandboxNotFoundError) { - this.markQuiescent() - return - } - throw error - } - } - - private async terminateRemoteInSandbox(): Promise { - const handle = await this.commandState.promise - if (handle === undefined) { - this.markQuiescent() - return - } - if (!isValidProcessId(handle.pid) && this.remoteProcessGroupId === undefined) { - await handle.kill() - this.markQuiescent() - return - } - const sandbox = await this.runtime.getSandbox() - const processGroupId = this.remoteProcessGroupId ?? handle.pid - await this.terminateGroup(sandbox, handle, processGroupId) - } - - private async terminateGroup(sandbox: Sandbox, handle: CommandHandle, processGroupId: number): Promise { - this.terminationSignal = 'SIGTERM' - try { - await signalRemoteGroups(sandbox, this.controlEnvs, [processGroupId], 'TERM') - if (await this.waitForGroupExit(sandbox, processGroupId)) { - this.markQuiescent() - return - } - } catch (_gracefulTerminationFailure) { - // Failed TERM delivery or observation cannot prove exit; force cleanup still owns the group. - } - this.terminationSignal = 'SIGKILL' - await this.forceKillGroup(sandbox, handle, processGroupId) - this.markQuiescent() - } - - private async forceKillGroup(sandbox: Sandbox, handle: CommandHandle, processGroupId: number): Promise { - try { - await signalRemoteGroups(sandbox, this.controlEnvs, [processGroupId], 'KILL') - } catch (_processGroupKillFailure) { - // SDK kill and the final liveness probe remain independent cleanup paths. - } - try { - await handle.kill() - } catch (_sdkKillFailure) { - // The final liveness probe, not either transport's self-report, proves cleanup. - } - if (await this.waitForGroupExit(sandbox, processGroupId)) return - throw new Error(`subprocess-e2b: remote process group ${processGroupId} remained live after force termination`) - } - - private async waitForGroupExit(sandbox: Sandbox, processGroupId: number): Promise { - const deadline = Date.now() + this.spec.graceMs - while (await this.groupAlive(sandbox, processGroupId)) { - if (Date.now() >= deadline) return false - await waitTick(this.pollMs) - } - return true - } - - private throwTerminationFailure(): void { - if (this.terminationFailure !== undefined) throw this.terminationFailure - } - - private async groupAlive(sandbox: Sandbox, pid: number, signal?: AbortSignal): Promise { - const result = await sandbox.commands.run( - `set -o pipefail; ps -eo pgid=,stat= | awk '$1 == ${pid} && $2 !~ /^[ZXx]/ { live=1 } END { if (live) print "live" }'`, - commandOpts(this.controlEnvs, signal), - ).catch((error: unknown) => { - if (signal?.aborted === true) return undefined - if (error instanceof SandboxNotFoundError) return { exitCode: 0, stdout: '', stderr: '' } - throw error - }) - return result?.stdout.trim() === 'live' - } - - private async finalizeSpills(sandbox: Sandbox): Promise { - const removals: Promise[] = [] - const collect = (mode: SubprocessOutputMode, reader: E2BOutputReader | undefined, path: string): void => { - if (!hasSpill(mode)) return - // A spill mode is a collect mode, so construction always created its reader. - const size = (reader as E2BOutputReader).size - if (this.outputDrainExpired || size <= mode.maxBytes || size > mode.spill.maxBytes) { - removals.push(sandbox.files.remove(path).catch((_adapterPrivateSpillRemovalFailure: unknown) => { - // The command outcome is authoritative; owner teardown bounds private residue. - })) - } - } - collect(this.spec.stdio.stdout, this.stdoutReader, this.paths.stdout) - collect(this.spec.stdio.stderr, this.stderrReader, this.paths.stderr) - await Promise.all(removals) - } - - private async removeFailedState(sandbox: Sandbox): Promise { - const failures: Error[] = [] - for (const path of [this.paths.environment, this.stateDir]) { - try { - await sandbox.files.remove(path) - } catch (error: unknown) { - if (!(error instanceof FileNotFoundError)) failures.push(asError(error)) - } - } - if (failures.length > 0) { - throw new AggregateError(failures, 'subprocess-e2b: failed to remove private command state') - } - } -} diff --git a/packages/e2b/subprocess-e2b/src/remote.ts b/packages/e2b/subprocess-e2b/src/remote.ts deleted file mode 100644 index 1937f54bf0..0000000000 --- a/packages/e2b/subprocess-e2b/src/remote.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Shared remote-control helpers for the E2B subprocess adapter: SDK option - * shaping, poll ticks, and the one tolerant process-group signal used by both - * the ordinary-process and terminal teardown ladders. - */ - -import { CommandExitError, e2bControlEnvs, SandboxNotFoundError } from '@deepseek-ai/dsh-e2b' -import type { Sandbox } from '@deepseek-ai/dsh-e2b' - -/** - * Normalize an unknown rejection into an Error. - * @param error - Any thrown or rejected value. - * @returns The value itself when already an Error, else a stringified wrapper. - */ -export function asError(error: unknown): Error { - return error instanceof Error ? error : new Error(String(error)) -} - -/** - * Shape the optional-signal SDK options object. - * @param signal - Optional cancellation for one SDK request. - * @returns An options fragment that omits an undefined signal. - */ -export function signalOpts(signal: AbortSignal | undefined): { signal?: AbortSignal } { - return signal === undefined ? {} : { signal } -} - -/** - * Shape control-shell command options with the isolated HOME override. - * @param envs - Explicit environment entries for the control command. - * @param signal - Optional cancellation for the SDK request. - * @returns Options for `sandbox.commands.run` control invocations. - */ -export function commandOpts( - envs: Record, - signal?: AbortSignal, -): { envs: Record; signal?: AbortSignal } { - return { envs: e2bControlEnvs(envs), ...signalOpts(signal) } -} - -/** - * Resolve after one duration. - * @param ms - Milliseconds to wait. - * @returns Settles after the timeout. - */ -export function delay(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)) -} - -/** - * Wait one poll interval or until the signal aborts. - * @param pollMs - Poll cadence in milliseconds. - * @param signal - Optional abort that ends the wait early. - * @returns `true` after a full tick, `false` when aborted first. - */ -export function waitTick(pollMs: number, signal?: AbortSignal): Promise { - if (signal?.aborted === true) return Promise.resolve(false) - return new Promise((resolve) => { - const timer = setTimeout(() => { - signal?.removeEventListener('abort', onAbort) - resolve(true) - }, pollMs) - const onAbort = (): void => { - clearTimeout(timer) - resolve(false) - } - signal?.addEventListener('abort', onAbort, { once: true }) - }) -} - -/** - * Signal remote process groups, tolerating the shared teardown outcomes: a - * nonzero `kill` (groups already gone) and a disappeared sandbox. Both the - * pgid-keyed process ladder and the sid-keyed terminal ladder deliver signals - * through this single tolerance so they cannot drift apart. - * @param sandbox - Live SDK handle. - * @param envs - Control-shell environment entries. - * @param groups - Positive process-group ids to signal. - * @param signal - `TERM` or `KILL`. - */ -export async function signalRemoteGroups( - sandbox: Sandbox, - envs: Record, - groups: readonly number[], - signal: 'TERM' | 'KILL', -): Promise { - // TODO(e2b-pgid-identity): Prefer an atomic identity-bound group signal if E2B adds one; - // a userspace identity precheck cannot close the numeric-PGID reuse race. - try { - await sandbox.commands.run( - `kill -${signal} -- ${groups.map(group => `-${group}`).join(' ')}`, - commandOpts(envs), - ) - } catch (error: unknown) { - if (!(error instanceof CommandExitError) && !(error instanceof SandboxNotFoundError)) throw error - } -} diff --git a/packages/e2b/subprocess-e2b/src/terminal.ts b/packages/e2b/subprocess-e2b/src/terminal.ts deleted file mode 100644 index 29af8c29c5..0000000000 --- a/packages/e2b/subprocess-e2b/src/terminal.ts +++ /dev/null @@ -1,567 +0,0 @@ -/** E2B PTY allocation and process-session ownership for the subprocess seam. */ - -import { Buffer } from 'node:buffer' -import { randomUUID } from 'node:crypto' -import { PassThrough } from 'node:stream' -import { posix } from 'node:path' -import { - CommandExitError, - e2bControlEnvs, - FileNotFoundError, - SandboxNotFoundError, - quoteE2BShellArg, -} from '@deepseek-ai/dsh-e2b' -import type { CommandHandle, CommandResult, Sandbox } from '@deepseek-ai/dsh-e2b' -import type { - SubprocessOutcome, - SubprocessTerminalForeground, - SubprocessTerminalHandle, - SubprocessTerminalSignal, - SubprocessTerminalSpawnSpec, -} from '@deepseek-ai/dsh-subprocess' -import type E2BRuntime from '@deepseek-ai/dsh-e2b' -import { - bootstrapEnvironment, - readRemoteEnvironment, - serializeRemoteEnvironment, -} from './environment.ts' -import { asError, commandOpts, delay, signalOpts, signalRemoteGroups } from './remote.ts' - -const TERMINAL_RUNNER_SOURCE = [ - '#!/bin/bash', - 'set -euo pipefail', - 'dsh_state=$1', - 'mapfile -d \'\' -t dsh_env < "$dsh_state/environment"', - 'mapfile -d \'\' -t dsh_argv < "$dsh_state/argv"', - 'dsh_output_marker=$(<"$dsh_state/output-marker")', - 'rm -f -- "$dsh_state/environment" "$dsh_state/argv" "$dsh_state/output-marker" "$dsh_state/runner.bash"', - 'if (( ${#dsh_argv[@]} == 0 )); then', - " printf 'terminal runner received empty argv\\n' >&2", - ' exit 125', - 'fi', - 'printf \'%s\' "$dsh_output_marker"', - 'exec env -i -- "${dsh_env[@]}" "${dsh_argv[@]}"', - '', -].join('\n') - -interface TerminalPaths { - runner: string - environment: string - argv: string - outputMarker: string -} - -class BootstrapOutputFilter { - readonly ready: Promise - - private readonly readyState = Promise.withResolvers() - private pending = Buffer.alloc(0) - private published = false - - constructor( - private readonly marker: Buffer, - private readonly output: PassThrough, - ) { - this.ready = this.readyState.promise - } - - push(data: Uint8Array): void { - if (this.published) { - this.write(data) - return - } - const combined = Buffer.concat([this.pending, Buffer.from(data)]) - const markerOffset = combined.indexOf(this.marker) - if (markerOffset < 0) { - const retained = Math.min(combined.length, this.marker.length - 1) - this.pending = Buffer.from(combined.subarray(combined.length - retained)) - return - } - this.published = true - this.pending = Buffer.alloc(0) - this.readyState.resolve() - this.write(combined.subarray(markerOffset + this.marker.length)) - } - - private write(data: Uint8Array): void { - if (data.length > 0 && !this.output.destroyed) this.output.write(data) - } -} - -async function waitForBootstrapOutput( - ready: Promise, - completion: Promise, - signal?: AbortSignal, -): Promise { - signal?.throwIfAborted() - await new Promise((resolve, reject) => { - let settled = false - let removeAbort: (() => void) | undefined - const finish = (complete: () => void): void => { - if (settled) return - settled = true - removeAbort?.() - complete() - } - const onExit = (): void => { - finish(() => { reject(new Error('subprocess-e2b: terminal exited before publishing its output boundary')) }) - } - if (signal !== undefined) { - const onAbort = (): void => { - finish(() => { reject(asError(signal.reason)) }) - } - signal.addEventListener('abort', onAbort, { once: true }) - removeAbort = () => { signal.removeEventListener('abort', onAbort) } - } - void ready.then(() => { finish(resolve) }) - void completion.then(onExit, onExit) - }) -} - -function parsePositiveId(value: string, message: string): number { - const raw = value.trim() - const id = Number(raw) - if (!/^[1-9][0-9]*$/.test(raw) || !Number.isSafeInteger(id)) throw new Error(message) - return id -} - -function serializeValues(values: readonly string[], kind: string): string { - for (const value of values) { - if (value.includes('\0')) throw new Error(`subprocess-e2b: terminal ${kind} must not contain NUL bytes`) - } - return values.map(value => `${value}\0`).join('') -} - -async function terminalSessionId( - sandbox: Sandbox, - pid: number, - envs: Record, - signal?: AbortSignal, -): Promise { - const result = await sandbox.commands.run(`ps -o sid= -p ${pid}`, commandOpts(envs, signal)) - signal?.throwIfAborted() - return parsePositiveId(result.stdout, `subprocess-e2b: cannot resolve process session for terminal ${pid}`) -} - -async function sessionProcessGroups( - sandbox: Sandbox, - sessionId: number, - envs: Record, -): Promise { - let result: CommandResult - try { - result = await sandbox.commands.run( - `set -o pipefail; ps -eo sid=,pgid=,stat= | awk '$1 == ${sessionId} && $3 !~ /^[ZXx]/ { print $2 }'`, - commandOpts(envs), - ) - } catch (error: unknown) { - if (error instanceof SandboxNotFoundError) return [] - throw error - } - const groups = new Set() - for (const raw of result.stdout.trim().split(/\s+/)) { - if (raw.length === 0) continue - const group = parsePositiveId( - raw, - `subprocess-e2b: invalid process group ${JSON.stringify(raw)} in terminal session ${sessionId}`, - ) - if (group <= 1) { - throw new Error(`subprocess-e2b: unsafe process group ${group} in terminal session ${sessionId}`) - } - groups.add(group) - } - return [...groups] -} - -async function awaitSessionEmpty( - sandbox: Sandbox, - sessionId: number, - envs: Record, - graceMs: number, - pollMs: number, - kill = false, -): Promise { - const deadline = Date.now() + graceMs - for (;;) { - const groups = await sessionProcessGroups(sandbox, sessionId, envs) - if (groups.length === 0) return groups - if (kill) { - await signalRemoteGroups(sandbox, envs, groups, 'KILL') - if (Date.now() >= deadline) return await sessionProcessGroups(sandbox, sessionId, envs) - } else if (Date.now() >= deadline) { - return groups - } - await delay(Math.min(pollMs, Math.max(1, deadline - Date.now()))) - } -} - -async function rollbackUnpublishedTerminal( - sandbox: Sandbox, - handle: CommandHandle, - completion: Promise, - envs: Record, - graceMs: number, - pollMs: number, -): Promise { - let topLevelExited = false - void completion.then( - () => { topLevelExited = true }, - () => { topLevelExited = true }, - ) - const validPid = Number.isSafeInteger(handle.pid) && handle.pid > 1 - const attemptFailures: Error[] = [] - let sessionId: number | undefined - if (validPid) { - sessionId = handle.pid - try { - sessionId = await terminalSessionId(sandbox, handle.pid, envs) - } catch (_sessionLookupFailure) { - // E2B's PTY leader is also the provisional POSIX session leader, so its - // PID remains usable after the setup lookup itself fails or is canceled. - } - try { - let groups = await sessionProcessGroups(sandbox, sessionId, envs) - if (groups.length > 0) { - await signalRemoteGroups(sandbox, envs, groups, 'TERM') - groups = await awaitSessionEmpty(sandbox, sessionId, envs, graceMs, pollMs) - } - if (groups.length > 0) { - await awaitSessionEmpty(sandbox, sessionId, envs, graceMs, pollMs, true) - } - } catch (error: unknown) { - attemptFailures.push(asError(error)) - } - } - // Completion can settle while any awaited provider cleanup above is running. - // oxlint-disable-next-line typescript/no-unnecessary-condition -- Provider cleanup yields to completion. - if (!topLevelExited) { - try { - await handle.kill() - } catch (error: unknown) { - if (error instanceof SandboxNotFoundError) return - attemptFailures.push(asError(error)) - } - await Promise.race([completion.catch(() => undefined), delay(graceMs)]) - } - const proofFailures: Error[] = [] - if (sessionId !== undefined) { - try { - const groups = await awaitSessionEmpty(sandbox, sessionId, envs, graceMs, pollMs, true) - if (groups.length > 0) { - proofFailures.push(new Error( - `subprocess-e2b: terminal setup rollback failed; surviving process groups: ${groups.join(', ')}`, - )) - } - } catch (error: unknown) { - proofFailures.push(asError(error)) - } - } - // The bounded completion race above updates this callback-owned state. - // oxlint-disable-next-line typescript/no-unnecessary-condition -- The callback mutates this after a race. - if (!topLevelExited) { - proofFailures.push(new Error(`subprocess-e2b: terminal setup rollback failed; surviving pid: ${handle.pid}`)) - } - if (proofFailures.length > 0) { - throw new AggregateError( - [...attemptFailures, ...proofFailures], - 'subprocess-e2b: terminal setup rollback did not reach quiescence', - ) - } - try { - await handle.disconnect() - } catch (error: unknown) { - if (!(error instanceof SandboxNotFoundError)) throw error - } -} - -/** One E2B PTY and all process groups in its remote process session. */ -export class E2BTerminalHandle implements SubprocessTerminalHandle { - readonly pid: number - readonly done: Promise - - private topLevelExited = false - private cleanup: Promise | undefined - private readonly operationController = new AbortController() - private readonly operations = new Set>() - private terminationSignal: NodeJS.Signals | null = null - - constructor( - private readonly sandbox: Sandbox, - private readonly handle: CommandHandle, - readonly output: PassThrough, - private readonly completion: Promise, - private readonly sessionId: number, - private readonly controlEnvs: Record, - private readonly stateDir: string, - private readonly graceMs: number, - private readonly pollMs: number, - ) { - this.pid = handle.pid - this.done = this.waitForCommand() - } - - // TODO(e2b-pgid-identity): Replace retained numeric PTY/session ids when E2B - // exposes identity-bound input, foreground-signal, and cleanup operations. - /** @inheritdoc */ - write(data: string): Promise { - return this.trackOperation(async (signal) => { - if (this.topLevelExited) throw new Error('terminal process has exited') - await this.sandbox.pty.sendInput(this.pid, Buffer.from(data, 'utf8'), { signal }) - }) - } - - /** @inheritdoc */ - inspectForeground(): Promise { - return this.trackOperation(signal => this.inspectForegroundOnce(signal)) - } - - /** @inheritdoc */ - signalForeground(signal: SubprocessTerminalSignal): Promise { - return this.trackOperation(async (operationSignal) => { - const foreground = await this.inspectForegroundOnce(operationSignal) - if (foreground === undefined) { - throw new Error(`subprocess-e2b: cannot resolve foreground process group for terminal ${this.pid}`) - } - if (signal === 'SIGKILL' && foreground.processGroupId === this.pid) { - throw new Error('refusing to SIGKILL the terminal shell; terminate the terminal session instead') - } - await this.sandbox.commands.run( - `kill -${signal.slice(3)} -- -${foreground.processGroupId}`, - commandOpts(this.controlEnvs, operationSignal), - ) - return foreground.processGroupId - }) - } - - /** @inheritdoc */ - terminate(): Promise { - if (this.cleanup !== undefined) return this.cleanup - this.operationController.abort(new Error('subprocess-e2b: terminal is terminating')) - const cleanup = this.closeAfterOperations() - this.cleanup = cleanup - void cleanup.catch((_cleanupFailure: unknown) => { - this.cleanup = undefined - }) - return cleanup - } - - private async inspectForegroundOnce( - signal: AbortSignal, - ): Promise { - try { - const result = await this.sandbox.commands.run( - `ps -o tpgid= -p ${this.pid}`, - commandOpts(this.controlEnvs, signal), - ) - return { - processGroupId: parsePositiveId( - result.stdout, - `subprocess-e2b: cannot resolve foreground process group for terminal ${this.pid}`, - ), - // E2B exposes process-table commands but not the /proc memory access - // needed to prove a specific syscall is waiting on fd 0. - inputWaiting: false, - } - } catch (error: unknown) { - if (error instanceof CommandExitError && (error.exitCode === 1 || this.topLevelExited)) return undefined - throw error - } - } - - private trackOperation(operation: (signal: AbortSignal) => Promise): Promise { - if (this.operationController.signal.aborted) { - return Promise.reject(new Error('subprocess-e2b: terminal is terminating')) - } - const pending = operation(this.operationController.signal) - this.operations.add(pending) - void pending.then( - () => { this.operations.delete(pending) }, - () => { this.operations.delete(pending) }, - ) - return pending - } - - private async closeAfterOperations(): Promise { - await Promise.allSettled(this.operations) - await this.closeOnce() - } - - private async waitForCommand(): Promise { - try { - const result = await this.completion - return { exitCode: result.exitCode, signal: null } - } catch (error: unknown) { - if (error instanceof CommandExitError) { - return this.terminationSignal === null - ? { exitCode: error.exitCode, signal: null } - : { exitCode: null, signal: this.terminationSignal } - } - this.output.destroy(error instanceof Error ? error : new Error(String(error))) - throw error - } finally { - this.topLevelExited = true - if (!this.output.destroyed) this.output.end() - } - } - - private async closeOnce(): Promise { - let groups = await sessionProcessGroups(this.sandbox, this.sessionId, this.controlEnvs) - if (groups.length > 0) { - this.terminationSignal = 'SIGTERM' - await signalRemoteGroups(this.sandbox, this.controlEnvs, groups, 'TERM') - groups = await awaitSessionEmpty(this.sandbox, this.sessionId, this.controlEnvs, this.graceMs, this.pollMs) - } - if (groups.length === 0 && !this.topLevelExited) { - await Promise.race([this.done.catch(() => undefined), delay(this.graceMs)]) - } - if (groups.length > 0 || !this.topLevelExited) { - this.terminationSignal = 'SIGKILL' - if (!this.topLevelExited) { - try { - await this.handle.kill() - } catch (error: unknown) { - if (error instanceof SandboxNotFoundError) return - throw error - } - } - groups = await awaitSessionEmpty(this.sandbox, this.sessionId, this.controlEnvs, this.graceMs, this.pollMs, true) - if (!this.topLevelExited) await Promise.race([this.done.catch(() => undefined), delay(this.graceMs)]) - } - if (groups.length > 0) { - throw new Error(`subprocess-e2b: terminal cleanup failed; surviving process groups: ${groups.join(', ')}`) - } - if (!this.topLevelExited) { - throw new Error(`subprocess-e2b: terminal cleanup failed; surviving pid: ${this.pid}`) - } - try { - await this.handle.disconnect() - } catch (error: unknown) { - if (!(error instanceof SandboxNotFoundError)) throw error - } - try { - await this.sandbox.files.remove(this.stateDir) - } catch (_adapterPrivateStateRemovalFailure) { - // The terminal is quiescent; owner teardown bounds private residue. - } - } -} - -/** - * Allocate an E2B PTY, replace its bootstrap shell with the requested argv, - * and return only after the private runner has published readiness. - * @param runtime - Shared E2B sandbox owner. - * @param spec - Fully specified terminal-process request. - * @param stateDir - Private remote directory for one startup transaction. - * @param pollMs - Remote session liveness poll cadence. - * @returns The live subprocess terminal handle. - */ -export async function spawnE2BTerminal( - runtime: E2BRuntime, - spec: SubprocessTerminalSpawnSpec, - stateDir: string, - pollMs: number, -): Promise { - const sandbox = await runtime.getSandbox() - spec.signal?.throwIfAborted() - const paths: TerminalPaths = { - runner: posix.join(stateDir, 'runner.bash'), - environment: posix.join(stateDir, 'environment'), - argv: posix.join(stateDir, 'argv'), - outputMarker: posix.join(stateDir, 'output-marker'), - } - const outputMarker = Buffer.from(`dsh-e2b-bootstrap:${randomUUID()}`) - const output = new PassThrough() - const outputFilter = new BootstrapOutputFilter(outputMarker, output) - let handle: CommandHandle | undefined - let completion: Promise | undefined - let stateDirectoryCreated = false - let controlEnvs: Record = {} - try { - const ambient = await readRemoteEnvironment(sandbox, spec.signal) - controlEnvs = bootstrapEnvironment(ambient) - const environment = serializeRemoteEnvironment(ambient, spec.env) - const argv = serializeValues(spec.argv, 'argv') - stateDirectoryCreated = true - await sandbox.files.makeDir(stateDir, signalOpts(spec.signal)) - await sandbox.commands.run( - `chmod 700 -- ${quoteE2BShellArg(stateDir)}`, - commandOpts(controlEnvs, spec.signal), - ) - await sandbox.files.write([ - { path: paths.runner, data: TERMINAL_RUNNER_SOURCE }, - { path: paths.environment, data: environment }, - { path: paths.argv, data: argv }, - { path: paths.outputMarker, data: outputMarker.toString('utf8') }, - ], signalOpts(spec.signal)) - await sandbox.commands.run( - `chmod 600 -- ${quoteE2BShellArg(paths.runner)} ${quoteE2BShellArg(paths.environment)} ${quoteE2BShellArg(paths.argv)} ${quoteE2BShellArg(paths.outputMarker)}`, - commandOpts(controlEnvs, spec.signal), - ) - handle = await sandbox.pty.create({ - rows: spec.rows, - cols: spec.cols, - cwd: spec.cwd, - envs: e2bControlEnvs(controlEnvs), - timeoutMs: 0, - onData: (data) => { outputFilter.push(data) }, - }) - completion = handle.wait() - void completion.catch(() => {}) - spec.signal?.throwIfAborted() - if (!Number.isSafeInteger(handle.pid) || handle.pid <= 0) { - throw new Error(`subprocess-e2b: E2B returned invalid terminal pid ${handle.pid}`) - } - const command = `exec /bin/bash ${quoteE2BShellArg(paths.runner)} ${quoteE2BShellArg(stateDir)}\r` - await sandbox.pty.sendInput(handle.pid, Buffer.from(command), signalOpts(spec.signal)) - await waitForBootstrapOutput(outputFilter.ready, completion, spec.signal) - const sessionId = await terminalSessionId(sandbox, handle.pid, controlEnvs, spec.signal) - return new E2BTerminalHandle( - sandbox, - handle, - output, - completion, - sessionId, - controlEnvs, - stateDir, - spec.graceMs, - pollMs, - ) - } catch (error: unknown) { - output.destroy() - let terminalQuiescent = handle === undefined - let stateRemoved = !stateDirectoryCreated - const cleanup = async (): Promise => { - const failures: Error[] = [] - if (!terminalQuiescent && handle !== undefined) { - try { - if (completion === undefined) await handle.kill() - else await rollbackUnpublishedTerminal(sandbox, handle, completion, controlEnvs, spec.graceMs, pollMs) - terminalQuiescent = true - } catch (cleanupError: unknown) { - if (cleanupError instanceof SandboxNotFoundError) terminalQuiescent = true - else failures.push(asError(cleanupError)) - } - } - if (!stateRemoved) { - try { - await sandbox.files.remove(stateDir) - stateRemoved = true - } catch (stateError: unknown) { - if (stateError instanceof FileNotFoundError || stateError instanceof SandboxNotFoundError) stateRemoved = true - else failures.push(asError(stateError)) - } - } - if (failures.length > 0) { - throw new AggregateError(failures, 'subprocess-e2b: terminal setup cleanup did not complete') - } - } - try { - await cleanup() - } catch (cleanupError: unknown) { - // TODO(e2b-terminal-setup-rollback): Retain retry state only if a real - // double failure must be recovered before sandbox disposal or timeout. - throw new AggregateError([asError(error), asError(cleanupError)], asError(error).message) - } - throw error - } -} diff --git a/packages/e2b/subprocess-e2b/tests/subprocess.spec.ts b/packages/e2b/subprocess-e2b/tests/subprocess.spec.ts deleted file mode 100644 index c1f1a3e4d7..0000000000 --- a/packages/e2b/subprocess-e2b/tests/subprocess.spec.ts +++ /dev/null @@ -1,1810 +0,0 @@ -import { once } from 'node:events' -import { Context } from '@deepseek-ai/cordis' -import { - CommandExitError, - FileNotFoundError, - SandboxNotFoundError, - type CommandHandle, - type CommandResult, - type Sandbox, -} from '@deepseek-ai/dsh-e2b' -import type E2BRuntime from '@deepseek-ai/dsh-e2b' -import type { SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess' -import E2BSubprocessRuntime from '@deepseek-ai/dsh-subprocess-e2b' -import { E2BBase64Decoder, E2B_OUTPUT_COMPLETE_FRAME, E2BOutputReader } from '../src/output.ts' -import { E2BSubprocessHandle } from '../src/process.ts' -import { describe, expect, it, vi } from 'vitest' - -function commandError(exitCode: number): CommandExitError { - return new CommandExitError({ exitCode, stdout: '', stderr: '', error: `exit ${exitCode}` }) -} - -interface StartOptions { - background: true - cwd: string - stdin: boolean - timeoutMs: number - signal?: AbortSignal - envs?: Record - onStdout?: (data: string) => void | Promise - onStderr?: (data: string) => void | Promise -} - -class FakeCommandHandle { - pid = 4242 - readonly sent: Array = [] - closes = 0 - kills = 0 - disconnects = 0 - killError: unknown - killResult = true - disconnectError: unknown - private readonly result = Promise.withResolvers() - private settled = false - - constructor(private readonly onKill: () => void = () => {}) {} - - wait(): Promise { - return this.result.promise - } - - async sendStdin(data: string | Uint8Array): Promise { - this.sent.push(data) - } - - async closeStdin(): Promise { - this.closes += 1 - } - - async kill(): Promise { - this.kills += 1 - if (this.killError !== undefined) throw this.killError - this.onKill() - return this.killResult - } - - async disconnect(): Promise { - this.disconnects += 1 - if (this.disconnectError !== undefined) throw this.disconnectError - } - - succeed(exitCode = 0): void { - if (this.settled) return - this.settled = true - this.result.resolve({ exitCode, stdout: '', stderr: '' }) - } - - fail(exitCode: number): void { - if (this.settled) return - this.settled = true - this.result.reject(commandError(exitCode)) - } - - crash(error: unknown): void { - if (this.settled) return - this.settled = true - this.result.reject(error) - } -} - -class FakeSandbox { - readonly handle: FakeCommandHandle - readonly commandsSeen: string[] = [] - readonly writtenFiles: string[][] = [] - readonly writtenFileData = new Map() - readonly removed: string[] = [] - readonly directories: string[] = [] - startOptions: StartOptions | undefined - backgroundError: unknown - envError: unknown - statusError: unknown - nextRemoveError: unknown - probeError: unknown - signalError: unknown - readonly signalErrors: unknown[] = [] - trapsTerm = false - delaysKill = false - delaysKillCompletion = false - sdkKillStops = true - alive = true - zombieOnly = false - ambient = 'PATH=/ambient/bin\0KEEP=safe\0UNICODE=你好\0NPM_TOKEN=secret\0DSH_STALE=old\0BROKEN\0=bad\0' - environmentHome = '/home/user' - environmentWire: string | undefined - environmentRequest: ((signal: AbortSignal | undefined) => Promise) | undefined - processGroupId = '4242\n' - exitStatus = '' - statusReads = 0 - readonly processGroupReads: string[] = [] - afterStatusRead: (() => void) | undefined - beforeProbe: (() => void) | undefined - afterProbe: (() => void) | undefined - private startGate: Promise | undefined - private openStart: (() => void) | undefined - private processGroupReadGate: Promise | undefined - private openProcessGroupRead: (() => void) | undefined - private signalGate: Promise | undefined - private openSignal: (() => void) | undefined - - constructor() { - this.handle = new FakeCommandHandle(() => { - if (this.sdkKillStops) { - this.alive = false - this.handle.fail(137) - } - }) - } - - deferStart(): void { - const gate = Promise.withResolvers() - this.startGate = gate.promise - this.openStart = () => { gate.resolve(undefined) } - } - - releaseStart(): void { - this.openStart?.() - } - - deferProcessGroupRead(): void { - const gate = Promise.withResolvers() - this.processGroupReadGate = gate.promise - this.openProcessGroupRead = () => { gate.resolve(undefined) } - } - - releaseProcessGroupRead(): void { - this.openProcessGroupRead?.() - } - - deferSignals(): void { - const gate = Promise.withResolvers() - this.signalGate = gate.promise - this.openSignal = () => { gate.resolve(undefined) } - } - - releaseSignals(): void { - this.openSignal?.() - } - - finish(exitCode = 0): void { - this.alive = false - void this.completeOutput().then( - () => { - if (exitCode === 0) this.handle.succeed(0) - else this.handle.fail(exitCode) - }, - (error: unknown) => { this.handle.crash(error) }, - ) - } - - async completeOutput(): Promise { - await Promise.all([ - this.stdoutWire(`${E2B_OUTPUT_COMPLETE_FRAME}\n`), - this.stderrWire(`${E2B_OUTPUT_COMPLETE_FRAME}\n`), - ]) - } - - async stdout(data: string): Promise { - await this.stdoutWire(data.length === 0 ? '' : `${Buffer.from(data).toString('base64')}\n`) - } - - async stderr(data: string): Promise { - await this.stderrWire(data.length === 0 ? '' : `${Buffer.from(data).toString('base64')}\n`) - } - - async stdoutWire(data: string): Promise { - await this.startOptions?.onStdout?.(data) - } - - async stderrWire(data: string): Promise { - await this.startOptions?.onStderr?.(data) - } - - readonly sandbox = { - sandboxId: 'fake', - files: { - makeDir: async (path: string): Promise => { - this.directories.push(path) - return true - }, - write: async (files: Array<{ path: string; data: string }>): Promise => { - this.writtenFiles.push(files.map(file => file.path)) - for (const file of files) this.writtenFileData.set(file.path, file.data) - return files.map(() => ({})) - }, - read: async (path: string): Promise => { - if (!path.endsWith('/exit-code')) { - await this.processGroupReadGate - return this.processGroupReads.shift() ?? this.processGroupId - } - if (this.statusError !== undefined) { - const error = this.statusError - this.statusError = undefined - throw error - } - this.statusReads += 1 - this.afterStatusRead?.() - return this.exitStatus - }, - remove: async (path: string): Promise => { - this.removed.push(path) - if (this.nextRemoveError !== undefined) { - const error = this.nextRemoveError - this.nextRemoveError = undefined - throw error - } - }, - }, - commands: { - run: async (command: string, options?: StartOptions | { signal?: AbortSignal }): Promise => { - this.commandsSeen.push(command) - if (command.includes('env -0 | base64')) { - await this.environmentRequest?.(options?.signal) - if (this.envError !== undefined) throw this.envError - return { - exitCode: 0, - stdout: this.environmentWire ?? [this.environmentHome, this.ambient] - .map(value => Buffer.from(value).toString('base64')) - .join('\n'), - stderr: '', - } - } - if (command.startsWith('set -o pipefail; ps -eo pgid=,stat=')) { - this.beforeProbe?.() - if (options?.signal?.aborted === true) throw new DOMException('aborted', 'AbortError') - if (this.probeError !== undefined) { - const error = this.probeError - this.probeError = undefined - throw error - } - const stdout = this.alive && !this.zombieOnly ? 'live\n' : '' - this.afterProbe?.() - return { exitCode: 0, stdout, stderr: '' } - } - if (command.startsWith('kill -TERM ')) { - await this.signalGate - const error = this.signalErrors.shift() ?? this.signalError - if (error !== undefined) { - if (this.signalErrors.length === 0) this.signalError = undefined - throw error - } - if (!this.trapsTerm) { - this.alive = false - this.handle.fail(143) - } - return { exitCode: 0, stdout: '', stderr: '' } - } - if (command.startsWith('kill -KILL ')) { - await this.signalGate - const error = this.signalErrors.shift() ?? this.signalError - if (error !== undefined) { - if (this.signalErrors.length === 0) this.signalError = undefined - throw error - } - if (!this.delaysKill) this.alive = false - if (!this.delaysKillCompletion) this.handle.fail(137) - return { exitCode: 0, stdout: '', stderr: '' } - } - if ((options as StartOptions | undefined)?.background === true) { - this.startOptions = options as StartOptions - await this.startGate - if (this.backgroundError !== undefined) throw this.backgroundError - return this.handle as unknown as CommandHandle - } - return { exitCode: 0, stdout: '', stderr: '' } - }, - }, - } as unknown as Sandbox -} - -function spec(overrides: Partial = {}): SubprocessSpawnSpec { - return { - argv: ['bash', '-c', 'printf ok'], - cwd: '/workspace', - stdio: { - stdin: 'ignore', - stdout: { maxBytes: 4, spill: { maxBytes: 16 } }, - stderr: { maxBytes: 4 }, - }, - graceMs: 5, - ...overrides, - } -} - -function runtime(fake: FakeSandbox, getSandbox: () => Promise = async () => fake.sandbox): E2BRuntime { - return { - cwd: '/workspace', - runtimeRoot: '/workspace/.dsh-e2b', - getSandbox, - } as unknown as E2BRuntime -} - -async function flush(): Promise { - await new Promise(resolve => setTimeout(resolve, 0)) -} - -/** Construct the handle under test with the config default the service would pass. */ -function testHandle( - runtime: ConstructorParameters[0], - spec: ConstructorParameters[1], - stateDir: string, - pollMs = 20, -): E2BSubprocessHandle { - return new E2BSubprocessHandle(runtime, spec, stateDir, pollMs) -} - -describe('E2BOutputReader', () => { - it('decodes base64 across arbitrary callback boundaries and rejects malformed framing', () => { - const decoder = new E2BBase64Decoder() - expect(decoder.push('')).toEqual(Buffer.alloc(0)) - expect(decoder.push('5')).toEqual(Buffer.alloc(0)) - expect(decoder.push('L2')).toEqual(Buffer.alloc(0)) - expect(decoder.push('g\n').toString()).toBe('你') - expect(decoder.push('YQ==\nYg==\n').toString()).toBe('ab') - expect(decoder.push(`${Buffer.from([0, 255]).toString('base64')}\n`)).toEqual(Buffer.from([0, 255])) - expect(decoder.push(`${E2B_OUTPUT_COMPLETE_FRAME}\n`)).toEqual(Buffer.alloc(0)) - decoder.finish() - - expect(() => new E2BBase64Decoder().push('%\n')).toThrow('invalid base64') - expect(() => new E2BBase64Decoder().push('AB==\n')).toThrow('invalid base64') - expect(() => decoder.push(`${E2B_OUTPUT_COMPLETE_FRAME}\n`)).toThrow('duplicate output transport completion') - expect(() => decoder.push('YQ==\n')).toThrow('continued after completion') - const truncated = new E2BBase64Decoder() - truncated.push('YQ') - expect(() => { truncated.finish() }).toThrow('truncated base64') - expect(() => { new E2BBase64Decoder().finish() }).toThrow('incomplete output transport') - const interrupted = new E2BBase64Decoder() - interrupted.push('YQ') - expect(() => { interrupted.finish(false) }).not.toThrow() - }) - - it('keeps a byte-exact tail with independent whole-stream cursors', () => { - const reader = new E2BOutputReader(4, 10, '/remote/spill') - reader.push(Buffer.alloc(0)) - reader.push(Buffer.from('ab')) - reader.push(Buffer.from('cdef')) - expect(reader.size).toBe(6) - expect(reader.readFrom(0)).toEqual({ text: 'cdef', nextOffset: 6, lossy: true, spillPath: '/remote/spill' }) - expect(reader.readFrom(2)).toEqual({ text: 'cdef', nextOffset: 6, lossy: false }) - expect(reader.readFrom(5)).toEqual({ text: 'f', nextOffset: 6, lossy: false }) - expect(reader.readFrom(99)).toEqual({ text: '', nextOffset: 6, lossy: false }) - reader.invalidateSpill() - expect(reader.readFrom(0)).toEqual({ text: 'cdef', nextOffset: 6, lossy: true }) - }) - - it('drops whole head chunks and withholds absent or over-cap spills', () => { - const withoutSpill = new E2BOutputReader(2, undefined, '/unused') - withoutSpill.push(Buffer.from('ab')) - withoutSpill.push(Buffer.from('cd')) - expect(withoutSpill.readFrom(0)).toEqual({ text: 'cd', nextOffset: 4, lossy: true }) - const overCap = new E2BOutputReader(2, 3, '/too-small') - overCap.push(Buffer.from('abcd')) - expect(overCap.readFrom(0)).toEqual({ text: 'cd', nextOffset: 4, lossy: true }) - }) -}) - -describe('E2BSubprocessHandle', () => { - it('starts asynchronously, keeps secrets out of the command, and supports deferred piped stdin/output', async () => { - const fake = new FakeSandbox() - fake.processGroupId = '4343\n' - fake.deferStart() - const handle = testHandle(runtime(fake), spec({ - argv: ['tool', 'argument with spaces'], - stdio: { stdin: 'pipe', stdout: 'pipe', stderr: { maxBytes: 8, spill: { maxBytes: 32 } } }, - env: { - PATH: '/bin', - 'FOO-BAR': 'hyphen-value', - '--split-string': 'literal-value', - DEEPSEEK_API_KEY: 'explicit-secret', - DSH_MODE: 'test', - // The seam's tombstone: an explicit undefined removes the ambient entry. - KEEP: undefined, - }, - }), '/workspace/.dsh-e2b/processes/one') - handle.stdin!.write('hello') - handle.stdin!.end() - fake.releaseStart() - await flush() - expect(fake.handle.sent.map(value => String(value))).toEqual(['hello']) - expect(fake.handle.closes).toBe(1) - const controlEnvs = fake.startOptions?.envs - expect(controlEnvs?.HOME).toMatch(/^\/\.dsh-e2b-control-/) - expect(controlEnvs).toEqual({ - TERM: 'dumb', - NPM_TOKEN: '', - DSH_STALE: '', - HOME: controlEnvs?.HOME, - }) - const command = fake.commandsSeen.find(value => value.includes('exec "$dsh_e2b_env_bin" -i'))! - expect(command).toContain('"$dsh_e2b_setsid" --wait -- "$dsh_e2b_bash" -c') - expect(command).not.toContain('DEEPSEEK_API_KEY') - expect(command).not.toContain('DSH_MODE') - expect(command).not.toContain('FOO-BAR') - expect(command).not.toContain('explicit-secret') - expect(command).not.toContain('hyphen-value') - expect(command).not.toContain('${!dsh_e2b_name}') - const environmentProbe = fake.commandsSeen.find(value => value.includes('env -0 | base64')) - expect(environmentProbe).toContain('getent passwd "$(id -u)"') - expect(environmentProbe).toContain('test -n "$dsh_e2b_home" -a -d "$dsh_e2b_home"') - expect(environmentProbe).not.toContain('"$PWD"') - expect(command).toContain('mapfile -d') - expect(command).toContain('dsh_e2b_node="$(command -v node)"') - expect(command).toContain('"$dsh_e2b_env_bin" -i "$dsh_e2b_node" -e') - expect(command).toContain('"$dsh_e2b_env_bin" -i -- "${dsh_e2b_env[@]}" "$@"') - expect(command).toContain('exec "$dsh_e2b_env_bin" -i -- "${dsh_e2b_env[@]}"') - expect(command).toContain('>&2 2>/dev/null') - expect(command).not.toContain('2>/dev/null >&2') - expect(command).toContain('base64') - expect(fake.writtenFiles[0]).toEqual([ - '/workspace/.dsh-e2b/processes/one/pid', - '/workspace/.dsh-e2b/processes/one/exit-code', - '/workspace/.dsh-e2b/processes/one/environment', - '/workspace/.dsh-e2b/processes/one/stderr.log', - ]) - expect(fake.writtenFileData.get('/workspace/.dsh-e2b/processes/one/environment')).toBe( - 'PATH=/bin\0UNICODE=你好\0HOME=/home/user\0FOO-BAR=hyphen-value\0--split-string=literal-value\0DEEPSEEK_API_KEY=explicit-secret\0DSH_MODE=test\0', - ) - - let piped = '' - handle.stdout!.on('data', (chunk) => { piped += String(chunk) }) - await fake.stdout('pipe-data') - await fake.stderr('err') - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - expect(piped).toBe('pipe-data') - expect(handle.collected.stderr!.readFrom(0)).toMatchObject({ text: 'err', lossy: false }) - expect(fake.removed).toContain('/workspace/.dsh-e2b/processes/one/stderr.log') - await expect(handle.waitForExit()).resolves.toBe(true) - }) - - it('rejects an unrepresentable graceMs before any remote work', () => { - const ctx = new Context() - const service = Object.create(E2BSubprocessRuntime.prototype) as E2BSubprocessRuntime - Reflect.set(service, 'disposing', false) - Reflect.set(service, 'ctx', ctx) - for (const graceMs of [0, -1, Number.NaN, Number.POSITIVE_INFINITY]) { - expect(() => service.spawn(spec({ graceMs }))).toThrow('graceMs must be a positive finite number') - void expect(service.spawnTerminal({ - argv: ['bash'], cwd: '/w', rows: 24, cols: 80, graceMs, - })).rejects.toThrow('graceMs must be a positive finite number') - } - }) - - it('rejects malformed environment entries before command start', async () => { - for (const env of [{ 'BAD=NAME': 'x' }, { BAD: 'x\0INJECTED=1' }]) { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ env }), '/runtime/invalid-environment') - await expect(handle.done).rejects.toThrow('environment entries') - expect(fake.startOptions).toBeUndefined() - expect(fake.removed).toContain('/runtime/invalid-environment') - } - }) - - it('preserves UTF-8 bytes when the ASCII transport is split across callbacks', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ - stdio: { stdin: 'ignore', stdout: 'pipe', stderr: { maxBytes: 4 } }, - }), '/runtime/split-utf8') - await flush() - const chunks: Buffer[] = [] - handle.stdout!.on('data', (chunk: Buffer) => { chunks.push(chunk) }) - for (const character of `${Buffer.from('A你好B').toString('base64')}\n`) { - await fake.stdoutWire(character) - } - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - expect(Buffer.concat(chunks).toString('utf8')).toBe('A你好B') - }) - - it('rejects malformed output transport without confusing it with a consumer sink failure', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec(), '/runtime/malformed-output') - await flush() - await fake.stdoutWire('%\n') - fake.finish() - await expect(handle.done).rejects.toThrow('invalid base64 output transport') - - const stderrFake = new FakeSandbox() - const stderrHandle = testHandle(runtime(stderrFake), spec(), '/runtime/malformed-stderr') - await flush() - await stderrFake.stderrWire('%\n') - stderrFake.finish() - await expect(stderrHandle.done).rejects.toThrow('invalid base64 output transport') - }) - - it('rejects a naturally completed command whose encoder omits its completion frame', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec(), '/runtime/incomplete-output') - await flush() - fake.alive = false - fake.handle.succeed(0) - await expect(handle.done).rejects.toThrow('incomplete output transport') - }) - - it('bounds descendant-held output draining and withholds the incomplete spill', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ graceMs: 5 }), '/runtime/drain-bound') - await flush() - await fake.stdout('leader-output') - fake.exitStatus = '0\n' - - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - expect(fake.handle.disconnects).toBe(1) - expect(handle.collected.stdout?.readFrom(0)).toEqual({ - text: 'tput', - nextOffset: 13, - lossy: true, - }) - expect(fake.removed).toContain('/runtime/drain-bound/stdout.log') - - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - }) - - it('releases an inherited-output callback blocked on host backpressure at drain expiry', async () => { - const fake = new FakeSandbox() - const written: string[] = [] - const stdoutWrite = vi.spyOn(process.stdout, 'write').mockImplementation(((chunk: Uint8Array) => { - written.push(Buffer.from(chunk).toString()) - return false - }) as typeof process.stdout.write) - try { - const handle = testHandle(runtime(fake), spec({ - graceMs: 5, - stdio: { stdin: 'ignore', stdout: 'inherit', stderr: { maxBytes: 4 } }, - }), '/runtime/inherit-backpressure') - await flush() - let callbackSettled = false - const blocked = fake.stdout('blocked bytes').then(() => { callbackSettled = true }) - await flush() - expect(callbackSettled).toBe(false) - fake.exitStatus = '0\n' - - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - await blocked - expect(callbackSettled).toBe(true) - expect(written.join('')).toBe('blocked bytes') - expect(fake.handle.disconnects).toBe(1) - - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - } finally { - stdoutWrite.mockRestore() - } - }) - - it('waits for lossless raw-pipe output after the direct status is published', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ - graceMs: 1, - stdio: { stdin: 'ignore', stdout: 'pipe', stderr: { maxBytes: 4 } }, - }), '/runtime/pipe-drain') - let output = '' - handle.stdout!.on('data', (chunk) => { output += String(chunk) }) - await flush() - fake.exitStatus = '0\n' - - let settled = false - void handle.done.then(() => { settled = true }) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(settled).toBe(false) - expect(fake.handle.disconnects).toBe(0) - expect(fake.statusReads).toBe(0) - - await fake.stdout('complete protocol frame') - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - expect(output).toBe('complete protocol frame') - expect(fake.statusReads).toBe(1) - }) - - it('accepts clean encoder completion inside the output-drain grace', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ graceMs: 100 }), '/runtime/drain-complete') - await flush() - fake.exitStatus = '0\n' - fake.afterStatusRead = () => { - fake.afterStatusRead = undefined - setTimeout(() => { fake.finish() }, 0) - } - - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - expect(fake.handle.disconnects).toBe(0) - }) - - it('preserves a published exit code when requested termination outlives output draining', async () => { - const fake = new FakeSandbox() - fake.trapsTerm = true - fake.delaysKill = true - fake.delaysKillCompletion = true - fake.sdkKillStops = false - const handle = testHandle(runtime(fake), spec({ graceMs: 5 }), '/runtime/drain-signal') - await flush() - - handle.terminate() - await vi.waitFor(() => { expect(fake.commandsSeen).toContain('kill -KILL -- -4242') }) - fake.exitStatus = '0\n' - - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - expect(fake.handle.disconnects).toBe(1) - fake.alive = false - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - }) - - it('preserves a published nonzero exit code when termination settles the SDK inside the drain grace', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ graceMs: 100 }), '/runtime/drain-signal-settled') - await flush() - fake.exitStatus = '7\n' - fake.afterStatusRead = () => { - fake.afterStatusRead = undefined - handle.terminate() - } - - await expect(handle.done).resolves.toEqual({ exitCode: 7, signal: null }) - await expect(handle.waitForExit()).resolves.toBe(true) - }) - - it('rejects an invalid direct-command exit status', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec(), '/runtime/invalid-status') - await flush() - fake.exitStatus = '999\n' - await expect(handle.done).rejects.toThrow('invalid exit code') - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - }) - - it('rolls back a published process group before rejecting a monitoring failure', async () => { - const fake = new FakeSandbox() - fake.statusError = new Error('status transport failed') - const handle = testHandle(runtime(fake), spec(), '/runtime/status-failure') - - await expect(handle.done).rejects.toThrow('status transport failed') - expect(fake.commandsSeen).toContain('kill -TERM -- -4242') - expect(fake.alive).toBe(false) - await expect(handle.waitForExit()).resolves.toBe(true) - - const failed = new FakeSandbox() - failed.statusError = new Error('status transport failed') - failed.signalErrors.push(new Error('TERM transport failed'), new Error('KILL transport failed')) - failed.handle.killError = new Error('SDK kill failed') - const retained = testHandle(runtime(failed), spec({ graceMs: 1 }), '/runtime/status-cleanup-failure') - - await expect(retained.done).rejects.toThrow( - 'command monitoring failed and process-group rollback did not reach quiescence', - ) - expect(failed.alive).toBe(true) - failed.handle.killError = undefined - retained.terminate() - await expect(retained.waitForExit()).resolves.toBe(true) - - // A state-cleanup failure on top preserves the rollback failure instead of - // re-aggregating only the original monitoring error. - const triple = new FakeSandbox() - triple.statusError = new Error('status transport failed') - triple.signalErrors.push(new Error('TERM transport failed'), new Error('KILL transport failed')) - triple.handle.killError = new Error('SDK kill failed') - triple.nextRemoveError = new Error('state cleanup failed') - const tripleHandle = testHandle(runtime(triple), spec({ graceMs: 1 }), '/runtime/triple-failure') - const failure = await tripleHandle.done.catch((error: unknown) => error as AggregateError) - expect(failure).toBeInstanceOf(AggregateError) - expect((failure as AggregateError).message).toContain('private state cleanup failed') - const nested = (failure as AggregateError).errors[0] as AggregateError - expect(nested.message).toContain('rollback did not reach quiescence') - triple.handle.killError = undefined - tripleHandle.terminate() - await expect(tripleHandle.waitForExit()).resolves.toBe(true) - }) - - it('surfaces deferred piped-stdin write and close failures as stream errors', async () => { - const writeFake = new FakeSandbox() - writeFake.deferStart() - vi.spyOn(writeFake.handle, 'sendStdin').mockRejectedValueOnce('stdin rejected') - const writeHandle = testHandle(runtime(writeFake), spec({ - stdio: { stdin: 'pipe', stdout: { maxBytes: 4 }, stderr: { maxBytes: 4 } }, - }), '/runtime/stdin-write-error') - const writeError = once(writeHandle.stdin!, 'error') - writeHandle.stdin!.write('input') - writeFake.releaseStart() - await expect(writeError).resolves.toMatchObject([{ message: 'stdin rejected' }]) - writeFake.finish() - await writeHandle.done - - const closeFake = new FakeSandbox() - vi.spyOn(closeFake.handle, 'closeStdin').mockRejectedValueOnce(new Error('close rejected')) - const closeHandle = testHandle(runtime(closeFake), spec({ - stdio: { stdin: 'pipe', stdout: { maxBytes: 4 }, stderr: { maxBytes: 4 } }, - }), '/runtime/stdin-close-error') - await flush() - const closeError = once(closeHandle.stdin!, 'error') - closeHandle.stdin!.end() - await expect(closeError).resolves.toMatchObject([{ message: 'close rejected' }]) - closeFake.finish() - await closeHandle.done - }) - - it('collects bounded tails, retains valid spills, and maps natural nonzero exits', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ - stdio: { - stdin: { data: 'batch' }, - stdout: { maxBytes: 4, spill: { maxBytes: 16 } }, - stderr: { maxBytes: 3 }, - }, - }), '/runtime/two') - await flush() - await fake.stdout('abcdef') - await fake.stderr('12345') - fake.finish(7) - await expect(handle.done).resolves.toEqual({ exitCode: 7, signal: null }) - expect(fake.handle.sent).toEqual(['batch']) - expect(fake.handle.closes).toBe(1) - expect(handle.collected.stdout!.readFrom(0)).toEqual({ - text: 'cdef', - nextOffset: 6, - lossy: true, - spillPath: '/runtime/two/stdout.log', - }) - expect(handle.collected.stderr!.readFrom(0)).toEqual({ text: '345', nextOffset: 5, lossy: true }) - expect(fake.removed).not.toContain('/runtime/two/stdout.log') - }) - - it('removes a spill once the complete stream exceeds its cap', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ - stdio: { stdin: 'ignore', stdout: { maxBytes: 2, spill: { maxBytes: 3 } }, stderr: 'inherit' }, - }), '/runtime/oversize') - await flush() - await fake.stdout('abcd') - await fake.stderr('') - fake.finish() - await handle.done - expect(handle.collected.stdout!.readFrom(0)).toEqual({ text: 'cd', nextOffset: 4, lossy: true }) - expect(fake.removed).toContain('/runtime/oversize/stdout.log') - const command = fake.commandsSeen.find(value => value.includes('dsh_e2b_tee='))! - expect(command).toContain('"$dsh_e2b_head" -c 3') - expect(command).toContain('/runtime/oversize/stdout.log') - expect(command).toContain('"$dsh_e2b_tee" --output-error=warn-nopipe') - expect(command).not.toContain('tee -a') - }) - - it('contains remote spill-removal failures and routes empty inherited output', async () => { - const fake = new FakeSandbox() - fake.nextRemoveError = new Error('already removed') - const handle = testHandle(runtime(fake), spec({ - stdio: { stdin: 'ignore', stdout: 'inherit', stderr: { maxBytes: 4, spill: { maxBytes: 8 } } }, - }), '/runtime/remove-error') - await flush() - await fake.stdout('') - await fake.stderr('') - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - expect(fake.removed).toContain('/runtime/remove-error/stderr.log') - }) - - it('terminates a process group with TERM and reports the signal outcome', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec(), '/runtime/term') - await flush() - handle.terminate() - handle.terminate() - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGTERM' }) - await expect(handle.waitForExit()).resolves.toBe(true) - expect(fake.commandsSeen).toContain('kill -TERM -- -4242') - expect(fake.commandsSeen).not.toContain('kill -KILL -- -4242') - const signals = fake.commandsSeen.filter(command => command.startsWith('kill -')).length - fake.alive = true - handle.terminate() - await flush() - expect(fake.alive).toBe(true) - expect(fake.commandsSeen.filter(command => command.startsWith('kill -'))).toHaveLength(signals) - }) - - it('makes termination a permanent no-op after natural quiescence is observed', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec(), '/runtime/natural-quiescence') - await flush() - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - await expect(handle.waitForExit()).resolves.toBe(true) - - const signals = fake.commandsSeen.filter(command => command.startsWith('kill -')).length - fake.alive = true - handle.terminate() - await flush() - expect(fake.alive).toBe(true) - expect(fake.commandsSeen.filter(command => command.startsWith('kill -'))).toHaveLength(signals) - }) - - it('treats a zombie-only process group as quiescent', async () => { - const fake = new FakeSandbox() - fake.zombieOnly = true - const handle = testHandle(runtime(fake), spec(), '/runtime/zombie-quiescence') - await flush() - - await expect(handle.waitForExit()).resolves.toBe(true) - expect(fake.commandsSeen).toContain( - 'set -o pipefail; ps -eo pgid=,stat= | awk \'$1 == 4242 && $2 !~ /^[ZXx]/ { live=1 } END { if (live) print "live" }\'', - ) - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - }) - - it('keeps proven quiescence after a concurrent termination transport fails', async () => { - const fake = new FakeSandbox() - fake.signalErrors.push(new Error('TERM transport failed'), new Error('KILL transport failed')) - fake.handle.killError = new Error('SDK kill failed') - fake.deferSignals() - const handle = testHandle(runtime(fake), spec(), '/runtime/quiescent-race') - await flush() - - handle.terminate() - await vi.waitFor(() => { expect(fake.commandsSeen).toContain('kill -TERM -- -4242') }) - fake.alive = false - await expect(handle.waitForExit()).resolves.toBe(true) - - fake.probeError = new Error('post-quiescence probe failed') - fake.releaseSignals() - await vi.waitFor(() => { expect(fake.handle.kills).toBe(1) }) - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - const signals = fake.commandsSeen.filter(command => command.startsWith('kill -')).length - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - expect(fake.commandsSeen.filter(command => command.startsWith('kill -'))).toHaveLength(signals) - }) - - it('escalates a TERM-trapping process group to KILL and uses the SDK kill as fallback', async () => { - const fake = new FakeSandbox() - fake.trapsTerm = true - fake.handle.killError = new Error('already gone') - const handle = testHandle(runtime(fake), spec({ graceMs: 1 }), '/runtime/kill') - await flush() - handle.terminate() - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' }) - await expect(handle.waitForExit()).resolves.toBe(true) - expect(fake.commandsSeen).toContain('kill -KILL -- -4242') - expect(fake.handle.kills).toBe(1) - }) - - it('keeps force cleanup retryable until quiescence is proven', async () => { - const fake = new FakeSandbox() - fake.trapsTerm = true - fake.delaysKill = true - fake.delaysKillCompletion = true - fake.sdkKillStops = false - const handle = testHandle(runtime(fake), spec({ graceMs: 1 }), '/runtime/termination-fence') - await flush() - handle.terminate() - await vi.waitFor(() => { expect(fake.handle.kills).toBe(1) }) - await expect(handle.waitForExit()).rejects.toThrow('remained live after force termination') - expect(fake.alive).toBe(true) - - fake.delaysKill = false - fake.delaysKillCompletion = false - fake.sdkKillStops = true - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' }) - }) - - it('honors termination requested before asynchronous startup finishes', async () => { - const fake = new FakeSandbox() - fake.deferStart() - const handle = testHandle(runtime(fake), spec(), '/runtime/deferred-kill') - handle.terminate() - fake.releaseStart() - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGTERM' }) - }) - - it('aborts a stalled preparation request before reporting startup quiescence', async () => { - const fake = new FakeSandbox() - let preparationSignal: AbortSignal | undefined - fake.environmentRequest = async (signal) => { - preparationSignal = signal - await new Promise((_resolve, reject) => { - const rejectAbort = (): void => { - const reason: unknown = signal?.reason - reject(reason instanceof Error ? reason : new Error(String(reason))) - } - if (signal?.aborted === true) { - rejectAbort() - return - } - signal?.addEventListener('abort', rejectAbort, { once: true }) - }) - } - const handle = testHandle(runtime(fake), spec(), '/runtime/stalled-preparation') - await vi.waitFor(() => { expect(preparationSignal).toBeDefined() }) - - handle.terminate() - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGTERM' }) - await expect(handle.waitForExit()).resolves.toBe(true) - expect(preparationSignal?.aborted).toBe(true) - expect(fake.startOptions).toBeUndefined() - }) - - it('kills through the provisional SDK handle before process-group publication', async () => { - const fake = new FakeSandbox() - fake.deferProcessGroupRead() - fake.signalErrors.push(commandError(1), commandError(1)) - const handle = testHandle(runtime(fake), spec(), '/runtime/pre-publication-kill') - await vi.waitFor(() => { expect(fake.startOptions).toBeDefined() }) - - handle.terminate() - await vi.waitFor(() => { expect(fake.handle.kills).toBe(1) }) - expect(fake.alive).toBe(false) - await expect(handle.waitForExit()).resolves.toBe(true) - - fake.releaseProcessGroupRead() - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' }) - }) - - it('does not treat an unsuccessful SDK fallback as provisional group quiescence', async () => { - const fake = new FakeSandbox() - fake.deferProcessGroupRead() - fake.trapsTerm = true - fake.delaysKill = true - fake.delaysKillCompletion = true - fake.sdkKillStops = false - fake.handle.killResult = false - const handle = testHandle(runtime(fake), spec({ graceMs: 1 }), '/runtime/provisional-sdk-false') - await vi.waitFor(() => { expect(fake.startOptions).toBeDefined() }) - - handle.terminate() - await vi.waitFor(() => { expect(fake.handle.kills).toBe(1) }) - await expect(handle.waitForExit()).rejects.toThrow('remained live after force termination') - fake.alive = false - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - fake.releaseProcessGroupRead() - fake.finish() - await handle.done - }) - - it('bounds a quiescence observer while provisional termination is awaiting the controller', async () => { - const fake = new FakeSandbox() - fake.deferProcessGroupRead() - const reconnect = Promise.withResolvers() - let calls = 0 - const delayedRuntime = runtime(fake, async () => { - calls += 1 - return calls === 1 ? fake.sandbox : await reconnect.promise - }) - const handle = testHandle(delayedRuntime, spec(), '/runtime/pre-publication-observer') - await vi.waitFor(() => { expect(fake.startOptions).toBeDefined() }) - handle.terminate() - - const controller = new AbortController() - const waiting = handle.waitForExit(controller.signal) - await flush() - controller.abort() - await expect(waiting).resolves.toBe(false) - - reconnect.resolve(fake.sandbox) - await expect(handle.waitForExit()).resolves.toBe(true) - fake.releaseProcessGroupRead() - await handle.done - }) - - it('proves a provisional group exit when the SDK kill fallback fails', async () => { - const fake = new FakeSandbox() - fake.deferProcessGroupRead() - fake.trapsTerm = true - fake.handle.killError = new Error('SDK kill unavailable') - const handle = testHandle(runtime(fake), spec({ graceMs: 1 }), '/runtime/pre-publication-group-kill') - await vi.waitFor(() => { expect(fake.startOptions).toBeDefined() }) - - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - fake.releaseProcessGroupRead() - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' }) - }) - - it('reports failed provisional group and SDK force transports', async () => { - const fake = new FakeSandbox() - fake.deferProcessGroupRead() - fake.signalErrors.push(new Error('TERM transport failed'), new Error('KILL transport failed')) - fake.handle.killError = new Error('SDK kill failed') - const handle = testHandle(runtime(fake), spec({ graceMs: 1 }), '/runtime/pre-publication-failure') - await vi.waitFor(() => { expect(fake.startOptions).toBeDefined() }) - - handle.terminate() - await expect(handle.waitForExit()).rejects.toThrow('remained live after force termination') - - fake.handle.killError = undefined - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - fake.releaseProcessGroupRead() - await handle.done - - const absentGroup = new FakeSandbox() - absentGroup.deferProcessGroupRead() - absentGroup.signalErrors.push(commandError(1), commandError(1)) - absentGroup.handle.killError = new Error('SDK kill failed without a provisional group') - const absentHandle = testHandle( - runtime(absentGroup), - spec({ graceMs: 1 }), - '/runtime/pre-publication-absent-group', - ) - await vi.waitFor(() => { expect(absentGroup.startOptions).toBeDefined() }) - absentHandle.terminate() - await expect(absentHandle.waitForExit()).rejects.toThrow('remained live after force termination') - absentGroup.handle.killError = undefined - absentHandle.terminate() - await expect(absentHandle.waitForExit()).resolves.toBe(true) - absentGroup.releaseProcessGroupRead() - await absentHandle.done - - const optimisticSdk = new FakeSandbox() - optimisticSdk.deferProcessGroupRead() - optimisticSdk.signalErrors.push(commandError(1), commandError(1)) - optimisticSdk.sdkKillStops = false - const optimisticHandle = testHandle( - runtime(optimisticSdk), - spec({ graceMs: 1 }), - '/runtime/pre-publication-optimistic-sdk', - ) - await vi.waitFor(() => { expect(optimisticSdk.startOptions).toBeDefined() }) - optimisticHandle.terminate() - await expect(optimisticHandle.waitForExit()).rejects.toThrow('remained live after force termination') - optimisticHandle.terminate() - await expect(optimisticHandle.waitForExit()).resolves.toBe(true) - optimisticSdk.releaseProcessGroupRead() - await optimisticHandle.done - }) - - it('honors an already-aborted signal when constructing the asynchronous handle directly', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ signal: AbortSignal.abort('stop') }), '/runtime/pre-aborted') - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGTERM' }) - }) - - it('reacts to a signal that aborts after the remote command has started', async () => { - const fake = new FakeSandbox() - const controller = new AbortController() - const handle = testHandle(runtime(fake), spec({ signal: controller.signal }), '/runtime/live-abort') - await flush() - controller.abort('stop') - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGTERM' }) - }) - - it('can terminate a surviving process group after the command leader settles', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec(), '/runtime/surviving-group') - await flush() - await fake.completeOutput() - fake.handle.succeed(0) - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - expect(fake.alive).toBe(true) - - handle.terminate() - await flush() - const signaled = fake.commandsSeen.includes('kill -TERM -- -4242') - if (!signaled) fake.finish() - await expect(handle.waitForExit()).resolves.toBe(true) - expect(signaled).toBe(true) - }) - - it('bounds waitForExit while startup or a live group is pending', async () => { - const fake = new FakeSandbox() - fake.deferStart() - const handle = testHandle(runtime(fake), spec(), '/runtime/wait') - const beforeStart = new AbortController() - const pending = handle.waitForExit(beforeStart.signal) - beforeStart.abort() - await expect(pending).resolves.toBe(false) - await expect(handle.waitForExit(AbortSignal.abort())).resolves.toBe(false) - fake.releaseStart() - await flush() - const live = new AbortController() - const liveWait = handle.waitForExit(live.signal) - live.abort() - await expect(liveWait).resolves.toBe(false) - fake.finish() - await handle.done - - const terminatingFake = new FakeSandbox() - terminatingFake.deferStart() - const terminating = testHandle(runtime(terminatingFake), spec(), '/runtime/wait-termination-start') - terminating.terminate() - const beforeHandle = new AbortController() - const handlePending = terminating.waitForExit(beforeHandle.signal) - beforeHandle.abort() - await expect(handlePending).resolves.toBe(false) - terminatingFake.releaseStart() - await terminating.done - }) - - it('bounds both sides of the liveness-poll abort race', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec(), '/runtime/poll-abort') - await flush() - - const beforeTick = new AbortController() - fake.afterProbe = () => { beforeTick.abort(); fake.afterProbe = undefined } - await expect(handle.waitForExit(beforeTick.signal)).resolves.toBe(false) - - const duringTick = new AbortController() - fake.afterProbe = () => { - fake.afterProbe = undefined - setTimeout(() => { duringTick.abort() }, 0) - } - await expect(handle.waitForExit(duringTick.signal)).resolves.toBe(false) - - const duringProbe = new AbortController() - fake.beforeProbe = () => { duringProbe.abort(); fake.beforeProbe = undefined } - await expect(handle.waitForExit(duringProbe.signal)).resolves.toBe(false) - - let racedAbort = false - const raceSignal = { - get aborted() { return racedAbort }, - addEventListener: () => { racedAbort = true }, - removeEventListener: () => {}, - } as unknown as AbortSignal - await expect(handle.waitForExit(raceSignal)).resolves.toBe(false) - fake.finish() - await handle.done - }) - - it('observes a live group across one successful bounded poll', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec(), '/runtime/poll-success') - await flush() - setTimeout(() => { fake.finish() }, 1) - await expect(handle.waitForExit(new AbortController().signal)).resolves.toBe(true) - await handle.done - }) - - it('treats startup failure as no live tree and contains readiness rejection', async () => { - const fake = new FakeSandbox() - fake.backgroundError = new Error('start failed') - const handle = testHandle(runtime(fake), spec(), '/runtime/fail') - await expect(handle.done).rejects.toThrow('start failed') - expect(fake.removed).toContain('/runtime/fail/environment') - expect(fake.removed).toContain('/runtime/fail') - await expect(handle.waitForExit()).resolves.toBe(true) - handle.terminate() - - const unavailableHandle = testHandle( - runtime(new FakeSandbox(), async () => { throw new Error('sandbox unavailable') }), - spec(), - '/runtime/unavailable-start', - ) - await expect(unavailableHandle.done).rejects.toThrow('sandbox unavailable') - await expect(unavailableHandle.waitForExit()).resolves.toBe(true) - - const envFailure = new FakeSandbox() - envFailure.envError = new Error('ambient lookup failed') - const envHandle = testHandle(runtime(envFailure), spec(), '/runtime/env-failure') - await expect(envHandle.done).rejects.toThrow('ambient lookup failed') - expect(envFailure.removed).toEqual([]) - - const expectEnvironmentFailure = async (name: string, wire: string, message: string): Promise => { - const fake = new FakeSandbox() - fake.environmentWire = wire - const failed = testHandle(runtime(fake), spec(), `/runtime/${name}`) - await expect(failed.done).rejects.toThrow(message) - } - const encodedEnvironment = Buffer.from('PATH=/bin\0').toString('base64') - const encodedHome = Buffer.from('/home/user').toString('base64') - await expectEnvironmentFailure('malformed-frame', '%', 'invalid base64') - await expectEnvironmentFailure('malformed-base64', `${encodedHome}\n%`, 'invalid base64') - await expectEnvironmentFailure( - 'invalid-utf8-home', - `${Buffer.from([0xff]).toString('base64')}\n${encodedEnvironment}`, - 'not valid UTF-8', - ) - await expectEnvironmentFailure( - 'invalid-utf8-environment', - `${encodedHome}\n${Buffer.from([0xff]).toString('base64')}`, - 'not valid UTF-8', - ) - await expectEnvironmentFailure( - 'relative-home', - `${Buffer.from('home/user').toString('base64')}\n${encodedEnvironment}`, - 'remote login home is invalid', - ) - await expectEnvironmentFailure( - 'nul-home', - `${Buffer.from('/home/user\0tail').toString('base64')}\n${encodedEnvironment}`, - 'remote login home is invalid', - ) - - const cleanupFailure = new FakeSandbox() - cleanupFailure.backgroundError = new Error('start failed before credential consumption') - cleanupFailure.nextRemoveError = new Error('credential cleanup failed') - const cleanupHandle = testHandle(runtime(cleanupFailure), spec(), '/runtime/cleanup-failure') - await expect(cleanupHandle.done).rejects.toThrow('command failed and private state cleanup failed') - - const absentState = new FakeSandbox() - absentState.backgroundError = new Error('start failed after external cleanup') - absentState.nextRemoveError = new FileNotFoundError('already removed') - const absentHandle = testHandle(runtime(absentState), spec(), '/runtime/absent-state') - await expect(absentHandle.done).rejects.toThrow('start failed after external cleanup') - }) - - it('bounds a readiness rejection with a still-live caller signal', async () => { - const fake = new FakeSandbox() - fake.deferStart() - fake.backgroundError = new Error('start failed') - const handle = testHandle(runtime(fake), spec(), '/runtime/fail-with-signal') - const waiting = handle.waitForExit(new AbortController().signal) - fake.releaseStart() - await expect(handle.done).rejects.toThrow('start failed') - await expect(waiting).resolves.toBe(true) - }) - - it('propagates an unavailable sandbox unless the caller aborts the wait', async () => { - const fake = new FakeSandbox() - let calls = 0 - const unavailable = runtime(fake, async () => { - calls += 1 - if (calls === 1) return fake.sandbox - throw new Error('connection unavailable') - }) - const handle = testHandle(unavailable, spec(), '/runtime/unavailable') - await flush() - await expect(handle.waitForExit()).rejects.toThrow('connection unavailable') - fake.finish() - await handle.done - }) - - it('returns false when the caller aborts while reconnecting for liveness', async () => { - const fake = new FakeSandbox() - const reconnect = Promise.withResolvers() - let calls = 0 - const unavailable = runtime(fake, async () => { - calls += 1 - return calls === 1 ? fake.sandbox : await reconnect.promise - }) - const handle = testHandle(unavailable, spec(), '/runtime/reconnect-abort') - await flush() - const controller = new AbortController() - const waiting = handle.waitForExit(controller.signal) - await flush() - controller.abort() - reconnect.reject(new Error('connection unavailable')) - await expect(waiting).resolves.toBe(false) - fake.finish() - await handle.done - }) - - it('returns false when a liveness request itself is aborted and surfaces other probe failures', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec(), '/runtime/probe') - await flush() - const controller = new AbortController() - controller.abort() - await expect(handle.waitForExit(controller.signal)).resolves.toBe(false) - fake.probeError = new Error('probe failed') - await expect(handle.waitForExit()).rejects.toThrow('probe failed') - fake.finish() - await handle.done - }) - - it('treats a timeout-killed sandbox as quiescent during liveness probing', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec(), '/runtime/expired-sandbox') - await flush() - fake.finish() - await handle.done - fake.probeError = new SandboxNotFoundError('sandbox expired') - - await expect(handle.waitForExit()).resolves.toBe(true) - }) - - it('treats a missing sandbox handle as quiescent during liveness acquisition', async () => { - const fake = new FakeSandbox() - let calls = 0 - const handle = testHandle(runtime(fake, async () => { - calls += 1 - if (calls === 1) return fake.sandbox - throw new SandboxNotFoundError('sandbox expired') - }), spec(), '/runtime/expired-acquisition') - await flush() - - await expect(handle.waitForExit()).resolves.toBe(true) - await fake.completeOutput() - fake.alive = false - fake.handle.succeed(0) - await handle.done - }) - - it('treats sandbox loss during termination as quiescent', async () => { - const fake = new FakeSandbox() - let calls = 0 - const handle = testHandle(runtime(fake, async () => { - calls += 1 - if (calls === 1) return fake.sandbox - throw new SandboxNotFoundError('sandbox expired') - }), spec(), '/runtime/expired-termination') - await flush() - await fake.completeOutput() - - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - fake.alive = false - fake.handle.succeed(0) - await handle.done - }) - - it('makes batch stdin close failures best-effort', async () => { - const fake = new FakeSandbox() - vi.spyOn(fake.handle, 'sendStdin').mockRejectedValueOnce(new Error('closed')) - const handle = testHandle(runtime(fake), spec({ - stdio: { stdin: { data: 'ignored' }, stdout: { maxBytes: 4 }, stderr: { maxBytes: 4 } }, - }), '/runtime/stdin-closed') - await flush() - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - }) - - it('rejects malformed SDK process ids and non-command settlement failures', async () => { - const invalidPid = new FakeSandbox() - invalidPid.handle.pid = 0 - const invalid = testHandle(runtime(invalidPid), spec(), '/runtime/invalid-pid') - await expect(invalid.done).rejects.toThrow(/invalid command pid 0/) - expect(invalidPid.handle.kills).toBe(1) - expect(invalidPid.removed).toContain('/runtime/invalid-pid/environment') - await expect(invalid.waitForExit()).resolves.toBe(true) - - const failedRollback = new FakeSandbox() - failedRollback.handle.pid = 0 - failedRollback.handle.killError = new Error('invalid handle kill failed') - const retained = testHandle(runtime(failedRollback), spec(), '/runtime/invalid-pid-retained') - await expect(retained.done).rejects.toThrow('invalid command pid rollback did not reach quiescence') - await expect(retained.waitForExit()).rejects.toThrow('invalid handle kill failed') - failedRollback.handle.killError = undefined - retained.terminate() - await expect(retained.waitForExit()).resolves.toBe(true) - - const crashedFake = new FakeSandbox() - const crashed = testHandle(runtime(crashedFake), spec(), '/runtime/crashed') - await flush() - crashedFake.alive = false - crashedFake.handle.crash(new Error('command transport failed')) - await expect(crashed.done).rejects.toThrow('command transport failed') - }) - - it('rejects invalid or absent process-group publication', async () => { - const invalidGroup = new FakeSandbox() - invalidGroup.processGroupId = 'not-a-pid\n' - invalidGroup.delaysKill = true - invalidGroup.sdkKillStops = false - invalidGroup.afterProbe = () => { invalidGroup.alive = false } - const invalid = testHandle(runtime(invalidGroup), spec(), '/runtime/invalid-group') - await expect(invalid.done).rejects.toThrow(/invalid process-group id/) - expect(invalidGroup.handle.kills).toBe(1) - expect(invalidGroup.commandsSeen).toContain('kill -KILL -- -4242') - await expect(invalid.waitForExit()).resolves.toBe(true) - - // A rewritten pid file must not aim the kill at every process (`-- -1`). - const unsafeGroup = new FakeSandbox() - unsafeGroup.processGroupId = '1\n' - unsafeGroup.delaysKill = true - unsafeGroup.sdkKillStops = false - unsafeGroup.afterProbe = () => { unsafeGroup.alive = false } - const unsafe = testHandle(runtime(unsafeGroup), spec(), '/runtime/unsafe-group') - await expect(unsafe.done).rejects.toThrow(/unsafe published process-group id 1/) - expect(unsafeGroup.commandsSeen).not.toContain('kill -KILL -- -1') - await expect(unsafe.waitForExit()).resolves.toBe(true) - - const absentGroup = new FakeSandbox() - absentGroup.processGroupId = '' - const absent = testHandle(runtime(absentGroup), spec(), '/runtime/absent-group') - await flush() - absentGroup.finish() - await expect(absent.done).rejects.toThrow(/exited before publishing/) - expect(absentGroup.handle.kills).toBe(1) - expect(absentGroup.commandsSeen).toContain('kill -KILL -- -4242') - await expect(absent.waitForExit()).resolves.toBe(true) - }) - - it('keeps polling while a running command has not published its process group yet', async () => { - const fake = new FakeSandbox() - fake.processGroupReads.push('', '4242\n') - const handle = testHandle(runtime(fake), spec(), '/runtime/delayed-group-publication', 1) - - await vi.waitFor(() => { expect(fake.processGroupReads).toEqual([]) }) - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - await expect(handle.waitForExit()).resolves.toBe(true) - }) - - it('preserves publication failure and reports cleanup that cannot be verified', async () => { - const fake = new FakeSandbox() - fake.processGroupId = 'not-a-pid\n' - fake.signalError = new Error('rollback signal failed') - fake.handle.killError = new Error('SDK kill failed') - const handle = testHandle(runtime(fake), spec(), '/runtime/failed-rollback') - - let failure: unknown - try { - await handle.done - } catch (error: unknown) { - failure = error - } - expect(failure).toBeInstanceOf(AggregateError) - if (!(failure instanceof AggregateError)) throw new Error('expected AggregateError') - expect(failure.message).toBe('subprocess-e2b: process-group publication failed and rollback did not reach quiescence') - const failures = Array.from(failure.errors as Iterable) - expect(failures).toHaveLength(2) - expect(failures[0]).toBeInstanceOf(Error) - expect(failures[1]).toBeInstanceOf(Error) - if (!(failures[0] instanceof Error) || !(failures[1] instanceof Error)) throw new Error('expected nested errors') - expect(failures[0].message).toContain('invalid process-group id') - expect(failures[1].message).toContain('remained live after force termination') - expect(fake.handle.kills).toBe(1) - const bounded = new AbortController() - const waiting = handle.waitForExit(bounded.signal) - bounded.abort() - await expect(waiting).resolves.toBe(false) - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - expect(fake.commandsSeen).toContain('kill -TERM -- -4242') - - const naturallyGone = new FakeSandbox() - naturallyGone.processGroupId = 'not-a-pid\n' - naturallyGone.signalError = new Error('rollback signal failed') - naturallyGone.handle.killError = new Error('SDK kill failed') - const observed = testHandle(runtime(naturallyGone), spec(), '/runtime/failed-rollback-observed') - await expect(observed.done).rejects.toThrow('process-group publication failed') - naturallyGone.alive = false - await expect(observed.waitForExit()).resolves.toBe(true) - }) - - it('handles output backpressure and contains a stderr sink failure', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ - stdio: { stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' }, - }), '/runtime/backpressure') - await flush() - - handle.stdout!.on('error', () => {}) - const stdoutWrite = vi.spyOn(handle.stdout!, 'write').mockReturnValueOnce(false) - const stdoutPending = fake.stdout('blocked') - queueMicrotask(() => { handle.stdout!.emit('drain') }) - await stdoutPending - stdoutWrite.mockRestore() - - handle.stderr!.on('error', () => {}) - const stderrWrite = vi.spyOn(handle.stderr!, 'write').mockReturnValueOnce(false) - const stderrPending = fake.stderr('broken') - queueMicrotask(() => { handle.stderr!.emit('error', new Error('sink failed')) }) - await stderrPending - stderrWrite.mockRestore() - - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - }) - - it('settles output backpressure when the consumer closes the pipe', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ - stdio: { stdin: 'ignore', stdout: 'pipe', stderr: { maxBytes: 4 } }, - }), '/runtime/backpressure-close') - await flush() - - const stdoutWrite = vi.spyOn(handle.stdout!, 'write').mockReturnValueOnce(false) - const pending = fake.stdout('discarded') - queueMicrotask(() => { handle.stdout!.destroy() }) - await pending - stdoutWrite.mockRestore() - - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - }) - - it('breaks output backpressure when termination owns the command', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ - stdio: { stdin: 'ignore', stdout: 'pipe', stderr: { maxBytes: 4 } }, - }), '/runtime/backpressure-termination') - await flush() - - const stdoutWrite = vi.spyOn(handle.stdout!, 'write').mockReturnValueOnce(false) - let released = false - const pending = fake.stdout('blocked').then(() => { released = true }) - await Promise.resolve() - handle.terminate() - await flush() - const releasedByTermination = released - if (!released) handle.stdout!.emit('drain') - await pending - stdoutWrite.mockRestore() - await handle.done - - expect(releasedByTermination).toBe(true) - }) - - it('settles backpressure when a synchronous pipe write starts termination', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ - stdio: { stdin: 'ignore', stdout: 'pipe', stderr: { maxBytes: 4 } }, - }), '/runtime/backpressure-synchronous-termination') - await flush() - - const stdoutWrite = vi.spyOn(handle.stdout!, 'write').mockImplementationOnce(() => { - handle.terminate() - return false - }) - await expect(fake.stdout('blocked')).resolves.toBeUndefined() - stdoutWrite.mockRestore() - await handle.done - }) - - it('contains a pipe callback failure instead of rejecting command settlement', async () => { - const fake = new FakeSandbox() - const handle = testHandle(runtime(fake), spec({ - stdio: { stdin: 'ignore', stdout: 'pipe', stderr: { maxBytes: 4 } }, - }), '/runtime/pipe-error') - await flush() - const emitted = once(handle.stdout!, 'error') - handle.stdout!.destroy(new Error('consumer failed')) - await emitted - await fake.stdout('late output') - fake.finish() - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - }) - - it('contains an already-gone group signal and escalates after a TERM transport failure', async () => { - const gone = new FakeSandbox() - gone.trapsTerm = true - gone.signalError = commandError(1) - const goneHandle = testHandle(runtime(gone), spec({ graceMs: 1 }), '/runtime/gone-signal') - await flush() - goneHandle.terminate() - await expect(goneHandle.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' }) - - const failed = new FakeSandbox() - failed.signalError = new Error('signal transport failed') - const failedHandle = testHandle(runtime(failed), spec(), '/runtime/failed-signal') - await flush() - failedHandle.terminate() - await expect(failedHandle.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' }) - expect(failed.commandsSeen).toContain('kill -KILL -- -4242') - }) - - it('allows termination retry after both force transports fail', async () => { - const fake = new FakeSandbox() - fake.signalErrors.push(new Error('TERM transport failed'), new Error('KILL transport failed')) - fake.handle.killError = new Error('SDK kill failed') - const handle = testHandle(runtime(fake), spec({ graceMs: 1 }), '/runtime/retry-signal') - await flush() - - handle.terminate() - await expect(handle.waitForExit()).rejects.toThrow('remained live after force termination') - fake.handle.killError = undefined - handle.terminate() - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGTERM' }) - expect(fake.commandsSeen.filter(command => command.startsWith('kill -TERM '))).toHaveLength(2) - - const missingGroup = new FakeSandbox() - missingGroup.trapsTerm = true - missingGroup.signalErrors.push(undefined, commandError(1)) - missingGroup.handle.killError = new Error('SDK kill failed after group exit race') - const raced = testHandle(runtime(missingGroup), spec({ graceMs: 1 }), '/runtime/group-exit-race') - await flush() - raced.terminate() - await expect(raced.waitForExit()).rejects.toThrow('remained live after force termination') - missingGroup.handle.killError = undefined - raced.terminate() - await expect(raced.waitForExit()).resolves.toBe(true) - }) - - it('rejects an optimistic SDK kill while descendants survive a failed group KILL', async () => { - const fake = new FakeSandbox() - fake.trapsTerm = true - fake.sdkKillStops = false - fake.signalErrors.push(undefined, new Error('KILL transport failed')) - const handle = testHandle(runtime(fake), spec({ graceMs: 1 }), '/runtime/optimistic-sdk-kill') - await flush() - - handle.terminate() - await expect(handle.waitForExit()).rejects.toThrow('remained live after force termination') - expect(fake.alive).toBe(true) - - fake.sdkKillStops = true - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' }) - }) -}) - -describe('E2BSubprocessRuntime', () => { - async function service( - fake = new FakeSandbox(), - providedRuntime: E2BRuntime = runtime(fake), - ): Promise<{ ctx: Context; fiber: Awaited> }> { - const ctx = new Context() - ctx.provide('e2b', providedRuntime) - const fiber = await ctx.plugin(E2BSubprocessRuntime) - return { ctx, fiber } - } - - it('registers handles and disposal terminates and joins live remote groups regardless of sandbox policy', async () => { - const fake = new FakeSandbox() - fake.trapsTerm = true - const { ctx, fiber } = await service(fake) - const handle = ctx.subprocess.spawn(spec({ graceMs: 1 })) - await flush() - await fiber.dispose() - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' }) - expect(fake.alive).toBe(false) - }) - - it('awaits SDK settlement after the remote process group becomes quiescent', async () => { - const fake = new FakeSandbox() - fake.trapsTerm = true - const { ctx, fiber } = await service(fake) - const handle = ctx.subprocess.spawn(spec()) - await flush() - fake.alive = false - - let disposed = false - const disposing = fiber.dispose().then(() => { disposed = true }) - await flush() - expect(disposed).toBe(false) - - fake.finish() - await disposing - await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null }) - }) - - it('reports a failed termination transaction from disposal instead of waiting on done', async () => { - const fake = new FakeSandbox() - fake.signalErrors.push(new Error('TERM transport failed'), new Error('KILL transport failed')) - fake.handle.killError = new Error('SDK kill failed') - const { ctx, fiber } = await service(fake) - const handle = ctx.subprocess.spawn(spec({ graceMs: 1 })) - await flush() - - await expect(fiber.dispose()).resolves.toBeUndefined() - await expect(handle.waitForExit()).rejects.toThrow('remained live after force termination') - - fake.handle.killError = undefined - handle.terminate() - await expect(handle.waitForExit()).resolves.toBe(true) - await expect(handle.done).resolves.toEqual({ exitCode: null, signal: 'SIGTERM' }) - }) - - it('aggregates sibling cleanup failures instead of reporting only the first', async () => { - const { ctx, fiber } = await service() - const disposalErrors: unknown[] = [] - ctx.logger.error = ((error: unknown) => { disposalErrors.push(error) }) as typeof ctx.logger.error - const first = { - terminate: vi.fn(), - waitForExit: vi.fn(async () => { throw new Error('first cleanup failed') }), - done: Promise.resolve({ exitCode: 0, signal: null }), - } as unknown as E2BSubprocessHandle - const second = { - terminate: vi.fn(), - waitForExit: vi.fn(async () => { throw new Error('second cleanup failed') }), - done: Promise.resolve({ exitCode: 0, signal: null }), - } as unknown as E2BSubprocessHandle - const live = (ctx.subprocess as unknown as { live: Set }).live - live.add(first) - live.add(second) - - await fiber.dispose() - const failure = disposalErrors[0] - expect(failure).toBeInstanceOf(AggregateError) - if (!(failure instanceof AggregateError)) throw new Error('expected AggregateError') - expect(failure.errors.map(error => (error as Error).message).sort()).toEqual([ - 'first cleanup failed', - 'second cleanup failed', - ]) - }) - - it('waits for every owned cleanup before reporting a disposal failure', async () => { - const { ctx, fiber } = await service() - const failed = { - terminate: vi.fn(), - waitForExit: vi.fn(async () => { throw new Error('cleanup failed') }), - done: Promise.resolve({ exitCode: 0, signal: null }), - } as unknown as E2BSubprocessHandle - let finishCleanup!: () => void - const cleanup = new Promise((resolve) => { - finishCleanup = () => { resolve(true) } - }) - const draining = { - terminate: vi.fn(), - waitForExit: vi.fn(() => cleanup), - done: Promise.resolve({ exitCode: 0, signal: null }), - } as unknown as E2BSubprocessHandle - const live = (ctx.subprocess as unknown as { live: Set }).live - live.add(failed) - live.add(draining) - - let disposed = false - const disposing = fiber.dispose().then(() => { disposed = true }) - await flush() - expect(disposed).toBe(false) - finishCleanup() - await disposing - expect(live).toEqual(new Set([failed])) - }) - - it('releases naturally settled handles before later service disposal', async () => { - const fake = new FakeSandbox() - const { ctx, fiber } = await service(fake) - const handle = ctx.subprocess.spawn(spec()) - await flush() - fake.finish() - await handle.done - await flush() - const signalsBefore = fake.commandsSeen.filter(command => command.startsWith('kill -')).length - await fiber.dispose() - expect(fake.commandsSeen.filter(command => command.startsWith('kill -')).length).toBe(signalsBefore) - }) - - it('contains a release liveness failure and retries quiescence during disposal', async () => { - const fake = new FakeSandbox() - let calls = 0 - const reconnecting = runtime(fake, async () => { - calls += 1 - if (calls === 2) throw new Error('transient liveness failure') - return fake.sandbox - }) - const { ctx, fiber } = await service(fake, reconnecting) - const handle = ctx.subprocess.spawn(spec()) - await flush() - fake.finish() - await handle.done - await flush() - await fiber.dispose() - expect(calls).toBeGreaterThanOrEqual(3) - }) - - it('contains spawn rejection while disposal is joining the pending handle', async () => { - const fake = new FakeSandbox() - fake.deferStart() - fake.backgroundError = new Error('start failed during disposal') - const { ctx, fiber } = await service(fake) - const subprocess = ctx.subprocess - const handle = subprocess.spawn(spec()) - await vi.waitFor(() => { expect(fake.startOptions).toBeDefined() }) - const disposing = fiber.dispose() - await flush() - expect(() => subprocess.spawn(spec())).toThrow('service is disposing') - fake.releaseStart() - await expect(disposing).resolves.toBeUndefined() - await expect(handle.done).rejects.toThrow('start failed during disposal') - }) - - it('validates synchronous spawn preconditions before handle or remote work', async () => { - const fake = new FakeSandbox() - const getSandbox = vi.fn(async () => fake.sandbox) - const { ctx } = await service(fake, runtime(fake, getSandbox)) - const live = (ctx.subprocess as unknown as { live: Set }).live - expect(() => ctx.subprocess.spawn(spec({ argv: [] }))).toThrow(/non-empty program/) - expect(() => ctx.subprocess.spawn(spec({ signal: AbortSignal.abort('stop') }))) - .toThrow(new Error('aborted before spawn: stop')) - expect(() => ctx.subprocess.spawn(spec({ signal: AbortSignal.abort(null) }))) - .toThrow(new Error('aborted before spawn: aborted')) - const throwingReason = { toString: () => { throw new Error('caller reason escaped') } } - expect(() => ctx.subprocess.spawn(spec({ signal: AbortSignal.abort(throwingReason) }))) - .toThrow(new Error('aborted before spawn: aborted')) - - for (const invalid of [ - spec({ argv: ['bash\0'] }), - spec({ argv: ['bash', 'bad\0arg'] }), - spec({ cwd: 'bad\0cwd' }), - spec({ env: { REMOVED: undefined, 'BAD\0KEY': 'value' } }), - spec({ env: { BAD: 'bad\0value' } }), - ]) { - let thrown: unknown - try { - ctx.subprocess.spawn(invalid) - } catch (error) { - thrown = error - } - expect(thrown).toMatchObject({ name: 'TypeError', code: 'ERR_INVALID_ARG_VALUE' }) - } - expect(getSandbox).not.toHaveBeenCalled() - expect(live).toEqual(new Set()) - expect(fake.directories).toEqual([]) - }) -}) diff --git a/packages/e2b/subprocess-e2b/tests/terminal.spec.ts b/packages/e2b/subprocess-e2b/tests/terminal.spec.ts deleted file mode 100644 index bc6118b0ff..0000000000 --- a/packages/e2b/subprocess-e2b/tests/terminal.spec.ts +++ /dev/null @@ -1,942 +0,0 @@ -import { Buffer } from 'node:buffer' -import { once } from 'node:events' -import { Context } from '@deepseek-ai/cordis' -import { describe, expect, it, vi } from 'vitest' -import { - CommandExitError, - FileNotFoundError, - SandboxNotFoundError, - type CommandHandle, - type CommandResult, - type Sandbox, -} from '@deepseek-ai/dsh-e2b' -import type E2BRuntime from '@deepseek-ai/dsh-e2b' -import type { SubprocessTerminalSpawnSpec } from '@deepseek-ai/dsh-subprocess' -import E2BSubprocessRuntime from '@deepseek-ai/dsh-subprocess-e2b' -import { spawnE2BTerminal } from '../src/terminal.ts' - -function commandError(exitCode: number): CommandExitError { - return new CommandExitError({ exitCode, stdout: '', stderr: '', error: `exit ${exitCode}` }) -} - -interface CommandOptions { - signal?: AbortSignal - cwd?: string - envs?: Record -} - -class FakeTerminalCommandHandle { - pid = 123 - disconnects = 0 - sdkKills = 0 - disconnectError: unknown - sdkKillError: unknown - waitError: unknown - settleOnSdkKill = true - private readonly result = Promise.withResolvers() - private settled = false - - wait(): Promise { - if (this.waitError !== undefined) throw this.waitError - return this.result.promise - } - - async disconnect(): Promise { - this.disconnects += 1 - if (this.disconnectError !== undefined) throw this.disconnectError - } - - async kill(): Promise { - this.sdkKills += 1 - if (this.sdkKillError !== undefined) { - const error = this.sdkKillError - if (this.settleOnSdkKill) this.fail(137) - throw error - } - if (this.settleOnSdkKill) this.fail(137) - return true - } - - succeed(exitCode = 0): void { - if (this.settled) return - this.settled = true - this.result.resolve({ exitCode, stdout: '', stderr: '' }) - } - - fail(exitCode: number): void { - if (this.settled) return - this.settled = true - this.result.reject(commandError(exitCode)) - } - - crash(error: unknown): void { - if (this.settled) return - this.settled = true - this.result.reject(error) - } - - asHandle(): CommandHandle { - return this as unknown as CommandHandle - } -} - -class FakeTerminalSandbox { - readonly handle = new FakeTerminalCommandHandle() - readonly commands: string[] = [] - readonly commandOptions: CommandOptions[] = [] - readonly inputs: Array<{ pid: number; data: Buffer }> = [] - readonly removed: string[] = [] - readonly directories: string[] = [] - readonly writes = new Map() - createOptions: Parameters[0] | undefined - ambient = 'KEEP=visible\0UNICODE=你好\0NPM_TOKEN=secret\0DSH_STALE=old\0BROKEN\0=bad\0' - sessionId = '123\n' - foreground = '456\n' - groups = [123] - zombieGroups: number[] = [] - createError: unknown - writeError: unknown - sendError: unknown - commandFailure: unknown - makeDirRequest: ((signal: AbortSignal | undefined) => Promise) | undefined - sendInputRequest: ((signal: AbortSignal | undefined) => Promise) | undefined - foregroundRequest: ((signal: AbortSignal | undefined) => Promise) | undefined - signalRequest: ((signal: AbortSignal | undefined) => Promise) | undefined - sessionGroupsFailure: unknown - foregroundFailure: unknown - termFailure: unknown - removeError: unknown - clearOnTerm = true - clearOnKill = true - resolvedExecutable = '/usr/bin/node\n' - requestedOutput = 'requested-shell$ ' - emitOutputMarker = true - afterSessionLookup: (() => void) | undefined - private createGate: Promise | undefined - private releaseCreateGate: (() => void) | undefined - - deferCreate(): void { - const gate = Promise.withResolvers() - this.createGate = gate.promise - this.releaseCreateGate = () => { gate.resolve(undefined) } - } - - releaseCreate(): void { - this.releaseCreateGate?.() - } - - readonly sandbox = { - files: { - makeDir: async (path: string, options?: CommandOptions): Promise => { - this.directories.push(path) - await this.makeDirRequest?.(options?.signal) - options?.signal?.throwIfAborted() - return true - }, - write: async (files: Array<{ path: string; data: string }>): Promise => { - for (const file of files) this.writes.set(file.path, file.data) - if (this.writeError !== undefined) throw this.writeError - return files.map(() => ({})) - }, - remove: async (path: string): Promise => { - this.removed.push(path) - if (this.removeError !== undefined) throw this.removeError - }, - }, - commands: { - run: async (command: string, options?: CommandOptions): Promise => { - this.commands.push(command) - if (options !== undefined) this.commandOptions.push(options) - options?.signal?.throwIfAborted() - if (this.commandFailure !== undefined) { - const error = this.commandFailure - this.commandFailure = undefined - throw error - } - if (command.includes('env -0 | base64')) { - return { - exitCode: 0, - stdout: ['/home/user', this.ambient].map(value => Buffer.from(value).toString('base64')).join('\n'), - stderr: '', - } - } - if (command.includes('command -v -- ')) { - return { exitCode: 0, stdout: this.resolvedExecutable, stderr: '' } - } - if (command.startsWith('ps -o sid=')) { - this.afterSessionLookup?.() - return { exitCode: 0, stdout: this.sessionId, stderr: '' } - } - if (command.startsWith('ps -o tpgid=')) { - await this.foregroundRequest?.(options?.signal) - options?.signal?.throwIfAborted() - if (this.foregroundFailure !== undefined) throw this.foregroundFailure - return { exitCode: 0, stdout: this.foreground, stderr: '' } - } - if (command.startsWith('set -o pipefail; ps -eo sid=')) { - if (this.sessionGroupsFailure !== undefined) throw this.sessionGroupsFailure - const groups = command.includes('stat=') && command.includes('$3 !~ /^[ZXx]/') - ? this.groups - : [...this.groups, ...this.zombieGroups] - return { exitCode: 0, stdout: groups.map(group => `${group}\n`).join(''), stderr: '' } - } - if (command.startsWith('kill -TERM -- ')) { - if (this.termFailure !== undefined) throw this.termFailure - if (this.clearOnTerm) { - this.groups = [] - this.handle.fail(143) - } - } - if (command.startsWith('kill -INT -- ')) { - await this.signalRequest?.(options?.signal) - options?.signal?.throwIfAborted() - } - if (command.startsWith('kill -KILL -- ') && this.clearOnKill) this.groups = [] - return { exitCode: 0, stdout: '', stderr: '' } - }, - }, - pty: { - create: async (options: Parameters[0]): Promise => { - this.createOptions = options - if (this.createError !== undefined) throw this.createError - await this.createGate - options.signal?.throwIfAborted() - await options.onData(Buffer.from('buffered banner\n')) - return this.handle.asHandle() - }, - sendInput: async (pid: number, data: Uint8Array, options?: { signal?: AbortSignal }): Promise => { - options?.signal?.throwIfAborted() - await this.sendInputRequest?.(options?.signal) - options?.signal?.throwIfAborted() - this.inputs.push({ pid, data: Buffer.from(data) }) - if (this.sendError !== undefined) throw this.sendError - if (this.emitOutputMarker && Buffer.from(data).includes(Buffer.from('runner.bash'))) { - const marker = [...this.writes].find(([path]) => path.endsWith('/output-marker'))?.[1] - const onData = this.createOptions?.onData - if (marker !== undefined && onData !== undefined) { - await onData(Buffer.from(Buffer.from(data).toString().replace(/\r$/, '\r\n'))) - const split = Math.floor(marker.length / 2) - await onData(Buffer.from(marker.slice(0, split))) - await onData(Buffer.from(marker.slice(split))) - await onData(Buffer.from(this.requestedOutput)) - } - } - }, - }, - } as unknown as Sandbox -} - -function runtime(fake: FakeTerminalSandbox): E2BRuntime { - return { - cwd: '/workspace', - runtimeRoot: '/workspace/.dsh-e2b', - getSandbox: async () => fake.sandbox, - } as unknown as E2BRuntime -} - -function spec(overrides: Partial = {}): SubprocessTerminalSpawnSpec { - return { - argv: ['/bin/bash', '--noprofile', '--norc'], - cwd: '/workspace', - rows: 24, - cols: 80, - graceMs: 5, - env: { TERM: 'dumb', DSH_SESSION_ID: 'owner', TOKEN_EXPLICIT: 'kept' }, - ...overrides, - } -} - -function holdRequestUntilAbort(started: PromiseWithResolvers) { - return async (signal: AbortSignal | undefined): Promise => { - if (signal === undefined) throw new Error('expected an operation signal') - signal.throwIfAborted() - started.resolve(signal) - await new Promise((_resolve, reject) => { - signal.addEventListener('abort', () => { - reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason))) - }, { once: true }) - }) - } -} - -/** Spawn the terminal under test with the config default the service would pass. */ -function testSpawn( - runtime: Parameters[0], - spec: Parameters[1], - stateDir: string, - pollMs = 20, -): ReturnType { - return spawnE2BTerminal(runtime, spec, stateDir, pollMs) -} - -describe('E2B terminal allocation', () => { - it('hides bootstrap-shell bytes and preserves requested-shell bytes across the output boundary', async () => { - const fake = new FakeTerminalSandbox() - const terminal = await testSpawn(runtime(fake), spec(), '/runtime/terminal-one') - let output = '' - terminal.output.on('data', (chunk) => { output += String(chunk) }) - await new Promise(resolve => setTimeout(resolve, 0)) - - expect(output).toBe('requested-shell$ ') - expect(output).not.toContain('buffered banner') - expect(output).not.toContain('runner.bash') - expect(fake.createOptions).toMatchObject({ rows: 24, cols: 80, cwd: '/workspace', timeoutMs: 0 }) - const controlEnvs = fake.createOptions?.envs - expect(controlEnvs?.HOME).toMatch(/^\/\.dsh-e2b-control-/) - expect(controlEnvs).toEqual({ - TERM: 'dumb', - NPM_TOKEN: '', - DSH_STALE: '', - HOME: controlEnvs?.HOME, - }) - expect(fake.inputs[0]?.data.toString()).toContain("exec /bin/bash '/runtime/terminal-one/runner.bash'") - expect(fake.writes.get('/runtime/terminal-one/environment')).toContain('KEEP=visible\0') - expect(fake.writes.get('/runtime/terminal-one/environment')).toContain('UNICODE=你好\0') - expect(fake.writes.get('/runtime/terminal-one/environment')).toContain('TOKEN_EXPLICIT=kept\0') - expect(fake.writes.get('/runtime/terminal-one/environment')).not.toContain('secret') - expect(fake.writes.get('/runtime/terminal-one/environment')).not.toContain('DSH_STALE') - expect(fake.writes.get('/runtime/terminal-one/argv')).toBe('/bin/bash\0--noprofile\0--norc\0') - const marker = fake.writes.get('/runtime/terminal-one/output-marker') ?? '' - expect(marker).toMatch(/^dsh-e2b-bootstrap:/) - expect(fake.inputs[0]?.data.toString()).not.toContain(marker) - const runner = fake.writes.get('/runtime/terminal-one/runner.bash') ?? '' - expect(runner).toContain('if (( ${#dsh_argv[@]} == 0 )); then') - expect(runner).toContain('printf \'%s\' "$dsh_output_marker"') - expect(runner).toContain('exec env -i -- "${dsh_env[@]}" "${dsh_argv[@]}"') - expect(runner).not.toContain('\u007f') - terminal.output.destroy() - await fake.createOptions?.onData(Buffer.from('late bootstrap callback')) - expect(output).toBe('requested-shell$ ') - - await terminal.write('echo ok\r') - expect(fake.inputs.at(-1)?.data.toString()).toBe('echo ok\r') - await expect(terminal.inspectForeground()).resolves.toEqual({ processGroupId: 456, inputWaiting: false }) - await expect(terminal.signalForeground('SIGINT')).resolves.toBe(456) - expect(fake.commands).toContain('kill -INT -- -456') - - const terminated = terminal.terminate() - await expect(terminal.done).resolves.toEqual({ exitCode: null, signal: 'SIGTERM' }) - await terminated - expect(fake.handle.disconnects).toBe(1) - expect(fake.removed).toContain('/runtime/terminal-one') - }) - - it('inherits only safe ambient values and limits the allocation signal to setup', async () => { - const fake = new FakeTerminalSandbox() - const controller = new AbortController() - const terminal = await testSpawn( - runtime(fake), - spec({ env: undefined, signal: controller.signal }), - '/runtime/abort-live', - ) - const environment = fake.writes.get('/runtime/abort-live/environment') ?? '' - expect(environment).toContain('KEEP=visible\0') - expect(environment).not.toContain('secret') - expect(environment).not.toContain('DSH_STALE') - - controller.abort(new Error('stop')) - await terminal.write('still live\r') - expect(fake.inputs.at(-1)?.data.toString()).toBe('still live\r') - await terminal.terminate() - await expect(terminal.done).resolves.toEqual({ exitCode: null, signal: 'SIGTERM' }) - }) - - it('publishes the PTY handle before honoring allocation cancellation', async () => { - const fake = new FakeTerminalSandbox() - fake.deferCreate() - const controller = new AbortController() - const spawning = testSpawn( - runtime(fake), - spec({ signal: controller.signal }), - '/runtime/allocation-cancel', - ) - await vi.waitFor(() => { expect(fake.createOptions).toBeDefined() }) - - controller.abort(new Error('allocation cancelled')) - fake.releaseCreate() - await expect(spawning).rejects.toThrow('allocation cancelled') - expect(fake.createOptions?.signal).toBeUndefined() - expect(fake.groups).toEqual([]) - expect(fake.handle.disconnects).toBe(1) - }) - - it('rejects malformed environment and argv values before PTY allocation', async () => { - const invalidName = new FakeTerminalSandbox() - await expect(testSpawn(runtime(invalidName), spec({ env: { 'BAD=NAME': 'x' } }), '/runtime/name')) - .rejects.toThrow('environment entries') - expect(invalidName.createOptions).toBeUndefined() - - const invalidValue = new FakeTerminalSandbox() - await expect(testSpawn(runtime(invalidValue), spec({ env: { BAD: 'x\0y' } }), '/runtime/value')) - .rejects.toThrow('environment entries') - - const invalidArg = new FakeTerminalSandbox() - await expect(testSpawn(runtime(invalidArg), spec({ argv: ['/bin/bash', 'x\0y'] }), '/runtime/argv')) - .rejects.toThrow('argv must not contain NUL') - }) - - it('cleans malformed handles, bootstrap failures, and readiness failures', async () => { - const failedState = new FakeTerminalSandbox() - failedState.writeError = new Error('state write failed') - await expect(testSpawn(runtime(failedState), spec(), '/runtime/state-write')) - .rejects.toThrow('state write failed') - expect(failedState.writes.get('/runtime/state-write/environment')).toContain('KEEP=visible\0') - expect(failedState.removed).toContain('/runtime/state-write') - expect(failedState.createOptions).toBeUndefined() - - const stateAlreadyGone = new FakeTerminalSandbox() - stateAlreadyGone.writeError = new Error('state write failed after external cleanup') - stateAlreadyGone.removeError = new FileNotFoundError('state already gone') - await expect(testSpawn(runtime(stateAlreadyGone), spec(), '/runtime/state-gone')) - .rejects.toThrow('state write failed after external cleanup') - - const invalidPid = new FakeTerminalSandbox() - invalidPid.handle.pid = 0 - await expect(testSpawn(runtime(invalidPid), spec(), '/runtime/invalid-pid')) - .rejects.toThrow('invalid terminal pid 0') - expect(invalidPid.handle.sdkKills).toBe(1) - expect(invalidPid.removed).toContain('/runtime/invalid-pid') - - const failedInput = new FakeTerminalSandbox() - failedInput.sendError = new Error('bootstrap failed') - await expect(testSpawn(runtime(failedInput), spec(), '/runtime/input')) - .rejects.toThrow('bootstrap failed') - expect(failedInput.commands).toContain('kill -TERM -- -123') - expect(failedInput.groups).toEqual([]) - - const invalidSession = new FakeTerminalSandbox() - invalidSession.sessionId = 'not-a-session\n' - invalidSession.clearOnTerm = false - await expect(testSpawn(runtime(invalidSession), spec(), '/runtime/session')) - .rejects.toThrow('cannot resolve process session') - expect(invalidSession.commands).toContain('kill -TERM -- -123') - expect(invalidSession.commands).toContain('kill -KILL -- -123') - expect(invalidSession.groups).toEqual([]) - expect(invalidSession.handle.sdkKills).toBe(1) - const lateData = invalidSession.createOptions?.onData - if (lateData === undefined) throw new Error('missing captured terminal callback') - expect(lateData(Buffer.from('late bytes'))).toBeUndefined() - - const termFailed = new FakeTerminalSandbox() - termFailed.sendError = new Error('bootstrap failed') - termFailed.termFailure = new Error('TERM transport failed') - await expect(testSpawn(runtime(termFailed), spec(), '/runtime/term-failed')) - .rejects.toThrow('bootstrap failed') - expect(termFailed.commands).toContain('kill -KILL -- -123') - expect(termFailed.handle.sdkKills).toBe(1) - - const uninspectable = new FakeTerminalSandbox() - uninspectable.sendError = new Error('bootstrap failed') - uninspectable.sessionGroupsFailure = 'session enumeration failed' - uninspectable.handle.sdkKillError = new Error('PTY kill failed') - let uninspectableFailure: unknown - try { - await testSpawn(runtime(uninspectable), spec(), '/runtime/uninspectable') - } catch (error: unknown) { - uninspectableFailure = error - } - expect(uninspectableFailure).toBeInstanceOf(AggregateError) - expect(uninspectable.handle.sdkKills).toBe(1) - - const survivingGroups = new FakeTerminalSandbox() - survivingGroups.sendError = new Error('bootstrap failed') - survivingGroups.clearOnTerm = false - survivingGroups.clearOnKill = false - await expect(testSpawn(runtime(survivingGroups), spec({ graceMs: 1 }), '/runtime/surviving-groups')) - .rejects.toThrow('bootstrap failed') - - const survivingPid = new FakeTerminalSandbox() - survivingPid.sendError = new Error('bootstrap failed') - survivingPid.groups = [] - survivingPid.handle.settleOnSdkKill = false - await expect(testSpawn(runtime(survivingPid), spec({ graceMs: 1 }), '/runtime/surviving-pid')) - .rejects.toThrow('bootstrap failed') - - const waitFailed = new FakeTerminalSandbox() - waitFailed.handle.waitError = new Error('wait failed') - waitFailed.handle.settleOnSdkKill = false - waitFailed.handle.sdkKillError = new Error('kill failed') - await expect(testSpawn(runtime(waitFailed), spec(), '/runtime/wait-failed')) - .rejects.toThrow('wait failed') - expect(waitFailed.handle.sdkKills).toBe(1) - - const cleanupFailed = new FakeTerminalSandbox() - cleanupFailed.handle.pid = 0 - cleanupFailed.handle.sdkKillError = new Error('kill transport failed') - cleanupFailed.removeError = new Error('remove transport failed') - await expect(testSpawn(runtime(cleanupFailed), spec(), '/runtime/cleanup-failed')) - .rejects.toThrow('invalid terminal pid 0') - - const expiredDuringRollback = new FakeTerminalSandbox() - expiredDuringRollback.sendError = new Error('bootstrap failed before timeout') - expiredDuringRollback.groups = [] - expiredDuringRollback.handle.settleOnSdkKill = false - expiredDuringRollback.handle.sdkKillError = new SandboxNotFoundError('sandbox expired') - expiredDuringRollback.removeError = new SandboxNotFoundError('sandbox expired') - await expect(testSpawn(runtime(expiredDuringRollback), spec(), '/runtime/expired-rollback')) - .rejects.toThrow('bootstrap failed before timeout') - expect(expiredDuringRollback.handle.sdkKills).toBe(1) - - const expiredBeforeSdkRollback = new FakeTerminalSandbox() - expiredBeforeSdkRollback.handle.waitError = new Error('wait failed after timeout') - expiredBeforeSdkRollback.handle.sdkKillError = new SandboxNotFoundError('sandbox expired') - expiredBeforeSdkRollback.handle.settleOnSdkKill = false - await expect(testSpawn(runtime(expiredBeforeSdkRollback), spec(), '/runtime/expired-sdk-rollback')) - .rejects.toThrow('wait failed after timeout') - - const missingDuringDisconnect = new FakeTerminalSandbox() - missingDuringDisconnect.sendError = new Error('bootstrap failed before disconnect') - missingDuringDisconnect.handle.disconnectError = new SandboxNotFoundError('sandbox expired') - await expect(testSpawn(runtime(missingDuringDisconnect), spec(), '/runtime/missing-disconnect')) - .rejects.toThrow('bootstrap failed before disconnect') - - const failedDisconnect = new FakeTerminalSandbox() - failedDisconnect.sendError = new Error('bootstrap failed with disconnect failure') - failedDisconnect.handle.disconnectError = new Error('disconnect transport failed') - await expect(testSpawn(runtime(failedDisconnect), spec(), '/runtime/failed-disconnect')) - .rejects.toThrow('bootstrap failed with disconnect failure') - }) - - it('propagates setup cancellation and provider failures', async () => { - const aborted = new FakeTerminalSandbox() - await expect(testSpawn(runtime(aborted), spec({ signal: AbortSignal.abort(new Error('stop')) }), '/runtime/abort')) - .rejects.toThrow('stop') - - const createFailed = new FakeTerminalSandbox() - createFailed.createError = new Error('create failed') - await expect(testSpawn(runtime(createFailed), spec(), '/runtime/create')) - .rejects.toThrow('create failed') - - }) - - it('bounds a missing bootstrap-output boundary by process exit or cancellation', async () => { - const exited = new FakeTerminalSandbox() - exited.emitOutputMarker = false - const exiting = testSpawn(runtime(exited), spec(), '/runtime/missing-output-boundary') - await vi.waitFor(() => { expect(exited.inputs).toHaveLength(1) }) - exited.handle.succeed(0) - await expect(exiting).rejects.toThrow('terminal exited before publishing its output boundary') - - const cancelled = new FakeTerminalSandbox() - cancelled.emitOutputMarker = false - const controller = new AbortController() - const cancelling = testSpawn( - runtime(cancelled), - spec({ signal: controller.signal }), - '/runtime/cancel-output-boundary', - ) - await vi.waitFor(() => { expect(cancelled.inputs).toHaveLength(1) }) - await new Promise(resolve => setTimeout(resolve, 0)) - controller.abort(new Error('cancel output boundary')) - await expect(cancelling).rejects.toThrow('cancel output boundary') - }) -}) - -describe('E2B terminal lifecycle', () => { - it('aborts and joins in-flight terminal operations before cleanup', async () => { - const fake = new FakeTerminalSandbox() - const terminal = await testSpawn(runtime(fake), spec(), '/runtime/in-flight-operations') - const writeStarted = Promise.withResolvers() - const inspectStarted = Promise.withResolvers() - const signalStarted = Promise.withResolvers() - fake.sendInputRequest = holdRequestUntilAbort(writeStarted) - let foregroundRequests = 0 - fake.foregroundRequest = async (signal) => { - foregroundRequests += 1 - if (foregroundRequests === 1) await holdRequestUntilAbort(inspectStarted)(signal) - } - let signalCompleted = false - fake.signalRequest = async (operationSignal) => { - await holdRequestUntilAbort(signalStarted)(operationSignal) - signalCompleted = true - } - const write = terminal.write('late input') - const inspect = terminal.inspectForeground() - await Promise.all([writeStarted.promise, inspectStarted.promise]) - const signal = terminal.signalForeground('SIGINT') - await signalStarted.promise - - const terminating = terminal.terminate() - await expect(write).rejects.toThrow('terminal is terminating') - await expect(inspect).rejects.toThrow('terminal is terminating') - await expect(signal).rejects.toThrow('terminal is terminating') - await terminating - expect(signalCompleted).toBe(false) - expect(fake.inputs).toHaveLength(1) - const commandCount = fake.commands.length - await expect(terminal.write('after termination')).rejects.toThrow('terminal is terminating') - await expect(terminal.inspectForeground()).rejects.toThrow('terminal is terminating') - await expect(terminal.signalForeground('SIGINT')).rejects.toThrow('terminal is terminating') - expect(fake.commands).toHaveLength(commandCount) - }) - - it('maps ordinary exits, closes output, and reports an absent foreground after exit', async () => { - const fake = new FakeTerminalSandbox() - fake.groups = [] - const terminal = await testSpawn(runtime(fake), spec(), '/runtime/natural') - terminal.output.resume() - const ended = once(terminal.output, 'end') - fake.handle.succeed(7) - await expect(terminal.done).resolves.toEqual({ exitCode: 7, signal: null }) - await ended - await expect(terminal.write('late')).rejects.toThrow('exited') - fake.foregroundFailure = commandError(1) - await expect(terminal.inspectForeground()).resolves.toBeUndefined() - await expect(terminal.signalForeground('SIGINT')).rejects.toThrow('cannot resolve foreground process group') - await terminal.terminate() - }) - - it.each([ - [7, { exitCode: 7, signal: null }], - [143, { exitCode: 143, signal: null }], - [255, { exitCode: 255, signal: null }], - ] as const)('classifies an unrequested command exit %i', async (exitCode, expected) => { - const fake = new FakeTerminalSandbox() - fake.groups = [] - const terminal = await testSpawn(runtime(fake), spec(), `/runtime/exit-${exitCode}`) - fake.handle.fail(exitCode) - await expect(terminal.done).resolves.toEqual(expected) - await terminal.terminate() - }) - - it('treats a terminal session containing only zombies as quiescent', async () => { - const fake = new FakeTerminalSandbox() - fake.groups = [] - fake.zombieGroups = [123] - const terminal = await testSpawn(runtime(fake), spec(), '/runtime/zombie-session') - - fake.handle.succeed(0) - await expect(terminal.done).resolves.toEqual({ exitCode: 0, signal: null }) - await terminal.terminate() - expect(fake.commands).toContain( - "set -o pipefail; ps -eo sid=,pgid=,stat= | awk '$1 == 123 && $3 !~ /^[ZXx]/ { print $2 }'", - ) - }) - - it('treats a timeout-killed sandbox as quiescent during terminal cleanup', async () => { - const fake = new FakeTerminalSandbox() - const terminal = await testSpawn(runtime(fake), spec(), '/runtime/expired-sandbox') - fake.sessionGroupsFailure = new SandboxNotFoundError('sandbox expired') - fake.handle.succeed(0) - - await expect(terminal.done).resolves.toEqual({ exitCode: 0, signal: null }) - await terminal.terminate() - }) - - it('treats sandbox disappearance during PTY kill as quiescent', async () => { - const fake = new FakeTerminalSandbox() - fake.groups = [] - fake.handle.settleOnSdkKill = false - fake.handle.sdkKillError = new SandboxNotFoundError('sandbox expired') - const terminal = await testSpawn(runtime(fake), spec({ graceMs: 1 }), '/runtime/expired-pty-kill') - - await terminal.terminate() - expect(fake.handle.sdkKills).toBe(1) - }) - - it('propagates a non-missing PTY kill failure', async () => { - const fake = new FakeTerminalSandbox() - fake.groups = [] - fake.handle.settleOnSdkKill = false - fake.handle.sdkKillError = new Error('PTY kill transport failed') - const terminal = await testSpawn(runtime(fake), spec({ graceMs: 1 }), '/runtime/failed-pty-kill') - - await expect(terminal.terminate()).rejects.toThrow('PTY kill transport failed') - fake.handle.sdkKillError = undefined - fake.handle.succeed(0) - await terminal.done - await terminal.terminate() - }) - - it.each([ - ['accepts sandbox loss', new SandboxNotFoundError('sandbox expired'), true], - ['propagates another failure', new Error('disconnect failed'), false], - ] as const)('%s while disconnecting a settled terminal', async (_label, failure, accepted) => { - const fake = new FakeTerminalSandbox() - const terminal = await testSpawn(runtime(fake), spec(), `/runtime/disconnect-${accepted}`) - fake.handle.disconnectError = failure - fake.groups = [] - fake.handle.succeed(0) - - if (accepted) await expect(terminal.terminate()).resolves.toBeUndefined() - else await expect(terminal.terminate()).rejects.toThrow('disconnect failed') - }) - - it('rejects killing the terminal shell and propagates live foreground failures', async () => { - const fake = new FakeTerminalSandbox() - fake.foreground = '123\n' - const terminal = await testSpawn(runtime(fake), spec(), '/runtime/signal') - await expect(terminal.signalForeground('SIGKILL')).rejects.toThrow('refusing to SIGKILL') - fake.foreground = 'invalid\n' - await expect(terminal.inspectForeground()).rejects.toThrow('cannot resolve foreground') - fake.foregroundFailure = commandError(1) - await expect(terminal.inspectForeground()).resolves.toBeUndefined() - fake.foregroundFailure = commandError(2) - await expect(terminal.inspectForeground()).rejects.toBeInstanceOf(CommandExitError) - fake.clearOnTerm = true - await terminal.terminate() - }) - - it('sends KILL before checking an expired force-cleanup deadline', async () => { - const fake = new FakeTerminalSandbox() - fake.groups = [123, 456] - fake.clearOnTerm = false - const terminal = await testSpawn(runtime(fake), spec({ graceMs: 0 }), '/runtime/escalate') - const terminating = terminal.terminate() - await expect(terminal.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' }) - await terminating - expect(fake.commands).toContain('kill -TERM -- -123 -456') - expect(fake.commands).toContain('kill -KILL -- -123 -456') - }) - - it('surfaces cleanup failures and allows a later retry', async () => { - const fake = new FakeTerminalSandbox() - fake.groups = [1] - const terminal = await testSpawn(runtime(fake), spec({ graceMs: 1 }), '/runtime/retry') - await expect(terminal.terminate()).rejects.toThrow('unsafe process group 1') - - fake.groups = [] - fake.handle.succeed(0) - await terminal.done - await terminal.terminate() - }) - - it('propagates a process-group signalling transport failure before retry', async () => { - const fake = new FakeTerminalSandbox() - fake.termFailure = new Error('signal transport failed') - const terminal = await testSpawn(runtime(fake), spec({ graceMs: 1 }), '/runtime/signal-failure') - await expect(terminal.terminate()).rejects.toThrow('signal transport failed') - - fake.groups = [] - fake.handle.succeed(0) - await terminal.done - await terminal.terminate() - - const alreadyExited = new FakeTerminalSandbox() - alreadyExited.termFailure = commandError(1) - const tolerant = await testSpawn(runtime(alreadyExited), spec({ graceMs: 1 }), '/runtime/group-exited') - const tolerantTermination = tolerant.terminate() - await expect(tolerant.done).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' }) - await tolerantTermination - }) - - it('keeps command rejection authoritative while cleanup is already waiting', async () => { - const fake = new FakeTerminalSandbox() - fake.groups = [] - fake.removeError = new Error('private state already gone') - const terminal = await testSpawn(runtime(fake), spec(), '/runtime/reject-during-cleanup') - terminal.output.on('error', () => {}) - const cleanup = terminal.terminate() - await Promise.resolve() - fake.handle.crash(new Error('command transport failed')) - await expect(terminal.done).rejects.toThrow('command transport failed') - await cleanup - }) - - it('keeps a late command rejection authoritative after PTY kill', async () => { - const fake = new FakeTerminalSandbox() - fake.groups = [] - fake.handle.settleOnSdkKill = false - const terminal = await testSpawn(runtime(fake), spec({ graceMs: 1 }), '/runtime/reject-after-kill') - terminal.output.on('error', () => {}) - const cleanup = terminal.terminate() - while (fake.handle.sdkKills === 0) await new Promise(resolve => setTimeout(resolve, 0)) - await Promise.resolve() - fake.handle.crash(new Error('late command transport failed')) - await expect(terminal.done).rejects.toThrow('late command transport failed') - await cleanup - }) - - it('reports surviving groups, a surviving top-level pid, and transport failure', async () => { - const survivor = new FakeTerminalSandbox() - survivor.clearOnTerm = false - survivor.clearOnKill = false - const terminal = await testSpawn(runtime(survivor), spec({ graceMs: 1 }), '/runtime/survivor') - await expect(terminal.terminate()).rejects.toThrow('surviving process groups: 123') - - const livePid = new FakeTerminalSandbox() - livePid.groups = [] - livePid.handle.settleOnSdkKill = false - const live = await testSpawn(runtime(livePid), spec({ graceMs: 1 }), '/runtime/live-pid') - await expect(live.terminate()).rejects.toThrow('surviving pid: 123') - livePid.handle.succeed(0) - await live.done - - const crashed = new FakeTerminalSandbox() - crashed.groups = [] - const failed = await testSpawn(runtime(crashed), spec(), '/runtime/crashed') - const outputError = once(failed.output, 'error') - crashed.handle.crash('transport gone') - await expect(failed.done).rejects.toEqual('transport gone') - await expect(outputError).resolves.toMatchObject([{ message: 'transport gone' }]) - await failed.terminate() - }) -}) - -describe('E2B subprocess terminal service', () => { - async function service(fake = new FakeTerminalSandbox()): Promise<{ - ctx: Context - fiber: Awaited> - fake: FakeTerminalSandbox - }> { - const ctx = new Context() - ctx.provide('e2b', runtime(fake)) - const fiber = await ctx.plugin(E2BSubprocessRuntime) - return { ctx, fiber, fake } - } - - it('resolves remote executables', async () => { - const { ctx, fake } = await service() - await expect(ctx.subprocess.resolveExecutable('/bin/bash')).resolves.toBe('/bin/bash') - await expect(ctx.subprocess.resolveExecutable('node', { PATH: '/custom/bin' }, new AbortController().signal)) - .resolves.toBe('/usr/bin/node') - fake.resolvedExecutable = 'tools/bin/node\n' - await expect(ctx.subprocess.resolveExecutable('node', { PATH: 'tools/bin' })) - .resolves.toBe('/workspace/tools/bin/node') - const commandOptions = fake.commandOptions.at(-1) - expect(commandOptions).toMatchObject({ cwd: '/workspace' }) - expect(commandOptions?.envs?.HOME).toMatch(/^\/\.dsh-e2b-control-/) - expect(commandOptions?.envs).toEqual({ HOME: commandOptions?.envs?.HOME }) - expect((ctx.e2b)).toBeDefined() - }) - - it('rejects invalid executable lookup inputs and results', async () => { - const { ctx, fake } = await service() - await expect(ctx.subprocess.resolveExecutable('')).rejects.toThrow('non-empty') - await expect(ctx.subprocess.resolveExecutable('./bin/server')).rejects.toThrow('is a relative path') - await expect(ctx.subprocess.resolveExecutable('node_modules/.bin/server')).rejects.toThrow('is a relative path') - await expect(ctx.subprocess.resolveExecutable('node', undefined, AbortSignal.abort(new Error('stop')))) - .rejects.toThrow('stop') - fake.resolvedExecutable = 'node\n' - await expect(ctx.subprocess.resolveExecutable('node')).rejects.toThrow('did not resolve') - fake.resolvedExecutable = '/one\n/two\n' - await expect(ctx.subprocess.resolveExecutable('node')).rejects.toThrow('did not resolve') - }) - - it('rejects a non-positive poll cadence at load', async () => { - const ctx = new Context() - ctx.provide('e2b', runtime(new FakeTerminalSandbox())) - await expect(ctx.plugin(E2BSubprocessRuntime, { pollMs: 0 })) - .rejects.toThrow('pollMs must be a positive safe integer') - const explicit = await ctx.plugin(E2BSubprocessRuntime, { pollMs: 5 }) - await explicit.dispose() - }) - - it('owns live terminals through service disposal', async () => { - const { ctx, fiber, fake } = await service() - const terminal = await ctx.subprocess.spawnTerminal(spec({ signal: new AbortController().signal })) - await fiber.dispose() - await expect(terminal.done).resolves.toEqual({ exitCode: null, signal: 'SIGTERM' }) - expect(fake.handle.disconnects).toBe(1) - }) - - it('joins and rejects terminal setup that completes during service disposal', async () => { - const fake = new FakeTerminalSandbox() - const { ctx, fiber } = await service(fake) - let disposing: Promise | undefined - fake.afterSessionLookup = () => { - fake.afterSessionLookup = undefined - queueMicrotask(() => { - queueMicrotask(() => { disposing = fiber.dispose() }) - }) - } - const subprocess = ctx.subprocess - const spawning = ctx.subprocess.spawnTerminal(spec()) - const rejected = expect(spawning).rejects.toThrow('service disposed during terminal setup') - await vi.waitFor(() => { expect(disposing).toBeDefined() }) - await expect(subprocess.spawnTerminal(spec())).rejects.toThrow('service is disposing') - - await rejected - await disposing - expect(fake.groups).toEqual([]) - expect(fake.handle.disconnects).toBe(1) - expect(fake.removed.some(path => path.includes('/terminals/'))).toBe(true) - }) - - it('aborts and rolls back terminal setup that cannot publish its output boundary during disposal', async () => { - const fake = new FakeTerminalSandbox() - fake.emitOutputMarker = false - const { ctx, fiber } = await service(fake) - const spawning = ctx.subprocess.spawnTerminal(spec()) - const rejected = expect(spawning).rejects.toThrow('service disposed during terminal setup') - await vi.waitFor(() => { expect(fake.inputs).toHaveLength(1) }) - - await fiber.dispose() - await rejected - expect(fake.groups).toEqual([]) - expect(fake.handle.disconnects).toBe(1) - }) - - it('owns and cancels terminal state-directory creation during disposal', async () => { - const fake = new FakeTerminalSandbox() - fake.makeDirRequest = async (signal) => { - await new Promise((_resolve, reject) => { - const onAbort = (): void => { - const reason: unknown = signal?.reason - reject(reason instanceof Error ? reason : new Error(String(reason))) - } - signal?.addEventListener('abort', onAbort, { once: true }) - if (signal?.aborted === true) onAbort() - }) - } - const { ctx, fiber } = await service(fake) - const spawning = ctx.subprocess.spawnTerminal(spec()) - const rejected = expect(spawning).rejects.toThrow('service disposed during terminal setup') - await vi.waitFor(() => { expect(fake.directories.some(path => path.includes('/terminals/'))).toBe(true) }) - - await fiber.dispose() - await rejected - expect(fake.removed.some(path => path.includes('/terminals/'))).toBe(true) - expect(fake.createOptions).toBeUndefined() - }) - - it('releases naturally settled terminals and validates terminal requests', async () => { - const { ctx, fiber, fake } = await service() - for (const request of [ - spec({ argv: [] }), - spec({ signal: AbortSignal.abort(new Error('cancelled')) }), - ]) { - await expect(ctx.subprocess.spawnTerminal(request)).rejects.toThrow() - } - - fake.groups = [] - const terminal = await ctx.subprocess.spawnTerminal(spec()) - fake.handle.succeed(0) - await terminal.done - await terminal.terminate() - const signals = fake.commands.filter(command => command.startsWith('kill -')).length - await fiber.dispose() - expect(fake.commands.filter(command => command.startsWith('kill -'))).toHaveLength(signals) - }) - - it('contains a failed automatic terminal release until service disposal retries it', async () => { - const { fiber, fake } = await service() - fake.clearOnTerm = false - fake.clearOnKill = false - const terminal = await (fiber.ctx).subprocess.spawnTerminal(spec({ graceMs: 1 })) - fake.handle.succeed(0) - await terminal.done - await new Promise(resolve => setTimeout(resolve, 10)) - expect(fake.commands).toContain('kill -KILL -- -123') - - fake.groups = [] - await fiber.dispose() - await expect(terminal.terminate()).resolves.toBeUndefined() - }) - - it('contains an immediate automatic terminal release rejection before disposal retries it', async () => { - const { fiber, fake } = await service() - fake.groups = [] - const terminal = await (fiber.ctx).subprocess.spawnTerminal(spec()) - const terminate = vi.spyOn(terminal, 'terminate') - .mockRejectedValueOnce(new Error('automatic release failed')) - fake.handle.succeed(0) - await terminal.done - await vi.waitFor(() => { expect(terminate).toHaveBeenCalledTimes(1) }) - await new Promise(resolve => setTimeout(resolve, 0)) - - await fiber.dispose() - expect(terminate).toHaveBeenCalledTimes(2) - expect(fake.handle.disconnects).toBe(1) - }) -}) diff --git a/packages/e2b/subprocess-e2b/tsconfig.json b/packages/e2b/subprocess-e2b/tsconfig.json deleted file mode 100644 index 8a35f8298a..0000000000 --- a/packages/e2b/subprocess-e2b/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "lib/types" - }, - "include": [ - "src" - ], - "references": [ - { - "path": "../../../vendor/cordis" - }, - { - "path": "../../../vendor/cosmokit" - }, - { - "path": "../../../vendor/schemastery" - }, - { - "path": "../../subprocess/subprocess" - }, - { - "path": "../../util/timeout" - }, - { - "path": "../e2b" - } - ] -} diff --git a/packages/experimental/agent-team-profile/README.i18n.yaml b/packages/experimental/agent-team-profile/README.i18n.yaml index 2daf763a5f..addd6193d7 100644 --- a/packages/experimental/agent-team-profile/README.i18n.yaml +++ b/packages/experimental/agent-team-profile/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/experimental/agent-team-profile/README.md -README.md: 511b42269d68e508c797bbd9499c462a269bf552 -README.zh.md: 243fd9145982d05626c04cae758e7cb3c688ebd9 +README.md: 86ac08c5d2278013ecbc371301bbb6a3a01a65d9 +README.zh.md: 973f5f2342b32109d26291d762f4ad7b9b4c3306 diff --git a/packages/experimental/agent-team-profile/README.md b/packages/experimental/agent-team-profile/README.md index 511b42269d..86ac08c5d2 100644 --- a/packages/experimental/agent-team-profile/README.md +++ b/packages/experimental/agent-team-profile/README.md @@ -1,5 +1,5 @@ --- -description: "Published experimental Agent Teams profile layer over dsh-base with Team-scoped coordination tools and one-shot delegation." +description: "Published experimental Agent Teams profile layer over dsh-base with teammate delegation and fresh workflow children." kind: "package-bundle" --- @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -`dsh-experimental-agent-team-profile` is a published experimental profile layer that enables [Agent Teams](../agent-team/README.md) over `@deepseek-ai/dsh-base`. Its patch inserts the Team domain and Team-scoped tools, disables the overlapping global continuable-child controls, and keeps the ordinary fresh and fork delegation tools as one-shot operations. Add it explicitly to an initialized profile; no shipped profile enables it by default. +`dsh-experimental-agent-team-profile` is a published experimental profile layer that enables [Agent Teams](../agent-team/README.md) over `@deepseek-ai/dsh-base`. Its patch inserts the Team domain and Team-scoped tools and disables ordinary subagent delegation and the overlapping global continuable-child controls. Workflow remains available with fresh children. Add it explicitly to an initialized profile; no shipped profile enables it by default. ## Table of Contents @@ -38,7 +38,7 @@ The profile must already contain `@deepseek-ai/dsh-base`, whose Subagent service ### What you get -The layer adds the Agent Teams domain and its scoped creation, roster, messaging, interruption, waiting, and task-board tools. It disables the global continuable-child control rows whose tool names overlap with Team controls, while leaving `subagent` and `subagent_fork` available as one-shot delegation tools. +The layer adds the Agent Teams domain and its scoped creation, roster, messaging, interruption, waiting, and task-board tools. Direct delegation uses `spawn_teammate`, which supports fresh and fork context. The `subagent` and `subagent_fork` tools and overlapping global child controls are disabled. Workflow retains the base profile’s `spawn` provider, while the underlying Subagent services and both providers remain available to teammates and workflow. ----- @@ -48,7 +48,7 @@ The layer adds the Agent Teams domain and its scoped creation, roster, messaging
Implementation internals — click to expand -The package's runtime content is [`cordis.patch.yml`](cordis.patch.yml). Applied after `dsh-base`, the patch disables `tool-subagent-control` and `tool-subagent-list-agents`; sets the fresh and fork Subagent rows to `one-shot`; and inserts the Team service and tool rows with explicit providers and limits. +The package's runtime content is [`cordis.patch.yml`](cordis.patch.yml). Applied after `dsh-base`, the patch disables `tool-subagent-control`, `tool-subagent-list-agents`, `tool-subagent`, and `tool-subagent-fork`, and inserts the Team service and tool rows with explicit providers and limits. | File | Role | |---|---| @@ -77,7 +77,7 @@ The package's runtime content is [`cordis.patch.yml`](cordis.patch.yml). Applied #### What the model sees -The Team policy and schemas belong to [`@deepseek-ai/dsh-experimental-tool-agent-team`](../tool-agent-team/README.md). This bundle changes composition only: Team-scoped `list_agents`, `send_message`, and `interrupt_agent` replace the disabled global continuable-child controls. `subagent` and `subagent_fork` remain available as one-shot delegation tools, whose children do not receive the continuable-child `report` tool. +The Team policy and schemas belong to [`@deepseek-ai/dsh-experimental-tool-agent-team`](../tool-agent-team/README.md). This bundle changes composition only: Team-scoped `list_agents`, `send_message`, and `interrupt_agent` replace the disabled global continuable-child controls. `spawn_teammate` is the direct delegation tool. Workflow’s `agent()` calls create fresh one-shot children; their prompts must contain the context needed for their tasks. #### Token effect @@ -92,6 +92,7 @@ The bundle's composition is prefix-stable while its patch, Team identity, and co - **Opt-in only** — the package is public, but no shipped CLI, Web, SDK, ACP, or Python profile enables it. +- **Workflow child tools** — the [Team tool visibility limitation](../tool-agent-team/README.md#known-limitations-and-deferred-work) also applies to workflow children. - **Shared checkout** — every teammate observes the same working directory; this bundle adds no worktree isolation or filesystem locking. - **Base profile required** — the patch depends on row ids and Subagent providers supplied by `dsh-base`; it is not a standalone profile. diff --git a/packages/experimental/agent-team-profile/README.zh.md b/packages/experimental/agent-team-profile/README.zh.md index 243fd91459..973f5f2342 100644 --- a/packages/experimental/agent-team-profile/README.zh.md +++ b/packages/experimental/agent-team-profile/README.zh.md @@ -1,5 +1,5 @@ --- -description: "叠加在 dsh-base 上公开发布的实验性 Agent Teams profile 层,提供 Team-scoped 协作工具并保留一次性 delegation。" +description: "叠加在 dsh-base 上公开发布的实验性 Agent Teams profile 层,提供 teammate 委派与 fresh workflow 子代理。" kind: "package-bundle" --- @@ -9,7 +9,7 @@ kind: "package-bundle" ## 概述 -`dsh-experimental-agent-team-profile` 是在 `@deepseek-ai/dsh-base` 之上启用 [Agent Teams](../agent-team/README.zh.md) 的公开实验性 profile 层。它的 patch 会插入 Team domain 与 Team-scoped 工具、禁用名称重叠的全局 continuable-child control,并保留普通的一次性 fresh 与 fork delegation 工具。必须将本包显式添加到已初始化的 profile;随附 profile 默认都不会启用它。 +`dsh-experimental-agent-team-profile` 是在 `@deepseek-ai/dsh-base` 之上启用 [Agent Teams](../agent-team/README.zh.md) 的公开实验性 profile 层。它的 patch 会插入 Team domain 与 Team-scoped 工具,并禁用普通 subagent 委派和名称重叠的全局 continuable-child control。Workflow 仍可创建 fresh 子代理。必须将本包显式添加到已初始化的 profile;随附 profile 默认都不会启用它。 ## 目录 @@ -38,7 +38,7 @@ profile 必须已经包含 `@deepseek-ai/dsh-base`,本层会使用其中的 Su ### 获得的功能 -本层会添加 Agent Teams domain,以及 Team-scoped 创建、roster、消息、interrupt、等待与任务板工具。它会禁用工具名与 Team control 重叠的全局 continuable-child control 行,同时保留 `subagent` 与 `subagent_fork` 作为一次性 delegation 工具。 +本层会添加 Agent Teams domain,以及 Team-scoped 创建、roster、消息、interrupt、等待与任务板工具。直接委派使用支持 fresh 和 fork 上下文的 `spawn_teammate`。`subagent`、`subagent_fork` 工具和名称重叠的全局 child control 均被禁用。Workflow 保留 base profile 的 `spawn` 提供方,底层 Subagent 服务和两个提供方仍供 teammate 与 workflow 使用。 ----- @@ -48,7 +48,7 @@ profile 必须已经包含 `@deepseek-ai/dsh-base`,本层会使用其中的 Su
实现细节——点击展开 -本包的运行时内容是 [`cordis.patch.yml`](cordis.patch.yml)。在 `dsh-base` 之后应用时,patch 会禁用 `tool-subagent-control` 与 `tool-subagent-list-agents`,把 fresh 与 fork Subagent 行设置为 `one-shot`,并以显式 provider 和限制插入 Team 服务与工具行。 +本包的运行时内容是 [`cordis.patch.yml`](cordis.patch.yml)。在 `dsh-base` 之后应用时,patch 会禁用 `tool-subagent-control`、`tool-subagent-list-agents`、`tool-subagent` 和 `tool-subagent-fork`,并以显式 provider 和限制插入 Team 服务与工具行。 | 文件 | 职责 | |---|---| @@ -77,7 +77,7 @@ profile 必须已经包含 `@deepseek-ai/dsh-base`,本层会使用其中的 Su #### 模型会看到什么 -Team 策略与 schema 由 [`@deepseek-ai/dsh-experimental-tool-agent-team`](../tool-agent-team/README.zh.md) 所有。本 bundle 只改变 composition:Team-scoped `list_agents`、`send_message` 与 `interrupt_agent` 会替代已禁用的全局 continuable-child control。`subagent` 与 `subagent_fork` 仍作为一次性 delegation 工具可用,其子 agent 不会获得 continuable-child `report` 工具。 +Team 策略与 schema 由 [`@deepseek-ai/dsh-experimental-tool-agent-team`](../tool-agent-team/README.zh.md) 所有。本 bundle 只改变 composition:Team-scoped `list_agents`、`send_message` 与 `interrupt_agent` 会替代已禁用的全局 continuable-child control。`spawn_teammate` 是直接委派工具。Workflow 的 `agent()` 调用创建 fresh 一次性子代理;其提示词必须包含任务所需的上下文。 #### Token 影响 @@ -92,6 +92,7 @@ Team 策略与 schema 由 [`@deepseek-ai/dsh-experimental-tool-agent-team`](../t - **仅显式启用**——本包公开发布,但随附 CLI、Web、SDK、ACP 与 Python profile 都不会启用它。 +- **Workflow 子代理工具**——[Team 工具可见性限制](../tool-agent-team/README.zh.md#known-limitations-and-deferred-work)也适用于 workflow 子代理。 - **共享 checkout**——所有 teammate 都观察同一个工作目录;本 bundle 不提供 worktree 隔离或文件系统锁。 - **需要 base profile**——本 patch 依赖 `dsh-base` 提供的配置行 id 与 Subagent 提供方;它不是独立 profile。 diff --git a/packages/experimental/agent-team-profile/cordis.patch.yml b/packages/experimental/agent-team-profile/cordis.patch.yml index c5e3730d1e..f1eee0e9ba 100644 --- a/packages/experimental/agent-team-profile/cordis.patch.yml +++ b/packages/experimental/agent-team-profile/cordis.patch.yml @@ -1,6 +1,5 @@ # Experimental Agent Teams profile layer. Apply after dsh-base so these replacements -# remove the global continuable-child controls before the scoped Team tools -# register the overlapping list_agents, send_message, and interrupt_agent names. +# keep direct delegation and coordination on the Team tools. - id: tool-subagent-control disabled: true @@ -9,16 +8,10 @@ disabled: true - id: tool-subagent - config: - provider: spawn - toolName: subagent - backgroundMode: one-shot + disabled: true - id: tool-subagent-fork - config: - provider: fork - toolName: subagent_fork - backgroundMode: one-shot + disabled: true - insert: - id: agent-team diff --git a/packages/experimental/agent-team-profile/tests/profile.spec.ts b/packages/experimental/agent-team-profile/tests/profile.spec.ts index 133f7a6d99..fdebc49182 100644 --- a/packages/experimental/agent-team-profile/tests/profile.spec.ts +++ b/packages/experimental/agent-team-profile/tests/profile.spec.ts @@ -37,8 +37,8 @@ describe('Agent Teams profile bundle', () => { }[] expect(patches.find(patch => patch.id === 'tool-subagent-control')).toMatchObject({ disabled: true }) expect(patches.find(patch => patch.id === 'tool-subagent-list-agents')).toMatchObject({ disabled: true }) - expect(patches.find(patch => patch.id === 'tool-subagent')?.config).toMatchObject({ backgroundMode: 'one-shot' }) - expect(patches.find(patch => patch.id === 'tool-subagent-fork')?.config).toMatchObject({ backgroundMode: 'one-shot' }) + expect(patches.find(patch => patch.id === 'tool-subagent')).toMatchObject({ disabled: true }) + expect(patches.find(patch => patch.id === 'tool-subagent-fork')).toMatchObject({ disabled: true }) const inserted = patches.flatMap(patch => patch.insert ?? []) expect(inserted.find(entry => entry.id === 'agent-team')).toMatchObject({ name: '@deepseek-ai/dsh-experimental-agent-team', diff --git a/packages/experimental/agent-team/README.i18n.yaml b/packages/experimental/agent-team/README.i18n.yaml index 6575a3ae22..56fefe2904 100644 --- a/packages/experimental/agent-team/README.i18n.yaml +++ b/packages/experimental/agent-team/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/experimental/agent-team/README.md -README.md: 3aa5f8606c76111dc2a85ce505f7989b15cf3b78 -README.zh.md: edf69e9d7dc852d75ca276dba6cddd950929df81 +README.md: 1dba55aaef5c2f061de85de3b38cf5c644fa90b7 +README.zh.md: 749f5b9d27ed863639a6dabad80254c9a19727dd diff --git a/packages/experimental/agent-team/README.md b/packages/experimental/agent-team/README.md index 3aa5f8606c..1dba55aaef 100644 --- a/packages/experimental/agent-team/README.md +++ b/packages/experimental/agent-team/README.md @@ -48,7 +48,7 @@ With the tools installed, the model does the rest on request — for example, "c | Field | Default | Meaning | |---|---|---| -| `maxMembers` | `8` | Maximum teammates a team may ever create, including failed ones | +| `maxMembers` | `16` | Maximum teammates a team may ever create, including failed ones | | `maxTasks` | `256` | Maximum active tasks on the board | | `maxPendingMessagesPerMember` | `64` | Maximum queued messages for one member | | `maxMessageBytes` | `65,536` | Maximum size of one sent message | diff --git a/packages/experimental/agent-team/README.zh.md b/packages/experimental/agent-team/README.zh.md index edf69e9d7d..749f5b9d27 100644 --- a/packages/experimental/agent-team/README.zh.md +++ b/packages/experimental/agent-team/README.zh.md @@ -48,7 +48,7 @@ kind: "package-reference" | 字段 | 默认值 | 含义 | |---|---|---| -| `maxMembers` | `8` | 一支团队最多可创建的 teammate 数,包括失败的 | +| `maxMembers` | `16` | 一支团队最多可创建的 teammate 数,包括失败的 | | `maxTasks` | `256` | 任务板上最多的活动任务数 | | `maxPendingMessagesPerMember` | `64` | 单个成员最多可排队的消息数 | | `maxMessageBytes` | `65,536` | 单条发送消息的最大尺寸 | diff --git a/packages/experimental/agent-team/src/index.ts b/packages/experimental/agent-team/src/index.ts index 6fa0500eeb..cb4fcea25a 100644 --- a/packages/experimental/agent-team/src/index.ts +++ b/packages/experimental/agent-team/src/index.ts @@ -41,7 +41,7 @@ declare module '@deepseek-ai/cordis' { } } -const DEFAULT_MAX_MEMBERS = 8 +const DEFAULT_MAX_MEMBERS = 16 const DEFAULT_MAX_TASKS = 256 const DEFAULT_MAX_PENDING_MESSAGES = 64 const DEFAULT_MAX_MESSAGE_BYTES = 65_536 diff --git a/packages/experimental/tool-agent-team/README.i18n.yaml b/packages/experimental/tool-agent-team/README.i18n.yaml index c05220948d..3658a87d71 100644 --- a/packages/experimental/tool-agent-team/README.i18n.yaml +++ b/packages/experimental/tool-agent-team/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/experimental/tool-agent-team/README.md -README.md: 58e2853a863710ec094f61b0af04a0fe9b6bd4ce -README.zh.md: 5dd4d0fd100f7f962ad89b1e463776d7e0e3d8e0 +README.md: 225b9cb90173803ba7c777c5d643e04e47c6c602 +README.zh.md: 382c60ee0c92c26919d52ae24137913800fb5fa7 diff --git a/packages/experimental/tool-agent-team/README.md b/packages/experimental/tool-agent-team/README.md index 58e2853a86..225b9cb901 100644 --- a/packages/experimental/tool-agent-team/README.md +++ b/packages/experimental/tool-agent-team/README.md @@ -25,7 +25,7 @@ This package lets the model create named teammates, send them messages, inspect ## Use this package -Add this package on top of `@deepseek-ai/dsh-experimental-agent-team` when the model should run a team through tools. Once mounted, every team member — the Lead and each teammate — gets the same nine tools plus a policy paragraph that states its own role and name. +Add this package on top of `@deepseek-ai/dsh-experimental-agent-team` when the model should run a team through tools. Once mounted, every team member — the Lead and each teammate — gets the same nine tools plus the same coordination policy. `spawn_teammate` prefixes the initial task with the teammate’s role and name. ### When to choose it @@ -81,7 +81,7 @@ This section explains the design decisions behind the adapter and points at the The adapter is built on three commitments: -- **Scoped, not global.** Every registration lives on the member Agent's own `ctx`; nothing is installed for non-Team subagents or the host. +- **Scoped, not global.** Every registration lives on the member Agent's own `ctx`; installation uses the member identity available when the Agent is published. - **Declared results, compact JSON.** Every tool declares its complete result schema and renders that value as compact JSON, so the compiler checks `execute` against what the model is promised and no result spends tokens on indentation. - **The domain owns authority.** Tools delegate to `ctx.agentTeams`, which enforces Lead authority and revision checks; the adapter adds no weaker path. @@ -96,7 +96,7 @@ The [Agent Teams Agent Note](../../../.agents/notes/implemented/feature/2026-08- ### Policy and tools -One `team:policy` section on the member scope teaches each member its role and the coordination rules; the fixed text and the nine tool registrations are declared in [`src/index.ts`](src/index.ts). The nine tool schemas appear only in Team member scopes, so non-Team subagents keep the default catalog. Scoped registrations with the same names as the legacy global continuable-subagent controls shadow those globals for team members only. +One `team:policy` section on the member scope states the shared coordination rules; the fixed text and the nine tool registrations are declared in [`src/index.ts`](src/index.ts). The nine tool schemas are registered in scopes recognized as Team members at publication. Scoped registrations with the same names as the legacy global continuable-subagent controls shadow those globals for team members only. ### Scoped registration and teardown @@ -125,20 +125,21 @@ Read these pages when the package-level contract is not enough. They move from t #### What the model sees -One stable policy section states the exact Team role/name/id, the explicit-delegation requirement, shared-cwd behavior, filesystem stale-version recovery, Bash/formatter/codegen risk, task and write-scope coordination, Steer delivery, the no-retry mailbox rule, and the Lead's duty to wait before answering. The nine Team schemas from `spawn_teammate` through `team_task_update` appear only in Team member scopes. +One shared system policy states the explicit-delegation requirement, shared-cwd behavior, filesystem stale-version recovery, Bash/formatter/codegen risk, task and write-scope coordination, Steer delivery, the no-retry mailbox rule, and the Lead's duty to wait before answering. All nine Team schemas are identical for Leads and teammates; execution enforces Lead-only operations. `spawn_teammate` prefixes its initial user message with `\nYou are teammate "".\n`, followed by a blank line and the task. The prefix contains no Team id and works when runtime context is disabled. Forks inherit history without an additional Lead identity message. #### Token effect -Fixed policy and schema cost on every Team member request. Tool calls add compact JSON roster, task, wait, or receipt results. Peer content is retained by the Team domain in the target's history. +Fixed policy and schema cost on every Team member request. The initial identity text follows ordinary history through later steps, cold recovery, and compaction; the plugin neither scans for it nor reinserts it. Tool calls add compact JSON roster, task, wait, or receipt results. Peer content is retained by the Team domain in the target's history. #### KV Cache effect -Prefix-stable while the Team plugin generation, configuration, member role/name, and schemas remain unchanged. The per-member identity line differs across Agents. Tool results and peer messages append after the reusable request prefix. +With the same provider/model, shared system policy, and tool schemas, a fork retains the parent request prefix and appends the initial task with its identity prefix. Tool results and peer messages append after the reusable request prefix. Sessions recorded with identity inside the system prompt can change that prefix on their first request under this layout; actual provider cache hits remain best-effort. ## Known Limitations and Deferred Work +- **One-shot child tool visibility** — in-process one-shot children receive their subagent descriptor after publication. Team installation can therefore mistake them for Leads and expose Team policy and tools. Calls are rejected once the descriptor identifies them as non-members. Correcting installation timing is deferred. These limits describe what the policy and tools cannot guarantee for a team. They are current package constraints, not a comparison with other collaboration surfaces. diff --git a/packages/experimental/tool-agent-team/README.zh.md b/packages/experimental/tool-agent-team/README.zh.md index 5dd4d0fd10..382c60ee0c 100644 --- a/packages/experimental/tool-agent-team/README.zh.md +++ b/packages/experimental/tool-agent-team/README.zh.md @@ -25,7 +25,7 @@ kind: "package-reference" ## 使用本包 -当模型应该通过工具运行一支团队时,在 `@deepseek-ai/dsh-experimental-agent-team` 之上挂载本包。挂载后,每个团队成员——Lead 与每个 teammate——都会获得相同的九个工具,外加一段说明自身角色与名字的策略段落。 +当模型应该通过工具运行一支团队时,在 `@deepseek-ai/dsh-experimental-agent-team` 之上挂载本包。挂载后,每个团队成员——Lead 与每个 teammate——都会获得相同的九个工具,以及相同的协作策略。`spawn_teammate` 在初始任务前加上 teammate 的角色和名字。 ### 何时选择 @@ -81,7 +81,7 @@ kind: "package-reference" 适配器建立在三项承诺之上: -- **按作用域,而非全局。** 每个注册都位于成员 Agent(智能体)自己的 `ctx` 上;非 Team subagent 或宿主不会安装任何内容。 +- **按作用域,而非全局。** 每个注册都位于成员 Agent(智能体)自己的 `ctx` 上;安装依据 Agent 发布时可用的成员身份。 - **声明式结果,紧凑 JSON。** 每个工具都声明完整结果 schema,并把该值渲染为紧凑 JSON,因此编译器会对照向模型承诺的结果检查 `execute`,任何结果都不会在缩进上消耗 token。 - **领域掌握裁决权。** 工具委托给 `ctx.agentTeams`,后者强制执行 Lead 权限与 revision 校验;适配器不添加更弱的路径。 @@ -96,7 +96,7 @@ kind: "package-reference" ### 策略与工具 -member scope 上的一个 `team:policy` 段落教每个成员自己的角色与协作规则;固定文本与九个工具注册都声明在 [`src/index.ts`](src/index.ts)。九个工具 schema 只出现在 Team member scope 中,因此非 Team subagent 保持默认目录。与旧全局 continuable-subagent 控件同名的 scoped 注册只会为团队成员覆盖这些全局控件。 +member scope 上的一个 `team:policy` 段落说明共享的协作规则;固定文本与九个工具注册都声明在 [`src/index.ts`](src/index.ts)。九个工具 schema 注册在发布时被识别为 Team member 的 scope 中。与旧全局 continuable-subagent 控件同名的 scoped 注册只会为团队成员覆盖这些全局控件。 ### 按作用域注册与拆除 @@ -125,20 +125,21 @@ member scope 上的一个 `team:policy` 段落教每个成员自己的角色与 #### 模型看到什么 -一段稳定策略会说明确切 Team role/name/id、显式 delegation 要求、共享 cwd 行为、文件陈旧版本恢复、Bash/formatter/codegen 风险、task/write-scope 协调、Steer 投递、mailbox 不重试规则,以及 Lead 必须在回答前等待。`spawn_teammate` 到 `team_task_update` 的九个 Team schema 只出现在 Team member scope。 +一段共享 system 策略会说明显式 delegation 要求、共享 cwd 行为、文件陈旧版本恢复、Bash/formatter/codegen 风险、task/write-scope 协调、Steer 投递、mailbox 不重试规则,以及 Lead 必须在回答前等待。Lead 与 teammate 的全部九个 Team schema 相同;执行时检查仅限 Lead 的操作权限。`spawn_teammate` 在初始 user 消息前加上 `\nYou are teammate "".\n`,接着是一个空行和任务。该前缀不含 Team id,禁用运行时上下文时也能生效。fork 继承历史,不额外添加 Lead 身份消息。 #### Token 影响 -每次 Team member 请求都有固定策略与 schema 成本。工具调用会增加紧凑 JSON roster、task、wait 或 receipt 结果。Peer 内容由 Team 领域保留在 target 历史中。 +每次 Team member 请求都有固定策略与 schema 成本。初始身份文本随普通历史经历后续步骤、冷恢复和压缩;插件不扫描或重新插入它。工具调用会增加紧凑 JSON roster、task、wait 或 receipt 结果。Peer 内容由 Team 领域保留在 target 历史中。 #### KV Cache 影响 -Team 插件 generation、配置、member role/name 与 schema 不变时,前缀保持稳定。每个成员的身份行不同。工具结果与 peer 消息追加在可复用请求前缀之后。 +provider/model、共享 system 策略和工具 schema 相同时,fork 保留父请求前缀并追加带身份前缀的初始任务。工具结果与 peer 消息追加在可复用请求前缀之后。原先在 system prompt 中记录身份的 Session,首次使用此布局请求时可能改变该前缀;提供方实际缓存命中仍为尽力而为。 ## 已知限制与延期工作 +- **一次性子代理工具可见性**——进程内一次性子代理在发布后才获得 subagent descriptor。Team 安装因此可能将它们误认作 Lead 并暴露 Team 策略和工具。descriptor 将它们识别为非成员后,调用会被拒绝。安装时序修复留待以后处理。 这些限制说明策略与工具无法为一支团队保证什么。它们是当前包约束,不是与其他协作方式的对比。 diff --git a/packages/experimental/tool-agent-team/package.json b/packages/experimental/tool-agent-team/package.json index be33c259a2..6783456a2d 100644 --- a/packages/experimental/tool-agent-team/package.json +++ b/packages/experimental/tool-agent-team/package.json @@ -53,6 +53,8 @@ "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-experimental-agent-team": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", - "@deepseek-ai/cordis": "workspace:^" + "@deepseek-ai/cordis": "workspace:^", + "@deepseek-ai/dsh-llm": "workspace:^", + "@deepseek-ai/dsh-llm-deepseek": "workspace:^" } } diff --git a/packages/experimental/tool-agent-team/src/index.ts b/packages/experimental/tool-agent-team/src/index.ts index 824c042f23..8b2c3ec4a7 100644 --- a/packages/experimental/tool-agent-team/src/index.ts +++ b/packages/experimental/tool-agent-team/src/index.ts @@ -164,10 +164,7 @@ function install(agent: Agent, ctx: Context, config: Required): () => vo register(scoped.systemPrompt.section({ name: 'team:policy', order: scoped.systemPrompt.getSectionOrder('TEAM_POLICY'), - text: () => { - const membership = ctx.agentTeams.membership(agent) - return `${POLICY}\n\nYour Team role is ${membership.role}; your Team name is ${membership.name}; Team id is ${membership.id}.` - }, + text: POLICY, })) register(scoped.tools.register(defineTool({ @@ -190,7 +187,10 @@ function install(agent: Agent, ctx: Context, config: Required): () => vo return await ctx.agentTeams.spawnTeammate(agent, { name: args.name, description: args.description, - prompt: [{ type: 'text', text: args.prompt }], + prompt: [ + { type: 'text', text: `\nYou are teammate "${args.name.trim()}".\n\n\n` }, + { type: 'text', text: args.prompt }, + ], context, provider: context === 'fork' ? config.forkProvider : config.freshProvider, signal: exec.signal, diff --git a/packages/experimental/tool-agent-team/tests/tool-team.spec.ts b/packages/experimental/tool-agent-team/tests/tool-team.spec.ts index 6ae2fddca3..bd09be0221 100644 --- a/packages/experimental/tool-agent-team/tests/tool-team.spec.ts +++ b/packages/experimental/tool-agent-team/tests/tool-team.spec.ts @@ -6,18 +6,19 @@ import { Context } from '@deepseek-ai/cordis' import type { Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import { ToolCallId } from '@deepseek-ai/dsh-llm' +import { ToolCallId, createUserMessage } from '@deepseek-ai/dsh-llm' import { scopeOf } from '@deepseek-ai/dsh-scope' -import { SessionId } from '@deepseek-ai/dsh-session' +import { SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session' import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' import SessionQueryEngine from '@deepseek-ai/dsh-session-query' import SubagentService from '@deepseek-ai/dsh-subagent' import * as SubagentFork from '@deepseek-ai/dsh-subagent-fork-in-process' import * as SubagentSpawn from '@deepseek-ai/dsh-subagent-spawn-in-process' -import { renderPrompt } from '@deepseek-ai/dsh-system-prompt' +import { renderPrompt, renderContextSnapshot } from '@deepseek-ai/dsh-system-prompt' import * as ToolSubagentControl from '@deepseek-ai/dsh-tool-subagent-control' import { defineContentToolFixture } from '@deepseek-ai/dsh-tools' -import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' +import { serializeRequest } from '@deepseek-ai/dsh-llm-deepseek/src/protocols/chat-completions/serialize.ts' +import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' import TeamService from '../../agent-team/src/index.ts' import * as toolTeam from '../src/index.ts' @@ -35,6 +36,7 @@ const TOOL_NAMES = [ ].sort() const roots: string[] = [] +const contexts = new Set() let callNumber = 0 /** Session query implementation whose search faces are outside these tests. */ @@ -48,12 +50,15 @@ class TestSessionQuery extends SessionQueryEngine { } } -afterEach(() => { +afterEach(async () => { + for (const ctx of contexts) await ctx.fiber.dispose() + contexts.clear() for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) }) async function setup(script: ConstructorParameters[0], legacyControl = false) { const ctx = new Context() + contexts.add(ctx) await mountAgentLoopTestDependencies(ctx) const storageRoot = mkdtempSync(join(tmpdir(), 'dsh-tool-team-')) roots.push(storageRoot) @@ -69,7 +74,7 @@ async function setup(script: ConstructorParameters[0], legac const adapter = new MockAdapter(script) ctx.llm.registerAdapter(['mock'], adapter) const lead = await ctx.agentLoop.create(SessionId('tool-team-lead'), { provider: 'mock', model: 'mock' }) - return { ctx, lead, fiber } + return { ctx, lead, fiber, adapter } } function execute( @@ -110,6 +115,11 @@ async function assembly(ctx: Context, agent: Agent) { return ctx.systemPrompt.assemble({ scope }) } +async function runTurn(agent: Agent, text: string): Promise { + agent.followup(createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } })) + await agent.whenIdle() +} + async function waitRunning(ctx: Context, id: SessionId): Promise { return vi.waitFor(() => { const child = ctx.agents.get(id) @@ -134,25 +144,29 @@ describe('dsh-tool-team', () => { expect(leadPrompt).toContain('Bash, formatters, code generators, and scripts are not fully protected') expect(leadPrompt).toContain('Task readiness never starts an owner') expect(leadPrompt).toContain('returns noProgress immediately') - expect(leadPrompt).toContain('Your Team role is lead') + expect(leadPrompt).not.toContain('Your Team role') + expect(renderContextSnapshot(leadAssembly)).toBe('') const spawned = await execute(ctx, lead, 'spawn_teammate', { name: 'tool-worker', description: 'exercise scoped tools', prompt: 'stay available', }) - expect(spawned.isError).toBe(false) + expect(spawned.isError, text(spawned)).toBe(false) const childId = spawnedChildId(spawned) const child = await waitRunning(ctx, childId) const childAssembly = await assembly(ctx, child) expect(childAssembly.tools.map(schema => schema.name).filter(name => TOOL_NAMES.includes(name)).sort()) .toEqual(TOOL_NAMES) - expect(renderPrompt(childAssembly)).toContain('Your Team role is teammate; your Team name is tool-worker') + expect(renderPrompt(childAssembly)).toBe(leadPrompt) + expect(renderContextSnapshot(childAssembly)).not.toContain('team:identity') + expect(child.session.deriveMessages().some(message => message.content.some(block => + block.type === 'text' && block.text === '\nYou are teammate "tool-worker".\n\n\n'))).toBe(true) const initialPrompt = child.session.snapshotEvents().find(event => event.type === 'user/message' && event.data.source.kind === 'user') expect(initialPrompt?.type === 'user/message' ? initialPrompt.data.content.flatMap(block => block.type === 'text' ? [block.text] : []) - : []).toEqual(['stay available']) + : []).toEqual(['\nYou are teammate "tool-worker".\n\n\n', 'stay available']) const denied = await execute(ctx, child, 'spawn_teammate', { name: 'nested', description: 'not allowed', prompt: 'no', @@ -163,6 +177,174 @@ describe('dsh-tool-team', () => { await vi.waitFor(() => { expect(ctx.agents.get(childId)).toBeUndefined() }, { timeout: 5_000 }) }) + it.each([ + ['fresh', undefined], ['fork', undefined], ['fork', 'in-history'], + ] as const)('records %s teammate identity with update mode %s and keeps the wire prefix', async (mode, systemPromptUpdate) => { + const { ctx, lead, adapter } = await setup([textResponse('parent answer'), textResponse('child answer')]) + if (systemPromptUpdate !== undefined) adapter.systemPromptUpdate = systemPromptUpdate + await runTurn(lead, 'Parent task') + const parentRequest = serializeRequest(adapter.requests[0]!) + const parentHistory = structuredClone(lead.session.deriveMessages()) + const spawned = await execute(ctx, lead, 'spawn_teammate', { + name: 'reviewer', description: 'review', prompt: 'Review the work', context: mode, + }) + expect(spawned.isError, text(spawned)).toBe(false) + const childId = spawnedChildId(spawned) + await waitNoAgent(ctx, childId) + const childRequest = serializeRequest(adapter.requests[1]!) + expect(childRequest.tools).toEqual(parentRequest.tools) + expect(childRequest.messages[0]).toEqual(parentRequest.messages[0]) + expect(JSON.stringify(childRequest.messages[0])).not.toContain('Your Team role') + if (mode === 'fork') { + expect(childRequest.messages.slice(0, parentRequest.messages.length)).toEqual(parentRequest.messages) + expect(adapter.requests[1]!.messages.slice(0, parentHistory.length)).toEqual(parentHistory) + } else { + expect(JSON.stringify(childRequest.messages)).not.toContain('Parent task') + } + expect(childRequest.messages).toContainEqual({ + role: 'user', + content: '\nYou are teammate "reviewer".\n\n\nReview the work', + }) + await using persisted = await ctx.sessionPersistence.open(childId, 'read') + const { events } = await persisted.read() + const initial = events.findLast(event => event.type === 'user/message' && event.data.source.kind === 'user') + expect(initial?.type === 'user/message' ? initial.data.content : []).toEqual([ + { type: 'text', text: '\nYou are teammate "reviewer".\n\n\n' }, + { type: 'text', text: 'Review the work' }, + ]) + }) + + it('keeps an ordinary Lead fork free of identity reminders without replacing the inherited prefix', async () => { + const { ctx, lead, adapter } = await setup([textResponse('parent answer'), textResponse('fork answer')]) + await runTurn(lead, 'Parent task') + const seed = lead.session.snapshotEvents() + const childId = SessionId('ordinary-team-fork') + const handle = await ctx.agents.create({ + sessionId: childId, + seed, + inheritedEventCount: SessionLogOffset(seed.length), + meta: { parentSession: lead.id, isSeeded: true }, + agentOptions: { provider: 'mock', model: 'mock' }, + signal: SIGNAL, + }) + await runTurn(handle.agent, 'Continue independently') + const parentRequest = serializeRequest(adapter.requests[0]!) + const childRequest = serializeRequest(adapter.requests[1]!) + expect(childRequest.tools).toEqual(parentRequest.tools) + expect(childRequest.messages.slice(0, parentRequest.messages.length)).toEqual(parentRequest.messages) + expect(childRequest.messages.at(-1)?.content).toBe('Continue independently') + expect(JSON.stringify(childRequest.messages)).not.toContain('system-reminder') + expect(handle.agent.session.snapshotEvents().slice(0, seed.length)).toEqual(seed) + expect(ctx.agentTeams.listMembers(handle.agent).map(member => member.name)).toEqual(['lead']) + await handle.dispose() + }) + + it.each([undefined, 'in-history'] as const)('keeps a teammate fork prefix with update mode %s without a Lead correction', async (systemPromptUpdate) => { + const { ctx, lead, adapter } = await setup([textResponse('review done'), textResponse('lead notified'), textResponse('fork answer')]) + if (systemPromptUpdate !== undefined) adapter.systemPromptUpdate = systemPromptUpdate + const spawned = await execute(ctx, lead, 'spawn_teammate', { + name: 'reviewer', description: 'review', prompt: 'Review the work', + }) + const teammateId = spawnedChildId(spawned) + await waitNoAgent(ctx, teammateId) + await using persisted = await ctx.sessionPersistence.open(teammateId, 'read') + const { events: seed } = await persisted.read() + const handle = await ctx.agents.create({ + sessionId: SessionId('fork-from-teammate'), + seed, + inheritedEventCount: SessionLogOffset(seed.length), + meta: { parentSession: teammateId, isSeeded: true }, + agentOptions: { provider: 'mock', model: 'mock' }, + signal: SIGNAL, + }) + try { + const inheritedHistory = structuredClone(handle.agent.session.deriveMessages()) + await runTurn(handle.agent, 'Continue independently') + const original = serializeRequest(adapter.requests.find(request => request.sessionId === teammateId)!) + const forkRequest = adapter.requests.find(request => request.sessionId === handle.agent.id)! + const fork = serializeRequest(forkRequest) + expect(fork.tools).toEqual(original.tools) + expect(fork.messages.slice(0, original.messages.length)).toEqual(original.messages) + expect(forkRequest.messages.slice(0, inheritedHistory.length)).toEqual(inheritedHistory) + expect(fork.messages).toContainEqual({ role: 'user', content: 'Continue independently' }) + expect(JSON.stringify(fork.messages)).not.toContain('You are the Team Lead') + expect(handle.agent.session.snapshotEvents().slice(0, seed.length)).toEqual(seed) + } finally { + await handle.dispose() + } + }) + + it('leaves initial identity to ordinary history compaction without reinserting it', async () => { + const { ctx, lead, adapter } = await setup([ + toolCallResponse('first-list', 'list_agents', {}), + toolCallResponse('second-list', 'list_agents', {}), + textResponse('done'), + ]) + ctx.on('agent/pre-step', async ({ agent, step }, next) => { + if (step === 3) { + const identity = agent.session.snapshotEvents().find(event => event.type === 'user/message' + && event.data.source.kind === 'user') + if (identity === undefined) throw new Error('expected initial teammate reminder') + agent.session.append('user/message', createUserMessage({ + content: [{ type: 'text', text: 'Compacted earlier context.' }], + source: { kind: 'plugin', plugin: 'test-compaction' }, + }), { + surfaceOp: { op: 'replace', startSeq: identity.seq, endSeq: identity.seq }, + sourceEventSeqs: [identity.seq], + }) + } + return next() + }) + const spawned = await execute(ctx, lead, 'spawn_teammate', { + name: 'reviewer', description: 'review', prompt: 'Review the work', + }) + const childId = spawnedChildId(spawned) + await waitNoAgent(ctx, childId) + const reminder = '\nYou are teammate "reviewer".\n\n\n' + const first = serializeRequest(adapter.requests[0]!).messages + expect(first).toContainEqual({ role: 'user', content: `${reminder}Review the work` }) + expect(adapter.requests[1]!.messages.filter(message => message.content.some(block => + block.type === 'text' && block.text === reminder))).toHaveLength(1) + expect(JSON.stringify(serializeRequest(adapter.requests[2]!).messages)).not.toContain('You are teammate') + await using persisted = await ctx.sessionPersistence.open(childId, 'read') + const { events } = await persisted.read() + expect(events.filter(event => event.type === 'user/message' + && event.data.source.kind === 'plugin' && event.data.source.plugin === toolTeam.name)).toHaveLength(0) + }) + + it.each(['reject', 'empty', 'abort'] as const)('does not revive a teammate step after %s', async (mode) => { + const { ctx, lead, adapter } = await setup([]) + ctx.on('agent/pre-step', async ({ agent }, next) => { + const decision = await next() + if (agent === lead) return decision + if (mode === 'reject') return { kind: 'reject' } + if (mode === 'abort') agent.cancel({ kind: 'user' }) + return { kind: 'enter', messages: [] } + }) + await execute(ctx, lead, 'spawn_teammate', { + name: 'reviewer', description: 'review', prompt: 'Review the work', + }) + const childId = SessionId(ctx.agentTeams.listMembers(lead).find(member => member.name === 'reviewer')!.id) + await waitNoAgent(ctx, childId) + expect(adapter.requests.filter(request => request.sessionId === childId)).toEqual([]) + await using persisted = await ctx.sessionPersistence.open(childId, 'read') + const { events } = await persisted.read() + expect(events.filter(event => event.type === 'user/message' + && event.data.source.kind === 'user')).toEqual([]) + }) + + it('keeps teammate reminders when runtime context is suppressed', async () => { + const { ctx, lead, adapter } = await setup([textResponse('worker done')]) + ctx.systemPrompt.suppressRuntimeContext() + const spawned = await execute(ctx, lead, 'spawn_teammate', { + name: 'reviewer', description: 'review', prompt: 'Review the work', + }) + expect(spawned.isError, text(spawned)).toBe(false) + await waitNoAgent(ctx, spawnedChildId(spawned)) + expect(serializeRequest(adapter.requests[0]!).messages.at(-1)?.content) + .toBe('\nYou are teammate "reviewer".\n\n\nReview the work') + }) + it('returns actionable no-progress output and renders structured wait cancellation', async () => { const inactiveSetup = await setup([textResponse('worker done')]) const inactiveSpawn = await execute(inactiveSetup.ctx, inactiveSetup.lead, 'spawn_teammate', { @@ -393,7 +575,7 @@ describe('dsh-tool-team', () => { const assembled = await assembly(ctx, lead) expect(assembled.tools.filter(schema => TOOL_NAMES.includes(schema.name)).map(schema => schema.name)) .toEqual(['spawn_teammate']) - expect(renderPrompt(assembled)).not.toContain('Your Team role is lead') + expect(renderContextSnapshot(assembled)).not.toContain('Your Team role is lead') }) it('resolves direct-apply defaults without Loader schema normalization', async () => { @@ -412,7 +594,7 @@ describe('dsh-tool-team', () => { }) it('reinstalls Team scope before a cold-resumed teammate request', async () => { - const { ctx, lead } = await setup([textResponse('first'), 'hang']) + const { ctx, lead, adapter } = await setup([textResponse('first'), 'hang', 'hang']) const spawned = await execute(ctx, lead, 'spawn_teammate', { name: 'cold-worker', description: 'cold worker', prompt: 'finish once', }) @@ -427,7 +609,14 @@ describe('dsh-tool-team', () => { const resumed = await waitRunning(ctx, childId) expect((await assembly(ctx, resumed)).tools.map(schema => schema.name) .filter(name => TOOL_NAMES.includes(name)).sort()).toEqual(TOOL_NAMES) - expect(renderPrompt(await assembly(ctx, resumed))).toContain('Your Team role is teammate; your Team name is cold-worker') + expect(renderContextSnapshot(await assembly(ctx, resumed))).not.toContain('You are teammate') + const childRequests = () => adapter.requests.filter(request => request.messages.some(message => message.role === 'user' + && message.content.some(block => block.type === 'text' && block.text.includes('You are teammate "cold-worker".')))) + await vi.waitFor(() => { expect(childRequests()).toHaveLength(2) }) + const firstMessages = childRequests()[0]!.messages + expect(childRequests()[1]!.messages.slice(0, firstMessages.length)).toEqual(firstMessages) + expect(resumed.session.snapshotEvents().filter(event => event.type === 'user/message' + && event.data.source.kind === 'user')).toHaveLength(1) await execute(ctx, lead, 'interrupt_agent', { target: 'cold-worker' }) await vi.waitFor(() => { expect(ctx.agents.get(childId)).toBeUndefined() }, { timeout: 5_000 }) }) diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index 893e3c4a26..ce6362aee0 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -870,30 +870,6 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ }, ], }, - { - key: 'e2b', - summary: 'Creates one lazily consumable E2B SDK handle and deletes the sandbox at timeout or disposal.', - description: 'Creates one lazily consumable E2B SDK handle and deletes the sandbox at timeout or disposal. Creation begins at plugin construction; adapters await getSandbox before their first operation.', - methods: [ - { - signature: 'readonly cwd: string', - description: 'Validated remote working directory shared by provider adapters.', - parameters: [], - }, - { - signature: 'readonly runtimeRoot: string', - description: 'Remote directory reserved for adapter-owned process and terminal state.', - parameters: [], - }, - { - signature: 'async getSandbox(): Promise', - description: 'Return the shared live SDK handle.', - parameters: [], - returns: 'the created sandbox after the configured cwd exists.', - throws: ['when E2B rejects creation or the service is disposing.'], - }, - ], - }, { key: 'fileReferences', summary: 'Host capability for cancellable file-reference discovery.', @@ -3725,8 +3701,8 @@ export const TYPE_API: readonly TypeApiEntry[] = [ declaration: 'export interface AssistantMessage extends Message {\n readonly role: \'assistant\';\n readonly source: ModelMessageSource;\n}', }, { - name: 'AssistantProvenance', - declaration: 'export interface AssistantProvenance {\n provider: string;\n model: string;\n replayState?: unknown;\n}', + name: 'AssistantProviderMetadata', + declaration: 'export interface AssistantProviderMetadata {\n provider: string;\n model: string;\n replayState?: unknown;\n}', }, { name: 'AssistantStreamFrame', @@ -4726,7 +4702,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'ModelMessageSource', - declaration: 'export interface ModelMessageSource extends AssistantProvenance {\n kind: \'model\';\n}', + declaration: 'export interface ModelMessageSource extends AssistantProviderMetadata {\n kind: \'model\';\n}', }, { name: 'ModelModality', @@ -5485,8 +5461,8 @@ export const TYPE_API: readonly TypeApiEntry[] = [ declaration: 'export interface SessionTitleEventData {\n readonly title: string;\n readonly messageSeqs: SessionSeq[];\n readonly source: SessionTitleSource;\n}', }, { - name: 'SessionTitleModelProvenance', - declaration: 'export interface SessionTitleModelProvenance {\n readonly provider: string;\n readonly model: string;\n}', + name: 'SessionTitleModelIdentity', + declaration: 'export interface SessionTitleModelIdentity {\n readonly provider: string;\n readonly model: string;\n}', }, { name: 'SessionTitleObservation', @@ -5502,11 +5478,11 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'SessionTitleProviderRequest', - declaration: 'export interface SessionTitleProviderRequest {\n readonly session: Session;\n readonly messages: readonly SessionTitleUserMessage[];\n readonly route?: SessionTitleModelProvenance;\n readonly signal: AbortSignal;\n}', + declaration: 'export interface SessionTitleProviderRequest {\n readonly session: Session;\n readonly messages: readonly SessionTitleUserMessage[];\n readonly route?: SessionTitleModelIdentity;\n readonly signal: AbortSignal;\n}', }, { name: 'SessionTitleProviderResult', - declaration: 'export interface SessionTitleProviderResult {\n readonly title: string;\n readonly messageSeqs: readonly SessionSeq[];\n readonly model?: SessionTitleModelProvenance;\n}', + declaration: 'export interface SessionTitleProviderResult {\n readonly title: string;\n readonly messageSeqs: readonly SessionSeq[];\n readonly model?: SessionTitleModelIdentity;\n}', }, { name: 'SessionTitleSnapshot', @@ -5514,7 +5490,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'SessionTitleSource', - declaration: 'export type SessionTitleSource = {\n readonly kind: \'fallback\';\n} | {\n readonly kind: \'provider\';\n readonly provider: SessionTitleProviderId;\n readonly model?: SessionTitleModelProvenance;\n} | {\n readonly kind: \'user\';\n};', + declaration: 'export type SessionTitleSource = {\n readonly kind: \'fallback\';\n} | {\n readonly kind: \'provider\';\n readonly provider: SessionTitleProviderId;\n readonly model?: SessionTitleModelIdentity;\n} | {\n readonly kind: \'user\';\n};', }, { name: 'SessionTitleUserMessage', diff --git a/packages/fs/README.i18n.yaml b/packages/fs/README.i18n.yaml index c732897309..66d30e9e19 100644 --- a/packages/fs/README.i18n.yaml +++ b/packages/fs/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/fs/README.md -README.md: dd3e6f1fab3a8439dd8e2ff15f854e2872549eef -README.zh.md: e4139d05c5bbd08c14bce80c0d2f363e92eeaaf4 +README.md: 6a1a6208ae7ec6794d1ce4c945f01178556228b8 +README.zh.md: 847001264e603f0a584e6d702dd18f23b8fa281b diff --git a/packages/fs/README.md b/packages/fs/README.md index dd3e6f1fab..6a1a6208ae 100644 --- a/packages/fs/README.md +++ b/packages/fs/README.md @@ -22,14 +22,13 @@ The `fs/` group gives agents durable, policy-governed access to files: the `ctx. ## Packages -Eight packages plus the remote sibling `fs-e2b` play the filesystem roles; the subsystem reference owns the exhaustive contracts and the error taxonomy. +Eight packages play the filesystem roles; the subsystem reference owns the exhaustive contracts and the error taxonomy. | Package | Role | ctx key | |---|---|---| | [`fs/`](fs/README.md) | `ctx.fs` service contract: execution-world paths, bounded text I/O, and atomic mutations with an optional version guard | `ctx.fs` | | [`fs-local/`](fs-local/README.md) | Host-filesystem backend: reads, writes, and edits real files on the local machine | registers on `ctx.fs` | | [`fs-sandbox/`](fs-sandbox/README.md) | Sandbox-enforcing backend: fences writes and edits by the per-call sandbox mode while reads pass through | registers on `ctx.fs` | -| [`e2b/fs-e2b`](../e2b/fs-e2b/README.md) | E2B-backed backend: file state lives in the remote execution world shared with the E2B subprocess provider | registers on `ctx.fs` | | [`fs-observation-policy/`](fs-observation-policy/README.md) | Read-before-edit policy: records observed presence or absence and guards write/edit through the `fs/*` events | `fs/*` listeners | | [`tool-fs/`](tool-fs/README.md) | Model-facing `read`, `read_image`, `write`, and `edit` tools plus their executor | registers on `ctx.tools` | | [`tool-fs-search/`](tool-fs-search/README.md) | Model-facing `glob` and `grep` discovery tools backed by the packaged ripgrep binary | registers on `ctx.tools` | @@ -47,7 +46,7 @@ Start with the subsystem reference for the shared vocabulary and error taxonomy, - [Filesystem subsystem](../../docs/subsystems/filesystem.md) — targets, outcomes, guards, policy events, and the error taxonomy. - [Cross-family fs sandbox decision](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) — the shared sandbox mode fence over the filesystem seam. -- [Portable execution world consumers decision](../../.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md) — why the E2B backend shares the remote execution world. +- [Portable execution world consumers decision](../../.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md) — why filesystem and subprocess providers share one execution world. ## Dev Note diff --git a/packages/fs/README.zh.md b/packages/fs/README.zh.md index e4139d05c5..847001264e 100644 --- a/packages/fs/README.zh.md +++ b/packages/fs/README.zh.md @@ -22,14 +22,13 @@ kind: "package-group" ## 包 -八个包加上远程同级 `fs-e2b` 承担文件系统角色;子系统参考文档完整收录各项约定与错误分类体系。 +八个包承担文件系统角色;子系统参考文档完整收录各项约定与错误分类体系。 | 包 | 职责 | ctx 键 | |---|---|---| | [`fs/`](fs/README.zh.md) | `ctx.fs` 服务约定:执行世界路径、有界文本 I/O,以及带可选版本防护的原子变更 | `ctx.fs` | | [`fs-local/`](fs-local/README.zh.md) | 宿主文件系统后端:读取、写入并编辑本机上的真实文件 | 注册到 `ctx.fs` | | [`fs-sandbox/`](fs-sandbox/README.zh.md) | 沙箱强制后端:按每次调用的沙箱模式约束写入与编辑,读取直接通过 | 注册到 `ctx.fs` | -| [`e2b/fs-e2b`](../e2b/fs-e2b/README.zh.md) | 以 E2B 为后端:文件状态位于与 E2B 子进程提供方共享的远程执行世界 | 注册到 `ctx.fs` | | [`fs-observation-policy/`](fs-observation-policy/README.zh.md) | 编辑前读取策略:记录观测到的存在或缺失,并通过 `fs/*` 事件防护写入/编辑 | `fs/*` 监听器 | | [`tool-fs/`](tool-fs/README.zh.md) | 面向模型的 `read`、`read_image`、`write` 与 `edit` 工具及其执行器 | 注册到 `ctx.tools` | | [`tool-fs-search/`](tool-fs-search/README.zh.md) | 由打包 ripgrep 二进制支持的面向模型 `glob` 与 `grep` 发现工具 | 注册到 `ctx.tools` | @@ -47,7 +46,7 @@ kind: "package-group" - [文件系统子系统](../../docs/subsystems/filesystem.zh.md)——目标、结果、防护、策略事件与错误分类体系。 - [跨能力族 fs 沙箱决策](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md)——文件系统 seam 上共享的沙箱模式围栏。 -- [可移植执行世界消费方决策](../../.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md)——E2B 后端为何共享远程执行世界。 +- [可移植执行世界消费方决策](../../.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md)——文件系统与子进程提供方为何共享一个执行世界。 ## 开发备注 diff --git a/packages/fs/fs-local/README.i18n.yaml b/packages/fs/fs-local/README.i18n.yaml index 34946cdf84..6967acc7f8 100644 --- a/packages/fs/fs-local/README.i18n.yaml +++ b/packages/fs/fs-local/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/fs/fs-local/README.md -README.md: d8f1afaa59e8313c553ceb1ea4e94cec17eaca10 -README.zh.md: 7daa9143a4b7455446b2238889524089a8172ea1 +README.md: 6b53b8bea81a31d047154232f4fe03899b6909ec +README.zh.md: 56c834c7e024bf28909d28c1ebfacbb99dc83518 diff --git a/packages/fs/fs-local/README.md b/packages/fs/fs-local/README.md index d8f1afaa59..6b53b8bea8 100644 --- a/packages/fs/fs-local/README.md +++ b/packages/fs/fs-local/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -Use `dsh-fs-local` to read, list, atomically write, and edit files on the host filesystem. Relative paths resolve from a configurable base directory, while absolute paths and parent traversal remain unrestricted. Paths and symlinks that reach the same file share one identity. Writes preserve file permissions, and optional version guards reject stale overwrites. Choose this package for direct host access; use `fs-sandbox` for confined mutations or `fs-e2b` for files in a remote execution world. +Use `dsh-fs-local` to read, list, atomically write, and edit files on the host filesystem. Relative paths resolve from a configurable base directory, while absolute paths and parent traversal remain unrestricted. Paths and symlinks that reach the same file share one identity. Writes preserve file permissions, and optional version guards reject stale overwrites. Choose this package for direct host access; use `fs-sandbox` for confined mutations. ## Table of Contents @@ -29,7 +29,7 @@ Mount this backend when a composition needs `ctx.fs` backed by the real host fil ### When to choose it -Choose `fs-local` for ordinary host-file access in a single process. Choose [`fs-sandbox`](../fs-sandbox/README.md) when a session's writes and edits must be confined to its workspace and temp roots — it extends this backend and adds only the mode fence. Choose [`fs-e2b`](../../e2b/fs-e2b/README.md) when files must live in a remote execution world shared with subprocesses. `config.cwd` is a resolution default, not a containment boundary: absolute paths and `..` escape it. +Choose `fs-local` for ordinary host-file access in a single process. Choose [`fs-sandbox`](../fs-sandbox/README.md) when a session's writes and edits must be confined to its workspace and temp roots — it extends this backend and adds only the mode fence. `config.cwd` is a resolution default, not a containment boundary: absolute paths and `..` escape it. ### Minimal configuration diff --git a/packages/fs/fs-local/README.zh.md b/packages/fs/fs-local/README.zh.md index 7daa9143a4..56c834c7e0 100644 --- a/packages/fs/fs-local/README.zh.md +++ b/packages/fs/fs-local/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -使用 `dsh-fs-local` 可在宿主文件系统上读取、列出、原子写入和编辑文件。相对路径从可配置的基准目录解析,而绝对路径和父目录遍历不受限制。到达同一文件的路径和符号链接共享一个身份。写入保留文件权限,可选版本防护会拒绝陈旧覆盖。直接访问宿主文件时选择本包;需要约束变更时使用 `fs-sandbox`,文件位于远程执行世界时使用 `fs-e2b`。 +使用 `dsh-fs-local` 可在宿主文件系统上读取、列出、原子写入和编辑文件。相对路径从可配置的基准目录解析,而绝对路径和父目录遍历不受限制。到达同一文件的路径和符号链接共享一个身份。写入保留文件权限,可选版本防护会拒绝陈旧覆盖。直接访问宿主文件时选择本包;需要约束变更时使用 `fs-sandbox`。 ## 目录 @@ -29,7 +29,7 @@ kind: "package-reference" ### 何时选择 -在单个进程中进行普通宿主文件访问时,请选择 `fs-local`。会话的写入与编辑必须限制在工作区与临时根目录内时,选择 [`fs-sandbox`](../fs-sandbox/README.zh.md)——它扩展此后端,只增加模式围栏。文件必须位于与子进程共享的远程执行世界时,选择 [`fs-e2b`](../../e2b/fs-e2b/README.zh.md)。`config.cwd` 只是解析默认值,不是约束边界:绝对路径与 `..` 都可以逃逸它。 +在单个进程中进行普通宿主文件访问时,请选择 `fs-local`。会话的写入与编辑必须限制在工作区与临时根目录内时,选择 [`fs-sandbox`](../fs-sandbox/README.zh.md)——它扩展此后端,只增加模式围栏。`config.cwd` 只是解析默认值,不是约束边界:绝对路径与 `..` 都可以逃逸它。 ### 最小配置 diff --git a/packages/fs/fs/README.i18n.yaml b/packages/fs/fs/README.i18n.yaml index 00b994a90f..8a8c0dbc63 100644 --- a/packages/fs/fs/README.i18n.yaml +++ b/packages/fs/fs/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/fs/fs/README.md -README.md: baad80c13fa5cbdfebc4e1a367d0726f728cffdb -README.zh.md: a6598c9b532ebe0349c5ad9af9c56eab75f4c756 +README.md: e36e400d53cc581eb19733c9b3ccd890f34932a2 +README.zh.md: 3efc2106a9200ee84ab1a859a19fdb5b1631f056 diff --git a/packages/fs/fs/README.md b/packages/fs/fs/README.md index baad80c13f..e36e400d53 100644 --- a/packages/fs/fs/README.md +++ b/packages/fs/fs/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -Use `dsh-fs` when an application needs consistent filesystem operations across host, confined, or remote execution environments. It lets consumers resolve stable file identities, map shared host files where supported, perform bounded text and byte reads, list directories, and apply atomic text writes and literal edits. Version guards are optional, so a backend works without policy enforcement; callers can supply a guard to reject a mutation after the file changes. Choose `fs-local`, `fs-sandbox`, or `fs-e2b` for the required execution environment. Model-facing filesystem tools are provided separately by `dsh-tool-fs`. +Use `dsh-fs` when an application needs consistent filesystem operations across host, confined, or remote execution environments. It lets consumers resolve stable file identities, map shared host files where supported, perform bounded text and byte reads, list directories, and apply atomic text writes and literal edits. Version guards are optional, so a backend works without policy enforcement; callers can supply a guard to reject a mutation after the file changes. Choose `fs-local` or `fs-sandbox` for host execution. Model-facing filesystem tools are provided separately by `dsh-tool-fs`. ## Table of Contents @@ -29,7 +29,7 @@ You rarely load `dsh-fs` directly: you mount a backend that registers as `ctx.fs ### Choosing and mounting a backend -Pick [`fs-local`](../fs-local/README.md) for ordinary host files, [`fs-sandbox`](../fs-sandbox/README.md) when a session's mutations must be confined to its workspace and temp roots, and [`fs-e2b`](../../e2b/fs-e2b/README.md) when file state must live in a remote execution world. Mounting any backend populates `ctx.fs`; swapping backends changes nothing for the policy plugin, the tools, or the tool schemas. A composition that mounts no backend has no `ctx.fs` at all, and the tools fail at registration. +Pick [`fs-local`](../fs-local/README.md) for ordinary host files or [`fs-sandbox`](../fs-sandbox/README.md) when a session's mutations must be confined to its workspace and temp roots. Mounting any backend populates `ctx.fs`; swapping backends changes nothing for the policy plugin, the tools, or the tool schemas. A composition that mounts no backend has no `ctx.fs` at all, and the tools fail at registration. ### What the service lets you do diff --git a/packages/fs/fs/README.zh.md b/packages/fs/fs/README.zh.md index a6598c9b53..3efc2106a9 100644 --- a/packages/fs/fs/README.zh.md +++ b/packages/fs/fs/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -应用需要在宿主、受限或远程执行环境中使用一致的文件系统操作时,选择 `dsh-fs`。消费方可以解析稳定的文件身份、在受支持时映射共享宿主文件、执行有界的文本与字节读取、列出目录,并原子地写入文本及执行字面量编辑。版本防护是可选的,因此后端无需策略强制也能工作;调用方可以提供防护,在文件变化后拒绝变更。根据所需执行环境选择 `fs-local`、`fs-sandbox` 或 `fs-e2b`。面向模型的文件系统工具由 `dsh-tool-fs` 单独提供。 +应用需要在宿主、受限或远程执行环境中使用一致的文件系统操作时,选择 `dsh-fs`。消费方可以解析稳定的文件身份、在受支持时映射共享宿主文件、执行有界的文本与字节读取、列出目录,并原子地写入文本及执行字面量编辑。版本防护是可选的,因此后端无需策略强制也能工作;调用方可以提供防护,在文件变化后拒绝变更。在宿主执行时选择 `fs-local` 或 `fs-sandbox`。面向模型的文件系统工具由 `dsh-tool-fs` 单独提供。 ## 目录 @@ -29,7 +29,7 @@ kind: "package-reference" ### 选择并挂载后端 -普通宿主文件选择 [`fs-local`](../fs-local/README.zh.md),会话变更必须限制在工作区与临时根目录内时选择 [`fs-sandbox`](../fs-sandbox/README.zh.md),文件状态必须位于远程执行世界时选择 [`fs-e2b`](../../e2b/fs-e2b/README.zh.md)。挂载任一后端都会填充 `ctx.fs`;更换后端不会改变策略插件、工具或工具 schema。未挂载任何后端的组合就没有 `ctx.fs`,工具会在注册时失败。 +普通宿主文件选择 [`fs-local`](../fs-local/README.zh.md),会话变更必须限制在工作区与临时根目录内时选择 [`fs-sandbox`](../fs-sandbox/README.zh.md)。挂载任一后端都会填充 `ctx.fs`;更换后端不会改变策略插件、工具或工具 schema。未挂载任何后端的组合就没有 `ctx.fs`,工具会在注册时失败。 ### 服务能做什么 diff --git a/packages/host/plugin-inventory/README.i18n.yaml b/packages/host/plugin-inventory/README.i18n.yaml index 4b2a6a19f1..2cb23a1f0d 100644 --- a/packages/host/plugin-inventory/README.i18n.yaml +++ b/packages/host/plugin-inventory/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/host/plugin-inventory/README.md -README.md: f4981bfdee614854a214ef27f1c9463f05585d28 +README.md: b64a017df28716cfd0b5c3a41f437b87b8e4f19e README.zh.md: a29bc402537d88fa512675bfb768502753ca7929 diff --git a/packages/host/plugin-inventory/README.md b/packages/host/plugin-inventory/README.md index f4981bfdee..b64a017df2 100644 --- a/packages/host/plugin-inventory/README.md +++ b/packages/host/plugin-inventory/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -Clients can call `pluginInventory/list` to display the host’s current plugins in load order, including each entry’s identifier, module specifier, effective enablement, and live phase. Deployments with an agent-preset roster also report each preset’s metadata, health, and flattened plugin composition; without a roster, preset data is absent. Each response is a point-in-time, read-only snapshot for display and diagnostics: it cannot mutate plugins and provides no history, provenance, or change subscription. +Clients can call `pluginInventory/list` to display the host’s current plugins in load order, including each entry’s identifier, module specifier, effective enablement, and live phase. Deployments with an agent-preset roster also report each preset’s metadata, health, and flattened plugin composition; without a roster, preset data is absent. Each response is a point-in-time, read-only snapshot for display and diagnostics: it cannot mutate plugins and provides no history, introduction source, or change subscription. ## Table of Contents @@ -97,7 +97,7 @@ None; this package neither assembles nor sends a provider request. These limits define what a point-in-time inventory cannot tell a client. They are current package constraints, not a task backlog. - **Point-in-time state only** — the result contains no durable failure history or subscription; a missing root Fiber is reported as `null`, regardless of why no live root exists. -- **No provenance or mutation** — the service does not identify which bundle, profile, or override introduced an entry, and it cannot enable, disable, add, or remove plugins in either plane. +- **No introduction source or mutation** — the service does not identify which bundle, profile, or override introduced an entry, and it cannot enable, disable, add, or remove plugins in either plane. - **Presets appear only with a roster** — a deployment without `dsh-agent-presets` serves Loader entries alone; the `agentPresets` field is absent rather than empty. diff --git a/packages/interaction/permission-presets/src/index.ts b/packages/interaction/permission-presets/src/index.ts index 826f86073a..eb42d94598 100644 --- a/packages/interaction/permission-presets/src/index.ts +++ b/packages/interaction/permission-presets/src/index.ts @@ -41,7 +41,7 @@ declare module '@deepseek-ai/cordis' { declare module '@deepseek-ai/dsh-session-projection/types' { interface SessionProjectionStateMap { - /** Latest logged permission overrides and constructor-seed provenance. */ + /** Latest logged permission overrides and constructor-seed status. */ permissions: PermissionProjectionState } } @@ -101,7 +101,7 @@ export interface KnobState { approval: ApprovalPolicy | null } -/** Projection state for permission overrides and constructor-seed provenance. */ +/** Projection state for permission overrides and constructor-seed status. */ interface PermissionProjectionState extends KnobState { /** Whether the log contains a constructor-seed boundary. */ seeded: boolean diff --git a/packages/llm/llm-deepseek/src/protocols/messages/replay.ts b/packages/llm/llm-deepseek/src/protocols/messages/replay.ts index 378a5caa8e..d6ff95804b 100644 --- a/packages/llm/llm-deepseek/src/protocols/messages/replay.ts +++ b/packages/llm/llm-deepseek/src/protocols/messages/replay.ts @@ -31,7 +31,7 @@ export function replayState(model: string, blocks: ReplayBlock[]): ReplayEnvelop } /** Validate native replay, discarding unusable metadata before serializing durable content. - * @param message - durable assistant content and provenance. + * @param message - durable assistant content and source metadata. * @param model - target model; cross-model signatures are not portable. * @param onDegrade - diagnostic for unusable metadata; receives no message content or signatures. * @returns index-aligned metadata, absent for foreign, cross-model or degraded history. @@ -51,7 +51,7 @@ function validateReplay(message: Message, model: string): ReplayBlock[] | undefi const envelope = object(message.source.replayState, 'INVALID_REPLAY_STATE') const response = object(envelope.response, 'INVALID_REPLAY_STATE') if (response.kind !== 'deepseek-messages' || response.version !== 1) return fail('unsupported kind or version') - if (response.model !== message.source.model) return fail('model does not match assistant provenance') + if (response.model !== message.source.model) return fail('model does not match assistant source model') if (!Array.isArray(envelope.blocks) || envelope.blocks.length !== message.content.length) return fail('block count mismatch') const blocks = envelope.blocks.map((value, index): ReplayBlock => { const block = object(value, 'INVALID_REPLAY_STATE') diff --git a/packages/llm/llm-deepseek/src/protocols/messages/translate.ts b/packages/llm/llm-deepseek/src/protocols/messages/translate.ts index a8dd6282cf..deb901119e 100644 --- a/packages/llm/llm-deepseek/src/protocols/messages/translate.ts +++ b/packages/llm/llm-deepseek/src/protocols/messages/translate.ts @@ -98,7 +98,7 @@ function stopReason(raw: unknown): FinishReason { /** Translate decoded SSE data into the Harness stream protocol. * @param events - framed, decoded provider events in arrival order. - * @param model - requested model id for durable replay provenance. + * @param model - requested model id stored in durable replay state. * @returns blocks, one final usage value, and exactly one terminal finish. */ export async function* translate(events: AsyncIterable>, model: string): AsyncGenerator { diff --git a/packages/llm/llm-pi-ai/src/stream.ts b/packages/llm/llm-pi-ai/src/stream.ts index 9bc2f98c4c..380eec2ced 100644 --- a/packages/llm/llm-pi-ai/src/stream.ts +++ b/packages/llm/llm-pi-ai/src/stream.ts @@ -135,7 +135,7 @@ export function mapStopReason(message: AssistantMessage, contextWindow?: number) * @param contextWindow - resolved catalog capacity for usage-based overflow detection. * @param callerSignal - caller cancellation state; an aborted caller makes any * in-band terminal error an aborted finish. - * @param requestedModel - request model identity for durable replay provenance. + * @param requestedModel - request model identity recorded for durable replay. * @returns the harness chunks, ending with `usage` then `finish`; throws * `LlmError` (`STREAM_CLOSED`) if the source ends without a terminal event. */ diff --git a/packages/llm/llm/src/message.ts b/packages/llm/llm/src/message.ts index 6f920fe019..05af6bae3e 100644 --- a/packages/llm/llm/src/message.ts +++ b/packages/llm/llm/src/message.ts @@ -7,7 +7,7 @@ import type { MessageId, ToolCallId } from './brand.ts' import type { ContentBlock, ToolResultBlock } from './types.ts' /** Provider/model identity and adapter-private replay data for an assistant message. */ -export interface AssistantProvenance { +export interface AssistantProviderMetadata { /** Provider route that produced the message. */ provider: string /** Provider model id that produced the message. */ @@ -21,7 +21,7 @@ export interface AssistantProvenance { } /** Required source of an assistant message produced by a routed model. */ -export interface ModelMessageSource extends AssistantProvenance { +export interface ModelMessageSource extends AssistantProviderMetadata { kind: 'model' } @@ -33,7 +33,7 @@ export interface ToolMessageSource { /** * The kind of information in producer-supplied context, declared by the - * producer beside its provenance. + * producer in the same `MessageSource`. * * `MessageSource.kind` answers *who produced this*; `form` answers *what kind * of thing it is*, and the two axes are deliberately independent — several diff --git a/packages/llm/llm/src/types.ts b/packages/llm/llm/src/types.ts index bfddde7fc4..23a56e73a3 100644 --- a/packages/llm/llm/src/types.ts +++ b/packages/llm/llm/src/types.ts @@ -26,7 +26,7 @@ declare module '@deepseek-ai/cordis' { export type { AssistantMessage, - AssistantProvenance, + AssistantProviderMetadata, Message, MessageSource, MessageSourceMap, diff --git a/packages/llm/plugin-package-inventory-deepseek/README.i18n.yaml b/packages/llm/plugin-package-inventory-deepseek/README.i18n.yaml index 8756b37184..63b0ceb3ac 100644 --- a/packages/llm/plugin-package-inventory-deepseek/README.i18n.yaml +++ b/packages/llm/plugin-package-inventory-deepseek/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/llm/plugin-package-inventory-deepseek/README.md -README.md: 8da1b526608348c8c6ec0ff75d7d472921e12aed +README.md: fd574c6526a759ecad7872bc89e3436927c21815 README.zh.md: 5c333cef4b5ba12485bb28db7780b1f19b59bb7f diff --git a/packages/llm/plugin-package-inventory-deepseek/README.md b/packages/llm/plugin-package-inventory-deepseek/README.md index 8da1b52660..fd574c6526 100644 --- a/packages/llm/plugin-package-inventory-deepseek/README.md +++ b/packages/llm/plugin-package-inventory-deepseek/README.md @@ -60,7 +60,7 @@ None; package lifecycle changes do not alter the model-visible prefix. -- **Loader package provenance only** — programmatic child fibers and in-memory dynamic plugins do not have authoritative npm name/version provenance and remain outside this inventory. +- **Loader package identity only** — programmatic child fibers and in-memory dynamic plugins do not have authoritative npm names and versions and remain outside this inventory. - **Loose modules are omitted** — a relative file without a named and versioned owning manifest is a plugin module, not a plugin package. - **In-place package replacement requires restart** — manifest identities are cached for the process lifetime. Loader enable, disable, mount, unmount, and ordinary source HMR still refresh the active entry set, but replacing a mounted package's manifest with another version in the same process is not a supported upgrade path. diff --git a/packages/llm/plugin-package-inventory-deepseek/src/index.ts b/packages/llm/plugin-package-inventory-deepseek/src/index.ts index 83161732b9..b373a76040 100644 --- a/packages/llm/plugin-package-inventory-deepseek/src/index.ts +++ b/packages/llm/plugin-package-inventory-deepseek/src/index.ts @@ -1,7 +1,7 @@ /** * Active Loader-backed plugin package inventory for official DeepSeek requests. * Host entries and the requesting agent's standing preset are resolved at request time; - * installed dependencies and plugin fibers without Loader package provenance are excluded. + * installed dependencies and plugin fibers without Loader-backed package identity are excluded. * @module @deepseek-ai/dsh-plugin-package-inventory-deepseek */ @@ -180,7 +180,7 @@ async function collectActivePluginPackages( /** * Register the complete `dsh_plugin_packages` request contribution when enabled. - * @param ctx - plugin context carrying Loader provenance and the DeepSeek request-extension registry. + * @param ctx - plugin context carrying Loader entry metadata and the DeepSeek request-extension registry. * @param config - validated default-on configuration. */ export function apply(ctx: Context, config: Config): void { diff --git a/packages/llm/token-meter/tests/context-breakdown-projection.spec.ts b/packages/llm/token-meter/tests/context-breakdown-projection.spec.ts index 769f00e0ec..dc1fcfb19e 100644 --- a/packages/llm/token-meter/tests/context-breakdown-projection.spec.ts +++ b/packages/llm/token-meter/tests/context-breakdown-projection.spec.ts @@ -323,7 +323,7 @@ describe('contextBreakdown session projection', () => { content: [{ type: 'text', text: 'rewritten question' }], source: { kind: 'user' }, }), { surfaceOp: { op: 'replace', startSeq: question, endSeq: question }, sourceEventSeqs: [question] }).seq expect(question).toBeGreaterThan(middle) - // Provenance can cite a surviving prompt outside the replaced span. + // Source-event references can cite a surviving prompt outside the replaced span. session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'middle summary' }], source: { kind: 'user' }, }), { surfaceOp: { op: 'replace', startSeq: question, endSeq: middle }, sourceEventSeqs: [question, middle, tail] }) diff --git a/packages/session-query/session-query/tests/tracing.spec.ts b/packages/session-query/session-query/tests/tracing.spec.ts index 2b16c4b89e..177edad1d7 100644 --- a/packages/session-query/session-query/tests/tracing.spec.ts +++ b/packages/session-query/session-query/tests/tracing.spec.ts @@ -477,7 +477,7 @@ describe('session event tracing', () => { { ...appendEvent(SessionSeq(2), [0]), surfaceOp: { op: 'replace', startSeq: 1, endSeq: 1 } }, ]], ] as const)('rejects an invalid surface log: %s', async (_name, rawEvents) => { - const durable = header('invalid-provenance') + const durable = header('invalid-source-reference') const events = structuredClone(rawEvents) as unknown as SessionEvent[] TracePersistence.reset([{ meta: durable, events }]) const ctx = await queryContext() diff --git a/packages/session/session-format-v0-to-v1/README.i18n.yaml b/packages/session/session-format-v0-to-v1/README.i18n.yaml index 68272905ba..305aea15b0 100644 --- a/packages/session/session-format-v0-to-v1/README.i18n.yaml +++ b/packages/session/session-format-v0-to-v1/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/session/session-format-v0-to-v1/README.md -README.md: 052a416c6c0ba8fa4ba434b1de483748f6c817b5 +README.md: 2ff830f4863a3e207f5c23be45b4765ebcb2a02b README.zh.md: 057109a70a71d6ae4f068e1d2ef194839bc41747 diff --git a/packages/session/session-format-v0-to-v1/README.md b/packages/session/session-format-v0-to-v1/README.md index 052a416c6c..2ff830f486 100644 --- a/packages/session/session-format-v0-to-v1/README.md +++ b/packages/session/session-format-v0-to-v1/README.md @@ -40,7 +40,7 @@ stage.transformEvent(event, migrationContext) const targetInheritedEventCount = stage.finish(migrationContext) ``` -`releasedV0SessionFormatCodec` reads the exact v0 header and physical rows, including packed Assistant deltas and range-encoded provenance. Its decoder emits either a scalar event or a codec-owned compact run through `emitEvent()` and `emitRun()`. `sessionFormatV0ToV1` creates one stateful stage per restore; the static catalog connects that decoder and stage so migration does not retain a physical-row array. `releasedV1SessionFormatCodec` exposes the same row-at-a-time decoder for the v1 physical layout without freezing the ordinary event vocabulary. +`releasedV0SessionFormatCodec` reads the exact v0 header and physical rows, including packed Assistant deltas and range-encoded source-event references. Its decoder emits either a scalar event or a codec-owned compact run through `emitEvent()` and `emitRun()`. `sessionFormatV0ToV1` creates one stateful stage per restore; the static catalog connects that decoder and stage so migration does not retain a physical-row array. `releasedV1SessionFormatCodec` exposes the same row-at-a-time decoder for the v1 physical layout without freezing the ordinary event vocabulary. The alpha edge refuses every event type outside its frozen inventory, including an unknown event marked `ignorable: true`. It also refuses unexpected payload members. `tool/result.meta` and nested PTC `arguments` remain explicit opaque JSON fields and are preserved without Session-sequence interpretation. Unknown content-block `type`, message-source `kind`, assistant finish-reason `kind`, and `turn/end` reason `kind` arms remain owner-opaque JSON while their known arms receive structural validation. @@ -58,7 +58,7 @@ The physical codec validates each packed row atomically, emits it as a compact r | File | Role | |---|---| -| [`src/codec.ts`](src/codec.ts) | Frozen v0/v1 physical headers, packed rows, and provenance ranges | +| [`src/codec.ts`](src/codec.ts) | Frozen v0/v1 physical headers, packed rows, and source-event ranges | | [`src/dispositions.ts`](src/dispositions.ts) | Released-v0 event and payload-member inventory | | [`src/payload-validation.ts`](src/payload-validation.ts) | Frozen nested payload semantics for every released-v0/v1 event type | | [`src/relationships.ts`](src/relationships.ts) | Frozen cross-event pairings: turns, steps, tool starts and results, retries, compaction, titles | diff --git a/packages/session/session-format-v0-to-v1/src/migration.ts b/packages/session/session-format-v0-to-v1/src/migration.ts index 5fce679de8..0a4ead8bcf 100644 --- a/packages/session/session-format-v0-to-v1/src/migration.ts +++ b/packages/session/session-format-v0-to-v1/src/migration.ts @@ -21,6 +21,8 @@ import { } from './validation.ts' import { assertReleasedV0Keys, releasedV0Record } from './validation-helpers.ts' +const LEGACY_ASSISTANT_SOURCE_KEY = ['pro', 'venance'].join('') + /** Identity format edge that promotes released v0 into released v1. */ export const sessionFormatV0ToV1 = defineSessionFormatMigration({ name: '@deepseek-ai/dsh-session-format-v0-to-v1', @@ -363,12 +365,15 @@ function normalizeLegacyMessage( } case 'assistant/message': { if (Object.hasOwn(data, 'message') - || !Object.hasOwn(data, 'content') || !Object.hasOwn(data, 'provenance')) return event - const { content, provenance, ...eventData } = data as typeof data & { - content: SessionFormatJsonValue - provenance: SessionFormatJsonValue - } - const source = releasedV0Record(provenance, `assistant/message ${event.seq} provenance`) + || !Object.hasOwn(data, 'content') || !Object.hasOwn(data, LEGACY_ASSISTANT_SOURCE_KEY)) return event + const content = data['content'] as SessionFormatJsonValue + const eventData = { ...data } + delete eventData['content'] + const source = releasedV0Record( + eventData[LEGACY_ASSISTANT_SOURCE_KEY], + `assistant/message ${event.seq} legacy source`, + ) + Reflect.deleteProperty(eventData, LEGACY_ASSISTANT_SOURCE_KEY) return { ...event, data: { diff --git a/packages/session/session-format-v0-to-v1/src/relationships.ts b/packages/session/session-format-v0-to-v1/src/relationships.ts index 2cd4a432ed..ddd1a4b71d 100644 --- a/packages/session/session-format-v0-to-v1/src/relationships.ts +++ b/packages/session/session-format-v0-to-v1/src/relationships.ts @@ -437,8 +437,8 @@ function assertTitleSources( throw new SessionFormatError(`${event.type} ${event.seq} messageSeqs must cite earlier human user/message events`) } const sourceData = releasedV0Record(source.data, `${source.type} ${seq} data`) - const provenance = releasedV0Record(sourceData['source'], `${source.type} ${seq} source`) - if (provenance['kind'] !== 'user') { + const messageSource = releasedV0Record(sourceData['source'], `${source.type} ${seq} source`) + if (messageSource['kind'] !== 'user') { throw new SessionFormatError(`${event.type} ${event.seq} messageSeqs must cite earlier human user/message events`) } const content = sourceData['content'] as readonly Record[] diff --git a/packages/session/session-format-v0-to-v1/src/validation.ts b/packages/session/session-format-v0-to-v1/src/validation.ts index f61080c7bc..762ed963f7 100644 --- a/packages/session/session-format-v0-to-v1/src/validation.ts +++ b/packages/session/session-format-v0-to-v1/src/validation.ts @@ -145,7 +145,7 @@ export function assertReleasedArtifactCoordinates( * @param record - exact event envelope. * @param seq - event position used for earlier-reference checks. * @param type - surface event type used in diagnostics. - * @param assistantSources - whether this generation admits empty Assistant chunk provenance. + * @param assistantSources - whether this generation admits empty Assistant chunk references. */ export function assertReleasedSurfaceMetadata( record: Record, @@ -155,7 +155,7 @@ export function assertReleasedSurfaceMetadata( ): void { const sources = record['sourceEventSeqs'] if (type === 'assistant/message' && sources !== undefined && assistantSources === 'forbid-assistant') { - throw new SessionFormatError(`assistant/message ${seq} retains obsolete chunk provenance`) + throw new SessionFormatError(`assistant/message ${seq} retains obsolete chunk references`) } if (sources !== undefined) { if (!Array.isArray(sources)) throw new SessionFormatError(`${type} ${seq} sourceEventSeqs must be an array`) diff --git a/packages/session/session-format-v0-to-v1/tests/codec.spec.ts b/packages/session/session-format-v0-to-v1/tests/codec.spec.ts index fd60e4f0e4..5a58b33e66 100644 --- a/packages/session/session-format-v0-to-v1/tests/codec.spec.ts +++ b/packages/session/session-format-v0-to-v1/tests/codec.spec.ts @@ -160,7 +160,7 @@ describe('released v0/v1 physical codecs', () => { ['reversed range', [[2, 1]]], ['range past event', [[0, 3]]], ['overlapping ranges', [[0, 1], [1, 2]]], - ])('refuses malformed stored provenance: %s', (_name, sourceEventSeqs) => { + ])('refuses malformed stored source-event ranges: %s', (_name, sourceEventSeqs) => { const rows = [ { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }, { type: 'user/message', seq: 1, time: 2, data: { @@ -216,9 +216,9 @@ describe('released v0/v1 physical codecs', () => { }) expect(runs).toHaveLength(1) expect([...((runs[0] as SessionFormatEventRun).expand())]).toHaveLength(2) - const provenance = create(header) - decodeRow(provenance, { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }) - const item = decodeRow(provenance, { + const codec = create(header) + decodeRow(codec, { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }) + const item = decodeRow(codec, { type: 'user/message', seq: 1, time: 2, data: { id: 'm', role: 'user', content: [], source: { kind: 'user' } }, sourceEventSeqs: [0], surfaceOp: 'append', diff --git a/packages/session/session-format-v0-to-v1/tests/legacy.spec.ts b/packages/session/session-format-v0-to-v1/tests/legacy.spec.ts index 3dd1d82c3a..8d303b5d95 100644 --- a/packages/session/session-format-v0-to-v1/tests/legacy.spec.ts +++ b/packages/session/session-format-v0-to-v1/tests/legacy.spec.ts @@ -12,6 +12,7 @@ const header = { createdAt: 1, delegationDepth: 0, } as const +const legacyAssistantSourceKey = ['pro', 'venance'].join('') function migrate(rows: readonly unknown[]) { return restoreV0ToV1(header, rows) @@ -33,7 +34,7 @@ describe('released v0 legacy normalization', () => { turn: 1, step: 1, content: [{ type: 'tool-call', id: 'call-1', name: 'read', arguments: '{}' }], - provenance: { provider: 'mock', model: 'mock' }, + [legacyAssistantSourceKey]: { provider: 'mock', model: 'mock' }, }, surfaceOp: 'append', }, diff --git a/packages/session/session-format-v0-to-v1/tests/migration.spec.ts b/packages/session/session-format-v0-to-v1/tests/migration.spec.ts index c786a49ddf..adfea4a67f 100644 --- a/packages/session/session-format-v0-to-v1/tests/migration.spec.ts +++ b/packages/session/session-format-v0-to-v1/tests/migration.spec.ts @@ -144,10 +144,10 @@ describe('released Session format v0 to v1', () => { ], 'recoverable')).toThrow(/seq gap/) }) - it('requires canonical delegation depth and decodes provenance without mutating source rows', () => { + it('requires canonical delegation depth and decodes source-event ranges without mutating source rows', () => { const incompleteHeader = { type: 'session', version: 0, id: 'old', createdAt: 1 } const header = { ...incompleteHeader, delegationDepth: 0 } - const provenanceRow = { + const sourceEventRow = { type: 'assistant/message', seq: 3, time: 5, @@ -171,7 +171,7 @@ describe('released Session format v0 to v1', () => { type: 'assistant/chunk', seq: 2, time: 4, data: { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'x' } }, }, - provenanceRow, + sourceEventRow, ] const migrated = restoreV0ToV1(header, rows) @@ -179,7 +179,7 @@ describe('released Session format v0 to v1', () => { expect(() => restoreV0ToV1(incompleteHeader, rows)).toThrow(/delegationDepth/) expect(migrated.header.delegationDepth).toBe(0) expect(migrated.events[3]?.sourceEventSeqs).toEqual([0, 1, 2]) - expect(provenanceRow.sourceEventSeqs).toEqual([[0, 2]]) + expect(sourceEventRow.sourceEventSeqs).toEqual([[0, 2]]) expect(migrated.header).toEqual({ version: 1, id: 'old', createdAt: 1, isSeeded: false, delegationDepth: 0, }) diff --git a/packages/session/session-format-v0-to-v1/tests/relationships.spec.ts b/packages/session/session-format-v0-to-v1/tests/relationships.spec.ts index ef6223aa79..148a42447f 100644 --- a/packages/session/session-format-v0-to-v1/tests/relationships.spec.ts +++ b/packages/session/session-format-v0-to-v1/tests/relationships.spec.ts @@ -39,7 +39,7 @@ describe('released v1 whole-artifact relationships', () => { data: { rootCallId: 'root', parentCallId: 'root', subCallId: 'sub', name: 'read', arguments: {} }, }, ]], - ['surface replace without shadow provenance', [ + ['surface replace without source-event references', [ { type: 'user/message', seq: 0, time: 1, data: user('one'), surfaceOp: 'append' }, { type: 'user/message', seq: 1, time: 2, data: user('two'), diff --git a/packages/session/session-format-v0-to-v1/tests/validation.spec.ts b/packages/session/session-format-v0-to-v1/tests/validation.spec.ts index 209576c636..8d5dbd6681 100644 --- a/packages/session/session-format-v0-to-v1/tests/validation.spec.ts +++ b/packages/session/session-format-v0-to-v1/tests/validation.spec.ts @@ -369,7 +369,7 @@ describe('released event and payload inventory', () => { }) }).toThrow(/unknown historical event/) }) - it('permits empty Assistant provenance only under the released-v1 policy', () => { + it('permits empty Assistant source-event references only under the released-v1 policy', () => { const assistant = { type: 'assistant/message', seq: 1, time: 2, data: {}, sourceEventSeqs: [], surfaceOp: 'append', @@ -377,7 +377,7 @@ describe('released event and payload inventory', () => { expect(() => { assertReleasedSurfaceMetadata(assistant, 1, assistant.type, 'allow-empty-assistant') }) .not.toThrow() expect(() => { assertReleasedSurfaceMetadata(assistant, 1, assistant.type, 'forbid-assistant') }) - .toThrow(/obsolete chunk provenance/) + .toThrow(/obsolete chunk references/) }) it('keeps capturedFormatVersion v1-only inside session-reference sources', () => { diff --git a/packages/session/session-format-v1-to-v2/README.i18n.yaml b/packages/session/session-format-v1-to-v2/README.i18n.yaml index 3d8e1f3463..adfada7c15 100644 --- a/packages/session/session-format-v1-to-v2/README.i18n.yaml +++ b/packages/session/session-format-v1-to-v2/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/session/session-format-v1-to-v2/README.md -README.md: e2bcd2a2be31fddfd8ce77a1ab5fe3ef8cb22599 -README.zh.md: cf8aa67722b04060ac70f46c6bbe65c9485028d2 +README.md: 9fc2e2b837df65bc3b2df9245c0cc44f84ae1ff2 +README.zh.md: 9bc82a5b1623cee24740ea69f03b03abf1b3ec5b diff --git a/packages/session/session-format-v1-to-v2/README.md b/packages/session/session-format-v1-to-v2/README.md index e2bcd2a2be..9fc2e2b837 100644 --- a/packages/session/session-format-v1-to-v2/README.md +++ b/packages/session/session-format-v1-to-v2/README.md @@ -43,11 +43,11 @@ const eventRecord = releasedV2SessionFormatCodec.encodeEvent(currentEvent) `releasedV1SessionFormatCodec` reads the frozen v1 physical language one row at a time. `sessionFormatV1ToV2` creates the cardinality-changing Stage that the static catalog connects to that decoder without retaining a v1 event array. The catalog remaps declared references and validates the released-v2 envelope, inherited cut, event admission, and relationships. Persistence applies full installed-current validation in its Worker before publication. `releasedV2SessionFormatCodec` creates a released-v2 row decoder and encodes v2 headers and events one record at a time. -A successful v1 `assistant/message` must cite its complete ordered attempt. The migration removes the cited top-level chunks and obsolete message provenance, compacts the chunks without joining token boundaries, and stores the stream on that message. An unclaimed attempt becomes one log-only `assistant/attempt` at its final chunk position. Unrelated interleaved events keep their relative order. +A successful v1 `assistant/message` must cite its complete ordered attempt. The migration removes the cited top-level chunks and obsolete message chunk references, compacts the chunks without joining token boundaries, and stores the stream on that message. An unclaimed attempt becomes one log-only `assistant/attempt` at its final chunk position. Unrelated interleaved events keep their relative order. The edge also closes the bounded legacy restart pattern in which a non-empty `next-turn` inbox insertion is followed by the next `turn/start` without the prior `turn/end`. It records that prior turn as interrupted. A legacy round-zero goal mutation becomes a `goal/change` followed by the original model-visible message with ordinary plugin attribution, so both durable goal state and historical model input survive. -The migration refuses a reference to a consumed chunk instead of redirecting it to a different semantic event. It remaps declared event provenance, surface replacements, command source events, compaction ranges and lists, and title message lists. The already model-visible `session/title-llm-request.messages` text remains byte-identical after source validation, so target validation does not reinterpret the old sequence numbers embedded in that prompt. A seeded source also refuses an inherited cut that splits an Assistant attempt; the target marks the exact cut with `session/end-seed { inherited: true }`. +The migration refuses a reference to a consumed chunk instead of redirecting it to a different semantic event. It remaps declared source-event references, surface replacements, command source events, compaction ranges and lists, and title message lists. The already model-visible `session/title-llm-request.messages` text remains byte-identical after source validation, so target validation does not reinterpret the old sequence numbers embedded in that prompt. A seeded source also refuses an inherited cut that splits an Assistant attempt; the target marks the exact cut with `session/end-seed { inherited: true }`. The v2 physical header requires `isSeeded` and does not store a numeric cut. The codec derives the cut from the last inherited end-seed marker, writes one event per row, range-encodes only `sourceEventSeqs`, and remains neutral to ordinary event vocabulary and payload growth. Released-current restoration admits event types known to the installed Session package plus unknown events carrying `ignorable: true`, and validates event members and relationships. Ordinary Session restoration checks runtime-required settlement fields without replaying embedded streams; persistence publication and the frozen writer-image fixture validator retain full stream verification. @@ -64,7 +64,7 @@ The incremental edge retains one unsettled Assistant attempt, events whose outpu | File | Role | |---|---| | [`src/migration.ts`](src/migration.ts) | Attempt grouping, settlement substitution, dense sequence mapping, and reference rewriting | -| [`src/codec.ts`](src/codec.ts) | Released-v2 header, one-event-per-row encoding, provenance ranges, and recoverable prefix decoding | +| [`src/codec.ts`](src/codec.ts) | Released-v2 header, one-event-per-row encoding, source-event ranges, and recoverable prefix decoding | | [`src/validation.ts`](src/validation.ts) | Physical v2 envelope/cut validation and released-current event admission and relationships | | [`src/dispositions.ts`](src/dispositions.ts) | Frozen released-v2 event and payload-member inventory | diff --git a/packages/session/session-format-v1-to-v2/README.zh.md b/packages/session/session-format-v1-to-v2/README.zh.md index cf8aa67722..9bc82a5b16 100644 --- a/packages/session/session-format-v1-to-v2/README.zh.md +++ b/packages/session/session-format-v1-to-v2/README.zh.md @@ -43,11 +43,11 @@ const eventRecord = releasedV2SessionFormatCodec.encodeEvent(currentEvent) `releasedV1SessionFormatCodec` 逐行读取冻结的 v1 物理语言。`sessionFormatV1ToV2` 创建改变事件基数的 Stage,静态 catalog 把它连接到 decoder,且不保留 v1 事件数组。Catalog 会重映射已声明引用,并校验 released-v2 envelope、inherited cut、事件准入与关系。持久化在发布前通过 Worker 执行完整 installed-current 校验。`releasedV2SessionFormatCodec` 创建已发布 v2 格式的逐行 decoder,并逐条编码 v2 header 与事件。 -成功的 v1 `assistant/message` 必须引用其完整有序 attempt。迁移会移除这些顶层 chunk 和已停用的 message provenance,在不合并 token 边界的前提下压缩 chunk,并把 stream 存到该 message 上。未被 message 认领的 attempt 会在其最后一个 chunk 的位置变成一个仅日志可见的 `assistant/attempt`。无关的交错事件保持相对顺序。 +成功的 v1 `assistant/message` 必须引用其完整有序 attempt。迁移会移除这些顶层 chunk 和已停用的 message chunk reference,在不合并 token 边界的前提下压缩 chunk,并把 stream 存到该 message 上。未被 message 认领的 attempt 会在其最后一个 chunk 的位置变成一个仅日志可见的 `assistant/attempt`。无关的交错事件保持相对顺序。 该 edge 还会闭合一种有限的旧版恢复模式:非空的 `next-turn` inbox 插入后直接出现下一个 `turn/start`,但缺少前一轮的 `turn/end`;迁移将前一轮记录为 interrupted。旧版 round-zero goal mutation 会变成一个 `goal/change`,随后保留原本模型可见的 message 并改用普通 plugin attribution,因此持久 goal 状态与历史模型输入都会保留。 -如果引用指向被消费的 chunk,迁移会失败,而不会把它重定向到语义不同的事件。它会重映射已声明的事件 provenance、surface replacement、command source event、compaction range 与 list,以及 title message list。已经对模型可见的 `session/title-llm-request.messages` 文本会在源校验后保持逐字节不变,因此目标校验不会重新解释该 prompt 中嵌入的旧序号。带 seed 的源若让继承切点切开一个 Assistant attempt,也会迁移失败;目标会用 `session/end-seed { inherited: true }` 标出精确切点。 +如果引用指向被消费的 chunk,迁移会失败,而不会把它重定向到语义不同的事件。它会重映射已声明的 source-event reference、surface replacement、command source event、compaction range 与 list,以及 title message list。已经对模型可见的 `session/title-llm-request.messages` 文本会在源校验后保持逐字节不变,因此目标校验不会重新解释该 prompt 中嵌入的旧序号。带 seed 的源若让继承切点切开一个 Assistant attempt,也会迁移失败;目标会用 `session/end-seed { inherited: true }` 标出精确切点。 v2 物理 header 要求 `isSeeded`,且不存储数值切点。编解码器从最后一个 inherited end-seed marker 推导切点,每行写入一个事件,只对 `sourceEventSeqs` 做范围编码,并对普通事件词汇与 payload 扩展保持中立。Released-current restoration 准入 installed Session package 已知的事件 type,以及携带 `ignorable: true` 的未知事件,并校验事件 member 与关系。普通 Session restore 只检查 runtime 直接依赖的 settlement 字段,不重放嵌入 stream;persistence publication 与冻结的 writer-image fixture validator 保留完整 stream verification。 @@ -64,7 +64,7 @@ v2 物理 header 要求 `isSeeded`,且不存储数值切点。编解码器从 | 文件 | 职责 | |---|---| | [`src/migration.ts`](src/migration.ts) | Attempt 分组、settlement 替换、密集序号映射与引用重写 | -| [`src/codec.ts`](src/codec.ts) | 已发布 v2 header、每行一个事件的编码、provenance 范围与可恢复前缀解码 | +| [`src/codec.ts`](src/codec.ts) | 已发布 v2 header、每行一个事件的编码、source-event 范围与可恢复前缀解码 | | [`src/validation.ts`](src/validation.ts) | v2 物理 envelope/cut 校验,以及 released-current 事件准入与关系校验 | | [`src/dispositions.ts`](src/dispositions.ts) | 冻结的已发布 v2 事件与 payload 成员清单 | diff --git a/packages/session/session-format-v1-to-v2/src/codec.ts b/packages/session/session-format-v1-to-v2/src/codec.ts index 7aad50c1c3..4e461f4274 100644 --- a/packages/session/session-format-v1-to-v2/src/codec.ts +++ b/packages/session/session-format-v1-to-v2/src/codec.ts @@ -34,7 +34,7 @@ export const releasedV2SessionFormatCodec = Object.freeze({ return encodeHeader(header, inheritedEventCount) }, encodeEvent(event: SessionFormatEvent) { - return encodeProvenance(event) + return encodeSourceEventRanges(event) }, } satisfies SessionFormatCodec & SessionFormatCurrentEncoder) @@ -175,7 +175,7 @@ function encodeHeader( } } -function encodeProvenance(event: SessionFormatEvent): SessionFormatJsonObject { +function encodeSourceEventRanges(event: SessionFormatEvent): SessionFormatJsonObject { if (event.sourceEventSeqs === undefined) return event return { ...event, diff --git a/packages/session/session-format-v1-to-v2/src/migration.ts b/packages/session/session-format-v1-to-v2/src/migration.ts index 2e6d13e331..b7a8b94afb 100644 --- a/packages/session/session-format-v1-to-v2/src/migration.ts +++ b/packages/session/session-format-v1-to-v2/src/migration.ts @@ -291,7 +291,7 @@ function transformMessage( } if (pending === undefined || !matchesChunkSources(pending.group, sources)) { - throw refusal(`assistant/message ${event.seq} chunk provenance is not one complete ordered attempt`) + throw refusal(`assistant/message ${event.seq} chunk references are not one complete ordered attempt`) } assertAttemptCut(state, pending.group, event.seq) pending.group.terminal = true diff --git a/packages/session/session-format-v1-to-v2/tests/codec.spec.ts b/packages/session/session-format-v1-to-v2/tests/codec.spec.ts index bb1d37b075..bafce48c37 100644 --- a/packages/session/session-format-v1-to-v2/tests/codec.spec.ts +++ b/packages/session/session-format-v1-to-v2/tests/codec.spec.ts @@ -142,7 +142,7 @@ describe('releasedV2SessionFormatCodec headers', () => { }) describe('releasedV2SessionFormatCodec rows', () => { - it('stores one event per row and compacts scalar, pair, and long-run provenance exactly', () => { + it('stores one event per row and compacts scalar, pair, and long-run source-event references exactly', () => { const source = artifact([ feedback(0), userMessage(1, [0]), @@ -159,7 +159,7 @@ describe('releasedV2SessionFormatCodec rows', () => { expect(decodeV2(encoded.header, encoded.rows)).toStrictEqual(source) }) - it('keeps non-monotonic provenance scalar-only for the current backend reader', () => { + it('keeps non-monotonic source-event references scalar-only for the current backend reader', () => { const sourceEventSeqs = [4, 5, 1, 2, 3] const source = artifact([ feedback(0), feedback(1), feedback(2), feedback(3), feedback(4), feedback(5), @@ -186,7 +186,7 @@ describe('releasedV2SessionFormatCodec rows', () => { expect(decodeV2(encoded.header, encoded.rows)).toStrictEqual(source) }) - it('expands mixed stored ranges and preserves non-provenance rows', () => { + it('expands mixed stored source-event ranges and preserves unrelated rows', () => { const rows: SessionFormatJsonObject[] = [feedback(0), feedback(1), feedback(2), feedback(3), feedback(4), { ...userMessage(5), sourceEventSeqs: [[0, 2], 4], @@ -213,7 +213,7 @@ describe('releasedV2SessionFormatCodec rows', () => { ['duplicate scalars', [0, 0], /unique earlier/], ['overlapping range and scalar', [[0, 1], 1], /unique earlier/], ['non-monotonic range', [3, [0, 2]], /strictly increasing/], - ])('refuses malformed stored provenance: %s', (_name, sourceEventSeqs, message) => { + ])('refuses malformed stored source-event ranges: %s', (_name, sourceEventSeqs, message) => { const rows = [feedback(0), feedback(1), feedback(2), feedback(3), { ...userMessage(4), sourceEventSeqs, }] diff --git a/packages/session/session-format-v1-to-v2/tests/migration.spec.ts b/packages/session/session-format-v1-to-v2/tests/migration.spec.ts index df3b595304..9d516cd555 100644 --- a/packages/session/session-format-v1-to-v2/tests/migration.spec.ts +++ b/packages/session/session-format-v1-to-v2/tests/migration.spec.ts @@ -863,7 +863,7 @@ describe('sessionFormatV1ToV2', () => { }) it.each([undefined, []] as const)( - 'retains a legacy message with provenance %s as an empty embedded stream', + 'retains a legacy message with sourceEventSeqs %s as an empty embedded stream', (sourceEventSeqs) => { const source: SessionFormatArtifact = { header: { @@ -890,10 +890,10 @@ describe('sessionFormatV1ToV2', () => { }, ) - it('refuses missing, partial, or reordered provenance for a present v1 attempt', () => { + it('refuses missing, partial, or reordered source-event references for a present v1 attempt', () => { const build = (sourceEventSeqs: readonly number[] | undefined): SessionFormatArtifact => ({ header: { - version: 1, id: `v1-provenance-${String(sourceEventSeqs)}`, createdAt: 1, + version: 1, id: `v1-source-event-references-${String(sourceEventSeqs)}`, createdAt: 1, isSeeded: false, delegationDepth: 0, }, inheritedEventCount: 0, @@ -1020,7 +1020,7 @@ describe('sessionFormatV1ToV2', () => { expect(() => migrateV1ToV2(source)).toThrow(/cut 4 splits one Assistant attempt/) }) - it('keeps referenced-session generation provenance frozen', () => { + it('keeps referenced-session generation metadata frozen', () => { const reference = { ...userMessage, id: 'reference', diff --git a/packages/session/session-format-v1-to-v2/tests/validation.spec.ts b/packages/session/session-format-v1-to-v2/tests/validation.spec.ts index 0ad2542a6a..5a475c6388 100644 --- a/packages/session/session-format-v1-to-v2/tests/validation.spec.ts +++ b/packages/session/session-format-v1-to-v2/tests/validation.spec.ts @@ -337,30 +337,30 @@ describe('released v2 seed and surface relationships', () => { }) it.each([ - ['assistant chunk provenance', artifact([ + ['assistant chunk references', artifact([ event('user/message', 0, userData(), { surfaceOp: 'append' }), event('assistant/message', 1, assistantData({ content: [], stream: [], usage: null, replayState: null }), { surfaceOp: 'append', sourceEventSeqs: [0], }), - ]), /obsolete chunk provenance/], - ['non-array provenance', artifact([ + ]), /obsolete chunk references/], + ['non-array source-event references', artifact([ event('feedback/record', 0, { text: 'x' }), event('user/message', 1, userData(), { surfaceOp: 'append', sourceEventSeqs: 0 }), ]), /must be an array/], - ['invalid provenance member', artifact([ + ['invalid source-event reference', artifact([ event('feedback/record', 0, { text: 'x' }), event('user/message', 1, userData(), { surfaceOp: 'append', sourceEventSeqs: [-1] }), ]), /sourceEventSeqs member/], - ['current provenance member', artifact([ + ['current source-event reference', artifact([ event('feedback/record', 0, { text: 'x' }), event('user/message', 1, userData(), { surfaceOp: 'append', sourceEventSeqs: [1] }), ]), /unique earlier seqs/], - ['duplicate provenance member', artifact([ + ['duplicate source-event reference', artifact([ event('feedback/record', 0, { text: 'x' }), event('feedback/record', 1, { text: 'y' }), event('user/message', 2, userData(), { surfaceOp: 'append', sourceEventSeqs: [0, 0] }), ]), /unique earlier seqs/], - ['empty provenance', artifact([ + ['empty source-event references', artifact([ event('feedback/record', 0, { text: 'x' }), event('user/message', 1, userData(), { surfaceOp: 'append', sourceEventSeqs: [] }), ]), /must be non-empty/], diff --git a/packages/session/session-format-v2-to-v3/README.i18n.yaml b/packages/session/session-format-v2-to-v3/README.i18n.yaml index 5ae76eaec6..7088b8a0e3 100644 --- a/packages/session/session-format-v2-to-v3/README.i18n.yaml +++ b/packages/session/session-format-v2-to-v3/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/session/session-format-v2-to-v3/README.md -README.md: 65e62f6d28f9f4cb0a75127429ae9a7ae610f5b2 -README.zh.md: e319e97cc01b23fe50e09c4a31513da801e54c3c +README.md: 3f3fe7255a0542840db27d7ae605862e5cb97de9 +README.zh.md: 8f26cfcb6854a8b4fd6eec038c411bb1b524a535 diff --git a/packages/session/session-format-v2-to-v3/README.md b/packages/session/session-format-v2-to-v3/README.md index 65e62f6d28..3f3fe7255a 100644 --- a/packages/session/session-format-v2-to-v3/README.md +++ b/packages/session/session-format-v2-to-v3/README.md @@ -69,7 +69,7 @@ The first `step/start` is followed immediately by an empty `system/message` appe At every `request/header`, absent `data.header.system` means the empty prompt; otherwise its string is compared exactly with the current prompt. A change inserts a system message immediately before that request header, replacing exactly the current protected head and citing it in `sourceEventSeqs`. An unchanged prompt inserts nothing. Empty strings and absent fields clear an earlier prompt; whitespace-only strings remain nonempty text. Every request header loses `data.header.system`, regardless of whether a replacement was needed. -Synthetic messages carry the open step's `turn` and `step`, the anchor event's `time`, role `system`, and source `{ kind: 'plugin', plugin: '@deepseek-ai/dsh-system-prompt' }`. Empty prompts use `content: []`; other prompts use one text block containing the exact string. The first append has no provenance; each replacement uses the preceding head's target sequence for both endpoints and its sole source reference. Empty heads retain protection but produce no model message. +Synthetic messages carry the open step's `turn` and `step`, the anchor event's `time`, role `system`, and source `{ kind: 'plugin', plugin: '@deepseek-ai/dsh-system-prompt' }`. Empty prompts use `content: []`; other prompts use one text block containing the exact string. The first append has no source-event reference; each replacement uses the preceding head's target sequence for both endpoints and its sole source reference. Empty heads retain protection but produce no model message. Each synthetic id is `v2-to-v3-system-` followed by the hexadecimal SHA-256 of `JSON.stringify(['session-format-v2-to-v3', sourceHeader.id, anchor.seq, anchor.type])`. The anchor is the source `step/start` for initial creation or the changed `request/header` for replacement. Collisions with generated or source message ids are refused in either encounter order, including ids in inbox insertions and title-request messages. Existing message ids never change. In particular, a `TOOL_NOT_STARTED` repair id retains its canonical historical `interrupted-tool-result--` suffix; that suffix is not a target sequence coordinate. @@ -105,7 +105,7 @@ Similar plugin names, other source kinds, arbitrary text, nested JSON, and histo After structural insertion and reference remapping, canonicalization converts exact envelope replacements `{ op: 'replace', start, end }` to `{ op: 'replace', startSeq, endSeq }` on original and synthetic events. It omits exactly `tools: []` and `adapterDefaults: {}` from `request/header.data.header`. This final operation preserves its input event count, coordinates, timestamps, order, and inherited cut; it neither remaps twice nor normalizes unrelated empty values such as `config.stop: []`. -All four V3 surface types (`system/message`, `user/message`, `assistant/message`, `tool/result`) require `surfaceOp`. Assistant messages alone forbid `sourceEventSeqs`; for the others, a supplied list must be nonempty, unique, and refer only to earlier events. Known log-only events allow neither surface metadata field. Replacements allow no aliases or extra keys. Their endpoints identify an inclusive span in current surface order, not numeric sequence order; restoration checks live membership, endpoint order, and complete provenance coverage. +All four V3 surface types (`system/message`, `user/message`, `assistant/message`, `tool/result`) require `surfaceOp`. Assistant messages alone forbid `sourceEventSeqs`; for the others, a supplied list must be nonempty, unique, and refer only to earlier events. Known log-only events allow neither surface metadata field. Replacements allow no aliases or extra keys. Their endpoints identify an inclusive span in current surface order, not numeric sequence order; restoration checks live membership, endpoint order, and complete source-event coverage. Source surface events already require placement; migration does not invent missing append markers. A `tool/result` with `data.error` requires its single tool-result block to carry `isError: true`. Failed results may omit structured error identity. Contradictory outcomes are refused, never repaired by adding `isError` or deleting diagnostics. Ordinary tool and PTC lifecycle relationships still require validation after these event-local checks. @@ -144,7 +144,7 @@ Input already marked V3 does not run V2-to-V3. Native catalog reads with `valida - Native V3 admits in-history system appends, non-head system replacements, and compaction of non-head system nodes. System messages require valid payloads and matching open-step ownership. The first surface system head can be replaced only by a system message covering exactly that head; ordinary replacements and compaction cannot consume it. Migration itself produces only the initial head and head replacements, not route-dependent in-history updates. - Native V3 rejects every `request/header.data.header.system`, even empty or malformed, and rejects noncanonical replacement spellings and the two empty header optionals. It preserves whitespace content, empty stop lists, and admitted nested header/source/data extensions. That extension admission does not widen the V2 source audit or the exact logical Session header fields. -- Required predecessor PTC tags are refused even if installed. Obsolete or unknown ignorable events remain opaque, including their logical metadata, and cannot satisfy current PTC relationships. Installed ordinary event additions are admitted as log-only envelopes; unknown required types are refused by vocabulary-aware restoration. The physical codec still enforces released framing and provenance encoding. +- Required predecessor PTC tags are refused even if installed. Obsolete or unknown ignorable events remain opaque, including their logical metadata, and cannot satisfy current PTC relationships. Installed ordinary event additions are admitted as log-only envelopes; unknown required types are refused by vocabulary-aware restoration. The physical codec still enforces released framing and source-event range encoding. - V3 event-local checks run before encoding and after decoding. Raw retired-system-header, malformed-system-payload, and required predecessor-PTC refusal run before recoverable suppression, including after corrupt rows. Strict reads reject canonical errors immediately. Recoverable canonical decoding withholds the first invalid event and its suffix; a later `turn/end` establishes a commit and rejects that suffix. Only accepted inherited markers count; a seeded accepted prefix without one is refused. Unclassified event metadata is deferred to vocabulary-aware restoration rather than discarded as canonical corruption, so it cannot hide an unknown required type. ----- diff --git a/packages/session/session-format-v2-to-v3/README.zh.md b/packages/session/session-format-v2-to-v3/README.zh.md index e319e97cc0..8f26cfcb68 100644 --- a/packages/session/session-format-v2-to-v3/README.zh.md +++ b/packages/session/session-format-v2-to-v3/README.zh.md @@ -69,7 +69,7 @@ const targetHeader = sessionFormatV2ToV3.migrateHeader(sourceHeader) 在每条 `request/header` 处,缺失的 `data.header.system` 表示空提示词;否则,其字符串与当前提示词进行精确比较。发生变化时,在该请求头之前立即插入系统消息,恰好替换当前受保护的头节点,并在 `sourceEventSeqs` 中引用它。提示词不变时不插入消息。空字符串和缺失字段会清空先前的提示词;仅含空白的字符串仍为非空文本。每个请求头都会移除 `data.header.system`,无论是否需要替换。 -合成消息携带开放步骤的 `turn` 和 `step`、锚点事件的 `time`、角色 `system`,以及来源 `{ kind: 'plugin', plugin: '@deepseek-ai/dsh-system-prompt' }`。空提示词使用 `content: []`;其他提示词使用包含精确字符串的单个文本块。首次追加没有溯源;每次替换的两个端点及唯一源引用都使用前一头节点的目标序号。空头节点保持受保护,但不产生模型消息。 +合成消息携带开放步骤的 `turn` 和 `step`、锚点事件的 `time`、角色 `system`,以及来源 `{ kind: 'plugin', plugin: '@deepseek-ai/dsh-system-prompt' }`。空提示词使用 `content: []`;其他提示词使用包含精确字符串的单个文本块。首次追加没有源事件引用;每次替换的两个端点及唯一源引用都使用前一头节点的目标序号。空头节点保持受保护,但不产生模型消息。 每个合成标识由 `v2-to-v3-system-` 加上 `JSON.stringify(['session-format-v2-to-v3', sourceHeader.id, anchor.seq, anchor.type])` 的十六进制 SHA-256 构成。首次创建的锚点是源 `step/start`,替换的锚点是提示词变化的 `request/header`。与已生成或源消息标识的冲突均被拒绝,不受遇到顺序影响;检查范围包括收件箱插入消息与标题请求消息中的标识。现有消息标识绝不改变。特别是,`TOOL_NOT_STARTED` 修复标识保留规范的历史 `interrupted-tool-result--` 后缀;该后缀不是目标序列坐标。 @@ -105,7 +105,7 @@ const targetHeader = sessionFormatV2ToV3.migrateHeader(sourceHeader) 结构插入和引用重映射完成后,规范化将原始与合成事件上的精确信封替换对象 `{ op: 'replace', start, end }` 转为 `{ op: 'replace', startSeq, endSeq }`。它仅从 `request/header.data.header` 中省略精确的 `tools: []` 和 `adapterDefaults: {}`。这个最终操作保留其输入事件数、坐标、时间戳、顺序与继承切点;既不重复映射,也不规范化 `config.stop: []` 等无关空值。 -四种 V3 surface 类型(`system/message`、`user/message`、`assistant/message`、`tool/result`)都要求 `surfaceOp`。仅 assistant 消息禁止 `sourceEventSeqs`;其他类型提供的列表必须非空、唯一,且仅引用更早的事件。已知仅日志事件对这两个 surface 元数据字段均不允许。替换不允许别名或额外键。端点按当前 surface 顺序而非数值序号顺序标识闭区间;恢复会检查存活成员、端点顺序与完整溯源覆盖。 +四种 V3 surface 类型(`system/message`、`user/message`、`assistant/message`、`tool/result`)都要求 `surfaceOp`。仅 assistant 消息禁止 `sourceEventSeqs`;其他类型提供的列表必须非空、唯一,且仅引用更早的事件。已知仅日志事件对这两个 surface 元数据字段均不允许。替换不允许别名或额外键。端点按当前 surface 顺序而非数值序号顺序标识闭区间;恢复会检查存活成员、端点顺序与完整源事件覆盖。 源 surface 事件本就要求位置标记;迁移不虚构缺失的追加标记。带有 `data.error` 的 `tool/result` 要求其唯一工具结果块携带 `isError: true`。失败结果可以省略结构化错误身份。矛盾结果会被拒绝,绝不通过添加 `isError` 或删除诊断来修复。普通工具与 PTC 生命周期关系仍须在这些事件本地检查后验证。 @@ -144,7 +144,7 @@ V2 `session-log-deepseek/delivery-accepted` 若携带 `data.sessionFormatVersion - 原生 V3 接纳历史内系统消息追加、非头系统节点替换和非头系统节点压缩。系统消息要求有效载荷及匹配的开放步骤归属。首个 surface 系统头节点只能被恰好覆盖该头节点的系统消息替换;普通替换和压缩不能消耗它。迁移本身只产生初始头节点与头节点替换,不产生依赖路由的历史内更新。 - 原生 V3 拒绝任何 `request/header.data.header.system`,包括空值或格式错误值,并拒绝非规范替换拼写及两个空请求头可选字段。它保留空白内容、空停止列表和已接纳的嵌套 header/source/data 扩展。此扩展准入不会扩大 V2 源审计或精确的逻辑会话头字段范围。 -- 必需的前代 PTC 标签即使已安装也会被拒绝。已退役或未知的可忽略事件(包括其逻辑元数据)保持不透明,且不能满足当前 PTC 关系。已安装的普通事件新增项作为仅日志信封接纳;未知必需类型由识别词汇的恢复阶段拒绝。物理编解码器仍执行已发布的分帧与溯源编码规则。 +- 必需的前代 PTC 标签即使已安装也会被拒绝。已退役或未知的可忽略事件(包括其逻辑元数据)保持不透明,且不能满足当前 PTC 关系。已安装的普通事件新增项作为仅日志信封接纳;未知必需类型由识别词汇的恢复阶段拒绝。物理编解码器仍执行已发布的分帧与源事件区间编码规则。 - V3 事件本地检查在编码前及解码后执行。原始行的已退役系统头字段、畸形系统载荷和必需前代 PTC 的拒绝先于可恢复抑制执行,包括损坏行之后。严格读取立即拒绝规范错误。可恢复的规范解码不产出首个无效事件及其后缀;后续 `turn/end` 建立提交事实并拒绝该后缀。只有已接纳的继承标记计数;有种子的已接纳前缀若没有标记则被拒绝。未分类事件元数据会延迟到识别词汇的恢复阶段,而不是作为规范损坏丢弃,因此不能隐藏未知必需类型。 ----- diff --git a/packages/session/session-format-v2-to-v3/src/codec.ts b/packages/session/session-format-v2-to-v3/src/codec.ts index 4873993eed..aae2c2b762 100644 --- a/packages/session/session-format-v2-to-v3/src/codec.ts +++ b/packages/session/session-format-v2-to-v3/src/codec.ts @@ -11,7 +11,7 @@ import { releasedV2SessionFormatCodec } from '@deepseek-ai/dsh-session-format-v1 import { assertReleasedV3Header, assertV3EventAdmission } from './validation.ts' import { assertV3Event, assertV3StructuralRow } from './payload.ts' -/** V3 codec validates structural rows before recovery and logical envelopes after provenance decoding. */ +/** V3 codec validates structural rows before recovery and logical envelopes after source-event range decoding. */ export const releasedV3SessionFormatCodec = Object.freeze({ version: 3, decodeHeader(value: unknown) { @@ -77,7 +77,7 @@ export const releasedV3SessionFormatCodec = Object.freeze({ /** * Validate owned V3 admission rules before a scanner or codec can discard a recoverable tail. - * This checks only identified structural payloads; physical provenance still belongs to decoding. + * This checks only identified structural payloads; physical source-event ranges still belong to decoding. * @param row - parsed physical row, before envelope or compressed-range decoding. */ export function assertV3RowAdmission(row: unknown): void { diff --git a/packages/session/session-format-v2-to-v3/tests/canonical-envelopes.spec.ts b/packages/session/session-format-v2-to-v3/tests/canonical-envelopes.spec.ts index 7467b38195..bded77922f 100644 --- a/packages/session/session-format-v2-to-v3/tests/canonical-envelopes.spec.ts +++ b/packages/session/session-format-v2-to-v3/tests/canonical-envelopes.spec.ts @@ -84,7 +84,7 @@ function toolLog(isError: boolean, error?: SessionFormatJsonValue): SessionForma } describe('canonical V2 to V3 envelopes', () => { - it('renames replacement keys after structural insertion and remaps provenance', () => { + it('renames replacement keys after structural insertion and remaps source-event references', () => { const first = user(2) const replacement = user(3, { surfaceOp: { op: 'replace', start: 2, end: 2 }, sourceEventSeqs: [2] }) const source = [...opening(), first, replacement] @@ -232,14 +232,14 @@ describe.each(['migration', 'native V3'] as const)('%s event admission', (mode) ] satisfies [string, SessionFormatJsonValue][])('forbids assistant sourceEventSeqs: %s', (_name, sourceEventSeqs) => { const events = toolLog(false) events[2] = { ...events[2]!, sourceEventSeqs } - expect(() => admit(events)).toThrow(mode === 'migration' ? /retains obsolete chunk provenance/ : /cannot carry sourceEventSeqs/) + expect(() => admit(events)).toThrow(mode === 'migration' ? /chunk references/ : /cannot carry sourceEventSeqs/) }) it.each([ ['empty', []], ['scalar', 0], ['null', null], ['negative', [-1]], ['fractional', [0.5]], ['unsafe integer', [Number.MAX_SAFE_INTEGER + 1]], ['current', [3]], ['future', [4]], ['duplicate', [0, 0]], ] satisfies [string, SessionFormatJsonValue][])( - 'refuses invalid provenance: %s', (_name, sourceEventSeqs) => { + 'refuses invalid source-event references: %s', (_name, sourceEventSeqs) => { expect(() => admit([...opening(), user(2), user(3, { surfaceOp: 'append', sourceEventSeqs })])) .toThrow(/sourceEventSeqs/) }, @@ -299,7 +299,7 @@ describe('native V3 opaque event preservation', () => { ) it.each(['external/future', 'tool/code-dispatch-start', 'tool/code-dispatch'])( - 'round-trips ignorable %s with physical provenance and arbitrary data', (type) => { + 'round-trips ignorable %s with physical source-event ranges and arbitrary data', (type) => { for (const data of [null, false, ['opaque'], { nested: { sourceEventSeqs: null } }]) { const source = event(type, 1, data, { ignorable: true, sourceEventSeqs: [0], surfaceOp: { future: ['opaque'] } }) const row = releasedV3SessionFormatCodec.encodeEvent(source) @@ -497,8 +497,8 @@ describe('V3 full artifact relationships', () => { it.each([ ['shadowed endpoint', 2, 4, [2, 4], /range is not on the current surface/], ['reversed surface order', 4, 5, [4, 5], /range is not on the current surface/], - ['missing provenance', 5, 4, undefined, /omit a shadowed surface node/], - ['incomplete provenance', 5, 4, [5], /omit a shadowed surface node/], + ['missing source-event references', 5, 4, undefined, /omit a shadowed surface node/], + ['incomplete source-event references', 5, 4, [5], /omit a shadowed surface node/], ] as const)('retains range and source-coverage validation: %s', (_name, start, end, sources, message) => { const source = [ ...opening(), user(2), user(3), user(4), diff --git a/packages/session/session-log-deepseek/README.i18n.yaml b/packages/session/session-log-deepseek/README.i18n.yaml index 7f75b42d3d..d2c8bc5b03 100644 --- a/packages/session/session-log-deepseek/README.i18n.yaml +++ b/packages/session/session-log-deepseek/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/session/session-log-deepseek/README.md -README.md: 38a5ec64cee32c25b11e5b7c0b55b7302f66952a -README.zh.md: 2764b3d14ed5a98e140e43583c94424b527065e0 +README.md: fb243a2b1d4975a5c1f8f9671b6564300af2d888 +README.zh.md: a49eae2b89c7e550c77fdccb63a527fa1d8ddc51 diff --git a/packages/session/session-log-deepseek/README.md b/packages/session/session-log-deepseek/README.md index 38a5ec64ce..fb243a2b1d 100644 --- a/packages/session/session-log-deepseek/README.md +++ b/packages/session/session-log-deepseek/README.md @@ -34,7 +34,7 @@ Shipped profiles mount the plugin so an overlay can enable it, but the default c ## Request field -For a request carrying a live `sessionId`, the plugin folds the greatest accepted watermark for that exact Session format generation, snapshots `Session.events`, and sends the contiguous suffix after the watermark. A process-local fold scans each event once and consumes later appends incrementally; restart and HMR rebuild it from the durable log. The version-1 field contains `sessionFormatVersion`, a raw session header (`seedLength` is present only for a seeded Session), numeric `afterSeq` and `throughSeq`, and every complete canonical event translated to raw-number envelope fields. Forked sessions ignore inherited parent watermarks because both the recorded Session id and format generation must match the request source. Surface events require `surfaceOp`, with numeric `startSeq` and `endSeq` for replacements; only user and tool events may carry `sourceEventSeqs`. Assistant provenance stays in the embedded stream, and log-only events carry neither metadata field. +For a request carrying a live `sessionId`, the plugin folds the greatest accepted watermark for that exact Session format generation, snapshots `Session.events`, and sends the contiguous suffix after the watermark. A process-local fold scans each event once and consumes later appends incrementally; restart and HMR rebuild it from the durable log. The version-1 field contains `sessionFormatVersion`, a raw session header (`seedLength` is present only for a seeded Session), numeric `afterSeq` and `throughSeq`, and every complete canonical event translated to raw-number envelope fields. Forked sessions ignore inherited parent watermarks because both the recorded Session id and format generation must match the request source. Surface events require `surfaceOp`, with numeric `startSeq` and `endSeq` for replacements; only user and tool events may carry `sourceEventSeqs`. Assistant provider metadata stays in the embedded stream, and log-only events carry neither metadata field. ## Acceptance and retry diff --git a/packages/session/session-log-deepseek/README.zh.md b/packages/session/session-log-deepseek/README.zh.md index 2764b3d14e..a49eae2b89 100644 --- a/packages/session/session-log-deepseek/README.zh.md +++ b/packages/session/session-log-deepseek/README.zh.md @@ -34,7 +34,7 @@ kind: "package-reference" ## 请求字段 -对于携带存活 `sessionId` 的请求,插件会折叠该确切会话格式代的最大已接受水位,对 `Session.events` 取快照,并发送水位之后的连续后缀。进程内 fold 会让每条事件只被扫描一次并增量消费后续追加;重启与 HMR(热模块替换)会从持久日志重建它。版本 1 字段包含 `sessionFormatVersion`、原始会话 header(仅 seeded Session 携带 `seedLength`)、数值型 `afterSeq` 与 `throughSeq`,以及每个已转换为原始数值 envelope 字段的完整规范事件。只有记录的会话 id 与格式代均匹配请求来源时水位才生效,因此 fork 会话会忽略从父会话继承的水位。表层事件必须携带 `surfaceOp`,替换范围使用数值型 `startSeq` 与 `endSeq`;仅 user 与 tool 事件可以携带 `sourceEventSeqs`。assistant 的来源保留在内嵌流中,只出现在日志中的事件不携带这两个元数据字段。 +对于携带存活 `sessionId` 的请求,插件会折叠该确切会话格式代的最大已接受水位,对 `Session.events` 取快照,并发送水位之后的连续后缀。进程内 fold 会让每条事件只被扫描一次并增量消费后续追加;重启与 HMR(热模块替换)会从持久日志重建它。版本 1 字段包含 `sessionFormatVersion`、原始会话 header(仅 seeded Session 携带 `seedLength`)、数值型 `afterSeq` 与 `throughSeq`,以及每个已转换为原始数值 envelope 字段的完整规范事件。只有记录的会话 id 与格式代均匹配请求来源时水位才生效,因此 fork 会话会忽略从父会话继承的水位。表层事件必须携带 `surfaceOp`,替换范围使用数值型 `startSeq` 与 `endSeq`;仅 user 与 tool 事件可以携带 `sourceEventSeqs`。assistant 的提供方元数据保留在内嵌流中,只出现在日志中的事件不携带这两个元数据字段。 ## 接受与重试 diff --git a/packages/session/session-log-deepseek/src/types.ts b/packages/session/session-log-deepseek/src/types.ts index c2ea6cbdad..4201c51f99 100644 --- a/packages/session/session-log-deepseek/src/types.ts +++ b/packages/session/session-log-deepseek/src/types.ts @@ -25,7 +25,7 @@ export type DeepSeekSessionLogWireSurfaceOp = /** * One canonical event translated to raw JSON primitives for upload. Surface * events require an operation; system, user, and tool events may cite sources. - * Assistant provenance is embedded in its data; log-only events carry neither field. + * Assistant provider metadata is embedded in its data; log-only events carry neither field. */ export type DeepSeekSessionLogWireEvent = { [K in SessionEvent['type']]: { diff --git a/packages/session/session-log-deepseek/tests/upload.spec.ts b/packages/session/session-log-deepseek/tests/upload.spec.ts index e92df32921..7527b8b92a 100644 --- a/packages/session/session-log-deepseek/tests/upload.spec.ts +++ b/packages/session/session-log-deepseek/tests/upload.spec.ts @@ -83,7 +83,7 @@ describe('incremental DeepSeek session-log upload', () => { }>() }) - it('uploads assistant provenance only through its embedded stream', async () => { + it('uploads Assistant provider metadata only through its embedded stream', async () => { const { ctx, session } = await harness('wire-assistant') const assistant = session.append('assistant/message', { turn: 1, @@ -106,7 +106,7 @@ describe('incremental DeepSeek session-log upload', () => { }]) }) - it('uploads system append and replacement placement with unchanged data and provenance', async () => { + it('uploads system append and replacement placement with unchanged data and source-event references', async () => { const { ctx, session } = await harness('wire-system') const headData = { turn: 1, step: 1, message: createSystemMessage('head', 'fixture'), extra: { retained: true } } const head = session.append('system/message', headData, { surfaceOp: 'append' }) diff --git a/packages/session/session-persistence-jsonl/README.i18n.yaml b/packages/session/session-persistence-jsonl/README.i18n.yaml index 16b3376a52..b0af7efebf 100644 --- a/packages/session/session-persistence-jsonl/README.i18n.yaml +++ b/packages/session/session-persistence-jsonl/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/session/session-persistence-jsonl/README.md -README.md: 942f8540bde43671d358f979554c89358c3ad1bf +README.md: fc431ff27bcd236c4d44b8d3e7b20d4291b936d4 README.zh.md: 3b265bf43f058d4c0711568df156825febed4d24 diff --git a/packages/session/session-persistence-jsonl/README.md b/packages/session/session-persistence-jsonl/README.md index 942f8540bd..fc431ff27b 100644 --- a/packages/session/session-persistence-jsonl/README.md +++ b/packages/session/session-persistence-jsonl/README.md @@ -53,7 +53,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a ### On-disk layout -Each session gets a session-owned directory under a readable project directory. Every canonical generation starts with a physical header whose version equals its filename. The current format stores one physical row per durable event; the frozen v0 and v1 readers also understand their historical packed Assistant-delta rows. The current format stores `isSeeded` in the header and derives the inherited cut from the last tagged `session/end-seed` marker, while historical codecs translate their numeric `seedLength`. The format catalog completes that translation before a handle exposes current logical values. Current storage records use the lossless provenance representation described below: +Each session gets a session-owned directory under a readable project directory. Every canonical generation starts with a physical header whose version equals its filename. The current format stores one physical row per durable event; the frozen v0 and v1 readers also understand their historical packed Assistant-delta rows. The current format stores `isSeeded` in the header and derives the inherited cut from the last tagged `session/end-seed` marker, while historical codecs translate their numeric `seedLength`. The format catalog completes that translation before a handle exposes current logical values. Current storage records use the lossless source-event representation described below: ```text / diff --git a/packages/session/session-persistence-jsonl/tests/jsonl.spec.ts b/packages/session/session-persistence-jsonl/tests/jsonl.spec.ts index 61224de226..854aa5226b 100644 --- a/packages/session/session-persistence-jsonl/tests/jsonl.spec.ts +++ b/packages/session/session-persistence-jsonl/tests/jsonl.spec.ts @@ -1998,9 +1998,9 @@ describe('JsonlSessionPersistence: scanLog unit', () => { expect(() => { scanner.write(Buffer.from('null\n')) }).toThrow(/invalid committed event/) }) - it('expands valid stored provenance ranges', () => { + it('expands valid stored source-event ranges', () => { const log = [ - JSON.stringify(toHeaderLine(meta('scanner-provenance'))), + JSON.stringify(toHeaderLine(meta('scanner-source-ranges'))), JSON.stringify({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } }), JSON.stringify(oneTurnLog()[1]), JSON.stringify({ diff --git a/packages/session/session-title-llm/src/index.ts b/packages/session/session-title-llm/src/index.ts index c8ccadcde5..d0092ec956 100644 --- a/packages/session/session-title-llm/src/index.ts +++ b/packages/session/session-title-llm/src/index.ts @@ -17,7 +17,7 @@ import { } from '@deepseek-ai/dsh-session-title' import type { SessionTitleAutomaticMode, - SessionTitleModelProvenance, + SessionTitleModelIdentity, SessionTitleProviderRequest, SessionTitleProviderResult, SessionTitleUserMessage, @@ -30,7 +30,7 @@ export interface SessionTitleLlmRequestEventData { /** Exact human `user/message` seqs represented in `messages`. */ readonly messageSeqs: SessionSeq[] /** Exact auxiliary LLM route. */ - readonly route: SessionTitleModelProvenance + readonly route: SessionTitleModelIdentity /** Exact auxiliary system prompt. */ readonly system: string /** Exact auxiliary message list. */ @@ -174,7 +174,7 @@ export function registerSessionTitleLlmProvider( function resolveRoute( config: ResolvedSessionTitleLlmConfig, request: SessionTitleProviderRequest, -): SessionTitleModelProvenance { +): SessionTitleModelIdentity { if (config.provider !== undefined && config.model !== undefined) { return { provider: config.provider, model: config.model } } diff --git a/packages/session/session-title/src/index.ts b/packages/session/session-title/src/index.ts index 014310b529..8d9a550a51 100644 --- a/packages/session/session-title/src/index.ts +++ b/packages/session/session-title/src/index.ts @@ -21,7 +21,7 @@ import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection' import type {} from '@deepseek-ai/dsh-agent' export type { SessionTitleEventData, - SessionTitleModelProvenance, + SessionTitleModelIdentity, SessionTitleSnapshot, SessionTitleSource, SessionTitleUserMessage, @@ -30,7 +30,7 @@ export type { import { fallbackSessionTitle, normalizeSessionTitle } from './normalize.ts' import type { SessionTitleEventData, - SessionTitleModelProvenance, + SessionTitleModelIdentity, SessionTitleSnapshot, SessionTitleSource, SessionTitleUserMessage, @@ -98,7 +98,7 @@ export interface SessionTitleProviderRequest { /** All eligible human messages through this generation revision. */ readonly messages: readonly SessionTitleUserMessage[] /** Exact current logged main-request route, when one has been recorded. */ - readonly route?: SessionTitleModelProvenance + readonly route?: SessionTitleModelIdentity /** Cancellation for supersession, disposal, timeout composition, or the explicit caller. */ readonly signal: AbortSignal } @@ -110,7 +110,7 @@ export interface SessionTitleProviderResult { /** Exact seqs from `request.messages` used by this result. */ readonly messageSeqs: readonly SessionSeq[] /** Auxiliary LLM route, when generation used a model. */ - readonly model?: SessionTitleModelProvenance + readonly model?: SessionTitleModelIdentity } /** One optional asynchronous title implementation registered with the service. */ @@ -556,7 +556,7 @@ export class SessionTitleService extends Service { session: Session, state: SessionTitleWorkState, pending: PendingAutomaticWork, - route: SessionTitleModelProvenance, + route: SessionTitleModelIdentity, ): void { delete state.pending this.defer(async () => { @@ -578,7 +578,7 @@ export class SessionTitleService extends Service { private startProvider( session: Session, work: ActiveProviderWork, - route?: SessionTitleModelProvenance, + route?: SessionTitleModelIdentity, ): Promise { const run = Promise.resolve().then(() => this.runProvider(session, work, route)) return this.track(run, work.registration) @@ -588,7 +588,7 @@ export class SessionTitleService extends Service { private async runProvider( session: Session, work: ActiveProviderWork, - route?: SessionTitleModelProvenance, + route?: SessionTitleModelIdentity, ): Promise { try { this.assertCurrent(session, work) @@ -651,7 +651,7 @@ export class SessionTitleService extends Service { previous = index } const modelCandidate = candidate.model - let model: SessionTitleModelProvenance | undefined + let model: SessionTitleModelIdentity | undefined if (modelCandidate !== undefined) { if (modelCandidate === null || typeof modelCandidate !== 'object') { throw new Error('session-title provider result model must contain non-empty provider and model strings') diff --git a/packages/session/session-title/src/types.ts b/packages/session/session-title/src/types.ts index c7d913f9e3..20855445a5 100644 --- a/packages/session/session-title/src/types.ts +++ b/packages/session/session-title/src/types.ts @@ -17,7 +17,7 @@ import type { OptionalSessionSeq, SessionSeq } from '@deepseek-ai/dsh-session/ty export type SessionTitleProviderId = Branded<'SessionTitleProviderId'> /** Exact auxiliary model route that produced a title. */ -export interface SessionTitleModelProvenance { +export interface SessionTitleModelIdentity { /** Registered LLM provider route. */ readonly provider: string /** Provider model id. */ @@ -30,7 +30,7 @@ export type SessionTitleSource = | { readonly kind: 'provider' readonly provider: SessionTitleProviderId - readonly model?: SessionTitleModelProvenance + readonly model?: SessionTitleModelIdentity } | { /** Explicit user rename: pins the title — automatic generation stops scheduling. */ diff --git a/packages/shell/pwsh-sandbox/src/helpers.ts b/packages/shell/pwsh-sandbox/src/helpers.ts index 9c843dcc82..963d769390 100644 --- a/packages/shell/pwsh-sandbox/src/helpers.ts +++ b/packages/shell/pwsh-sandbox/src/helpers.ts @@ -26,7 +26,7 @@ function isUsableWorkdir(path: string): boolean { } /** - * Attribute only Node ENOENT/EACCES failures with positive argv[0] provenance + * Attribute only Node ENOENT/EACCES failures whose path or syscall identifies argv[0] * after independently ruling out the caller-owned cwd. A supplied error path * must exactly identify the runner; without one, the syscall must. With a * usable cwd, these codes describe resolution or execute permission for that diff --git a/packages/shell/pwsh-sandbox/tests/sandbox.spec.ts b/packages/shell/pwsh-sandbox/tests/sandbox.spec.ts index 166e5ed042..6b09a1cf4e 100644 --- a/packages/shell/pwsh-sandbox/tests/sandbox.spec.ts +++ b/packages/shell/pwsh-sandbox/tests/sandbox.spec.ts @@ -87,7 +87,7 @@ describe('helpers (pure)', () => { const bare = 'node' const relative = './sandbox-runner' - it('attributes ENOENT/EACCES with argv[0] provenance and a usable workdir', () => { + it('attributes ENOENT/EACCES when path or syscall identifies argv[0] and the workdir is usable', () => { for (const runnerProgram of [absolute, bare, relative]) { expect(isRunnerSpawnFailure({ code: 'ENOENT', syscall: `spawn ${runnerProgram}`, path: runnerProgram }, runnerProgram, workdir)).toBe(true) expect(isRunnerSpawnFailure({ code: 'EACCES', syscall: `spawn ${runnerProgram}`, path: runnerProgram }, runnerProgram, workdir)).toBe(true) @@ -96,7 +96,7 @@ describe('helpers (pure)', () => { } }) - it('rejects mismatched provenance, foreign codes, unusable workdirs, and non-object errors', () => { + it('rejects mismatched runner evidence, foreign codes, unusable workdirs, and non-object errors', () => { expect(isRunnerSpawnFailure({ code: 'ENOENT', syscall: 'spawn', path: 'other' }, 'node', workdir)).toBe(false) expect(isRunnerSpawnFailure({ code: 'ENOENT', syscall: 'spawn other', path: 'node' }, 'node', workdir)).toBe(false) expect(isRunnerSpawnFailure({ code: 'EMFILE', syscall: 'spawn', path: 'node' }, 'node', workdir)).toBe(false) @@ -300,7 +300,7 @@ describe.skipIf(!pwshAvailable())('SandboxPwshExecutor', () => { expect(denied.sandbox).toEqual({ mode: 'read-only', denied: true, enforcement: 'full' }) }, 30_000) - it('background provider rejections with runner provenance settle as runnerFailed facts', async () => { + it('background provider rejections with runner identity settle as runnerFailed facts', async () => { const { executor } = await setup(() => ({ argv: ['definitely-not-a-real-runner', '--', 'pwsh'], enforcement: 'full', diff --git a/packages/spill/spill-policy/src/notice.ts b/packages/spill/spill-policy/src/notice.ts index 80a19c93b6..94d8b7b87a 100644 --- a/packages/spill/spill-policy/src/notice.ts +++ b/packages/spill/spill-policy/src/notice.ts @@ -31,7 +31,7 @@ function isOmission(text: string): boolean { /** * Recognize a final spill-policy notice in persisted text, including notice-only output. - * This identifies the text convention, not authenticated provenance of tool output. + * This identifies the text convention, not authenticated tool-output origin. * @param text - complete recorded text result. * @returns whether a complete notice occupies the end of the result. */ diff --git a/packages/spill/spill/README.i18n.yaml b/packages/spill/spill/README.i18n.yaml index b266c0af25..d79c75ad97 100644 --- a/packages/spill/spill/README.i18n.yaml +++ b/packages/spill/spill/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/spill/spill/README.md -README.md: e05dad3f5ea8ed6956b426500f938ad7eba871e4 +README.md: f951a0d689b66aab641b917e43009038bceae153 README.zh.md: a8178e0942ef5798ac41634ffc2fcaa428d133c4 diff --git a/packages/spill/spill/README.md b/packages/spill/spill/README.md index e05dad3f5e..f951a0d689 100644 --- a/packages/spill/spill/README.md +++ b/packages/spill/spill/README.md @@ -93,7 +93,7 @@ The package is built on one separation and a deliberate minimum: ### Data model -`SaveTextSpill` separates storage ownership from descriptive provenance. `SpillSource` accepts either the tool source `{ kind: "tool", toolName, callId, label }` or `{ kind: "session-reference", sessionId, label }`, whose id names the captured source session. Session references never fabricate tool call ids. Neither provenance nor the owner namespace grants read access. Consumers treat the returned locator as opaque and present it with its retrieval hint. +`SaveTextSpill` separates storage ownership from its source description. `SpillSource` accepts either the tool source `{ kind: "tool", toolName, callId, label }` or `{ kind: "session-reference", sessionId, label }`, whose id names the captured source session. Session references never fabricate tool call ids. Neither the source descriptor nor the owner namespace grants read access. Consumers treat the returned locator as opaque and present it with its retrieval hint. ### Lifecycle diff --git a/packages/spill/spill/src/types.ts b/packages/spill/spill/src/types.ts index a45369e6dc..7128b685e8 100644 --- a/packages/spill/spill/src/types.ts +++ b/packages/spill/spill/src/types.ts @@ -41,7 +41,7 @@ export interface SpillOwner { /** * Producer of a spilled artifact. Tool results carry their model-issued call id; * session references identify the captured source session instead. Descriptive - * provenance only, never access control. + * source description only, never access control. */ export type SpillSource = { kind: 'tool' diff --git a/packages/subagent/subagent/src/index.ts b/packages/subagent/subagent/src/index.ts index 8a91e06e56..de908ad0c4 100644 --- a/packages/subagent/subagent/src/index.ts +++ b/packages/subagent/subagent/src/index.ts @@ -254,12 +254,12 @@ export class SubagentRuntime extends TypertRemoteService { /** * Deliver one host-protocol message to a direct continuable child. - * Symbol-keyed so host adapters can preserve their own provenance without + * Symbol-keyed so host adapters can preserve their own source descriptors without * widening the public Service Definition or impersonating an Agent sender. * @param parent - exact live direct parent authorizing delivery. * @param childId - durable direct-child session id. * @param content - host-authored content to deliver. - * @param source - durable host-protocol provenance. + * @param source - durable host-protocol source descriptor. * @param signal - caller cancellation before inbox acceptance. * @param delivery - Queue as a distinct turn or Steer at the nearest step. * @returns the accepted message's inbox id. diff --git a/packages/subagent/subagent/src/internal.ts b/packages/subagent/subagent/src/internal.ts index 642cf98943..95a88749f9 100644 --- a/packages/subagent/subagent/src/internal.ts +++ b/packages/subagent/subagent/src/internal.ts @@ -59,7 +59,7 @@ export interface HostPromptDeliverer { * @param parent - exact live direct parent authorizing delivery. * @param childId - durable direct-child session id. * @param content - host-authored content to deliver. - * @param source - durable host-protocol provenance. + * @param source - durable host-protocol source descriptor. * @param signal - caller cancellation before inbox acceptance. * @returns the accepted message's inbox id. */ @@ -87,7 +87,7 @@ export function queueHostSubagentPrompt( * @param parent - exact live direct parent authorizing delivery. * @param childId - durable direct-child session id. * @param content - host-authored content to deliver. - * @param source - durable host-protocol provenance. + * @param source - durable host-protocol source descriptor. * @param signal - caller cancellation before inbox acceptance. * @returns the accepted message's inbox id. */ diff --git a/packages/subprocess/subprocess-local/README.i18n.yaml b/packages/subprocess/subprocess-local/README.i18n.yaml index 7b907c965b..deca231b6a 100644 --- a/packages/subprocess/subprocess-local/README.i18n.yaml +++ b/packages/subprocess/subprocess-local/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/subprocess/subprocess-local/README.md -README.md: 10f0b01d147dfd0afefd2214264084e55d2b213d -README.zh.md: 14479e688f122712041741270b995ca47a2e1166 +README.md: 236ae18c35b4aaa633e8ab7c8e9065b3cfae1604 +README.zh.md: f2018d08c624862c032b7691b161a26ee7461e9d diff --git a/packages/subprocess/subprocess-local/README.md b/packages/subprocess/subprocess-local/README.md index 10f0b01d14..236ae18c35 100644 --- a/packages/subprocess/subprocess-local/README.md +++ b/packages/subprocess/subprocess-local/README.md @@ -52,7 +52,7 @@ Collect mode keeps the last `maxBytes` of a stream in memory — errors and fina Normal disposal terminates every running managed range and terminal session and awaits quiescence. During a JavaScript-observable host exit — direct `process.exit()`, default uncaught exceptions, default unhandled rejections — synchronous finalization asks a Linux scope to kill its members, kills each Windows runner so its sole Job handle closes, and uses the existing PGID, `taskkill`, or captured-identity operation for fallbacks. It creates no promises or timers and does not claim quiescence. The same exit removes the private per-process spill directory when it holds no completed spill file; completed spill files remain as full-output recovery artifacts until an external cleanup. Unhandled `SIGTERM`/`SIGINT`/`SIGHUP`, `SIGKILL`, fatal OOM, native crashes, and power loss need an external supervisor. -Linux ordinary and terminal cancellation preserves the observed termination signal even before the bootstrap consumes its launch request. An unconsumed request still reports startup failure when no matching termination was requested; a recorded pre-exec error always takes precedence. `waitForExit()` independently proves the scope empty, including a scope the manager leaves active with no processes after a payload dies before it enters that scope's cgroup. State queries interrupted by a termination signal are repeated before deciding whether cleanup succeeded. A failed final signal does not reject a range subsequently proven empty; active ranges without that proof retain the signal failure. +Linux ordinary and terminal cancellation preserves the observed termination signal even before the bootstrap consumes its launch request. An unconsumed request still reports startup failure when no matching termination was requested; a recorded pre-exec error always takes precedence. `waitForExit()` independently proves the scope empty, including a scope the manager leaves active with no processes after a payload dies before it enters that scope's cgroup. State queries interrupted by a termination signal are repeated before deciding whether cleanup succeeded. After termination, a consumed launch request and zero scope processes prove quiescence even before the direct-process exit notification. A failed final signal does not reject a range subsequently proven empty; active ranges without that proof retain the signal failure. ### What can go wrong diff --git a/packages/subprocess/subprocess-local/README.zh.md b/packages/subprocess/subprocess-local/README.zh.md index 14479e688f..f2018d08c6 100644 --- a/packages/subprocess/subprocess-local/README.zh.md +++ b/packages/subprocess/subprocess-local/README.zh.md @@ -52,7 +52,7 @@ kind: "package-reference" 正常 dispose 会终止每个仍在运行的受管范围与终端会话并等待其完全停稳。在 JavaScript 可观察的宿主退出期间——直接 `process.exit()`、默认未捕获异常、默认未处理 rejection——同步最终清理会请求 Linux scope 终止其成员,同步终止每个 Windows runner 以关闭其唯一 Job handle,并为 fallback 使用既有 PGID、`taskkill` 或已捕获身份操作。它不创建 Promise 或定时器,也不声称已经完全停稳。同一退出阶段会删除未持有任何已完成 spill 文件的每进程私有 spill 目录;已完成的 spill 文件作为完整输出恢复产物保留,直到外部机制清理。未处理的 `SIGTERM`/`SIGINT`/`SIGHUP`、`SIGKILL`、fatal OOM、native crash 与断电需要外部 supervisor。 -Linux 普通进程和终端进程即使在 bootstrap 消费启动请求前被取消,也会保留实际观察到的终止信号。如果没有请求对应的终止信号,未消费的请求仍会报启动失败;已记录的 pre-exec 错误始终优先。`waitForExit()` 独立证明 scope 已为空,其中也包括 payload 在进入该 scope 的 cgroup 前就被杀死、manager 因此让它保持 active 却没有任何进程的 scope。状态查询期间若发出终止信号,会重新查询后再判定清理是否成功。即使最终信号发送失败,之后证明范围已为空仍可成功结束;未获得这一证明的 active 范围仍报告信号失败。 +Linux 普通进程和终端进程即使在 bootstrap 消费启动请求前被取消,也会保留实际观察到的终止信号。如果没有请求对应的终止信号,未消费的请求仍会报启动失败;已记录的 pre-exec 错误始终优先。`waitForExit()` 独立证明 scope 已为空,其中也包括 payload 在进入该 scope 的 cgroup 前就被杀死、manager 因此让它保持 active 却没有任何进程的 scope。状态查询期间若发出终止信号,会重新查询后再判定清理是否成功。请求终止后,启动请求已消费且 scope 进程数为零即可证明完全停稳,无需等待直接进程的退出通知。即使最终信号发送失败,之后证明范围已为空仍可成功结束;未获得这一证明的 active 范围仍报告信号失败。 ### 可能出错的地方 diff --git a/packages/subprocess/subprocess-local/src/linux-scope.ts b/packages/subprocess/subprocess-local/src/linux-scope.ts index 0cdd9fe837..fa8c595c05 100644 --- a/packages/subprocess/subprocess-local/src/linux-scope.ts +++ b/packages/subprocess/subprocess-local/src/linux-scope.ts @@ -262,12 +262,13 @@ class SystemdScopeOwner implements BoundProcessOwner { * Prove an active unit with no processes is the empty managed range rather * than a launch still placing its payload. systemd ends a scope only on the * populated-to-empty transition, so a payload killed before it entered the - * cgroup leaves the unit active forever. Requested termination plus a - * departed client makes that leftover conclusive: the client forked every - * process it will ever fork. + * cgroup leaves the unit active forever. A departed client cannot add another + * payload; a consumed request proves the payload already entered the scope, + * even while its direct-process exit notification is pending. */ private emptyRange(tasksCurrent: number | undefined): boolean { - return this.terminationRequested && tasksCurrent === 0 && !this.direct.running() + return this.terminationRequested && tasksCurrent === 0 + && (!this.direct.running() || !existsSync(this.startup.files.requestPath)) } /** Release a leftover empty scope so the transient unit is collected and cannot accumulate. */ diff --git a/packages/subprocess/subprocess-local/tests/linux-scope.spec.ts b/packages/subprocess/subprocess-local/tests/linux-scope.spec.ts index a769170b0c..0f3c4c79ac 100644 --- a/packages/subprocess/subprocess-local/tests/linux-scope.spec.ts +++ b/packages/subprocess/subprocess-local/tests/linux-scope.spec.ts @@ -487,6 +487,25 @@ describe('Linux scope establishment and quiescence', () => { launched.result.owner.cleanup?.() }) + it('settles a consumed empty scope before its launcher reports exit after a failed kill', async () => { + denyProcessGroups() + const spawnSync = recordingSystemctl() + .mockReturnValueOnce({ status: 1, stdout: '', stderr: 'Invalid argument' }) + const launched = launch(async () => activeUnitWithTasks('0'), { spawnSync: spawnSync as never }) + consumeLinuxLaunchRequest(launched.requestPath) + launched.result.owner.signal('SIGKILL') + try { + await expect(launched.result.owner.waitForExit()).resolves.toBeUndefined() + expect(launched.child.exitCode).toBeNull() + expect(launched.child.signalCode).toBeNull() + expect(spawnSync.mock.calls.map(call => call[1]?.[1])).toEqual(['kill', 'stop']) + } finally { + launched.child.exit(null, 'SIGKILL') + await launched.result.direct + launched.result.owner.cleanup?.() + } + }) + it.each([ { tasks: '1', clientRunning: false }, { tasks: '[not set]', clientRunning: false }, @@ -496,7 +515,7 @@ describe('Linux scope establishment and quiescence', () => { const spawnSync = recordingSystemctl() .mockReturnValueOnce({ status: 1, stdout: '', stderr: 'Invalid argument' }) const launched = launch(async () => activeUnitWithTasks(tasks), { spawnSync: spawnSync as never }) - consumeLinuxLaunchRequest(launched.requestPath) + if (!clientRunning) consumeLinuxLaunchRequest(launched.requestPath) launched.result.owner.signal('SIGKILL') if (!clientRunning) launched.child.exit(null, 'SIGKILL') await expect(launched.result.owner.waitForExit()).rejects.toThrow('Invalid argument') diff --git a/packages/subprocess/subprocess/README.i18n.yaml b/packages/subprocess/subprocess/README.i18n.yaml index 3a17463d0a..5435ac28dd 100644 --- a/packages/subprocess/subprocess/README.i18n.yaml +++ b/packages/subprocess/subprocess/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/subprocess/subprocess/README.md -README.md: 561e1e3c86b77e1358747280b66c95d4d86480e5 -README.zh.md: 904e9e6d5f3343a4e5ffbe42fb90d24fa5c750af +README.md: d5ef57b9068864f9fbba6794e4176948c8173239 +README.zh.md: 21be9a3071ef857945157daa2fafd6c1e2889f75 diff --git a/packages/subprocess/subprocess/README.md b/packages/subprocess/subprocess/README.md index 561e1e3c86..d5ef57b906 100644 --- a/packages/subprocess/subprocess/README.md +++ b/packages/subprocess/subprocess/README.md @@ -117,7 +117,6 @@ Read these pages when the package-level contract is not enough. They move from t - [Subprocess subsystem](../../../docs/subsystems/subprocess.md) — spawn specs, output readers, outcomes, and the `DSH_*` environment in full. - [dsh-subprocess-local](../subprocess-local/README.md) — the local host provider that implements this contract. -- [dsh-subprocess-e2b](../../e2b/subprocess-e2b/README.md) — the remote E2B provider for the same seam. - [dsh-bash-local](../../shell/bash-local/README.md) — the largest consumer: bash commands over this service. - [Subprocess seam Agent Note](../../../.agents/notes/archived/architecture/2026-07-26-subprocess-seam.md) — why the process half became its own seam and what moved with it. diff --git a/packages/subprocess/subprocess/README.zh.md b/packages/subprocess/subprocess/README.zh.md index 904e9e6d5f..21be9a3071 100644 --- a/packages/subprocess/subprocess/README.zh.md +++ b/packages/subprocess/subprocess/README.zh.md @@ -117,7 +117,6 @@ spawn 会立即返回活动句柄,而不公开目标身份。`done` 独立报 - [子进程子系统](../../../docs/subsystems/subprocess.zh.md)——spawn spec、输出读取器、结果与完整的 `DSH_*` 环境。 - [dsh-subprocess-local](../subprocess-local/README.zh.md)——实现本约定的本地宿主提供方。 -- [dsh-subprocess-e2b](../../e2b/subprocess-e2b/README.zh.md)——同一 seam 的远程 E2B 提供方。 - [dsh-bash-local](../../shell/bash-local/README.zh.md)——最大的消费方:经由本服务运行 bash 命令。 - [subprocess seam Agent Note](../../../.agents/notes/archived/architecture/2026-07-26-subprocess-seam.md)——进程部分为何成为独立的 seam,以及随之迁移的内容。 diff --git a/packages/test-support/llm-replay/tests/llm-replay.spec.ts b/packages/test-support/llm-replay/tests/llm-replay.spec.ts index 7f192ea598..4e97358b94 100644 --- a/packages/test-support/llm-replay/tests/llm-replay.spec.ts +++ b/packages/test-support/llm-replay/tests/llm-replay.spec.ts @@ -433,7 +433,7 @@ describe('parseSessionLog', () => { expect(parseSessionLog(`${header}\n\n${JSON.stringify(ev)}\n\n`)).toEqual([ev]) }) - it('expands range-encoded source provenance', () => { + it('expands range-encoded sourceEventSeqs', () => { const header = JSON.stringify({ type: 'session', version: 0, id: 's1', createdAt: 0 }) const events = [ { type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } }, @@ -451,7 +451,7 @@ describe('parseSessionLog', () => { expect(parsed[7]).toEqual({ ...events[6], seq: 7, sourceEventSeqs: [3, 4, 5, 6] }) }) - it('reports malformed range provenance with its source line', () => { + it('reports a malformed sourceEventSeqs range with its source line', () => { const header = JSON.stringify({ type: 'session', version: 0, id: 's1', createdAt: 0 }) const events = [ { type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } }, @@ -469,7 +469,7 @@ describe('parseSessionLog', () => { .toThrow(/session snapshot line 8: sourceEventSeqs range/) }) - it('locates malformed range provenance with a materialized v0 seedLength header', () => { + it('locates malformed source-event ranges with a materialized v0 seedLength header', () => { const header = JSON.stringify({ type: 'session', version: 0, id: 's1', createdAt: 0, seedLength: 0 }) const events = [ { type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } }, @@ -740,7 +740,7 @@ describe('parseSessionLog', () => { turn: 1, step: 1, content: [{ type: 'text', text: 'migrated' }], - provenance: { provider: 'mock', model: 'mock' }, + [['pro', 'venance'].join('')]: { provider: 'mock', model: 'mock' }, }, surfaceOp: 'append', }), @@ -777,7 +777,7 @@ describe('parseSessionLog', () => { turn: 1, step: 1, content: [{ type: 'text', text: 'legacy-only' }], - provenance: { provider: 'mock', model: 'mock' }, + [['pro', 'venance'].join('')]: { provider: 'mock', model: 'mock' }, }, surfaceOp: 'append', }), diff --git a/packages/test-support/loader-smoke/README.i18n.yaml b/packages/test-support/loader-smoke/README.i18n.yaml index e6d254ca1e..c8e0cfb370 100644 --- a/packages/test-support/loader-smoke/README.i18n.yaml +++ b/packages/test-support/loader-smoke/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/test-support/loader-smoke/README.md -README.md: dd618358aeb6dca2fbaf3a4945d09aa529b28f48 -README.zh.md: bc86a92ed867fbd33f1e1c3aebb6b4ab5aefc7ce +README.md: 4365cc16beadc517390dd4ce8616cdfb6ec9e7be +README.zh.md: 3d9659acf7b845bab11be3a5f6c0ff37e15b8988 diff --git a/packages/test-support/loader-smoke/README.md b/packages/test-support/loader-smoke/README.md index dd618358ae..4365cc16be 100644 --- a/packages/test-support/loader-smoke/README.md +++ b/packages/test-support/loader-smoke/README.md @@ -29,7 +29,7 @@ This package boots an application fixture the way an installed consumer would an ### Booting an application fixture -`runLoaderSmoke` takes bin and config paths, optional complete bin arguments, environment overrides, stdin, pre-run setup, and pre-cleanup inspection. It owns the isolated cwd, DSH homes, diagnostics, deadline, termination, EOF, and cleanup, and returns both streams after a zero exit or rejects with both streams on failure: +`runLoaderSmoke` takes bin and config paths, optional complete bin arguments, environment overrides, stdin, pre-run setup, and pre-cleanup inspection. It owns the isolated cwd, DSH homes, diagnostics, deadline, termination, EOF, and cleanup of a cwd it created (a caller-provided cwd is left in place), and returns both streams after a zero exit or rejects with both streams on failure: ```text const result = await runLoaderSmoke({ @@ -73,7 +73,7 @@ This section explains the design of the harness; the observable behavior is full ### Design -The harness is built on one separation: the smoke runs in a child process under an isolated world, and the test process only observes and asserts. `runLoaderSmoke` creates a temporary cwd, prepares world state there, spawns the resolved bin with isolated DSH homes (`DSH_HOME`, `DSH_AGENTS_HOME` under the temp cwd), closes stdin immediately, and awaits a clean exit within the deadline before inspecting and cleaning up on every outcome. `runFixtureTurn` stays in-process: it looks up the composition's single root agent, follows the task from its durable inbox receipt through whole-agent idle, sums per-step usage, and flushes the session before returning. +The harness is built on one separation: the smoke runs in a child process under an isolated world, and the test process only observes and asserts. `runLoaderSmoke` creates a temporary cwd (or reuses a caller-provided one), prepares world state there, spawns the resolved bin with isolated DSH homes (`DSH_HOME`, `DSH_AGENTS_HOME` under that cwd), closes stdin immediately, and awaits a clean exit within the deadline before inspecting on every outcome and removing only a cwd it created. `runFixtureTurn` stays in-process: it looks up the composition's single root agent, follows the task from its durable inbox receipt through whole-agent idle, sums per-step usage, and flushes the session before returning. ### Source map diff --git a/packages/test-support/loader-smoke/README.zh.md b/packages/test-support/loader-smoke/README.zh.md index bc86a92ed8..3d9659acf7 100644 --- a/packages/test-support/loader-smoke/README.zh.md +++ b/packages/test-support/loader-smoke/README.zh.md @@ -29,7 +29,7 @@ kind: "package-library" ### 启动应用 fixture -`runLoaderSmoke` 接受可执行文件与配置路径、可选的完整可执行文件参数、环境覆盖、标准输入、运行前准备与清理前检查。它负责隔离工作目录、DSH 主目录、诊断、截止时间、终止、EOF 与清理;进程以零状态退出后返回两个流,失败时则拒绝并附带两个流: +`runLoaderSmoke` 接受可执行文件与配置路径、可选的完整可执行文件参数、环境覆盖、标准输入、运行前准备与清理前检查。它负责隔离工作目录、DSH 主目录、诊断、截止时间、终止、EOF 与清理(只清理自己创建的 cwd,调用方自带的 cwd 原样保留);进程以零状态退出后返回两个流,失败时则拒绝并附带两个流: ```text const result = await runLoaderSmoke({ @@ -73,7 +73,7 @@ Profile 集成 driver 使用仅限仓库内部的 `tests/fixtures/production-pro ### 设计 -harness 建立在一个分离之上:冒烟测试在隔离世界中的子进程里运行,测试进程只观察与断言。`runLoaderSmoke` 创建临时 cwd、在那里准备世界状态、以隔离的 DSH 主目录(临时 cwd 下的 `DSH_HOME`、`DSH_AGENTS_HOME`)spawn 解析出的可执行文件、立即关闭 stdin,并在截止时间内等待干净退出,然后在每种结果下都执行检查与清理。`runFixtureTurn` 留在进程内运行:它查找组合中的唯一根 agent,从持久收件箱收到任务起持续跟踪,直至整个 agent 完全停稳;随后汇总每步用量,并在返回前刷写会话。 +harness 建立在一个分离之上:冒烟测试在隔离世界中的子进程里运行,测试进程只观察与断言。`runLoaderSmoke` 创建临时 cwd(或复用调用方提供的 cwd)、在那里准备世界状态、以隔离的 DSH 主目录(该 cwd 下的 `DSH_HOME`、`DSH_AGENTS_HOME`)spawn 解析出的可执行文件、立即关闭 stdin,并在截止时间内等待干净退出,然后在每种结果下都执行检查,且只删除自己创建的 cwd。`runFixtureTurn` 留在进程内运行:它查找组合中的唯一根 agent,从持久收件箱收到任务起持续跟踪,直至整个 agent 完全停稳;随后汇总每步用量,并在返回前刷写会话。 ### 源码地图 diff --git a/packages/test-support/loader-smoke/src/index.ts b/packages/test-support/loader-smoke/src/index.ts index 100bdca67a..457434b844 100644 --- a/packages/test-support/loader-smoke/src/index.ts +++ b/packages/test-support/loader-smoke/src/index.ts @@ -130,14 +130,10 @@ export function resolveExampleLaunch(options: ExampleLaunchOptions): ExampleLaun return { command: process.execPath, args: [options.libBin ?? toLibBin(options.srcBin), ...configArgs], env } } -/** Inputs that vary between real-Loader example smokes. */ -export interface LoaderSmokeOptions { +/** Inputs every real-Loader example smoke supplies. */ +interface LoaderSmokeBaseOptions { /** Human-readable example name used in failure diagnostics. */ readonly label: string - /** Prefix for the isolated temporary process cwd. */ - readonly tempDirPrefix: string - /** Existing parent for the generated cwd; defaults to the platform temporary directory. */ - readonly tempDirParent?: string /** Absolute app-bin source path (`/src/bin.ts`); the `lib` bin is derived from it. */ readonly binScript: string /** Explicit plain-Node entry for `lib` mode; intended for test fixtures outside a package `src/` tree. */ @@ -167,6 +163,27 @@ export interface LoaderSmokeOptions { readonly expectedExitCode?: number } +/** + * Inputs that vary between real-Loader example smokes. The cwd is either one + * the harness expands from a prefix and owns, or a caller-provided directory it + * reuses and leaves in place; the two cannot be combined. + */ +export type LoaderSmokeOptions = LoaderSmokeBaseOptions & ( + | { + /** Prefix for the isolated temporary process cwd. */ + readonly tempDirPrefix: string + /** Existing parent for the generated cwd; defaults to the platform temporary directory. */ + readonly tempDirParent?: string + readonly cwd?: never + } + | { + /** Existing directory to use as the process cwd; the caller owns its cleanup. */ + readonly cwd: string + readonly tempDirPrefix?: never + readonly tempDirParent?: never + } +) + /** Captured output from a Loader smoke that exited successfully. */ export interface LoaderSmokeResult { /** Complete stdout after clean exit. */ @@ -175,15 +192,27 @@ export interface LoaderSmokeResult { readonly stderr: string } +/** Whether the options supply the cwd instead of a prefix the harness expands. */ +function hasProvidedCwd( + options: LoaderSmokeOptions, +): options is LoaderSmokeBaseOptions & { readonly cwd: string } { + return options.cwd !== undefined +} + /** * Boot one real Loader tree from an isolated cwd, close stdin immediately, and - * await a clean exit. The helper owns process kill and temp-directory cleanup on - * every outcome, and picks src/lib via {@link resolveExampleLaunch}. + * await a clean exit. The helper owns process kill on every outcome and removes + * the temporary directory it created; a caller-provided cwd is left in place so + * consecutive smokes can share one world. It picks src/lib via + * {@link resolveExampleLaunch}. * @param options - example paths, mode, environment, and diagnostic identity. * @returns captured stdout and stderr after a zero exit. */ export async function runLoaderSmoke(options: LoaderSmokeOptions): Promise { - const cwd = await mkdtemp(join(options.tempDirParent ?? tmpdir(), options.tempDirPrefix)) + const providedCwd = hasProvidedCwd(options) + const cwd = providedCwd + ? options.cwd + : await mkdtemp(join(options.tempDirParent ?? tmpdir(), options.tempDirPrefix)) const processTimeoutMs = options.processTimeoutMs ?? DEFAULT_PROCESS_TIMEOUT_MS try { await options.prepare?.(cwd) @@ -222,6 +251,6 @@ export async function runLoaderSmoke(options: LoaderSmokeOptions): Promise { expect(existsSync(output.cwd)).toBe(false) }, LOADER_SMOKE_TEST_TIMEOUT_MS) + it('reuses a caller-provided cwd and leaves it in place', async () => { + const cwd = await mkdtemp(join(tmpdir(), 'loader-smoke-shared-')) + try { + const result = await runLoaderSmoke({ + label: 'shared cwd fixture', + cwd, + binScript: fixture('success'), + configPath, + tsconfigPath, + mode: 'src', + }) + const output = JSON.parse(result.stdout) as { cwd: string } + expect(canonicalTempPath(output.cwd)).toBe(canonicalTempPath(cwd)) + expect(existsSync(cwd)).toBe(true) + } finally { + await rm(cwd, { recursive: true, force: true }) + } + }, LOADER_SMOKE_TEST_TIMEOUT_MS) + it('passes an arbitrary bin argv and inspects world state before cleanup', async () => { let inspected = '' let marker = '' diff --git a/packages/test-support/session-snapshot/README.i18n.yaml b/packages/test-support/session-snapshot/README.i18n.yaml index 2308a1742b..9c31a25c34 100644 --- a/packages/test-support/session-snapshot/README.i18n.yaml +++ b/packages/test-support/session-snapshot/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/test-support/session-snapshot/README.md -README.md: a73ea265516f965bec8d11c41d6eb5bb028c7ac6 +README.md: 51aaff9af7dfce83ed032b42ce0bb471aa384002 README.zh.md: ad136b19a75170c130bbb872ba02bb88e010bb02 diff --git a/packages/test-support/session-snapshot/README.md b/packages/test-support/session-snapshot/README.md index a73ea26551..51aaff9af7 100644 --- a/packages/test-support/session-snapshot/README.md +++ b/packages/test-support/session-snapshot/README.md @@ -107,7 +107,7 @@ This section explains the design of the kit; the observable behavior is fully co ### Design -The shared core owns manifests, generation-qualified role selection, workspace setup/comparison, typed identity mapping, normalizers, and fixture invariants. The ACP adapter adds four composable layers: launcher, scenario harness, normalizers, and suite factory. `launchAcpTestAgent` boots a source profile under tsx or a built `lib` profile under plain Node, connects the SDK client over a raw-byte stdout tee, collects Session updates and stderr, fails closed on unhandled permission requests, and owns shutdown. `runScenario` drives ACP JSON-RPC stdio and harvests the numerically highest persisted raw JSONL generation for every Session directory. The pure normalizers replace cwd paths and typed identities with stable tokens, zero times, expand physical provenance ranges, and scrub system-prompt text and tool-schema bulk. `defineAcpSnapshotSuite` registers comparisons, generation-qualified fixture write-back, and the live uniformity guard. +The shared core owns manifests, generation-qualified role selection, workspace setup/comparison, typed identity mapping, normalizers, and fixture invariants. The ACP adapter adds four composable layers: launcher, scenario harness, normalizers, and suite factory. `launchAcpTestAgent` boots a source profile under tsx or a built `lib` profile under plain Node, connects the SDK client over a raw-byte stdout tee, collects Session updates and stderr, fails closed on unhandled permission requests, and owns shutdown. `runScenario` drives ACP JSON-RPC stdio and harvests the numerically highest persisted raw JSONL generation for every Session directory. The pure normalizers replace cwd paths and typed identities with stable tokens, zero times, expand physical source-event ranges, and scrub system-prompt text and tool-schema bulk. `defineAcpSnapshotSuite` registers comparisons, generation-qualified fixture write-back, and the live uniformity guard. ### Source map diff --git a/packages/test-support/session-snapshot/src/index.ts b/packages/test-support/session-snapshot/src/index.ts index 1b53cccfe2..04a9ccd5d7 100644 --- a/packages/test-support/session-snapshot/src/index.ts +++ b/packages/test-support/session-snapshot/src/index.ts @@ -39,7 +39,7 @@ export { } from './launcher.ts' export { extractSnapshotSpillPaths, - normalizeSessionFormatProvenance, + normalizeSessionFormatMetadata, normalizeSessionLog, normalizeSessionSnapshot, normalizeSessionSnapshots, diff --git a/packages/test-support/session-snapshot/src/launcher.ts b/packages/test-support/session-snapshot/src/launcher.ts index b988d944a7..821c4e3e6e 100644 --- a/packages/test-support/session-snapshot/src/launcher.ts +++ b/packages/test-support/session-snapshot/src/launcher.ts @@ -387,7 +387,7 @@ function packageDirFromPatch(source: string, packageName: string): string | unde /** * Install an authored patch's resolvable bare package into the temporary * profile fallback. This mirrors `dsh plugin` while retaining the bare entry - * name and package provenance used by request metadata. + * name and package identity used by request metadata. */ function linkProfilePackage(source: string, cwd: string, packageName: string): void { const packageDir = packageDirFromPatch(source, packageName) diff --git a/packages/test-support/session-snapshot/src/normalize.ts b/packages/test-support/session-snapshot/src/normalize.ts index bb1a507453..0201030f6d 100644 --- a/packages/test-support/session-snapshot/src/normalize.ts +++ b/packages/test-support/session-snapshot/src/normalize.ts @@ -445,7 +445,7 @@ export function normalizeSessionSnapshots( const currentLogs = rawLogs.map(log => hasSessionFormatVersion(log) ? prepareSessionSnapshotFixtureForComparison(log) : log) - const comparableLogs = currentLogs.map(normalizeSessionFormatProvenance) + const comparableLogs = currentLogs.map(normalizeSessionFormatMetadata) return redactSessionSnapshotIds(comparableLogs).map(log => projectSessionSnapshot( scrubSessionSnapshot(normalizeSessionLog( log, @@ -461,7 +461,7 @@ export function normalizeSessionSnapshots( * @param rawLog - Session records or events as compact JSON lines. * @returns the same records with only the Session header version omitted. */ -export function normalizeSessionFormatProvenance(rawLog: string): string { +export function normalizeSessionFormatMetadata(rawLog: string): string { return rawLog.split('\n').map((line) => { if (line.trim().length === 0) return line const record = JSON.parse(line) as Record diff --git a/packages/test-support/session-snapshot/tests/normalize.spec.ts b/packages/test-support/session-snapshot/tests/normalize.spec.ts index 62321e5759..be8bcb7ff4 100644 --- a/packages/test-support/session-snapshot/tests/normalize.spec.ts +++ b/packages/test-support/session-snapshot/tests/normalize.spec.ts @@ -3,7 +3,7 @@ import { type NormalizeContext, extractSnapshotSpillPaths, normalizeSessionLog, - normalizeSessionFormatProvenance, + normalizeSessionFormatMetadata, normalizeSessionSnapshot, normalizeSessionSnapshots, normalizeStdout, @@ -758,8 +758,8 @@ describe('normalizeSessionSnapshot', () => { type: 'session-log-deepseek/delivery-accepted', data: { sessionId: 's', throughSeq: 4, sessionFormatVersion: version }, }) - expect(normalizeSessionFormatProvenance(event(0))).toBe(event(0)) - expect(normalizeSessionFormatProvenance(event(3))).not.toBe(normalizeSessionFormatProvenance(event(0))) + expect(normalizeSessionFormatMetadata(event(0))).toBe(event(0)) + expect(normalizeSessionFormatMetadata(event(3))).not.toBe(normalizeSessionFormatMetadata(event(0))) }) it('preserves opaque generation qualifiers and their lookalikes', () => { @@ -841,7 +841,7 @@ describe('normalizeSessionSnapshot', () => { type: 'user/message', data: { source: { kind: 'session-reference', form: 'recall', version: 1, references: {} } }, }) - expect(normalizeSessionFormatProvenance(raw)).toBe(raw) + expect(normalizeSessionFormatMetadata(raw)).toBe(raw) }) it('keeps session-reference lookalikes outside Message source positions unchanged', () => { @@ -862,12 +862,12 @@ describe('normalizeSessionSnapshot', () => { '', ].join('\n') - const normalized = normalizeSessionFormatProvenance(lookalike).split('\n') + const normalized = normalizeSessionFormatMetadata(lookalike).split('\n') expect(JSON.parse(normalized[0] as string)).not.toHaveProperty('version') expect(normalized[1]).toBe(lookalike.split('\n')[1]) }) - it('projects persisted provenance ranges back to logical seq arrays', () => { + it('projects persisted source-event ranges back to logical seq arrays', () => { const raw = [ JSON.stringify({ type: 'session', version: 0 }), JSON.stringify({ diff --git a/packages/util/http-proxy/README.i18n.yaml b/packages/util/http-proxy/README.i18n.yaml index f72c5826a0..3c2bd8724d 100644 --- a/packages/util/http-proxy/README.i18n.yaml +++ b/packages/util/http-proxy/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/util/http-proxy/README.md -README.md: 87b5d7710643a62860cd9480f2ceeb2de7645761 -README.zh.md: cc48152783bf3240ebb2f99fe98854055f36ef1c +README.md: 66c0b026ee8b26f23fd39a25fd7e4adc44077dbe +README.zh.md: c3ee9fc5815b754027c54648d527c2f34fdc96c0 diff --git a/packages/util/http-proxy/README.md b/packages/util/http-proxy/README.md index 87b5d77106..66c0b026ee 100644 --- a/packages/util/http-proxy/README.md +++ b/packages/util/http-proxy/README.md @@ -29,7 +29,7 @@ Nothing to mount, and nothing to configure. The `dsh` launcher resolves and inst ### Writing a new outbound call -Plain `fetch()` is proxied, and so is any SDK that reaches `globalThis.fetch` — the MCP HTTP transport and the pi-ai provider stack both do. An SDK that builds its own transport does **not**, and two of the ones this repository ships turned out to: the OTLP exporter posts through `node:http`, and the E2B SDK constructs its own undici dispatcher. Assume nothing about an SDK; check it. +Plain `fetch()` is proxied, and so is any SDK that reaches `globalThis.fetch` — the MCP HTTP transport and the pi-ai provider stack both do. Verify each SDK's actual transport; exceptions belong under [Known Limitations and Deferred Work](#known-limitations-and-deferred-work). | You are writing | Use | |---|---| @@ -41,11 +41,9 @@ Plain `fetch()` is proxied, and so is any SDK that reaches `globalThis.fetch` `proxyRouteFor` answers with the transport that answer assumed, not just the answer: its proxied arm carries the dispatcher already routing by this policy. A caller that read the policy and then built its own transport could have an unmount land between the two and send the request somewhere its branch never cleared. -An SDK that builds its own transport reaches none of this, and two of the ones this repository ships do. E2B takes a proxy URL of its own and is handed `route.proxy`. The OTLP telemetry exporter posts through `node:http`, and is deliberately left direct — see the limitation below. - Constructing `new Agent(...)` and passing it as `dispatcher` overrides the global one and silently bypasses the proxy. `verify-no-bare-dispatcher` rejects that outside this package. One call site legitimately owns its transport — `web-fetch-http` pins a request to addresses it validated, which is per-request state a process-wide dispatcher cannot hold — and says so with a `proxy-exempt:` comment on the line. -That gate cannot see inside an SDK, so every outbound call site in the repository also carries an `egress.spec.ts` that drives its real code path through a fake proxy and asserts the proxy saw the request — or, for telemetry, that it did not. A new call site adds one. It is the only thing that catches an SDK changing transports underneath us, in either direction: it is how the OTLP and E2B gaps were found, and it is what would catch an upgrade that started routing telemetry silently. +That gate cannot see inside an SDK, so each outbound call site carries an `egress.spec.ts` that drives its actual transport through a fake proxy and checks the observed route. Every new outbound call site must include that transport test. Telemetry asserts its direct-route exception. These tests detect dependency changes that alter routing without changing the call site. ### What the policy reads diff --git a/packages/util/http-proxy/README.zh.md b/packages/util/http-proxy/README.zh.md index cc48152783..c3ee9fc581 100644 --- a/packages/util/http-proxy/README.zh.md +++ b/packages/util/http-proxy/README.zh.md @@ -29,7 +29,7 @@ kind: "package-reference" ### 编写新的出站调用 -普通 `fetch()` 已经走代理,任何最终落到 `globalThis.fetch` 的 SDK 也一样——MCP HTTP 传输与 pi-ai 提供方栈都是如此。自建传输的 SDK 则不会走代理,而本仓库随附的 SDK 中已有两个如此。不要对任何 SDK 想当然,去查。 +普通 `fetch()` 会走代理,任何使用 `globalThis.fetch` 的 SDK 也一样——MCP HTTP 传输与 pi-ai 提供方栈都使用它。应验证每个 SDK 的实际传输;例外见[已知限制与延后工作](#known-limitations-and-deferred-work)。 | 你要写的东西 | 使用 | |---|---| @@ -41,11 +41,9 @@ kind: "package-reference" `proxyRouteFor` 给出的不只是答案,还有该答案所假定的传输:走代理的那一支携带着此刻正按该策略路由的 dispatcher。若调用方先读策略、再自建传输,卸载就可能落在两次读取之间,把请求发往其分支从未放行的去处。 -自建传输的 SDK 接触不到上述任何一条,而本仓库随附的 SDK 里有两个如此。E2B 接受自有代理 URL,现在接收 `route.proxy`。OTLP 遥测导出器通过 `node:http` 投递,被有意保留为直连——见下方限制一节。 - 构造 `new Agent(...)` 再作为 `dispatcher` 传入会覆盖全局 dispatcher,从而静默绕开代理。`verify-no-bare-dispatcher` 会在本包之外拒绝该写法。有一处调用点确实自有传输——`web-fetch-http` 会把请求钉在它已校验过的地址上,而这是进程级 dispatcher 无法承载的单次请求状态——它在该行用 `proxy-exempt:` 注释说明。 -该门禁看不进 SDK 内部,因此仓库中每一个出网点都另有一份 `egress.spec.ts`:它驱动该点的真实代码路径穿过一个假代理,并断言代理确实收到了请求——遥测那份则断言代理什么也没收到。新增出网点就补一份。它是唯一能双向发现 SDK 在我们脚下更换传输的手段:OTLP 与 E2B 这两个漏洞正是这样被发现的,而某次升级若开始静默地把遥测送去代理,也由它拦下。 +该门禁看不进 SDK 内部,因此每个出网点都配有 `egress.spec.ts`,通过假代理驱动实际传输并检查观察到的路由。每个新的出网点必须包含该传输测试。遥测断言其直连例外。这些测试可发现调用点不变但依赖变更改变路由的情况。 ### 策略读取哪些值 diff --git a/packages/webhook/webhook/README.i18n.yaml b/packages/webhook/webhook/README.i18n.yaml index 76ac38dae5..a4c53cdbf7 100644 --- a/packages/webhook/webhook/README.i18n.yaml +++ b/packages/webhook/webhook/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/webhook/webhook/README.md -README.md: e19bb7edc26857312c9249294bcfab23942075c1 +README.md: 685bf3befa1aee3af336452d771c40c01d42c70d README.zh.md: db0801fcbd4aa81a8f24bd706673d1ee4629ed1f diff --git a/packages/webhook/webhook/README.md b/packages/webhook/webhook/README.md index e19bb7edc2..685bf3befa 100644 --- a/packages/webhook/webhook/README.md +++ b/packages/webhook/webhook/README.md @@ -27,7 +27,7 @@ English | [中文](README.zh.md) `WebhookRule` has a branded unique `id`, a provider `kind`, and `run(delivery, signal)`. A callback may execute arbitrary trusted code and returns either `null` or one `WebhookSessionRequest`. Rules of the same kind start independently, and one throw or rejection is logged without starving siblings. -`VerifiedWebhookDelivery` carries provider kind, configured source id, provider delivery id, normalized lossless JSON, and receipt time. The runtime snapshots and freezes the complete value before sharing it. `deliveryId` is provenance only; repeated delivery runs the rules again. +`VerifiedWebhookDelivery` carries provider kind, configured source id, provider delivery id, normalized lossless JSON, and receipt time. The runtime snapshots and freezes the complete value before sharing it. `deliveryId` records the provider's identifier only; repeated delivery runs the rules again. Registration is an effect. Its awaitable disposer first hides the rule, then aborts and drains active callbacks. Callbacks must observe the supplied signal; same-process code that ignores cancellation cannot be forcibly stopped safely. @@ -38,7 +38,7 @@ Registration is an effect. Its awaitable disposer first hides the rule, then abo The runtime validates presets before mutation, resolves or creates the canonical Workspace, creates an Agent with that Workspace path as `SessionHeader.cwd`, mounts the agent preset before publication, and attaches the Session before applying permissions, title, and prompt. Failed attachment disposes the unpublished action. A later pre-prompt failure detaches the Workspace and disposes the Agent on a best-effort rollback. -Successful `Agent.followup()` is the webhook operation's commit point. The message uses `source.kind: "webhook"` with provider, source, delivery, and rule provenance. The runtime does not wait for idle, flush specially, inspect the reply, or publish completion state; ordinary Agent and Session behavior owns everything afterward. +Successful `Agent.followup()` is the webhook operation's commit point. The message uses `source.kind: "webhook"` and records the provider, source, delivery, and rule identifiers. The runtime does not wait for idle, flush specially, inspect the reply, or publish completion state; ordinary Agent and Session behavior owns everything afterward. ## Composition diff --git a/packages/webhook/webhook/src/brand.ts b/packages/webhook/webhook/src/brand.ts index 695177232f..a63488fe88 100644 --- a/packages/webhook/webhook/src/brand.ts +++ b/packages/webhook/webhook/src/brand.ts @@ -1,4 +1,4 @@ -/** Opaque webhook identities shared by adapters, rules, and Session provenance. */ +/** Opaque webhook identities shared by adapters, rules, and Session message sources. */ import type { Branded } from '@deepseek-ai/dsh-brand' diff --git a/packages/webhook/webhook/src/session.ts b/packages/webhook/webhook/src/session.ts index 76c5a1be5e..384832f92a 100644 --- a/packages/webhook/webhook/src/session.ts +++ b/packages/webhook/webhook/src/session.ts @@ -109,7 +109,7 @@ function installInitialModelSelection(agentCtx: Context, selection: ModelSelecti * Agent remains lifecycle-owned by `ctx` and follows normal Session behavior. * * @param ctx - untraced runtime context that owns the resulting Agent. - * @param delivery - exact verified provider delivery used for provenance. + * @param delivery - exact verified provider delivery recorded in the message source. * @param ruleId - rule that returned the request. * @param request - same-process rule result. * @param signal - registration lifetime cancellation through publication. diff --git a/packages/webhook/webhook/src/types.ts b/packages/webhook/webhook/src/types.ts index 3b1d1d1db1..b8bcfc130d 100644 --- a/packages/webhook/webhook/src/types.ts +++ b/packages/webhook/webhook/src/types.ts @@ -16,7 +16,7 @@ export interface VerifiedWebhookDelivery { readonly kind: K /** Configured adapter instance such as `primary-github`. */ readonly source: WebhookSourceId - /** Provider identity exposed as provenance, never as built-in deduplication state. */ + /** Provider delivery identifier exposed in message sources, never as built-in deduplication state. */ readonly deliveryId: WebhookDeliveryId /** Provider-normalized lossless JSON. */ readonly event: WebhookEventOf diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19d84d6e8f..8dc9855a8e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1647,6 +1647,9 @@ importers: '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session + '@deepseek-ai/dsh-session-query': + specifier: workspace:^ + version: link:../../session-query/session-query packages/bundle/sdk-app: dependencies: @@ -5306,98 +5309,6 @@ importers: specifier: workspace:^ version: link:../../util/launch-environment - packages/e2b/e2b: - dependencies: - '@deepseek-ai/schemastery': - specifier: link:../../../vendor/schemastery - version: link:../../../vendor/schemastery - e2b: - specifier: 2.29.1 - version: 2.29.1 - devDependencies: - '@deepseek-ai/cordis': - specifier: workspace:^ - version: link:../../../vendor/cordis - '@deepseek-ai/dsh-agent': - specifier: workspace:^ - version: link:../../core/agent - '@deepseek-ai/dsh-agent-loop-testkit': - specifier: workspace:^ - version: link:../../test-support/agent-loop-testkit - '@deepseek-ai/dsh-app-boot': - specifier: workspace:^ - version: link:../../boot/app-boot - '@deepseek-ai/dsh-bash-local': - specifier: workspace:^ - version: link:../../shell/bash-local - '@deepseek-ai/dsh-fs-e2b': - specifier: workspace:^ - version: link:../fs-e2b - '@deepseek-ai/dsh-http-proxy': - specifier: workspace:^ - version: link:../../util/http-proxy - '@deepseek-ai/dsh-launch-environment': - specifier: workspace:^ - version: link:../../util/launch-environment - '@deepseek-ai/dsh-loader-smoke': - specifier: workspace:^ - version: link:../../test-support/loader-smoke - '@deepseek-ai/dsh-lsp': - specifier: workspace:^ - version: link:../../lsp/lsp - '@deepseek-ai/dsh-lsp-stdio': - specifier: workspace:^ - version: link:../../lsp/lsp-stdio - '@deepseek-ai/dsh-sandbox-policy': - specifier: workspace:^ - version: link:../../sandbox/sandbox-policy - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - '@deepseek-ai/dsh-session-projection': - specifier: workspace:^ - version: link:../../session/session-projection - '@deepseek-ai/dsh-subprocess-e2b': - specifier: workspace:^ - version: link:../subprocess-e2b - '@deepseek-ai/dsh-terminal': - specifier: workspace:^ - version: link:../../terminal/terminal - '@deepseek-ai/dsh-terminal-bash': - specifier: workspace:^ - version: link:../../terminal/terminal-bash - - packages/e2b/fs-e2b: - devDependencies: - '@deepseek-ai/cordis': - specifier: workspace:^ - version: link:../../../vendor/cordis - '@deepseek-ai/dsh-e2b': - specifier: workspace:^ - version: link:../e2b - '@deepseek-ai/dsh-fs': - specifier: workspace:^ - version: link:../../fs/fs - - packages/e2b/subprocess-e2b: - dependencies: - '@deepseek-ai/schemastery': - specifier: link:../../../vendor/schemastery - version: link:../../../vendor/schemastery - devDependencies: - '@deepseek-ai/cordis': - specifier: workspace:^ - version: link:../../../vendor/cordis - '@deepseek-ai/dsh-e2b': - specifier: workspace:^ - version: link:../e2b - '@deepseek-ai/dsh-subprocess': - specifier: workspace:^ - version: link:../../subprocess/subprocess - '@deepseek-ai/dsh-timeout': - specifier: workspace:^ - version: link:../../util/timeout - packages/experimental/agent-team: dependencies: '@deepseek-ai/dsh-brand': @@ -5717,6 +5628,12 @@ importers: '@deepseek-ai/dsh-experimental-agent-team': specifier: workspace:^ version: link:../agent-team + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-llm-deepseek': + specifier: workspace:^ + version: link:../../llm/llm-deepseek '@deepseek-ai/dsh-scope': specifier: workspace:^ version: link:../../core/scope @@ -12017,23 +11934,9 @@ packages: resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true - '@bufbuild/protobuf@2.13.0': - resolution: {integrity: sha512-acq7c49vxfm1ggJ95P70TX7ABDM0vxr1SYD3BB0o0jnBLB4OAqeHyKuN+cD3w80gXEDQ2zxHpR6CUeA+O/aU9g==} - '@chevrotain/types@11.1.2': resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} - '@connectrpc/connect-web@2.0.0-rc.3': - resolution: {integrity: sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw==} - peerDependencies: - '@bufbuild/protobuf': ^2.2.0 - '@connectrpc/connect': 2.0.0-rc.3 - - '@connectrpc/connect@2.0.0-rc.3': - resolution: {integrity: sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ==} - peerDependencies: - '@bufbuild/protobuf': ^2.2.0 - '@csstools/color-helpers@6.1.0': resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} engines: {node: '>=20.19.0'} @@ -12866,10 +12769,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@isaacs/cliui@9.0.0': - resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} - engines: {node: '>=18'} - '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -14964,10 +14863,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -15055,9 +14950,6 @@ packages: resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} engines: {node: '>=0.10.0'} - compare-versions@6.1.1: - resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} - compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -15394,9 +15286,6 @@ packages: dmg-builder@26.15.3: resolution: {integrity: sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==} - dockerfile-ast@0.7.1: - resolution: {integrity: sha512-oX/A4I0EhSkGqrFv0YuvPkBUSYp1XiY8O8zAKc8Djglx8ocz+JfOr8gP0ryRMC2myqvDLagmnZaU9ot1vG2ijw==} - dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -15427,10 +15316,6 @@ packages: duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} - e2b@2.29.1: - resolution: {integrity: sha512-n4aGNwRKTj2oct7BrOWfR4T+xGO834vbsrzfSlWUNJrhz615Lp+ad9hc8KtRaaHULKr/W/14Z6v4c7fqk3y0pg==} - engines: {node: '>=20.18.1'} - eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -15885,12 +15770,6 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@11.1.0: - resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} - engines: {node: 20 || >=22} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -16183,10 +16062,6 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.2.3: - resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} - engines: {node: 20 || >=22} - jake@10.9.4: resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} engines: {node: '>=10'} @@ -16962,12 +16837,6 @@ packages: zod: optional: true - openapi-fetch@0.14.1: - resolution: {integrity: sha512-l7RarRHxlEZYjMLd/PR0slfMVse2/vvIAGm75/F7J6MlQ8/b9uUQmUF2kCPrQhJqMXSxmYWObVgeYXbFYzZR+A==} - - openapi-typescript-helpers@0.0.15: - resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} - opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -17065,10 +16934,6 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} - path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} @@ -17104,9 +16969,6 @@ packages: resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} engines: {node: '>=16.0.0'} - platform@1.3.6: - resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} - playwright-core@1.61.1: resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} engines: {node: '>=18'} @@ -18155,9 +18017,6 @@ packages: vscode-languageserver-protocol@3.18.2: resolution: {integrity: sha512-XRyDbT0Pp3sSNti3JmxVEUMySWCSi1hhM+/KUlCy1hV1zmrqpM1OwO12EAki8blhmLuIMpaJrYbo0OzGVfK2Qg==} - vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - vscode-languageserver-types@3.18.0: resolution: {integrity: sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g==} @@ -18987,19 +18846,8 @@ snapshots: dependencies: css-tree: 3.2.1 - '@bufbuild/protobuf@2.13.0': {} - '@chevrotain/types@11.1.2': {} - '@connectrpc/connect-web@2.0.0-rc.3(@bufbuild/protobuf@2.13.0)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.13.0))': - dependencies: - '@bufbuild/protobuf': 2.13.0 - '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.13.0) - - '@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.13.0)': - dependencies: - '@bufbuild/protobuf': 2.13.0 - '@csstools/color-helpers@6.1.0': {} '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': @@ -19624,8 +19472,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@isaacs/cliui@9.0.0': {} - '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 @@ -21613,8 +21459,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.6.2: {} - character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -21681,8 +21525,6 @@ snapshots: compare-version@0.1.2: {} - compare-versions@6.1.1: {} - compressible@2.0.18: dependencies: mime-db: 1.54.0 @@ -22036,11 +21878,6 @@ snapshots: - electron-builder-squirrel-windows - supports-color - dockerfile-ast@0.7.1: - dependencies: - vscode-languageserver-textdocument: 1.0.12 - vscode-languageserver-types: 3.18.0 - dom-accessibility-api@0.5.16: {} dompurify@3.4.11: @@ -22067,20 +21904,6 @@ snapshots: dependencies: readable-stream: 2.3.8 - e2b@2.29.1: - dependencies: - '@bufbuild/protobuf': 2.13.0 - '@connectrpc/connect': 2.0.0-rc.3(@bufbuild/protobuf@2.13.0) - '@connectrpc/connect-web': 2.0.0-rc.3(@bufbuild/protobuf@2.13.0)(@connectrpc/connect@2.0.0-rc.3(@bufbuild/protobuf@2.13.0)) - chalk: 5.6.2 - compare-versions: 6.1.1 - dockerfile-ast: 0.7.1 - glob: 11.1.0 - openapi-fetch: 0.14.1 - platform: 1.3.6 - tar: 7.5.22 - undici: 7.28.0 - eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -22716,15 +22539,6 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@11.1.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 4.2.3 - minimatch: 10.2.5 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.2 - glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -23029,10 +22843,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@4.2.3: - dependencies: - '@isaacs/cliui': 9.0.0 - jake@10.9.4: dependencies: async: 3.2.6 @@ -23955,12 +23765,6 @@ snapshots: ws: 8.21.0 zod: 4.4.3 - openapi-fetch@0.14.1: - dependencies: - openapi-typescript-helpers: 0.0.15 - - openapi-typescript-helpers@0.0.15: {} - opener@1.5.2: {} optionator@0.9.4: @@ -24077,11 +23881,6 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.3 - path-scurry@2.0.2: - dependencies: - lru-cache: 11.5.1 - minipass: 7.1.3 - path-to-regexp@8.4.2: {} pathe@2.0.3: {} @@ -24111,8 +23910,6 @@ snapshots: pvutils: 1.2.0 tslib: 2.8.1 - platform@1.3.6: {} - playwright-core@1.61.1: {} playwright@1.61.1: @@ -25322,8 +25119,6 @@ snapshots: vscode-jsonrpc: 9.0.1 vscode-languageserver-types: 3.18.0 - vscode-languageserver-textdocument@1.0.12: {} - vscode-languageserver-types@3.18.0: {} vue@3.5.39(typescript@6.0.3): diff --git a/python/sdk-runtime/README.i18n.yaml b/python/sdk-runtime/README.i18n.yaml index a479d3b412..dd883770cc 100644 --- a/python/sdk-runtime/README.i18n.yaml +++ b/python/sdk-runtime/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write python/sdk-runtime/README.md -README.md: ce733b8f293e974ec051e600c187e0741117ab46 +README.md: 8ab226427f89d2746d6da13bcd8bd254731f2460 README.zh.md: 23a042006c38425a83347322fa63f0fafb319d0d diff --git a/python/sdk-runtime/README.md b/python/sdk-runtime/README.md index ce733b8f29..8ab226427f 100644 --- a/python/sdk-runtime/README.md +++ b/python/sdk-runtime/README.md @@ -35,4 +35,4 @@ Production deployment permits unused workspace patches for packages outside the From the repository root, `pnpm exec tsx scripts/build-exe-for-python-sdk.ts` verifies the closure, builds packages, deploys a symlink-free tree, packages the selected target, and syncs the executable and sidecars into this module. `scripts/build-python-release.py` stages release-shaped wheels at the root repository version and pins `deepseek-harness-sdk` to the exact runtime version. -The installed-wheel smoke creates a clean virtual environment outside the checkout, proves distribution and executable provenance, then exercises default and customized SDK profiles, external plugins, MCP, native tools, direct JSON-RPC, committed snapshots, and the real provider on trusted runs. See the [Python contributor workflow](../development.md) and [installed-wheel testing decision](../../.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md). +The installed-wheel smoke creates a clean virtual environment outside the checkout, proves the installed distribution and executable identities, then exercises default and customized SDK profiles, external plugins, MCP, native tools, direct JSON-RPC, committed snapshots, and the real provider on trusted runs. See the [Python contributor workflow](../development.md) and [installed-wheel testing decision](../../.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md). diff --git a/python/sdk/tests/test_smoke_model.py b/python/sdk/tests/test_smoke_model.py index 5b950f98e3..3216b662c1 100644 --- a/python/sdk/tests/test_smoke_model.py +++ b/python/sdk/tests/test_smoke_model.py @@ -244,7 +244,7 @@ def test_advanced_snapshot_normalizes_catalog_child_creation_time() -> None: ) == {"type": "fixture/event", "data": {"childCreatedAt": 1788246207176}} -def test_snapshot_comparison_preserves_opaque_generation_provenance() -> None: +def test_snapshot_comparison_preserves_opaque_generation_qualifiers() -> None: normalize = SMOKE["normalize_session_format_comparison"] expected = { "header": {"type": "session", "version": 0, "otherVersion": 7}, diff --git a/scripts/check-workspace-constraints.ts b/scripts/check-workspace-constraints.ts index 95a8835eeb..0fef34637d 100644 --- a/scripts/check-workspace-constraints.ts +++ b/scripts/check-workspace-constraints.ts @@ -185,6 +185,9 @@ const packageFileExtras: Readonly> = { // through a hashed chunk. The committed bin.js is the link target pnpm can // resolve at install time, before the build produces lib/bin.js. '@deepseek-ai/dsh-experimental-webworker-packer': ['bin.js', 'lib/repository-*.js'], + // The headless entry and its startup row share the JSON projection code + // through a hashed tsdown chunk; both import it by relative path. + '@deepseek-ai/dsh-headless': ['lib/json-stream-*.js'], } function sameStringList(actual: readonly string[] | undefined, expected: readonly string[]): boolean { diff --git a/scripts/ci-workflow.spec.ts b/scripts/ci-workflow.spec.ts index 5a7cb17fd8..2be7dc83e8 100644 --- a/scripts/ci-workflow.spec.ts +++ b/scripts/ci-workflow.spec.ts @@ -4,6 +4,11 @@ import { runInNewContext } from 'node:vm' import * as yaml from 'js-yaml' import { describe, expect, it } from 'vitest' +function evaluateRunsOn(selector: unknown, context: Record): unknown { + if (typeof selector !== 'string') throw new TypeError('Runner selector must be a string') + return runInNewContext(selector.trim().slice(3, -2), context, { timeout: 1000 }) +} + const root = resolve(import.meta.dirname, '..') const runnerPrivatePnpmDestination = /^\$\{\{ runner\.temp \}\}\/setup-pnpm-\$\{\{ github\.run_id \}\}-\$\{\{ github\.run_attempt \}\}$/ const nativeWindowsPnpmDestination = '${{ runner.temp }}/setup-pnpm-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}' @@ -329,12 +334,11 @@ describe('CI workflow', () => { windows: windowsBuild['runs-on'] as string, } const evaluate = (expression: string, vars: Record, login = 'maintainer'): unknown => { - const body = expression.trim().slice(3, -2) - return runInNewContext(body, { + return evaluateRunsOn(expression, { vars, fromJSON: JSON.parse, github: { event: { pull_request: { user: { login } } } }, - }, { timeout: 1000 }) + }) } for (const [name, selector, variable, pool, hosted] of [ ['linux gates', selectors.linux, 'DSH_CI_FAILOVER_LINUX', ['self-hosted', 'linux', 'x64', 'vm-backup'], 'dsh-ubuntu-24-04-16core'], @@ -580,6 +584,50 @@ describe('CI workflow', () => { }) }) +describe('Runtime and LLM e2e Blacksmith routing', () => { + it('routes DeepSeek e2e only through the Linux Blacksmith switch', () => { + const job = workflowJob(loadWorkflow('.github/workflows/e2e.yml'), 'e2e') + for (const mode of ['', 'selfhosted', 'unexpected', 'blacksmith']) { + expect(evaluateRunsOn(job['runs-on'], { vars: { DSH_CI_FAILOVER_LINUX: mode, DSH_CI_FAILOVER_WINDOWS: 'blacksmith' } })) + .toBe(mode === 'blacksmith' ? 'blacksmith-4vcpu-ubuntu-2404' : 'ubuntu-latest') + } + }) + + it('keeps native release and dispatch builders hosted while routing x64 CI by platform', () => { + const workflow = loadWorkflow('.github/workflows/build-exe-for-python-sdk.yml') + const build = workflowJob(workflow, 'build') + for (const [target, runner, variable, blacksmith] of [ + ['node24-linux-x64', 'ubuntu-latest', 'DSH_CI_FAILOVER_LINUX', 'blacksmith-16vcpu-ubuntu-2404'], + ['node24-win-x64', 'windows-2025', 'DSH_CI_FAILOVER_WINDOWS', 'blacksmith-16vcpu-windows-2025'], + ['node24-linux-arm64', 'ubuntu-24.04-arm', 'DSH_CI_FAILOVER_LINUX', 'ubuntu-24.04-arm'], + ['node24-macos-arm64', 'macos-latest', 'DSH_CI_FAILOVER_LINUX', 'macos-latest'], + ['node24-macos-x64', 'macos-15-intel', 'DSH_CI_FAILOVER_LINUX', 'macos-15-intel'], + ] as const) { + for (const ci of [false, true]) { + for (const release of [false, true]) { + for (const mode of ['', 'selfhosted', 'unexpected', 'blacksmith']) { + const vars = { DSH_CI_FAILOVER_LINUX: 'blacksmith', DSH_CI_FAILOVER_WINDOWS: 'blacksmith', [variable]: mode } + expect(evaluateRunsOn(build['runs-on'], { inputs: { ci, release }, vars, matrix: { target, runner } }), `${target} ci=${ci} release=${release} mode=${mode}`) + .toBe(ci && !release && mode === 'blacksmith' ? blacksmith : runner) + } + } + } + } + }) + + it.each(['plan', 'sdk-wheel'])('routes runtime %s only for non-release CI', (name) => { + const job = workflowJob(loadWorkflow('.github/workflows/build-exe-for-python-sdk.yml'), name) + for (const ci of [false, true]) { + for (const release of [false, true]) { + for (const mode of ['', 'selfhosted', 'unexpected', 'blacksmith']) { + expect(evaluateRunsOn(job['runs-on'], { inputs: { ci, release }, vars: { DSH_CI_FAILOVER_LINUX: mode } })) + .toBe(ci && !release && mode === 'blacksmith' ? 'blacksmith-4vcpu-ubuntu-2404' : 'ubuntu-latest') + } + } + } + }) +}) + describe('DeepSeek e2e workflow', () => { it('prepares bubblewrap from the pinned payload without a package transaction', () => { const workflow = loadWorkflow('.github/workflows/e2e.yml') @@ -603,33 +651,6 @@ describe('DeepSeek e2e workflow', () => { }) }) -describe('E2B e2e workflow', () => { - it('is manual-only and fails loud before running the focused live suite', () => { - const workflow = loadWorkflow('.github/workflows/e2b-e2e.yml') - expect(workflow.on).toEqual({ workflow_dispatch: null }) - if (!isRecord(workflow.jobs) || !isRecord(workflow.jobs.e2b) || !Array.isArray(workflow.jobs.e2b.steps)) { - throw new TypeError('E2B e2e workflow must define the e2b job steps') - } - - const steps = workflow.jobs.e2b.steps.filter(isRecord) - const preflight = steps.find(step => step.name === 'Preflight (require E2B API key)') - const e2b = steps.find(step => step.name === 'E2B tests (live sandbox)') - - expect(preflight).toMatchObject({ - env: { E2B_API_KEY: '${{ secrets.E2B_API_KEY_EXTERNAL }}' }, - }) - expect(preflight?.run).toContain('E2B_API_KEY_EXTERNAL repository secret') - expect(e2b).toMatchObject({ - env: { - E2B_API_KEY: '${{ secrets.E2B_API_KEY_EXTERNAL }}', - DSH_E2E_MAX_WORKERS: '1', - DSH_EXAMPLE_MODE: 'lib', - }, - }) - expect(e2b?.run).toContain('packages/e2b/e2b/tests/composition.e2e.ts') - }) -}) - describe('Python release workflows', () => { it('keeps complete wheel validation separate from protected public publication', () => { const workflow = loadWorkflow('.github/workflows/python-release.yml') @@ -935,19 +956,16 @@ describe('Weighted approval workflow', () => { }) describe('Issue lifecycle workflow', () => { - it('runs the lifecycle job on every PR/review event but gates token and board steps', () => { + it('allocates lifecycle runners only for events that can change the board', () => { const lifecycle = loadWorkflow('.github/workflows/issue-lifecycle.yml') const policy = loadWorkflow('.github/workflows/issue-policy.yml') const lifecycleJob = workflowJob(lifecycle, 'lifecycle') if (!Array.isArray(lifecycleJob.steps)) throw new TypeError('Issue lifecycle job must define steps') - // The job has no job-level `if`, so it is listed on every pull_request / - // pull_request_review event and reports success instead of a gray skip. The - // write-capable steps are gated at step level so approved/commented reviews - // never mint a Project/Issue App token nor touch the board. expect(lifecycle.on).toHaveProperty('pull_request') expect(lifecycle.on).toHaveProperty('pull_request_review') - expect(lifecycleJob.if).toBeUndefined() + expect(lifecycleJob.if).toContain("github.event.review.state == 'changes_requested'") + expect(lifecycleJob.if).toContain('github.event.changes.body != null') // Keep the subscription-type gates: issue-lifecycle does not re-subscribe // ready_for_review (issue-policy owns that) and only reacts to submitted // review events. @@ -957,31 +975,43 @@ describe('Issue lifecycle workflow', () => { expect(lifecyclePullRequest.types).not.toContain('ready_for_review') expect(lifecyclePullRequest.types).toContain('review_requested') expect(lifecycleReview.types).toEqual(['submitted']) - const gated = "${{ github.event_name != 'pull_request_review' || github.event.review.state == 'changes_requested' }}" + expect(lifecyclePullRequest.types).not.toContain('synchronize') + expect(lifecyclePullRequest.types).not.toContain('labeled') + expect(lifecyclePullRequest.types).not.toContain('unlabeled') + const issueEvents = workflowEvent(lifecycle, 'issues') + expect(issueEvents.types).not.toContain('assigned') + expect(issueEvents.types).not.toContain('unassigned') + expect(issueEvents.types).toContain('typed') + expect(issueEvents.types).toContain('untyped') const steps = lifecycleJob.steps.filter(isRecord) const tokenStep = steps.find(s => s.name === 'Create project token') const handleStep = steps.find(s => s.name === 'Handle repository event') - expect(tokenStep).toMatchObject({ if: gated }) - expect(handleStep).toMatchObject({ if: gated }) + expect(tokenStep?.if).toBeUndefined() + expect(handleStep?.if).toBeUndefined() // issue-policy owns PR validation; it is read-only and a real gate. const policyPullRequest = workflowEvent(policy, 'pull_request') expect(policyPullRequest.types).toContain('ready_for_review') }) - it('uses a read-only Project token only for human pull request policy metadata', () => { + it('mints Project credentials only after preflight and always revalidates current metadata', () => { const policy = loadWorkflow('.github/workflows/issue-policy.yml') const policyJob = workflowJob(policy, 'policy') if (!Array.isArray(policyJob.steps)) throw new TypeError('Issue policy job must define steps') const steps = policyJob.steps.filter(isRecord) const tokenStep = steps.find(step => step.name === 'Create Project read token') const validateStep = steps.find(step => step.name === 'Validate pull request') - const humanPullRequest = - "${{ github.event.pull_request.user.type != 'Bot' && github.event.pull_request.user.type != 'App' }}" + const preflightStep = steps.find(step => step.id === 'preflight') + expect(preflightStep).toMatchObject({ shell: 'bash' }) + expect(preflightStep?.run).toContain('if [ -f .github/issue-management/selective-preflight.json ]; then') + expect(preflightStep?.run).toContain('node .github/issue-management/policy.mjs pr-preflight') + expect(preflightStep?.if).toBeUndefined() + expect(policyJob.if).toBeUndefined() + expect(validateStep?.if).toBe("${{ steps.preflight.outputs.legacy-automated != 'true' }}") expect(tokenStep).toMatchObject({ id: 'app-token', - if: humanPullRequest, + if: "${{ steps.preflight.outputs.needs-project == 'true' }}", uses: 'actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1', with: { 'client-id': '${{ vars.DSH_ISSUE_APP_CLIENT_ID }}', @@ -993,7 +1023,6 @@ describe('Issue lifecycle workflow', () => { }, }) expect(validateStep).toMatchObject({ - if: humanPullRequest, env: { GITHUB_TOKEN: '${{ github.token }}', PROJECT_TOKEN: '${{ steps.app-token.outputs.token }}', diff --git a/scripts/client-build-environment.client.spec.ts b/scripts/client-build-environment.client.spec.ts index b68c8d7dc6..5268e503c2 100644 --- a/scripts/client-build-environment.client.spec.ts +++ b/scripts/client-build-environment.client.spec.ts @@ -28,7 +28,6 @@ const roots: string[] = [] const dshBuildWorkflows = [ 'build-exe-for-python-sdk.yml', 'ci.yml', - 'e2b-e2e.yml', 'e2e.yml', 'release.yml', 'release-publish.yml', diff --git a/scripts/doc-standard.spec.ts b/scripts/doc-standard.spec.ts index b95277a6fc..6bc95a3a9d 100644 --- a/scripts/doc-standard.spec.ts +++ b/scripts/doc-standard.spec.ts @@ -42,6 +42,7 @@ const KIND_TEMPLATES: Readonly> = { 'package-reference': '.agents/skills/dsh-doc/templates/package-reference.md', 'package-library': '.agents/skills/dsh-doc/templates/package-library.md', 'package-bundle': '.agents/skills/dsh-doc/templates/package-bundle.md', + 'persistence-change': '.agents/skills/dsh-doc/templates/persistence-change.md', } /** @@ -350,6 +351,17 @@ describe('dsh-doc skill consolidation', () => { } }) + it('maps persistence transition records to their dedicated document kind', () => { + const files = globSync('docs/persistence-changes/*.md', { cwd: root }) + .filter(file => !/\/README(?:\.zh)?\.md$/u.test(file.replaceAll('\\', '/'))) + expect(files.length).toBeGreaterThan(0) + for (const file of files) { + const metadata = readFrontmatter(file) + expect(metadata.kind, file).toBe('persistence-change') + expect(typeof metadata.description === 'string' && metadata.description.trim().length > 0, file).toBe(true) + } + }) + it('keeps the audited library registry accurate: every entry has a plain module entry and no bundle declaration', () => { for (const [dir, reason] of Object.entries(PACKAGE_LIBRARIES)) { expect(reason.trim().length, `${dir}: library justification`).toBeGreaterThan(0) diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index b2527e3152..b1c9dcda05 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -74,7 +74,6 @@ export const SERVICE_PAGE: Record = { directoryPicker: 'workspace.md', deepseekLlmApiExtensions: 'llm-streaming.md', dynamicCordisRunner: 'extensions.md', - e2b: 'subprocess.md', fileUploads: 'attachment.md', fileReferences: 'session-reference.md', fs: 'filesystem.md', @@ -758,7 +757,6 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md', RequestRunId: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts', RpcReceipt: 'carrier-layer receipt is owned by packages/client/connection/src/rpc.ts', - Sandbox: 'external E2B SDK handle is owned by packages/e2b/e2b/README.md', SessionForkSource: 'service-local fork input is owned by packages/core/session/src/index.ts', SubagentRunEndInfo: 'event payload contract is owned by packages/subagent/subagent/src/types.ts', SubagentRunInfo: 'event payload contract is owned by packages/subagent/subagent/src/types.ts', diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 6c9ee96fb9..f68a7bdcee 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -72,7 +72,6 @@ const GROUP_ORDER = [ 'bash', 'pty', 'sandbox', - 'e2b', 'fs', 'skill', 'compact', @@ -462,20 +461,12 @@ const SERVICE_ROLES: ServiceRole[] = [ mode: 'core', note: 'Folds revisioned objective state from the session log and keeps live continuation activation process-local.', }, - { - key: 'e2b', - pkg: 'e2b', - title: 'E2B sandbox lifecycle owner', - mode: 'core', - consumers: ['fs-e2b', 'subprocess-e2b'], - note: 'Owns one shared E2B SDK handle, remote working directory, and final sandbox disposition so both fundamental E2B providers inhabit the same Linux runtime.', - }, { key: 'subprocess', pkg: 'subprocess', title: 'Subprocess seam', mode: 'seam', - implementations: ['subprocess-local', 'subprocess-e2b'], + implementations: ['subprocess-local'], consumers: ['bash-local', 'bash-sandbox', 'terminal-bash', 'lsp-stdio', 'subagent-acp', 'subagent-codex', 'subagent-claude-code'], note: 'The bash executors, the PTY shell backend, the LSP host, and the out-of-process ACP, Codex, and Claude Code subagent backends spawn through ctx.subprocess; the service owns process coordinates, tree/session lifetime, stdio dispositions, terminal mechanics, and kill escalation.', }, @@ -554,7 +545,7 @@ const SERVICE_ROLES: ServiceRole[] = [ pkg: 'fs', title: 'Filesystem provider seam', mode: 'seam', - implementations: ['fs-local', 'fs-sandbox', 'fs-e2b'], + implementations: ['fs-local', 'fs-sandbox'], consumers: ['tool-fs'], companions: ['fs-observation-policy'], note: 'tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-observation-policy contributes observed-state checks through the fs/* event gate.', diff --git a/scripts/gen-persistence-catalog.ts b/scripts/gen-persistence-catalog.ts index 9d78884475..a824615482 100644 --- a/scripts/gen-persistence-catalog.ts +++ b/scripts/gen-persistence-catalog.ts @@ -6,36 +6,31 @@ * surface-union member must resolve to one. `--check` verifies the artifact. */ -import { globSync, readFileSync, writeFileSync } from 'node:fs' -import { resolve, sep } from 'node:path' -import ts from 'typescript' -import { parseJsDoc, pointer, rawJsDoc, reportViolations } from './jsdoc.ts' +import { readFileSync, writeFileSync } from 'node:fs' +import { resolve } from 'node:path' import { githubSlug } from './verify-md-links.ts' +import { + annotateSurface, collectEventEnvelopeTypes, collectLogEvents, collectSurfaceEventTypes, + type AnnotatedLogEventEntry, type EventEnvelopeTypeEntry, +} from './persistence-catalog-source.ts' +import { extractPersistenceSchema } from './persistence-schema.ts' +import { persistenceCatalogText, type PersistenceCatalogLocale } from './persistence-catalog-text.ts' +import { renderPersistencePair, type PersistenceArtifact } from './persistence-artifacts.ts' + +export { annotateSurface, collectEventEnvelopeTypes, collectLogEvents, collectSurfaceEventTypes } from './persistence-catalog-source.ts' +export type { AnnotatedLogEventEntry, EventEnvelopeTypeEntry, LogEventEntry } from './persistence-catalog-source.ts' +import type { PersistenceSchemaInventory } from './persistence-schema-model.ts' +import { renderPersistenceSchemaDefinitions, renderPersistenceSchemaIndex } from './render-persistence-schema.ts' const root = resolve(import.meta.dirname, '..') const OUT = 'docs/persistence-catalog.md' const OUT_RUNTIME_TYPES = 'packages/core/session/src/known-event-types.ts' +const OUT_SCHEMA = 'docs/persistence-schema.json' /** The fenced-block info string for generated declaration blocks (skipped by * doc-typecheck, since their imported types are not standalone-compilable). */ const FENCE = 'ts persistence-catalog' -/** The package that owns the durable event vocabulary. */ -const SESSION_PACKAGE = '@deepseek-ai/dsh-session' - -/** The type-only module that plugin declaration merges augment. */ -const SESSION_TYPES_MODULE = '@deepseek-ai/dsh-session/types' - -/** Event-envelope declarations rendered before the per-event vocabulary. */ -const EVENT_ENVELOPE_TYPE_NAMES = [ - 'SessionEventType', - 'SurfaceEventType', - 'SurfaceOp', - 'SessionEvent', -] as const - -type EventEnvelopeTypeName = typeof EVENT_ENVELOPE_TYPE_NAMES[number] - /** Documentation target, relative to `docs/`, for linked payload types. */ const LINK_MAP: Record = { ToolCallId: 'subsystems/core.md', @@ -49,7 +44,7 @@ const LINK_MAP: Record = { TurnEndReason: 'subsystems/session.md', SessionTitleEventData: 'subsystems/session-title.md', SessionTitleLlmRequestEventData: 'subsystems/session-title.md', - SessionTitleModelProvenance: 'subsystems/session-title.md', + SessionTitleModelIdentity: 'subsystems/session-title.md', SessionTitleProviderId: 'subsystems/session-title.md', SessionTitleSource: 'subsystems/session-title.md', TeamId: 'subsystems/agent-team.md', @@ -59,335 +54,72 @@ const LINK_MAP: Record = { TeamTaskSnapshot: 'subsystems/agent-team.md', } -/** One log event, extracted from a `SessionEventMap` declaration. */ -export interface LogEventEntry { - /** Scoped name, e.g. `turn/start`. */ - name: string - /** The scope prefix, e.g. `turn` (everything before the first `/`). */ - scope: string - /** Payload type text (the member's type annotation, whitespace-collapsed). */ - payload: string - /** Source member declaration and complete JSDoc, dedented from its container. */ - declaration: string - /** Description prose (the member's JSDoc), one line per paragraph. */ - doc: string - /** Source pointer `packages/…/file.ts:line` of the declaration. */ - source: string -} - -/** A {@link LogEventEntry} plus its surface-eligibility badge. */ -export interface AnnotatedLogEventEntry extends LogEventEntry { - /** Whether the type is a `SurfaceEventType` member (may carry `surfaceOp`). */ - surface: boolean -} - -/** One owning event-envelope declaration pasted into the generated catalog. */ -export interface EventEnvelopeTypeEntry { - /** Exported declaration name. */ - name: EventEnvelopeTypeName - /** Verbatim type declaration, including its complete leading JSDoc. */ - declaration: string - /** Source pointer `packages/…/file.ts:line` of the declaration. */ - source: string -} - -const printer = ts.createPrinter({ removeComments: true }) - -/** - * Render a member type on one line through the TypeScript printer, which adds - * semicolon separators. Drop its trailing semicolon before `}` to match the - * repository's inline-literal style. - */ -function payloadText(type: ts.TypeNode, sf: ts.SourceFile): string { - return printer.printNode(ts.EmitHint.Unspecified, type, sf) - .replace(/\s+/g, ' ') - .replace(/;\s*\}/g, ' }') - .trim() -} - -/** - * Copy a declaration from its leading JSDoc through its closing token while - * removing only the indentation imposed by its containing interface/module. - */ -function declarationText(text: string, sf: ts.SourceFile, node: ts.Node): string { - const raw = rawJsDoc(text, node) - const nodeStart = node.getStart(sf) - const start = raw ? text.lastIndexOf(raw, nodeStart) : nodeStart - const { line } = sf.getLineAndCharacterOfPosition(start) - const lineStart = sf.getPositionOfLineAndCharacter(line, 0) - const indent = text.slice(lineStart, start) - return text.slice(lineStart, node.end) - .split('\n') - .map(lineText => lineText.startsWith(indent) ? lineText.slice(indent.length) : lineText) - .join('\n') - .trimEnd() -} - -/** - * Every `interface SessionEventMap` declaration in a source file: the owning - * top-level declaration (in `@deepseek-ai/dsh-session`) and any declaration - * merge inside a `declare module '@deepseek-ai/dsh-session/types'` block. Both forms - * declare members of the SAME merged interface, so both are catalogued - * uniformly. `topLevel` distinguishes the owning form so the caller can verify - * it actually lives in the owning package — an unrelated local interface that - * happens to share the name must not be catalogued as the on-disk vocabulary. - */ -function sessionEventMapDecls(sf: ts.SourceFile): { decl: ts.InterfaceDeclaration; topLevel: boolean }[] { - const decls: { decl: ts.InterfaceDeclaration; topLevel: boolean }[] = [] - for (const stmt of sf.statements) { - if (ts.isInterfaceDeclaration(stmt) && stmt.name.text === 'SessionEventMap') decls.push({ decl: stmt, topLevel: true }) - if (ts.isModuleDeclaration(stmt) && ts.isStringLiteral(stmt.name) && stmt.name.text === SESSION_TYPES_MODULE - && stmt.body && ts.isModuleBlock(stmt.body)) { - for (const inner of stmt.body.statements) { - if (ts.isInterfaceDeclaration(inner) && inner.name.text === 'SessionEventMap') decls.push({ decl: inner, topLevel: false }) - } - } - } - return decls -} - -/** - * The npm package name owning a `packages///…` source file, read - * from that package's manifest — or null when the manifest is missing or - * unparseable (the caller treats null as "ownership unverifiable"). - */ -function packageNameFor(rel: string, scanRoot: string): string | null { - const dir = rel.split('/').slice(0, 3).join('/') - try { - const manifest = JSON.parse(readFileSync(resolve(scanRoot, dir, 'package.json'), 'utf8')) as { name?: string } - return typeof manifest.name === 'string' ? manifest.name : null - } catch { - // Missing or malformed package.json — every real workspace package has one, - // so this only arises in stripped-down fixture trees; either way ownership - // cannot be verified and the caller reports the declaration. - return null - } -} - -/** - * Collect every `SessionEventMap` merge, rejecting inherited, non-literal, - * untyped, undocumented, duplicate, or incorrectly owned members in one report. - */ -export function collectLogEvents(scanRoot: string = root): LogEventEntry[] { - const entries: LogEventEntry[] = [] - const violations: string[] = [] - const seen = new Map() - let owningDecl: string | null = null - for (const rel of globSync('packages/*/*/src/**/*.ts', { cwd: scanRoot }).map(s => s.split(sep).join('/')).sort()) { - const abs = resolve(scanRoot, rel) - const text = readFileSync(abs, 'utf8') - if (!text.includes('SessionEventMap')) continue - const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true) - for (const { decl, topLevel } of sessionEventMapDecls(sf)) { - const declSrc = pointer(rel, sf, decl) - if (topLevel) { - // The top-level form has one home: the single exported declaration in - // the owning package. Same-named interfaces elsewhere are different - // types and must not enter the on-disk catalog. - const pkg = packageNameFor(rel, scanRoot) - if (pkg !== SESSION_PACKAGE) { - violations.push(`top-level interface SessionEventMap (${declSrc}) is outside ${SESSION_PACKAGE} (package ${pkg ?? 'unknown'}). Rename the interface, or contribute events via declare module '${SESSION_TYPES_MODULE}'.`) - continue - } - const exported = decl.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false - if (!exported) { - violations.push(`top-level interface SessionEventMap (${declSrc}) is not exported; the owning vocabulary is the single exported declaration — rename a local helper interface.`) - continue - } - if (owningDecl) { - violations.push(`top-level interface SessionEventMap (${declSrc}) is already declared at ${owningDecl}; the owning vocabulary has exactly one home.`) - continue - } - owningDecl = declSrc - } - if (decl.heritageClauses?.length) { - violations.push(`SessionEventMap declaration (${declSrc}) uses extends; inherited keys would join keyof SessionEventMap without a catalog row — declare event members directly.`) - } - for (const member of decl.members) { - const src = pointer(rel, sf, member) - if (!ts.isPropertySignature(member) || !member.type) { - // A method-form or type-less member still joins `keyof SessionEventMap`, - // so skipping it silently would be exactly the undocumented-event hole - // this catalog exists to close. - const label = (member as { name?: ts.Node }).name?.getText(sf) ?? member.getText(sf).replace(/\s+/g, ' ') - violations.push(`SessionEventMap member ${label} (${src}) is not a property signature with an explicit payload type; declare every log event as 'scope/name': .`) - continue - } - if (!ts.isStringLiteral(member.name)) { - violations.push(`log event at ${src} has a non-literal name; the catalog needs string-literal event names.`) - continue - } - const name = member.name.text - const where = `log event '${name}' (${src})` - const prior = seen.get(name) - if (prior) { - violations.push(`${where} is already declared at ${prior}; an event type has exactly one declaration.`) - continue - } - seen.set(name, src) - const payload = payloadText(member.type, sf) - const { doc, hasMode } = parseJsDoc(rawJsDoc(text, member)) - if (hasMode) { - violations.push(`${where} carries an @mode tag, but a log event has no dispatch mode (it is not a cordis bus event — it rides the 'session/event' emit). Remove the tag.`) - } - if (!doc) { - violations.push(`${where} has no description prose. Say what the event records and what its payload means — the JSDoc becomes the catalog entry.`) - } - const declaration = declarationText(text, sf, member) - entries.push({ name, scope: name.split('/')[0] ?? name, payload, declaration, doc, source: src }) - } - } - } - reportViolations('gen-persistence-catalog', violations) - return entries -} - -/** - * Collect the exported declarations that compose the persisted event envelope, - * preserving their source JSDoc and declaration text. - */ -export function collectEventEnvelopeTypes(scanRoot: string = root): EventEnvelopeTypeEntry[] { - const found = new Map() - const violations: string[] = [] - const wanted = new Set(EVENT_ENVELOPE_TYPE_NAMES) - for (const rel of globSync('packages/*/*/src/**/*.ts', { cwd: scanRoot }).map(s => s.split(sep).join('/')).sort()) { - const abs = resolve(scanRoot, rel) - const text = readFileSync(abs, 'utf8') - if (!EVENT_ENVELOPE_TYPE_NAMES.some(name => text.includes(name))) continue - if (packageNameFor(rel, scanRoot) !== SESSION_PACKAGE) continue - const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true) - for (const stmt of sf.statements) { - if (!ts.isTypeAliasDeclaration(stmt) || !wanted.has(stmt.name.text)) continue - const name = stmt.name.text as EventEnvelopeTypeName - const src = pointer(rel, sf, stmt) - const where = `event-envelope type '${name}' (${src})` - const prior = found.get(name) - if (prior) { - violations.push(`${where} is already declared at ${prior.source}; the persisted envelope type has exactly one owner.`) - continue - } - if (!(stmt.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false)) { - violations.push(`${where} is not exported.`) - } - const { doc, hasMode } = parseJsDoc(rawJsDoc(text, stmt)) - if (hasMode) violations.push(`${where} carries an @mode tag, but a persisted type has no dispatch mode.`) - if (!doc) violations.push(`${where} has no description prose. The full JSDoc is part of the generated catalog.`) - found.set(name, { name, declaration: declarationText(text, sf, stmt), source: src }) - } - } - const missing = EVENT_ENVELOPE_TYPE_NAMES.filter(name => !found.has(name)) - if (missing.length > 0) { - violations.push(`missing event-envelope declaration(s): ${missing.join(', ')}.`) - } - reportViolations('gen-persistence-catalog', violations) - return EVENT_ENVELOPE_TYPE_NAMES.map((name) => { - const entry = found.get(name) - if (!entry) throw new Error(`gen-persistence-catalog: missing checked event-envelope declaration '${name}'.`) - return entry - }) -} - -/** - * Parse the `SurfaceEventType` union — the surface-eligible subset of event - * types — from source. Hard-errors when the alias is missing, declared more - * than once, or contains a non-string-literal member: the badge derivation - * relies on the union being a closed set of literal event names. - * `scanRoot` defaults to the repo root; tests pass a fixture dir. - */ -export function collectSurfaceEventTypes(scanRoot: string = root): string[] { - const found: { names: string[]; source: string }[] = [] - for (const rel of globSync('packages/*/*/src/**/*.ts', { cwd: scanRoot }).map(s => s.split(sep).join('/')).sort()) { - const abs = resolve(scanRoot, rel) - const text = readFileSync(abs, 'utf8') - if (!text.includes('SurfaceEventType')) continue - const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true) - for (const stmt of sf.statements) { - if (!ts.isTypeAliasDeclaration(stmt) || stmt.name.text !== 'SurfaceEventType') continue - const src = pointer(rel, sf, stmt) - const members = ts.isUnionTypeNode(stmt.type) ? [...stmt.type.types] : [stmt.type] - const names: string[] = [] - for (const m of members) { - if (ts.isLiteralTypeNode(m) && ts.isStringLiteral(m.literal)) names.push(m.literal.text) - else throw new Error(`gen-persistence-catalog: SurfaceEventType (${src}) has a non-string-literal member; the badge derivation needs a closed literal union.`) - } - found.push({ names, source: src }) - } - } - const only = found[0] - if (!only) throw new Error('gen-persistence-catalog: no SurfaceEventType union found under packages/*/*/src.') - if (found.length > 1) throw new Error(`gen-persistence-catalog: SurfaceEventType is declared more than once (${found.map(f => f.source).join(', ')}); the surface subset has exactly one owner.`) - return only.names -} - -/** - * Attach the surface/log-only badge to each event. Hard-errors when a - * `SurfaceEventType` union member names no collected event — a stale union - * member would otherwise silently badge nothing. - */ -export function annotateSurface(events: LogEventEntry[], surfaceTypes: string[]): AnnotatedLogEventEntry[] { - const names = new Set(events.map(e => e.name)) - const stale = surfaceTypes.filter(t => !names.has(t)) - if (stale.length > 0) { - throw new Error(`gen-persistence-catalog: SurfaceEventType member(s) ${stale.map(t => `'${t}'`).join(', ')} name no declared log event (stale union member?).`) - } - const surface = new Set(surfaceTypes) - return events.map(e => ({ ...e, surface: surface.has(e.name) })) -} - /** Render the cross-link "Types:" line for a payload, or '' if none apply. */ -function typeLinks(payload: string): string { +function typeLinks(payload: string, locale: PersistenceCatalogLocale): string { const seen = new Set() for (const name of Object.keys(LINK_MAP)) { if (new RegExp(`\\b${name}\\b`).test(payload)) seen.add(name) } if (seen.size === 0) return '' - const links = [...seen].sort().map(n => `[${n}](${LINK_MAP[n]})`) - return `Types: ${links.join(' · ')}` + const links = Object.entries(LINK_MAP).filter(([name]) => seen.has(name)).sort(([left], [right]) => left.localeCompare(right)) + .map(([name, path]) => { + return `[${name}](${locale === 'zh' ? path.replace(/\.md$/u, '.zh.md') : path})` + }) + return `${persistenceCatalogText[locale].types}${links.join(' · ')}` } /** Render one log event entry. */ -function renderEvent(e: AnnotatedLogEventEntry): string[] { +function renderEvent(e: AnnotatedLogEventEntry, locale: PersistenceCatalogLocale): string[] { const heading = `${e.name} — ${e.surface ? 'surface' : 'log-only'}` const out = [``, '', `#### \`${e.name}\` — ${e.surface ? 'surface' : 'log-only'}`, ''] out.push('```' + FENCE, e.declaration, '```', '') - const links = typeLinks(e.payload) + const links = typeLinks(e.payload, locale) if (links) out.push(links, '') - out.push(`Source: [\`${e.source}\`](../${e.source.split(':')[0]})`, '') + out.push(`${persistenceCatalogText[locale].source}[\`${e.source}\`](../${e.source.split(':')[0]})`, '') return out } /** Render the full catalog (pure, deterministic given the collected inputs). */ -export function render(events: AnnotatedLogEventEntry[], envelopeTypes: EventEnvelopeTypeEntry[]): string { +export function render( + events: AnnotatedLogEventEntry[], + envelopeTypes: EventEnvelopeTypeEntry[], + schema?: PersistenceSchemaInventory, + locale: PersistenceCatalogLocale = 'en', +): string { + const text = persistenceCatalogText[locale] const lines: string[] = [ '', '', - '# Session Persistence Event Catalog', + `# ${text.title}`, '', - 'Every event type that can appear in a session\'s durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge into `@deepseek-ai/dsh-session/types` in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](subsystems/persistence.md) (how the log is made durable), and the generated region of [session.md](subsystems/session.md#cordis-surface) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit).', + ...(locale === 'zh' ? ['[English](persistence-catalog.md) | 中文', ''] : []), + text.intro, '', - 'This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks retain the source declaration and nested property JSDoc, removing only the indentation imposed by a containing interface/module, and use a `ts persistence-catalog` fence (skipped by doc-typecheck because declarations reference types from their owning modules). Type names in a payload link to the page that documents them. The archived [persistence-log-catalog record](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md) documents the original catalog decision.', + text.generation, '', - 'The envelope declarations below compose each event\'s `type`, monotonic `seq`, epoch-ms `time`, `data`, the optional `ignorable` unknown-type skip marker, and the conditional `surfaceOp`/`sourceEventSeqs` fields. **surface** marks a `SurfaceEventType` member: it produces an LLM message and declares how it joins the surface list. **log-only** marks everything else: a durable, replayable record with no derived-history contribution. Every payload is JSON-serializable (enforced at `Session.append`). Current writers stamp `SESSION_FORMAT_VERSION`; supported historical artifacts reach this current vocabulary through the build-static adjacent migration catalog ([the version lifecycle](subsystems/persistence.md)). Scope: the packages in this repo; a downstream plugin can merge further current-version event types, which are outside this catalog by construction and require an explicit disposition at a later format edge.', + text.envelopeIntro, '', - '## Event envelope', + ...(schema ? [renderPersistenceSchemaIndex(schema, locale)] : []), + `## ${text.envelope}`, '', '```' + FENCE, envelopeTypes.map(entry => entry.declaration).join('\n\n'), '```', '', - `Sources: ${envelopeTypes.map(entry => `[\`${entry.source}\`](../${entry.source.split(':')[0]})`).join(' · ')}`, + `${text.sources}${envelopeTypes.map(entry => `[\`${entry.source}\`](../${entry.source.split(':')[0]})`).join(' · ')}`, '', - '## Events', + `## ${text.events}`, '', ] const scopes = [...new Set(events.map(e => e.scope))].sort() for (const scope of scopes) { lines.push(`### \`${scope}/*\``, '') for (const e of events.filter(x => x.scope === scope).sort((a, b) => a.name.localeCompare(b.name))) { - lines.push(...renderEvent(e)) + lines.push(...renderEvent(e, locale)) } } + if (schema) lines.push(renderPersistenceSchemaDefinitions(schema, locale)) return lines.join('\n') } @@ -427,26 +159,33 @@ export function renderKnownEventTypes(events: AnnotatedLogEventEntry[]): string ].join('\n') } -/** One generated artifact: repo-relative target and its freshly-rendered content. */ -interface GeneratedArtifact { - readonly out: string - readonly content: string +/** + * Render the complete generated persistence reference from one extracted inventory. + * @param scanRoot - checkout whose source event declarations supply JSDoc. + * @param schema - already extracted source schemas; shared with record validation. + * @returns both catalog languages, pairing metadata, runtime names and machine schemas. + */ +export function persistenceCatalogArtifacts(scanRoot: string, schema: PersistenceSchemaInventory): PersistenceArtifact[] { + const events = annotateSurface(collectLogEvents(scanRoot), collectSurfaceEventTypes(scanRoot)) + const envelope = collectEventEnvelopeTypes(scanRoot) + return [ + ...renderPersistencePair(scanRoot, OUT, render(events, envelope, schema), render(events, envelope, schema, 'zh')), + { path: OUT_RUNTIME_TYPES, content: renderKnownEventTypes(events) }, + { path: OUT_SCHEMA, content: `${JSON.stringify(schema, null, 2)}\n` }, + ] } /** CLI entry: default writes the artifacts, `--check` fails if a committed copy * is stale. Guarded behind an entry-point check so importing this module for * tests neither regenerates the committed files nor calls process.exit. */ function main(): void { - const events = annotateSurface(collectLogEvents(), collectSurfaceEventTypes()) - const artifacts: GeneratedArtifact[] = [ - { out: OUT, content: render(events, collectEventEnvelopeTypes()) }, - { out: OUT_RUNTIME_TYPES, content: renderKnownEventTypes(events) }, - ] + const schema = extractPersistenceSchema(root) + const artifacts = persistenceCatalogArtifacts(root, schema) if (process.argv.includes('--check')) { const stale = artifacts.filter((artifact) => { let committed: string | null = null try { - committed = readFileSync(resolve(root, artifact.out), 'utf8') + committed = readFileSync(resolve(root, artifact.path), 'utf8') } catch { // Only ENOENT (not yet generated) is expected; a present-but-unreadable // file is not a state this repo produces. Either way the remedy is the @@ -456,16 +195,16 @@ function main(): void { return committed !== artifact.content }) if (stale.length === 0) { - console.log(`gen-persistence-catalog: ${artifacts.map(a => a.out).join(', ')} are up to date.`) + console.log(`gen-persistence-catalog: ${artifacts.map(a => a.path).join(', ')} are up to date.`) process.exit(0) } - console.error(`gen-persistence-catalog: ${stale.map(a => a.out).join(', ')} stale. Run \`pnpm run gen-persistence-catalog\` and commit the result.`) + console.error(`gen-persistence-catalog: ${stale.map(a => a.path).join(', ')} stale. Run \`pnpm run gen-persistence-catalog\` and commit the result.`) process.exit(1) } for (const artifact of artifacts) { - writeFileSync(resolve(root, artifact.out), artifact.content) - console.log(`gen-persistence-catalog: wrote ${artifact.out}.`) + writeFileSync(resolve(root, artifact.path), artifact.content) + console.log(`gen-persistence-catalog: wrote ${artifact.path}.`) } } diff --git a/scripts/persistence-artifacts.ts b/scripts/persistence-artifacts.ts new file mode 100644 index 0000000000..bc1d162e81 --- /dev/null +++ b/scripts/persistence-artifacts.ts @@ -0,0 +1,53 @@ +/** Render complete persistence documentation pairs without Git or file mutation. */ + +import { readFileSync } from 'node:fs' +import { hasLanguageSwitcher } from './translation-links.ts' +import { translationPairPaths } from './translation-pairing-record.ts' +import { + blobHash, languageSwitcherTargets, parseTranslationMarkdown, parseTranslationPairingManifest, + renderPairMeta, requiresSourceLanguageSwitcher, translationPairSourcePredicate, + translationStructureDiff, translationStructureSignature, +} from './translation-pairing.ts' + +const isTranslationPairSource = translationPairSourcePredicate(parseTranslationPairingManifest( + readFileSync(new URL('./translation-pairing.manifest.json', import.meta.url), 'utf8'), +)) + +/** One repository-relative generated file and its complete UTF-8 content. */ +export interface PersistenceArtifact { + readonly path: string + readonly content: string +} + +/** + * Check a pair's code, structure and localized links, then render its three files. + * Link existence remains the Markdown gate's responsibility. Pair hashes are + * computed in-process; staging the documents stores the corresponding Git blobs. + * @param root - checkout root used to resolve relative link identities. + * @param source - repository-relative English document path. + * @param en - complete authored or generated English Markdown. + * @param zh - complete authored or generated Chinese Markdown. + * @returns the documents and their matching consistency sidecar, without writing files. + */ +export function renderPersistencePair(root: string, source: string, en: string, zh: string): PersistenceArtifact[] { + const paths = translationPairPaths(source) + const sourceTree = parseTranslationMarkdown(en) + const zhTree = parseTranslationMarkdown(zh) + const sourceTargets = languageSwitcherTargets(paths.source) + const zhTargets = languageSwitcherTargets(paths.zh) + if (!hasLanguageSwitcher(zhTree, zh, sourceTargets) + || requiresSourceLanguageSwitcher(source) && !hasLanguageSwitcher(sourceTree, en, zhTargets)) { + throw new Error(`${source}: both authored languages need their counterpart switcher`) + } + const context = { repoRoot: root, isTranslationPairSource, repositoryFileExists: () => true } + const errors = translationStructureDiff( + translationStructureSignature(sourceTree, zhTargets, { ...context, sourcePath: paths.source, markdown: en }), + translationStructureSignature(zhTree, sourceTargets, { ...context, sourcePath: paths.zh, markdown: zh }), + ) + if (errors.length > 0) throw new Error(`${source}: bilingual structure mismatch: ${errors.join('; ')}`) + return [ + { path: paths.source, content: en }, + { path: paths.zh, content: zh }, + { path: paths.meta, content: renderPairMeta(paths.source, blobHash(Buffer.from(en)), paths.zh, blobHash(Buffer.from(zh))) }, + ] +} diff --git a/scripts/persistence-catalog-source.ts b/scripts/persistence-catalog-source.ts new file mode 100644 index 0000000000..01a42c51b9 --- /dev/null +++ b/scripts/persistence-catalog-source.ts @@ -0,0 +1,299 @@ +/** Source discovery and declaration validation shared by persistence documentation and schemas. */ + +import { globSync, readFileSync } from 'node:fs' +import { resolve, sep } from 'node:path' +import ts from 'typescript' +import { parseJsDoc, pointer, rawJsDoc, reportViolations } from './jsdoc.ts' + +const root = resolve(import.meta.dirname, '..') + +/** The package that owns the durable event vocabulary. */ +const SESSION_PACKAGE = '@deepseek-ai/dsh-session' + +/** The type-only module that plugin declaration merges augment. */ +const SESSION_TYPES_MODULE = '@deepseek-ai/dsh-session/types' + +/** Event-envelope declarations rendered before the per-event vocabulary. */ +const EVENT_ENVELOPE_TYPE_NAMES = [ + 'SessionEventType', + 'SurfaceEventType', + 'SurfaceOp', + 'SessionEvent', +] as const + +type EventEnvelopeTypeName = typeof EVENT_ENVELOPE_TYPE_NAMES[number] + +/** One log event, extracted from a `SessionEventMap` declaration. */ +export interface LogEventEntry { + /** Scoped name, e.g. `turn/start`. */ + name: string + /** The scope prefix, e.g. `turn` (everything before the first `/`). */ + scope: string + /** Payload type text (the member's type annotation, whitespace-collapsed). */ + payload: string + /** Source member declaration and complete JSDoc, dedented from its container. */ + declaration: string + /** Description prose (the member's JSDoc), one line per paragraph. */ + doc: string + /** Source pointer `packages/…/file.ts:line` of the declaration. */ + source: string +} + +/** A {@link LogEventEntry} plus its surface-eligibility badge. */ +export interface AnnotatedLogEventEntry extends LogEventEntry { + /** Whether the type is a `SurfaceEventType` member (may carry `surfaceOp`). */ + surface: boolean +} + +/** One owning event-envelope declaration pasted into the generated catalog. */ +export interface EventEnvelopeTypeEntry { + /** Exported declaration name. */ + name: EventEnvelopeTypeName + /** Verbatim type declaration, including its complete leading JSDoc. */ + declaration: string + /** Source pointer `packages/…/file.ts:line` of the declaration. */ + source: string +} + +const printer = ts.createPrinter({ removeComments: true }) + +/** + * Render a member type on one line through the TypeScript printer, which adds + * semicolon separators. Drop its trailing semicolon before `}` to match the + * repository's inline-literal style. + */ +function payloadText(type: ts.TypeNode, sf: ts.SourceFile): string { + return printer.printNode(ts.EmitHint.Unspecified, type, sf) + .replace(/\s+/g, ' ') + .replace(/;\s*\}/g, ' }') + .trim() +} + +/** + * Copy a declaration from its leading JSDoc through its closing token while + * removing only the indentation imposed by its containing interface/module. + */ +function declarationText(text: string, sf: ts.SourceFile, node: ts.Node): string { + const raw = rawJsDoc(text, node) + const nodeStart = node.getStart(sf) + const start = raw ? text.lastIndexOf(raw, nodeStart) : nodeStart + const { line } = sf.getLineAndCharacterOfPosition(start) + const lineStart = sf.getPositionOfLineAndCharacter(line, 0) + const indent = text.slice(lineStart, start) + return text.slice(lineStart, node.end) + .split('\n') + .map(lineText => lineText.startsWith(indent) ? lineText.slice(indent.length) : lineText) + .join('\n') + .trimEnd() +} + +/** + * Every `interface SessionEventMap` declaration in a source file: the owning + * top-level declaration (in `@deepseek-ai/dsh-session`) and any declaration + * merge inside a `declare module '@deepseek-ai/dsh-session/types'` block. Both forms + * declare members of the SAME merged interface, so both are catalogued + * uniformly. `topLevel` distinguishes the owning form so the caller can verify + * it actually lives in the owning package — an unrelated local interface that + * happens to share the name must not be catalogued as the on-disk vocabulary. + */ +function sessionEventMapDecls(sf: ts.SourceFile): { decl: ts.InterfaceDeclaration; topLevel: boolean }[] { + const decls: { decl: ts.InterfaceDeclaration; topLevel: boolean }[] = [] + for (const stmt of sf.statements) { + if (ts.isInterfaceDeclaration(stmt) && stmt.name.text === 'SessionEventMap') decls.push({ decl: stmt, topLevel: true }) + if (ts.isModuleDeclaration(stmt) && ts.isStringLiteral(stmt.name) && stmt.name.text === SESSION_TYPES_MODULE + && stmt.body && ts.isModuleBlock(stmt.body)) { + for (const inner of stmt.body.statements) { + if (ts.isInterfaceDeclaration(inner) && inner.name.text === 'SessionEventMap') decls.push({ decl: inner, topLevel: false }) + } + } + } + return decls +} + +/** + * The npm package name owning a `packages///…` source file, read + * from that package's manifest — or null when the manifest is missing or + * unparseable (the caller treats null as "ownership unverifiable"). + */ +function packageNameFor(rel: string, scanRoot: string): string | null { + const dir = rel.split('/').slice(0, 3).join('/') + try { + const manifest = JSON.parse(readFileSync(resolve(scanRoot, dir, 'package.json'), 'utf8')) as { name?: string } + return typeof manifest.name === 'string' ? manifest.name : null + } catch { + // Missing or malformed package.json — every real workspace package has one, + // so this only arises in stripped-down fixture trees; either way ownership + // cannot be verified and the caller reports the declaration. + return null + } +} + +/** + * Collect every `SessionEventMap` merge, rejecting inherited, non-literal, + * untyped, undocumented, duplicate, or incorrectly owned members in one report. + */ +export function collectLogEvents(scanRoot: string = root): LogEventEntry[] { + const entries: LogEventEntry[] = [] + const violations: string[] = [] + const seen = new Map() + let owningDecl: string | null = null + for (const rel of globSync('packages/*/*/src/**/*.{ts,tsx}', { cwd: scanRoot }).map(s => s.split(sep).join('/')).sort()) { + const abs = resolve(scanRoot, rel) + const text = readFileSync(abs, 'utf8') + if (!text.includes('SessionEventMap')) continue + const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true) + for (const { decl, topLevel } of sessionEventMapDecls(sf)) { + const declSrc = pointer(rel, sf, decl) + if (topLevel) { + // The top-level form has one home: the single exported declaration in + // the owning package. Same-named interfaces elsewhere are different + // types and must not enter the on-disk catalog. + const pkg = packageNameFor(rel, scanRoot) + if (pkg !== SESSION_PACKAGE) { + violations.push(`top-level interface SessionEventMap (${declSrc}) is outside ${SESSION_PACKAGE} (package ${pkg ?? 'unknown'}). Rename the interface, or contribute events via declare module '${SESSION_TYPES_MODULE}'.`) + continue + } + const exported = decl.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false + if (!exported) { + violations.push(`top-level interface SessionEventMap (${declSrc}) is not exported; the owning vocabulary is the single exported declaration — rename a local helper interface.`) + continue + } + if (owningDecl) { + violations.push(`top-level interface SessionEventMap (${declSrc}) is already declared at ${owningDecl}; the owning vocabulary has exactly one home.`) + continue + } + owningDecl = declSrc + } + if (decl.heritageClauses?.length) { + violations.push(`SessionEventMap declaration (${declSrc}) uses extends; inherited keys would join keyof SessionEventMap without a catalog row — declare event members directly.`) + } + for (const member of decl.members) { + const src = pointer(rel, sf, member) + if (!ts.isPropertySignature(member) || !member.type) { + // A method-form or type-less member still joins `keyof SessionEventMap`, + // so skipping it silently would be exactly the undocumented-event hole + // this catalog exists to close. + const label = (member as { name?: ts.Node }).name?.getText(sf) ?? member.getText(sf).replace(/\s+/g, ' ') + violations.push(`SessionEventMap member ${label} (${src}) is not a property signature with an explicit payload type; declare every log event as 'scope/name': .`) + continue + } + if (!ts.isStringLiteral(member.name)) { + violations.push(`log event at ${src} has a non-literal name; the catalog needs string-literal event names.`) + continue + } + const name = member.name.text + const where = `log event '${name}' (${src})` + const prior = seen.get(name) + if (prior) { + violations.push(`${where} is already declared at ${prior}; an event type has exactly one declaration.`) + continue + } + seen.set(name, src) + const payload = payloadText(member.type, sf) + const { doc, hasMode } = parseJsDoc(rawJsDoc(text, member)) + if (hasMode) { + violations.push(`${where} carries an @mode tag, but a log event has no dispatch mode (it is not a cordis bus event — it rides the 'session/event' emit). Remove the tag.`) + } + if (!doc) { + violations.push(`${where} has no description prose. Say what the event records and what its payload means — the JSDoc becomes the catalog entry.`) + } + const declaration = declarationText(text, sf, member) + entries.push({ name, scope: name.split('/')[0] ?? name, payload, declaration, doc, source: src }) + } + } + } + reportViolations('gen-persistence-catalog', violations) + return entries +} + +/** + * Collect the exported declarations that compose the persisted event envelope, + * preserving their source JSDoc and declaration text. + */ +export function collectEventEnvelopeTypes(scanRoot: string = root): EventEnvelopeTypeEntry[] { + const found = new Map() + const violations: string[] = [] + const wanted = new Set(EVENT_ENVELOPE_TYPE_NAMES) + for (const rel of globSync('packages/*/*/src/**/*.{ts,tsx}', { cwd: scanRoot }).map(s => s.split(sep).join('/')).sort()) { + const abs = resolve(scanRoot, rel) + const text = readFileSync(abs, 'utf8') + if (!EVENT_ENVELOPE_TYPE_NAMES.some(name => text.includes(name))) continue + if (packageNameFor(rel, scanRoot) !== SESSION_PACKAGE) continue + const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true) + for (const stmt of sf.statements) { + if (!ts.isTypeAliasDeclaration(stmt) || !wanted.has(stmt.name.text)) continue + const name = stmt.name.text as EventEnvelopeTypeName + const src = pointer(rel, sf, stmt) + const where = `event-envelope type '${name}' (${src})` + const prior = found.get(name) + if (prior) { + violations.push(`${where} is already declared at ${prior.source}; the persisted envelope type has exactly one owner.`) + continue + } + if (!(stmt.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false)) { + violations.push(`${where} is not exported.`) + } + const { doc, hasMode } = parseJsDoc(rawJsDoc(text, stmt)) + if (hasMode) violations.push(`${where} carries an @mode tag, but a persisted type has no dispatch mode.`) + if (!doc) violations.push(`${where} has no description prose. The full JSDoc is part of the generated catalog.`) + found.set(name, { name, declaration: declarationText(text, sf, stmt), source: src }) + } + } + const missing = EVENT_ENVELOPE_TYPE_NAMES.filter(name => !found.has(name)) + if (missing.length > 0) { + violations.push(`missing event-envelope declaration(s): ${missing.join(', ')}.`) + } + reportViolations('gen-persistence-catalog', violations) + return EVENT_ENVELOPE_TYPE_NAMES.map((name) => { + const entry = found.get(name) + if (!entry) throw new Error(`gen-persistence-catalog: missing checked event-envelope declaration '${name}'.`) + return entry + }) +} + +/** + * Parse the `SurfaceEventType` union — the surface-eligible subset of event + * types — from source. Hard-errors when the alias is missing, declared more + * than once, or contains a non-string-literal member: the badge derivation + * relies on the union being a closed set of literal event names. + * `scanRoot` defaults to the repo root; tests pass a fixture dir. + */ +export function collectSurfaceEventTypes(scanRoot: string = root): string[] { + const found: { names: string[]; source: string }[] = [] + for (const rel of globSync('packages/*/*/src/**/*.{ts,tsx}', { cwd: scanRoot }).map(s => s.split(sep).join('/')).sort()) { + const abs = resolve(scanRoot, rel) + const text = readFileSync(abs, 'utf8') + if (!text.includes('SurfaceEventType')) continue + const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true) + for (const stmt of sf.statements) { + if (!ts.isTypeAliasDeclaration(stmt) || stmt.name.text !== 'SurfaceEventType') continue + const src = pointer(rel, sf, stmt) + const members = ts.isUnionTypeNode(stmt.type) ? [...stmt.type.types] : [stmt.type] + const names: string[] = [] + for (const m of members) { + if (ts.isLiteralTypeNode(m) && ts.isStringLiteral(m.literal)) names.push(m.literal.text) + else throw new Error(`gen-persistence-catalog: SurfaceEventType (${src}) has a non-string-literal member; the badge derivation needs a closed literal union.`) + } + found.push({ names, source: src }) + } + } + const only = found[0] + if (!only) throw new Error('gen-persistence-catalog: no SurfaceEventType union found under packages/*/*/src.') + if (found.length > 1) throw new Error(`gen-persistence-catalog: SurfaceEventType is declared more than once (${found.map(f => f.source).join(', ')}); the surface subset has exactly one owner.`) + return only.names +} + +/** + * Attach the surface/log-only badge to each event. Hard-errors when a + * `SurfaceEventType` union member names no collected event — a stale union + * member would otherwise silently badge nothing. + */ +export function annotateSurface(events: LogEventEntry[], surfaceTypes: string[]): AnnotatedLogEventEntry[] { + const names = new Set(events.map(e => e.name)) + const stale = surfaceTypes.filter(t => !names.has(t)) + if (stale.length > 0) { + throw new Error(`gen-persistence-catalog: SurfaceEventType member(s) ${stale.map(t => `'${t}'`).join(', ')} name no declared log event (stale union member?).`) + } + const surface = new Set(surfaceTypes) + return events.map(e => ({ ...e, surface: surface.has(e.name) })) +} diff --git a/scripts/persistence-catalog-text.ts b/scripts/persistence-catalog-text.ts new file mode 100644 index 0000000000..f687c06962 --- /dev/null +++ b/scripts/persistence-catalog-text.ts @@ -0,0 +1,43 @@ +/** Authored language pairs for generated persistence reference prose. */ + +/** Language of a generated persistence reference. */ +export type PersistenceCatalogLocale = 'en' | 'zh' + +const english = { + title: 'Session Persistence Event Catalog', + intro: 'Every repository-declared durable Session event appears here with its source declaration and resolved types. The catalog covers the logical and physical headers, event envelopes, and every plugin declaration merge. See [Session](subsystems/session.md) for replay and [persistence](subsystems/persistence.md) for storage.', + generation: 'Run `pnpm run gen-persistence-catalog` to regenerate both catalog languages, their pairing record, the known-event module, and the machine schema inventory. `pnpm run verify-persistence-catalog` checks all generated files. Declaration fences preserve source JSDoc and type references; resolved definitions expose their transitive structure.', + envelopeIntro: 'The envelope carries `type`, `seq`, `time`, `data`, optional `ignorable`, and conditional `surfaceOp` / `sourceEventSeqs`. A **surface** event produces model history; a **log-only** event does not. The inventory covers this repository; external plugin types require their own declarations and are outside this catalog.', + envelope: 'Event envelope', events: 'Events', sources: 'Sources: ', source: 'Source: ', types: 'Types: ', + fingerprints: 'Persistence type fingerprints', + fingerprintsIntro: 'The [machine inventory](persistence-schema.json) contains every reachable normalized type and its SHA-256 digest. Root digests include referenced types. Comments, source locations, alias names, erased brands, readonly markers, and harmless field, union, or intersection reordering do not affect these fingerprints. Tuple order, property names, value types, and optionality do. Catalog text and source locations can still produce a diff when digests stay unchanged.', + historyIntro: 'The [change records](persistence-changes/README.md) acknowledge exact transitions using snapshots kept in this tree. Follow the [review workflow](cookbook/reviewing-persistence-type-changes.md) to classify a change and record it. These checks cover declared type structure; opaque payload contents and behavior without type changes are outside their scope.', + rootColumns: '| Root | Kind | SHA-256 | Resolved type |', + definitions: 'Resolved persistence types', + definitionsIntro: 'Each definition appears once. References preserve sharing and recursion; the digest beside a definition includes its complete reachable structure. Source names and locations identify its declarations but are excluded from its digest.', + propertyColumns: '| Property | Presence | Type |', positionColumns: '| Position | Presence | Type |', + optional: 'optional', required: 'required', rest: 'rest', index: 'index signature', + emptyObject: 'Object with no declared properties.', arrayPrefix: 'Array of ', arraySuffix: '.', + oneOf: 'One of:', opaque: ' (opaque)', opaqueExplanation: ": the declaration does not expose the stored value's internal fields.", +} + +const chinese: Record = { + title: '会话持久化事件目录', + intro: '本目录列出仓库声明的每个持久化 Session 事件及其源码声明和解析类型,覆盖逻辑与物理 header、事件信封以及各插件的声明合并。回放规则参见 [Session](subsystems/session.zh.md),存储规则参见[持久化](subsystems/persistence.zh.md)。', + generation: '运行 `pnpm run gen-persistence-catalog` 可重新生成目录的两种语言、配对记录、已知事件模块和机器 schema 目录。`pnpm run verify-persistence-catalog` 检查所有生成文件。声明围栏保留源码 JSDoc 和类型引用;解析后的定义展开其传递引用结构。', + envelopeIntro: '信封包含 `type`、`seq`、`time`、`data`、可选的 `ignorable` 以及条件字段 `surfaceOp` / `sourceEventSeqs`。**surface** 事件产生模型历史,**log-only** 事件不产生模型历史。目录覆盖本仓库;外部插件类型需要独立声明,不属于本目录。', + envelope: '事件信封', events: '事件', sources: '来源:', source: '来源:', types: '类型:', + fingerprints: '持久化类型指纹', + fingerprintsIntro: '[机器可读目录](persistence-schema.json)包含所有可达的规范化类型及其 SHA-256 摘要。根类型的摘要涵盖引用类型。注释、源码位置、别名、擦除的品牌标记、readonly 标记以及无语义变化的字段、联合类型或交叉类型重排不影响摘要;元组顺序、属性名称、值类型和可选性会影响摘要。摘要不变时,目录文本和源码位置仍可能产生 diff。', + historyIntro: '[变更记录](persistence-changes/README.zh.md)通过保存在本源码树中的快照确认精确的类型转换。按照[评审流程](cookbook/reviewing-persistence-type-changes.zh.md)分类并记录变更。这些检查覆盖已声明的类型结构;不透明载荷的内部内容和未改变类型的行为变更不在检查范围内。', + rootColumns: '| 根类型 | 类别 | SHA-256 | 已解析类型 |', + definitions: '已解析的持久化类型', + definitionsIntro: '每个类型定义仅列出一次。引用保留共享和递归关系;定义旁的摘要涵盖其完整可达结构。源码名称和位置标识声明来源,但不参与摘要计算。', + propertyColumns: '| 属性 | 存在性 | 类型 |', positionColumns: '| 位置 | 存在性 | 类型 |', + optional: '可选', required: '必需', rest: '剩余项', index: '索引签名', + emptyObject: '无已声明属性的对象。', arrayPrefix: '', arraySuffix: ' 的数组。', + oneOf: '以下类型之一:', opaque: '(不透明)', opaqueExplanation: ':此声明未暴露存储值的内部字段。', +} + +/** Complete translated prose; adding an English key requires its Chinese counterpart. */ +export const persistenceCatalogText = { en: english, zh: chinese } diff --git a/scripts/persistence-changes.spec.ts b/scripts/persistence-changes.spec.ts new file mode 100644 index 0000000000..4425587bd7 --- /dev/null +++ b/scripts/persistence-changes.spec.ts @@ -0,0 +1,544 @@ +/** Current-tree persistence history rejects uncovered and incorrectly acknowledged type changes. */ + +import { spawnSync } from 'node:child_process' +import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join, resolve } from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import { canonicalizeSchema, schemaDigest } from './persistence-schema-model.ts' +import type { PersistenceRoot, PersistenceSchemaInventory, SchemaNode, SchemaProperty } from './persistence-schema-model.ts' +import { extractPersistenceSchema } from './persistence-schema.ts' +import { persistenceCatalogArtifacts } from './gen-persistence-catalog.ts' +import { + classifyPersistenceChange, + loadPersistenceHistory, + parsePersistenceSnapshot, + runPersistenceChanges as executePersistenceChanges, + validatePersistenceHistory, + verifyPersistenceChanges, +} from './persistence-changes.ts' +import type { PersistenceChangeRecord, PersistenceHistoryEntry } from './persistence-changes.ts' + +function runPersistenceChanges( + args: readonly string[], root: string, extract: (root: string) => PersistenceSchemaInventory, +): string { + return executePersistenceChanges(args, root, extract, (_root, current) => [{ + path: 'docs/persistence-schema.json', content: JSON.stringify(current, null, 2) + '\n', + }]) +} + +function jsonResult(source: string): { ok: boolean; files: readonly string[] } { + return JSON.parse(source) as { ok: boolean; files: readonly string[] } +} + +const AUTHORED_PROSE = { + en: { summary: 'Adds optional metadata.', compatibility: 'Readers may omit the metadata.', verification: 'The focused tests passed.' }, + zh: { summary: '添加可选元数据。', compatibility: '读取方可省略元数据。', verification: '定向测试通过。' }, +} + +function proseFile(root: string): string { + const path = join(root, 'prose.json') + writeFileSync(path, JSON.stringify(AUTHORED_PROSE)) + return path +} + +const roots: string[] = [] +afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) }) + +function fixture(): string { + const root = mkdtempSync(join(tmpdir(), 'dsh-persistence-changes-')) + roots.push(root) + mkdirSync(join(root, 'docs/persistence-changes'), { recursive: true }) + return root +} + +type Shape = 'string' | 'number' | 'boolean' | { readonly [property: string]: Shape } + +function typeRoot(key: string, shape: Shape, version = 3): PersistenceRoot { + const nodes: SchemaNode[] = [] + function add(shape: Shape): number { + const index = nodes.length + nodes.push({ kind: 'primitive', type: 'never' }) + nodes[index] = typeof shape === 'string' ? { kind: 'primitive', type: shape } : { + kind: 'object', indices: [], properties: Object.entries(shape).map(([name, child]) => ({ + name: name.replace(/\?$/u, ''), optional: name.endsWith('?'), type: add(child), + })), + } + return index + } + if (key === 'SessionHeader') { + const index = add(shape) + const node = nodes[index]! + if (node.kind !== 'object') throw new Error('header fixture requires an object') + nodes[index] = { ...node, properties: [...node.properties, { name: 'version', optional: false, type: nodes.length }] } + nodes.push({ kind: 'literal', value: version }) + } else if (key.startsWith('event:')) { + add({ data: shape, type: 'string' }) + const node = nodes[0]! + if (node.kind !== 'object') throw new Error('event fixture requires an object') + const type = node.properties.find(item => item.name === 'type')!.type + nodes[type] = { kind: 'literal', value: key.slice(6) } + } else add(shape) + const schema = canonicalizeSchema(nodes, 0) + return { key, kind: ['SessionHeader', 'JsonlHeaderLine'].includes(key) ? 'header' : key === 'SessionEventEnvelope' ? 'envelope' : 'event', + ...(key.startsWith('event:') ? { event: key.slice(6), surface: false } : {}), schema, digest: schemaDigest(schema) } +} + +function inventory(eventShape: Shape = { value: 'string' }, version = 3): PersistenceSchemaInventory { + return { formatVersion: 1, types: [], roots: [typeRoot('SessionHeader', { id: 'string' }, version), typeRoot('SessionEventEnvelope', { type: 'string' }), typeRoot('event:example/value', eventShape), typeRoot('JsonlHeaderLine', { version: 'number' })] } +} + +function entry( + id: string, schema: PersistenceSchemaInventory, previous: string | null, + baseline = false, decision: 'same-version' | 'version-bump' = 'same-version', +): PersistenceHistoryEntry { + return { + record: { schemaVersion: 1, id, baseline, changes: schema.roots.map(root => ({ + root: root.key, previous, after: root.digest, decision, + })) }, + snapshot: schema, + } +} + +const BASE_ID = '2026-09-11-baseline' +const NEXT_ID = '2026-09-11-change' + +function onlyEvent(schema: PersistenceSchemaInventory): PersistenceSchemaInventory { + return { ...schema, roots: schema.roots.filter(root => root.kind === 'event') } +} + +function finishDocuments(root: string, id: string): void { + for (const suffix of ['.md', '.zh.md']) { + const path = join(root, 'docs/persistence-changes', id + suffix) + writeFileSync(path, readFileSync(path, 'utf8').replaceAll('TODO: explain this change.', 'Optional payload metadata preserves the recorded value.') + .replaceAll('TODO: record validation evidence.', 'The focused persistence-history tests passed.')) + } +} + +function commitCurrent(root: string, schema: PersistenceSchemaInventory): void { + writeFileSync(join(root, 'docs/persistence-schema.json'), JSON.stringify(schema, null, 2) + '\n') +} + +function baseline(root: string, schema = inventory()): void { + runPersistenceChanges(['--baseline', BASE_ID], root, () => schema) + finishDocuments(root, BASE_ID) + commitCurrent(root, schema) +} + +function unionBody(arms: readonly (readonly SchemaProperty[])[]): PersistenceRoot { + const nodes: SchemaNode[] = [ + { kind: 'object', indices: [], properties: [{ name: 'data', type: 1, optional: false }] }, + { kind: 'union', types: arms.map((_, index) => 5 + index) }, + { kind: 'primitive', type: 'string' }, + { kind: 'primitive', type: 'number' }, + { kind: 'primitive', type: 'boolean' }, + ...arms.map(properties => ({ kind: 'object' as const, indices: [], properties })), + ] + const schema = canonicalizeSchema(nodes, 0) + return { ...typeRoot('event:example/value', {}), schema, digest: schemaDigest(schema) } +} + +describe('persistence change classification', () => { + it('treats a new optional payload subtree as one additive change even with required descendants', () => { + const before = typeRoot('event:example/value', { value: 'string' }) + const after = typeRoot('event:example/value', { value: 'string', 'details?': { name: 'string', count: 'number' } }) + expect(classifyPersistenceChange(before, after)).toEqual([{ path: 'event:example/value.data.details', kind: 'optional-property-added', description: 'optional property added', requiresVersionBump: false }]) + }) + + it('permits required-to-optional payload properties while rejecting opposite changes, type changes, and removals', () => { + const required = typeRoot('event:example/value', { value: 'string' }) + const optional = typeRoot('event:example/value', { 'value?': 'string' }) + expect(classifyPersistenceChange(required, optional).every(change => !change.requiresVersionBump)).toBe(true) + for (const after of [required, typeRoot('event:example/value', { 'value?': 'number' }), typeRoot('event:example/value', {})]) { + expect(classifyPersistenceChange(optional, after).some(change => change.requiresVersionBump)).toBe(true) + } + }) + + it('pairs multiple discriminated union arms when a shared payload gains an optional property', () => { + function unionRoot(optional: boolean): PersistenceRoot { + const nodes: SchemaNode[] = [ + { kind: 'object', indices: [], properties: [{ name: 'data', type: 1, optional: false }] }, + { kind: 'union', types: [2, 3] }, + { kind: 'object', indices: [], properties: [{ name: 'kind', type: 4, optional: false }, { name: 'payload', type: 6, optional: false }] }, + { kind: 'object', indices: [], properties: [{ name: 'kind', type: 5, optional: false }, { name: 'payload', type: 6, optional: false }] }, + { kind: 'literal', value: 'left' }, { kind: 'literal', value: 'right' }, + { kind: 'object', indices: [], properties: [{ name: 'value', type: 7, optional: false }, ...(optional ? [{ name: 'label', type: 7, optional: true }] : [])] }, + { kind: 'primitive', type: 'string' }, + ] + const schema = canonicalizeSchema(nodes, 0) + return { ...typeRoot('event:example/value', {}), schema, digest: schemaDigest(schema) } + } + const differences = classifyPersistenceChange(unionRoot(false), unionRoot(true)) + expect(differences.length).toBeGreaterThan(0) + expect(differences.every(change => !change.requiresVersionBump)).toBe(true) + }) + + it('allows optional additions across undiscriminated union arms', () => { + const first = [{ name: 'a', type: 2, optional: false }] + const second = [{ name: 'b', type: 3, optional: false }] + const extra = { name: 'x', type: 4, optional: true } + const differences = classifyPersistenceChange(unionBody([first, second]), unionBody([[...first, extra], [...second, extra]])) + expect(differences).toEqual([expect.objectContaining({ path: 'event:example/value.data.x', requiresVersionBump: false })]) + }) + + it('allows required-to-optional fields across undiscriminated union arms', () => { + const before = unionBody([[{ name: 'a', type: 2, optional: false }], [{ name: 'b', type: 3, optional: false }]]) + const after = unionBody([[{ name: 'a', type: 2, optional: true }], [{ name: 'b', type: 3, optional: true }]]) + const differences = classifyPersistenceChange(before, after) + expect(differences.map(change => change.path).sort()).toEqual(['event:example/value.data.a', 'event:example/value.data.b']) + expect(differences.every(change => change.kind === 'property-made-optional' && !change.requiresVersionBump)).toBe(true) + }) + + it('finds a complete matching when one union arm has multiple additive successors', () => { + const required = [{ name: 'a', type: 2, optional: false }] + const optional = [{ name: 'a', type: 2, optional: true }] + const flexible = [{ name: '0', type: 4, optional: true }, ...optional] + const constrained = [...required, { name: 'z', type: 4, optional: true }] + const before = unionBody([required, optional]) + for (const after of [unionBody([flexible, constrained]), unionBody([constrained, flexible])]) { + const differences = classifyPersistenceChange(before, after) + expect(differences.length).toBeGreaterThan(0) + expect(differences.every(change => !change.requiresVersionBump)).toBe(true) + } + const noCompleteMatching = unionBody([flexible, [{ name: 'different', type: 4, optional: true }]]) + expect(classifyPersistenceChange(before, noCompleteMatching).some(change => change.requiresVersionBump)).toBe(true) + }) + + it('keeps union cardinality, property removal, new required fields, and value types strict', () => { + const first = [{ name: 'a', type: 2, optional: false }] + const second = [{ name: 'b', type: 3, optional: false }] + const third = [{ name: 'c', type: 4, optional: false }] + const before = unionBody([first, second]) + const variants = [ + unionBody([first, second, third]), + unionBody([first]), + unionBody([first, third]), + unionBody([[...first, { name: 'x', type: 4, optional: false }], [...second, { name: 'x', type: 4, optional: true }]]), + unionBody([[{ name: 'a', type: 3, optional: true }], [{ name: 'b', type: 3, optional: true }]]), + ] + for (const after of variants) expect(classifyPersistenceChange(before, after).some(change => change.requiresVersionBump)).toBe(true) + }) + + it('checks all recursive union arms without retaining provisional successes between candidates', () => { + const next = { name: 'next', type: 1, optional: true } + const first = [{ name: 'a', type: 2, optional: false }, next] + const second = [{ name: 'b', type: 3, optional: false }, next] + const extra = { name: 'x', type: 4, optional: true } + const before = unionBody([first, second]) + const additive = unionBody([[...first, extra], [...second, extra]]) + expect(classifyPersistenceChange(before, additive).every(change => !change.requiresVersionBump)).toBe(true) + const invalid = unionBody([[...first, extra], [{ name: 'b', type: 2, optional: false }, next, extra]]) + expect(classifyPersistenceChange(before, invalid).some(change => change.requiresVersionBump)).toBe(true) + expect(classifyPersistenceChange(invalid, before).some(change => change.requiresVersionBump)).toBe(true) + }) + + it('preserves body scope through arrays and tuples and rejects a simultaneous value-type change', () => { + function wrapped(optional: boolean, primitive: 'string' | 'number' = 'string'): PersistenceRoot { + const nodes: SchemaNode[] = [ + { kind: 'object', indices: [], properties: [{ name: 'data', type: 1, optional: false }] }, + { kind: 'tuple', elements: [{ type: 2, optional: false, rest: false }] }, + { kind: 'array', element: 3 }, + { kind: 'object', indices: [], properties: [{ name: 'value', type: 4, optional }] }, + { kind: 'primitive', type: primitive }, + ] + const schema = canonicalizeSchema(nodes, 0) + return { ...typeRoot('event:example/value', {}), schema, digest: schemaDigest(schema) } + } + expect(classifyPersistenceChange(wrapped(false), wrapped(true)).every(change => !change.requiresVersionBump)).toBe(true) + expect(classifyPersistenceChange(wrapped(false), wrapped(true, 'number')).some(change => change.requiresVersionBump)).toBe(true) + }) + + it('keeps optional header/envelope properties and surface event additions strict', () => { + for (const key of ['SessionHeader', 'JsonlHeaderLine', 'SessionEventEnvelope']) { + expect(classifyPersistenceChange(typeRoot(key, {}), typeRoot(key, { 'metadata?': 'string' }))[0]?.requiresVersionBump).toBe(true) + } + const added = typeRoot('event:example/added', { value: 'string' }) + expect(classifyPersistenceChange(null, added)[0]?.requiresVersionBump).toBe(false) + expect(classifyPersistenceChange(null, { ...added, surface: true })[0]?.requiresVersionBump).toBe(true) + expect(classifyPersistenceChange(added, null)[0]?.requiresVersionBump).toBe(true) + }) +}) + +describe('persistence history verification', () => { + it('accepts a baseline and a successive optional addition without comparing the historical schema to current', () => { + const before = inventory() + const after = inventory({ value: 'string', 'label?': 'string' }) + const history = validatePersistenceHistory([entry(BASE_ID, before, null, true), entry(NEXT_ID, onlyEvent(after), BASE_ID)]) + expect(history.tips.get('event:example/value')?.root?.digest).toBe(after.roots[2]?.digest) + expect(history.tips.get('SessionHeader')?.id).toBe(BASE_ID) + }) + + it('rejects missing predecessors, forks, cycles, duplicates, and missing baseline', () => { + const base = entry(BASE_ID, inventory(), null, true) + const next = entry(NEXT_ID, onlyEvent(inventory({ value: 'string', 'label?': 'string' })), BASE_ID) + expect(() => validatePersistenceHistory([next])).toThrow('exactly one baseline') + expect(() => validatePersistenceHistory([base, entry(NEXT_ID, next.snapshot, '2026-09-11-missing')])).toThrow('missing predecessor') + expect(() => validatePersistenceHistory([base, next, entry('2026-09-11-fork', next.snapshot, BASE_ID)])).toThrow('forked') + expect(() => validatePersistenceHistory([base, base])).toThrow('exactly one baseline') + const cycle = entry('2026-09-11-cycle', { ...next.snapshot, roots: [typeRoot('event:cycle/event', {})] }, '2026-09-11-cycle') + expect(() => validatePersistenceHistory([base, cycle])).toThrow('cycle') + }) + + it('binds breaking decisions to the same record header version transition', () => { + const base = entry(BASE_ID, inventory(), null, true) + const breaking = inventory({ value: 'number' }, 4) + expect(() => validatePersistenceHistory([base, entry(NEXT_ID, onlyEvent(breaking), BASE_ID)])).toThrow('requires a format version bump') + expect(() => validatePersistenceHistory([base, entry(NEXT_ID, onlyEvent(breaking), BASE_ID, false, 'version-bump')])).toThrow("this record's own") + const touched = { ...breaking, roots: breaking.roots.filter(root => root.key === 'SessionHeader' || root.kind === 'event') } + expect(validatePersistenceHistory([base, entry(NEXT_ID, touched, BASE_ID, false, 'version-bump')]).tips.get('SessionHeader')?.id).toBe(NEXT_ID) + const later = entry('2026-09-11-later', onlyEvent(inventory({ value: 'boolean' }, 4)), NEXT_ID, false, 'version-bump') + expect(() => validatePersistenceHistory([base, entry(NEXT_ID, touched, BASE_ID, false, 'version-bump'), later])).toThrow("this record's own") + }) + + it('retains deletion tombstones and requires re-added roots to continue them', () => { + const base = entry(BASE_ID, inventory(), null, true) + const after = inventory({}, 4) + const header = after.roots[0]! + const record: PersistenceChangeRecord = { schemaVersion: 1, id: NEXT_ID, baseline: false, changes: [ + { root: 'SessionHeader', previous: BASE_ID, after: header.digest, decision: 'version-bump' }, + { root: 'event:example/value', previous: BASE_ID, after: null, decision: 'version-bump' }, + ] } + const deletion = { record, snapshot: { ...after, roots: [header] } } + expect(validatePersistenceHistory([base, deletion]).tips.get('event:example/value')?.root).toBeNull() + const restored = entry('2026-09-11-restore', onlyEvent(inventory()), NEXT_ID) + expect(validatePersistenceHistory([base, deletion, restored]).tips.get('event:example/value')?.id).toBe(restored.record.id) + expect(() => validatePersistenceHistory([base, deletion, entry('2026-09-11-restore', restored.snapshot, null)])).toThrow('forked') + }) + + it('rejects malformed references, unknown schema variants, digest tampering, and extra snapshot roots', () => { + const schema = inventory() + const malformed = structuredClone(schema) as unknown as { roots: Array<{ schema: { nodes: unknown[] }; digest: string }> } + malformed.roots[0]!.schema.nodes = [{ kind: 'array', element: 99 }] + expect(() => parsePersistenceSnapshot(malformed)).toThrow('unknown schema node') + malformed.roots[0]!.schema.nodes = [{ kind: 'future' }] + expect(() => parsePersistenceSnapshot(malformed)).toThrow('unknown schema node kind') + const tampered = structuredClone(schema) + Object.assign(tampered.roots[0]!, { digest: '0'.repeat(64) }) + expect(() => parsePersistenceSnapshot(tampered)).toThrow('digest mismatch') + expect(() => parsePersistenceSnapshot({ ...schema, formatVersion: 2 })).toThrow('normalization version') + const next = entry(NEXT_ID, onlyEvent(inventory({ value: 'string', 'label?': 'string' })), BASE_ID) + expect(() => validatePersistenceHistory([entry(BASE_ID, schema, null, true), { ...next, snapshot: inventory() }])).toThrow('snapshot roots') + }) +}) + +describe('persistence changes current-tree commands', () => { + it('requires completed record prose, rejects stale generated output, and reports unacknowledged paths', () => { + const root = fixture() + const before = inventory() + runPersistenceChanges(['--baseline', BASE_ID], root, () => before) + expect(() => loadPersistenceHistory(root)).toThrow('complete compatibility') + finishDocuments(root, BASE_ID) + rmSync(join(root, 'docs/persistence-schema.json')) + expect(() => verifyPersistenceChanges(root, before)).toThrow('is missing') + commitCurrent(root, before) + const after = inventory({ value: 'string', 'label?': 'string' }) + expect(() => verifyPersistenceChanges(root, after)).toThrow('is stale') + commitCurrent(root, after) + expect(() => verifyPersistenceChanges(root, after)).toThrow('event:example/value.data.label: optional property added (same-version allowed)') + runPersistenceChanges(['--record', NEXT_ID, '--decision', 'same-version'], root, () => after) + finishDocuments(root, NEXT_ID) + expect(runPersistenceChanges([], root, () => after)).toContain('4 roots match 2 history records') + expect(() => runPersistenceChanges(['--baseline', '2026-09-11-reset'], root, () => after)).toThrow('cannot reset history') + expect(() => runPersistenceChanges(['--record', '2026-09-11-empty', '--decision', 'same-version'], root, () => after)).toThrow('no persistence type changes') + }) + + it('rejects changed bilingual machine declarations and unreferenced snapshots', () => { + const root = fixture() + baseline(root) + const chinese = join(root, 'docs/persistence-changes', `${BASE_ID}.zh.md`) + const original = readFileSync(chinese, 'utf8') + writeFileSync(chinese, original.replace('baseline: true', 'baseline: false')) + expect(() => loadPersistenceHistory(root)).toThrow('bilingual machine records differ') + writeFileSync(chinese, original) + writeFileSync(join(root, 'docs/persistence-changes/2026-09-11-orphan.schema.json'), '{}\n') + expect(() => loadPersistenceHistory(root)).toThrow('unreferenced') + }) + + it.each([ + ['optional field', inventory({ value: 'string', 'label?': 'string' })], + ['required-to-optional field', inventory({ 'value?': 'string' })], + ['ordinary event addition', { ...inventory(), roots: [...inventory().roots, typeRoot('event:example/added', { count: 'number' })] }], + ] as const)('infers same-version for %s', (_name, after) => { + const root = fixture() + baseline(root) + runPersistenceChanges(['--record', NEXT_ID, '--prose', proseFile(root)], root, () => after) + const changes = loadPersistenceHistory(root).entries.find(entry => entry.record.id === NEXT_ID)?.record.changes + expect(changes?.length).toBeGreaterThan(0) + expect(changes?.every(change => change.decision === 'same-version')).toBe(true) + verifyPersistenceChanges(root, after) + }) + + it('infers a version bump when the source includes its own increasing header version', () => { + const root = fixture() + baseline(root) + const after = inventory({ value: 'number' }, 4) + runPersistenceChanges(['--record', NEXT_ID, '--prose', proseFile(root)], root, () => after) + const changes = loadPersistenceHistory(root).entries.find(entry => entry.record.id === NEXT_ID)?.record.changes + expect(changes?.map(change => change.root).sort()).toEqual(['SessionHeader', 'event:example/value']) + expect(changes?.every(change => change.decision === 'version-bump')).toBe(true) + verifyPersistenceChanges(root, after) + }) + + it('rejects inferred bumps without a header transition and explicit incorrect assertions before writing', () => { + const root = fixture() + baseline(root) + const prose = proseFile(root) + const directory = join(root, 'docs/persistence-changes') + const files = readdirSync(directory).sort() + const inventoryPath = join(root, 'docs/persistence-schema.json') + const before = readFileSync(inventoryPath, 'utf8') + for (const [args, code] of [ + [[], 'version-transition-required'], + [['--decision', 'same-version'], 'version-bump-required'], + ] as const) { + const result = jsonResult(runPersistenceChanges(['--record', NEXT_ID, '--prose', prose, '--json', ...args], root, + () => inventory({ value: 'number' }))) + expect(result).toMatchObject({ ok: false, code, files: [], changes: [expect.objectContaining({ requiresVersionBump: true })] }) + expect(readFileSync(inventoryPath, 'utf8')).toBe(before) + expect(readdirSync(directory).sort()).toEqual(files) + } + }) + + it('authors a complete pair from explicit prose and reports source changes even when generated artifacts are stale', () => { + const root = fixture() + baseline(root) + const after = inventory({ value: 'string', 'label?': 'string' }) + const pending = jsonResult(runPersistenceChanges(['--check', '--json'], root, () => after)) + expect(pending).toMatchObject({ ok: false, operation: 'check', code: 'stale-artifacts', + roots: [{ root: 'event:example/value', kind: 'event', before: inventory().roots[2]?.digest, after: after.roots[2]?.digest }], changes: [ + { root: 'event:example/value', path: 'event:example/value.data.label', kind: 'optional-property-added', requiresVersionBump: false }, + ] }) + const written = jsonResult(runPersistenceChanges(['--record', NEXT_ID, '--decision', 'same-version', '--prose', proseFile(root), '--json'], root, () => after)) + expect(written).toMatchObject({ ok: true, operation: 'record', recordId: NEXT_ID }) + expect(written.files).toEqual([ + 'docs/persistence-schema.json', `docs/persistence-changes/${NEXT_ID}.md`, `docs/persistence-changes/${NEXT_ID}.zh.md`, + `docs/persistence-changes/${NEXT_ID}.i18n.yaml`, `docs/persistence-changes/${NEXT_ID}.schema.json`, + ]) + expect(written).toMatchObject({ roots: [{ root: 'event:example/value', kind: 'event', + before: inventory().roots[2]?.digest, after: after.roots[2]?.digest }] }) + expect(readFileSync(join(root, `docs/persistence-changes/${NEXT_ID}.md`), 'utf8')).toContain(AUTHORED_PROSE.en.compatibility) + expect(jsonResult(runPersistenceChanges(['--check', '--json'], root, () => after)).ok).toBe(true) + }) + + it('refreshes only a terminal acknowledgement while preserving its authored prose and predecessor', () => { + const root = fixture() + baseline(root) + const after = inventory({ value: 'string', 'label?': 'string' }) + const prose = proseFile(root) + runPersistenceChanges(['--record', NEXT_ID, '--decision', 'same-version', '--prose', prose], root, () => after) + const path = join(root, `docs/persistence-changes/${NEXT_ID}.md`) + const beforeText = readFileSync(path, 'utf8') + const refreshed = inventory({ value: 'string', 'label?': 'string', 'extra?': 'number' }) + runPersistenceChanges(['--update', NEXT_ID], root, () => refreshed) + const stripRecord = (value: string): string => value.replace(/```yaml persistence-change[\s\S]*?```/u, '') + expect(stripRecord(readFileSync(path, 'utf8'))).toBe(stripRecord(beforeText)) + expect(loadPersistenceHistory(root).tips.get('event:example/value')?.id).toBe(NEXT_ID) + expect(loadPersistenceHistory(root).entries.find(entry => entry.record.id === NEXT_ID)?.record.changes[0]?.previous).toBe(BASE_ID) + expect(jsonResult(runPersistenceChanges(['--check', '--json'], root, () => refreshed)).ok).toBe(true) + const final = inventory({ value: 'string', 'label?': 'string', 'extra?': 'number', 'last?': 'boolean' }) + runPersistenceChanges(['--record', '2026-09-11-successor', '--decision', 'same-version', '--prose', prose], root, () => final) + expect(() => runPersistenceChanges(['--update', NEXT_ID, '--decision', 'same-version'], root, () => final)).toThrow('with successors') + expect(() => runPersistenceChanges(['--update', BASE_ID, '--decision', 'same-version'], root, () => final)).toThrow('cannot update the persistence baseline') + }) + + it('completes a scaffold through update and rejects invalid prose or decisions before changing files', () => { + const root = fixture() + baseline(root) + const after = inventory({ value: 'string', 'label?': 'string' }) + runPersistenceChanges(['--record', NEXT_ID, '--decision', 'same-version'], root, () => after) + const prose = proseFile(root) + runPersistenceChanges(['--update', NEXT_ID, '--decision', 'same-version', '--prose', prose], root, () => after) + expect(runPersistenceChanges(['--check'], root, () => after)).toContain('roots match') + expect(readFileSync(join(root, `docs/persistence-changes/${NEXT_ID}.md`), 'utf8')).not.toContain('TODO:') + const pairedPaths = ['.md', '.zh.md', '.i18n.yaml'].map(suffix => join(root, `docs/persistence-changes/${NEXT_ID}${suffix}`)) + const completed = pairedPaths.map(path => readFileSync(path, 'utf8')) + runPersistenceChanges(['--update', NEXT_ID, '--decision', 'same-version', '--prose', prose], root, () => after) + expect(pairedPaths.map(path => readFileSync(path, 'utf8'))).toEqual(completed) + const path = join(root, `docs/persistence-changes/${NEXT_ID}.md`) + const before = readFileSync(path, 'utf8') + const invalid = inventory({ value: 'number' }) + const result = jsonResult(runPersistenceChanges(['--update', NEXT_ID, '--decision', 'same-version', '--json'], root, () => invalid)) + expect(result).toMatchObject({ ok: false, code: 'version-bump-required', changes: [expect.objectContaining({ kind: 'type-changed', requiresVersionBump: true })] }) + expect(readFileSync(path, 'utf8')).toBe(before) + for (const value of [ + { ...AUTHORED_PROSE, extra: 'unsupported' }, + { ...AUTHORED_PROSE, en: { ...AUTHORED_PROSE.en, compatibility: ' ' } }, + { ...AUTHORED_PROSE, zh: { ...AUTHORED_PROSE.zh, verification: 'TODO: record validation evidence.' } }, + { ...AUTHORED_PROSE, zh: { ...AUTHORED_PROSE.zh, verification: '```text\nUnpaired code.\n```' } }, + ]) { + writeFileSync(prose, JSON.stringify(value)) + expect(() => runPersistenceChanges(['--update', NEXT_ID, '--decision', 'same-version', '--prose', prose], root, () => after)).toThrow() + expect(readFileSync(path, 'utf8')).toBe(before) + } + }) + + it('executes the real CLI against a source-only temporary checkout without Git history', () => { + const root = fixture() + const physical = join(root, 'packages/session/session-persistence-jsonl/src') + mkdirSync(physical, { recursive: true }) + writeFileSync(join(physical, 'format.ts'), "interface HeaderLine { type: 'session'; version: number; id: string; delegationDepth: number }; export {}\n") + const session = join(root, 'packages/core/session/src') + mkdirSync(session, { recursive: true }) + writeFileSync(join(session, '../package.json'), JSON.stringify({ name: '@deepseek-ai/dsh-session' })) + writeFileSync(join(root, 'tsconfig.host.json'), JSON.stringify({ compilerOptions: { + target: 'ESNext', module: 'ESNext', moduleResolution: 'Bundler', types: [], skipLibCheck: true, + paths: { '@deepseek-ai/dsh-session/types': ['./packages/core/session/src/types.ts'] }, + } })) + const source = [ + '/** Stored header. */', 'export interface SessionHeader { version: 3; id: string }', + '/** Stored event payloads. */', 'export interface SessionEventMap {', '/** Saved value. */', + "'example/value': { value: string }", '}', '/** Surface event names. */', "export type SurfaceEventType = 'example/value'", + '/** Persisted event names. */', 'export type SessionEventType = keyof SessionEventMap', + '/** Surface placement. */', "export type SurfaceOp = 'append'", '/** Persisted event. */', + 'export type SessionEvent =', + ' { [P in K]: { type: P; seq: number; data: SessionEventMap[P]; surfaceOp: SurfaceOp } }[K]', '', + ].join('\n') + writeFileSync(join(session, 'types.ts'), source) + const script = resolve(import.meta.dirname, 'persistence-changes.ts') + const cli = (...args: string[]): ReturnType => spawnSync(process.execPath, ['--import', import.meta.resolve('tsx/esm'), script, '--root', root, ...args], { + cwd: root, encoding: 'utf8', timeout: 120_000, + }) + const prose = proseFile(root) + const initialized = cli('--baseline', BASE_ID, '--prose', prose) + expect(initialized.error).toBeUndefined() + expect(initialized.signal).toBeNull() + expect(initialized.status, String(initialized.stderr)).toBe(0) + const accepted = cli('--check') + expect(accepted.error).toBeUndefined() + expect(accepted.signal).toBeNull() + expect(accepted.status, String(accepted.stderr)).toBe(0) + const optional = source.replace('value: string', 'value: string; label?: string') + writeFileSync(join(session, 'types.ts'), optional) + const authored = cli('--record', NEXT_ID, '--prose', prose, '--json') + expect(authored.error).toBeUndefined() + expect(authored.signal).toBeNull() + expect(authored.status, String(authored.stderr)).toBe(0) + expect(JSON.parse(String(authored.stdout)) as unknown).toMatchObject({ ok: true, operation: 'record' }) + const beforeUpdate = readFileSync(join(root, `docs/persistence-changes/${NEXT_ID}.md`), 'utf8') + writeFileSync(join(session, 'types.ts'), optional.replace('label?: string', 'label?: string; extra?: number')) + const updated = cli('--update', NEXT_ID, '--json') + expect(updated.error).toBeUndefined() + expect(updated.signal).toBeNull() + expect(updated.status, String(updated.stderr)).toBe(0) + expect(JSON.parse(String(updated.stdout)) as unknown).toMatchObject({ ok: true, operation: 'update' }) + expect(readFileSync(join(root, `docs/persistence-changes/${NEXT_ID}.md`), 'utf8').replace(/```yaml persistence-change[\s\S]*?```/u, '')) + .toBe(beforeUpdate.replace(/```yaml persistence-change[\s\S]*?```/u, '')) + const generated = extractPersistenceSchema(root) + for (const file of persistenceCatalogArtifacts(root, generated)) expect(readFileSync(join(root, file.path), 'utf8')).toBe(file.content) + verifyPersistenceChanges(root, generated) + writeFileSync(join(session, 'types.ts'), optional.replace('label?: string', 'label?: string; extra?: number').replace('value: string', 'value: number')) + commitCurrent(root, extractPersistenceSchema(root)) + const rejected = cli('--check') + expect(rejected.error).toBeUndefined() + expect(rejected.signal).toBeNull() + expect(rejected.status).toBe(1) + expect(String(rejected.stderr)).toContain('version-bump required') + const structured = cli('--check', '--json') + expect(structured.error).toBeUndefined() + expect(structured.signal).toBeNull() + expect(structured.status).toBe(1) + expect(JSON.parse(String(structured.stdout)) as unknown).toMatchObject({ + ok: false, code: 'unacknowledged-changes', changes: [expect.objectContaining({ kind: 'type-changed', requiresVersionBump: true })], + }) + }) +}) diff --git a/scripts/persistence-changes.ts b/scripts/persistence-changes.ts new file mode 100644 index 0000000000..ec3bb3573e --- /dev/null +++ b/scripts/persistence-changes.ts @@ -0,0 +1,795 @@ +/** Verify and acknowledge persistence type changes from current-tree schema history. */ + +import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs' +import { basename, join, resolve } from 'node:path' +import { parseArgs } from 'node:util' +import { JSON_SCHEMA, load } from 'js-yaml' +import { canonicalizeSchema, schemaDigest } from './persistence-schema-model.ts' +import type { CanonicalSchema, PersistenceRoot, PersistenceSchemaInventory, SchemaNode, SchemaTupleElement } from './persistence-schema-model.ts' +import { extractPersistenceSchema } from './persistence-schema.ts' +import { persistenceCatalogArtifacts } from './gen-persistence-catalog.ts' +import { renderPersistencePair } from './persistence-artifacts.ts' +import type { PersistenceArtifact } from './persistence-artifacts.ts' + +const HISTORY_DIRECTORY = 'docs/persistence-changes' +const CURRENT_SCHEMA = 'docs/persistence-schema.json' +const ID_PATTERN = /^\d{4}-\d{2}-\d{2}-[a-z0-9]+(?:-[a-z0-9]+)*$/u +const DIGEST_PATTERN = /^[a-f0-9]{64}$/u +const EXPLANATION_PLACEHOLDER = 'TODO: explain this change.' +const EVIDENCE_PLACEHOLDER = 'TODO: record validation evidence.' + +/** The author's acknowledgement of one mechanically classified transition. */ +export type PersistenceDecision = 'same-version' | 'version-bump' + +/** One root's successor; null after values preserve a deletion in its history. */ +export interface PersistenceChange { + readonly root: string + readonly previous: string | null + readonly after: string | null + readonly decision: PersistenceDecision +} + +/** A document's machine record, independent of its translated prose. */ +export interface PersistenceChangeRecord { + readonly schemaVersion: 1 + readonly id: string + readonly baseline: boolean + readonly changes: readonly PersistenceChange[] +} + +/** A parsed acknowledgement and its self-contained after schemas. */ +export interface PersistenceHistoryEntry { + readonly record: PersistenceChangeRecord + readonly snapshot: PersistenceSchemaInventory +} + +/** One detected type change, with a path that reviewers can locate. */ +export interface PersistenceTypeChange { + readonly kind: PersistenceTypeChangeKind + readonly path: string + readonly description: string + readonly requiresVersionBump: boolean +} + +const CHANGE_DESCRIPTIONS = { + 'root-added': 'root added', + 'root-removed': 'root removed', + 'root-classification-changed': 'root classification changed', + 'type-changed': 'type changed', + 'property-removed': 'property removed', + 'property-made-optional': 'property made optional', + 'property-made-required': 'property made required', + 'optional-property-added': 'optional property added', + 'required-property-added': 'required property added', + 'index-signature-changed': 'index signature changed', + 'tuple-length-changed': 'tuple length changed', + 'tuple-element-cardinality-changed': 'tuple element cardinality changed', + 'union-variants-changed': 'union variants changed', +} as const + +/** Stable structural classification independent of diagnostic prose. */ +export type PersistenceTypeChangeKind = keyof typeof CHANGE_DESCRIPTIONS + +/** Author-supplied paragraphs used to complete one language of a change record. */ +export interface PersistenceChangeProse { + readonly summary: string + readonly compatibility: string + readonly verification: string +} + +/** Explicit bilingual prose; the CLI supplies no compatibility or validation claims. */ +export interface PersistenceChangeProsePair { + readonly en: PersistenceChangeProse + readonly zh: PersistenceChangeProse +} + +interface ReportedChange extends PersistenceTypeChange { + readonly root: string +} + +interface RootTransition { + readonly root: string + readonly kind: PersistenceRoot['kind'] + readonly before: string | null + readonly after: string | null +} + +class PersistenceChangeFailure extends Error { + constructor( + message: string, readonly code: string, readonly changes: readonly ReportedChange[] = [], + readonly roots: readonly RootTransition[] = [], + ) { + super(message) + } +} + +interface CommandResult { + readonly schemaVersion: 1 + readonly ok: boolean + readonly operation: 'check' | 'baseline' | 'record' | 'update' + readonly message: string + readonly changes: readonly ReportedChange[] + readonly roots: readonly RootTransition[] + readonly files: readonly string[] + readonly recordId?: string + readonly code?: string +} + +interface Tip { + readonly id: string + readonly root: PersistenceRoot | null +} + +/** Verified per-root history tips; historical schemas need not match the current tree. */ +export interface PersistenceHistory { + readonly entries: readonly PersistenceHistoryEntry[] + readonly tips: ReadonlyMap +} + +function record(value: unknown, label: string): Record { + if (value === null || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be an object`) + return value as Record +} + +function keys(value: Record, expected: readonly string[], label: string, optional: readonly string[] = []): void { + const missing = expected.find(key => !Object.hasOwn(value, key)) + const unexpected = Object.keys(value).find(key => !expected.includes(key) && !optional.includes(key)) + if (missing !== undefined || unexpected !== undefined) throw new Error(`${label}: ${missing === undefined ? `unknown field ${unexpected}` : `missing field ${missing}`}`) +} + +function array(value: unknown, label: string): unknown[] { + if (!Array.isArray(value)) throw new Error(`${label} must be an array`) + return value +} + +function textValue(value: unknown, label: string): string { + if (typeof value !== 'string' || value.length === 0) throw new Error(`${label} must be a non-empty string`) + return value +} + +function bool(value: unknown, label: string): boolean { + if (typeof value !== 'boolean') throw new Error(`${label} must be boolean`) + return value +} + +function identifier(value: unknown, label: string): string { + const id = textValue(value, label) + if (!ID_PATTERN.test(id)) throw new Error(`${label} must be YYYY-MM-DD-slug`) + return id +} + +function digest(value: unknown, label: string): string { + const result = textValue(value, label) + if (!DIGEST_PATTERN.test(result)) throw new Error(`${label} must be a SHA-256 digest`) + return result +} + +function reference(value: unknown, count: number, label: string): number { + if (!Number.isSafeInteger(value) || (value as number) < 0 || (value as number) >= count) throw new Error(`${label} references an unknown schema node`) + return value as number +} + +function parseSchema(value: unknown, label: string): CanonicalSchema { + const input = record(value, label) + keys(input, ['root', 'nodes'], label) + if (input.root !== 0) throw new Error(`${label}.root must be zero`) + const nodes = array(input.nodes, `${label}.nodes`) + if (nodes.length === 0) throw new Error(`${label}.nodes must not be empty`) + const ref = (value: unknown): number => reference(value, nodes.length, label) + for (const [index, raw] of nodes.entries()) { + const node = record(raw, `${label}.nodes[${index}]`) + switch (node.kind) { + case 'primitive': + keys(node, ['kind', 'type'], label) + if (!['null', 'boolean', 'number', 'string', 'never'].includes(String(node.type))) throw new Error(`${label}: invalid primitive`) + break + case 'literal': + keys(node, ['kind', 'value'], label) + if (!['string', 'boolean', 'number'].includes(typeof node.value) + || typeof node.value === 'number' && !Number.isFinite(node.value)) throw new Error(`${label}: invalid literal`) + break + case 'opaque': + keys(node, ['kind', 'reason'], label) + if (!['any', 'unknown'].includes(String(node.reason))) throw new Error(`${label}: invalid opaque reason`) + break + case 'array': + keys(node, ['kind', 'element'], label) + ref(node.element) + break + case 'tuple': + keys(node, ['kind', 'elements'], label) + for (const rawElement of array(node.elements, label)) { + const element = record(rawElement, label) + keys(element, ['type', 'optional', 'rest'], label) + ref(element.type) + bool(element.optional, label) + bool(element.rest, label) + } + break + case 'object': { + keys(node, ['kind', 'properties', 'indices'], label) + const names = new Set() + for (const rawProperty of array(node.properties, label)) { + const property = record(rawProperty, label) + keys(property, ['name', 'type', 'optional'], label) + if (typeof property.name !== 'string') throw new Error(`${label}: property name must be a string`) + const name = property.name + if (names.has(name)) throw new Error(`${label}: duplicate property ${name}`) + names.add(name) + ref(property.type) + bool(property.optional, label) + } + for (const rawIndex of array(node.indices, label)) { + const entry = record(rawIndex, label) + keys(entry, ['key', 'value'], label) + ref(entry.key) + ref(entry.value) + } + break + } + case 'union': + keys(node, ['kind', 'types'], label) + if (array(node.types, label).length === 0) throw new Error(`${label}: empty union`) + for (const item of node.types as unknown[]) ref(item) + break + default: + throw new Error(`${label}: unknown schema node kind ${String(node.kind)}`) + } + } + const schema = input as unknown as CanonicalSchema + const canonical = canonicalizeSchema(schema.nodes, schema.root) + if (JSON.stringify(canonical) !== JSON.stringify(schema)) throw new Error(`${label}: schema is not canonical`) + return schema +} + +/** Parse a persisted schema inventory, rejecting malformed graphs and digest drift. + * @param value - JSON read from a current or historical schema file. + * @returns the validated inventory. + */ +export function parsePersistenceSnapshot(value: unknown): PersistenceSchemaInventory { + const input = record(value, 'schema inventory') + keys(input, ['formatVersion', 'roots', 'types'], 'schema inventory') + if (input.formatVersion !== 1) throw new Error('unsupported persistence schema normalization version') + const names = new Set() + for (const rawRoot of array(input.roots, 'schema roots')) { + const root = record(rawRoot, 'schema root') + keys(root, ['key', 'kind', 'digest', 'schema'], 'schema root', ['event', 'surface']) + const key = textValue(root.key, 'root key') + if (names.has(key)) throw new Error(`duplicate schema root ${key}`) + names.add(key) + if (root.kind === 'event') { + if (key !== `event:${textValue(root.event, 'event name')}`) throw new Error(`invalid event root key ${key}`) + bool(root.surface, 'event surface membership') + } else if ((root.kind !== 'header' || !['SessionHeader', 'JsonlHeaderLine'].includes(key)) + && (root.kind !== 'envelope' || key !== 'SessionEventEnvelope')) throw new Error(`invalid schema root ${key}`) + if (root.kind !== 'event' && (root.event !== undefined || root.surface !== undefined)) throw new Error(`${key}: non-event metadata`) + const schema = parseSchema(root.schema, key) + if (root.kind === 'event') validateEventMetadata(schema, String(root.event), root.surface === true) + if (digest(root.digest, `${key} digest`) !== schemaDigest(schema)) throw new Error(`${key}: schema digest mismatch`) + } + for (const rawType of array(input.types, 'schema types')) { + const type = record(rawType, 'schema type') + keys(type, ['digest', 'schema', 'names', 'sources'], 'schema type') + const schema = parseSchema(type.schema, 'shared schema') + if (digest(type.digest, 'shared digest') !== schemaDigest(schema)) throw new Error('shared schema digest mismatch') + for (const name of array(type.names, 'type names')) textValue(name, 'type name') + for (const source of array(type.sources, 'type sources')) textValue(source, 'type source') + } + return input as unknown as PersistenceSchemaInventory +} + +function validateEventMetadata(schema: CanonicalSchema, event: string, surface: boolean): void { + const pending = [0] + const visited = new Set() + while (pending.length > 0) { + const index = pending.pop() as number + if (visited.has(index)) continue + visited.add(index) + const node = schema.nodes[index] as SchemaNode + if (node.kind === 'union') { pending.push(...node.types); continue } + if (node.kind !== 'object') throw new Error(`${event}: event schema must be an object`) + const tag = node.properties.find(property => property.name === 'type') + const value = tag === undefined ? undefined : schema.nodes[tag.type] + if (tag?.optional !== false || value?.kind !== 'literal' || value.value !== event) throw new Error(`${event}: event schema type does not match its root`) + const operation = node.properties.find(property => property.name === 'surfaceOp') + if (surface ? operation?.optional !== false : operation !== undefined) throw new Error(`${event}: surface metadata does not match its schema`) + } +} + +function subDigest(schema: CanonicalSchema, node: number): string { + return schemaDigest(canonicalizeSchema(schema.nodes, node)) +} + +function matchUnionVariants(candidates: readonly (readonly number[])[]): number[] | undefined { + const owners = new Map() + function assign(previous: number, visited: Set): boolean { + for (const next of candidates[previous] ?? []) { + if (visited.has(next)) continue + visited.add(next) + const owner = owners.get(next) + if (owner === undefined || assign(owner, visited)) { + owners.set(next, previous) + return true + } + } + return false + } + for (let previous = 0; previous < candidates.length; previous += 1) { + if (!assign(previous, new Set())) return undefined + } + const matches: number[] = [] + for (const [next, previous] of owners) matches[previous] = next + return matches +} + +/** Classify structural differences; only optional payload properties and ordinary event additions are additive. + * @param before - predecessor root, or absence for an addition. + * @param after - successor root, or absence for deletion. + * @returns concrete changes and their format-bump requirement. + */ +export function classifyPersistenceChange(before: PersistenceRoot | null, after: PersistenceRoot | null): PersistenceTypeChange[] { + if (before === null) { + return after === null ? [] : [{ path: after.key, kind: 'root-added', description: 'root added', + requiresVersionBump: after.kind !== 'event' || after.surface !== false }] + } + if (after === null) return [{ path: before.key, kind: 'root-removed', description: 'root removed', requiresVersionBump: true }] + const key = after.key + const oldRoot = before + const newRoot = after + const describe = (path: string, kind: PersistenceTypeChangeKind, requiresVersionBump = true): PersistenceTypeChange => + ({ path, kind, description: CHANGE_DESCRIPTIONS[kind], requiresVersionBump }) + const changes: PersistenceTypeChange[] = [] + if (before.kind !== after.kind || before.surface !== after.surface) changes.push(describe(key, 'root-classification-changed')) + if (before.digest === after.digest) return changes + const fingerprints = [new Map(), new Map()] as const + const fingerprint = (schema: CanonicalSchema, index: number, side: 0 | 1): string => { + let result = fingerprints[side].get(index) + if (result === undefined) { result = subDigest(schema, index); fingerprints[side].set(index, result) } + return result + } + type Scope = 'event' | 'body' | 'strict' + function compare( + oldIndex: number, newIndex: number, path: string, scope: Scope, ancestors: ReadonlySet, + ): PersistenceTypeChange[] { + if (fingerprint(oldRoot.schema, oldIndex, 0) === fingerprint(newRoot.schema, newIndex, 1)) return [] + const pair = `${oldIndex}:${newIndex}:${scope}` + if (ancestors.has(pair)) return [] + // Recursive pairs are assumptions for this candidate only. A failed sibling + // or unmatched union arm cannot leave a cached success for another candidate. + const active = new Set(ancestors).add(pair) + const differences: PersistenceTypeChange[] = [] + const add = (path: string, kind: PersistenceTypeChangeKind, requiresVersionBump = true): void => { + differences.push(describe(path, kind, requiresVersionBump)) + } + const descend = (oldType: number, newType: number, child: string, childScope: Scope): void => { + differences.push(...compare(oldType, newType, child, childScope, active)) + } + const oldNode = oldRoot.schema.nodes[oldIndex] as SchemaNode + const newNode = newRoot.schema.nodes[newIndex] as SchemaNode + if (oldNode.kind !== newNode.kind) return [describe(path, 'type-changed')] + if (oldNode.kind === 'object' && newNode.kind === 'object') { + const oldProps = new Map(oldNode.properties.map(property => [property.name, property])) + const newProps = new Map(newNode.properties.map(property => [property.name, property])) + for (const [name, property] of oldProps) { + const next = newProps.get(name) + const child = `${path}.${name}` + if (next === undefined) { add(child, 'property-removed'); continue } + if (property.optional !== next.optional) add(child, next.optional ? 'property-made-optional' : 'property-made-required', scope !== 'body' || !next.optional) + descend(property.type, next.type, child, scope === 'body' || scope === 'event' && name === 'data' ? 'body' : 'strict') + } + for (const [name, property] of newProps) { + if (!oldProps.has(name)) add(`${path}.${name}`, property.optional ? 'optional-property-added' : 'required-property-added', scope !== 'body' || !property.optional) + } + const oldIndices = new Map(oldNode.indices.map(entry => [fingerprint(oldRoot.schema, entry.key, 0), entry])) + const newIndices = new Map(newNode.indices.map(entry => [fingerprint(newRoot.schema, entry.key, 1), entry])) + if (oldIndices.size !== newIndices.size || [...oldIndices.keys()].some(index => !newIndices.has(index))) add(path, 'index-signature-changed') + for (const [index, entry] of oldIndices) { + const next = newIndices.get(index) + if (next !== undefined) descend(entry.value, next.value, `${path}[*]`, scope === 'body' ? 'body' : 'strict') + } + return differences + } + if (oldNode.kind === 'array' && newNode.kind === 'array') { + return compare(oldNode.element, newNode.element, `${path}[]`, scope === 'body' ? 'body' : 'strict', active) + } + if (oldNode.kind === 'tuple' && newNode.kind === 'tuple') { + if (oldNode.elements.length !== newNode.elements.length) return [describe(path, 'tuple-length-changed')] + for (const [index, element] of oldNode.elements.entries()) { + const next = newNode.elements[index] as SchemaTupleElement + if (element.optional !== next.optional || element.rest !== next.rest) add(`${path}[${index}]`, 'tuple-element-cardinality-changed') + descend(element.type, next.type, `${path}[${index}]`, scope === 'body' ? 'body' : 'strict') + } + return differences + } + if (oldNode.kind === 'union' && newNode.kind === 'union') { + if (oldNode.types.length !== newNode.types.length) return [describe(path, 'union-variants-changed')] + const candidates = oldNode.types.map(oldType => newNode.types.map(newType => compare(oldType, newType, path, scope, active))) + const matching = matchUnionVariants(candidates.map(row => row.flatMap((candidate, index) => + candidate.every(change => !change.requiresVersionBump) ? [index] : []))) + if (matching !== undefined) return matching.flatMap((next, previous) => candidates[previous]?.[next] ?? []) + const oldTypes = new Map(oldNode.types.map(index => [fingerprint(oldRoot.schema, index, 0), index])) + const newTypes = new Map(newNode.types.map(index => [fingerprint(newRoot.schema, index, 1), index])) + const removed = [...oldTypes].filter(([hash]) => !newTypes.has(hash)).map(([, index]) => index) + const added = [...newTypes].filter(([hash]) => !oldTypes.has(hash)).map(([, index]) => index) + if (removed.length === 1 && added.length === 1) return compare(removed[0] as number, added[0] as number, path, scope, active) + return [describe(path, 'union-variants-changed')] + } + return [describe(path, 'type-changed')] + } + changes.push(...compare(0, 0, key, before.kind === 'event' ? 'event' : 'strict', new Set())) + if (changes.length === 0) changes.push(describe(key, 'type-changed')) + return [...new Map(changes.map(change => [JSON.stringify([change.path, change.kind, change.requiresVersionBump]), change])).values()] +} + +function parseDocument(source: string, filename: string, allowIncomplete = false): PersistenceChangeRecord { + const frontmatter = /^---\n([\s\S]*?)\n---\n/u.exec(source) + if (frontmatter === null || record(load(frontmatter[1] as string, { schema: JSON_SCHEMA }), filename).kind !== 'persistence-change') throw new Error(`${filename}: kind must be persistence-change`) + const openings = [...source.matchAll(/^```yaml persistence-change\s*$/gmu)] + const block = /^```yaml persistence-change[^\S\n]*\n([\s\S]*?)^```[^\S\n]*$/mu.exec(source) + if (openings.length !== 1 || block === null) throw new Error(`${filename}: expected exactly one persistence-change block`) + const input = record(load(block[1] as string, { schema: JSON_SCHEMA }), filename) + keys(input, ['schemaVersion', 'id', 'baseline', 'changes'], filename) + if (input.schemaVersion !== 1) throw new Error(`${filename}: unsupported acknowledgement schema version`) + const id = identifier(input.id, filename) + if (basename(filename) !== `${id}.md`) throw new Error(`${filename}: record id does not match filename`) + bool(input.baseline, filename) + const roots = new Set() + for (const value of array(input.changes, `${filename} changes`)) { + const change = record(value, filename) + keys(change, ['root', 'previous', 'after', 'decision'], filename) + const root = textValue(change.root, 'changed root') + if (roots.has(root)) throw new Error(`${filename}: duplicate change for ${root}`) + roots.add(root) + if (change.previous !== null) identifier(change.previous, 'previous record') + if (change.after !== null) digest(change.after, 'after digest') + if (change.decision !== 'same-version' && change.decision !== 'version-bump') throw new Error(`${filename}: invalid compatibility decision`) + } + if (roots.size === 0) throw new Error(`${filename}: changes must not be empty`) + if (!allowIncomplete && (source.includes(EXPLANATION_PLACEHOLDER) || source.includes(EVIDENCE_PLACEHOLDER))) throw new Error(`${filename}: complete compatibility and verification prose`) + return input as unknown as PersistenceChangeRecord +} + +function headerVersion(root: PersistenceRoot | null): number | undefined { + if (root === null) return undefined + const node = root.schema.nodes[0] + if (node?.kind !== 'object') return undefined + const property = node.properties.find(item => item.name === 'version') + if (property === undefined) return undefined + const version = root.schema.nodes[property.type] + return version?.kind === 'literal' && typeof version.value === 'number' && Number.isSafeInteger(version.value) ? version.value : undefined +} + +/** Check every historical transition and return each root's unique current tip. + * @param entries - parsed documents and their self-contained schema snapshots. + * @returns validated history and tips, without consulting Git or current source. + */ +export function validatePersistenceHistory(entries: readonly PersistenceHistoryEntry[]): PersistenceHistory { + if (entries.filter(entry => entry.record.baseline).length !== 1) throw new Error('persistence history requires exactly one baseline') + const records = new Map() + for (const entry of entries) { + if (records.has(entry.record.id)) throw new Error(`duplicate persistence record ${entry.record.id}`) + records.set(entry.record.id, entry) + const expected = entry.record.changes.filter(change => change.after !== null).map(change => change.root).sort() + if (JSON.stringify(expected) !== JSON.stringify(entry.snapshot.roots.map(root => root.key).sort())) throw new Error(`${entry.record.id}: snapshot roots do not match acknowledged after schemas`) + for (const change of entry.record.changes) { + const root = entry.snapshot.roots.find(root => root.key === change.root) + if ((root?.digest ?? null) !== change.after) throw new Error(`${entry.record.id}: after digest mismatch for ${change.root}`) + if (entry.record.baseline && (change.previous !== null || change.after === null || change.decision !== 'same-version')) throw new Error(`${entry.record.id}: invalid baseline transition`) + } + } + const states = new Map() + const successors = new Map() + const nodes = new Map() + const tips = new Map() + const nodeKey = (id: string | null, root: string): string => JSON.stringify([id, root]) + for (const entry of entries) for (const change of entry.record.changes) { + const parentKey = nodeKey(change.previous, change.root) + if (successors.has(parentKey)) throw new Error(`forked persistence history for ${change.root}: ${successors.get(parentKey)} and ${entry.record.id}`) + successors.set(parentKey, entry.record.id) + nodes.set(nodeKey(entry.record.id, change.root), { entry, change }) + } + function visit(id: string, root: string): PersistenceRoot | null { + const key = nodeKey(id, root) + const found = nodes.get(key) + if (found === undefined) throw new Error(`missing predecessor ${id} for ${root}`) + if (states.get(key) === 'visiting') throw new Error(`cycle in persistence history for ${root}`) + const after = found.entry.snapshot.roots.find(item => item.key === root) ?? null + if (states.get(key) === 'visited') return after + states.set(key, 'visiting') + const before = found.change.previous === null ? null : visit(found.change.previous, root) + if (!found.entry.record.baseline) { + const differences = classifyPersistenceChange(before, after) + if (differences.length === 0) throw new Error(`${id}: unchanged acknowledgement for ${root}`) + if (differences.some(change => change.requiresVersionBump) && found.change.decision !== 'version-bump') { + throw new PersistenceChangeFailure( + `${id}: ${root} requires a format version bump (${differences.filter(change => change.requiresVersionBump).map(change => change.path + ': ' + change.description).join('; ')})`, + 'version-bump-required', differences.map(change => ({ root, ...change })), [rootTransition(before, after)], + ) + } + if (found.change.decision === 'version-bump') { + const header = found.entry.record.changes.find(change => change.root === 'SessionHeader') + const oldHeader = header?.previous === null || header === undefined ? null : visit(header.previous, 'SessionHeader') + const from = headerVersion(oldHeader) + const to = headerVersion(found.entry.snapshot.roots.find(item => item.key === 'SessionHeader') ?? null) + if (from === undefined || to === undefined || to <= from) { + throw new PersistenceChangeFailure(`${id}: version-bump requires this record's own increasing SessionHeader.version transition`, + 'version-transition-required', differences.map(change => ({ root, ...change })), [rootTransition(before, after)]) + } + } + } + states.set(key, 'visited') + if (!successors.has(key)) tips.set(root, { id, root: after }) + return after + } + for (const entry of entries) for (const change of entry.record.changes) visit(entry.record.id, change.root) + const baseline = entries.find(entry => entry.record.baseline) as PersistenceHistoryEntry + if (!baseline.snapshot.roots.some(root => root.key === 'SessionHeader') + || !baseline.snapshot.roots.some(root => root.key === 'SessionEventEnvelope') + || !baseline.snapshot.roots.some(root => root.key === 'JsonlHeaderLine')) { + throw new Error('baseline requires SessionHeader, JsonlHeaderLine, and SessionEventEnvelope roots') + } + return { entries, tips } +} + +/** Read and validate all current-tree persistence acknowledgement files. + * @param root - checkout or isolated fixture root. + * @returns checked history without comparing its tips to current source. + */ +export function loadPersistenceHistory(root: string): PersistenceHistory { + return validatePersistenceHistory(readPersistenceEntries(root)) +} + +function readPersistenceEntries(root: string, allowIncompleteId?: string): PersistenceHistoryEntry[] { + const directory = join(root, HISTORY_DIRECTORY) + if (!existsSync(directory)) throw new Error('persistence history is missing; use pnpm run persistence-changes --baseline ID for explicit initialization') + const files = readdirSync(directory).sort() + const documents = files.filter(file => file.endsWith('.md') && !file.endsWith('.zh.md') && file !== 'README.md' && file !== 'AGENTS.md') + const snapshots = new Set(files.filter(file => file.endsWith('.schema.json'))) + const entries = documents.map((filename) => { + const source = readFileSync(join(directory, filename), 'utf8').replaceAll('\r\n', '\n') + const allowIncomplete = filename === `${allowIncompleteId}.md` + const change = parseDocument(source, filename, allowIncomplete) + const snapshotName = `${change.id}.schema.json` + if (!snapshots.delete(snapshotName)) throw new Error(`${filename}: missing schema snapshot ${snapshotName}`) + const snapshot = parsePersistenceSnapshot(JSON.parse(readFileSync(join(directory, snapshotName), 'utf8'))) + const translatedName = `${change.id}.zh.md` + if (!files.includes(translatedName)) throw new Error(`${filename}: missing Chinese counterpart`) + const translated = readFileSync(join(directory, translatedName), 'utf8').replaceAll('\r\n', '\n') + const englishBlock = source.match(/^```yaml persistence-change[^\S\n]*\n([\s\S]*?)^```[^\S\n]*$/mu)?.[1] + const chineseBlocks = [...translated.matchAll(/^```yaml persistence-change[^\S\n]*\n([\s\S]*?)^```[^\S\n]*$/gmu)] + if (chineseBlocks.length !== 1 || chineseBlocks[0]?.[1] !== englishBlock) throw new Error(`${filename}: bilingual machine records differ`) + if (!allowIncomplete && (translated.includes(EXPLANATION_PLACEHOLDER) || translated.includes(EVIDENCE_PLACEHOLDER))) throw new Error(`${translatedName}: complete compatibility and verification prose`) + return { record: change, snapshot } + }) + if (snapshots.size !== 0) throw new Error(`unreferenced persistence schema snapshot: ${[...snapshots].join(', ')}`) + return entries +} + +function currentDifferences(history: PersistenceHistory, current: PersistenceSchemaInventory): string[] { + const roots = new Map(current.roots.map(root => [root.key, root])) + const differences: string[] = [] + for (const key of new Set([...history.tips.keys(), ...roots.keys()])) { + if (classifyPersistenceChange(history.tips.get(key)?.root ?? null, roots.get(key) ?? null).length !== 0) differences.push(key) + } + return differences.sort() +} + +/** Verify current generated output and acknowledgement tips together. + * @param root - checkout or isolated fixture root. + * @param current - freshly extracted current-source inventory. + * @returns verified history. + */ +export function verifyPersistenceChanges(root: string, current: PersistenceSchemaInventory): PersistenceHistory { + const history = loadPersistenceHistory(root) + const differences = reportedDifferences(history, current) + const transitions = rootTransitions(history, current) + const committedPath = join(root, CURRENT_SCHEMA) + if (!existsSync(committedPath)) { + throw new PersistenceChangeFailure(`${CURRENT_SCHEMA} is missing; regenerate the persistence catalog`, 'generated-artifact-missing', differences, transitions) + } + const committed = parsePersistenceSnapshot(JSON.parse(readFileSync(committedPath, 'utf8'))) + if (JSON.stringify(committed) !== JSON.stringify(current)) { + throw new PersistenceChangeFailure(`${CURRENT_SCHEMA} is stale; regenerate the persistence catalog`, 'stale-artifacts', differences, transitions) + } + if (differences.length !== 0) { + const details = differences.map(change => ` ${change.path}: ${change.description} (${change.requiresVersionBump ? 'version-bump required' : 'same-version allowed'})`) + throw new PersistenceChangeFailure(`unacknowledged persistence type changes:\n${details.join('\n')}`, 'unacknowledged-changes', differences, transitions) + } + return history +} + +function rootTransition(before: PersistenceRoot | null, after: PersistenceRoot | null): RootTransition { + const root = (after ?? before) as PersistenceRoot + return { root: root.key, kind: root.kind, before: before?.digest ?? null, after: after?.digest ?? null } +} + +function rootTransitions(history: PersistenceHistory | undefined, current: PersistenceSchemaInventory): RootTransition[] { + const changed = history === undefined ? current.roots.map(root => root.key) : currentDifferences(history, current) + return changed.map(root => rootTransition(history?.tips.get(root)?.root ?? null, current.roots.find(item => item.key === root) ?? null)) +} + +function reportedDifferences(history: PersistenceHistory, current: PersistenceSchemaInventory): ReportedChange[] { + return currentDifferences(history, current).flatMap(root => classifyPersistenceChange( + history.tips.get(root)?.root ?? null, current.roots.find(item => item.key === root) ?? null, + ).map(change => ({ root, ...change }))) +} + +function machineBlock(change: PersistenceChangeRecord): string { + return ['```yaml persistence-change', 'schemaVersion: 1', `id: ${change.id}`, `baseline: ${String(change.baseline)}`, 'changes:', + ...change.changes.flatMap(item => [` - root: ${JSON.stringify(item.root)}`, ` previous: ${item.previous === null ? 'null' : JSON.stringify(item.previous)}`, ` after: ${item.after === null ? 'null' : JSON.stringify(item.after)}`, ` decision: ${item.decision}`]), '```'].join('\n') +} + +function scaffold(change: PersistenceChangeRecord, chinese: boolean, prose?: PersistenceChangeProse): string { + const summary = chinese ? '概述' : 'Summary' + const compatibility = chinese ? '兼容性' : 'Compatibility' + const verification = chinese ? '验证' : 'Verification' + return ['---', `description: ${JSON.stringify(chinese ? '记录持久化类型更改及其兼容性确认。' : 'Records a persistence type transition and its compatibility acknowledgement.')}`, 'kind: persistence-change', '---', '', + `# ${change.id}`, '', chinese ? `[English](${change.id}.md) | 中文` : `English | [中文](${change.id}.zh.md)`, '', + `## ${summary}`, '', prose?.summary ?? EXPLANATION_PLACEHOLDER, '', '## ' + (chinese ? '目录' : 'Table of Contents'), '', + `- [${chinese ? '声明' : 'Declaration'}](#declaration)`, `- [${compatibility}](#compatibility)`, `- [${verification}](#verification)`, `- [${chinese ? '开发备注' : 'Dev Note'}](#dev-note)`, '', + '', `## ${chinese ? '声明' : 'Declaration'}`, '', machineBlock(change), '', + '', `## ${compatibility}`, '', prose?.compatibility ?? EXPLANATION_PLACEHOLDER, '', + '', `## ${verification}`, '', prose?.verification ?? EVIDENCE_PLACEHOLDER, '', '', `## ${chinese ? '开发备注' : 'Dev Note'}`, '', chinese ? '无。' : 'None.', ''].join('\n') +} + +/** Parse explicit authored prose without supplying compatibility or validation claims. + * @param value - decoded JSON supplied through --prose. + * @returns complete English and Chinese section text. + */ +export function parsePersistenceProse(value: unknown): PersistenceChangeProsePair { + const pair = record(value, 'persistence prose') + keys(pair, ['en', 'zh'], 'persistence prose') + for (const locale of ['en', 'zh']) { + const sections = record(pair[locale], `persistence prose ${locale}`) + keys(sections, ['summary', 'compatibility', 'verification'], `persistence prose ${locale}`) + for (const [name, value] of Object.entries(sections)) { + const text = textValue(value, `${locale}.${name}`) + if (text.trim().length === 0 || text.includes(EXPLANATION_PLACEHOLDER) || text.includes(EVIDENCE_PLACEHOLDER)) { + throw new Error(`${locale}.${name} requires authored prose without scaffold placeholders`) + } + } + } + return pair as unknown as PersistenceChangeProsePair +} + +function updateDocument(source: string, change: PersistenceChangeRecord, chinese: boolean, prose?: PersistenceChangeProse): string { + source = source.replace(/^```yaml persistence-change[^\S\n]*\n[\s\S]*?^```[^\S\n]*$/mu, machineBlock(change)) + if (prose === undefined) return source + const headings = chinese ? ['概述', '兼容性', '验证'] : ['Summary', 'Compatibility', 'Verification'] + for (const [index, text] of [prose.summary, prose.compatibility, prose.verification].entries()) { + const lines = source.split('\n') + const heading = `## ${headings[index]}` + const start = lines.indexOf(heading) + if (start < 0 || lines.lastIndexOf(heading) !== start) throw new Error(`--prose requires one ${heading} section in the existing record`) + let end = lines.findIndex((line, lineIndex) => lineIndex > start && /^##? /u.test(line)) + if (end < 0) end = lines.length + let anchor = end + while (anchor > start + 1 && lines[anchor - 1] === '') anchor -= 1 + if (/^<\/a>$/u.test(lines[anchor - 1] ?? '')) end = anchor - 1 + lines.splice(start + 1, end - start - 1, '', text.trim(), '') + source = lines.join('\n') + } + return source +} + +function commandOperation(args: readonly string[]): CommandResult['operation'] { + if (args.some(arg => arg === '--baseline' || arg.startsWith('--baseline='))) return 'baseline' + if (args.some(arg => arg === '--record' || arg.startsWith('--record='))) return 'record' + if (args.some(arg => arg === '--update' || arg.startsWith('--update='))) return 'update' + return 'check' +} + +function executeCommand( + args: readonly string[], root: string, extract: (root: string) => PersistenceSchemaInventory, + artifacts: (root: string, current: PersistenceSchemaInventory) => readonly PersistenceArtifact[], +): CommandResult { + const { values } = parseArgs({ args: [...args], strict: true, allowPositionals: false, options: { + check: { type: 'boolean' }, baseline: { type: 'string' }, record: { type: 'string' }, update: { type: 'string' }, + decision: { type: 'string' }, root: { type: 'string' }, prose: { type: 'string' }, json: { type: 'boolean' }, + } }) + if (values.root !== undefined) root = resolve(values.root) + const selected = [values.check === true, values.baseline !== undefined, values.record !== undefined, values.update !== undefined] + if (selected.filter(Boolean).length > 1) throw new Error('choose exactly one of --check, --baseline ID, --record ID, or --update ID') + if (values.record === undefined && values.update === undefined && values.decision !== undefined) throw new Error('--decision requires --record or --update') + const operation = commandOperation(args) + if (operation === 'check' && values.prose !== undefined) throw new Error('--prose requires --baseline, --record, or --update') + const prose = values.prose === undefined ? undefined : parsePersistenceProse(JSON.parse(readFileSync(resolve(root, values.prose), 'utf8'))) + const current = parsePersistenceSnapshot(extract(root)) + if (operation === 'check') { + const history = verifyPersistenceChanges(root, current) + return { schemaVersion: 1, ok: true, operation, + message: `persistence changes: ${current.roots.length} roots match ${history.entries.length} history records.`, changes: [], roots: [], files: [] } + } + const baseline = operation === 'baseline' + const update = operation === 'update' + const id = identifier(values.baseline ?? values.record ?? values.update, 'record id') + const directory = join(root, HISTORY_DIRECTORY) + if (baseline && existsSync(directory) && readdirSync(directory).some(file => file.endsWith('.schema.json') || ID_PATTERN.test(file.replace(/\.md$/u, '')))) throw new Error('persistence baseline already exists; baseline creation cannot reset history') + if (values.decision !== undefined && values.decision !== 'same-version' && values.decision !== 'version-bump') { + throw new Error('--decision must be same-version or version-bump') + } + const entries = baseline ? [] : readPersistenceEntries(root, update ? id : undefined) + const existing = update ? entries.find(entry => entry.record.id === id) : undefined + if (update && existing === undefined) throw new Error(`${id}: cannot update a missing acknowledgement`) + if (existing?.record.baseline === true) throw new Error('cannot update the persistence baseline') + if (existing !== undefined && entries.some(entry => entry.record.changes.some(change => change.previous === id))) { + throw new Error(`${id}: cannot update an acknowledgement with successors`) + } + const prior = entries.filter(entry => entry !== existing) + const history = baseline ? undefined : validatePersistenceHistory(prior) + const changed = baseline ? current.roots.map(root => root.key) : currentDifferences(history as PersistenceHistory, current) + if (changed.length === 0) throw new Error('no persistence type changes to acknowledge') + const differences = history === undefined ? [] : reportedDifferences(history, current) + const decision = values.decision ?? (differences.some(change => change.requiresVersionBump) ? 'version-bump' : 'same-version') + const roots = current.roots.filter(root => changed.includes(root.key)) + const change: PersistenceChangeRecord = { schemaVersion: 1, id, baseline, changes: changed.sort().map(key => ({ + root: key, previous: history?.tips.get(key)?.id ?? null, + after: roots.find(root => root.key === key)?.digest ?? null, decision, + })) } + const snapshot: PersistenceSchemaInventory = { formatVersion: 1, roots, types: [] } + validatePersistenceHistory([...prior, { record: change, snapshot }]) + const document = (chinese: boolean): string => { + const supplied = chinese ? prose?.zh : prose?.en + return existing === undefined ? scaffold(change, chinese, supplied) + : updateDocument(readFileSync(join(directory, `${id}${chinese ? '.zh' : ''}.md`), 'utf8'), change, chinese, supplied) + } + const english = document(false) + const chinese = document(true) + parseDocument(english, `${id}.md`, prose === undefined && existing === undefined) + parseDocument(chinese, `${id}.md`, prose === undefined && existing === undefined) + const recordFiles = [ + ...renderPersistencePair(root, `${HISTORY_DIRECTORY}/${id}.md`, english, chinese), + { path: `${HISTORY_DIRECTORY}/${id}.schema.json`, content: JSON.stringify(snapshot, null, 2) + '\n' }, + ] + if (!update && recordFiles.some(file => existsSync(join(root, file.path)))) throw new Error(`${id}: acknowledgement file already exists`) + const outputs = [...artifacts(root, current), ...recordFiles] + for (const file of outputs) mkdirSync(resolve(root, file.path, '..'), { recursive: true }) + for (const file of outputs) writeFileSync(resolve(root, file.path), file.content, { flag: recordFiles.includes(file) && !update ? 'wx' : 'w' }) + const completion = baseline + ? 'Complete both record documents and refresh their translation pairing.' + : `Complete the compatibility and verification prose with --update ${id} --prose FILE.` + const message = existing === undefined && prose === undefined + ? `Created ${HISTORY_DIRECTORY}/${id}.md and paired schema files. ${completion}` + : `${update ? 'Updated' : 'Created'} ${HISTORY_DIRECTORY}/${id}.md; schema artifacts and bilingual pairing are current.` + return { schemaVersion: 1, ok: true, operation, recordId: id, message, + changes: differences, + roots: rootTransitions(history, current), files: outputs.map(file => file.path) } +} + +/** Execute tree-only verification or author an explicit persistence acknowledgement. + * @param args - check, baseline, record, or update arguments; --json selects structured output. + * @param root - checkout root; defaults to this script's repository. + * @param extract - current-source extraction function; fixtures supply their own source reader. + * @param artifacts - renderer for current generated artifacts; fixtures may isolate the inventory artifact. + * @returns text or one JSON result; JSON failures retain ok:false for the process entry point. + */ +export function runPersistenceChanges( + args: readonly string[], + root: string = resolve(import.meta.dirname, '..'), + extract: (root: string) => PersistenceSchemaInventory = extractPersistenceSchema, + artifacts: (root: string, current: PersistenceSchemaInventory) => readonly PersistenceArtifact[] = persistenceCatalogArtifacts, +): string { + try { + const result = executeCommand(args, root, extract, artifacts) + return args.includes('--json') ? JSON.stringify(result) : result.message + } catch (error: unknown) { + if (!args.includes('--json')) throw error + const result: CommandResult = { schemaVersion: 1, ok: false, operation: commandOperation(args), + message: error instanceof Error ? error.message : String(error), + code: error instanceof PersistenceChangeFailure ? error.code : 'verification-failed', + changes: error instanceof PersistenceChangeFailure ? error.changes : [], + roots: error instanceof PersistenceChangeFailure ? error.roots : [], files: [] } + return JSON.stringify(result) + } +} + +if (process.argv[1] !== undefined && resolve(process.argv[1]) === import.meta.filename) { + try { + const output = runPersistenceChanges(process.argv.slice(2)) + console.log(output) + if (process.argv.includes('--json') && !(JSON.parse(output) as { ok: boolean }).ok) process.exitCode = 1 + } catch (error: unknown) { + console.error(error instanceof Error ? error.message : String(error)) + process.exitCode = 1 + } +} diff --git a/scripts/persistence-schema-model.spec.ts b/scripts/persistence-schema-model.spec.ts new file mode 100644 index 0000000000..2750650543 --- /dev/null +++ b/scripts/persistence-schema-model.spec.ts @@ -0,0 +1,89 @@ +import { describe, expect, it } from 'vitest' +import { canonicalizeSchema, isArbitraryJsonSchema, schemaDigest, type SchemaNode } from './persistence-schema-model.ts' + +const string: SchemaNode = { kind: 'primitive', type: 'string' } +const number: SchemaNode = { kind: 'primitive', type: 'number' } + +describe('canonical persisted type graphs', () => { + it('ignores source ordering and shared versus duplicated subtypes', () => { + const left: SchemaNode[] = [ + { kind: 'object', properties: [{ name: 'b', type: 1, optional: false }, { name: 'a', type: 1, optional: true }], indices: [] }, string, + ] + const right: SchemaNode[] = [ + string, string, + { kind: 'object', properties: [{ name: 'a', type: 0, optional: true }, { name: 'b', type: 1, optional: false }], indices: [] }, + ] + expect(canonicalizeSchema(left, 0)).toEqual(canonicalizeSchema(right, 2)) + }) + + it('minimizes differently factored recursive aliases to the same graph', () => { + const self: SchemaNode[] = [ + { kind: 'object', properties: [{ name: 'next', type: 0, optional: true }, { name: 'value', type: 1, optional: false }], indices: [] }, string, + ] + const mutual: SchemaNode[] = [ + { kind: 'object', properties: [{ name: 'value', type: 2, optional: false }, { name: 'next', type: 1, optional: true }], indices: [] }, + { kind: 'object', properties: [{ name: 'next', type: 0, optional: true }, { name: 'value', type: 3, optional: false }], indices: [] }, string, string, + ] + const normalized = canonicalizeSchema(self, 0) + expect(canonicalizeSchema(mutual, 0)).toEqual(normalized) + expect(canonicalizeSchema(normalized.nodes, 0)).toEqual(normalized) + mutual[3] = number + expect(schemaDigest(canonicalizeSchema(mutual, 0))).not.toBe(schemaDigest(normalized)) + }) + + it('flattens reordered unions and removes structurally equal alternatives', () => { + const flat: SchemaNode[] = [{ kind: 'union', types: [1, 2] }, string, number] + const factored: SchemaNode[] = [string, number, { kind: 'union', types: [0, 3] }, { kind: 'union', types: [1, 0] }] + expect(canonicalizeSchema(flat, 0)).toEqual(canonicalizeSchema(factored, 2)) + expect(canonicalizeSchema([{ kind: 'union', types: [1, 2] }, string, string], 0)).toEqual(canonicalizeSchema([string], 0)) + expect(canonicalizeSchema([{ kind: 'union', types: [1, 2] }, { kind: 'literal', value: true }, { kind: 'literal', value: false }], 0)) + .toEqual(canonicalizeSchema([{ kind: 'primitive', type: 'boolean' }], 0)) + }) + + it('ignores recursive union, field, index signature and graph numbering order together', () => { + const original: SchemaNode[] = [ + { kind: 'union', types: [1, 2, 3] }, + { kind: 'object', properties: [{ name: 'label', type: 5, optional: false }, { name: 'next', type: 0, optional: true }], indices: [] }, + { kind: 'array', element: 0 }, + { kind: 'object', properties: [], indices: [{ key: 5, value: 0 }, { key: 4, value: 1 }] }, + number, + string, + ] + const reordered: SchemaNode[] = [ + string, + { kind: 'object', properties: [], indices: [{ key: 3, value: 5 }, { key: 0, value: 4 }] }, + { kind: 'array', element: 4 }, + number, + { kind: 'union', types: [1, 2, 5] }, + { kind: 'object', properties: [{ name: 'next', type: 4, optional: true }, { name: 'label', type: 0, optional: false }], indices: [] }, + ] + expect(canonicalizeSchema(reordered, 4)).toEqual(canonicalizeSchema(original, 0)) + }) + + it('preserves tuple positions, element absence, and rest elements', () => { + const baseline: SchemaNode[] = [{ kind: 'tuple', elements: [{ type: 1, optional: false, rest: false }, { type: 2, optional: true, rest: false }] }, string, number] + const reversed: SchemaNode[] = [{ kind: 'tuple', elements: [{ type: 2, optional: false, rest: false }, { type: 1, optional: true, rest: false }] }, string, number] + const rest: SchemaNode[] = [{ kind: 'tuple', elements: [{ type: 1, optional: false, rest: false }, { type: 2, optional: false, rest: true }] }, string, number] + const digest = schemaDigest(canonicalizeSchema(baseline, 0)) + expect(schemaDigest(canonicalizeSchema(reversed, 0))).not.toBe(digest) + expect(schemaDigest(canonicalizeSchema(rest, 0))).not.toBe(digest) + }) + + it('recognizes arbitrary recursive JSON structurally and retains its definition', () => { + const json: SchemaNode[] = [ + { kind: 'union', types: [1, 2, 3, 4, 5, 6] }, + { kind: 'primitive', type: 'null' }, { kind: 'primitive', type: 'boolean' }, number, string, + { kind: 'array', element: 0 }, { kind: 'object', properties: [], indices: [{ key: 4, value: 0 }] }, + ] + const schema = canonicalizeSchema(json, 0) + expect(isArbitraryJsonSchema(schema)).toBe(true) + expect(schema.nodes.some(node => node.kind === 'array' && node.element === 0)).toBe(true) + json[0] = { kind: 'union', types: [1, 2, 3, 4, 5] } + expect(isArbitraryJsonSchema(canonicalizeSchema(json, 0))).toBe(false) + }) + + it('rejects missing references and unproductive union cycles', () => { + expect(() => canonicalizeSchema([{ kind: 'array', element: 5 }], 0)).toThrow('missing node 5') + expect(() => canonicalizeSchema([{ kind: 'union', types: [0] }], 0)).toThrow('union cycle') + }) +}) diff --git a/scripts/persistence-schema-model.ts b/scripts/persistence-schema-model.ts new file mode 100644 index 0000000000..f12213b9f1 --- /dev/null +++ b/scripts/persistence-schema-model.ts @@ -0,0 +1,246 @@ +/** + * Source-independent JSON type graphs and canonical persistence fingerprints. + * Numeric references are local to one graph; names and source locations are metadata. + */ + +import { createHash } from 'node:crypto' + +/** One JSON property, with absence represented independently of its value type. */ +export interface SchemaProperty { + readonly name: string + readonly type: number + readonly optional: boolean +} + +/** One positional tuple element. */ +export interface SchemaTupleElement { + readonly type: number + readonly optional: boolean + readonly rest: boolean +} + +/** A resolved persisted type; references address nodes in the enclosing graph. */ +export type SchemaNode = + | { readonly kind: 'primitive'; readonly type: 'null' | 'boolean' | 'number' | 'string' | 'never' } + | { readonly kind: 'literal'; readonly value: string | number | boolean } + | { readonly kind: 'opaque'; readonly reason: 'any' | 'unknown' } + | { readonly kind: 'array'; readonly element: number } + | { readonly kind: 'tuple'; readonly elements: readonly SchemaTupleElement[] } + | { readonly kind: 'object'; readonly properties: readonly SchemaProperty[]; readonly indices: readonly { readonly key: number; readonly value: number }[] } + | { readonly kind: 'union'; readonly types: readonly number[] } + +/** A self-contained minimized graph with deterministic root-first node numbering. */ +export interface CanonicalSchema { + readonly root: 0 + readonly nodes: readonly SchemaNode[] +} + +/** One independently tracked persistent record vocabulary. */ +export interface PersistenceRoot { + readonly key: string + readonly kind: 'header' | 'envelope' | 'event' + readonly event?: string + readonly surface?: boolean + readonly digest: string + readonly schema: CanonicalSchema +} + +/** One reachable structural type and its non-fingerprinted source declaration metadata. */ +export interface PersistenceType { + readonly digest: string + readonly schema: CanonicalSchema + readonly names: readonly string[] + readonly sources: readonly string[] +} + +/** Complete current-source persistence inventory; version pins normalization too. */ +export interface PersistenceSchemaInventory { + readonly formatVersion: 1 + readonly roots: readonly PersistenceRoot[] + readonly types: readonly PersistenceType[] +} + +/** + * Visit direct graph edges in their normalized semantic order. + * @param node - resolved graph node. + * @returns referenced node indices, including repeated ordered edges. + */ +export function schemaChildren(node: SchemaNode): number[] { + switch (node.kind) { + case 'array': return [node.element] + case 'tuple': return node.elements.map(element => element.type) + case 'object': return [...node.properties.map(property => property.type), ...node.indices.flatMap(index => [index.key, index.value])] + case 'union': return [...node.types] + case 'primitive': + case 'literal': + case 'opaque': return [] + default: return assertNever(node) + } +} + +function mapNode(node: SchemaNode, ref: (id: number) => number): SchemaNode { + switch (node.kind) { + case 'array': return { kind: 'array', element: ref(node.element) } + case 'tuple': return { kind: 'tuple', elements: node.elements.map(element => ({ type: ref(element.type), optional: element.optional, rest: element.rest })) } + case 'object': return { + kind: 'object', + properties: [...node.properties].sort((left, right) => compare(left.name, right.name)) + .map(property => ({ name: property.name, type: ref(property.type), optional: property.optional })), + indices: node.indices.map(index => ({ key: ref(index.key), value: ref(index.value) })) + .sort((left, right) => left.key - right.key || left.value - right.value), + } + case 'union': return { kind: 'union', types: [...new Set(node.types.map(ref))].sort((left, right) => left - right) } + case 'primitive': return { kind: 'primitive', type: node.type } + case 'literal': return { kind: 'literal', value: node.value } + case 'opaque': return { kind: 'opaque', reason: node.reason } + default: return assertNever(node) + } +} + +function partition(nodes: readonly SchemaNode[]): number[] { + let colors = nodes.map(() => 0) + for (;;) { + const signatures = nodes.map((node, index) => JSON.stringify([colors[index], mapNode(node, id => colors[id] as number)])) + const ordered = [...new Set(signatures)].sort(compare) + const ids = new Map(ordered.map((value, index) => [value, index])) + const next = signatures.map(value => ids.get(value) as number) + const unchanged = new Set(next).size === new Set(colors).size + colors = next + if (unchanged) return colors + } +} + +/** + * Minimize bisimilar recursive nodes and number the reachable graph deterministically. + * @param input - resolved nodes; property and union order may be arbitrary. + * @param root - index of the requested root. + * @returns canonical graph excluding unreachable nodes and duplicate structures. + */ +export function canonicalizeSchema(input: readonly SchemaNode[], root: number): CanonicalSchema { + input = normalizeUnions(input) + const selected: number[] = [] + const positions = new Map() + const select = (id: number): void => { + if (!Number.isInteger(id) || id < 0 || id >= input.length) throw new Error(`persistence schema: missing node ${String(id)}`) + if (positions.has(id)) return + positions.set(id, selected.length) + selected.push(id) + for (const child of schemaChildren(input[id] as SchemaNode)) select(child) + } + select(root) + let nodes = selected.map(id => mapNode(input[id] as SchemaNode, child => positions.get(child) as number)) + let rootIndex = 0 + for (;;) { + const colors = partition(nodes) + const representatives = new Map() + colors.forEach((color, index) => { if (!representatives.has(color)) representatives.set(color, index) }) + const aliases = new Map() + nodes.forEach((node, index) => { + if (node.kind !== 'union') return + const members = [...new Set(node.types.map(child => colors[child] as number))] + if (members.length === 1) aliases.set(index, representatives.get(members[0] as number) as number) + }) + if (aliases.size > 0) { + const resolve = (id: number): number => { + const seen = new Set() + while (aliases.has(id)) { + if (seen.has(id)) throw new Error('persistence schema: union cycle has no material type') + seen.add(id) + id = aliases.get(id) as number + } + return id + } + rootIndex = resolve(rootIndex) + nodes = nodes.map((node, index) => aliases.has(index) ? nodes[resolve(index)] as SchemaNode : mapNode(node, resolve)) + continue + } + const emitted = new Map() + const result: SchemaNode[] = [] + const visit = (id: number): number => { + const color = colors[id] as number + const existing = emitted.get(color) + if (existing !== undefined) return existing + const position = result.length + emitted.set(color, position) + result.push({ kind: 'primitive', type: 'never' }) + const representative = representatives.get(color) as number + const colored = mapNode(nodes[representative] as SchemaNode, child => colors[child] as number) + result[position] = mapNode(colored, child => visit(representatives.get(child) as number)) + return position + } + visit(rootIndex) + return { root: 0, nodes: result } + } +} + +function normalizeUnions(input: readonly SchemaNode[]): SchemaNode[] { + const result = [...input] + const flattened = (id: number, visiting: Set): number[] => { + const node = input[id] + if (node === undefined) throw new Error(`persistence schema: missing node ${String(id)}`) + if (node.kind !== 'union') return [id] + if (visiting.has(id)) throw new Error('persistence schema: union cycle has no material type') + const next = new Set(visiting).add(id) + return node.types.flatMap(child => flattened(child, next)) + } + for (const [id, node] of input.entries()) { + if (node.kind !== 'union') continue + let members = [...new Set(flattened(id, new Set()))] + const primitives = new Set(members.flatMap((child) => { + const value = input[child] as SchemaNode + return value.kind === 'primitive' ? [value.type] : [] + })) + const literals = members.flatMap((child) => { + const value = input[child] as SchemaNode + return value.kind === 'literal' ? [value.value] : [] + }) + if (!primitives.has('boolean') && literals.includes(true) && literals.includes(false)) { + primitives.add('boolean') + members.push(result.length) + result.push({ kind: 'primitive', type: 'boolean' }) + } + members = members.filter((child) => { + const value = result[child] as SchemaNode + if (value.kind === 'primitive') return value.type !== 'never' + return value.kind !== 'literal' || !primitives.has(typeof value.value as 'string' | 'number' | 'boolean') + }) + result[id] = members.length === 0 ? { kind: 'primitive', type: 'never' } : { kind: 'union', types: members } + } + return result +} + +/** + * Compute the versioned SHA-256 fingerprint of a canonical graph. + * @param schema - canonical resolved persisted type. + * @returns lowercase hexadecimal digest. + */ +export function schemaDigest(schema: CanonicalSchema): string { + return createHash('sha256').update('dsh-persistence-schema-v1\n').update(JSON.stringify(schema)).digest('hex') +} + +/** + * Recognize the complete recursive JSON value language without relying on type names. + * @param schema - canonical resolved type. + * @returns whether the type permits arbitrary JSON values. + */ +export function isArbitraryJsonSchema(schema: CanonicalSchema): boolean { + return schemaDigest(schema) === JSON_VALUE_DIGEST +} + +const JSON_VALUE_DIGEST = schemaDigest(canonicalizeSchema([ + { kind: 'union', types: [1, 2, 3, 4, 5, 6] }, + { kind: 'primitive', type: 'null' }, + { kind: 'primitive', type: 'boolean' }, + { kind: 'primitive', type: 'number' }, + { kind: 'primitive', type: 'string' }, + { kind: 'array', element: 0 }, + { kind: 'object', properties: [], indices: [{ key: 4, value: 0 }] }, +], 0)) + +function compare(left: string, right: string): number { + return left < right ? -1 : left > right ? 1 : 0 +} + +function assertNever(value: never): never { + throw new Error(`persistence schema: unsupported node ${JSON.stringify(value)}`) +} diff --git a/scripts/persistence-schema.spec.ts b/scripts/persistence-schema.spec.ts new file mode 100644 index 0000000000..6b0f403ed8 --- /dev/null +++ b/scripts/persistence-schema.spec.ts @@ -0,0 +1,323 @@ +import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import { collectLogEvents } from './gen-persistence-catalog.ts' +import { extractPersistenceSchema } from './persistence-schema.ts' +import { canonicalizeSchema, isArbitraryJsonSchema, schemaDigest, type PersistenceSchemaInventory } from './persistence-schema-model.ts' + +const roots: string[] = [] +afterEach(() => { + for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true, maxRetries: 3 }) +}) + +function put(root: string, file: string, source: string): void { + const path = join(root, file) + mkdirSync(dirname(path), { recursive: true }) + writeFileSync(path, source) +} + +function fixture(payload: string, options: { surface?: string; event?: string } = {}): string { + const root = mkdtempSync(join(tmpdir(), 'dsh-persistence-schema-')) + roots.push(root) + put(root, 'tsconfig.host.json', JSON.stringify({ compilerOptions: { + target: 'es2024', module: 'esnext', moduleResolution: 'bundler', strict: true, skipLibCheck: true, + types: [], paths: { + '@deepseek-ai/dsh-session/types': ['./packages/core/session/src/types.ts'], + '@fixture/payload': ['./packages/domain/payload/src/types.ts'], + }, + }, include: ['packages/**/src/**/*.ts'] })) + put(root, 'packages/core/session/package.json', '{"name":"@deepseek-ai/dsh-session"}') + put(root, 'packages/domain/payload/package.json', '{"name":"@fixture/payload"}') + put(root, 'packages/session/session-persistence-jsonl/src/format.ts', "interface HeaderLine {type: 'session'; version: number; id: string; delegationDepth: number}\nexport {}\n") + put(root, 'packages/domain/payload/src/types.ts', payload) + put(root, 'packages/core/session/src/types.ts', ` +import type { Payload } from '@fixture/payload' +export interface SessionHeader { version: 3; id: string; createdAt: number } +export interface SessionEventMap { + /** One recorded payload. */ + 'test/record': Payload + ${options.event ?? ''} +} +export type SurfaceEventType = ${options.surface ?? 'never'} +export type SessionEvent = { + [K in keyof SessionEventMap]: { type: K; seq: number; time: number; data: SessionEventMap[K] } + & (K extends SurfaceEventType ? { surfaceOp: 'append' | { replace: number } } : { surfaceOp?: never }) +}[T] +`) + return root +} + +function event(inventory: PersistenceSchemaInventory, name = 'test/record'): string { + const root = inventory.roots.find(root => root.event === name) + if (root === undefined) throw new Error(`missing test event ${name}`) + return root.digest +} + +describe('persistent source type extraction', () => { + it('ignores alias names, files, documentation, readonly, brands and property order', () => { + const left = fixture('declare const brand: unique symbol; type Id = string & {readonly [brand]: "Id"}; export interface Payload { readonly id: Id; value?: number }') + const right = fixture('type Renamed = string; /** Different documentation. */ export type Payload = {value?: number; id: Renamed}') + expect(event(extractPersistenceSchema(left))).toBe(event(extractPersistenceSchema(right))) + }) + + it.each([ + ['nested field order', + 'export interface Payload {\n id: string\n detail: {\n label: string\n count?: number\n }\n}', + 'export interface Payload {\n detail: {\n count?: number\n label: string\n }\n id: string\n}'], + ['anonymous union order', + 'export type Payload =\n { a: string }\n | { b: number }\n | { c?: boolean }', + 'export type Payload =\n { c?: boolean }\n | { b: number }\n | { a: string }'], + ['recursive union order', + 'export type Payload =\n { a: string; next?: Payload }\n | { b: number; next?: Payload }\n | null', + 'export type Payload =\n null\n | { next?: Payload; b: number }\n | { next?: Payload; a: string }'], + ['intersection and distributed union order', + 'type A = { a: string } | { b: number }; type B = { c: boolean }; export type Payload = A & B', + 'type A = { b: number } | { a: string }; type B = { c: boolean }; export type Payload = B & A'], + ['index signature order', + 'export interface Payload { [key: string]: string | number; [key: number]: number }', + 'export interface Payload { [key: number]: number; [key: string]: number | string }'], + ['type declaration order', + 'interface A { a: string }\ninterface B { b: number }\nexport type Payload = A | B', + 'interface B { b: number }\ninterface A { a: string }\nexport type Payload = B | A'], + ['merged declaration order', + 'export interface Payload { a: string }\nexport interface Payload { b: number }', + 'export interface Payload { b: number }\nexport interface Payload { a: string }'], + ['mapped key order', + 'export type Payload = { [K in "a" | "b" | "c"]: string }', + 'export type Payload = { [K in "c" | "b" | "a"]: string }'], + ['explicit enum member order', + 'enum Value {\n A = "a",\n B = "b",\n}\nexport interface Payload { value: Value }', + 'enum Value {\n B = "b",\n A = "a",\n}\nexport interface Payload { value: Value }'], + ])('keeps every root and type digest unchanged by %s', (_name, before, after) => { + const root = fixture(before) + const inventory = extractPersistenceSchema(root) + put(root, 'packages/domain/payload/src/types.ts', after) + const reordered = extractPersistenceSchema(root) + expect(reordered.roots).toEqual(inventory.roots) + expect(reordered.types.map(({ digest, schema }) => ({ digest, schema }))) + .toEqual(inventory.types.map(({ digest, schema }) => ({ digest, schema }))) + }) + + it.each([ + ['tuple positions', 'export type Payload = [string, number]', 'export type Payload = [number, string]'], + ['implicit enum values', 'enum Value { A, B }; export interface Payload { value: Value.A }', 'enum Value { B, A }; export interface Payload { value: Value.A }'], + ])('changes the digest when reordering changes %s', (_name, before, after) => { + const root = fixture(before) + const digest = event(extractPersistenceSchema(root)) + put(root, 'packages/domain/payload/src/types.ts', after) + expect(event(extractPersistenceSchema(root))).not.toBe(digest) + }) + + it('keeps header, envelope and event digests unchanged when root declarations are reordered', () => { + const record = " /** One recorded payload. */\n 'test/record': Payload" + const other = "/** Another recorded payload. */\n 'test/other': { value: number }" + const root = fixture('export interface Payload { id: string }', { + surface: "'test/record' | 'test/other'", + event: other, + }) + const inventory = extractPersistenceSchema(root) + const source = readFileSync(join(root, 'packages/core/session/src/types.ts'), 'utf8') + const reordered = source + .replace(`${record}\n ${other}`, ` ${other}\n${record}`) + .replace('version: 3; id: string; createdAt: number', 'createdAt: number; id: string; version: 3') + .replace("'test/record' | 'test/other'", "'test/other' | 'test/record'") + .replace('type: K; seq: number; time: number; data: SessionEventMap[K]', 'data: SessionEventMap[K]; time: number; seq: number; type: K') + expect(reordered.indexOf("'test/other':")).toBeLessThan(reordered.indexOf("'test/record':")) + put(root, 'packages/core/session/src/types.ts', reordered) + put(root, 'packages/session/session-persistence-jsonl/src/format.ts', "interface HeaderLine {delegationDepth: number; id: string; version: number; type: 'session'}\nexport {}\n") + const result = extractPersistenceSchema(root) + expect(result.roots).toEqual(inventory.roots) + expect(result.types.map(({ digest, schema }) => ({ digest, schema }))) + .toEqual(inventory.types.map(({ digest, schema }) => ({ digest, schema }))) + }) + + it('materializes generic, conditional and mapped aliases into their concrete properties', () => { + const generic = fixture('type Box = { [K in keyof T]: T[K] extends string ? string[] : T[K] }; export type Payload = Box<{label: string; count?: number}>') + const concrete = fixture('export interface Payload {count?: number; label: string[]}') + expect(event(extractPersistenceSchema(generic))).toBe(event(extractPersistenceSchema(concrete))) + }) + + it('retains transitive recursion without depending on alias factoring', () => { + const self = fixture('export interface Payload { value: string; next?: Payload }') + const mutual = fixture('interface Other {next?: Payload; value: string} export interface Payload {value: string; next?: Other}') + const changed = fixture('interface Other {next?: Payload; value: number} export interface Payload {value: string; next?: Other}') + const digest = event(extractPersistenceSchema(self)) + expect(event(extractPersistenceSchema(mutual))).toBe(digest) + expect(event(extractPersistenceSchema(changed))).not.toBe(digest) + }) + + it('includes independent Host modules that augment nested payload maps without event declarations', () => { + const root = fixture('export interface NestedMap {first: {value: string}}; export type Payload = NestedMap[keyof NestedMap]') + const before = extractPersistenceSchema(root) + put(root, 'packages/domain/extension/src/index.ts', "import '@fixture/payload'; declare module '@fixture/payload' {interface NestedMap {second: {value: number}}}") + const after = extractPersistenceSchema(root) + expect(collectLogEvents(root)).toHaveLength(1) + expect(event(after)).not.toBe(event(before)) + const data = after.types.find(item => item.schema.nodes[0]?.kind === 'union' && item.schema.nodes.some(node => node.kind === 'primitive' && node.type === 'number')) + expect(data).toBeDefined() + }) + + it('discovers plugin event merges and keeps ordinary event additions out of the envelope digest', () => { + const root = fixture('export interface Payload {id: string}') + const before = extractPersistenceSchema(root) + put(root, 'packages/domain/extension/src/index.ts', `import '@deepseek-ai/dsh-session/types'; declare module '@deepseek-ai/dsh-session/types' { +interface SessionEventMap { +/** A plugin event. */ +'plugin/new': { optional?: boolean } +}} +`) + const after = extractPersistenceSchema(root) + expect(after.roots.some(root => root.event === 'plugin/new')).toBe(true) + expect(after.roots.find(root => root.kind === 'envelope')?.digest).toBe(before.roots.find(root => root.kind === 'envelope')?.digest) + expect(event(after)).toBe(event(before)) + }) + + it('includes JSX-module events and rejects unsupported inherited declarations there', () => { + const root = fixture('export interface Payload {id: string}') + put(root, 'packages/domain/extension/src/index.tsx', `import '@deepseek-ai/dsh-session/types'; declare module '@deepseek-ai/dsh-session/types' { +interface SessionEventMap { +/** A JSX-module event. */ +'plugin/tsx': { value: number } +}} +`) + const config = join(root, 'tsconfig.host.json') + const settings = JSON.parse(readFileSync(config, 'utf8')) as { include: string[] } + settings.include.push('packages/**/src/**/*.tsx') + writeFileSync(config, JSON.stringify(settings)) + expect(extractPersistenceSchema(root).roots.some(root => root.event === 'plugin/tsx')).toBe(true) + put(root, 'packages/domain/extension/src/index.tsx', `import '@deepseek-ai/dsh-session/types'; +interface Extra { 'plugin/inherited': {value: number} } +declare module '@deepseek-ai/dsh-session/types' { interface SessionEventMap extends Extra {} } +`) + expect(() => extractPersistenceSchema(root)).toThrow('uses extends') + }) + + it('rejects an event map whose compiler vocabulary exceeds the discovered source corpus', () => { + const root = fixture("import '../../../../vendor/events.ts'; export interface Payload {id: string}") + put(root, 'vendor/events.ts', `import '@deepseek-ai/dsh-session/types'; declare module '@deepseek-ai/dsh-session/types' { +interface SessionEventMap { +/** A contribution outside the package source corpus. */ +'outside/event': { value: number } +}} +`) + expect(() => extractPersistenceSchema(root)).toThrow('omitted: outside/event') + }) + + it('keeps surface metadata strict and optional field additions visible', () => { + const old = extractPersistenceSchema(fixture('export interface Payload {id: string}')) + const optional = extractPersistenceSchema(fixture('export interface Payload {id: string; detail?: string}')) + const surface = extractPersistenceSchema(fixture('export interface Payload {id: string}', { surface: "'test/record'" })) + expect(event(old)).not.toBe(event(optional)) + expect(event(old)).not.toBe(event(surface)) + expect(surface.roots.find(root => root.event === 'test/record')?.surface).toBe(true) + expect(surface.roots.find(root => root.kind === 'envelope')?.digest).not.toBe(old.roots.find(root => root.kind === 'envelope')?.digest) + }) + + it('tracks the physical JSONL header independently of the logical Session header', () => { + const root = fixture('export interface Payload {id: string}') + const before = extractPersistenceSchema(root) + put(root, 'packages/session/session-persistence-jsonl/src/format.ts', "interface HeaderLine {type: 'session'; version: number; id: string; delegationDepth: number; marker?: string}\nexport {}\n") + const after = extractPersistenceSchema(root) + expect(after.roots.find(root => root.key === 'JsonlHeaderLine')?.digest).not.toBe(before.roots.find(root => root.key === 'JsonlHeaderLine')?.digest) + expect(after.roots.find(root => root.key === 'SessionHeader')?.digest).toBe(before.roots.find(root => root.key === 'SessionHeader')?.digest) + }) + + it('keeps arbitrary JSON structural while explicitly identifying unknown and any', () => { + const root = fixture('type Arbitrary = null | boolean | number | string | Arbitrary[] | {[key: string]: Arbitrary}; export interface Payload {json: Arbitrary; opaque: unknown; unsafe: any}') + const model = extractPersistenceSchema(root) + expect(model.types.some(item => isArbitraryJsonSchema(item.schema))).toBe(true) + expect(model.types.filter(item => item.schema.nodes[0]?.kind === 'opaque').map(item => item.schema.nodes[0])) + .toEqual(expect.arrayContaining([{ kind: 'opaque', reason: 'unknown' }, { kind: 'opaque', reason: 'any' }])) + put(root, 'packages/domain/payload/src/types.ts', 'type Arbitrary = null | boolean | number | string | Arbitrary[]; export interface Payload {json: Arbitrary; opaque: unknown; unsafe: any}') + const changed = extractPersistenceSchema(root) + expect(event(changed)).not.toBe(event(model)) + expect(changed.types.some(item => isArbitraryJsonSchema(item.schema))).toBe(false) + }) + + it('rejects invalid reachable declaration files despite inherited skipLibCheck', () => { + const root = fixture("export type { Payload } from './declared.js'") + put(root, 'packages/domain/payload/src/declared.d.ts', 'type Bad = ReturnType<42>; export interface Payload {value: Bad}') + expect(() => extractPersistenceSchema(root)).toThrow('TS2344') + }) + + it('preserves authored declaration-file any without checking unrelated declarations', () => { + const root = fixture("export type { Payload } from './declared.js'") + put(root, 'packages/domain/payload/src/declared.d.ts', 'type Declared = any; export interface Payload {value: Declared}; type Unrelated = ReturnType<42>') + const model = extractPersistenceSchema(root) + expect(model.types.some(item => item.schema.nodes[0]?.kind === 'opaque' && item.schema.nodes[0].reason === 'any')).toBe(true) + }) + + it('locates named transitive definitions at their declarations instead of their references', () => { + const root = fixture("import type { Detail as ImportedDetail } from './detail.js'\nexport interface Payload { first: ImportedDetail; second: ImportedDetail }") + put(root, 'packages/domain/payload/src/detail.ts', '/** Detailed payload. */\nexport interface Detail { code: string; done: true }\n') + const model = extractPersistenceSchema(root) + const detail = model.types.find(item => item.names.includes('packages/domain/payload/src/detail.ts#Detail')) + expect(detail?.sources).toEqual(['packages/domain/payload/src/detail.ts:2']) + }) + + it('keeps shared scalar declaration metadata at real aliases and omits plain property references', () => { + const root = fixture([ + 'type Label = string', + "type Done = 'done'", + 'export interface Payload {', + ' label: Label', + ' other: string', + ' state: Done', + " fallback: 'done'", + ' count: number', + '}', + ].join('\n')) + const model = extractPersistenceSchema(root) + const string = model.types.find(item => item.schema.nodes[0]?.kind === 'primitive' && item.schema.nodes[0].type === 'string') + const literal = model.types.find(item => item.schema.nodes[0]?.kind === 'literal' && item.schema.nodes[0].value === 'done') + const number = model.types.find(item => item.schema.nodes[0]?.kind === 'primitive' && item.schema.nodes[0].type === 'number') + expect(string?.names).toEqual(['packages/domain/payload/src/types.ts#Label']) + expect(string?.sources).toEqual(['packages/domain/payload/src/types.ts:1']) + expect(literal?.names).toEqual(['packages/domain/payload/src/types.ts#Done']) + expect(literal?.sources).toEqual(['packages/domain/payload/src/types.ts:2']) + expect(number?.names).toEqual([]) + expect(number?.sources).toEqual([]) + }) + + it('uses the declaration of an anonymous object literal and never its containing property', () => { + const root = fixture('export interface Payload {\n detail:\n { code: string }\n}') + const model = extractPersistenceSchema(root) + const detail = model.types.find((item) => { + const node = item.schema.nodes[0] + return node?.kind === 'object' && node.properties.length === 1 && node.properties[0]?.name === 'code' + }) + expect(detail?.sources).toEqual(['packages/domain/payload/src/types.ts:3']) + }) + + it.each([ + ['missing type', 'export interface Payload {value: Missing}', 'TS2304'], + ['callable value', 'export interface Payload {value: () => void}', 'callable data'], + ['class instance', 'export class Instance {value: string = ""}; export interface Payload {value: Instance}', 'class instances'], + ['required undefined', 'export interface Payload {value: string | undefined}', 'no supported JSON'], + ['unresolved template', 'export interface Payload {value: `prefix-${string}`}', 'no supported JSON'], + ['empty object type', 'export type Payload = {}', 'unconstrained empty object'], + ['symbol property', 'declare const key: unique symbol; export interface Payload {[key]: string; value: number}', 'symbol-keyed data'], + ['conflicting merged property', 'export interface Payload {value: string}; export interface Payload {value: number}', 'TS2717'], + ['class intersection', 'class Instance {value: string = ""}; export type Payload = Instance & {extra: string}', 'class instances'], + ['invalid generic alias', 'type Bad = ReturnType<42>; export interface Payload {value: Bad}', 'TS2344'], + ])('rejects %s rather than weakening the schema', (_name, source, error) => { + expect(() => extractPersistenceSchema(fixture(source))).toThrow(error) + }) + + it('includes every real repository event and fingerprints every reachable node', () => { + const root = resolve(import.meta.dirname, '..') + const model = extractPersistenceSchema(root) + expect(model.roots.filter(root => root.kind === 'event').map(root => root.event).sort()) + .toEqual(collectLogEvents(root).map(event => event.name).sort()) + expect(model.roots.some(root => root.kind === 'header')).toBe(true) + expect(model.roots.some(root => root.kind === 'envelope')).toBe(true) + const digests = new Set(model.types.map(type => type.digest)) + for (const root of model.roots) { + expect(schemaDigest(root.schema)).toBe(root.digest) + for (let node = 0; node < root.schema.nodes.length; node++) { + expect(digests.has(schemaDigest(canonicalizeSchema(root.schema.nodes, node)))).toBe(true) + } + } + }) +}) diff --git a/scripts/persistence-schema.ts b/scripts/persistence-schema.ts new file mode 100644 index 0000000000..2120b0874c --- /dev/null +++ b/scripts/persistence-schema.ts @@ -0,0 +1,462 @@ +/** Extract complete persistent Session record types from one source-only compiler program. */ + +import { readFileSync } from 'node:fs' +import { dirname, extname, relative, resolve, sep } from 'node:path' +import ts from 'typescript' +import { collectLogEvents } from './persistence-catalog-source.ts' +import { + canonicalizeSchema, + schemaChildren, + schemaDigest, + type PersistenceRoot, + type PersistenceSchemaInventory, + type PersistenceType, + type SchemaNode, + type SchemaProperty, +} from './persistence-schema-model.ts' + +interface DeclarationMetadata { + readonly names: Set + readonly sources: Set +} + +interface RootInput { + readonly key: string + readonly kind: PersistenceRoot['kind'] + readonly event?: string + readonly surface?: boolean + readonly node: number +} + +/** A reachable TypeScript type that cannot be represented as a persisted JSON type. */ +export class PersistenceSchemaError extends Error { + override name = 'PersistenceSchemaError' +} + +/** + * Extract every repository event, its complete envelope, and the Session header. + * @param root - repository root, with source paths in tsconfig.host.json. + * @returns canonical root fingerprints and all reachable type definitions. + */ +export function extractPersistenceSchema(root: string): PersistenceSchemaInventory { + root = resolve(root) + const events = collectLogEvents(root).sort((left, right) => compare(left.name, right.name)) + if (events.length === 0) throw new PersistenceSchemaError('persistence schema: no Session events were discovered') + const filename = resolve(root, 'scripts/__persistence_schema_roots__.ts') + const source = [ + "import type { SessionHeader, SessionEvent, SessionEventMap, SurfaceEventType } from '@deepseek-ai/dsh-session/types'", + 'export type HeaderRoot = SessionHeader', + 'export type SurfaceRoot = SurfaceEventType', + 'export type EventNamesRoot = keyof SessionEventMap', + ...events.map((event, index) => `export type EventRoot${String(index)} = SessionEvent<${JSON.stringify(event.name)}>`), + '', + ].join('\n') + const configPath = resolve(root, 'tsconfig.host.json') + const config = ts.readConfigFile(configPath, file => readFileSync(file, 'utf8')) + if (config.error !== undefined) throw new PersistenceSchemaError(diagnosticText(config.error)) + const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, root) + const configErrors = parsed.errors.filter(error => error.code !== 18003) + if (configErrors.length > 0) throw new PersistenceSchemaError(configErrors.map(diagnosticText).join('\n')) + const options: ts.CompilerOptions = { + ...parsed.options, + composite: false, + incremental: false, + noEmit: true, + rootDir: root, + noUnusedLocals: false, + noUnusedParameters: false, + // Reachable declaration-file errors must not become opaque any values. + skipLibCheck: false, + strict: true, + exactOptionalPropertyTypes: true, + } + const host = ts.createCompilerHost(options) + const originalGetSourceFile = host.getSourceFile.bind(host) + host.getSourceFile = (path, languageVersion, onError, shouldCreateNewSourceFile) => + resolve(path) === filename + ? ts.createSourceFile(filename, source, languageVersion, true) + : originalGetSourceFile(path, languageVersion, onError, shouldCreateNewSourceFile) + const files = [...new Set([ + ...hostSourceFiles(root, configPath), + ...events.map(event => resolve(root, event.source.slice(0, event.source.lastIndexOf(':')))), + ])] + const program = ts.createProgram({ rootNames: [filename, ...files], options, host }) + const rootSource = program.getSourceFile(filename) + if (rootSource === undefined) throw new PersistenceSchemaError('persistence schema: compiler omitted the requested roots') + const diagnostics = [ + ...program.getOptionsDiagnostics(), ...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics(rootSource), + ] + if (diagnostics.length > 0) throw new PersistenceSchemaError(diagnostics.map(diagnosticText).join('\n')) + const checker = program.getTypeChecker() + const declarations = new Map(rootSource.statements.filter(ts.isTypeAliasDeclaration) + .map(declaration => [declaration.name.text, declaration])) + const declaration = (name: string): ts.TypeAliasDeclaration => { + const found = declarations.get(name) + if (found === undefined) throw new PersistenceSchemaError(`persistence schema: missing compiler root ${name}`) + return found + } + const eventNamesDeclaration = declaration('EventNamesRoot') + const compiledEvents = stringLiterals(checker.getTypeFromTypeNode(eventNamesDeclaration.type), 'keyof SessionEventMap') + const discoveredEvents = new Set(events.map(event => event.name)) + const omitted = [...compiledEvents].filter(name => !discoveredEvents.has(name)) + const uncompiled = [...discoveredEvents].filter(name => !compiledEvents.has(name)) + if (omitted.length > 0 || uncompiled.length > 0) { + throw new PersistenceSchemaError(`persistence schema: compiler and source event discovery disagree; omitted: ${omitted.join(', ')}; uncompiled: ${uncompiled.join(', ')}`) + } + const surfaceDeclaration = declaration('SurfaceRoot') + const surface = stringLiterals(checker.getTypeFromTypeNode(surfaceDeclaration.type), 'SurfaceEventType') + for (const name of surface) { + if (!events.some(event => event.name === name)) throw new PersistenceSchemaError(`persistence schema: surface event ${name} has no declaration`) + } + const header = declaration('HeaderRoot') + const physicalFile = resolve(root, 'packages/session/session-persistence-jsonl/src/format.ts') + const physical = program.getSourceFile(physicalFile)?.statements + .filter((node): node is ts.InterfaceDeclaration | ts.TypeAliasDeclaration => + ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) + .filter(declaration => declaration.name.text === 'HeaderLine') + if (physical?.length !== 1) throw new PersistenceSchemaError('persistence schema: expected one current JSONL HeaderLine declaration') + const physicalHeader = physical[0] as ts.InterfaceDeclaration | ts.TypeAliasDeclaration + const declarationSources = validateReachableDeclarations(program, root, [...declarations.values(), physicalHeader]) + const extraction = new SchemaExtractor(root, program, declarationSources) + const roots: RootInput[] = [{ + key: 'SessionHeader', + kind: 'header', + node: extraction.convert(checker.getTypeFromTypeNode(header.type), header), + }] + roots.push({ key: 'JsonlHeaderLine', kind: 'header', node: extraction.convert(checker.getTypeAtLocation(physicalHeader), physicalHeader) }) + const envelopes: number[] = [] + for (const [index, event] of events.entries()) { + const item = declaration(`EventRoot${String(index)}`) + const node = extraction.convert(checker.getTypeFromTypeNode(item.type), item) + roots.push({ key: `event:${event.name}`, kind: 'event', event: event.name, surface: surface.has(event.name), node }) + envelopes.push(extraction.envelope(node, event.name)) + } + roots.splice(2, 0, { key: 'SessionEventEnvelope', kind: 'envelope', node: extraction.union(envelopes) }) + return extraction.inventory(roots) +} + +function hostSourceFiles(root: string, configPath: string, seen = new Set()): string[] { + configPath = resolve(configPath) + if (seen.has(configPath)) return [] + seen.add(configPath) + const read = ts.readConfigFile(configPath, file => readFileSync(file, 'utf8')) + if (read.error !== undefined) throw new PersistenceSchemaError(diagnosticText(read.error)) + const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, dirname(configPath), undefined, configPath) + const errors = parsed.errors.filter(error => error.code !== 18003) + if (errors.length > 0) throw new PersistenceSchemaError(errors.map(diagnosticText).join('\n')) + return [ + ...parsed.fileNames.filter(file => /^packages\/[^/]+\/[^/]+\/src\//.test(slash(relative(root, file)))), + ...(parsed.projectReferences ?? []).flatMap(reference => + hostSourceFiles(root, extname(reference.path) === '.json' ? reference.path : resolve(reference.path, 'tsconfig.json'), seen)), + ] +} + +function validateReachableDeclarations( + program: ts.Program, + root: string, + roots: readonly ts.Node[], +): ReadonlyMap { + const checker = program.getTypeChecker() + const visited = new Set() + const declarations = new Map() + const definitions = new Map() + const visit = (node: ts.Node): void => { + if (visited.has(node)) return + visited.add(node) + const file = node.getSourceFile() + if (trackedSource(slash(relative(root, file.fileName)))) { + const scopes = declarations.get(file) ?? [] + scopes.push(node) + declarations.set(file, scopes) + if (isNamedTypeDeclaration(node) || ts.isTypeLiteralNode(node)) { + // Primitive aliases share checker types with unaliased properties. + const type = checker.getTypeAtLocation(node) + const entries = definitions.get(type) ?? [] + entries.push(node) + definitions.set(type, entries) + } + } + const target = ts.isTypeReferenceNode(node) ? node.typeName + : ts.isExpressionWithTypeArguments(node) ? node.expression + : ts.isTypeQueryNode(node) ? node.exprName + : ts.isImportTypeNode(node) ? node.qualifier + : undefined + if (target !== undefined) { + let symbol = checker.getSymbolAtLocation(target) + if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias)) symbol = checker.getAliasedSymbol(symbol) + for (const declaration of symbol?.declarations ?? []) { + if (trackedSource(slash(relative(root, declaration.getSourceFile().fileName)))) visit(declaration) + } + } + if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) return + ts.forEachChild(node, visit) + } + for (const root of roots) visit(root) + const errors = [...declarations].flatMap(([file, scopes]) => program.getSemanticDiagnostics(file) + .filter((error) => { + const start = error.start + return start !== undefined && scopes.some(scope => start >= scope.getStart() && start < scope.end) + })) + if (errors.length > 0) throw new PersistenceSchemaError(errors.map(diagnosticText).join('\n')) + return definitions +} + +class SchemaExtractor { + readonly nodes: SchemaNode[] = [] + private readonly cache = new Map() + private readonly declarationMetadata = new Map() + + private readonly checker: ts.TypeChecker + + constructor( + private readonly root: string, + program: ts.Program, + private readonly declarationSources: ReadonlyMap, + ) { + this.checker = program.getTypeChecker() + } + + convert(type: ts.Type, site: ts.Node): number { + const cached = this.cache.get(type) + if (cached !== undefined) { + this.record(cached, type) + return cached + } + const id = this.nodes.length + this.nodes.push({ kind: 'primitive', type: 'never' }) + this.cache.set(type, id) + this.record(id, type) + const add = (node: SchemaNode): number => { this.nodes[id] = node; return id } + const flags = type.flags + if (flags & ts.TypeFlags.Any) { + if ((type as ts.Type & { intrinsicName?: string }).intrinsicName === 'error') this.fail(type, site, 'unresolved compiler type') + return add({ kind: 'opaque', reason: 'any' }) + } + if (flags & ts.TypeFlags.Unknown) return add({ kind: 'opaque', reason: 'unknown' }) + if (flags & ts.TypeFlags.Never) return add({ kind: 'primitive', type: 'never' }) + if (flags & ts.TypeFlags.Null) return add({ kind: 'primitive', type: 'null' }) + if (flags & ts.TypeFlags.String) return add({ kind: 'primitive', type: 'string' }) + if (flags & ts.TypeFlags.Number) return add({ kind: 'primitive', type: 'number' }) + if (flags & ts.TypeFlags.Boolean) return add({ kind: 'primitive', type: 'boolean' }) + if (flags & ts.TypeFlags.StringLiteral) return add({ kind: 'literal', value: (type as ts.StringLiteralType).value }) + if (flags & ts.TypeFlags.NumberLiteral) return add({ kind: 'literal', value: (type as ts.NumberLiteralType).value }) + if (flags & ts.TypeFlags.BooleanLiteral) return add({ kind: 'literal', value: this.checker.typeToString(type) === 'true' }) + if (type.isUnion()) return add(this.unionNode(type.types.map(member => this.convert(member, site)))) + if (type.isIntersection()) { + const material = type.types.filter(member => !this.phantom(member)) + for (const member of material) this.rejectClass(member, site) + if (material.length === 1) { + const target = this.convert(material[0] as ts.Type, site) + return add({ kind: 'union', types: [target] }) + } + if (material.length === 0 || material.some(member => (member.flags & ts.TypeFlags.Object) === 0)) { + this.fail(type, site, 'unsupported material intersection') + } + return add(this.object(type, site)) + } + if (this.checker.isTupleType(type)) { + const reference = type as ts.TypeReference + const target = reference.target as ts.TupleType + return add({ + kind: 'tuple', + elements: this.checker.getTypeArguments(reference).map((element, index) => { + const flags = target.elementFlags[index] ?? ts.ElementFlags.Required + const optional = (flags & ts.ElementFlags.Optional) !== 0 + const rest = (flags & (ts.ElementFlags.Rest | ts.ElementFlags.Variadic)) !== 0 + return { type: this.valueType(element, site, optional), optional, rest } + }), + }) + } + if (this.checker.isArrayType(type) || this.checker.isArrayLikeType(type)) { + const element = this.checker.getIndexTypeOfType(type, ts.IndexKind.Number) + if (element === undefined) this.fail(type, site, 'array element is unavailable') + return add({ kind: 'array', element: this.convert(element, site) }) + } + if (flags & ts.TypeFlags.Object) return add(this.object(type, site)) + this.fail(type, site, 'type has no supported JSON representation') + } + + private valueType(type: ts.Type, site: ts.Node, optional: boolean): number { + if (!optional) return this.convert(type, site) + const members = (type.isUnion() ? type.types : [type]).filter(member => !(member.flags & ts.TypeFlags.Undefined)) + return this.union(members.map(member => this.convert(member, site))) + } + + private object(type: ts.Type, site: ts.Node): SchemaNode { + this.rejectClass(type, site) + if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) this.fail(type, site, 'callable data is not JSON') + const properties: SchemaProperty[] = [] + for (const property of this.checker.getPropertiesOfType(type).sort((left, right) => compare(left.name, right.name))) { + if (property.getName().startsWith('__@')) this.fail(type, site, 'symbol-keyed data is not a JSON field') + const declaration = property.valueDeclaration ?? property.declarations?.[0] ?? site + const propertyType = this.checker.getTypeOfSymbolAtLocation(property, declaration) + const optional = (property.flags & ts.SymbolFlags.Optional) !== 0 + if (optional && (propertyType.isUnion() ? propertyType.types : [propertyType]) + .every(member => (member.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Never)) !== 0)) continue + const child = this.valueType(propertyType, declaration, optional) + properties.push({ name: property.getName(), type: child, optional }) + } + const indices = this.checker.getIndexInfosOfType(type).map(info => ({ + key: this.convert(info.keyType, site), + value: this.convert(info.type, site), + })) + if (properties.length === 0 && indices.length === 0) this.fail(type, site, 'unconstrained empty object type is not an explicit JSON record') + return { kind: 'object', properties, indices } + } + + private rejectClass(type: ts.Type, site: ts.Node): void { + const symbol = type.getSymbol() + if (symbol?.declarations?.some(declaration => ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration))) { + this.fail(type, site, 'class instances require an explicit persisted representation') + } + } + + private phantom(type: ts.Type): boolean { + if (!(type.flags & ts.TypeFlags.Object)) return false + const properties = this.checker.getPropertiesOfType(type) + return properties.length > 0 && properties.every(property => property.getName().startsWith('__@')) + && type.getCallSignatures().length === 0 && type.getConstructSignatures().length === 0 + && this.checker.getIndexInfosOfType(type).length === 0 + } + + union(types: readonly number[]): number { + const node = this.unionNode(types) + if (node.kind === 'union' && node.types.length === 1) return node.types[0] as number + this.nodes.push(node) + return this.nodes.length - 1 + } + + private unionNode(types: readonly number[]): SchemaNode { + if (types.length === 0) return { kind: 'primitive', type: 'never' } + return { kind: 'union', types: [...new Set(types)] } + } + + envelope(id: number, event: string): number { + const node = this.nodes[id] as SchemaNode + if (node.kind === 'union') return this.union(node.types.map(type => this.envelope(type, event))) + if (node.kind !== 'object') throw new PersistenceSchemaError('persistence schema: SessionEvent must resolve to object alternatives') + const tag = node.properties.find(property => property.name === 'type') + const data = node.properties.find(property => property.name === 'data') + const literal = tag === undefined ? undefined : canonicalizeSchema(this.nodes, tag.type).nodes[0] + if (tag?.optional !== false || data?.optional !== false || literal?.kind !== 'literal' || literal.value !== event) { + throw new PersistenceSchemaError(`persistence schema: SessionEvent<${JSON.stringify(event)}> must retain its required type and data fields`) + } + const string = this.nodes.length + this.nodes.push({ kind: 'primitive', type: 'string' }) + const envelope: SchemaNode = { + kind: 'object', + properties: node.properties.filter(property => property.name !== 'data') + .map(property => property.name === 'type' ? { ...property, type: string } : property), + indices: node.indices, + } + this.nodes.push(envelope) + return this.nodes.length - 1 + } + + inventory(inputs: readonly RootInput[]): PersistenceSchemaInventory { + const roots = inputs.map((input) => { + const schema = canonicalizeSchema(this.nodes, input.node) + return { + key: input.key, + kind: input.kind, + ...(input.event === undefined ? {} : { event: input.event }), + ...(input.surface === undefined ? {} : { surface: input.surface }), + digest: schemaDigest(schema), + schema, + } + }) + const found = new Set() + const paths = new Map>() + const visit = (id: number, path: string): void => { + const names = paths.get(id) ?? new Set() + names.add(path) + paths.set(id, names) + if (found.has(id)) return + found.add(id) + const node = this.nodes[id] as SchemaNode + if (node.kind === 'object') { + for (const property of node.properties) visit(property.type, `${path}.${property.name}`) + for (const index of node.indices) { visit(index.key, `${path}.[key]`); visit(index.value, `${path}.[value]`) } + } else { + for (const [index, child] of schemaChildren(node).entries()) visit(child, `${path}[${String(index)}]`) + } + } + for (const input of inputs) visit(input.node, input.key) + const types = new Map; sources: Set }>() + for (const id of found) { + const schema = canonicalizeSchema(this.nodes, id) + const digest = schemaDigest(schema) + const item = types.get(digest) ?? { schema, names: new Set(), sources: new Set() } + const declarationMetadata = this.declarationMetadata.get(id) + for (const name of declarationMetadata?.names ?? []) item.names.add(name) + for (const source of declarationMetadata?.sources ?? []) item.sources.add(source) + const kind = schema.nodes[0]?.kind + if (kind !== 'primitive' && kind !== 'literal' && (declarationMetadata === undefined || declarationMetadata.names.size === 0)) { + for (const path of paths.get(id) ?? []) item.names.add(path) + } + types.set(digest, item) + } + return { + formatVersion: 1, + roots, + types: [...types].sort(([left], [right]) => compare(left, right)).map(([digest, item]) => ({ + digest, + schema: item.schema, + names: [...item.names].sort(compare), + sources: [...item.sources].sort(compare), + })), + } + } + + private record(id: number, type: ts.Type): void { + const item = this.declarationMetadata.get(id) ?? { names: new Set(), sources: new Set() } + const declarations = new Set([ + ...this.declarationSources.get(type) ?? [], + ...type.aliasSymbol?.declarations ?? [], + ...type.getSymbol()?.declarations ?? [], + ]) + for (const declaration of declarations) { + if (!isNamedTypeDeclaration(declaration) && !ts.isTypeLiteralNode(declaration) && !ts.isEnumMember(declaration)) continue + const source = declaration.getSourceFile() + const file = slash(relative(this.root, source.fileName)) + if (!trackedSource(file)) continue + if (isNamedTypeDeclaration(declaration)) item.names.add(`${file}#${declaration.name.text}`) + const position = source.getLineAndCharacterOfPosition(declaration.getStart()) + item.sources.add(`${file}:${String(position.line + 1)}`) + } + this.declarationMetadata.set(id, item) + } + + private fail(type: ts.Type, site: ts.Node, reason: string): never { + const source = slash(relative(this.root, site.getSourceFile().fileName)) + const position = site.getSourceFile().getLineAndCharacterOfPosition(site.getStart()) + throw new PersistenceSchemaError(`persistence schema: ${source}:${String(position.line + 1)}: ${reason}: ${this.checker.typeToString(type)}`) + } +} + +function isNamedTypeDeclaration(node: ts.Node): node is ts.TypeAliasDeclaration | ts.InterfaceDeclaration | ts.EnumDeclaration { + return ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) +} + +function stringLiterals(type: ts.Type, name: string): Set { + if (type.flags & ts.TypeFlags.Never) return new Set() + const members = type.isUnion() ? type.types : [type] + if (members.some(member => !(member.flags & ts.TypeFlags.StringLiteral))) throw new PersistenceSchemaError(`persistence schema: ${name} is not a closed string-literal union`) + return new Set(members.map(member => (member as ts.StringLiteralType).value)) +} + +function diagnosticText(diagnostic: ts.Diagnostic): string { + return `persistence schema: TypeScript TS${String(diagnostic.code)}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}` +} + +function compare(left: string, right: string): number { + return left < right ? -1 : left > right ? 1 : 0 +} + +function slash(path: string): string { + return path.split(sep).join('/') +} + +function trackedSource(file: string): boolean { + return (file.startsWith('packages/') || file.startsWith('vendor/')) && !file.includes('/node_modules/') +} diff --git a/scripts/render-persistence-schema.spec.ts b/scripts/render-persistence-schema.spec.ts new file mode 100644 index 0000000000..7c47b15d11 --- /dev/null +++ b/scripts/render-persistence-schema.spec.ts @@ -0,0 +1,80 @@ +/** Linked catalog regressions for transitive changes and shared definitions. */ + +import { describe, expect, it } from 'vitest' +import { canonicalizeSchema, schemaDigest, type PersistenceSchemaInventory, type PersistenceType, type SchemaNode } from './persistence-schema-model.ts' +import { renderPersistenceSchemaDefinitions, renderPersistenceSchemaIndex } from './render-persistence-schema.ts' + +function fixture(optional = true): PersistenceSchemaInventory { + const types = new Map() + const roots = ['first/event', 'second/event'].map((event) => { + const nodes: SchemaNode[] = [ + { kind: 'object', properties: [{ name: 'type', type: 1, optional: false }, { name: 'data', type: 2, optional: false }], indices: [] }, + { kind: 'literal', value: event }, + { kind: 'object', properties: [{ name: 'error', type: 3, optional: true }], indices: [] }, + { kind: 'object', properties: [{ name: 'reason', type: 4, optional }], indices: [] }, + { kind: 'primitive', type: 'string' }, + ] + nodes.forEach((_, index) => { + const schema = canonicalizeSchema(nodes, index) + const digest = schemaDigest(schema) + if (!types.has(digest)) types.set(digest, { digest, schema, names: index === 3 ? ['ErrorInfo'] : [], sources: index === 3 ? ['packages/core/example/src/types.ts:8'] : [] }) + }) + const schema = canonicalizeSchema(nodes, 0) + return { key: `event:${event}`, kind: 'event' as const, event, surface: false, digest: schemaDigest(schema), schema } + }) + return { formatVersion: 1, roots, types: [...types.values()] } +} + +describe('persistence schema catalog', () => { + it('shows a shared nested definition once and updates both affected root fingerprints', () => { + const before = fixture() + const after = fixture(false) + const definitions = renderPersistenceSchemaDefinitions(after) + expect(definitions.match(/### `ErrorInfo`/gu)).toHaveLength(1) + expect(definitions).toContain('| `reason` | required | `string` |') + expect(definitions).toContain('[`ErrorInfo`](#persistence-type-errorinfo)') + const index = renderPersistenceSchemaIndex(after) + for (const [position, root] of after.roots.entries()) { + expect(root.digest).not.toBe(before.roots[position]?.digest) + expect(index).toContain(root.digest) + } + }) + + it('keeps named definition anchors when their digest changes', () => { + const before = renderPersistenceSchemaDefinitions(fixture()) + const after = renderPersistenceSchemaDefinitions(fixture(false)) + expect(before).toContain('') + expect(after).toContain('') + expect(before).not.toBe(after) + }) + + it('refuses a current inventory that omits a referenced definition', () => { + const complete = fixture() + const missing: PersistenceSchemaInventory = { ...complete, types: [] } + expect(() => renderPersistenceSchemaIndex(missing)).toThrow(/absent from the inventory/u) + }) + + it('renders a recursive definition through a finite self-reference', () => { + const schema = canonicalizeSchema([{ kind: 'object', properties: [{ name: 'next', type: 0, optional: true }], indices: [] }], 0) + const digest = schemaDigest(schema) + const inventory: PersistenceSchemaInventory = { + formatVersion: 1, + roots: [{ key: 'SessionHeader', kind: 'header', digest, schema }], + types: [{ digest, schema, names: ['Recursive'], sources: [] }], + } + expect(renderPersistenceSchemaDefinitions(inventory)).toContain('| `next` | optional | [`Recursive`](#persistence-type-recursive) |') + }) + + it('uses intrinsic scalar labels even when an alias or reference supplied a name', () => { + const schema = canonicalizeSchema([{ kind: 'primitive', type: 'string' }], 0) + const digest = schemaDigest(schema) + const inventory: PersistenceSchemaInventory = { + formatVersion: 1, + roots: [], + types: [{ digest, schema, names: ['SessionHeader.agentPreset'], sources: [] }], + } + const rendered = renderPersistenceSchemaDefinitions(inventory) + expect(rendered).toContain('### `string`') + expect(rendered).not.toContain('### `SessionHeader.agentPreset`') + }) +}) diff --git a/scripts/render-persistence-schema.ts b/scripts/render-persistence-schema.ts new file mode 100644 index 0000000000..62bf5a4b78 --- /dev/null +++ b/scripts/render-persistence-schema.ts @@ -0,0 +1,179 @@ +/** Readable, linked persistence schemas rendered from the fingerprint inventory. */ + +import { githubSlug } from './verify-md-links.ts' +import { persistenceCatalogText, type PersistenceCatalogLocale } from './persistence-catalog-text.ts' +import { + canonicalizeSchema, + schemaChildren, + schemaDigest, + type CanonicalSchema, + type PersistenceSchemaInventory, + type PersistenceType, + type SchemaNode, +} from './persistence-schema-model.ts' + +interface TypeDisplay { + readonly type: PersistenceType + readonly label: string + readonly anchor: string +} + +function code(text: string): string { + return '`' + text.replaceAll('`', '\\`').replaceAll('|', '\\|') + '`' +} + +function sourcePath(source: string): string { + return source.replace(/:\d+(?::\d+)?$/u, '') +} + +function nodeAt(schema: CanonicalSchema, index: number): SchemaNode { + const node = schema.nodes[index] + if (!node) throw new Error(`persistence catalog: missing schema node ${String(index)}`) + return node +} + +function childPath(node: SchemaNode, position: number): string { + switch (node.kind) { + case 'object': return node.properties[position]?.name ?? `index-${String(position - node.properties.length)}` + case 'array': return 'item' + case 'tuple': return `item-${String(position)}` + case 'union': return `variant-${String(position)}` + case 'primitive': + case 'literal': + case 'opaque': return String(position) + default: return assertNever(node) + } +} + +function displays(inventory: PersistenceSchemaInventory): Map { + const paths = new Map() + for (const root of inventory.roots) { + const seen = new Set() + const visit = (index: number, path: string): void => { + if (seen.has(index)) return + seen.add(index) + const digest = schemaDigest(canonicalizeSchema(root.schema.nodes, index)) + if (!paths.has(digest)) paths.set(digest, path) + const node = nodeAt(root.schema, index) + schemaChildren(node).forEach((child, position) => { visit(child, `${path}.${childPath(node, position)}`) }) + } + visit(0, root.key) + } + const labels = inventory.types.map((type) => { + const node = nodeAt(type.schema, 0) + const label = node.kind === 'primitive' ? node.type + : node.kind === 'literal' ? JSON.stringify(node.value) + : node.kind === 'opaque' ? node.reason + : type.names[0] ?? paths.get(type.digest) ?? node.kind + return { type, label } + }) + const counts = new Map() + for (const { label } of labels) counts.set(label, (counts.get(label) ?? 0) + 1) + const used = new Set() + return new Map(labels.map(({ type, label: original }) => { + const label = (counts.get(original) ?? 0) > 1 + ? `${original} (${type.sources[0] ? sourcePath(type.sources[0]) : paths.get(type.digest) ?? 'anonymous'})` + : original + const base = `persistence-type-${githubSlug(label)}` + let anchor = base + for (let suffix = 2; used.has(anchor); suffix += 1) anchor = `${base}-${String(suffix)}` + used.add(anchor) + return [type.digest, { type, label, anchor }] + })) +} + +function reference(digest: string, entries: ReadonlyMap): string { + const entry = entries.get(digest) + if (!entry) throw new Error(`persistence catalog: reachable type ${digest} is absent from the inventory`) + return `[${code(entry.label)}](#${entry.anchor})` +} + +function typeExpression( + schema: CanonicalSchema, + index: number, + entries: ReadonlyMap, + locale: PersistenceCatalogLocale, +): string { + const node = nodeAt(schema, index) + if (node.kind === 'primitive') return code(node.type) + if (node.kind === 'literal') return code(JSON.stringify(node.value)) + if (node.kind === 'opaque') return `${code(node.reason)}${persistenceCatalogText[locale].opaque}` + return reference(schemaDigest(canonicalizeSchema(schema.nodes, index)), entries) +} + +function definition(entry: TypeDisplay, entries: ReadonlyMap, locale: PersistenceCatalogLocale): string[] { + const text = persistenceCatalogText[locale] + const schema = entry.type.schema + const node = nodeAt(schema, 0) + const lines = [``, '', `### ${code(entry.label)}`, '', `SHA-256: ${code(entry.type.digest)}`, ''] + if (entry.type.sources.length > 0) { + lines.push(`${text.sources}${entry.type.sources.map(source => `[${code(source)}](../${sourcePath(source)})`).join(' · ')}`, '') + } + const expression = (index: number): string => typeExpression(schema, index, entries, locale) + switch (node.kind) { + case 'object': + if (node.properties.length === 0 && node.indices.length === 0) lines.push(text.emptyObject, '') + else { + lines.push(text.propertyColumns, '|---|---|---|') + for (const property of node.properties) { + lines.push(`| ${code(property.name)} | ${property.optional ? text.optional : text.required} | ${expression(property.type)} |`) + } + for (const index of node.indices) lines.push(`| [${expression(index.key)}] | ${text.index} | ${expression(index.value)} |`) + lines.push('') + } + break + case 'array': lines.push(`${text.arrayPrefix}${expression(node.element)}${text.arraySuffix}`, ''); break + case 'tuple': + lines.push(text.positionColumns, '|---|---|---|') + node.elements.forEach((element, index) => { + const presence = element.rest ? text.rest : element.optional ? text.optional : text.required + lines.push(`| ${String(index)} | ${presence} | ${expression(element.type)} |`) + }) + lines.push('') + break + case 'union': + lines.push(text.oneOf, '', ...node.types.map(index => `- ${expression(index)}`), '') + break + case 'primitive': lines.push(code(node.type), ''); break + case 'literal': lines.push(code(JSON.stringify(node.value)), ''); break + case 'opaque': lines.push(`${code(node.reason)}${text.opaqueExplanation}`, ''); break + default: assertNever(node) + } + return lines +} + +/** + * Render every tracked root with its exact digest and resolved type reference. + * @param inventory - complete current-source schemas and declaration metadata. + * @param locale - generated document language. + * @returns Markdown index including the history and contributor workflow links. + */ +export function renderPersistenceSchemaIndex(inventory: PersistenceSchemaInventory, locale: PersistenceCatalogLocale = 'en'): string { + const entries = displays(inventory) + const text = persistenceCatalogText[locale] + return [ + `## ${text.fingerprints}`, '', text.fingerprintsIntro, '', text.historyIntro, '', + text.rootColumns, '|---|---|---|---|', + ...inventory.roots.map(root => `| ${code(root.key)} | ${root.kind} | ${code(root.digest)} | ${reference(root.digest, entries)} |`), '', + ].join('\n') +} + +/** + * Render every reachable type once, with links for shared and recursive definitions. + * @param inventory - complete current-source schemas and declaration metadata. + * @param locale - generated document language. + * @returns Markdown definitions whose anchors use names or owning paths instead of hashes. + */ +export function renderPersistenceSchemaDefinitions(inventory: PersistenceSchemaInventory, locale: PersistenceCatalogLocale = 'en'): string { + const entries = displays(inventory) + const text = persistenceCatalogText[locale] + const sorted = [...entries.values()].sort((left, right) => left.anchor < right.anchor ? -1 : left.anchor > right.anchor ? 1 : 0) + return [ + `## ${text.definitions}`, '', text.definitionsIntro, '', + ...sorted.flatMap(entry => definition(entry, entries, locale)), + ].join('\n') +} + +function assertNever(value: never): never { + throw new Error(`persistence catalog: unsupported type ${JSON.stringify(value)}`) +} diff --git a/scripts/run-gates.spec.ts b/scripts/run-gates.spec.ts index cd26963bab..e449c1e529 100644 --- a/scripts/run-gates.spec.ts +++ b/scripts/run-gates.spec.ts @@ -188,6 +188,12 @@ describe('gate graph validation', () => { expect(ids).toContain('public-repository-links') }) + it('keeps the concrete terminology policy in the documentation gate', () => { + const ids = withPnpmEntrypoint(() => gatesForMode('doc-sync').map(subject => subject.id)) + + expect(ids).toContain('concrete-terms') + }) + it('keeps package-group subsystem ownership in the documentation gate', () => { const ids = withPnpmEntrypoint(() => gatesForMode('doc-sync').map(subject => subject.id)) diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 9105d246e5..685408ef54 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -741,8 +741,10 @@ function docSyncLeafGates(options: { pnpmScript('tool-catalog', 'verify-tool-catalog', { label: 'tool catalog' }), pnpmScript('config-catalog', 'verify-config-catalog', { label: 'config catalog' }), pnpmScript('persistence-catalog', 'verify-persistence-catalog', { label: 'persistence catalog' }), + pnpmScript('persistence-changes', 'verify-persistence-changes', { label: 'persistence type history' }), pnpmScript('session-format-catalog', 'verify-session-format-catalog', { label: 'Session format catalog' }), pnpmScript('public-repository-links', 'verify-public-repository-links', { label: 'public repository links', quick: true }), + pnpmScript('concrete-terms', 'verify-concrete-terms', { label: 'concrete terms', quick: true }), pnpmScript('doc-refs', 'verify-doc-refs', { label: 'doc refs', quick: true }), pnpmScript('subsystem-pages', 'verify-subsystem-pages', { label: 'subsystem pages' }), pnpmScript('package-paths', 'verify-package-paths', { label: 'package paths' }), diff --git a/scripts/session-fixture-layout.spec.ts b/scripts/session-fixture-layout.spec.ts index 069bcc2a97..37908f4dda 100644 --- a/scripts/session-fixture-layout.spec.ts +++ b/scripts/session-fixture-layout.spec.ts @@ -143,7 +143,7 @@ describe('canonicalSessionFixture', () => { expect(canonicalSessionFixture(content)).toBe(content) }) - it.each([0, 1, 2])('rejects v%i sequence gaps and invalid provenance ranges with source line diagnostics', (version) => { + it.each([0, 1, 2])('rejects v%i sequence gaps and invalid source-event ranges with source line diagnostics', (version) => { const header = JSON.stringify({ type: 'session', version, id: 'historical', createdAt: 1, delegationDepth: 0, ...(version === 2 ? { isSeeded: false } : {}) }) expect(() => canonicalSessionFixture(`${header}\n{"type":"feedback/record","seq":3,"data":{"text":"gap"}}\n`, 'gap.jsonl')) .toThrow(/gap\.jsonl: session snapshot line 2:.*seq/) diff --git a/scripts/session-fixture-layout.ts b/scripts/session-fixture-layout.ts index d220a30b77..9feac22ba5 100644 --- a/scripts/session-fixture-layout.ts +++ b/scripts/session-fixture-layout.ts @@ -128,7 +128,7 @@ function parseFixtureRows(content: string, headerValue: unknown): SessionEvent[] // Versionless protocol fixtures and current projected snapshots use scalar // event rows. Current snapshots may contain owner-restored scrub tokens such // as `{{tools}}`; semantic replay restores those sidecars, while this layout - // gate owns only envelopes, provenance ranges, and one-event-per-row form. + // gate owns only envelopes, source-event ranges, and one-event-per-row form. const projectedCurrent = headerValue !== null && typeof headerValue === 'object' && !Array.isArray(headerValue) diff --git a/scripts/smoke-python-runtime.py b/scripts/smoke-python-runtime.py index d7fbbf0e45..ef07687986 100644 --- a/scripts/smoke-python-runtime.py +++ b/scripts/smoke-python-runtime.py @@ -2238,7 +2238,7 @@ def project_session_snapshot(records: list[dict[str, object]]) -> list[dict[str, return projected -SESSION_FORMAT_PROVENANCE = "{{sessionFormatVersion}}" +SESSION_FORMAT_TOKEN = "{{sessionFormatVersion}}" def expand_snapshot_stream_member(member: object) -> list[dict[str, object]]: @@ -2327,7 +2327,7 @@ def normalize_session_format_comparison( value: object, source_session_version: int | None = None, ) -> object: - """Canonicalize only generation provenance that differs across immutable Session files.""" + """Canonicalize only generation metadata that differs across immutable Session files.""" if isinstance(value, list): return [ normalize_session_format_comparison(expanded, source_session_version) @@ -2342,7 +2342,7 @@ def normalize_session_format_comparison( for key, item in value.items() } if normalized.get("type") == "session" and "version" in normalized: - normalized["version"] = SESSION_FORMAT_PROVENANCE + normalized["version"] = SESSION_FORMAT_TOKEN normalized.setdefault("isSeeded", False) ordered_header = { key: normalized[key] @@ -2362,7 +2362,7 @@ def normalize_session_format_comparison( def normalize_snapshot_comparison_text(name: str, content: str) -> str: - """Normalize Session generation provenance only while comparing committed expected outputs.""" + """Normalize Session generation metadata only while comparing committed expected outputs.""" if name.startswith("session") and name.endswith(".jsonl"): parsed = [json.loads(line) for line in content.splitlines() if line] header = parsed[0] if parsed else None diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index 332fbe0447..b0855b0911 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -8,7 +8,7 @@ }, { "doc": "docs/subsystems/llm-streaming.md", - "symbol": "AssistantProvenance", + "symbol": "AssistantProviderMetadata", "source": "packages/llm/llm/src/message.ts" }, { @@ -688,7 +688,7 @@ }, { "doc": "docs/subsystems/session-title.md", - "symbol": "SessionTitleModelProvenance", + "symbol": "SessionTitleModelIdentity", "source": "packages/session/session-title/src/types.ts" }, { diff --git a/scripts/verify-concrete-terms.spec.ts b/scripts/verify-concrete-terms.spec.ts new file mode 100644 index 0000000000..9932703d0b --- /dev/null +++ b/scripts/verify-concrete-terms.spec.ts @@ -0,0 +1,66 @@ +import { mkdtempSync, rmSync, symlinkSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' +import { findConcreteTermViolations, readTrackedSource } from './verify-concrete-terms.ts' + +const blockedTerm = 'prove' + 'nance' + +describe('concrete terminology policy', () => { + it('rejects case variants in paths, prose, and identifiers', () => { + expect(findConcreteTermViolations(`docs/${blockedTerm}-notes.md`, [ + 'origin metadata', + blockedTerm.toUpperCase(), + `Assistant${blockedTerm[0]?.toUpperCase()}${blockedTerm.slice(1)}`, + ].join('\n'))).toEqual([ + { file: `docs/${blockedTerm}-notes.md`, line: null }, + { file: `docs/${blockedTerm}-notes.md`, line: 2 }, + { file: `docs/${blockedTerm}-notes.md`, line: 3 }, + ]) + }) + + it('scans text that contains an embedded NUL', () => { + expect(findConcreteTermViolations('packages/example/src/source.ts', `scope\0${blockedTerm}`)) + .toEqual([{ file: 'packages/example/src/source.ts', line: 1 }]) + }) + + it('normalizes compatibility characters before scanning', () => { + const fullwidthTerm = blockedTerm.split('') + .map(character => String.fromCodePoint(character.charCodeAt(0) + 0xfee0)) + .join('') + expect(findConcreteTermViolations('packages/example/src/source.ts', fullwidthTerm)) + .toEqual([{ file: 'packages/example/src/source.ts', line: 1 }]) + }) + + it.skipIf(process.platform === 'win32')('reads the target of a dangling tracked symlink', () => { + const repoRoot = mkdtempSync(join(tmpdir(), 'dsh-concrete-terms-')) + try { + symlinkSync(`../${blockedTerm}-target`, join(repoRoot, 'tracked-link')) + expect(findConcreteTermViolations( + 'tracked-link', + readTrackedSource(repoRoot, 'tracked-link') ?? '', + )).toEqual([{ file: 'tracked-link', line: 1 }]) + } finally { + rmSync(repoRoot, { recursive: true, force: true }) + } + }) + + it('accepts exact replacement terms', () => { + expect(findConcreteTermViolations( + 'packages/example/src/origin.ts', + 'provider metadata; source-event references; artifact identity; browser-zone evidence', + )).toEqual([]) + }) + + it('excludes vendored sources and frozen Agent Notes', () => { + expect(findConcreteTermViolations(`vendor/example/${blockedTerm}.ts`, blockedTerm)).toEqual([]) + expect(findConcreteTermViolations( + `.agents/notes/archived/process/${blockedTerm}.md`, + blockedTerm, + )).toEqual([]) + expect(findConcreteTermViolations( + '.agents/notes/implemented/process/current.md', + blockedTerm, + )).toEqual([{ file: '.agents/notes/implemented/process/current.md', line: 1 }]) + }) +}) diff --git a/scripts/verify-concrete-terms.ts b/scripts/verify-concrete-terms.ts new file mode 100644 index 0000000000..b2b59eef40 --- /dev/null +++ b/scripts/verify-concrete-terms.ts @@ -0,0 +1,97 @@ +/** Reject one ambiguous origin label from maintained tracked files. */ + +import { execFileSync } from 'node:child_process' +import { lstatSync, readFileSync, readlinkSync } from 'node:fs' +import { resolve } from 'node:path' +import { pathToFileURL } from 'node:url' + +const root = resolve(import.meta.dirname, '..') +const blockedTerm = 'prove' + 'nance' +const excludedPrefixes = ['vendor/', '.agents/notes/archived/'] as const + +/** One blocked term occurrence in a tracked path or text line. */ +export interface ConcreteTermViolation { + /** Repository-relative tracked path. */ + file: string + /** One-based source line, or null when the path contains the term. */ + line: number | null +} + +function isExcluded(file: string): boolean { + return excludedPrefixes.some(prefix => file.startsWith(prefix)) +} + +function containsBlockedTerm(value: string): boolean { + return value.normalize('NFKC').toLowerCase().includes(blockedTerm) +} + +/** + * Find the blocked term in one maintained tracked file. + * @param file - repository-relative tracked path. + * @param source - text contents or symlink target. + * @returns path and line violations; vendored sources and frozen Agent Notes are excluded. + */ +export function findConcreteTermViolations(file: string, source: string): ConcreteTermViolation[] { + if (isExcluded(file)) return [] + const violations: ConcreteTermViolation[] = [] + if (containsBlockedTerm(file)) violations.push({ file, line: null }) + for (const [index, line] of source.split('\n').entries()) { + if (containsBlockedTerm(line)) violations.push({ file, line: index + 1 }) + } + return violations +} + +function trackedFiles(repoRoot: string): string[] { + const files = execFileSync('git', ['ls-files', '-z'], { cwd: repoRoot, encoding: 'utf8' }) + .split('\0') + .filter(file => file !== '') + if (!files.includes('AGENTS.md') + || !files.some(file => file.startsWith('packages/')) + || !files.some(file => file.startsWith('docs/')) + || !files.some(file => file.startsWith('vendor/')) + || !files.some(file => file.startsWith('.agents/notes/archived/'))) { + throw new Error('verify-concrete-terms: tracked-file discovery omitted a required repository area') + } + return files +} + +/** + * Read one tracked file without following a symlink to its target. + * @param repoRoot - Repository root containing the tracked path. + * @param file - Repository-relative tracked path. + * @returns File text, the symlink target, or undefined when the path is absent or not a file. + */ +export function readTrackedSource(repoRoot: string, file: string): string | undefined { + const path = resolve(repoRoot, file) + const stat = lstatSync(path, { throwIfNoEntry: false }) + if (stat === undefined) return undefined + if (stat.isSymbolicLink()) return readlinkSync(path) + return stat.isFile() ? readFileSync(path, 'utf8') : undefined +} + +function scanRepository(repoRoot: string): ConcreteTermViolation[] { + const violations: ConcreteTermViolation[] = [] + for (const file of trackedFiles(repoRoot)) { + const source = readTrackedSource(repoRoot, file) + if (source === undefined) continue + violations.push(...findConcreteTermViolations(file, source)) + } + return violations +} + +const invokedPath = process.argv[1] +const isMain = invokedPath !== undefined && import.meta.url === pathToFileURL(resolve(invokedPath)).href +if (isMain) { + const violations = scanRepository(root) + if (violations.length === 0) { + console.log(`verify-concrete-terms: maintained tracked files contain no ${blockedTerm}.`) + } else { + console.error(`verify-concrete-terms: ${blockedTerm} is forbidden; name the exact source, field, identity, or evidence:`) + for (const violation of violations) { + console.error(violation.line === null + ? ` ${violation.file} (path)` + : ` ${violation.file}:${String(violation.line)}`) + } + process.exitCode = 1 + } +} diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 448f3be1da..7e63b4edf2 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -65,7 +65,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/preset/agent-presets': { kind: 'indirect', reason: 'The mount installs a preset\'s own plugins, which own every model-facing registration it makes visible.' }, 'packages/typert/registry': { kind: 'none', reason: 'Runtime type registry; consumers (cordis_inspect, wire faces, gates) own any model-visible projection of registry contents.' }, 'packages/typert/loader': { kind: 'none', reason: 'Loader integration only registers generated artifacts; consumers own any model-visible projection.' }, - 'packages/e2b/e2b': { kind: 'none', reason: 'The shared remote-runtime owner registers no model context; provider adapters and consumers own rendered effects.' }, 'packages/util/http-proxy': { kind: 'none', reason: 'Transport policy only: it changes how bytes reach the network and registers no prompt, schema, or result text.' }, 'packages/client/hmr': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, 'packages/client/modules': { kind: 'none', reason: 'Browser-side module-loading kernel machinery; registers nothing model-facing.' }, @@ -123,7 +122,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/client/web': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, 'packages/context/file-reference': { kind: 'indirect', reason: 'The discovery seam and grammar delegate model guidance to the composed provider.' }, 'packages/fs/fs': { kind: 'indirect', reason: 'The service interface delegates model rendering to dsh-tool-fs.' }, - 'packages/e2b/fs-e2b': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' }, 'packages/fs/fs-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' }, 'packages/hooks/hook-protocol': { kind: 'indirect', reason: 'Only the hook bridge plugins render decoded hook output to a model.' }, 'packages/host/directory-picker': { kind: 'none', reason: 'The GUI-host picking seam registers nothing model-facing.' }, @@ -143,7 +141,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/lsp/lsp': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-lsp.' }, 'packages/lsp/lsp-stdio': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-lsp.' }, 'packages/subprocess/subprocess': { kind: 'indirect', reason: 'The seam delegates all model rendering to consumer seams such as the bash executor family.' }, - 'packages/e2b/subprocess-e2b': { kind: 'indirect', reason: 'The remote spawn backend delegates model rendering to consumer seams such as the bash executor family.' }, 'packages/subprocess/subprocess-local': { kind: 'indirect', reason: 'The spawn backend delegates model rendering to consumer seams such as the bash executor family.' }, 'packages/sandbox/sandbox-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-bash-sandbox and dsh-tool-bash.' }, 'packages/sandbox/sandbox-windows-acl': { kind: 'indirect', reason: 'The provider backend delegates model rendering to the shell/pwsh sandbox executors and their tools.' }, diff --git a/snapshots/sdk/sdk.snapshot.ts b/snapshots/sdk/sdk.snapshot.ts index b3a839a027..a4580498d3 100644 --- a/snapshots/sdk/sdk.snapshot.ts +++ b/snapshots/sdk/sdk.snapshot.ts @@ -20,7 +20,7 @@ import { assertSessionFixtureVersion, captureExpectedWorkspaceSnapshot, captureWorkspaceSnapshot, - normalizeSessionFormatProvenance, + normalizeSessionFormatMetadata, normalizeSessionLog, normalizeSessionSnapshots, normalizeStdout, @@ -368,7 +368,7 @@ function normalizeNotifications(notifications: readonly HarnessNotification[], c const normalizedEvents = events.length === 0 ? [] : scrubModelRequestBulk(normalizeSessionLog( - normalizeSessionFormatProvenance(typedLog), + normalizeSessionFormatMetadata(typedLog), ctx, typedFeedback ? { identityMode: 'preserve' } : {}, )).trimEnd().split('\n').map(line => JSON.parse(line) as Record) diff --git a/snapshots/session/compaction-recovery/session.v2.jsonl b/snapshots/session/compaction-recovery/session.v2.jsonl index 5c18237d46..13416ce1d8 100644 --- a/snapshots/session/compaction-recovery/session.v2.jsonl +++ b/snapshots/session/compaction-recovery/session.v2.jsonl @@ -2,11 +2,11 @@ {"type":"permission/preset","data":{"preset":"danger-full-access"}} {"type":"sandbox/mode","data":{"mode":"danger-full-access"}} {"type":"approval/policy","data":{"policy":"never"}} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Establish a durable compaction premise before continuing. Record every part of this historical evidence: the snapshot uses keyless replay; persistence uses JSONL; the assembled headless application loads its real Cordis composition; model-visible inputs remain logged; tool calls and results remain paired and ordered; context overflow retains the original failure while recovery is attempted; compaction opens with compaction/start and closes with compaction/end; a successful auxiliary summary records compaction/summary provenance; the replacement surface shadows only an older balanced range; the checkpoint remains smaller than the history it replaces; the newest tool result remains verbatim; the retried request sees that checkpoint; the final response proves the same turn continued; deterministic snapshot evidence stays separate from the live-provider smoke; and no external API key is needed. Emit one alpha marker through bash, then finish the task after any required recovery with the exact words COMPACTION RECOVERED."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Establish a durable compaction premise before continuing. Record every part of this historical evidence: the snapshot uses keyless replay; persistence uses JSONL; the assembled headless application loads its real Cordis composition; model-visible inputs remain logged; tool calls and results remain paired and ordered; context overflow retains the original failure while recovery is attempted; compaction opens with compaction/start and closes with compaction/end; a successful auxiliary summary records compaction/summary source metadata; the replacement surface shadows only an older balanced range; the checkpoint remains smaller than the history it replaces; the newest tool result remains verbatim; the retried request sees that checkpoint; the final response proves the same turn continued; deterministic snapshot evidence stays separate from the live-provider smoke; and no external API key is needed. Emit one alpha marker through bash, then finish the task after any required recovery with the exact words COMPACTION RECOVERED."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}} {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":1,"step":1}} -{"type":"user/message","data":{"content":[{"type":"text","text":"Establish a durable compaction premise before continuing. Record every part of this historical evidence: the snapshot uses keyless replay; persistence uses JSONL; the assembled headless application loads its real Cordis composition; model-visible inputs remain logged; tool calls and results remain paired and ordered; context overflow retains the original failure while recovery is attempted; compaction opens with compaction/start and closes with compaction/end; a successful auxiliary summary records compaction/summary provenance; the replacement surface shadows only an older balanced range; the checkpoint remains smaller than the history it replaces; the newest tool result remains verbatim; the retried request sees that checkpoint; the final response proves the same turn continued; deterministic snapshot evidence stays separate from the live-provider smoke; and no external API key is needed. Emit one alpha marker through bash, then finish the task after any required recovery with the exact words COMPACTION RECOVERED."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Establish a durable compaction premise before continuing. Record every part of this historical evidence: the snapshot uses keyless replay; persistence uses JSONL; the assembled headless application loads its real Cordis composition; model-visible inputs remain logged; tool calls and results remain paired and ordered; context overflow retains the original failure while recovery is attempted; compaction opens with compaction/start and closes with compaction/end; a successful auxiliary summary records compaction/summary source metadata; the replacement surface shadows only an older balanced range; the checkpoint remains smaller than the history it replaces; the newest tool result remains verbatim; the retried request sees that checkpoint; the final response proves the same turn continued; deterministic snapshot evidence stays separate from the live-provider smoke; and no external API key is needed. Emit one alpha marker through bash, then finish the task after any required recovery with the exact words COMPACTION RECOVERED."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:2}}"},"surfaceOp":"append"} {"type":"session/title","data":{"title":"Establish a durable compaction premise","messageSeqs":[7],"source":{"kind":"fallback"}}} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} @@ -18,7 +18,7 @@ {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/attempt","data":{"turn":1,"step":2,"stream":[{"type":"chunk","time":1788233487095,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"snapshot request exceeded the model context window","code":"CONTEXT_WINDOW_EXCEEDED"}}}}]}} {"type":"compaction/start","data":{"compactionId":"{{id:1}}","turn":1}} -{"type":"compaction/summary","data":{"compactionId":"{{id:1}}","summary":[{"type":"text","text":"The request established a durable compaction premise."}],"rawOutput":[{"type":"text","text":"The request established a durable compaction premise."}],"llmStreamCall":true,"shadowedRange":{"start":7,"end":8},"shadowedSeqs":[7,8],"shadowedTokenCount":372,"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":32,"usage":{"inputTokens":20,"outputTokens":4}}} +{"type":"compaction/summary","data":{"compactionId":"{{id:1}}","summary":[{"type":"text","text":"The request established a durable compaction premise."}],"rawOutput":[{"type":"text","text":"The request established a durable compaction premise."}],"llmStreamCall":true,"shadowedRange":{"start":7,"end":8},"shadowedSeqs":[7,8],"shadowedTokenCount":373,"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":32,"usage":{"inputTokens":20,"outputTokens":4}}} {"type":"user/message","data":{"content":[{"type":"text","text":"This is an automatically generated checkpoint condensing an earlier span of the conversation to free up context. Treat the captured context as established background and build on it without restating it. Continue the task directly from the messages that follow, without acknowledging this checkpoint.\n\n"},{"type":"text","text":"The request established a durable compaction premise."},{"type":"text","text":""}],"source":{"kind":"plugin","plugin":"compact","compactionId":"{{id:1}}"},"role":"user","id":"{{message:5}}"},"sourceEventSeqs":[18,19,7,8],"surfaceOp":{"op":"replace","start":7,"end":8}} {"type":"compaction/end","data":{"compactionId":"{{id:1}}","turn":1}} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"series"}} diff --git a/snapshots/session/compaction-recovery/session.v3.jsonl b/snapshots/session/compaction-recovery/session.v3.jsonl index f731a05123..14bb80640b 100644 --- a/snapshots/session/compaction-recovery/session.v3.jsonl +++ b/snapshots/session/compaction-recovery/session.v3.jsonl @@ -2,12 +2,12 @@ {"type":"permission/preset","data":{"preset":"danger-full-access"}} {"type":"sandbox/mode","data":{"mode":"danger-full-access"}} {"type":"approval/policy","data":{"policy":"never"}} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Establish a durable compaction premise before continuing. Record every part of this historical evidence: the snapshot uses keyless replay; persistence uses JSONL; the assembled headless application loads its real Cordis composition; model-visible inputs remain logged; tool calls and results remain paired and ordered; context overflow retains the original failure while recovery is attempted; compaction opens with compaction/start and closes with compaction/end; a successful auxiliary summary records compaction/summary provenance; the replacement surface shadows only an older balanced range; the checkpoint remains smaller than the history it replaces; the newest tool result remains verbatim; the retried request sees that checkpoint; the final response proves the same turn continued; deterministic snapshot evidence stays separate from the live-provider smoke; and no external API key is needed. Emit one alpha marker through bash, then finish the task after any required recovery with the exact words COMPACTION RECOVERED."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Establish a durable compaction premise before continuing. Record every part of this historical evidence: the snapshot uses keyless replay; persistence uses JSONL; the assembled headless application loads its real Cordis composition; model-visible inputs remain logged; tool calls and results remain paired and ordered; context overflow retains the original failure while recovery is attempted; compaction opens with compaction/start and closes with compaction/end; a successful auxiliary summary records compaction/summary source metadata; the replacement surface shadows only an older balanced range; the checkpoint remains smaller than the history it replaces; the newest tool result remains verbatim; the retried request sees that checkpoint; the final response proves the same turn continued; deterministic snapshot evidence stays separate from the live-provider smoke; and no external API key is needed. Emit one alpha marker through bash, then finish the task after any required recovery with the exact words COMPACTION RECOVERED."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}} {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":1,"step":1}} {"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Establish a durable compaction premise before continuing. Record every part of this historical evidence: the snapshot uses keyless replay; persistence uses JSONL; the assembled headless application loads its real Cordis composition; model-visible inputs remain logged; tool calls and results remain paired and ordered; context overflow retains the original failure while recovery is attempted; compaction opens with compaction/start and closes with compaction/end; a successful auxiliary summary records compaction/summary provenance; the replacement surface shadows only an older balanced range; the checkpoint remains smaller than the history it replaces; the newest tool result remains verbatim; the retried request sees that checkpoint; the final response proves the same turn continued; deterministic snapshot evidence stays separate from the live-provider smoke; and no external API key is needed. Emit one alpha marker through bash, then finish the task after any required recovery with the exact words COMPACTION RECOVERED."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Establish a durable compaction premise before continuing. Record every part of this historical evidence: the snapshot uses keyless replay; persistence uses JSONL; the assembled headless application loads its real Cordis composition; model-visible inputs remain logged; tool calls and results remain paired and ordered; context overflow retains the original failure while recovery is attempted; compaction opens with compaction/start and closes with compaction/end; a successful auxiliary summary records compaction/summary source metadata; the replacement surface shadows only an older balanced range; the checkpoint remains smaller than the history it replaces; the newest tool result remains verbatim; the retried request sees that checkpoint; the final response proves the same turn continued; deterministic snapshot evidence stays separate from the live-provider smoke; and no external API key is needed. Emit one alpha marker through bash, then finish the task after any required recovery with the exact words COMPACTION RECOVERED."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}} @@ -19,7 +19,7 @@ {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/attempt","data":{"turn":1,"step":2,"stream":[{"type":"chunk","time":1788699353021,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"snapshot request exceeded the model context window","code":"CONTEXT_WINDOW_EXCEEDED"}}}}]}} {"type":"compaction/start","data":{"compactionId":"{{id:1}}","turn":1}} -{"type":"compaction/summary","data":{"compactionId":"{{id:1}}","summary":[{"type":"text","text":"The request established a durable compaction premise."}],"rawOutput":[{"type":"text","text":"The request established a durable compaction premise."}],"llmStreamCall":true,"shadowedRange":{"start":8,"end":9},"shadowedSeqs":[8,9],"shadowedTokenCount":372,"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":32,"usage":{"inputTokens":20,"outputTokens":4}}} +{"type":"compaction/summary","data":{"compactionId":"{{id:1}}","summary":[{"type":"text","text":"The request established a durable compaction premise."}],"rawOutput":[{"type":"text","text":"The request established a durable compaction premise."}],"llmStreamCall":true,"shadowedRange":{"start":8,"end":9},"shadowedSeqs":[8,9],"shadowedTokenCount":373,"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":32,"usage":{"inputTokens":20,"outputTokens":4}}} {"type":"user/message","data":{"content":[{"type":"text","text":"This is an automatically generated checkpoint condensing an earlier span of the conversation to free up context. Treat the captured context as established background and build on it without restating it. Continue the task directly from the messages that follow, without acknowledging this checkpoint.\n\n"},{"type":"text","text":"The request established a durable compaction premise."},{"type":"text","text":""}],"source":{"kind":"plugin","plugin":"compact","compactionId":"{{id:1}}"},"role":"user","id":"{{message:6}}"},"sourceEventSeqs":[19,20,8,9],"surfaceOp":{"op":"replace","startSeq":8,"endSeq":9}} {"type":"compaction/end","data":{"compactionId":"{{id:1}}","turn":1}} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"series"}} diff --git a/snapshots/session/cordis-inspect-jsdoc/session.v2.jsonl b/snapshots/session/cordis-inspect-jsdoc/session.v2.jsonl index 36188dda08..10638c75d7 100644 --- a/snapshots/session/cordis-inspect-jsdoc/session.v2.jsonl +++ b/snapshots/session/cordis-inspect-jsdoc/session.v2.jsonl @@ -13,7 +13,7 @@ {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:3}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"inspect-tools-api","name":"cordis_inspect_query","args":["{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"inspect-tools-api"},"content":[{"type":"tool-result","toolCallId":"inspect-tools-api","content":[{"type":"text","text":"{\n \"platform\": \"host\",\n \"provider\": \"Service\",\n \"method\": \"listService\",\n \"data\": {\n \"mode\": \"service\",\n \"service\": {\n \"key\": \"tools\",\n \"description\": \"Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch.\",\n \"access\": {\n \"optional\": {\n \"expression\": \"ctx.get(\\\"tools\\\")\",\n \"requiresUndefinedCheck\": true\n },\n \"hardDependency\": {\n \"inject\": [\n \"tools\"\n ],\n \"expression\": \"ctx.tools\"\n }\n },\n \"methods\": [\n {\n \"signature\": \"presentAs(mode: ToolPresentationMode): () => void\",\n \"description\": \"Present the calling scope's tools in `mode` instead of the deployment default. Nearest scope on the chain wins, so a preset's standing declaration covers every agent joined under it.\\n\\nScoped only, and one declaration per scope: this is how an agent preset composes PTC mode agents beside native ones in the same process, and a process-global override would be the `mode` config field instead.\",\n \"parameters\": [\n {\n \"name\": \"mode\",\n \"description\": \"the presentation the covered agents' models see.\"\n }\n ],\n \"returns\": \"the exact disposer that restores the deployment default.\"\n },\n {\n \"signature\": \"register(definition: ToolDefinition): () => void\",\n \"description\": \"Register globally or in the calling agent scope. Scoped tools shadow globals; duplicates within one layer and the reserved `run_code` name fail.\",\n \"parameters\": [\n {\n \"name\": \"definition\",\n \"description\": \"tool schema, execution, and optional finalization/presentation callbacks.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the tool.\"\n },\n {\n \"signature\": \"restrict(filter: ToolRestriction): () => void\",\n \"description\": \"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.\",\n \"parameters\": [\n {\n \"name\": \"filter\",\n \"description\": \"global-tool mask: `allow` (keep only) and/or `deny` (remove).\"\n }\n ],\n \"returns\": \"the exact disposer that lifts this restriction.\"\n },\n {\n \"signature\": \"guard(guard: ToolGuard): () => void\",\n \"description\": \"Register a monotonic guard after the extensible `tools/pre-execute` waterfall. A plain-context guard applies globally; one registered through `agent.ctx` applies only to that agent. Any matching guard may deny by returning a reason, while no guard can force-allow a call another guard denied. The exact effect disposer is returned for ordered ownership and HMR cleanup.\",\n \"parameters\": [\n {\n \"name\": \"guard\",\n \"description\": \"synchronous check; a returned string denies the execution.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the guard.\"\n },\n {\n \"signature\": \"get(name: string, scope?: ScopeKey): ToolDefinition | undefined\",\n \"description\": \"Look up a tool as one scope sees it (scoped shadows global; a restricted-away global reads as absent). Presenters pass the calling agent so the rendered card matches the definition that actually executed.\",\n \"parameters\": [\n {\n \"name\": \"name\",\n \"description\": \"the tool name as registered.\"\n },\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"the definition the scope resolves, or undefined when none is visible.\"\n },\n {\n \"signature\": \"schemas(scope?: ScopeKey): ToolSchema[]\",\n \"description\": \"Project visible definitions onto the allowlisted model-facing schema fields, excluding execution and presentation callbacks.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"one deep-cloned schema per visible tool.\"\n },\n {\n \"signature\": \"executionMode(exec: ToolExecutionInput): ToolExecutionMode\",\n \"description\": \"Classify a pending call through the caller's visible tool definition. Only an exact `true` is parallel; unknown, hidden, undeclared, invalid, or throwing classifiers are exclusive.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"call name, parsed arguments, and optional agent scope.\"\n }\n ],\n \"returns\": \"the fail-closed scheduling mode.\"\n },\n {\n \"signature\": \"async execute(exec: ToolExecutionInput): Promise\",\n \"description\": \"Execute through pre-policy, guards, around-dispatch, post-policy, definition-owned content finalization, and final notification. Tool and listener failures resolve as materialized error results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen snapshot final observers receive. Cancellation arriving after entry and before final result materialization skips a not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a successful started outcome with `ABORTED`; already-started work is still drained and may retain a tool-owned structured error.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"the typed same-process call input. The registry assigns its correlation token before policy begins.\"\n }\n ],\n \"returns\": \"the materialized final result.\"\n }\n ]\n },\n \"referencedTypes\": [\n {\n \"name\": \"Agent\",\n \"declaration\": \"export interface Agent {\\n readonly id: SessionId;\\n}\"\n },\n {\n \"name\": \"AssistantProvenance\",\n \"declaration\": \"export interface AssistantProvenance {\\n provider: string;\\n model: string;\\n replayState?: unknown;\\n}\"\n },\n {\n \"name\": \"Branded\",\n \"declaration\": \"export type Branded = string & {\\n readonly [BRAND]: B;\\n};\"\n },\n {\n \"name\": \"ContextFormed\",\n \"declaration\": \"export type ContextFormed = {\\n readonly form?: never;\\n} | {\\n readonly form: 'instructions';\\n} | {\\n readonly form: 'catalog';\\n} | {\\n readonly form: 'snapshot';\\n readonly sections: readonly ContextSnapshotSection[];\\n} | {\\n readonly form: 'notice';\\n readonly summary: string;\\n} | {\\n readonly form: 'relay';\\n} | {\\n readonly form: 'recall';\\n};\"\n },\n {\n \"name\": \"ContextSnapshotSection\",\n \"declaration\": \"export interface ContextSnapshotSection {\\n readonly name: string;\\n readonly text: string;\\n}\"\n },\n {\n \"name\": \"DiffCallView\",\n \"declaration\": \"export interface DiffCallView {\\n card: 'diff';\\n title: string;\\n diffs: FileDiff[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"DiffResultView\",\n \"declaration\": \"export interface DiffResultView {\\n card: 'diff';\\n title?: string;\\n diffs: FileDiff[];\\n}\"\n },\n {\n \"name\": \"FileDiff\",\n \"declaration\": \"export interface FileDiff {\\n path: string;\\n oldText: string | null;\\n newText: string;\\n}\"\n },\n {\n \"name\": \"FileLocation\",\n \"declaration\": \"export interface FileLocation {\\n path: string;\\n line?: number;\\n}\"\n },\n {\n \"name\": \"GenericCallView\",\n \"declaration\": \"export interface GenericCallView {\\n card: 'generic';\\n title: string;\\n kind?: ToolCallKind;\\n rawInput?: unknown;\\n content?: ContentBlock[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"GenericResultView\",\n \"declaration\": \"export interface GenericResultView {\\n card: 'generic';\\n title?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"JsonSchemaNode\",\n \"declaration\": \"export interface JsonSchemaNode {\\n type?: JsonSchemaType;\\n oneOf?: JsonSchemaNode[];\\n properties?: Record;\\n required?: string[];\\n additionalProperties?: boolean;\\n items?: JsonSchemaNode;\\n enum?: JsonSchemaScalar[];\\n const?: JsonSchemaScalar;\\n description?: string;\\n title?: string;\\n default?: JsonValue;\\n examples?: JsonValue;\\n}\"\n },\n {\n \"name\": \"JsonSchemaScalar\",\n \"declaration\": \"export type JsonSchemaScalar = string | number | boolean | null;\"\n },\n {\n \"name\": \"JsonSchemaType\",\n \"declaration\": \"export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';\"\n },\n {\n \"name\": \"JsonValue\",\n \"declaration\": \"export type JsonValue = null | boolean | number | string | JsonValue[] | {\\n [key: string]: JsonValue;\\n};\"\n },\n {\n \"name\": \"Message\",\n \"declaration\": \"export interface Message {\\n readonly id: MessageId;\\n readonly role: 'system' | 'user' | 'assistant';\\n readonly content: ContentBlock[];\\n readonly source: MessageSource;\\n}\"\n },\n {\n \"name\": \"MessageId\",\n \"declaration\": \"export type MessageId = Branded<'MessageId'>;\"\n },\n {\n \"name\": \"MessageSource\",\n \"declaration\": \"export type MessageSource = MessageSourceMap[keyof MessageSourceMap];\"\n },\n {\n \"name\": \"MessageSourceMap\",\n \"declaration\": \"export interface MessageSourceMap {\\n user: {\\n kind: 'user';\\n };\\n plugin: {\\n kind: 'plugin';\\n plugin: string;\\n } & ContextFormed;\\n model: ModelMessageSource;\\n tool: ToolMessageSource;\\n}\"\n },\n {\n \"name\": \"ModelMessageSource\",\n \"declaration\": \"export interface ModelMessageSource extends AssistantProvenance {\\n kind: 'model';\\n}\"\n },\n {\n \"name\": \"ReadFileLine\",\n \"declaration\": \"export interface ReadFileLine {\\n number: number;\\n text: string;\\n}\"\n },\n {\n \"name\": \"ReadResultView\",\n \"declaration\": \"export interface ReadResultView {\\n card: 'read';\\n title?: string;\\n path: string;\\n offset: number;\\n lines: ReadFileLine[];\\n totalLines: number;\\n lang?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"ScopeKey\",\n \"declaration\": \"export type ScopeKey = object;\"\n },\n {\n \"name\": \"SearchFileMatches\",\n \"declaration\": \"export interface SearchFileMatches {\\n path: string;\\n matches: SearchLineMatch[];\\n}\"\n },\n {\n \"name\": \"SearchLineMatch\",\n \"declaration\": \"export interface SearchLineMatch {\\n lineNumber: number;\\n line: string;\\n}\"\n },\n {\n \"name\": \"SearchMatchesResultView\",\n \"declaration\": \"export interface SearchMatchesResultView {\\n card: 'search';\\n shape: 'matches';\\n title?: string;\\n files: SearchFileMatches[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchPathsResultView\",\n \"declaration\": \"export interface SearchPathsResultView {\\n card: 'search';\\n shape: 'paths';\\n title?: string;\\n paths: string[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchResultView\",\n \"declaration\": \"export type SearchResultView = SearchMatchesResultView | SearchPathsResultView;\"\n },\n {\n \"name\": \"SessionId\",\n \"declaration\": \"export type SessionId = Branded<'SessionId'>;\"\n },\n {\n \"name\": \"TerminalCallView\",\n \"declaration\": \"export interface TerminalCallView {\\n card: 'terminal';\\n title: string;\\n description?: string;\\n cwd?: string;\\n}\"\n },\n {\n \"name\": \"TerminalResultView\",\n \"declaration\": \"export interface TerminalResultView {\\n card: 'terminal';\\n title?: string;\\n output?: string;\\n exitCode?: number;\\n signal?: string;\\n}\"\n },\n {\n \"name\": \"ToolCallKind\",\n \"declaration\": \"export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other';\"\n },\n {\n \"name\": \"ToolCallView\",\n \"declaration\": \"export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView;\"\n },\n {\n \"name\": \"ToolDefinition\",\n \"declaration\": \"export interface ToolDefinition extends ToolSchema {\\n readonly output: ToolOutputDefinition;\\n execute(args: unknown, exec: ToolRunContext): Promise;\\n finalizeContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\\n timeoutMs?: number;\\n isConcurrencySafe?(args: unknown): boolean;\\n presentCall?(args: unknown): ToolCallView | undefined;\\n presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined;\\n}\"\n },\n {\n \"name\": \"ToolErrorInfo\",\n \"declaration\": \"export interface ToolErrorInfo {\\n name: string;\\n code: string;\\n}\"\n },\n {\n \"name\": \"ToolExecution\",\n \"declaration\": \"export interface ToolExecution extends ToolExecutionInput {\\n readonly rootCallId: ToolCallId;\\n readonly token: ToolExecutionToken;\\n}\"\n },\n {\n \"name\": \"ToolExecutionFailure\",\n \"declaration\": \"export interface ToolExecutionFailure {\\n readonly isError: true;\\n readonly error: ToolFailure;\\n readonly value?: never;\\n readonly content: ContentBlock[];\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: never;\\n}\"\n },\n {\n \"name\": \"ToolExecutionInput\",\n \"declaration\": \"export interface ToolExecutionInput {\\n readonly callId: ToolCallId;\\n readonly rootCallId?: ToolCallId;\\n readonly name: string;\\n readonly arguments: unknown;\\n readonly agent?: Agent;\\n readonly parent?: ToolExecutionToken;\\n readonly signal: AbortSignal;\\n}\"\n },\n {\n \"name\": \"ToolExecutionMode\",\n \"declaration\": \"export type ToolExecutionMode = {\\n kind: 'parallel';\\n} | {\\n kind: 'exclusive';\\n};\"\n },\n {\n \"name\": \"ToolExecutionResult\",\n \"declaration\": \"export type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure;\"\n },\n {\n \"name\": \"ToolExecutionSuccess\",\n \"declaration\": \"export interface ToolExecutionSuccess {\\n readonly isError: false;\\n readonly value: JsonValue;\\n readonly content: ContentBlock[];\\n readonly error?: never;\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: true;\\n}\"\n },\n {\n \"name\": \"ToolExecutionToken\",\n \"declaration\": \"export type ToolExecutionToken = symbol & {\\n readonly [toolExecutionTokenBrand]: true;\\n};\"\n },\n {\n \"name\": \"ToolFailure\",\n \"declaration\": \"export interface ToolFailure {\\n message: string;\\n info?: ToolErrorInfo;\\n}\"\n },\n {\n \"name\": \"ToolGuard\",\n \"declaration\": \"export type ToolGuard = (execution: Readonly) => string | undefined;\"\n },\n {\n \"name\": \"ToolMessageSource\",\n \"declaration\": \"export interface ToolMessageSource {\\n kind: 'tool';\\n callId: ToolCallId;\\n}\"\n },\n {\n \"name\": \"ToolOutputDefinition\",\n \"declaration\": \"export interface ToolOutputDefinition {\\n readonly schema: JsonSchemaNode;\\n render(args: unknown, value: JsonValue): ContentBlock[];\\n presentationMeta?(args: unknown, value: JsonValue): JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolPresentationMode\",\n \"declaration\": \"export type ToolPresentationMode = 'native' | 'ptc' | 'both';\"\n },\n {\n \"name\": \"ToolRestriction\",\n \"declaration\": \"export interface ToolRestriction {\\n readonly allow?: readonly string[];\\n readonly deny?: readonly string[];\\n}\"\n },\n {\n \"name\": \"ToolResult\",\n \"declaration\": \"export interface ToolResult {\\n content: ContentBlock[];\\n isError: boolean;\\n meta?: JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolResultView\",\n \"declaration\": \"export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView;\"\n },\n {\n \"name\": \"ToolRunContext\",\n \"declaration\": \"export interface ToolRunContext extends ToolExecution {\\n deferContext(context: UserMessage): void;\\n concludeTurn(): void;\\n}\"\n },\n {\n \"name\": \"ToolSchema\",\n \"declaration\": \"export interface ToolSchema {\\n name: string;\\n description: string;\\n parameters: Record;\\n}\"\n },\n {\n \"name\": \"UserMessage\",\n \"declaration\": \"export interface UserMessage extends Message {\\n readonly role: 'user';\\n}\"\n },\n {\n \"name\": \"WebFetchResultView\",\n \"declaration\": \"export interface WebFetchResultView {\\n card: 'web';\\n kind: 'fetch';\\n title?: string;\\n url: string;\\n statusCode: number;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebResultView\",\n \"declaration\": \"export type WebResultView = WebSearchResultView | WebFetchResultView;\"\n },\n {\n \"name\": \"WebSearchResultView\",\n \"declaration\": \"export interface WebSearchResultView {\\n card: 'web';\\n kind: 'search';\\n title?: string;\\n sources: WebSource[];\\n answer?: string;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebSource\",\n \"declaration\": \"export interface WebSource {\\n url: string;\\n title?: string;\\n snippet?: string;\\n publishedAt?: string;\\n}\"\n }\n ]\n }\n}"}],"isError":false}],"role":"user","id":"{{message:4}}"}},"sourceEventSeqs":[13],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"inspect-tools-api"},"content":[{"type":"tool-result","toolCallId":"inspect-tools-api","content":[{"type":"text","text":"{\n \"platform\": \"host\",\n \"provider\": \"Service\",\n \"method\": \"listService\",\n \"data\": {\n \"mode\": \"service\",\n \"service\": {\n \"key\": \"tools\",\n \"description\": \"Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch.\",\n \"access\": {\n \"optional\": {\n \"expression\": \"ctx.get(\\\"tools\\\")\",\n \"requiresUndefinedCheck\": true\n },\n \"hardDependency\": {\n \"inject\": [\n \"tools\"\n ],\n \"expression\": \"ctx.tools\"\n }\n },\n \"methods\": [\n {\n \"signature\": \"presentAs(mode: ToolPresentationMode): () => void\",\n \"description\": \"Present the calling scope's tools in `mode` instead of the deployment default. Nearest scope on the chain wins, so a preset's standing declaration covers every agent joined under it.\\n\\nScoped only, and one declaration per scope: this is how an agent preset composes PTC mode agents beside native ones in the same process, and a process-global override would be the `mode` config field instead.\",\n \"parameters\": [\n {\n \"name\": \"mode\",\n \"description\": \"the presentation the covered agents' models see.\"\n }\n ],\n \"returns\": \"the exact disposer that restores the deployment default.\"\n },\n {\n \"signature\": \"register(definition: ToolDefinition): () => void\",\n \"description\": \"Register globally or in the calling agent scope. Scoped tools shadow globals; duplicates within one layer and the reserved `run_code` name fail.\",\n \"parameters\": [\n {\n \"name\": \"definition\",\n \"description\": \"tool schema, execution, and optional finalization/presentation callbacks.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the tool.\"\n },\n {\n \"signature\": \"restrict(filter: ToolRestriction): () => void\",\n \"description\": \"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.\",\n \"parameters\": [\n {\n \"name\": \"filter\",\n \"description\": \"global-tool mask: `allow` (keep only) and/or `deny` (remove).\"\n }\n ],\n \"returns\": \"the exact disposer that lifts this restriction.\"\n },\n {\n \"signature\": \"guard(guard: ToolGuard): () => void\",\n \"description\": \"Register a monotonic guard after the extensible `tools/pre-execute` waterfall. A plain-context guard applies globally; one registered through `agent.ctx` applies only to that agent. Any matching guard may deny by returning a reason, while no guard can force-allow a call another guard denied. The exact effect disposer is returned for ordered ownership and HMR cleanup.\",\n \"parameters\": [\n {\n \"name\": \"guard\",\n \"description\": \"synchronous check; a returned string denies the execution.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the guard.\"\n },\n {\n \"signature\": \"get(name: string, scope?: ScopeKey): ToolDefinition | undefined\",\n \"description\": \"Look up a tool as one scope sees it (scoped shadows global; a restricted-away global reads as absent). Presenters pass the calling agent so the rendered card matches the definition that actually executed.\",\n \"parameters\": [\n {\n \"name\": \"name\",\n \"description\": \"the tool name as registered.\"\n },\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"the definition the scope resolves, or undefined when none is visible.\"\n },\n {\n \"signature\": \"schemas(scope?: ScopeKey): ToolSchema[]\",\n \"description\": \"Project visible definitions onto the allowlisted model-facing schema fields, excluding execution and presentation callbacks.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"one deep-cloned schema per visible tool.\"\n },\n {\n \"signature\": \"executionMode(exec: ToolExecutionInput): ToolExecutionMode\",\n \"description\": \"Classify a pending call through the caller's visible tool definition. Only an exact `true` is parallel; unknown, hidden, undeclared, invalid, or throwing classifiers are exclusive.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"call name, parsed arguments, and optional agent scope.\"\n }\n ],\n \"returns\": \"the fail-closed scheduling mode.\"\n },\n {\n \"signature\": \"async execute(exec: ToolExecutionInput): Promise\",\n \"description\": \"Execute through pre-policy, guards, around-dispatch, post-policy, definition-owned content finalization, and final notification. Tool and listener failures resolve as materialized error results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen snapshot final observers receive. Cancellation arriving after entry and before final result materialization skips a not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a successful started outcome with `ABORTED`; already-started work is still drained and may retain a tool-owned structured error.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"the typed same-process call input. The registry assigns its correlation token before policy begins.\"\n }\n ],\n \"returns\": \"the materialized final result.\"\n }\n ]\n },\n \"referencedTypes\": [\n {\n \"name\": \"Agent\",\n \"declaration\": \"export interface Agent {\\n readonly id: SessionId;\\n}\"\n },\n {\n \"name\": \"AssistantProviderMetadata\",\n \"declaration\": \"export interface AssistantProviderMetadata {\\n provider: string;\\n model: string;\\n replayState?: unknown;\\n}\"\n },\n {\n \"name\": \"Branded\",\n \"declaration\": \"export type Branded = string & {\\n readonly [BRAND]: B;\\n};\"\n },\n {\n \"name\": \"ContextFormed\",\n \"declaration\": \"export type ContextFormed = {\\n readonly form?: never;\\n} | {\\n readonly form: 'instructions';\\n} | {\\n readonly form: 'catalog';\\n} | {\\n readonly form: 'snapshot';\\n readonly sections: readonly ContextSnapshotSection[];\\n} | {\\n readonly form: 'notice';\\n readonly summary: string;\\n} | {\\n readonly form: 'relay';\\n} | {\\n readonly form: 'recall';\\n};\"\n },\n {\n \"name\": \"ContextSnapshotSection\",\n \"declaration\": \"export interface ContextSnapshotSection {\\n readonly name: string;\\n readonly text: string;\\n}\"\n },\n {\n \"name\": \"DiffCallView\",\n \"declaration\": \"export interface DiffCallView {\\n card: 'diff';\\n title: string;\\n diffs: FileDiff[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"DiffResultView\",\n \"declaration\": \"export interface DiffResultView {\\n card: 'diff';\\n title?: string;\\n diffs: FileDiff[];\\n}\"\n },\n {\n \"name\": \"FileDiff\",\n \"declaration\": \"export interface FileDiff {\\n path: string;\\n oldText: string | null;\\n newText: string;\\n}\"\n },\n {\n \"name\": \"FileLocation\",\n \"declaration\": \"export interface FileLocation {\\n path: string;\\n line?: number;\\n}\"\n },\n {\n \"name\": \"GenericCallView\",\n \"declaration\": \"export interface GenericCallView {\\n card: 'generic';\\n title: string;\\n kind?: ToolCallKind;\\n rawInput?: unknown;\\n content?: ContentBlock[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"GenericResultView\",\n \"declaration\": \"export interface GenericResultView {\\n card: 'generic';\\n title?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"JsonSchemaNode\",\n \"declaration\": \"export interface JsonSchemaNode {\\n type?: JsonSchemaType;\\n oneOf?: JsonSchemaNode[];\\n properties?: Record;\\n required?: string[];\\n additionalProperties?: boolean;\\n items?: JsonSchemaNode;\\n enum?: JsonSchemaScalar[];\\n const?: JsonSchemaScalar;\\n description?: string;\\n title?: string;\\n default?: JsonValue;\\n examples?: JsonValue;\\n}\"\n },\n {\n \"name\": \"JsonSchemaScalar\",\n \"declaration\": \"export type JsonSchemaScalar = string | number | boolean | null;\"\n },\n {\n \"name\": \"JsonSchemaType\",\n \"declaration\": \"export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';\"\n },\n {\n \"name\": \"JsonValue\",\n \"declaration\": \"export type JsonValue = null | boolean | number | string | JsonValue[] | {\\n [key: string]: JsonValue;\\n};\"\n },\n {\n \"name\": \"Message\",\n \"declaration\": \"export interface Message {\\n readonly id: MessageId;\\n readonly role: 'system' | 'user' | 'assistant';\\n readonly content: ContentBlock[];\\n readonly source: MessageSource;\\n}\"\n },\n {\n \"name\": \"MessageId\",\n \"declaration\": \"export type MessageId = Branded<'MessageId'>;\"\n },\n {\n \"name\": \"MessageSource\",\n \"declaration\": \"export type MessageSource = MessageSourceMap[keyof MessageSourceMap];\"\n },\n {\n \"name\": \"MessageSourceMap\",\n \"declaration\": \"export interface MessageSourceMap {\\n user: {\\n kind: 'user';\\n };\\n plugin: {\\n kind: 'plugin';\\n plugin: string;\\n } & ContextFormed;\\n model: ModelMessageSource;\\n tool: ToolMessageSource;\\n}\"\n },\n {\n \"name\": \"ModelMessageSource\",\n \"declaration\": \"export interface ModelMessageSource extends AssistantProviderMetadata {\\n kind: 'model';\\n}\"\n },\n {\n \"name\": \"ReadFileLine\",\n \"declaration\": \"export interface ReadFileLine {\\n number: number;\\n text: string;\\n}\"\n },\n {\n \"name\": \"ReadResultView\",\n \"declaration\": \"export interface ReadResultView {\\n card: 'read';\\n title?: string;\\n path: string;\\n offset: number;\\n lines: ReadFileLine[];\\n totalLines: number;\\n lang?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"ScopeKey\",\n \"declaration\": \"export type ScopeKey = object;\"\n },\n {\n \"name\": \"SearchFileMatches\",\n \"declaration\": \"export interface SearchFileMatches {\\n path: string;\\n matches: SearchLineMatch[];\\n}\"\n },\n {\n \"name\": \"SearchLineMatch\",\n \"declaration\": \"export interface SearchLineMatch {\\n lineNumber: number;\\n line: string;\\n}\"\n },\n {\n \"name\": \"SearchMatchesResultView\",\n \"declaration\": \"export interface SearchMatchesResultView {\\n card: 'search';\\n shape: 'matches';\\n title?: string;\\n files: SearchFileMatches[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchPathsResultView\",\n \"declaration\": \"export interface SearchPathsResultView {\\n card: 'search';\\n shape: 'paths';\\n title?: string;\\n paths: string[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchResultView\",\n \"declaration\": \"export type SearchResultView = SearchMatchesResultView | SearchPathsResultView;\"\n },\n {\n \"name\": \"SessionId\",\n \"declaration\": \"export type SessionId = Branded<'SessionId'>;\"\n },\n {\n \"name\": \"TerminalCallView\",\n \"declaration\": \"export interface TerminalCallView {\\n card: 'terminal';\\n title: string;\\n description?: string;\\n cwd?: string;\\n}\"\n },\n {\n \"name\": \"TerminalResultView\",\n \"declaration\": \"export interface TerminalResultView {\\n card: 'terminal';\\n title?: string;\\n output?: string;\\n exitCode?: number;\\n signal?: string;\\n}\"\n },\n {\n \"name\": \"ToolCallKind\",\n \"declaration\": \"export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other';\"\n },\n {\n \"name\": \"ToolCallView\",\n \"declaration\": \"export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView;\"\n },\n {\n \"name\": \"ToolDefinition\",\n \"declaration\": \"export interface ToolDefinition extends ToolSchema {\\n readonly output: ToolOutputDefinition;\\n execute(args: unknown, exec: ToolRunContext): Promise;\\n finalizeContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\\n timeoutMs?: number;\\n isConcurrencySafe?(args: unknown): boolean;\\n presentCall?(args: unknown): ToolCallView | undefined;\\n presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined;\\n}\"\n },\n {\n \"name\": \"ToolErrorInfo\",\n \"declaration\": \"export interface ToolErrorInfo {\\n name: string;\\n code: string;\\n}\"\n },\n {\n \"name\": \"ToolExecution\",\n \"declaration\": \"export interface ToolExecution extends ToolExecutionInput {\\n readonly rootCallId: ToolCallId;\\n readonly token: ToolExecutionToken;\\n}\"\n },\n {\n \"name\": \"ToolExecutionFailure\",\n \"declaration\": \"export interface ToolExecutionFailure {\\n readonly isError: true;\\n readonly error: ToolFailure;\\n readonly value?: never;\\n readonly content: ContentBlock[];\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: never;\\n}\"\n },\n {\n \"name\": \"ToolExecutionInput\",\n \"declaration\": \"export interface ToolExecutionInput {\\n readonly callId: ToolCallId;\\n readonly rootCallId?: ToolCallId;\\n readonly name: string;\\n readonly arguments: unknown;\\n readonly agent?: Agent;\\n readonly parent?: ToolExecutionToken;\\n readonly signal: AbortSignal;\\n}\"\n },\n {\n \"name\": \"ToolExecutionMode\",\n \"declaration\": \"export type ToolExecutionMode = {\\n kind: 'parallel';\\n} | {\\n kind: 'exclusive';\\n};\"\n },\n {\n \"name\": \"ToolExecutionResult\",\n \"declaration\": \"export type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure;\"\n },\n {\n \"name\": \"ToolExecutionSuccess\",\n \"declaration\": \"export interface ToolExecutionSuccess {\\n readonly isError: false;\\n readonly value: JsonValue;\\n readonly content: ContentBlock[];\\n readonly error?: never;\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: true;\\n}\"\n },\n {\n \"name\": \"ToolExecutionToken\",\n \"declaration\": \"export type ToolExecutionToken = symbol & {\\n readonly [toolExecutionTokenBrand]: true;\\n};\"\n },\n {\n \"name\": \"ToolFailure\",\n \"declaration\": \"export interface ToolFailure {\\n message: string;\\n info?: ToolErrorInfo;\\n}\"\n },\n {\n \"name\": \"ToolGuard\",\n \"declaration\": \"export type ToolGuard = (execution: Readonly) => string | undefined;\"\n },\n {\n \"name\": \"ToolMessageSource\",\n \"declaration\": \"export interface ToolMessageSource {\\n kind: 'tool';\\n callId: ToolCallId;\\n}\"\n },\n {\n \"name\": \"ToolOutputDefinition\",\n \"declaration\": \"export interface ToolOutputDefinition {\\n readonly schema: JsonSchemaNode;\\n render(args: unknown, value: JsonValue): ContentBlock[];\\n presentationMeta?(args: unknown, value: JsonValue): JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolPresentationMode\",\n \"declaration\": \"export type ToolPresentationMode = 'native' | 'ptc' | 'both';\"\n },\n {\n \"name\": \"ToolRestriction\",\n \"declaration\": \"export interface ToolRestriction {\\n readonly allow?: readonly string[];\\n readonly deny?: readonly string[];\\n}\"\n },\n {\n \"name\": \"ToolResult\",\n \"declaration\": \"export interface ToolResult {\\n content: ContentBlock[];\\n isError: boolean;\\n meta?: JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolResultView\",\n \"declaration\": \"export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView;\"\n },\n {\n \"name\": \"ToolRunContext\",\n \"declaration\": \"export interface ToolRunContext extends ToolExecution {\\n deferContext(context: UserMessage): void;\\n concludeTurn(): void;\\n}\"\n },\n {\n \"name\": \"ToolSchema\",\n \"declaration\": \"export interface ToolSchema {\\n name: string;\\n description: string;\\n parameters: Record;\\n}\"\n },\n {\n \"name\": \"UserMessage\",\n \"declaration\": \"export interface UserMessage extends Message {\\n readonly role: 'user';\\n}\"\n },\n {\n \"name\": \"WebFetchResultView\",\n \"declaration\": \"export interface WebFetchResultView {\\n card: 'web';\\n kind: 'fetch';\\n title?: string;\\n url: string;\\n statusCode: number;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebResultView\",\n \"declaration\": \"export type WebResultView = WebSearchResultView | WebFetchResultView;\"\n },\n {\n \"name\": \"WebSearchResultView\",\n \"declaration\": \"export interface WebSearchResultView {\\n card: 'web';\\n kind: 'search';\\n title?: string;\\n sources: WebSource[];\\n answer?: string;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebSource\",\n \"declaration\": \"export interface WebSource {\\n url: string;\\n title?: string;\\n snippet?: string;\\n publishedAt?: string;\\n}\"\n }\n ]\n }\n}"}],"isError":false}],"role":"user","id":"{{message:4}}"}},"sourceEventSeqs":[13],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"inspect-subprocess-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:5}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"inspect-subprocess-api","name":"cordis_inspect_query","args":["{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"inspect-subprocess-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} diff --git a/snapshots/session/cordis-inspect-jsdoc/session.v3.jsonl b/snapshots/session/cordis-inspect-jsdoc/session.v3.jsonl index 3de980d577..50cb016716 100644 --- a/snapshots/session/cordis-inspect-jsdoc/session.v3.jsonl +++ b/snapshots/session/cordis-inspect-jsdoc/session.v3.jsonl @@ -14,7 +14,7 @@ {"type":"session/title","data":{"title":"Inspect the exact tools service","messageSeqs":[8],"source":{"kind":"fallback"}}} {"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1788778145981,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788778145981,"index":0,"dt":[],"id":"inspect-tools-api","name":"cordis_inspect_query","args":["{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"]},{"type":"chunk","time":1788778145981,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}}},{"type":"chunk","time":1788778145981,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1788778145981,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"inspect-tools-api"},"content":[{"type":"tool-result","toolCallId":"inspect-tools-api","content":[{"type":"text","text":"{\n \"platform\": \"host\",\n \"provider\": \"Service\",\n \"method\": \"listService\",\n \"data\": {\n \"mode\": \"service\",\n \"service\": {\n \"key\": \"tools\",\n \"description\": \"Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch.\",\n \"access\": {\n \"optional\": {\n \"expression\": \"ctx.get(\\\"tools\\\")\",\n \"requiresUndefinedCheck\": true\n },\n \"hardDependency\": {\n \"inject\": [\n \"tools\"\n ],\n \"expression\": \"ctx.tools\"\n }\n },\n \"methods\": [\n {\n \"signature\": \"presentAs(mode: ToolPresentationMode): () => void\",\n \"description\": \"Present the calling scope's tools in `mode` instead of the deployment default. Nearest scope on the chain wins, so a preset's standing declaration covers every agent joined under it.\\n\\nScoped only, and one declaration per scope: this is how an agent preset composes PTC mode agents beside native ones in the same process, and a process-global override would be the `mode` config field instead.\",\n \"parameters\": [\n {\n \"name\": \"mode\",\n \"description\": \"the presentation the covered agents' models see.\"\n }\n ],\n \"returns\": \"the exact disposer that restores the deployment default.\"\n },\n {\n \"signature\": \"register(definition: ToolDefinition): () => void\",\n \"description\": \"Register globally or in the calling agent scope. Scoped tools shadow globals; duplicates within one layer and the reserved `run_code` name fail.\",\n \"parameters\": [\n {\n \"name\": \"definition\",\n \"description\": \"tool schema, execution, and optional finalization/presentation callbacks.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the tool.\"\n },\n {\n \"signature\": \"restrict(filter: ToolRestriction): () => void\",\n \"description\": \"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.\",\n \"parameters\": [\n {\n \"name\": \"filter\",\n \"description\": \"global-tool mask: `allow` (keep only) and/or `deny` (remove).\"\n }\n ],\n \"returns\": \"the exact disposer that lifts this restriction.\"\n },\n {\n \"signature\": \"guard(guard: ToolGuard): () => void\",\n \"description\": \"Register a monotonic guard after the extensible `tools/pre-execute` waterfall. A plain-context guard applies globally; one registered through `agent.ctx` applies only to that agent. Any matching guard may deny by returning a reason, while no guard can force-allow a call another guard denied. The exact effect disposer is returned for ordered ownership and HMR cleanup.\",\n \"parameters\": [\n {\n \"name\": \"guard\",\n \"description\": \"synchronous check; a returned string denies the execution.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the guard.\"\n },\n {\n \"signature\": \"get(name: string, scope?: ScopeKey): ToolDefinition | undefined\",\n \"description\": \"Look up a tool as one scope sees it (scoped shadows global; a restricted-away global reads as absent). Presenters pass the calling agent so the rendered card matches the definition that actually executed.\",\n \"parameters\": [\n {\n \"name\": \"name\",\n \"description\": \"the tool name as registered.\"\n },\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"the definition the scope resolves, or undefined when none is visible.\"\n },\n {\n \"signature\": \"schemas(scope?: ScopeKey): ToolSchema[]\",\n \"description\": \"Project visible definitions onto the allowlisted model-facing schema fields, excluding execution and presentation callbacks.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"one deep-cloned schema per visible tool.\"\n },\n {\n \"signature\": \"executionMode(exec: ToolExecutionInput): ToolExecutionMode\",\n \"description\": \"Classify a pending call through the caller's visible tool definition. Only an exact `true` is parallel; unknown, hidden, undeclared, invalid, or throwing classifiers are exclusive.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"call name, parsed arguments, and optional agent scope.\"\n }\n ],\n \"returns\": \"the fail-closed scheduling mode.\"\n },\n {\n \"signature\": \"async execute(exec: ToolExecutionInput): Promise\",\n \"description\": \"Execute through pre-policy, guards, around-dispatch, post-policy, definition-owned content finalization, and final notification. Tool and listener failures resolve as materialized error results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen snapshot final observers receive. Cancellation arriving after entry and before final result materialization skips a not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a successful started outcome with `ABORTED`; already-started work is still drained and may retain a tool-owned structured error.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"the typed same-process call input. The registry assigns its correlation token before policy begins.\"\n }\n ],\n \"returns\": \"the materialized final result.\"\n }\n ]\n },\n \"referencedTypes\": [\n {\n \"name\": \"Agent\",\n \"declaration\": \"export interface Agent {\\n readonly id: SessionId;\\n}\"\n },\n {\n \"name\": \"AssistantProvenance\",\n \"declaration\": \"export interface AssistantProvenance {\\n provider: string;\\n model: string;\\n replayState?: unknown;\\n}\"\n },\n {\n \"name\": \"Branded\",\n \"declaration\": \"export type Branded = string & {\\n readonly [BRAND]: B;\\n};\"\n },\n {\n \"name\": \"ContextFormed\",\n \"declaration\": \"export type ContextFormed = {\\n readonly form?: never;\\n} | {\\n readonly form: 'instructions';\\n} | {\\n readonly form: 'catalog';\\n} | {\\n readonly form: 'snapshot';\\n readonly sections: readonly ContextSnapshotSection[];\\n} | {\\n readonly form: 'notice';\\n readonly summary: string;\\n} | {\\n readonly form: 'relay';\\n} | {\\n readonly form: 'recall';\\n};\"\n },\n {\n \"name\": \"ContextSnapshotSection\",\n \"declaration\": \"export interface ContextSnapshotSection {\\n readonly name: string;\\n readonly text: string;\\n}\"\n },\n {\n \"name\": \"DiffCallView\",\n \"declaration\": \"export interface DiffCallView {\\n card: 'diff';\\n title: string;\\n diffs: FileDiff[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"DiffResultView\",\n \"declaration\": \"export interface DiffResultView {\\n card: 'diff';\\n title?: string;\\n diffs: FileDiff[];\\n}\"\n },\n {\n \"name\": \"FileDiff\",\n \"declaration\": \"export interface FileDiff {\\n path: string;\\n oldText: string | null;\\n newText: string;\\n}\"\n },\n {\n \"name\": \"FileLocation\",\n \"declaration\": \"export interface FileLocation {\\n path: string;\\n line?: number;\\n}\"\n },\n {\n \"name\": \"GenericCallView\",\n \"declaration\": \"export interface GenericCallView {\\n card: 'generic';\\n title: string;\\n kind?: ToolCallKind;\\n rawInput?: unknown;\\n content?: ContentBlock[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"GenericResultView\",\n \"declaration\": \"export interface GenericResultView {\\n card: 'generic';\\n title?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"JsonSchemaNode\",\n \"declaration\": \"export interface JsonSchemaNode {\\n type?: JsonSchemaType;\\n oneOf?: JsonSchemaNode[];\\n properties?: Record;\\n required?: string[];\\n additionalProperties?: boolean;\\n items?: JsonSchemaNode;\\n enum?: JsonSchemaScalar[];\\n const?: JsonSchemaScalar;\\n description?: string;\\n title?: string;\\n default?: JsonValue;\\n examples?: JsonValue;\\n}\"\n },\n {\n \"name\": \"JsonSchemaScalar\",\n \"declaration\": \"export type JsonSchemaScalar = string | number | boolean | null;\"\n },\n {\n \"name\": \"JsonSchemaType\",\n \"declaration\": \"export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';\"\n },\n {\n \"name\": \"JsonValue\",\n \"declaration\": \"export type JsonValue = null | boolean | number | string | JsonValue[] | {\\n [key: string]: JsonValue;\\n};\"\n },\n {\n \"name\": \"Message\",\n \"declaration\": \"export interface Message {\\n readonly id: MessageId;\\n readonly role: 'system' | 'user' | 'assistant';\\n readonly content: ContentBlock[];\\n readonly source: MessageSource;\\n}\"\n },\n {\n \"name\": \"MessageId\",\n \"declaration\": \"export type MessageId = Branded<'MessageId'>;\"\n },\n {\n \"name\": \"MessageSource\",\n \"declaration\": \"export type MessageSource = MessageSourceMap[keyof MessageSourceMap];\"\n },\n {\n \"name\": \"MessageSourceMap\",\n \"declaration\": \"export interface MessageSourceMap {\\n user: {\\n kind: 'user';\\n };\\n plugin: {\\n kind: 'plugin';\\n plugin: string;\\n } & ContextFormed;\\n model: ModelMessageSource;\\n tool: ToolMessageSource;\\n}\"\n },\n {\n \"name\": \"ModelMessageSource\",\n \"declaration\": \"export interface ModelMessageSource extends AssistantProvenance {\\n kind: 'model';\\n}\"\n },\n {\n \"name\": \"ReadFileLine\",\n \"declaration\": \"export interface ReadFileLine {\\n number: number;\\n text: string;\\n}\"\n },\n {\n \"name\": \"ReadResultView\",\n \"declaration\": \"export interface ReadResultView {\\n card: 'read';\\n title?: string;\\n path: string;\\n offset: number;\\n lines: ReadFileLine[];\\n totalLines: number;\\n lang?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"ScopeKey\",\n \"declaration\": \"export type ScopeKey = object;\"\n },\n {\n \"name\": \"SearchFileMatches\",\n \"declaration\": \"export interface SearchFileMatches {\\n path: string;\\n matches: SearchLineMatch[];\\n}\"\n },\n {\n \"name\": \"SearchLineMatch\",\n \"declaration\": \"export interface SearchLineMatch {\\n lineNumber: number;\\n line: string;\\n}\"\n },\n {\n \"name\": \"SearchMatchesResultView\",\n \"declaration\": \"export interface SearchMatchesResultView {\\n card: 'search';\\n shape: 'matches';\\n title?: string;\\n files: SearchFileMatches[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchPathsResultView\",\n \"declaration\": \"export interface SearchPathsResultView {\\n card: 'search';\\n shape: 'paths';\\n title?: string;\\n paths: string[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchResultView\",\n \"declaration\": \"export type SearchResultView = SearchMatchesResultView | SearchPathsResultView;\"\n },\n {\n \"name\": \"SessionId\",\n \"declaration\": \"export type SessionId = Branded<'SessionId'>;\"\n },\n {\n \"name\": \"TerminalCallView\",\n \"declaration\": \"export interface TerminalCallView {\\n card: 'terminal';\\n title: string;\\n description?: string;\\n cwd?: string;\\n}\"\n },\n {\n \"name\": \"TerminalResultView\",\n \"declaration\": \"export interface TerminalResultView {\\n card: 'terminal';\\n title?: string;\\n output?: string;\\n exitCode?: number;\\n signal?: string;\\n}\"\n },\n {\n \"name\": \"ToolCallKind\",\n \"declaration\": \"export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other';\"\n },\n {\n \"name\": \"ToolCallView\",\n \"declaration\": \"export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView;\"\n },\n {\n \"name\": \"ToolDefinition\",\n \"declaration\": \"export interface ToolDefinition extends ToolSchema {\\n readonly output: ToolOutputDefinition;\\n execute(args: unknown, exec: ToolRunContext): Promise;\\n finalizeContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\\n timeoutMs?: number;\\n isConcurrencySafe?(args: unknown): boolean;\\n presentCall?(args: unknown): ToolCallView | undefined;\\n presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined;\\n}\"\n },\n {\n \"name\": \"ToolErrorInfo\",\n \"declaration\": \"export interface ToolErrorInfo {\\n name: string;\\n code: string;\\n reason?: string;\\n}\"\n },\n {\n \"name\": \"ToolExecution\",\n \"declaration\": \"export interface ToolExecution extends ToolExecutionInput {\\n readonly rootCallId: ToolCallId;\\n readonly token: ToolExecutionToken;\\n}\"\n },\n {\n \"name\": \"ToolExecutionFailure\",\n \"declaration\": \"export interface ToolExecutionFailure {\\n readonly isError: true;\\n readonly error: ToolFailure;\\n readonly value?: never;\\n readonly content: ContentBlock[];\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: never;\\n}\"\n },\n {\n \"name\": \"ToolExecutionInput\",\n \"declaration\": \"export interface ToolExecutionInput {\\n readonly callId: ToolCallId;\\n readonly rootCallId?: ToolCallId;\\n readonly name: string;\\n readonly schema?: ToolSchema;\\n readonly arguments: unknown;\\n readonly agent?: Agent;\\n readonly parent?: ToolExecutionToken;\\n readonly signal: AbortSignal;\\n}\"\n },\n {\n \"name\": \"ToolExecutionMode\",\n \"declaration\": \"export type ToolExecutionMode = {\\n kind: 'parallel';\\n} | {\\n kind: 'exclusive';\\n};\"\n },\n {\n \"name\": \"ToolExecutionResult\",\n \"declaration\": \"export type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure;\"\n },\n {\n \"name\": \"ToolExecutionSuccess\",\n \"declaration\": \"export interface ToolExecutionSuccess {\\n readonly isError: false;\\n readonly value: JsonValue;\\n readonly content: ContentBlock[];\\n readonly error?: never;\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: true;\\n}\"\n },\n {\n \"name\": \"ToolExecutionToken\",\n \"declaration\": \"export type ToolExecutionToken = symbol & {\\n readonly [toolExecutionTokenBrand]: true;\\n};\"\n },\n {\n \"name\": \"ToolFailure\",\n \"declaration\": \"export interface ToolFailure {\\n message: string;\\n info?: ToolErrorInfo;\\n}\"\n },\n {\n \"name\": \"ToolGuard\",\n \"declaration\": \"export type ToolGuard = (execution: Readonly) => string | undefined;\"\n },\n {\n \"name\": \"ToolMessageSource\",\n \"declaration\": \"export interface ToolMessageSource {\\n kind: 'tool';\\n callId: ToolCallId;\\n}\"\n },\n {\n \"name\": \"ToolOutputDefinition\",\n \"declaration\": \"export interface ToolOutputDefinition {\\n readonly schema: JsonSchemaNode;\\n render(args: unknown, value: JsonValue): ContentBlock[];\\n presentationMeta?(args: unknown, value: JsonValue): JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolPresentationMode\",\n \"declaration\": \"export type ToolPresentationMode = 'native' | 'ptc' | 'both';\"\n },\n {\n \"name\": \"ToolRestriction\",\n \"declaration\": \"export interface ToolRestriction {\\n readonly allow?: readonly string[];\\n readonly deny?: readonly string[];\\n}\"\n },\n {\n \"name\": \"ToolResult\",\n \"declaration\": \"export interface ToolResult {\\n content: ContentBlock[];\\n isError: boolean;\\n meta?: JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolResultView\",\n \"declaration\": \"export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView;\"\n },\n {\n \"name\": \"ToolRunContext\",\n \"declaration\": \"export interface ToolRunContext extends ToolExecution {\\n deferContext(context: UserMessage): void;\\n concludeTurn(): void;\\n}\"\n },\n {\n \"name\": \"ToolSchema\",\n \"declaration\": \"export interface ToolSchema {\\n name: string;\\n description: string;\\n parameters: Record;\\n}\"\n },\n {\n \"name\": \"UserMessage\",\n \"declaration\": \"export interface UserMessage extends Message {\\n readonly role: 'user';\\n}\"\n },\n {\n \"name\": \"WebFetchResultView\",\n \"declaration\": \"export interface WebFetchResultView {\\n card: 'web';\\n kind: 'fetch';\\n title?: string;\\n url: string;\\n statusCode: number;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebResultView\",\n \"declaration\": \"export type WebResultView = WebSearchResultView | WebFetchResultView;\"\n },\n {\n \"name\": \"WebSearchResultView\",\n \"declaration\": \"export interface WebSearchResultView {\\n card: 'web';\\n kind: 'search';\\n title?: string;\\n sources: WebSource[];\\n answer?: string;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebSource\",\n \"declaration\": \"export interface WebSource {\\n url: string;\\n title?: string;\\n snippet?: string;\\n publishedAt?: string;\\n}\"\n }\n ]\n }\n}"}],"isError":false}],"role":"user","id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"inspect-tools-api"},"content":[{"type":"tool-result","toolCallId":"inspect-tools-api","content":[{"type":"text","text":"{\n \"platform\": \"host\",\n \"provider\": \"Service\",\n \"method\": \"listService\",\n \"data\": {\n \"mode\": \"service\",\n \"service\": {\n \"key\": \"tools\",\n \"description\": \"Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch.\",\n \"access\": {\n \"optional\": {\n \"expression\": \"ctx.get(\\\"tools\\\")\",\n \"requiresUndefinedCheck\": true\n },\n \"hardDependency\": {\n \"inject\": [\n \"tools\"\n ],\n \"expression\": \"ctx.tools\"\n }\n },\n \"methods\": [\n {\n \"signature\": \"presentAs(mode: ToolPresentationMode): () => void\",\n \"description\": \"Present the calling scope's tools in `mode` instead of the deployment default. Nearest scope on the chain wins, so a preset's standing declaration covers every agent joined under it.\\n\\nScoped only, and one declaration per scope: this is how an agent preset composes PTC mode agents beside native ones in the same process, and a process-global override would be the `mode` config field instead.\",\n \"parameters\": [\n {\n \"name\": \"mode\",\n \"description\": \"the presentation the covered agents' models see.\"\n }\n ],\n \"returns\": \"the exact disposer that restores the deployment default.\"\n },\n {\n \"signature\": \"register(definition: ToolDefinition): () => void\",\n \"description\": \"Register globally or in the calling agent scope. Scoped tools shadow globals; duplicates within one layer and the reserved `run_code` name fail.\",\n \"parameters\": [\n {\n \"name\": \"definition\",\n \"description\": \"tool schema, execution, and optional finalization/presentation callbacks.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the tool.\"\n },\n {\n \"signature\": \"restrict(filter: ToolRestriction): () => void\",\n \"description\": \"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.\",\n \"parameters\": [\n {\n \"name\": \"filter\",\n \"description\": \"global-tool mask: `allow` (keep only) and/or `deny` (remove).\"\n }\n ],\n \"returns\": \"the exact disposer that lifts this restriction.\"\n },\n {\n \"signature\": \"guard(guard: ToolGuard): () => void\",\n \"description\": \"Register a monotonic guard after the extensible `tools/pre-execute` waterfall. A plain-context guard applies globally; one registered through `agent.ctx` applies only to that agent. Any matching guard may deny by returning a reason, while no guard can force-allow a call another guard denied. The exact effect disposer is returned for ordered ownership and HMR cleanup.\",\n \"parameters\": [\n {\n \"name\": \"guard\",\n \"description\": \"synchronous check; a returned string denies the execution.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the guard.\"\n },\n {\n \"signature\": \"get(name: string, scope?: ScopeKey): ToolDefinition | undefined\",\n \"description\": \"Look up a tool as one scope sees it (scoped shadows global; a restricted-away global reads as absent). Presenters pass the calling agent so the rendered card matches the definition that actually executed.\",\n \"parameters\": [\n {\n \"name\": \"name\",\n \"description\": \"the tool name as registered.\"\n },\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"the definition the scope resolves, or undefined when none is visible.\"\n },\n {\n \"signature\": \"schemas(scope?: ScopeKey): ToolSchema[]\",\n \"description\": \"Project visible definitions onto the allowlisted model-facing schema fields, excluding execution and presentation callbacks.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"one deep-cloned schema per visible tool.\"\n },\n {\n \"signature\": \"executionMode(exec: ToolExecutionInput): ToolExecutionMode\",\n \"description\": \"Classify a pending call through the caller's visible tool definition. Only an exact `true` is parallel; unknown, hidden, undeclared, invalid, or throwing classifiers are exclusive.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"call name, parsed arguments, and optional agent scope.\"\n }\n ],\n \"returns\": \"the fail-closed scheduling mode.\"\n },\n {\n \"signature\": \"async execute(exec: ToolExecutionInput): Promise\",\n \"description\": \"Execute through pre-policy, guards, around-dispatch, post-policy, definition-owned content finalization, and final notification. Tool and listener failures resolve as materialized error results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen snapshot final observers receive. Cancellation arriving after entry and before final result materialization skips a not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a successful started outcome with `ABORTED`; already-started work is still drained and may retain a tool-owned structured error.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"the typed same-process call input. The registry assigns its correlation token before policy begins.\"\n }\n ],\n \"returns\": \"the materialized final result.\"\n }\n ]\n },\n \"referencedTypes\": [\n {\n \"name\": \"Agent\",\n \"declaration\": \"export interface Agent {\\n readonly id: SessionId;\\n}\"\n },\n {\n \"name\": \"AssistantProviderMetadata\",\n \"declaration\": \"export interface AssistantProviderMetadata {\\n provider: string;\\n model: string;\\n replayState?: unknown;\\n}\"\n },\n {\n \"name\": \"Branded\",\n \"declaration\": \"export type Branded = string & {\\n readonly [BRAND]: B;\\n};\"\n },\n {\n \"name\": \"ContextFormed\",\n \"declaration\": \"export type ContextFormed = {\\n readonly form?: never;\\n} | {\\n readonly form: 'instructions';\\n} | {\\n readonly form: 'catalog';\\n} | {\\n readonly form: 'snapshot';\\n readonly sections: readonly ContextSnapshotSection[];\\n} | {\\n readonly form: 'notice';\\n readonly summary: string;\\n} | {\\n readonly form: 'relay';\\n} | {\\n readonly form: 'recall';\\n};\"\n },\n {\n \"name\": \"ContextSnapshotSection\",\n \"declaration\": \"export interface ContextSnapshotSection {\\n readonly name: string;\\n readonly text: string;\\n}\"\n },\n {\n \"name\": \"DiffCallView\",\n \"declaration\": \"export interface DiffCallView {\\n card: 'diff';\\n title: string;\\n diffs: FileDiff[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"DiffResultView\",\n \"declaration\": \"export interface DiffResultView {\\n card: 'diff';\\n title?: string;\\n diffs: FileDiff[];\\n}\"\n },\n {\n \"name\": \"FileDiff\",\n \"declaration\": \"export interface FileDiff {\\n path: string;\\n oldText: string | null;\\n newText: string;\\n}\"\n },\n {\n \"name\": \"FileLocation\",\n \"declaration\": \"export interface FileLocation {\\n path: string;\\n line?: number;\\n}\"\n },\n {\n \"name\": \"GenericCallView\",\n \"declaration\": \"export interface GenericCallView {\\n card: 'generic';\\n title: string;\\n kind?: ToolCallKind;\\n rawInput?: unknown;\\n content?: ContentBlock[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"GenericResultView\",\n \"declaration\": \"export interface GenericResultView {\\n card: 'generic';\\n title?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"JsonSchemaNode\",\n \"declaration\": \"export interface JsonSchemaNode {\\n type?: JsonSchemaType;\\n oneOf?: JsonSchemaNode[];\\n properties?: Record;\\n required?: string[];\\n additionalProperties?: boolean;\\n items?: JsonSchemaNode;\\n enum?: JsonSchemaScalar[];\\n const?: JsonSchemaScalar;\\n description?: string;\\n title?: string;\\n default?: JsonValue;\\n examples?: JsonValue;\\n}\"\n },\n {\n \"name\": \"JsonSchemaScalar\",\n \"declaration\": \"export type JsonSchemaScalar = string | number | boolean | null;\"\n },\n {\n \"name\": \"JsonSchemaType\",\n \"declaration\": \"export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';\"\n },\n {\n \"name\": \"JsonValue\",\n \"declaration\": \"export type JsonValue = null | boolean | number | string | JsonValue[] | {\\n [key: string]: JsonValue;\\n};\"\n },\n {\n \"name\": \"Message\",\n \"declaration\": \"export interface Message {\\n readonly id: MessageId;\\n readonly role: 'system' | 'user' | 'assistant';\\n readonly content: ContentBlock[];\\n readonly source: MessageSource;\\n}\"\n },\n {\n \"name\": \"MessageId\",\n \"declaration\": \"export type MessageId = Branded<'MessageId'>;\"\n },\n {\n \"name\": \"MessageSource\",\n \"declaration\": \"export type MessageSource = MessageSourceMap[keyof MessageSourceMap];\"\n },\n {\n \"name\": \"MessageSourceMap\",\n \"declaration\": \"export interface MessageSourceMap {\\n user: {\\n kind: 'user';\\n };\\n plugin: {\\n kind: 'plugin';\\n plugin: string;\\n } & ContextFormed;\\n model: ModelMessageSource;\\n tool: ToolMessageSource;\\n}\"\n },\n {\n \"name\": \"ModelMessageSource\",\n \"declaration\": \"export interface ModelMessageSource extends AssistantProviderMetadata {\\n kind: 'model';\\n}\"\n },\n {\n \"name\": \"ReadFileLine\",\n \"declaration\": \"export interface ReadFileLine {\\n number: number;\\n text: string;\\n}\"\n },\n {\n \"name\": \"ReadResultView\",\n \"declaration\": \"export interface ReadResultView {\\n card: 'read';\\n title?: string;\\n path: string;\\n offset: number;\\n lines: ReadFileLine[];\\n totalLines: number;\\n lang?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"ScopeKey\",\n \"declaration\": \"export type ScopeKey = object;\"\n },\n {\n \"name\": \"SearchFileMatches\",\n \"declaration\": \"export interface SearchFileMatches {\\n path: string;\\n matches: SearchLineMatch[];\\n}\"\n },\n {\n \"name\": \"SearchLineMatch\",\n \"declaration\": \"export interface SearchLineMatch {\\n lineNumber: number;\\n line: string;\\n}\"\n },\n {\n \"name\": \"SearchMatchesResultView\",\n \"declaration\": \"export interface SearchMatchesResultView {\\n card: 'search';\\n shape: 'matches';\\n title?: string;\\n files: SearchFileMatches[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchPathsResultView\",\n \"declaration\": \"export interface SearchPathsResultView {\\n card: 'search';\\n shape: 'paths';\\n title?: string;\\n paths: string[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchResultView\",\n \"declaration\": \"export type SearchResultView = SearchMatchesResultView | SearchPathsResultView;\"\n },\n {\n \"name\": \"SessionId\",\n \"declaration\": \"export type SessionId = Branded<'SessionId'>;\"\n },\n {\n \"name\": \"TerminalCallView\",\n \"declaration\": \"export interface TerminalCallView {\\n card: 'terminal';\\n title: string;\\n description?: string;\\n cwd?: string;\\n}\"\n },\n {\n \"name\": \"TerminalResultView\",\n \"declaration\": \"export interface TerminalResultView {\\n card: 'terminal';\\n title?: string;\\n output?: string;\\n exitCode?: number;\\n signal?: string;\\n}\"\n },\n {\n \"name\": \"ToolCallKind\",\n \"declaration\": \"export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other';\"\n },\n {\n \"name\": \"ToolCallView\",\n \"declaration\": \"export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView;\"\n },\n {\n \"name\": \"ToolDefinition\",\n \"declaration\": \"export interface ToolDefinition extends ToolSchema {\\n readonly output: ToolOutputDefinition;\\n execute(args: unknown, exec: ToolRunContext): Promise;\\n finalizeContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\\n timeoutMs?: number;\\n isConcurrencySafe?(args: unknown): boolean;\\n presentCall?(args: unknown): ToolCallView | undefined;\\n presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined;\\n}\"\n },\n {\n \"name\": \"ToolErrorInfo\",\n \"declaration\": \"export interface ToolErrorInfo {\\n name: string;\\n code: string;\\n reason?: string;\\n}\"\n },\n {\n \"name\": \"ToolExecution\",\n \"declaration\": \"export interface ToolExecution extends ToolExecutionInput {\\n readonly rootCallId: ToolCallId;\\n readonly token: ToolExecutionToken;\\n}\"\n },\n {\n \"name\": \"ToolExecutionFailure\",\n \"declaration\": \"export interface ToolExecutionFailure {\\n readonly isError: true;\\n readonly error: ToolFailure;\\n readonly value?: never;\\n readonly content: ContentBlock[];\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: never;\\n}\"\n },\n {\n \"name\": \"ToolExecutionInput\",\n \"declaration\": \"export interface ToolExecutionInput {\\n readonly callId: ToolCallId;\\n readonly rootCallId?: ToolCallId;\\n readonly name: string;\\n readonly schema?: ToolSchema;\\n readonly arguments: unknown;\\n readonly agent?: Agent;\\n readonly parent?: ToolExecutionToken;\\n readonly signal: AbortSignal;\\n}\"\n },\n {\n \"name\": \"ToolExecutionMode\",\n \"declaration\": \"export type ToolExecutionMode = {\\n kind: 'parallel';\\n} | {\\n kind: 'exclusive';\\n};\"\n },\n {\n \"name\": \"ToolExecutionResult\",\n \"declaration\": \"export type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure;\"\n },\n {\n \"name\": \"ToolExecutionSuccess\",\n \"declaration\": \"export interface ToolExecutionSuccess {\\n readonly isError: false;\\n readonly value: JsonValue;\\n readonly content: ContentBlock[];\\n readonly error?: never;\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: true;\\n}\"\n },\n {\n \"name\": \"ToolExecutionToken\",\n \"declaration\": \"export type ToolExecutionToken = symbol & {\\n readonly [toolExecutionTokenBrand]: true;\\n};\"\n },\n {\n \"name\": \"ToolFailure\",\n \"declaration\": \"export interface ToolFailure {\\n message: string;\\n info?: ToolErrorInfo;\\n}\"\n },\n {\n \"name\": \"ToolGuard\",\n \"declaration\": \"export type ToolGuard = (execution: Readonly) => string | undefined;\"\n },\n {\n \"name\": \"ToolMessageSource\",\n \"declaration\": \"export interface ToolMessageSource {\\n kind: 'tool';\\n callId: ToolCallId;\\n}\"\n },\n {\n \"name\": \"ToolOutputDefinition\",\n \"declaration\": \"export interface ToolOutputDefinition {\\n readonly schema: JsonSchemaNode;\\n render(args: unknown, value: JsonValue): ContentBlock[];\\n presentationMeta?(args: unknown, value: JsonValue): JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolPresentationMode\",\n \"declaration\": \"export type ToolPresentationMode = 'native' | 'ptc' | 'both';\"\n },\n {\n \"name\": \"ToolRestriction\",\n \"declaration\": \"export interface ToolRestriction {\\n readonly allow?: readonly string[];\\n readonly deny?: readonly string[];\\n}\"\n },\n {\n \"name\": \"ToolResult\",\n \"declaration\": \"export interface ToolResult {\\n content: ContentBlock[];\\n isError: boolean;\\n meta?: JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolResultView\",\n \"declaration\": \"export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView;\"\n },\n {\n \"name\": \"ToolRunContext\",\n \"declaration\": \"export interface ToolRunContext extends ToolExecution {\\n deferContext(context: UserMessage): void;\\n concludeTurn(): void;\\n}\"\n },\n {\n \"name\": \"ToolSchema\",\n \"declaration\": \"export interface ToolSchema {\\n name: string;\\n description: string;\\n parameters: Record;\\n}\"\n },\n {\n \"name\": \"UserMessage\",\n \"declaration\": \"export interface UserMessage extends Message {\\n readonly role: 'user';\\n}\"\n },\n {\n \"name\": \"WebFetchResultView\",\n \"declaration\": \"export interface WebFetchResultView {\\n card: 'web';\\n kind: 'fetch';\\n title?: string;\\n url: string;\\n statusCode: number;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebResultView\",\n \"declaration\": \"export type WebResultView = WebSearchResultView | WebFetchResultView;\"\n },\n {\n \"name\": \"WebSearchResultView\",\n \"declaration\": \"export interface WebSearchResultView {\\n card: 'web';\\n kind: 'search';\\n title?: string;\\n sources: WebSource[];\\n answer?: string;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebSource\",\n \"declaration\": \"export interface WebSource {\\n url: string;\\n title?: string;\\n snippet?: string;\\n publishedAt?: string;\\n}\"\n }\n ]\n }\n}"}],"isError":false}],"role":"user","id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"inspect-subprocess-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1788778146017,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788778146017,"index":0,"dt":[],"id":"inspect-subprocess-api","name":"cordis_inspect_query","args":["{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"]},{"type":"chunk","time":1788778146017,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"inspect-subprocess-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"}}},{"type":"chunk","time":1788778146017,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1788778146017,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} diff --git a/snapshots/web/message-actions/ui.expected.md b/snapshots/web/message-actions/ui.expected.md index 4a80390ff4..2ea4938f10 100644 --- a/snapshots/web/message-actions/ui.expected.md +++ b/snapshots/web/message-actions/ui.expected.md @@ -11,6 +11,7 @@ - navigation "Turn navigation": - button "Jump to turn 1" - button "Jump to turn 2" + - button "Jump to turn 3" - button "System prompt": - img - img @@ -55,6 +56,21 @@ - button "Copy": - img - paragraph: DONE +- button "Copy": + - img +- button "Good response": + - img +- button "Bad response": + - img +- button "Branch into a new conversation": + - img +- button "Ran for {{duration}}": + - img + - text: Ran for {{duration}} +- text: {{clock}} Keep this later input in the original conversation. {{clock}} +- button "Copy": + - img +- paragraph: ORIGINAL ONLY - button "Copy": - img - button "Good response": @@ -75,9 +91,9 @@ - text: DeepSeek-V4-Flash - img - button "Send message" [disabled] -- button "2 turns 3 steps · {{throughput}} tok/s": +- button "3 turns 4 steps · {{throughput}} tok/s": - img - - text: 2 turns 3 steps{{throughput}} tok/s + - text: 3 turns 4 steps{{throughput}} tok/s - button "7.9K tok · Cache hit 98%": - img - text: 7.9K tokCache hit 98% diff --git a/snapshots/web/workspace-recency/manual-blank.expected.md b/snapshots/web/workspace-recency/manual-blank.expected.md new file mode 100644 index 0000000000..a227f2398d --- /dev/null +++ b/snapshots/web/workspace-recency/manual-blank.expected.md @@ -0,0 +1,8 @@ +- tree "Sessions": + - treeitem "{{workspace}}" [expanded]: + - img + - text: {{workspace}} + - treeitem "New Session" [selected] + - treeitem "Newest conversation now" + - treeitem "Middle conversation 1min" + - treeitem "Oldest conversation 2min" diff --git a/snapshots/web/workspace-recency/sidebar.expected.md b/snapshots/web/workspace-recency/sidebar.expected.md new file mode 100644 index 0000000000..ce3169bbaf --- /dev/null +++ b/snapshots/web/workspace-recency/sidebar.expected.md @@ -0,0 +1,7 @@ +- tree "Sessions": + - treeitem "{{workspace}}" [expanded]: + - img + - text: {{workspace}} + - treeitem "Newest conversation now" [selected] + - treeitem "Middle conversation 1min" + - treeitem "Oldest conversation 2min" diff --git a/snapshots/web/workspace-recency/snapshot.yml b/snapshots/web/workspace-recency/snapshot.yml new file mode 100644 index 0000000000..ad11dd24fc --- /dev/null +++ b/snapshots/web/workspace-recency/snapshot.yml @@ -0,0 +1,9 @@ +version: 1 +scenario: workspace-recency +profile: web +composition: web-default +recording: authored +header: + class: web-default +session: + source: ../seeded-history/session.v3.jsonl diff --git a/tsconfig.base.json b/tsconfig.base.json index b7f0c67d1c..6befdbc448 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -293,12 +293,10 @@ "@deepseek-ai/dsh-credentials-local": ["./packages/credentials/credentials-local/src"], "@deepseek-ai/dsh-deepseek-llm-api-extensions": ["./packages/llm/deepseek-llm-api-extensions/src"], "@deepseek-ai/dsh-deque": ["./packages/util/deque/src"], - "@deepseek-ai/dsh-e2b": ["./packages/e2b/e2b/src"], "@deepseek-ai/dsh-file-reference": ["./packages/context/file-reference/src"], "@deepseek-ai/dsh-file-reference-local": ["./packages/context/file-reference-local/src"], "@deepseek-ai/dsh-fs": ["./packages/fs/fs/src"], "@deepseek-ai/dsh-fs/invariant": ["./packages/fs/fs/src/invariant.ts"], - "@deepseek-ai/dsh-fs-e2b": ["./packages/e2b/fs-e2b/src"], "@deepseek-ai/dsh-fs-local": ["./packages/fs/fs-local/src"], "@deepseek-ai/dsh-fs-observation-policy": ["./packages/fs/fs-observation-policy/src"], "@deepseek-ai/dsh-fs-sandbox": ["./packages/fs/fs-sandbox/src"], @@ -404,7 +402,6 @@ "@deepseek-ai/dsh-subagent-in-process-driver": ["./packages/subagent/subagent-in-process-driver/src"], "@deepseek-ai/dsh-subagent-spawn-in-process": ["./packages/subagent/subagent-spawn-in-process/src"], "@deepseek-ai/dsh-subprocess": ["./packages/subprocess/subprocess/src"], - "@deepseek-ai/dsh-subprocess-e2b": ["./packages/e2b/subprocess-e2b/src"], "@deepseek-ai/dsh-subprocess-local": ["./packages/subprocess/subprocess-local/src"], "@deepseek-ai/dsh-system-prompt": ["./packages/core/system-prompt/src"], "@deepseek-ai/dsh-system-prompt/invariant": ["./packages/core/system-prompt/src/invariant.ts"], diff --git a/tsconfig.host.json b/tsconfig.host.json index 69ca4e1a4b..fcad2e8a51 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -45,6 +45,7 @@ "apps/web/tests/remote-welcome.e2e.ts", "apps/web/tests/workspace-new-session-folding.e2e.ts", "apps/web/tests/workspace-management.e2e.ts", + "apps/web/tests/workspace-recency.e2e.ts", "apps/web/tests/replay-round-trip.e2e.ts", "apps/web/tests/hmr-live.e2e.ts", "apps/web/tests/preview-boot.e2e.ts", @@ -246,8 +247,6 @@ { "path": "./packages/subprocess/subprocess" }, { "path": "./packages/subprocess/subprocess-local" }, { "path": "./packages/subprocess/win32-process" }, - { "path": "./packages/e2b/e2b" }, - { "path": "./packages/e2b/subprocess-e2b" }, { "path": "./packages/shell/shell" }, { "path": "./packages/terminal/terminal" }, { "path": "./packages/terminal/terminal-bash" }, @@ -273,7 +272,6 @@ { "path": "./packages/shell/tool-bash" }, { "path": "./packages/fs/fs" }, { "path": "./packages/fs/fs-local" }, - { "path": "./packages/e2b/fs-e2b" }, { "path": "./packages/fs/fs-observation-policy" }, { "path": "./packages/fs/fs-sandbox" }, { "path": "./packages/fs/tool-fs" }, diff --git a/vitest.config.ts b/vitest.config.ts index 31049baa7e..8556b58442 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -243,7 +243,6 @@ export default defineConfig({ 'packages/client/ui-chat/src/client/conversation-nodes/*', 'packages/client/ui-chat/src/client/details/*', 'packages/client/ui-chat/src/client/model/*', - 'packages/client/ui-chat/src/client/contract/context-provenance.ts', 'packages/client/ui-chat/src/client/contract/snapshot.ts', 'packages/client/ui-chat/src/client/historical-images.ts', 'packages/client/ui-primitives/src/DisclosureRow.tsx',