diff --git a/apps/web/tests/navigation-panes.e2e.ts b/apps/web/tests/navigation-panes.e2e.ts index 7e9fdf81d8..34a223c41d 100644 --- a/apps/web/tests/navigation-panes.e2e.ts +++ b/apps/web/tests/navigation-panes.e2e.ts @@ -14,6 +14,7 @@ import { chromium } from 'playwright' import { strFromU8, unzipSync } from 'fflate' import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, onTestFailed, onTestFinished } from 'vitest' import { ToolCallId } from '@deepseek-ai/dsh-llm' +import type {} from '@deepseek-ai/dsh-client-ui-trajectory' import { defineTool } from '@deepseek-ai/dsh-tools' import { parseSessionLog } from '@deepseek-ai/dsh-llm-replay' import { SESSION_FORMAT_VERSION, SessionId, type SessionEvent } from '@deepseek-ai/dsh-session' diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml index 6d50ffcffe..715d948fbc 100644 --- a/docs/capability-seams.i18n.yaml +++ b/docs/capability-seams.i18n.yaml @@ -6,5 +6,5 @@ en: 41d772192075e133 zh: 5ef3da69b4de1d8a /capability-seams-and-core-services: - en: 6b3dff34b2714738 - zh: 48494c81a4e113f4 + en: 52ed5c25539d0eb2 + zh: fc6d012b1512f662 diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 3fe8a3fe09..9bbade1bc1 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -96,7 +96,8 @@ flowchart LR svc_speechController["ctx.speechController
Experimental transcription Remote"] svc_sessionController["ctx.sessionController
Host Session Remote controller"] svc_sessionFileReferences["ctx.sessionFileReferences
Session-addressed file-reference Remote adapter"] - svc_sessionToolResults["ctx.sessionToolResults
Live Session tool-result Remote"] + pkg_client_ui_trajectory["client-ui-trajectory"] + svc_sessionToolResults["ctx.sessionToolResults
Trajectory live tool-result Remote"] svc_sessionSkillCatalog["ctx.sessionSkillCatalog
Session-addressed skill Remote adapter"] pkg_api_job_controller["api-job-controller"] svc_jobController["ctx.jobController
Host job Remote controller"] @@ -317,7 +318,6 @@ flowchart LR pkg_api_session_controller --> svc_sessionController pkg_api_session_controller --> svc_sessionFileReferences pkg_api_session_controller --> svc_sessionSkillCatalog - pkg_api_session_controller --> svc_sessionToolResults pkg_api_settings_controller --> svc_credentialsController pkg_api_settings_controller --> svc_settingsController pkg_api_terminal_controller --> svc_terminalController @@ -336,6 +336,7 @@ flowchart LR pkg_client_modules --> svc_clientModules pkg_client_product_analytics --> svc_productAnalytics pkg_client_ui_plugin_manager --> svc_pluginRegistryProbe + pkg_client_ui_trajectory --> svc_sessionToolResults pkg_command_feedback --> svc_sessionFeedback pkg_commands --> svc_commands pkg_compaction --> svc_compaction @@ -661,7 +662,7 @@ flowchart LR | `ctx.speechController` | `core` | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | - | - | Validates bounded browser audio before provider dispatch. | | `ctx.sessionController` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | Owns Session commands, cold reads, durable-event following, live control state, model catalogs, workspace opening, and Agent activation policy. | | `ctx.sessionFileReferences` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | Delegates file-reference discovery through the Session Controller's established Agent lookup policy. | -| `ctx.sessionToolResults` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | Retains final tool values only for their live Session instance and invalidates inspectors when that instance unloads. | +| `ctx.sessionToolResults` | `core` | [`client-ui-trajectory`](../packages/client/ui-trajectory) | - | - | - | Retains final tool values only for their live Session instance and invalidates inspectors when that instance unloads. | | `ctx.sessionSkillCatalog` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | Lists the Session composition's user-invocable skills without activating a cold Agent. | | `ctx.jobController` | `core` | [`api-job-controller`](../packages/api/job-controller) | - | - | - | Streams one background job's observation record over the generated Remote namespace; the roster stays on the session control stream. | | `ctx.credentialsController` | `core` | [`api-settings-controller`](../packages/api/settings-controller) | - | - | - | Projects the credential-reference seam onto the generated Remote namespace: batch fan-out, view projection, and refusal mapping live here, not on the seam Definition. | diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index e87a928c68..11098365ae 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -98,7 +98,8 @@ flowchart LR svc_speechController["ctx.speechController
Experimental transcription Remote"] svc_sessionController["ctx.sessionController
Host Session Remote controller"] svc_sessionFileReferences["ctx.sessionFileReferences
Session-addressed file-reference Remote adapter"] - svc_sessionToolResults["ctx.sessionToolResults
Live Session tool-result Remote"] + pkg_client_ui_trajectory["client-ui-trajectory"] + svc_sessionToolResults["ctx.sessionToolResults
Trajectory live tool-result Remote"] svc_sessionSkillCatalog["ctx.sessionSkillCatalog
Session-addressed skill Remote adapter"] pkg_api_job_controller["api-job-controller"] svc_jobController["ctx.jobController
Host job Remote controller"] @@ -319,7 +320,6 @@ flowchart LR pkg_api_session_controller --> svc_sessionController pkg_api_session_controller --> svc_sessionFileReferences pkg_api_session_controller --> svc_sessionSkillCatalog - pkg_api_session_controller --> svc_sessionToolResults pkg_api_settings_controller --> svc_credentialsController pkg_api_settings_controller --> svc_settingsController pkg_api_terminal_controller --> svc_terminalController @@ -338,6 +338,7 @@ flowchart LR pkg_client_modules --> svc_clientModules pkg_client_product_analytics --> svc_productAnalytics pkg_client_ui_plugin_manager --> svc_pluginRegistryProbe + pkg_client_ui_trajectory --> svc_sessionToolResults pkg_command_feedback --> svc_sessionFeedback pkg_commands --> svc_commands pkg_compaction --> svc_compaction @@ -663,7 +664,7 @@ flowchart LR | `ctx.speechController` | `core` | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | - | - | 在 Provider 调用前校验受限的浏览器音频。 | | `ctx.sessionController` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | 负责 Session 命令、冷读取、持久事件跟随、实时控制状态、模型目录、workspace 打开与 Agent 激活策略。 | | `ctx.sessionFileReferences` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | 通过 Session Controller 的既有 Agent lookup 策略委托文件引用发现。 | -| `ctx.sessionToolResults` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | 仅在具体 Session 实例存活期间保留最终工具值,并在实例卸载时使检查器失效。 | +| `ctx.sessionToolResults` | `core` | [`client-ui-trajectory`](../packages/client/ui-trajectory) | - | - | - | 仅在具体 Session 实例存活期间保留最终工具值,并在实例卸载时使检查器失效。 | | `ctx.sessionSkillCatalog` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | 在不激活冷 Agent 的前提下列出 Session 组合中允许用户调用的 skill。 | | `ctx.jobController` | `core` | [`api-job-controller`](../packages/api/job-controller) | - | - | - | 经生成的 Remote namespace 流式发送一个后台任务的观测 record;名册仍在会话控制流上。 | | `ctx.credentialsController` | `core` | [`api-settings-controller`](../packages/api/settings-controller) | - | - | - | 把凭据引用 seam 投影到生成的 Remote namespace:批量扇出、视图投影与拒绝映射都在这里,而不在 seam Definition 上。 | diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 1d571e2573..73bc63d945 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -243,7 +243,7 @@ export interface Config { ## `@deepseek-ai/dsh-api-session-controller` - `inject`: `agentDefaultModel` · `agents` · `attachments` · `fileUploads` · `fs` · `llm` · `remoteAccess` · `sessions` · `sessionProjections` · `sessionQuery` · `typert` · `workspaceRegistry` -- `source`: [`packages/api/session-controller/src/index.ts:111`](../packages/api/session-controller/src/index.ts) +- `source`: [`packages/api/session-controller/src/index.ts:109`](../packages/api/session-controller/src/index.ts) ```ts config-catalog /** Session Controller deployment policy. */ @@ -4754,7 +4754,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-client-ui-skill` | — | [`packages/client/ui-skill/src/index.ts`](../packages/client/ui-skill/src/index.ts) | | `@deepseek-ai/dsh-client-ui-subagent` | — | [`packages/client/ui-subagent/src/index.ts`](../packages/client/ui-subagent/src/index.ts) | | `@deepseek-ai/dsh-client-ui-tool` | — | [`packages/client/ui-tool/src/index.ts`](../packages/client/ui-tool/src/index.ts) | -| `@deepseek-ai/dsh-client-ui-trajectory` | — | [`packages/client/ui-trajectory/src/index.ts`](../packages/client/ui-trajectory/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-trajectory` | `sessions` · `typert` | [`packages/client/ui-trajectory/src/index.ts`](../packages/client/ui-trajectory/src/index.ts) | | `@deepseek-ai/dsh-client-ui-user-questions` | — | [`packages/client/ui-user-questions/src/index.ts`](../packages/client/ui-user-questions/src/index.ts) | | `@deepseek-ai/dsh-client-ui-workflow-run` | — | [`packages/client/ui-workflow-run/src/index.ts`](../packages/client/ui-workflow-run/src/index.ts) | | `@deepseek-ai/dsh-client-ui-workspace` | — | [`packages/client/ui-workspace/src/index.ts`](../packages/client/ui-workspace/src/index.ts) | diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 65592264c6..f9bfc6f129 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -245,7 +245,7 @@ export interface Config { ## `@deepseek-ai/dsh-api-session-controller` - `inject`: `agentDefaultModel` · `agents` · `attachments` · `fileUploads` · `fs` · `llm` · `remoteAccess` · `sessions` · `sessionProjections` · `sessionQuery` · `typert` · `workspaceRegistry` -- `source`: [`packages/api/session-controller/src/index.ts:111`](../packages/api/session-controller/src/index.ts) +- `source`: [`packages/api/session-controller/src/index.ts:109`](../packages/api/session-controller/src/index.ts) ```ts config-catalog /** Session Controller deployment policy. */ @@ -4756,7 +4756,7 @@ export interface Config { | `@deepseek-ai/dsh-client-ui-skill` | — | [`packages/client/ui-skill/src/index.ts`](../packages/client/ui-skill/src/index.ts) | | `@deepseek-ai/dsh-client-ui-subagent` | — | [`packages/client/ui-subagent/src/index.ts`](../packages/client/ui-subagent/src/index.ts) | | `@deepseek-ai/dsh-client-ui-tool` | — | [`packages/client/ui-tool/src/index.ts`](../packages/client/ui-tool/src/index.ts) | -| `@deepseek-ai/dsh-client-ui-trajectory` | — | [`packages/client/ui-trajectory/src/index.ts`](../packages/client/ui-trajectory/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-trajectory` | `sessions` · `typert` | [`packages/client/ui-trajectory/src/index.ts`](../packages/client/ui-trajectory/src/index.ts) | | `@deepseek-ai/dsh-client-ui-user-questions` | — | [`packages/client/ui-user-questions/src/index.ts`](../packages/client/ui-user-questions/src/index.ts) | | `@deepseek-ai/dsh-client-ui-workflow-run` | — | [`packages/client/ui-workflow-run/src/index.ts`](../packages/client/ui-workflow-run/src/index.ts) | | `@deepseek-ai/dsh-client-ui-workspace` | — | [`packages/client/ui-workspace/src/index.ts`](../packages/client/ui-workspace/src/index.ts) | diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 8a486cd834..7fcb5c0b50 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -64,8 +64,8 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `schedule/resolve-agent` | `waterfall` | [`packages/schedule/schedule/src/index.ts:74`](../packages/schedule/schedule/src/index.ts) | [`schedule`](../packages/schedule/schedule) (`waterfall`) | `server` | | `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:44`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | | `session/created` | `emit` | [`packages/core/session/src/index.ts:56`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`schedule`](../packages/schedule/schedule), `server`, `session-controller`, [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry) | -| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:65`](../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), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:76`](../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-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, `claude-code-mods`, [`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), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node), `server`, `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), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter), [`tool-executor-ptc`](../packages/core/tool-executor-ptc), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:65`](../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), `ui-trajectory`, [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:76`](../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-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, `claude-code-mods`, [`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), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node), `server`, `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), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter), [`tool-executor-ptc`](../packages/core/tool-executor-ptc), [`tool-jobs`](../packages/jobs/tool-jobs), `ui-trajectory`, [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:84`](../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:75`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`emit`) | `remotes` | | `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:296`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - | @@ -80,7 +80,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `tools/post-execute` | `waterfall` | [`packages/core/tools/src/events.ts:41`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `claude-code-mods`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | | `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/events.ts:18`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tool-executor-ptc/src/runtime-types.ts:46`](../packages/core/tool-executor-ptc/src/runtime-types.ts) | [`tool-executor-ptc`](../packages/core/tool-executor-ptc) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | -| `tools/result` | `emit` | [`packages/core/tools/src/events.ts:49`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, `session-controller`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) | +| `tools/result` | `emit` | [`packages/core/tools/src/events.ts:49`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present), `ui-trajectory` | | `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:174`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes`, `server` | | `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:44`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `remote-access`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` | | `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index 2479f657a4..62885f03e9 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -66,8 +66,8 @@ | `schedule/resolve-agent` | `waterfall` | [`packages/schedule/schedule/src/index.ts:74`](../packages/schedule/schedule/src/index.ts) | [`schedule`](../packages/schedule/schedule) (`waterfall`) | `server` | | `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:44`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | | `session/created` | `emit` | [`packages/core/session/src/index.ts:56`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`schedule`](../packages/schedule/schedule), `server`, `session-controller`, [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry) | -| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:65`](../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), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:76`](../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-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, `claude-code-mods`, [`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), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node), `server`, `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), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter), [`tool-executor-ptc`](../packages/core/tool-executor-ptc), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:65`](../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), `ui-trajectory`, [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:76`](../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-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, `claude-code-mods`, [`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), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node), `server`, `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), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter), [`tool-executor-ptc`](../packages/core/tool-executor-ptc), [`tool-jobs`](../packages/jobs/tool-jobs), `ui-trajectory`, [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:84`](../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:75`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`emit`) | `remotes` | | `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:296`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - | @@ -82,7 +82,7 @@ | `tools/post-execute` | `waterfall` | [`packages/core/tools/src/events.ts:41`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `claude-code-mods`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | | `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/events.ts:18`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tool-executor-ptc/src/runtime-types.ts:46`](../packages/core/tool-executor-ptc/src/runtime-types.ts) | [`tool-executor-ptc`](../packages/core/tool-executor-ptc) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | -| `tools/result` | `emit` | [`packages/core/tools/src/events.ts:49`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, `session-controller`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) | +| `tools/result` | `emit` | [`packages/core/tools/src/events.ts:49`](../packages/core/tools/src/events.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present), `ui-trajectory` | | `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:174`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes`, `server` | | `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:44`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `remote-access`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` | | `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | diff --git a/docs/subsystems/session.md b/docs/subsystems/session.md index 80065364b8..4e1ef23338 100644 --- a/docs/subsystems/session.md +++ b/docs/subsystems/session.md @@ -1144,26 +1144,6 @@ Types: [CreateSessionOptions](persistence.md) · [PrepareSessionOptions](persist Source: [`packages/core/session/src/index.ts`](../../packages/core/session/src/index.ts) - - -### `ctx.sessionToolResults` — `SessionToolResults` - -Read-only Remote inspection of tool values produced in this Session lifetime. - -```ts cordis-catalog -/** - * Observe one committed result without loading a Session or reading its history. - * Values disappear when the exact Session instance leaves the registry. A cold - * read stays unavailable; opening a new stream observes the current instance. - * @param request - Authorized Session identity and durable result event sequence. - * @param signal - Remote consumer lifetime. - * @returns Current availability and changes until cancellation or service disposal. - */ -@Remote({ mode: 'stream' }) async *watch(request: SessionToolResultRequest, signal: AbortSignal): AsyncIterable -``` - -Source: [`packages/api/session-controller/src/tool-results.ts`](../../packages/api/session-controller/src/tool-results.ts) - ### `api-session/*` events diff --git a/docs/subsystems/session.zh.md b/docs/subsystems/session.zh.md index 80baf95f03..57e732d08c 100644 --- a/docs/subsystems/session.zh.md +++ b/docs/subsystems/session.zh.md @@ -1148,26 +1148,6 @@ Types: [CreateSessionOptions](persistence.zh.md) · [PrepareSessionOptions](pers Source: [`packages/core/session/src/index.ts`](../../packages/core/session/src/index.ts) - - -### `ctx.sessionToolResults` — `SessionToolResults` - -Read-only Remote inspection of tool values produced in this Session lifetime. - -```ts cordis-catalog -/** - * Observe one committed result without loading a Session or reading its history. - * Values disappear when the exact Session instance leaves the registry. A cold - * read stays unavailable; opening a new stream observes the current instance. - * @param request - Authorized Session identity and durable result event sequence. - * @param signal - Remote consumer lifetime. - * @returns Current availability and changes until cancellation or service disposal. - */ -@Remote({ mode: 'stream' }) async *watch(request: SessionToolResultRequest, signal: AbortSignal): AsyncIterable -``` - -Source: [`packages/api/session-controller/src/tool-results.ts`](../../packages/api/session-controller/src/tool-results.ts) - ### `api-session/*` events diff --git a/docs/subsystems/web-client.md b/docs/subsystems/web-client.md index bb68cd64a1..929dd30365 100644 --- a/docs/subsystems/web-client.md +++ b/docs/subsystems/web-client.md @@ -98,3 +98,32 @@ Use the four detailed references according to the extension being added: - [API Gateway](../api-gateway.md) for Host methods, generated Remote contributions, streams, and forwarded events. - [Web Client Slots](slots.md) for components, hooks, stores, injection, and placement. - [Conversation](conversation.md) for durable event correlation, target snapshots, and Chat or Trajectory view contributions. + + + + + +## Cordis API + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — 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.sessionToolResults` — `SessionToolResults` + +Read-only Remote inspection of tool values produced in this Session lifetime. + +```ts cordis-catalog +/** + * Observe one committed result without loading a Session or reading its history. + * Values disappear when the exact Session instance leaves the registry. A cold + * read stays unavailable; opening a new stream observes the current instance. + * @param request - Authorized Session identity and durable result event sequence. + * @param signal - Remote consumer lifetime. + * @returns Current availability and changes until cancellation or service disposal. + */ +@Remote({ mode: 'stream' }) async *watch(request: SessionToolResultRequest, signal: AbortSignal): AsyncIterable +``` + +Source: [`packages/client/ui-trajectory/src/index.ts`](../../packages/client/ui-trajectory/src/index.ts) + diff --git a/docs/subsystems/web-client.zh.md b/docs/subsystems/web-client.zh.md index a3d62f738f..130146441e 100644 --- a/docs/subsystems/web-client.zh.md +++ b/docs/subsystems/web-client.zh.md @@ -98,3 +98,32 @@ Web 和桌面端共享[代码工作工具偏好](../../packages/client/ui-settin - [API Gateway](../api-gateway.zh.md):Host method、生成的 Remote contribution、stream 与 forwarded event。 - [Web Client Slots](slots.zh.md):component、hook、store、injection 与 placement。 - [Conversation](conversation.zh.md):持久 event correlation、target snapshot,以及 Chat 或 Trajectory view contribution。 + + + + + +## Cordis API + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — 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.sessionToolResults` — `SessionToolResults` + +Read-only Remote inspection of tool values produced in this Session lifetime. + +```ts cordis-catalog +/** + * Observe one committed result without loading a Session or reading its history. + * Values disappear when the exact Session instance leaves the registry. A cold + * read stays unavailable; opening a new stream observes the current instance. + * @param request - Authorized Session identity and durable result event sequence. + * @param signal - Remote consumer lifetime. + * @returns Current availability and changes until cancellation or service disposal. + */ +@Remote({ mode: 'stream' }) async *watch(request: SessionToolResultRequest, signal: AbortSignal): AsyncIterable +``` + +Source: [`packages/client/ui-trajectory/src/index.ts`](../../packages/client/ui-trajectory/src/index.ts) + diff --git a/packages/api/remotes/package.json b/packages/api/remotes/package.json index 288aabea9b..55d5b2b90e 100644 --- a/packages/api/remotes/package.json +++ b/packages/api/remotes/package.json @@ -98,6 +98,7 @@ "@deepseek-ai/dsh-office-to-pdf": "workspace:*", "@deepseek-ai/dsh-api-account-controller": "workspace:*", "@deepseek-ai/dsh-deepseek-account": "workspace:*", - "@deepseek-ai/dsh-client-product-analytics": "workspace:*" + "@deepseek-ai/dsh-client-product-analytics": "workspace:*", + "@deepseek-ai/dsh-client-ui-trajectory": "workspace:*" } } diff --git a/packages/api/remotes/src/client/index.ts b/packages/api/remotes/src/client/index.ts index da6f814458..8a4b7dea8a 100644 --- a/packages/api/remotes/src/client/index.ts +++ b/packages/api/remotes/src/client/index.ts @@ -14,6 +14,7 @@ import scheduleRemote from '@deepseek-ai/dsh-schedule/remote' import llmRemote from '@deepseek-ai/dsh-llm/remote' import dynamicRemote from '@deepseek-ai/dsh-cordis-host-runner/remote' import pluginManagerRemote from '@deepseek-ai/dsh-plugin-manager/remote' +import toolResultsRemote from '@deepseek-ai/dsh-client-ui-trajectory/remote' import pluginRegistryProbeRemote from '@deepseek-ai/dsh-client-ui-plugin-manager/remote' import pluginMarketRemote from '@deepseek-ai/dsh-plugin-market/remote' import pluginInventoryRemote from '@deepseek-ai/dsh-host-plugin-inventory/remote' @@ -41,6 +42,7 @@ export type { } from '@deepseek-ai/dsh-plugin-manager/types' export type {} from '@deepseek-ai/dsh-plugin-manager/remote' export type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/remote' +export type {} from '@deepseek-ai/dsh-client-ui-trajectory/remote' export type {} from '@deepseek-ai/dsh-plugin-market/remote' export type { MarketCompatibility, MarketIndexSummary, MarketInstallation, MarketInstallationStatus, MarketListingEntry, MarketListingVersion, @@ -189,7 +191,7 @@ export async function apply(ctx: Context): Promise<() => Promise> { for (const contribution of [ productAnalyticsRemote, agentPresetsRemote, commandsRemote, settingsControllerRemote, accountRemote, goalsRemote, llmRemote, dynamicRemote, scheduleRemote, - pluginInventoryRemote, pluginManagerRemote, pluginMarketRemote, pluginRegistryProbeRemote, + pluginInventoryRemote, pluginManagerRemote, pluginMarketRemote, pluginRegistryProbeRemote, toolResultsRemote, messageFeedbackRemote, sessionFeedbackRemote, fileUploadsRemote, sessionReferencesRemote, permissionPresetsRemote, subagentsRemote, sessionRemote, jobRemote, workspaceRemote, workspaceFilesRemote, terminalRemote, diff --git a/packages/api/session-controller/README.i18n.yaml b/packages/api/session-controller/README.i18n.yaml index 94364733f1..cf1badb15b 100644 --- a/packages/api/session-controller/README.i18n.yaml +++ b/packages/api/session-controller/README.i18n.yaml @@ -9,14 +9,14 @@ en: c7a4c58ea6d3437b zh: 45c2b472a074bb1b /session-controller/summary: - en: 1d55e8672055ab8b - zh: 48afdd612dfa19e8 + en: 4914824c347fdab5 + zh: 684a7b7fa970c39f /session-controller/table-of-contents: en: 1fdf707322fb6996 zh: 0204f7e9d4aa3cce /session-controller/use-this-package: - en: a89cf41dd0f6192f - zh: 7b824980c2960e12 + en: 0c8945a47f87b5f7 + zh: b099d66521dca175 /session-controller/client-references: en: 41bdda1ac32df558 zh: aa5d0e325924066b diff --git a/packages/api/session-controller/README.md b/packages/api/session-controller/README.md index 502359fb4e..ae7cff7cb7 100644 --- a/packages/api/session-controller/README.md +++ b/packages/api/session-controller/README.md @@ -10,7 +10,7 @@ Desktop analytics follows the [product collection policy](../../client/product-a ## Summary -`@deepseek-ai/dsh-api-session-controller` owns the Host `ctx.sessionController` service and the generated Client `session`, `skills`, `fileReferences`, and `toolResults` Remote namespaces. It serves Session lifecycle and history, the Host-generation model catalog, human background-job kill, workspace-path opening, user-invocable skill discovery, and Agent-scoped file references. Use it through API Gateway when a Client needs operations addressed by a Session. +`@deepseek-ai/dsh-api-session-controller` owns the Host `ctx.sessionController` service and the generated Client `session`, `skills`, and `fileReferences` Remote namespaces. It serves Session lifecycle and history, the Host-generation model catalog, human background-job kill, workspace-path opening, user-invocable skill discovery, and Agent-scoped file references. Use it through API Gateway when a Client needs operations addressed by a Session. ## Table of Contents @@ -31,8 +31,6 @@ The controller reads a Session's recorded preset through the optional `agentPres 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. -`toolResults.watch` inspects a successful tool’s final post-policy value by Session and result-event sequence. Values are held only in memory for the exact live Session instance: unloading the Session clears pending and committed values and invalidates open inspectors. Cold reads, restored sessions, forks, and exports do not recover or inherit values. A new inspector or reconnect can read retained values while that instance survives. This capability never writes Session events or projection checkpoints; retained memory grows with successful result sizes during that Session lifetime. The browser subscribes only to the selected result, and ordinary failures leave rendered content usable. - The Client journal validates current Session 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. Cancellation requires live state; queue mutation, 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`. A receipt names a staged file or a staged image, and an image receipt subjects the prompt to the same image-input check as an encoded image. 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's effective working directory and scoped registry when present. Cold listing uses the projected current directory, falling back to the recorded origin and then the configured directory default. It retains the preset standing scope without activating 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 file-type association, including HTML and SVG. Both operations require the composed filesystem to map the requested Host path to the same canonical process path; unmapped remote paths are refused before a native command runs. `session.projections` normally reads an exact baseline, including current-format cold logs, and returns `kind: sequenced`. Only a read requiring migration returns `kind: migration-required` hints without `asOfSeq`; missing keys remain absent. It returns null for a missing Session. Fork callers may set `allowMigration: false` to receive `session/migration-required` instead of starting source migration; omission retains automatic migration. The Client exposes shared values and explicit-read state through `projectionsBySession`; domains select their own keys. Session-list summaries carry `agentAvailable`, updated through existing summary and status events independently of durable projections. Sequenced reads and live projection frames use the same sequence ordering; cached reads fill hints without replacing sequenced values. diff --git a/packages/api/session-controller/README.zh.md b/packages/api/session-controller/README.zh.md index 6bb4a87d72..0bbbd6ddaf 100644 --- a/packages/api/session-controller/README.zh.md +++ b/packages/api/session-controller/README.zh.md @@ -10,7 +10,7 @@ kind: "package-reference" ## 概述 -`@deepseek-ai/dsh-api-session-controller` 拥有 Host 的 `ctx.sessionController` 服务,以及生成的 Client `session`、`skills`、`fileReferences` 和 `toolResults` Remote namespace。它提供 Session 生命周期与历史、Host generation 模型目录、人工后台任务终止、工作区路径打开、用户可调用 skill(技能)发现和 Agent(智能体)范围的文件引用。当 Client 需要按 Session 寻址的操作时,请通过 API Gateway 使用它。 +`@deepseek-ai/dsh-api-session-controller` 拥有 Host 的 `ctx.sessionController` 服务,以及生成的 Client `session`、`skills` 和 `fileReferences` Remote namespace。它提供 Session 生命周期与历史、Host generation 模型目录、人工后台任务终止、工作区路径打开、用户可调用 skill(技能)发现和 Agent(智能体)范围的文件引用。当 Client 需要按 Session 寻址的操作时,请通过 API Gateway 使用它。 ## 目录 @@ -31,8 +31,6 @@ 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 数据。 -`toolResults.watch` 按 Session 与结果事件序号查看成功调用经过策略处理后的最终值。值仅保存在具体活跃 Session 实例的内存中:卸载 Session 会清除待关联及已提交的值,并使打开的检查器失效。冷读、恢复的会话、fork 及导出不会恢复或继承值;只要原实例仍存活,新检查器或重连就能读取已保留的值。此能力不写 Session 事件或投影 checkpoint;保留内存在 Session 存活期间随成功结果大小增长。浏览器只订阅选中的结果,普通读取失败不影响渲染内容的查看。 - Client journal 在发布 follow 快照、live entry 或历史页之前验证当前 Session 事件 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 即可报告原生打开能力。取消要求 live 状态;queue 变更、模型、重命名、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` 时按文件类型关联打开,包括 HTML 和 SVG。两种操作都要求当前文件系统将请求的 Host 路径映射到同一个规范进程路径;无法映射的远端路径会在执行原生命令之前被拒绝。 `session.projections` 正常读取准确基线,包括当前格式的冷日志,返回 `kind: sequenced`。只有需要迁移时才返回不带 `asOfSeq` 的 `kind: migration-required` 提示,缺少的键保持缺席。Session 不存在时返回 null。Fork 调用者可设置 `allowMigration: false`,在源历史需要迁移时收到 `session/migration-required`;省略则保持自动迁移。Client 通过 `projectionsBySession` 暴露共享值和显式读取状态,由领域选择自身的 key。Session 列表摘要携带 `agentAvailable`,通过已有摘要与状态事件更新,与持久化 projection 相互独立。有序读取与实时 projection 帧使用相同的序号排序规则;缓存读取只补充提示,不替换有序值。 diff --git a/packages/api/session-controller/package.json b/packages/api/session-controller/package.json index b9b1752f48..0f7b7bb63e 100644 --- a/packages/api/session-controller/package.json +++ b/packages/api/session-controller/package.json @@ -166,8 +166,6 @@ "@deepseek-ai/dsh-util-workspace-path": "workspace:*", "@deepseek-ai/dsh-workspace": "workspace:*", "@types/mime-types": "^3.0.1", - "@deepseek-ai/dsh-working-directory": "workspace:*", - "@deepseek-ai/dsh-tools": "workspace:*", - "@deepseek-ai/dsh-tool-executor-ptc": "workspace:*" + "@deepseek-ai/dsh-working-directory": "workspace:*" } } diff --git a/packages/api/session-controller/src/index.ts b/packages/api/session-controller/src/index.ts index 15ed56bee4..125bd1f3e3 100644 --- a/packages/api/session-controller/src/index.ts +++ b/packages/api/session-controller/src/index.ts @@ -26,7 +26,6 @@ import { SessionHistoryController } from './history.ts' import { SessionFileReferences } from './file-references.ts' import { ApiSessionList } from './list.ts' import { buildModelCatalog, hasProviderApiKey } from './catalog.ts' -import { SessionToolResults } from './tool-results.ts' import { SessionSkillCatalog } from './skill-catalog.ts' import { SessionMediaReferences } from './media-references.ts' import { ArchivedSessionGate } from './archived-session-gate.ts' @@ -70,7 +69,6 @@ export type * from './types.ts' export { ApiSessionNotFound } from './agent.ts' export { SessionFileReferences } from './file-references.ts' export { SessionSkillCatalog } from './skill-catalog.ts' -export { SessionToolResults } from './tool-results.ts' declare module '@deepseek-ai/cordis' { interface Context { @@ -206,7 +204,6 @@ export class SessionController extends TypertRemoteService { ctx.plugin(SessionFileReferences) ctx.plugin(SessionMediaReferences) ctx.plugin(SessionSkillCatalog) - ctx.plugin(SessionToolResults) // An archived Session, or a subagent descendant of one, runs no model step // until it is restored; what it still runs is stopped by the owners that // answer the Workspace registry's archive-admission events. diff --git a/packages/api/session-controller/src/tool-results.ts b/packages/api/session-controller/src/tool-results.ts deleted file mode 100644 index 8342bd00b3..0000000000 --- a/packages/api/session-controller/src/tool-results.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** Ephemeral tool values owned by live Session instances, outside the durable journal. */ -import { Context } from '@deepseek-ai/cordis' -import type { ToolCallId } from '@deepseek-ai/dsh-llm' -import type { Session } from '@deepseek-ai/dsh-session' -import type {} from '@deepseek-ai/dsh-tools' -import type {} from '@deepseek-ai/dsh-tool-executor-ptc' -import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol' -import type { JsonValue } from '@deepseek-ai/dsh-util-values' -import type { SessionToolResultRequest, SessionToolResultValue } from './types.ts' - -declare module '@deepseek-ai/cordis' { - interface Context { - /** Non-restorable tool values retained only while their Session is live. */ - sessionToolResults: SessionToolResults - } -} - -interface LiveResults { - readonly pending: Map - readonly values: Map - readonly listeners: Map void>> -} - -function emptyResults(): LiveResults { - return { pending: new Map(), values: new Map(), listeners: new Map() } -} - -/** Read-only Remote inspection of tool values produced in this Session lifetime. */ -export class SessionToolResults extends TypertRemoteService { - static inject = ['sessions', 'typert'] - private results = new WeakMap() - private readonly followers = new Set<() => void>() - - /** @param ctx - Owner of the live Session registry and tool result notifications. */ - constructor(ctx: Context) { - super(ctx, 'sessionToolResults', { namespace: 'toolResults' }) - ctx.on('tools/result', (exec, result) => { - const session = exec.agent?.session - if (result.isError || session === undefined || ctx.sessions.get(session.id) !== session) return - this.forSession(session).pending.set(exec.callId, result.value) - }, { global: true }) - ctx.on('session/event', (session, event) => { - const callId = event.type === 'tool/result' ? event.data.message.toolCallId - : event.type === 'tool/ptc-dispatch' ? event.data.subCallId : undefined - if (callId === undefined) return - const state = this.results.get(session) - const value = state?.pending.get(callId) - if (state === undefined || value === undefined) return - state.pending.delete(callId) - state.values.set(event.seq, value) - for (const listener of state.listeners.get(event.seq) ?? []) listener() - }, { global: true }) - ctx.on('session/disposed', (session) => { - const state = this.results.get(session) - if (state === undefined) return - this.results.delete(session) - state.pending.clear() - state.values.clear() - for (const listeners of state.listeners.values()) for (const listener of listeners) listener() - }, { global: true }) - ctx.effect(() => () => { - this.results = new WeakMap() - for (const close of this.followers) close() - this.followers.clear() - }, 'tool-results: live values and followers') - } - - private forSession(session: Session): LiveResults { - let state = this.results.get(session) - if (state === undefined) { - state = emptyResults() - this.results.set(session, state) - } - return state - } - - private forRequest(request: SessionToolResultRequest): LiveResults { - const session = this.ctx.sessions.get(request.sessionId) - return session === undefined ? emptyResults() : this.forSession(session) - } - - /** - * Observe one committed result without loading a Session or reading its history. - * Values disappear when the exact Session instance leaves the registry. A cold - * read stays unavailable; opening a new stream observes the current instance. - * @param request - Authorized Session identity and durable result event sequence. - * @param signal - Remote consumer lifetime. - * @returns Current availability and changes until cancellation or service disposal. - */ - @Remote({ mode: 'stream' }) - async *watch(request: SessionToolResultRequest, signal: AbortSignal): AsyncIterable { - const state = this.forRequest(request) - let dirty = true - const lifetime = { closed: false } - let wake: (() => void) | undefined - const changed = (): void => { dirty = true; wake?.() } - const close = (): void => { lifetime.closed = true; state.values.clear(); state.pending.clear(); wake?.() } - let listeners = state.listeners.get(request.seq) - if (listeners === undefined) { listeners = new Set(); state.listeners.set(request.seq, listeners) } - listeners.add(changed) - this.followers.add(close) - signal.addEventListener('abort', changed, { once: true }) - try { - while (!signal.aborted && !lifetime.closed) { - if (dirty) { - dirty = false - const value = state.values.get(request.seq) - yield value === undefined ? { available: false } : { available: true, value } - } else { - await new Promise((resolve) => { wake = resolve }) - wake = undefined - } - } - } finally { - listeners.delete(changed) - if (listeners.size === 0) state.listeners.delete(request.seq) - this.followers.delete(close) - signal.removeEventListener('abort', changed) - } - } -} diff --git a/packages/api/session-controller/src/types.ts b/packages/api/session-controller/src/types.ts index 6808978e78..8c6da77a60 100644 --- a/packages/api/session-controller/src/types.ts +++ b/packages/api/session-controller/src/types.ts @@ -613,14 +613,3 @@ export type SessionProjectionValue = JsonValue /** Application metadata returned by the serving desktop for one file. */ export type SessionWorkspacePathApplication = NativeFileApplication - -/** Live-only tool result inspection; never part of Session history or projections. */ -export interface SessionToolResultRequest { - readonly sessionId: SessionId - readonly seq: number -} - -/** Availability distinguishes missing results from valid JSON null. */ -export type SessionToolResultValue = - | { readonly available: false } - | { readonly available: true; readonly value: JsonValue } diff --git a/packages/api/session-controller/tsconfig.host.json b/packages/api/session-controller/tsconfig.host.json index 54c933c48d..8a5fa75166 100644 --- a/packages/api/session-controller/tsconfig.host.json +++ b/packages/api/session-controller/tsconfig.host.json @@ -19,8 +19,7 @@ "src/list.ts", "src/media-references.ts", "src/archived-session-gate.ts", - "src/skill-catalog.ts", - "src/tool-results.ts" + "src/skill-catalog.ts" ], "references": [ { @@ -115,12 +114,6 @@ }, { "path": "../../session/working-directory" - }, - { - "path": "../../core/tools" - }, - { - "path": "../../core/tool-executor-ptc" } ] } diff --git a/packages/client/ui-attachment/tsconfig.json b/packages/client/ui-attachment/tsconfig.json index 1b7d6e0129..6d6e4792bc 100644 --- a/packages/client/ui-attachment/tsconfig.json +++ b/packages/client/ui-attachment/tsconfig.json @@ -27,7 +27,7 @@ "path": "../ui-tool" }, { - "path": "../ui-trajectory" + "path": "../ui-trajectory/tsconfig.client.json" }, { "path": "../ui-slots" diff --git a/packages/client/ui-trajectory/README.i18n.yaml b/packages/client/ui-trajectory/README.i18n.yaml index e4e3cf1217..9beaaec8c9 100644 --- a/packages/client/ui-trajectory/README.i18n.yaml +++ b/packages/client/ui-trajectory/README.i18n.yaml @@ -24,20 +24,20 @@ en: 0f4ee2189324ac06 zh: 12e76fed22c09bc1 /deepseek-ai-dsh-client-ui-trajectory/understand-the-implementation: - en: 5c3542fa3ab53ab7 - zh: 05265f449ca6fb05 + en: 436c9201f508354c + zh: e332adcf07a6f272 /deepseek-ai-dsh-client-ui-trajectory/understand-the-implementation/virtual-rows: en: c85e342bc7eee4e6 zh: 9feab66ef1755b94 /deepseek-ai-dsh-client-ui-trajectory/understand-the-implementation/layout: - en: b9693a54b800d439 - zh: cd9b84382ae0b181 + en: 2fad51d9b75e49d4 + zh: 05cf292f6d5dd770 /deepseek-ai-dsh-client-ui-trajectory/further-exploration: en: a324740cfbe62c1e zh: 53077c3ffc69b510 /deepseek-ai-dsh-client-ui-trajectory/model-experience: - en: 3120df665299c634 - zh: 1d9f365d091d0d2a + en: 5432585f567e6546 + zh: 2c68717a4bf413c7 /deepseek-ai-dsh-client-ui-trajectory/model-experience/kv-cache-effect: en: ca75c51c89c2c9b0 zh: de54f3467ef3b9be diff --git a/packages/client/ui-trajectory/README.md b/packages/client/ui-trajectory/README.md index 63559f7d9a..cd8c18b5ce 100644 --- a/packages/client/ui-trajectory/README.md +++ b/packages/client/ui-trajectory/README.md @@ -63,13 +63,15 @@ Tool records begin at durable tool/call events and retain complete argument text A complete appended prompt without a loaded request header appears as a standalone system row; only its known text is available, with no inferred request options or tool catalog. Prepending its request history replaces that standalone presentation without duplicating the prompt. In-history system prompt changes compare against the most recent request state, including earlier prompt updates without a new request header. Each request retains the prompt and change that applied at its own position. Surface replacements, including compaction, restore the last nonempty surviving system prompt even without a new system event; an unloaded prompt remains unavailable until its page arrives. +The Trajectory Host plugin owns `ctx.sessionToolResults` and its `toolResults.watch` Remote stream, which inspects a successful tool’s final post-policy value by Session and result-event sequence. Values are held only in memory for the exact live Session instance: unloading the Session clears pending and committed values and invalidates open inspectors. Cold reads, restored sessions, forks, and exports do not recover or inherit values. A new inspector or reconnect can read retained values while that instance survives. Unloading the Trajectory plugin also clears every value; remounting starts empty even when the Session stays live. This capability never writes Session events or projection checkpoints; retained memory grows with successful result sizes during that Session lifetime. The browser subscribes only to the selected result, and ordinary failures leave rendered content usable. + ### Virtual rows Long ledgers initially derive React data from 50 target Nodes ending at the mount-time tail. Later Nodes extend that anchored window without evicting its prefix, and the existing load control reveals earlier resident Nodes before requesting another Session page. Virtualization mounts only the visible row window plus a small overscan; request-only separators share the next measurable virtual item, while semantic row keys and ARIA indexes survive prepends. The virtualizer owns bottom following for structural appends; non-virtual ledgers use a direct tail-position write. Content-only stream frames preserve virtual row keys and heights, reuse measurements, and do not issue repeated tail-scroll writes. Completed replies retain assembled blocks, timing, and usage in Trajectory target State, while the shared Session window keeps the raw Events. ### Layout -Trajectory asks the conversation shell to float the composer over the full-height ledger, while its responsive vertical scrollers reserve the composer's live height so final rows remain reachable. Scrollable Summary regions keep their scrollbar thumbs transparent until hovered or focused, without changing the reserved scroll geometry. The package provides no service and declares no Context merge. +Trajectory asks the conversation shell to float the composer over the full-height ledger, while its responsive vertical scrollers reserve the composer's live height so final rows remain reachable. Scrollable Summary regions keep their scrollbar thumbs transparent until hovered or focused, without changing the reserved scroll geometry. The Host and Client compile separately; the Host service loads and unloads with the Trajectory plugin. @@ -90,7 +92,7 @@ These pages cover the conversation host and the session data this view projects. ## Model Experience -None, as the package is a browser-side UI plugin layer that registers nothing model-facing. +None, as the Trajectory UI and its read-only Host inspector register nothing model-facing. #### KV Cache effect diff --git a/packages/client/ui-trajectory/README.zh.md b/packages/client/ui-trajectory/README.zh.md index f9fac7d61b..620052ca7a 100644 --- a/packages/client/ui-trajectory/README.zh.md +++ b/packages/client/ui-trajectory/README.zh.md @@ -63,13 +63,15 @@ Trajectory 标签页让你以按轮次组织的事件记录表和交互式时间 完整的追加提示词在请求头未加载时显示为独立系统行;仅提供已知文本,不推断请求选项或工具目录。补入其请求历史后,该独立展示被替代而不重复提示词。历史中的系统提示词变更与最近的请求状态比较,包括没有新请求头的先前提示词更新。每个请求保留其所在位置生效的提示词与变更。包括压缩在内的 surface 替换会恢复最后一个非空的存活系统提示词,即使没有新的系统事件;未加载的提示词在对应分页到达前仍不可用。 +Trajectory 的 Host 插件拥有 `ctx.sessionToolResults` 服务及其 `toolResults.watch` Remote 流,按 Session 与结果事件序号查看成功调用经过策略处理后的最终值。值仅保存在具体活跃 Session 实例的内存中:卸载 Session 会清除待关联及已提交的值,并使打开的检查器失效。冷读、恢复的会话、fork 及导出不会恢复或继承值;只要原实例仍存活,新检查器或重连就能读取已保留的值。卸载 Trajectory 插件也会清空所有值;即使 Session 仍存活,重新挂载也从空状态开始。此能力不写 Session 事件或投影 checkpoint;保留内存在 Session 存活期间随成功结果大小增长。浏览器只订阅选中的结果,普通读取失败不影响渲染内容的查看。 + ### 虚拟行 长记录表最初只从挂载时尾部结束的 50 个 target Node 派生 React 数据。后续 Node 会扩展这个固定起点的窗口而不会逐出其前缀;现有加载控件会先显露更早的驻留 Node,再请求下一个会话页面。虚拟化只挂载可见行窗口加少量缓冲;仅含请求的分隔行并入下一个具备可测高度的虚拟项,语义行键与 ARIA 索引在向前补页后保持不变。虚拟化器负责结构性追加后的底部跟随;非虚拟记录表会直接写入末尾位置。仅含内容更新的流式帧会保持虚拟行的键与高度、复用测量结果,并且不会重复写入末尾滚动位置。已完成的回复会在 Trajectory target State 中保留组装后的块、计时与用量,共享会话窗口则保留原始事件。 ### 布局 -Trajectory 要求会话壳把 composer 作为浮层置于全高记录表上方;其响应式纵向滚动容器会预留 composer 的实时高度,确保仍可滚动到最后几行。可滚动的 Summary 区域在悬停或聚焦前保持滚动条滑块透明,同时不改变预留的滚动几何空间。本包不提供服务,也不声明上下文合并。 +Trajectory 要求会话壳把 composer 作为浮层置于全高记录表上方;其响应式纵向滚动容器会预留 composer 的实时高度,确保仍可滚动到最后几行。可滚动的 Summary 区域在悬停或聚焦前保持滚动条滑块透明,同时不改变预留的滚动几何空间。Host 与 Client 分别编译;Host 服务随 Trajectory 插件加载和卸载。 @@ -90,7 +92,7 @@ Trajectory 要求会话壳把 composer 作为浮层置于全高记录表上方 ## 模型体验 -无。该包是浏览器端 UI 插件层,不注册任何面向模型的内容。 +无。Trajectory UI 及其只读 Host 检查器不注册任何面向模型的内容。 #### KV Cache 影响 diff --git a/packages/client/ui-trajectory/package.json b/packages/client/ui-trajectory/package.json index 68ad2e325c..c16307471b 100644 --- a/packages/client/ui-trajectory/package.json +++ b/packages/client/ui-trajectory/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-client-ui-trajectory", - "description": "Trajectory event ledger with an interactive timing overview: pure-consumer plugin registering into the conversation ViewMap (no service)", + "description": "Trajectory event ledger and timing overview with live tool-result inspection", "version": "0.2.1-rc.3", "publishConfig": { "access": "public" @@ -24,7 +24,18 @@ }, "./src/*": "./src/*", "./package.json": "./package.json", - "./locale/*.json": "./locale/*.json" + "./locale/*.json": "./locale/*.json", + "./typert": { + "types": "./lib/typert.host.d.ts", + "default": "./lib/typert.host.js" + }, + "./remote": { + "types": "./lib/typert.remote-client.d.ts", + "default": "./lib/typert.remote-client.js" + }, + "./types": { + "types": "./lib/types/types.d.ts" + } }, "dsh": { "client": { @@ -69,12 +80,21 @@ "@deepseek-ai/dsh-attachment": "workspace:*", "@tanstack/react-virtual": "3.14.10", "diff": "^9.0.0", - "@deepseek-ai/dsh-api-gateway": "workspace:*" + "@deepseek-ai/dsh-api-gateway": "workspace:*", + "@deepseek-ai/dsh-tools": "workspace:*" }, "files": [ "locale/*.json", "lib/index.js", "lib/client.js", - "lib/types/**/*.d.ts" - ] + "lib/types/**/*.d.ts", + "lib/typert.host.js", + "lib/typert.host.d.ts", + "lib/typert.remote-client.js", + "lib/typert.remote-client.d.ts" + ], + "dependencies": { + "@deepseek-ai/dsh-typert-protocol": "workspace:*", + "zod": "4.4.3" + } } diff --git a/packages/client/ui-trajectory/src/client/watch-tool-result.ts b/packages/client/ui-trajectory/src/client/watch-tool-result.ts index e5cbb55c1d..7a2411707a 100644 --- a/packages/client/ui-trajectory/src/client/watch-tool-result.ts +++ b/packages/client/ui-trajectory/src/client/watch-tool-result.ts @@ -1,6 +1,6 @@ /** Selected-result subscriptions retain no history-sized Client cache. */ import type { ClientRemote } from '@deepseek-ai/dsh-api-gateway/client' -import type { SessionToolResultValue } from '@deepseek-ai/dsh-api-session-controller/types' +import type { SessionToolResultValue } from '../types.ts' import type { SessionId } from '@deepseek-ai/dsh-session/types' import type { JsonValue } from '@deepseek-ai/dsh-util-values' diff --git a/packages/client/ui-trajectory/src/index.ts b/packages/client/ui-trajectory/src/index.ts index 33edfa914f..3d668551b4 100644 --- a/packages/client/ui-trajectory/src/index.ts +++ b/packages/client/ui-trajectory/src/index.ts @@ -1,4 +1,115 @@ -/** Host loader entry for the browser-only trajectory plugin. */ +/** Trajectory Host inspector retaining tool values only for live Session instances. */ +import { addAbortListener } from 'node:events' +import { Context } from '@deepseek-ai/cordis' +import type { ToolCallId } from '@deepseek-ai/dsh-llm' +import type { Session } from '@deepseek-ai/dsh-session' +import type {} from '@deepseek-ai/dsh-tools' +import type {} from '@deepseek-ai/dsh-tool-executor-ptc' +import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol' +import type { JsonValue } from '@deepseek-ai/dsh-util-values' +import type { SessionToolResultRequest, SessionToolResultValue } from './types.ts' -/** Provides no host-side behavior. */ -export function apply(): void {} +export type { SessionToolResultRequest, SessionToolResultValue } from './types.ts' + +declare module '@deepseek-ai/cordis' { + interface Context { + /** Non-restorable tool values retained only while their Session is live. */ + sessionToolResults: SessionToolResults + } +} + +interface LiveResults { + readonly pending: Map + readonly values: Map + readonly listeners: Set<(seq?: number) => void> +} + +function emptyResults(): LiveResults { + return { pending: new Map(), values: new Map(), listeners: new Set() } +} + +/** Read-only Remote inspection of tool values produced in this Session lifetime. */ +export default class SessionToolResults extends TypertRemoteService { + static inject = ['sessions', 'typert'] + private readonly results = new Map() + private readonly lifetime = new AbortController() + + /** @param ctx - Owner of the live Session registry and tool result notifications. */ + constructor(ctx: Context) { + super(ctx, 'sessionToolResults', { namespace: 'toolResults' }) + ctx.on('tools/result', (exec, result) => { + const session = exec.agent?.session + if (result.isError || session === undefined || ctx.sessions.get(session.id) !== session) return + this.forSession(session).pending.set(exec.callId, result.value) + }, { global: true }) + ctx.on('session/event', (session, event) => { + const callId = event.type === 'tool/result' ? event.data.message.toolCallId + : event.type === 'tool/ptc-dispatch' ? event.data.subCallId : undefined + if (callId === undefined) return + const state = this.results.get(session) + const value = state?.pending.get(callId) + if (state === undefined || value === undefined) return + state.pending.delete(callId) + state.values.set(event.seq, value) + for (const listener of state.listeners) listener(event.seq) + }, { global: true }) + ctx.on('session/disposed', (session) => { + const state = this.results.get(session) + if (state === undefined) return + this.results.delete(session) + state.pending.clear() + state.values.clear() + for (const listener of state.listeners) listener() + }, { global: true }) + ctx.effect(() => () => { + this.lifetime.abort() + for (const state of this.results.values()) { state.pending.clear(); state.values.clear() } + this.results.clear() + }, 'tool-results: live values and followers') + } + + private forSession(session: Session): LiveResults { + let state = this.results.get(session) + if (state === undefined) { + state = emptyResults() + this.results.set(session, state) + } + return state + } + + private forRequest(request: SessionToolResultRequest): LiveResults { + const session = this.ctx.sessions.get(request.sessionId) + return session === undefined ? emptyResults() : this.forSession(session) + } + + /** + * Observe one committed result without loading a Session or reading its history. + * Values disappear when the exact Session instance leaves the registry. A cold + * read stays unavailable; opening a new stream observes the current instance. + * @param request - Authorized Session identity and durable result event sequence. + * @param signal - Remote consumer lifetime. + * @returns Current availability and changes until cancellation or service disposal. + */ + @Remote({ mode: 'stream' }) + async *watch(request: SessionToolResultRequest, signal: AbortSignal): AsyncIterable { + const lifetime = AbortSignal.any([signal, this.lifetime.signal]) + if (lifetime.aborted) return + const state = this.forRequest(request) + let update = Promise.withResolvers() + const changed = (seq?: number): void => { + if (seq === undefined || seq === request.seq) update.resolve() + } + state.listeners.add(changed) + using _cancellation = addAbortListener(lifetime, () => { update.resolve() }) + try { + while (!lifetime.aborted) { + const value = state.values.get(request.seq) + yield value === undefined ? { available: false } : { available: true, value } + await update.promise + update = Promise.withResolvers() + } + } finally { + state.listeners.delete(changed) + } + } +} diff --git a/packages/client/ui-trajectory/src/types.ts b/packages/client/ui-trajectory/src/types.ts new file mode 100644 index 0000000000..c6d5f6f758 --- /dev/null +++ b/packages/client/ui-trajectory/src/types.ts @@ -0,0 +1,14 @@ +/** Ephemeral result inspection shared by Trajectory's Host and Client. */ +import type { SessionId } from '@deepseek-ai/dsh-session/types' +import type { JsonValue } from '@deepseek-ai/dsh-util-values' + +/** Live-only tool result inspection; never part of Session history or projections. */ +export interface SessionToolResultRequest { + readonly sessionId: SessionId + readonly seq: number +} + +/** Availability distinguishes missing results from valid JSON null. */ +export type SessionToolResultValue = + | { readonly available: false } + | { readonly available: true; readonly value: JsonValue } diff --git a/packages/api/session-controller/tests/tool-results.host.spec.ts b/packages/client/ui-trajectory/tests/tool-results.host.spec.ts similarity index 69% rename from packages/api/session-controller/tests/tool-results.host.spec.ts rename to packages/client/ui-trajectory/tests/tool-results.host.spec.ts index 780368bced..ba4e60cd79 100644 --- a/packages/api/session-controller/tests/tool-results.host.spec.ts +++ b/packages/client/ui-trajectory/tests/tool-results.host.spec.ts @@ -1,5 +1,6 @@ /** Live tool inspection shares the real Session's unload lifetime, never its replay data. */ import { Context } from '@deepseek-ai/cordis' +import TypertRegistry from '@deepseek-ai/dsh-typert-registry' import { expect, it, onTestFinished } from 'vitest' import type { Agent } from '@deepseek-ai/dsh-agent' import { mountAgentLoopTestDependencies, mountAgentLoopTestHarness } from '@deepseek-ai/dsh-agent-loop-testkit' @@ -7,13 +8,16 @@ import { ToolCallId } from '@deepseek-ai/dsh-llm' import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session' import { defineTool } from '@deepseek-ai/dsh-tools' import type { JsonValue } from '@deepseek-ai/dsh-util-values' -import { SessionToolResults } from '../src/tool-results.ts' +import SessionToolResults from '../src/index.ts' async function setup(value: JsonValue = { answer: 42 }) { const ctx = new Context() onTestFinished(() => ctx.fiber.dispose()) await mountAgentLoopTestDependencies(ctx) - const results = new SessionToolResults(ctx) + await ctx.plugin(TypertRegistry) + const feature = ctx.plugin(SessionToolResults) + await feature + const results = ctx.sessionToolResults ctx.effect(() => ctx.tools.register(defineTool({ name: 'value', description: 'Return a structured value.', parameters: {}, output: { schema: { type: 'json' }, render: () => [{ type: 'text', text: 'rendered' }] }, @@ -42,7 +46,7 @@ async function setup(value: JsonValue = { answer: 42 }) { onTestFinished(async () => { abort.abort(); await iterator.return?.() }) return iterator } - return { ctx, results, agent, owner, execute, watch } + return { ctx, results, feature, agent, owner, execute, watch } } it.each([null, false, 0, '', { answer: 42 }])('retains final live value %j without adding it to the journal', async (value) => { @@ -136,3 +140,51 @@ it('ends active observers when the service owner is disposed', async () => { await ctx.fiber.dispose() expect((await finished).done).toBe(true) }) + +it('unloads the Trajectory service without retaining values across a remount', async () => { + const { ctx, feature, agent, execute, watch } = await setup() + const event = await execute() + const observer = watch(event.seq) + expect((await observer.next()).value).toEqual({ available: true, value: { answer: 42 } }) + const finished = observer.next() + await feature.dispose() + expect((await finished).done).toBe(true) + expect(ctx.get('sessionToolResults')).toBeUndefined() + expect(ctx.sessions.get(agent.id)).toBe(agent.session) + const missed = await execute() + await ctx.plugin(SessionToolResults) + const abort = new AbortController() + const restored = ctx.sessionToolResults.watch({ sessionId: agent.id, seq: event.seq }, abort.signal)[Symbol.asyncIterator]() + const absent = ctx.sessionToolResults.watch({ sessionId: agent.id, seq: missed.seq }, abort.signal)[Symbol.asyncIterator]() + onTestFinished(async () => { abort.abort(); await restored.return?.(); await absent.return?.() }) + expect((await restored.next()).value).toEqual({ available: false }) + expect((await absent.next()).value).toEqual({ available: false }) +}) + +it('ends a subscription whose signal was aborted before opening', async () => { + const { results, agent } = await setup() + const abort = new AbortController() + abort.abort() + const observer = results.watch({ sessionId: agent.id, seq: 0 }, abort.signal)[Symbol.asyncIterator]() + expect((await observer.next()).done).toBe(true) +}) + +it('discards pending PTC values if the Session unloads before its result is committed', async () => { + const { ctx, agent, owner, watch } = await setup() + const callId = ToolCallId('unloaded:ptc:1') + await ctx.tools.execute({ + agent, signal: new AbortController().signal, + calls: [{ type: 'tool-call', id: callId, name: 'value', arguments: '{}' }], + }) + const observer = watch(agent.session.seq) + expect((await observer.next()).value).toEqual({ available: false }) + const unloaded = observer.next() + await owner.dispose() + expect((await unloaded).value).toEqual({ available: false }) + const resumed = ctx.sessions.create(agent.id) + const committed = resumed.append('tool/ptc-dispatch', { + rootCallId: ToolCallId('unloaded'), parentCallId: ToolCallId('unloaded'), subCallId: callId, + name: 'value', arguments: {}, content: [{ type: 'text', text: 'rendered' }], isError: false, + }) + expect((await watch(committed.seq).next()).value).toEqual({ available: false }) +}) diff --git a/packages/client/ui-trajectory/tests/views.client.spec.tsx b/packages/client/ui-trajectory/tests/views.client.spec.tsx index bb9f08d870..6d901131d9 100644 --- a/packages/client/ui-trajectory/tests/views.client.spec.tsx +++ b/packages/client/ui-trajectory/tests/views.client.spec.tsx @@ -41,7 +41,6 @@ import { createConversationStore } from '@deepseek-ai/dsh-client-ui-conversation import { zh as conversationZh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts' import * as localePlugin from '@deepseek-ai/dsh-client-locale/client' import { apply, inject } from '@deepseek-ai/dsh-client-ui-trajectory/client' -import { apply as nodeApply } from '@deepseek-ai/dsh-client-ui-trajectory' import { PartialArguments } from '@deepseek-ai/dsh-util-values' import type { TrajectoryTurnModel } from '../src/client/layout.ts' import { TrajectoryTimeline as LocalizedTrajectoryTimeline } from '../src/client/TrajectoryTimeline.tsx' @@ -1611,9 +1610,3 @@ describe('TrajectoryView state', () => { }) }) - -describe('node half', () => { - it('node apply is an intentional no-op (loader-managed lifecycle only)', () => { - expect(() => { nodeApply() }).not.toThrow() - }) -}) diff --git a/packages/client/ui-trajectory/tests/watch-tool-result.client.spec.ts b/packages/client/ui-trajectory/tests/watch-tool-result.client.spec.ts index 3ebcb80cd3..264af39e8f 100644 --- a/packages/client/ui-trajectory/tests/watch-tool-result.client.spec.ts +++ b/packages/client/ui-trajectory/tests/watch-tool-result.client.spec.ts @@ -2,7 +2,7 @@ import { expect, it, onTestFinished, vi } from 'vitest' import { RemoteStream, RemoteStreamCarrierError } from '@deepseek-ai/dsh-api-gateway/client' import { SessionId } from '@deepseek-ai/dsh-session/types' -import type { SessionToolResultValue } from '@deepseek-ai/dsh-api-session-controller/types' +import type { SessionToolResultValue } from '../src/types.ts' import { createToolResultWatcher } from '../src/client/watch-tool-result.ts' function deferred() { diff --git a/packages/client/ui-trajectory/tsconfig.client.json b/packages/client/ui-trajectory/tsconfig.client.json new file mode 100644 index 0000000000..8e671ca7f1 --- /dev/null +++ b/packages/client/ui-trajectory/tsconfig.client.json @@ -0,0 +1,63 @@ +{ + "extends": "../../../tsconfig.base.client.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types", + "tsBuildInfoFile": "lib/tsconfig.client.tsbuildinfo" + }, + "references": [ + { + "path": "../../../vendor/cordis" + }, + { + "path": "../locale/tsconfig.client.json" + }, + { + "path": "../store" + }, + { + "path": "../ui-conversation/tsconfig.client.json" + }, + { + "path": "../../util/values" + }, + { + "path": "../ui-renderer" + }, + { + "path": "../ui-session" + }, + { + "path": "../ui-slots" + }, + { + "path": "../../api/session-controller/tsconfig.client.json" + }, + { + "path": "../../core/agent" + }, + { + "path": "../../core/session" + }, + { + "path": "../../compaction/compaction" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../attachment/attachment" + }, + { + "path": "../../core/tool-executor-ptc" + }, + { + "path": "../../api/gateway/tsconfig.client.json" + } + ], + "include": [ + "src/client", + "src/css-modules.d.ts", + "src/types.ts" + ] +} diff --git a/packages/client/ui-trajectory/tsconfig.host.json b/packages/client/ui-trajectory/tsconfig.host.json new file mode 100644 index 0000000000..4267370371 --- /dev/null +++ b/packages/client/ui-trajectory/tsconfig.host.json @@ -0,0 +1,35 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types", + "tsBuildInfoFile": "lib/tsconfig.host.tsbuildinfo" + }, + "files": [ + "src/index.ts", + "src/types.ts" + ], + "references": [ + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../core/session" + }, + { + "path": "../../core/tools" + }, + { + "path": "../../core/tool-executor-ptc" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../util/values" + }, + { + "path": "../../typert/protocol" + } + ] +} diff --git a/packages/client/ui-trajectory/tsconfig.json b/packages/client/ui-trajectory/tsconfig.json index 6e49eb797a..2eca820546 100644 --- a/packages/client/ui-trajectory/tsconfig.json +++ b/packages/client/ui-trajectory/tsconfig.json @@ -1,60 +1,11 @@ { - "extends": "../../../tsconfig.base.client.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "lib/types" - }, - "include": [ - "src" - ], + "files": [], "references": [ { - "path": "../../../vendor/cordis" + "path": "./tsconfig.host.json" }, { - "path": "../locale/tsconfig.client.json" - }, - { - "path": "../store" - }, - { - "path": "../ui-conversation/tsconfig.client.json" - }, - { - "path": "../../util/values" - }, - { - "path": "../ui-renderer" - }, - { - "path": "../ui-session" - }, - { - "path": "../ui-slots" - }, - { - "path": "../../api/session-controller/tsconfig.client.json" - }, - { - "path": "../../core/agent" - }, - { - "path": "../../core/session" - }, - { - "path": "../../compaction/compaction" - }, - { - "path": "../../llm/llm" - }, - { - "path": "../../attachment/attachment" - }, - { - "path": "../../core/tool-executor-ptc" - }, - { - "path": "../../api/gateway/tsconfig.client.json" + "path": "./tsconfig.client.json" } ] } diff --git a/packages/client/ui-trajectory/tsdown.config.ts b/packages/client/ui-trajectory/tsdown.config.ts index 7aa03cebc1..620261533c 100644 --- a/packages/client/ui-trajectory/tsdown.config.ts +++ b/packages/client/ui-trajectory/tsdown.config.ts @@ -1,3 +1,3 @@ import { clientBundle } from '../tsdown.client.ts' -export default clientBundle(import.meta.dirname, ['lib/types/index.js']) +export default clientBundle(import.meta.dirname, ['lib/types/index.js'], { hostPhase: true }) diff --git a/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md b/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md index 5601ba74db..99d96d276d 100644 --- a/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md +++ b/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md @@ -118,7 +118,7 @@ Every package below exports a Cordis plugin that a bundle patch can name in a Lo | `@deepseek-ai/dsh-client-ui-subagent` | no | Subagent conversation catalog, continuation routing UI, and '@' reference source | | `@deepseek-ai/dsh-client-ui-theme` | yes | Theme plugin: Host bootstrap for the pre-plugin palette; ThemeRuntime for light/dark/system state and icon replacements; --dsw-* token styles and Appearance settings row | | `@deepseek-ai/dsh-client-ui-tool` | no | Client Tool call-tree renderer and keyed per-tool presentation slot | -| `@deepseek-ai/dsh-client-ui-trajectory` | no | Trajectory event ledger with an interactive timing overview: pure-consumer plugin registering into the conversation ViewMap (no service) | +| `@deepseek-ai/dsh-client-ui-trajectory` | no | Trajectory event ledger and timing overview with live tool-result inspection | | `@deepseek-ai/dsh-client-ui-user-questions` | no | Web ask_user_question composer takeover and plan-review presentation UI | | `@deepseek-ai/dsh-client-ui-workflow-run` | no | Durable workflow-run Conversation Node and nested member disclosure for dsh web | | `@deepseek-ai/dsh-client-ui-workspace` | no | Workspace picker plugin: one WorkspacePicker registered into the sidebar and empty-state workspace slots | diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9aca957a9..3772f3cf41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1493,6 +1493,9 @@ importers: '@deepseek-ai/dsh-client-ui-plugin-manager': specifier: workspace:* version: link:../../client/ui-plugin-manager + '@deepseek-ai/dsh-client-ui-trajectory': + specifier: workspace:* + version: link:../../client/ui-trajectory '@deepseek-ai/dsh-command-feedback': specifier: workspace:* version: link:../../feedback/command-feedback @@ -1692,12 +1695,6 @@ importers: '@deepseek-ai/dsh-subagent': specifier: workspace:* version: link:../../subagent/subagent - '@deepseek-ai/dsh-tool-executor-ptc': - specifier: workspace:* - version: link:../../core/tool-executor-ptc - '@deepseek-ai/dsh-tools': - specifier: workspace:* - version: link:../../core/tools '@deepseek-ai/dsh-typert-protocol': specifier: workspace:* version: link:../../typert/protocol @@ -6640,6 +6637,13 @@ importers: version: 18.3.1(react@18.3.1) packages/client/ui-trajectory: + dependencies: + '@deepseek-ai/dsh-typert-protocol': + specifier: workspace:* + version: link:../../typert/protocol + zod: + specifier: 4.4.3 + version: 4.4.3 devDependencies: '@deepseek-ai/cordis': specifier: workspace:~ @@ -6695,6 +6699,9 @@ importers: '@deepseek-ai/dsh-tool-executor-ptc': specifier: workspace:* version: link:../../core/tool-executor-ptc + '@deepseek-ai/dsh-tools': + specifier: workspace:* + version: link:../../core/tools '@deepseek-ai/dsh-util-values': specifier: workspace:* version: link:../../util/values diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index fddfd12e08..f4103a4299 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -120,7 +120,7 @@ export const SERVICE_PAGE: Record = { workingDirectory: 'working-directory.md', worktrees: 'worktrees.md', sessionController: 'session.md', - sessionToolResults: 'session.md', + sessionToolResults: 'web-client.md', sessionSkillCatalog: 'skills.md', sessions: 'session.md', settings: 'settings.md', @@ -435,8 +435,8 @@ export const LINK_MAP: Readonly> = { SessionEvent: 'session.md', SessionFollowFrame: 'session.md', SessionFollowRequest: 'session.md', - SessionToolResultRequest: 'session.md', - SessionToolResultValue: 'session.md', + SessionToolResultRequest: 'web-client.md', + SessionToolResultValue: 'web-client.md', SessionForkRequest: 'session.md', SessionForkValue: 'session.md', SessionId: 'core.md', diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index ee19c567dc..918b2cd322 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -331,8 +331,8 @@ const SERVICE_ROLES: ServiceRole[] = [ }, { key: 'sessionToolResults', - pkg: 'api-session-controller', - title: 'Live Session tool-result Remote', + pkg: 'client-ui-trajectory', + title: 'Trajectory live tool-result Remote', mode: 'core', note: 'Retains final tool values only for their live Session instance and invalidates inspectors when that instance unloads.', }, diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 44ac6ecef0..de913eeb7c 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -160,7 +160,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/client/ui-settings-web-search': { kind: 'none', reason: 'Browser-side settings surface; registers no model surface.' }, 'packages/client/ui-plan': { kind: 'indirect', reason: 'The chip dispatches /plan off; dsh-plan-mode owns the model-visible policy, exit tool, and logged state.' }, 'packages/client/ui-user-questions': { kind: 'indirect', reason: 'The package mounts dsh-tool-ask-user; that tool owns the model-visible schema and answer rendering.' }, - 'packages/client/ui-trajectory': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, + 'packages/client/ui-trajectory': { kind: 'none', reason: 'Trajectory UI and read-only Host inspector; register nothing model-facing.' }, 'packages/client/ui-workspace': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, 'packages/client/ui-directory-picker-browse': { kind: 'none', reason: 'Browser-side directory-browsing surface; registers nothing model-facing.' }, 'packages/client/ui-directory-picker-native': { kind: 'none', reason: 'Browser-side surface driving the local Desktop or Host OS chooser; registers nothing model-facing.' }, diff --git a/tsconfig.base.json b/tsconfig.base.json index 4a56dee404..91ef687ef0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -336,6 +336,7 @@ "@deepseek-ai/dsh-client-ui-plan": ["./packages/client/ui-plan/src"], "@deepseek-ai/dsh-client-ui-user-questions": ["./packages/client/ui-user-questions/src"], "@deepseek-ai/dsh-client-ui-trajectory": ["./packages/client/ui-trajectory/src"], + "@deepseek-ai/dsh-client-ui-trajectory/types": ["./packages/client/ui-trajectory/src/types.ts"], "@deepseek-ai/dsh-client-ui-workspace": ["./packages/client/ui-workspace/src"], "@deepseek-ai/dsh-client-ui-theme": ["./packages/client/ui-theme/src"], "@deepseek-ai/dsh-client-ui-theme/brand-font.css": ["./packages/client/ui-theme/src/styles/brand-font.css"], diff --git a/tsconfig.client.json b/tsconfig.client.json index 7e4eb91773..4ad6aa5b18 100644 --- a/tsconfig.client.json +++ b/tsconfig.client.json @@ -149,7 +149,7 @@ { "path": "./packages/client/ui-settings-subagent" }, { "path": "./packages/client/ui-settings-web-search" }, { "path": "./packages/client/ui-user-questions" }, - { "path": "./packages/client/ui-trajectory" }, + { "path": "./packages/client/ui-trajectory/tsconfig.client.json" }, { "path": "./packages/session-query/session-log-export/tsconfig.client.json" }, { "path": "./packages/client/ui-open-in-app" }, { "path": "./packages/client/ui-theme/tsconfig.client.json" }, diff --git a/tsconfig.host.json b/tsconfig.host.json index a8196f66f8..da069f7923 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -329,6 +329,7 @@ { "path": "./packages/util/native-command" }, { "path": "./packages/util/home-paths" }, { "path": "./packages/client/ui-plugin-manager/tsconfig.host.json" }, + { "path": "./packages/client/ui-trajectory/tsconfig.host.json" }, { "path": "./packages/util/time" }, { "path": "./packages/util/timeout" }, { "path": "./packages/util/crypto" },