Sfoglia il codice sorgente

Trim redundant source comments

Turtle 1 mese fa
parent
commit
fac6c35e9a
83 ha cambiato i file con 219 aggiunte e 748 eliminazioni
  1. 2 6
      apps/web/src/node-module-stub.ts
  2. 2 4
      apps/web/tests/smoke-real.e2e.ts
  3. 10 10
      docs/config-catalog.md
  4. 2 2
      docs/core-data-structures/subagent.i18n.yaml
  5. 10 27
      docs/core-data-structures/subagent.md
  6. 10 27
      docs/core-data-structures/subagent.zh.md
  7. 1 2
      examples/acp-agent/tests/acp.e2e.ts
  8. 1 1
      examples/acp-agent/tests/hooks.e2e.ts
  9. 5 9
      packages/client/connection/src/client/index.ts
  10. 1 7
      packages/client/connection/src/index.ts
  11. 4 9
      packages/client/i18n/src/client/index.ts
  12. 1 8
      packages/client/i18n/src/index.ts
  13. 1 5
      packages/client/runtime/src/client/contract/store.ts
  14. 8 30
      packages/client/runtime/src/client/index.ts
  15. 4 4
      packages/client/runtime/src/client/sessions/fold-adapter.ts
  16. 1 2
      packages/client/runtime/src/client/sessions/service.ts
  17. 8 15
      packages/client/runtime/src/client/sessions/session.ts
  18. 1 8
      packages/client/runtime/src/index.ts
  19. 1 2
      packages/client/runtime/tests/sessions-service.spec.ts
  20. 5 22
      packages/client/ui-conversation/src/client/apply.ts
  21. 1 6
      packages/client/ui-conversation/src/client/chat/StatsLine.tsx
  22. 3 23
      packages/client/ui-conversation/src/client/contract/slots.ts
  23. 3 16
      packages/client/ui-conversation/src/client/contract/views.ts
  24. 3 8
      packages/client/ui-conversation/src/client/index.ts
  25. 4 10
      packages/client/ui-conversation/src/client/service.ts
  26. 2 11
      packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
  27. 5 22
      packages/client/ui-conversation/src/client/stores.ts
  28. 2 8
      packages/client/ui-conversation/src/index.ts
  29. 0 1
      packages/client/ui-conversation/tests/apply-inject.spec.tsx
  30. 1 6
      packages/client/ui-conversation/tests/chat-store.spec.ts
  31. 0 2
      packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx
  32. 1 1
      packages/client/ui-conversation/tests/chat-view.spec.tsx
  33. 0 5
      packages/client/ui-conversation/tests/gate-branch-tails.spec.tsx
  34. 2 8
      packages/client/ui-conversation/tests/hook.ts
  35. 3 10
      packages/client/ui-conversation/tests/selection-survival.spec.ts
  36. 2 8
      packages/client/ui-layout/src/index.ts
  37. 1 1
      packages/client/ui-layout/tests/app-frame.spec.tsx
  38. 1 3
      packages/client/ui-primitives/src/index.ts
  39. 1 3
      packages/client/ui-question/tests/browser-plugin.spec.ts
  40. 3 11
      packages/client/ui-sidebar/src/client/SidebarRoot.tsx
  41. 5 16
      packages/client/ui-sidebar/src/client/index.ts
  42. 1 8
      packages/client/ui-sidebar/src/client/tree.ts
  43. 2 8
      packages/client/ui-sidebar/src/index.ts
  44. 1 2
      packages/client/ui-sidebar/tests/apply.spec.tsx
  45. 1 2
      packages/client/ui-sidebar/tests/sidebar-root.spec.tsx
  46. 4 9
      packages/client/ui-slots/src/index.ts
  47. 2 10
      packages/client/ui-slots/src/renderer.ts
  48. 1 15
      packages/client/ui-slots/src/store.ts
  49. 2 6
      packages/client/ui-theme/src/client/index.ts
  50. 1 8
      packages/client/ui-theme/src/index.ts
  51. 3 7
      packages/client/ui-trajectory/src/client/index.ts
  52. 2 8
      packages/client/ui-trajectory/src/index.ts
  53. 1 2
      packages/client/ui-trajectory/tests/views.spec.tsx
  54. 1 12
      packages/client/web-react/src/index.ts
  55. 2 17
      packages/client/web-react/src/scoped-slots.tsx
  56. 6 15
      packages/client/web-react/src/session-provider.tsx
  57. 2 4
      packages/client/web-react/tests/bind.spec.tsx
  58. 2 4
      packages/client/web-react/tests/stale-authorization.spec.tsx
  59. 5 14
      packages/client/web/src/app-shell.ts
  60. 2 6
      packages/client/web/src/platform.ts
  61. 1 8
      packages/core/agent-loop/tests/agent.spec.ts
  62. 0 2
      packages/core/agent-loop/tests/cancel.spec.ts
  63. 0 2
      packages/core/agent-loop/tests/contract-regressions.spec.ts
  64. 0 4
      packages/core/session/tests/surface.spec.ts
  65. 0 3
      packages/core/tools/tests/tools.spec.ts
  66. 2 2
      packages/fs/fs-local/src/fsio.ts
  67. 2 9
      packages/fs/tool-fs/tests/fs-tools.e2e.ts
  68. 1 2
      packages/guard/repeat-tool-guard/tests/repeat-tool-guard.spec.ts
  69. 0 5
      packages/hooks/hooks-claude/tests/coverage-cases.ts
  70. 0 1
      packages/host/webserver/tests/web-plugins.spec.ts
  71. 9 9
      packages/mcp/mcp-client/src/index.ts
  72. 0 2
      packages/mcp/mcp-client/tests/apply.spec.ts
  73. 1 4
      packages/mcp/mcp-client/tests/mcp-client.e2e.ts
  74. 0 1
      packages/mcp/mcp-client/tests/mcp-client.spec.ts
  75. 1 1
      packages/subagent/subagent-acp/tests/subagent-acp.e2e.ts
  76. 2 9
      packages/subagent/subagent-spawn/tests/spawn.e2e.ts
  77. 12 31
      packages/subagent/subagent/src/types.ts
  78. 18 77
      packages/ui/acp/src/index.ts
  79. 0 5
      packages/ui/acp/tests/bridge.spec.ts
  80. 1 1
      packages/ui/acp/tests/config-options.spec.ts
  81. 4 25
      packages/ui/acp/tests/dispose.spec.ts
  82. 2 5
      packages/ui/acp/tests/properties.spec.ts
  83. 1 4
      packages/ui/tui/tests/tui.spec.ts

+ 2 - 6
apps/web/src/node-module-stub.ts

@@ -1,10 +1,6 @@
 /**
- * Browser stand-in for `node:module`, mapped by the vite alias in
- * vite.config.ts (design §2.4). The vendored Loader's internal.ts imports
- * `createRequire` at module scope but only calls it inside
- * `ModuleLoader.fromInternal()`, whose version probe is compiled to the
- * `"0.0.0"` define in the browser build — so this throw is a fail-loud
- * tripwire for any path that would genuinely need Node's module machinery.
+ * Browser stand-in for `node:module`. `createRequire` is unreachable in the
+ * configured loader path and fails loud if that assumption changes.
  */
 
 /** Throwing stand-in for node:module's createRequire (never reached in the browser boot). */

+ 2 - 4
apps/web/tests/smoke-real.e2e.ts

@@ -333,10 +333,8 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY || notReady.length > 0)('web smoke
     const prompt = `Please answer this request carefully: explain event sourcing in two sentences, ending with exactly ${ROUND_DONE_MARKER}.`
     await input.fill(prompt)
     await input.press('Enter')
-    // startSession chain: session mounts, composer moves to the bottom.
-    // Regression pin (P0, 585671106): this send used to white-screen the tree
-    // (scope tag lost to a duplicate inlined runtime instance) — body going
-    // near-empty here means that class of bug is back.
+    // The first send must keep the session tree mounted; a near-empty body
+    // reveals a duplicate runtime bundle with incompatible scope tags.
     await page.waitForFunction(() => document.body.innerText.length > 50, undefined, { timeout: 15_000 })
     expect(pageErrors).toEqual([])
     await page.waitForFunction(

+ 10 - 10
docs/config-catalog.md

@@ -27,7 +27,7 @@ export interface AcpConfig {
 
 Depends on: `Stream` (`@agentclientprotocol/sdk`)
 
-Source: [`packages/ui/acp/src/index.ts:285`](../packages/ui/acp/src/index.ts)
+Source: [`packages/ui/acp/src/index.ts:275`](../packages/ui/acp/src/index.ts)
 
 ## `@deepseek-ai/dsh-acp-demo`
 
@@ -710,12 +710,12 @@ Source: [`packages/lsp/lsp-local/src/index.ts:85`](../packages/lsp/lsp-local/src
 Requires: `tools`
 
 ```ts config-catalog
-/** Discriminated union of all supported MCP transport configurations. */
+/** Configuration for one stdio or Streamable HTTP MCP server. */
 export type Config = StdioConfig | StreamableHttpConfig
 
 /** Config for connecting to an MCP server via a spawned child process over stdio. */
 export interface StdioConfig {
-  /** Transport type: spawn a child process and communicate over stdio. */
+  /** Selects child-process stdio transport. */
   transport: 'stdio'
   /**
    * Stable local namespace for this server's model-facing tool names
@@ -723,21 +723,21 @@ export interface StdioConfig {
    * unique across live mcp-client instances.
    */
   serverName: string
-  /** Executable to spawn. */
+  /** Executable used to start the server. */
   command: string
-  /** Arguments passed to the command. */
+  /** Arguments passed directly, without shell interpolation. */
   args: string[]
   /** Extra env vars merged on top of scrubbed ambient env. */
   env: Record<string, string>
   /** Working directory for the child process. */
   cwd: string
-  /** Timeout per callTool invocation (ms). */
+  /** Per-tool-call timeout in milliseconds. */
   toolCallTimeoutMs: number
 }
 
 /** Config for connecting to an MCP server over Streamable HTTP (SSE). */
 export interface StreamableHttpConfig {
-  /** Transport type: connect to an MCP server over Streamable HTTP (SSE). */
+  /** Selects Streamable HTTP transport. */
   transport: 'streamable-http'
   /**
    * Stable local namespace for this server's model-facing tool names
@@ -745,11 +745,11 @@ export interface StreamableHttpConfig {
    * unique across live mcp-client instances.
    */
   serverName: string
-  /** MCP server URL. */
+  /** MCP endpoint URL. */
   url: string
-  /** Extra headers (e.g. auth tokens). */
+  /** Additional headers attached to MCP requests. */
   headers: Record<string, string>
-  /** Timeout per callTool invocation (ms). */
+  /** Per-tool-call timeout in milliseconds. */
   toolCallTimeoutMs: number
 }
 ```

+ 2 - 2
docs/core-data-structures/subagent.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-subagent.md: 0335a3f0780ae17b57ae730f5a49a269261c8073
-subagent.zh.md: dac48b624f6e0cfc28737e3e1a2774ba2d97e85b
+subagent.md: fda4b4b738c648c893c65a633e4a0d6a1761424f
+subagent.zh.md: ba43789a3e4efe59b197f6454c977db52d90aca1

+ 10 - 27
docs/core-data-structures/subagent.md

@@ -22,13 +22,9 @@ A provider advertises its **start-time** features on a static descriptor the ser
  * is the capability.
  */
 interface SubagentCapabilities {
-  /** Honor {@link SubagentStartRequest.outputSchema} (structured final output). */
   readonly outputSchema: boolean
-  /** Enforce {@link SubagentStartRequest.maxDepth} (recursion cap). */
   readonly depthLimit: boolean
-  /** Enforce {@link SubagentStartRequest.toolFilter} (child tool scoping). */
   readonly toolFilter: boolean
-  /** Honor {@link SubagentStartRequest.persona} (a per-child persona). */
   readonly persona: boolean
 }
 ```
@@ -45,16 +41,11 @@ The tool layer builds this request from the model input and its own config; the
  * passes it to {@link SubagentProvider.start}.
  */
 interface SubagentStartRequest {
-  /** The task/prompt for the child agent (a user message in the child session). */
+  /** Content delivered as the child's user message. */
   readonly prompt: ContentBlock[]
   /**
-   * The spawning ("parent") agent — the one whose tool call started this
-   * subagent. REQUIRED: in-process backends read `parent.session.header` for
-   * the working directory, the `parentSession` lineage to stamp on the child,
-   * and the parent's delegation depth. The out-of-process backend (ACP) reads
-   * exactly one field — the session header's cwd, the child's workspace when
-   * no deployment `cwd` override is configured; nothing else crosses the
-   * process boundary.
+   * The spawning agent. In-process providers derive workspace, lineage, and
+   * delegation depth from its durable session state; ACP uses only its cwd.
    */
   readonly parent: Agent
   /**
@@ -65,7 +56,6 @@ interface SubagentStartRequest {
    * afterward.
    */
   readonly signal: AbortSignal
-  /** Per-child agent options (model and plugin-defined extension fields). */
   readonly agentOptions?: AgentOptions
   /**
    * Object-rooted JSON Schema within `assertObjectJsonSchema`'s enforced subset. Start rejects
@@ -135,15 +125,12 @@ interface SubagentResult {
  * non-`completed` result to an `isError` tool result.
  */
 interface SubagentStopReasonMap {
-  /** The child finished its turn normally. */
   completed: 'completed'
-  /** The run was cancelled by its request signal or by disposal. */
+  /** Cancelled through the request signal or disposal. */
   aborted: 'aborted'
-  /** The child failed (model error, transport error). */
+  /** Model or transport failure. */
   error: 'error'
-  /** The child hit its token ceiling before finishing. */
   'max-tokens': 'max-tokens'
-  /** The child declined the task. */
   refusal: 'refusal'
 }
 ```
@@ -180,9 +167,8 @@ interface SubagentRun {
    */
   readonly result: Promise<SubagentResult>
   /**
-   * Cancel remaining work, reach child quiescence, and release the run's
-   * resources (in-process: dispose the owned agent and remove its session;
-   * ACP: kill and reap the subprocess). Idempotent.
+   * Cancel remaining work, reach child quiescence, and release resources.
+   * Idempotent.
    */
   dispose(): Promise<void>
   /**
@@ -206,12 +192,9 @@ Each provider is a named child-agent transport, and multiple providers may coexi
 
 ```ts type-equiv
 /**
- * A subagent backend: one transport for running a child agent (in-process
- * spawn/fork, ACP to another process, …). Implementations register under a
- * unique name via {@link SubagentService.registerProvider}; multiple providers
- * coexist in one context (unlike the single-implementation bash seam). The
- * Providers are trusted same-process implementations; callers treat their
- * descriptors and returned values as borrowed immutable data.
+ * One registered transport for running child agents. Providers are trusted
+ * same-process implementations; callers treat descriptors and returned values
+ * as borrowed immutable data.
  */
 interface SubagentProvider {
   /** Unique registry name (e.g. `spawn`, `fork`, `acp`). */

+ 10 - 27
docs/core-data-structures/subagent.zh.md

@@ -22,13 +22,9 @@ subagent seam:一个 agent(智能体)将工作委派给子 agent。与 [ba
  * is the capability.
  */
 interface SubagentCapabilities {
-  /** Honor {@link SubagentStartRequest.outputSchema} (structured final output). */
   readonly outputSchema: boolean
-  /** Enforce {@link SubagentStartRequest.maxDepth} (recursion cap). */
   readonly depthLimit: boolean
-  /** Enforce {@link SubagentStartRequest.toolFilter} (child tool scoping). */
   readonly toolFilter: boolean
-  /** Honor {@link SubagentStartRequest.persona} (a per-child persona). */
   readonly persona: boolean
 }
 ```
@@ -45,16 +41,11 @@ interface SubagentCapabilities {
  * passes it to {@link SubagentProvider.start}.
  */
 interface SubagentStartRequest {
-  /** The task/prompt for the child agent (a user message in the child session). */
+  /** Content delivered as the child's user message. */
   readonly prompt: ContentBlock[]
   /**
-   * The spawning ("parent") agent — the one whose tool call started this
-   * subagent. REQUIRED: in-process backends read `parent.session.header` for
-   * the working directory, the `parentSession` lineage to stamp on the child,
-   * and the parent's delegation depth. The out-of-process backend (ACP) reads
-   * exactly one field — the session header's cwd, the child's workspace when
-   * no deployment `cwd` override is configured; nothing else crosses the
-   * process boundary.
+   * The spawning agent. In-process providers derive workspace, lineage, and
+   * delegation depth from its durable session state; ACP uses only its cwd.
    */
   readonly parent: Agent
   /**
@@ -65,7 +56,6 @@ interface SubagentStartRequest {
    * afterward.
    */
   readonly signal: AbortSignal
-  /** Per-child agent options (model and plugin-defined extension fields). */
   readonly agentOptions?: AgentOptions
   /**
    * Object-rooted JSON Schema within `assertObjectJsonSchema`'s enforced subset. Start rejects
@@ -135,15 +125,12 @@ interface SubagentResult {
  * non-`completed` result to an `isError` tool result.
  */
 interface SubagentStopReasonMap {
-  /** The child finished its turn normally. */
   completed: 'completed'
-  /** The run was cancelled by its request signal or by disposal. */
+  /** Cancelled through the request signal or disposal. */
   aborted: 'aborted'
-  /** The child failed (model error, transport error). */
+  /** Model or transport failure. */
   error: 'error'
-  /** The child hit its token ceiling before finishing. */
   'max-tokens': 'max-tokens'
-  /** The child declined the task. */
   refusal: 'refusal'
 }
 ```
@@ -182,9 +169,8 @@ interface SubagentRun {
    */
   readonly result: Promise<SubagentResult>
   /**
-   * Cancel remaining work, reach child quiescence, and release the run's
-   * resources (in-process: dispose the owned agent and remove its session;
-   * ACP: kill and reap the subprocess). Idempotent.
+   * Cancel remaining work, reach child quiescence, and release resources.
+   * Idempotent.
    */
   dispose(): Promise<void>
   /**
@@ -208,12 +194,9 @@ interface SubagentRun {
 
 ```ts type-equiv
 /**
- * A subagent backend: one transport for running a child agent (in-process
- * spawn/fork, ACP to another process, …). Implementations register under a
- * unique name via {@link SubagentService.registerProvider}; multiple providers
- * coexist in one context (unlike the single-implementation bash seam). The
- * Providers are trusted same-process implementations; callers treat their
- * descriptors and returned values as borrowed immutable data.
+ * One registered transport for running child agents. Providers are trusted
+ * same-process implementations; callers treat descriptors and returned values
+ * as borrowed immutable data.
  */
 interface SubagentProvider {
   /** Unique registry name (e.g. `spawn`, `fork`, `acp`). */

+ 1 - 2
examples/acp-agent/tests/acp.e2e.ts

@@ -114,11 +114,10 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('acp-agent e2e: real prompt over
     })
     expect(['end_turn', 'max_tokens']).toContain(res.stopReason)
 
-    // Verify the WORLD, not the agent's self-report: read the file from disk.
+    // Assert the filesystem effect independently of the model response.
     const proof = await readFile(join(workdir, 'proof.txt'), 'utf8')
     expect(proof).toContain('ACP_OK')
 
-    // And the client saw tool-call activity stream through.
     const toolCalls = updates.filter(u => u.sessionUpdate === 'tool_call')
     expect(toolCalls.length).toBeGreaterThan(0)
 

+ 1 - 1
examples/acp-agent/tests/hooks.e2e.ts

@@ -62,7 +62,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('acp-agent e2e: a PreToolUse hook
     // the model, not a turn failure).
     expect(['end_turn', 'max_tokens']).toContain(res.stopReason)
 
-    // Verify that the denied hook left no filesystem effect.
+    // Assert the denied operation independently of the model response.
     await expect(access(join(workdir, 'proof.txt'))).rejects.toThrow()
 
     // A blocked call is still streamed with the hook's reason as an error.

+ 5 - 9
packages/client/connection/src/client/index.ts

@@ -1,10 +1,7 @@
 /**
- * Browser half of the wire consumer layer (contract: api-contracts v3
- * section 3; export inventory = v3 §3.2). The wire is this package's client
- * half in its entirety — apply mounts ctx.connection: the shared api client
- * plus the connection controller handle. Mode selection (?fixture) happens
- * here so the rest of the client tree is mode-blind; the controller's sinks
- * are wired by the runtime plugin (object layer), which injects this service.
+ * Browser wire client. The plugin selects fixture or HTTP transport, provides
+ * the shared API client, and lets the runtime object layer start the stream
+ * controller with its sinks.
  */
 import type { Context } from 'cordis'
 import type { IApiClient } from './api.ts'
@@ -23,9 +20,8 @@ export type {
 } from './api.ts'
 export { RpcId, AbstractApiClient, transportError } from './api.ts'
 
-// ---- Connection loop types (part of the ConnectionHandle.start contract;
-// the controller class itself stays package-internal — apply owns the loop,
-// tests reach it via src) ----
+// Connection loop types are public through ConnectionHandle.start; the
+// controller remains package-internal.
 export type { ConnectionConfig, ConnectionSinks, ConnectionState }
 
 

+ 1 - 7
packages/client/connection/src/index.ts

@@ -1,10 +1,4 @@
-/**
- * Connection plugin, node half. The package IS a dshClient plugin: the wire
- * consumer layer lives in its client half in full (src/client/ — contract:
- * api-contracts v3 section 3, inventory §3.2); consumers import the /client
- * subpath. The empty apply exists so the plugin appears in the host Loader
- * (lifecycle governance + dshClient discovery).
- */
+/** Host loader entry for the browser wire client exported from `./client`. */
 
 /** Host plugin body — no host-side behavior for the connection plugin. */
 export function apply(_ctx: unknown): void {}

+ 4 - 9
packages/client/i18n/src/client/index.ts

@@ -1,15 +1,10 @@
 /**
- * i18n plugin, browser half: namespace x locale dictionary registry with a
- * bound translate function whose reference is stable (safe for inject
- * surfaces). Mounts ctx.i18n and seeds the zh/en base dictionaries.
- * Contract: api-contracts v3 section 8.
+ * Browser-side locale registry. Bound translation functions retain stable
+ * identity for injected consumers.
  */
 import type { Context } from 'cordis'
-// The snapshot-store engine lives in runtime (store relocation): framework
-// data stores like this locale cell use it directly. The store carries no
-// hook — a React consumer binds a selector hook via web-react's
-// bindSnapshotSelector at its own seam (none exists today; the current
-// consumers are translate() reads and test-side subscribe/set).
+// Snapshot stores are framework-neutral; React consumers bind hooks at their
+// rendering boundary.
 import type { SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
 import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
 import { en } from '../locales/en.ts'

+ 1 - 8
packages/client/i18n/src/index.ts

@@ -1,11 +1,4 @@
-/**
- * i18n plugin, node half. Pure UI plugin: the empty apply exists so the
- * plugin appears in the host cordis.yml / Loader (load and lifecycle follow
- * the host; the browser half ships via exports["./client"], discovered
- * through the package.json dshClient declaration). Everything else —
- * I18nService, Translate, LocaleDict — lives in the client half; consumers
- * import the /client subpath. Contract: api-contracts v3 section 8.
- */
+/** Host loader entry for the browser implementation exported from `./client`. */
 
 /** Host plugin body — no host-side behavior for the i18n plugin. */
 export function apply(): void {}

+ 1 - 5
packages/client/runtime/src/client/contract/store.ts

@@ -161,11 +161,7 @@ function deepFreeze(value: unknown): void {
   }
 }
 
-// ---- defineStore shell (slot terminal design §4) ----
-// The type authority is ui-slots' store family (create(scopeKey?) and
-// clearPersisted() included); this module houses only the engine-backed
-// implementation. The one engine-side widening left: instances expose the
-// raw engine store for framework/test surfaces.
+// ui-slots owns the contract; this module supplies the engine implementation.
 
 /** A live engine instance: the contract instance plus the raw engine store. */
 export interface EngineStoreInstance<T, A extends ActionsDecl<T>> extends StoreInstance<T, A> {

+ 8 - 30
packages/client/runtime/src/client/index.ts

@@ -1,12 +1,7 @@
 /**
- * Browser half: the whole runtime contract surface (api-contracts v3 §4) —
- * SlotsService (declaration ledger + renderer seam + store axis, built-in
- * 'root'), SessionsService (list store + current selection + scope tree +
- * object layer), and the cordis Context/Events merges. apply mounts
- * ctx.slots + ctx.sessions and wires the connection stream loop into the
- * object layer. A static-arrival entry: the web shell bundles this module
- * and mounts it through the host graph (module loading lives in
- * @deepseek-ai/dsh-client-modules, entry governance in the vendored Loader).
+ * Browser runtime services for slots, sessions, and connection-stream
+ * delivery. The web shell mounts this static client entry through the host
+ * plugin graph.
  */
 import type { Context } from 'cordis'
 import type { ConnectionHandle, SessionId } from '@deepseek-ai/dsh-client-connection/client'
@@ -17,15 +12,11 @@ import type { SessionListState } from './sessions/service.ts'
 import type { ConversationSnapshot, RunningToolCall, ToolResultNode } from './sessions/conversation.ts'
 
 export { SlotsService } from './slots.ts'
-// RootOwnerProps rides the 'root' SlotMap row (both migrated here from
-// ui-layout: the framework slot is declared by the framework package).
 export type { RootOwnerProps } from './slots.ts'
 export { SessionsService, scopeOf } from './sessions/service.ts'
 export type { Session } from './sessions/session.ts'
 export type { SessionBinding, SessionListState, SessionSummary } from './sessions/service.ts'
-// The snapshot-store engine lives here since the store migration (the data
-// layer owns its substrate; web-react is React glue only). The './client'
-// main export is the single serving door — no store subpath.
+// Runtime owns the snapshot store; web-react only binds it to React.
 export { createSnapshotStore, defineStore, shallowEqual } from './contract/store.ts'
 export type {
   EngineStoreHandle, EngineStoreInstance, ObservableSnapshot, SnapshotStore,
@@ -35,21 +26,11 @@ export type {
   RunningToolCall, SteeringMessageNode,
   ToolResultNode, UnknownSurfaceNode, UserMessageNode,
 } from './sessions/conversation.ts'
-// PendingWait is a value export: tests construct fixture waits directly.
 export { PendingWait } from './sessions/pending.ts'
 export type { PendingInteraction, PendingKind, PendingPayloads } from './sessions/pending.ts'
 export type { SessionId } from '@deepseek-ai/dsh-client-connection/client'
 
-// ---- Narrowed aliases (the single narrowing point of the slot type chain:
-// ui-slots/web-react stay generic and dependency-inverted; the client-tree
-// concrete types live here, where their subjects live) ----
-
-/**
- * The client cordis context face: the base Context plus the service keys
- * this package's declaration merge contributes (slots/sessions/loader) and
- * every later plugin's merge. A plain alias — the merges land on Context
- * itself inside the client program; the name marks intent at consumer seams.
- */
+/** Client-side Cordis context after declaration merging. */
 export type ClientContext = Context
 
 /** The conversation-snapshot selector hook (ConvViewProps/ToolRowProps take this). */
@@ -69,14 +50,12 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
    * every session-scope slot component receives these from the framework.
    */
   interface SessionStandardProps {
-    /** Selector hook over this session's conversation snapshot. */
     useSession: SnapshotSelectorHook<ConversationSnapshot>
     /** The framework-resolved session id (owners never pass it). */
     sessionId: SessionId
   }
-  /** Global standard kit, real members: the session-list hook every slot component receives. */
+  /** Props injected into every global slot component. */
   interface GlobalStandardProps {
-    /** Selector hook over the session list snapshot (`current` included — the arbitrated selection seat). */
     useSessions: SnapshotSelectorHook<SessionListState>
   }
 }
@@ -99,9 +78,8 @@ declare module 'cordis' {
 /** Required services: the wire handle mounted by the connection plugin. */
 export const inject = ['connection']
 
-/**
- * Client plugin body: mount slots + sessions, start the stream loop.
- * @param ctx - client cordis context.
+/** Mounts the browser runtime services and connection stream.
+ * @param ctx - Client Cordis context.
  */
 export function apply(ctx: Context): void {
   ctx.plugin(SlotsService)

+ 4 - 4
packages/client/runtime/src/client/sessions/fold-adapter.ts

@@ -24,10 +24,10 @@ export interface CallIndexEntry {
   callView: ToolCallView | null
 }
 
-/** Non-surface-eligible sentinel event (safely skipped by surfaceOpOf's undefined branch).
- *  'noop/padding' is not a real event type on purpose: a genuine type with fake data would
- *  surface as garbage the day anyone adds handling for it (design §D.1; the cast is the one
- *  place a synthetic event enters the window). */
+/** Non-surface sentinel used to preserve paged-window sequence offsets.
+ * `noop/padding` is deliberately not a real event type, so it cannot acquire
+ * surface behavior; this cast is the only synthetic event entry point.
+ */
 function paddingEvent(seq: number): SessionEvent {
   return { type: 'noop/padding', seq, time: 0, data: {} } as unknown as SessionEvent
 }

+ 1 - 2
packages/client/runtime/src/client/sessions/service.ts

@@ -291,8 +291,7 @@ export class SessionsService {
       fiber,
       ctx,
       binding: { sessionId: id, session, ctx },
-      // Bare source form (store migration): the Session object IS the
-      // observable; the React side binds the useSession hook per cell.
+      // Session is the observable; React binds a selector hook at its own seam.
       cell: { sessionId: id, session },
     }
     this.scopes.set(id, record)

+ 8 - 15
packages/client/runtime/src/client/sessions/session.ts

@@ -1,7 +1,4 @@
-// Session: wraps every contract call that needs a sessionId + all conversation state for this
-// session (design §A.2/§A.9/§D.2/§D.3). Instances are resident (ruling 2): never destroyed once
-// created, they keep consuming mux frames in the background; React connects directly via
-// subscribe/getSnapshot.
+// Sessions remain resident after creation so they continue consuming mux frames off-screen.
 
 import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
 import type { SessionEvent } from '@deepseek-ai/dsh-session/types'
@@ -22,14 +19,12 @@ import { FoldAdapter } from './fold-adapter.ts'
 import { Notifier } from './notifier.ts'
 import { PartialAccumulator } from './partial.ts'
 
-/** Messages per page (F.4 ledger: promote to Config at graduation; every call site references this constant). */
+/** Messages requested per history page. */
 export const PAGE_MESSAGES = 50
 
 /**
- * Per-session state owner: event window + fold + partial, snapshot out via
- * subscribe/getSnapshot (see the web client architecture RFC). Bare source
- * only (store migration): the React machinery binds the per-cell useSession
- * hook at its own seam — no selector hook member lives on the data layer.
+ * Owns a session's event window, derived conversation state, and observable
+ * snapshot. React bindings remain outside this data layer.
  */
 export class Session implements ObservableSnapshot<ConversationSnapshot> {
   // ---- Window and derived state (all private; the snapshot is the only read surface) ----
@@ -54,8 +49,7 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
    *  Derived from window events (turn/end sweep) — rebuilt by rebuildDerivedFromWindow like partial/openCalls. */
   private frozenNodes: ConversationNode[] = []
   private pending = new Map<string, PendingInteraction>()
-  // Revision counters + caches backing the snapshot's reference-stability contract (§A.9.4/§C.2,
-  // audit S5): buildSnapshot reuses the previous array when the revision is unchanged, so
+  // Revision counters preserve array identity when derived content is unchanged, so
   // React.memo children survive unrelated snapshot swaps (chunk storms must not re-render every
   // tool card and pending card). Mutation sites bump the matching revision. partial needs no
   // counter — PartialAccumulator.toPartial already returns a cached reference when unchanged.
@@ -69,9 +63,9 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
   private removed = false
   private promptError: PromptError | null = null
   private lastAgentError: string | null = null
-  /** Buffer for live events arriving while open/resync is in flight (stitched by seq once history lands, §D.3). */
+  /** Live events buffered during open/resync and stitched by sequence once history lands. */
   private liveBuffer: { event: SessionEvent; view: ToolEventView | undefined }[] = []
-  /** Gap-repair (resync-lite) in flight: acceptLiveEvent detours to liveBuffer until the tail page lands (audit S3). */
+  /** Gap repair in flight; live events detour to the buffer until the tail page lands. */
   private stitching = false
   /** subscribed.lastSeq baseline (gap detection; null when no subscribed frame arrived — degrade to the liveBuffer dedup path). */
   private subscribedLastSeq: number | null = null
@@ -292,8 +286,7 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
     this.notifier.markDirty()
   }
 
-  /** Instance-eviction hook, reserved no-op (design §F.6): resident instances are never destroyed
-   *  in v1; an eviction policy lands here (unsubscribe, drop buffers) without touching call sites. */
+  /** No-op because session instances remain resident. */
   dispose(): void {}
 
   // ---- 私有 ----

+ 1 - 8
packages/client/runtime/src/index.ts

@@ -1,11 +1,4 @@
-/**
- * Runtime plugin, node half. The implementation lives entirely in the client
- * half (src/client/ — SlotsService, SessionsService + object layer, and the
- * shell-held ClientLoader under ./loader); consumers import the /client or
- * /loader subpaths. The empty apply exists so the plugin appears in the host
- * Loader (lifecycle governance + dshClient discovery). Contract:
- * api-contracts v3 section 4.
- */
+/** Host loader entry for the browser runtime exported from `./client` and `./loader`. */
 
 /** Host plugin body — no host-side behavior for the runtime plugin. */
 export function apply(_ctx: unknown): void {}

+ 1 - 2
packages/client/runtime/tests/sessions-service.spec.ts

@@ -187,8 +187,7 @@ describe('cell (render-layer session kit)', () => {
     const cell = b.svc.cell('s1')
     expect(cell).toBeDefined()
     expect(cell?.sessionId).toBe('s1')
-    // Bare-source form (store migration): the cell carries the Session
-    // observable itself; hook binding happens in the React machinery.
+    // Hook binding happens in React; the cell carries the observable itself.
     expect(cell?.session).toBe(b.svc.manager.get(sid('s1')))
     expect(b.svc.cell('s1')).toBe(cell)
     expect(b.svc.cell('ghost')).toBeUndefined()

+ 5 - 22
packages/client/ui-conversation/src/client/apply.ts

@@ -1,14 +1,4 @@
-/**
- * Client plugin body: register the conversation/details slot occupants and
- * the no-session empty state, contribute the chat entry into the
- * 'conversation.view' ring that the conversation registration declares, then
- * mount the conversation service (class plugin) and the bash toolview sample.
- * Assembly only — components receive everything through props: the framework
- * standard kit and store faces arrive automatically from the declarations
- * below; the inject factories contribute the plain-data-and-callbacks
- * business face (design §5). Tool rows are ordinary keyed-slot registrations
- * into 'conversation.chat.toolview' — no dedicated registry exists.
- */
+/** Registers the conversation components, shared store, and service callbacks. */
 import type { Context } from 'cordis'
 import type { BoundActions } from '@deepseek-ai/dsh-client-ui-slots'
 import type { SessionId, SessionsService } from '@deepseek-ai/dsh-client-runtime/client'
@@ -25,7 +15,7 @@ import { ConversationRoot } from './skeleton/ConversationRoot.tsx'
 import { DetailsPanel } from './skeleton/DetailsPanel.tsx'
 import { EmptyState } from './skeleton/EmptyState.tsx'
 
-/** Required services (cordis fiber inject — the loader passes the whole export surface as an object plugin). */
+/** Services required by the conversation plugin. */
 export const inject = ['slots', 'layout', 'sessions']
 
 /** Resolve the session-scoped conversation service (scope-addressed send/cancel), failing loud. */
@@ -37,24 +27,17 @@ function scopedConversation(sessions: SessionsService, id: SessionId): Conversat
   return conversation
 }
 
-/**
- * Client plugin body.
- * @param ctx - client root context.
+/** Mounts the conversation plugin.
+ * @param ctx - Client root context.
  */
 export function apply(ctx: Context): void {
   const sessions = ctx.sessions
   const layout = ctx.layout
   const slots = ctx.slots
 
-  // Shared store handle, constructed here so its identity lives and dies with
-  // this fiber (a module-level handle would be a de-facto singleton). The
-  // conversation, chat-view, and details registrations all declare it; same
-  // scope key = same instance, so chat-view selection writes and details
-  // reads meet in one store.
+  // Apply-time construction keeps store identity bound to this fiber.
   const chatStore = createChatStore()
 
-  // Tab projection over the view ring's ledger (list entries carry id/order/
-  // label as registration options; the ledger keeps them order-sorted).
   const viewTabs = (): ViewTab[] => {
     const tabs: ViewTab[] = []
     for (const entry of slots.entries('conversation.view')) {

+ 1 - 6
packages/client/ui-conversation/src/client/chat/StatsLine.tsx

@@ -1,9 +1,4 @@
-// StatsLine: the session stats row (figma 122:11212 "cache hit 92% · 1,284
-// tokens · 45.2s · 5 turns · 32 steps"), rendered by ChatView under the flow
-// (part of the chat view body — the chrome attachment mechanism retired with
-// the view ring). Duration has no data source in P-I (ledger). Subscribes to
-// `nodes` only: chunk batches never swap that reference, so the row renders
-// zero times during streaming (the RFC performance model's acceptance row).
+// Settled-node identity prevents stream-delta updates from rerendering this row.
 
 import { memo, useMemo } from 'react'
 import type { ConversationSnapshot } from '@deepseek-ai/dsh-client-runtime/client'

+ 3 - 23
packages/client/ui-conversation/src/client/contract/slots.ts

@@ -1,15 +1,4 @@
-/**
- * Slot-ring contract for the conversation package: the 'conversation.view'
- * slot this package declares (the view ring — one list entry per conversation
- * view tab), the chat view's per-tool row hole ('conversation.chat.toolview',
- * keyed on the wire tool name), and the composed props shapes its registrants
- * mount into the layout-owned slots (conversation / details /
- * conversation.empty) plus its own slots. Terminal slot design (§3): full
- * component props are the automatic shares — PropsRuntime<K> (framework
- * standard kit) & PropsRenderSlots<S> (declared children) & PropsStore<H>
- * (declared store's read/write faces) & the injected business face declared
- * here.
- */
+/** Conversation slot declarations and their composed component props. */
 import type { PropsRenderSlots, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots'
 import type { PendingInteraction, SessionId, ToolCallBlock } from '@deepseek-ai/dsh-client-runtime/client'
 import type { createChatStore } from '../stores.ts'
@@ -93,15 +82,9 @@ export type ConvViewProps = PropsRuntime<'conversation.view'>
 /** The shared chat store handle type (apply constructs one; the conversation, details, and chat-view registrations all declare it). */
 export type ChatStore = ReturnType<typeof createChatStore>
 
-/**
- * Injected share of the conversation slot: plain data and callbacks only
- * (design §5 — hooks are framework-made). The store lines that used to ride
- * here live in the declared {@link ChatStore}; ancestry derives from the
- * standard useSessions hook in-component; views render through the declared
- * 'conversation.view' child slot, with this face projecting the tab strip.
- */
+/** Business callbacks injected into the conversation slot. */
 export interface ConversationInjected {
-  /** View tab read face (uSES triple over the 'conversation.view' slot ledger). */
+  /** Views projected from the `conversation.view` slot ledger. */
   views: {
     list(): readonly ViewTab[]
     subscribe(fn: () => void): () => void
@@ -111,7 +94,6 @@ export interface ConversationInjected {
   send(text: string, mode: 'queue' | 'steer'): void
   /** Cancel the in-flight turn (failure surfaces via snapshot.promptError). */
   stop(): void
-  /** Navigate to another session (breadcrumb ancestors). */
   open(id: SessionId): void
 }
 
@@ -123,7 +105,6 @@ export interface ConversationInjected {
  * with zero owner changes.
  */
 export interface ComposerChainProps {
-  /** The session's live pending waits, in arrival order (snapshot reference). */
   interactions: readonly PendingInteraction[]
 }
 
@@ -139,7 +120,6 @@ export type ConversationSlotProps =
 export interface ChatViewInjected {
   /** Selection write + details panel opening in one gesture (store action + layout orchestration). */
   openDetails(target: SelectionTarget): void
-  /** Pull one older history page. */
   loadOlder(): void
 }
 

+ 3 - 16
packages/client/ui-conversation/src/client/contract/views.ts

@@ -1,14 +1,4 @@
-/**
- * Shared conversation contract primitives: the view tab projection (slot
- * entries in 'conversation.view' surface as tabs), the chat store state
- * shared through the declared store, and the selection primitives every
- * domain consumes. Shared face between the skeleton domain (tab strip +
- * view outlet) and the chat domain; domain implementation files import this,
- * never each other. The view ring itself IS the 'conversation.view' slot
- * (contract in slots.ts) — the package-local view registry is retired, and
- * so is the hand-threaded translate channel (framework-level per-slot i18n
- * injection is the planned replacement).
- */
+/** Shared conversation view, selection, and store-state contracts. */
 
 /** Tool call identity as carried on the wire (branded upstream in connection). */
 export type CallId = string
@@ -23,11 +13,8 @@ export interface SelectionTarget { turnSeq: number; stepSeq?: number; callId?: C
 export interface ViewTab { id: string; label: string }
 
 /**
- * Chat store state (slot terminal design §4): the per-session store shared by
- * the conversation, chat-view, and details registrations. `createChatStore`
- * implements this shape. `view` may carry a stale persisted id after a view
- * plugin unloads — the slot ledger is the runtime validator (unknown ids fall
- * back to the first registered view).
+ * Per-session state shared by conversation, chat-view, and details slots.
+ * Unknown persisted view ids fall back to the first registered view.
  */
 export interface ChatStoreState {
   /** Details-linkage channel (conversation writes, details reads). */

+ 3 - 8
packages/client/ui-conversation/src/client/index.ts

@@ -1,12 +1,7 @@
 /**
- * Conversation domain plugin, browser half: skeleton (header/tabs/composer),
- * the 'conversation.view' slot ring (chat entry here; other plugins
- * contribute view tabs through ctx.slots), the chat view's keyed
- * 'conversation.chat.toolview' row hole, scope-addressed ConversationService,
- * minimal details panel. Contract: api-contracts v3 section 7. Thin shell:
- * type surfaces live in contract/, assembly in apply.ts; the implementation
- * domains (skeleton/chat) never import each other — contract/ is their only
- * shared face.
+ * Browser conversation plugin. `contract/` is the shared type boundary
+ * between the independently implemented skeleton and chat domains; `apply.ts`
+ * owns their slot assembly.
  */
 import type { ConversationService } from './service.ts'
 

+ 4 - 10
packages/client/ui-conversation/src/client/service.ts

@@ -1,17 +1,11 @@
 /**
- * ConversationService implementation: scope-addressed send/cancel and the
- * empty-state startSession chain. Contract: api-contracts v3 section 7.
- * Selection/draft state moved to the declared chat store (slot terminal
- * design §4); the view registry moved to the 'conversation.view' slot (slot
- * ledger owns registration, ordering, and disposal) — what remains is the
- * send/stop orchestration face.
+ * Scope-addressed conversation send, cancel, and empty-state session startup.
  *
  * Scope addressing rides the cordis Service tracker: property access through
  * `ctx.conversation` rebinds `this.ctx` to the caller's context, so methods
- * read the session tag with scopeOf (same mechanism as the host tool
- * registry). Mutable state lives in plain objects reached by one property
- * read — field assignment through the tracker's shadow proxy is off-limits,
- * as are `#` hard-private fields.
+ * read the session tag with `scopeOf`. Mutable state must remain reachable
+ * through one property read; assignment through the tracker proxy and `#`
+ * private fields bypass that rebinding.
  */
 import { Service } from 'cordis'
 import type { Context } from 'cordis'

+ 2 - 11
packages/client/ui-conversation/src/client/skeleton/InputBar.tsx

@@ -1,12 +1,5 @@
-// InputBar: the one composer input (figma Input_Bottom). The same component
-// serves the empty state (variant='hero': centered launch card) and the
-// resident composer (variant='composer') — the empty→content transition is a
-// position move of this component, never a swap (layout ruling). Running
-// LOCKS the input: textarea disabled with the draft visible, stop is the only
-// action; the turn ending re-enables and refocuses.
-//
-// Bottom chrome (attach / Plan / Read-only / model) is visual-only for now —
-// local native <select> state, no host wiring.
+// Shared empty-state and resident composer. Running retains the draft, locks
+// the textarea, and exposes only Stop. Bottom controls are local visual state.
 
 import { useEffect, useRef, useState } from 'react'
 import type { ChangeEvent, KeyboardEvent, MouseEvent, ReactNode } from 'react'
@@ -25,10 +18,8 @@ export interface InputBarProps {
   running: boolean
   disabled: boolean
   error: InputBarError | null
-  /** Hero = empty-state centered card; composer = resident bottom bar. */
   variant: 'hero' | 'composer'
   placeholder?: string
-  /** Optional leading accessory row above the textarea (kept for callers; empty state no longer uses it). */
   accessory?: ReactNode
   onDraftChange: (text: string) => void
   onSend: (mode: 'queue' | 'steer') => void

+ 5 - 22
packages/client/ui-conversation/src/client/stores.ts

@@ -1,21 +1,11 @@
 /**
- * Chat store factory (slot terminal design §4): selection + draft + active
- * view for one session, shared by the conversation and details registrations
- * (apply constructs one handle and passes it to both). Session-scope
- * derivation: both mount slots are scope=session, so the framework creates
- * one instance per session; the persist key is scope-suffixed by the
- * framework, aligning with the previous per-session draft persistence.
- *
- * Module exports the factory only — a module-level handle would pin identity
- * in the module cache (a de-facto singleton surviving plugin reloads).
+ * Per-session chat store shared by conversation and details registrations.
+ * The plugin creates its handle at apply time so identity follows the fiber.
  */
 import { defineStore, type EngineStoreHandle } from '@deepseek-ai/dsh-client-runtime/client'
 import type { ChatStoreState, SelectionTarget } from './contract/views.ts'
 
-/**
- * Annotation twin of the actions literal below (the export needs a declared
- * return type); drift fails assignability at the defineStore call.
- */
+/** Declared action shape used to give the exported factory a stable return type. */
 type ChatActions = {
   select: (draft: ChatStoreState, target: SelectionTarget | null) => void
   setDraft: (draft: ChatStoreState, text: string) => void
@@ -25,18 +15,11 @@ type ChatActions = {
 }
 
 /**
- * Declare the per-session chat store. `selection` is the details-linkage
- * channel (conversation writes, details reads); `draft` is the composer text
- * (persisted so it survives session switches and reloads); `view` is the
- * active conversation view id (a 'conversation.view' entry id — store seat is
- * the cross-remount survival channel, null falls back to the first view).
- * @returns the store handle (spec + identity + factory in one value).
+ * Declares the per-session chat state and write surface.
+ * @returns the store handle.
  */
 export function createChatStore(): EngineStoreHandle<ChatStoreState, ChatActions> {
   return defineStore({
-    // Anchored to the contract shape: consumers read the store through
-    // PropsStore<ChatStore>'s SnapshotSelectorHook<ChatStoreState>, so init
-    // and the contract cannot drift.
     init: (): ChatStoreState => ({ selection: null, draft: '', view: null }),
     persist: 'dsh.conversation.chat',
     actions: {

+ 2 - 8
packages/client/ui-conversation/src/index.ts

@@ -1,10 +1,4 @@
-/**
- * Conversation plugin, node half. Pure UI plugin: the empty apply exists so
- * the plugin appears in the host cordis.yml / Loader (load and lifecycle
- * follow the host; the browser half ships via exports["./client"], discovered
- * through the package.json dshClient declaration). Contract: api-contracts
- * v3 sections 0.3 and 7.
- */
+/** Host loader entry for the browser-only conversation plugin. */
 
-/** Host plugin body — no host-side behavior for the conversation plugin. */
+/** Provides no host-side behavior. */
 export function apply(): void {}

+ 0 - 1
packages/client/ui-conversation/tests/apply-inject.spec.tsx

@@ -137,7 +137,6 @@ describe('conversation slot inject surface', () => {
     expect(injected.views.list().map(v => v.id)).toEqual(['chat'])
     injected.open(ROOT)
     expect(b.sessionsFake.open).toHaveBeenCalledWith(ROOT)
-    // loadOlder moved to the chat view entry's face (the ring rider).
     const chatView = b.chatViewSurface(ROOT)
     chatView.injected.loadOlder()
     expect(b.sessionFake.loadOlder).toHaveBeenCalledTimes(1)

+ 1 - 6
packages/client/ui-conversation/tests/chat-store.spec.ts

@@ -1,10 +1,5 @@
 // @vitest-environment jsdom
-/**
- * createChatStore unit account (slot terminal design §4): the declared
- * actions write set, persist round-trip through the scope-suffixed key, and
- * factory purity (every create() is an independent instance; the factory
- * itself holds no singleton state).
- */
+/** Chat-store actions, scoped persistence, and instance isolation. */
 import { beforeEach, describe, expect, it } from 'vitest'
 import { createChatStore } from '../src/client/stores.ts'
 

+ 0 - 2
packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx

@@ -146,8 +146,6 @@ describe('keyed toolview hole through the real machinery', () => {
 
   it('a duplicate key registration fails loud at load', async () => {
     const b = await bench([])
-    // The bash sample already holds the 'bash' key (later-wins retired with
-    // the ring — the keyed ledger throws instead).
     expect(() => b.slots.register(
       { name: 'conversation.chat.toolview', key: 'bash' },
       () => null,

+ 1 - 1
packages/client/ui-conversation/tests/chat-view.spec.tsx

@@ -69,7 +69,7 @@ const runningCall = (callId: string, name = 'bash'): RunningToolCall => ({
   callId, name, argsRaw: `{"command":"cmd-${callId}"}`, turn: 2, step: 1, time: 1_000, callView: null,
 })
 
-/** Empty sessions-list hook stub (the global standard-kit seat; engines carry no hook since the store migration — bind here). */
+/** Empty sessions-list hook for the global standard-kit seat. */
 function emptySessions() {
   const store = createSnapshotStore<SessionListState>(
     { ids: [], byId: {}, current: undefined } as SessionListState)

+ 0 - 5
packages/client/ui-conversation/tests/gate-branch-tails.spec.tsx

@@ -1,9 +1,4 @@
 // @vitest-environment jsdom
-// Final branch tails for the coverage gate, terminal slot form:
-// AssistantMarkdown non-final reasoning, StatsLine usage-less node,
-// DetailsPanel titleless selection. (The old cwd WeakMap-cache account
-// retired with the mechanism — derivation lives in EmptyState now, covered
-// by the skeleton specs.)
 
 import { afterEach, describe, expect, it, vi } from 'vitest'
 import { cleanup, render } from '@testing-library/react'

+ 2 - 8
packages/client/ui-conversation/tests/hook.ts

@@ -1,12 +1,6 @@
 /**
- * Test-local selector-hook binder: the engine carries no hook since the store
- * migration (runtime is React-free); the renderer binds in production, specs
- * bind here. Delegates to web-react's bindSnapshotSelector SOURCE (same
- * with-selector uSES shim as production, so selector-level render economics —
- * a top-level snapshot swap with an unchanged slice does NOT re-render — hold
- * in Profiler-count specs). Source-relative import: the package dependency
- * edge to web-react is gone (store migration §7); tests reach the sibling
- * package the same way they reach their own src internals.
+ * Test-local selector binding through the production uSES implementation.
+ * Runtime remains React-free, so specs bind observable sources here.
  */
 import { bindSnapshotSelector } from '../../web-react/src/bind.ts'
 

+ 3 - 10
packages/client/ui-conversation/tests/selection-survival.spec.ts

@@ -1,13 +1,7 @@
 // @vitest-environment jsdom
 /**
- * Selection survival across the store seat (terminal design §4): the chat
- * store now carries what the per-scope selection account used to — this pins
- * the same behavior contract in the new mechanism. Drives the REAL
- * SlotsService store axis with the shared createChatStore handle (the exact
- * apply.ts shape: one handle, two session-slot registrations): same session's
- * two slots resolve one instance (conversation writes, details reads);
- * sessions are isolated; a session's death buries its instance AND its
- * persisted draft; a list refresh does not touch instance identity.
+ * Exercises selection persistence through the real SlotsService store axis;
+ * component stubs cannot prove per-session identity or disposal.
  */
 import { Context } from 'cordis'
 import { beforeEach, describe, expect, it } from 'vitest'
@@ -15,8 +9,7 @@ import { SessionsService, SlotsService } from '@deepseek-ai/dsh-client-runtime/c
 import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
 import { createChatStore } from '../src/client/stores.ts'
 
-// The runtime package's programmable fake lives in its tests; import through
-// the src path (same pattern the runtime specs use — test-support material).
+// Use the runtime's programmable fake to drive the real session service.
 import { FakeApiClient, ok } from '../../runtime/tests/fake-api.ts'
 
 const sid = (s: string): SessionId => s as SessionId

+ 2 - 8
packages/client/ui-layout/src/index.ts

@@ -1,10 +1,4 @@
-/**
- * Layout plugin, node half. Pure UI plugin: the empty apply exists so the
- * plugin appears in the host cordis.yml / Loader (load and lifecycle follow
- * the host; the browser half ships via exports["./client"], discovered
- * through the package.json dshClient declaration). Contract: api-contracts
- * v3 sections 0.3 and 5.
- */
+/** Host loader entry for the browser-only layout plugin. */
 
-/** Host plugin body — no host-side behavior for the layout plugin. */
+/** Provides no host-side behavior. */
 export function apply(): void {}

+ 1 - 1
packages/client/ui-layout/tests/app-frame.spec.tsx

@@ -42,7 +42,7 @@ class ResizeObserverStub {
 
 let frameWidth = 1920
 
-/** Minimal selector hook over an engine instance (the engine carries no hook since the store migration; the renderer binds in production, the spec binds here). */
+/** Test-local selector hook over a framework-neutral store instance. */
 function hookOf<T>(inst: { subscribe: (fn: () => void) => () => void; getSnapshot: () => T }) {
   return <S,>(sel: (s: T) => S): S => sel(useSyncExternalStore(inst.subscribe, inst.getSnapshot))
 }

+ 1 - 3
packages/client/ui-primitives/src/index.ts

@@ -1,7 +1,5 @@
 /**
- * Pure React atoms (zero cordis): StateDot, icons, Button/Pill/Menu/Modal/Input,
- * markdown family, ConnectionBanner. Everything consumes props plus --dsw-*
- * token vars only. Contract: api-contracts v3 section 8.
+ * Cordis-free React primitives styled only through `--dsw-*` tokens.
  */
 
 export { StateDot } from './StateDot.tsx'

+ 1 - 3
packages/client/ui-question/tests/browser-plugin.spec.ts

@@ -16,9 +16,7 @@ async function bench() {
   const ctx = new Context()
   await ctx.plugin(SlotsService).await()
   const slots = ctx.get('slots') as SlotsService
-  // Stand-in for ui-conversation's conversation entry: the composer slot only
-  // exists while a live entry declares it in children (declaration account:
-  // design §2.2).
+  // The composer slot exists only while its declaring entry is live.
   slots.register(
     { name: 'root', children: { 'conversation.composer': { kind: 'chain', scope: 'session' } } } as never,
     () => null,

+ 3 - 11
packages/client/ui-sidebar/src/client/SidebarRoot.tsx

@@ -1,12 +1,5 @@
 /**
- * SidebarRoot (figma 133:7629): logo row + collapse, New Session, WorkSpace
- * section header with the group-by menu, search, session tree list, Settings
- * foot. Pure presentational — the session list arrives through the standard
- * useSessions hook, viewing state (expansion, search) is local component
- * state, and rows are derived in render via useMemo (slot design section 6:
- * derived data is a pure function, no materializing store).
- *
- * Collapse is a slide + crossfade: the content freezes at its expanded
+ * Collapse is a slide plus crossfade: content freezes at its expanded
  * width (inline style) and fades out in place while the sliding column
  * (AppFrame grid tracks) clips it — nothing reflows mid-slide. At settle
  * the wide-only content (brand, labels, input, tree) unmounts, dropping
@@ -35,7 +28,7 @@ const EXPAND_SLIDE_MS = 300
 
 const GROUP_BY_ITEMS = [
   { id: 'workspace', label: 'WorkSpace' },
-  // Update/Status grouping has no design yet (figma §3) — visible, disabled.
+  // Only workspace grouping is implemented.
   { id: 'update', label: 'Update', disabled: true },
   { id: 'status', label: 'Status', disabled: true },
 ]
@@ -78,8 +71,7 @@ type SessionTreeProps = Pick<SidebarRootComponentProps, 'useSessions' | 'onOpen'
 /** The scrolling session tree; unmounting at collapse settle drops the sessions subscription and expansion state. */
 function SessionTree({ useSessions, onOpen, onCreate, query }: SessionTreeProps) {
   const list = useSessions((s) => s)
-  // Wave-2 seam: row highlight expects `current` on the sessions list
-  // snapshot (sessions.current lives with the runtime sessions service).
+  // Selection belongs to the sessions snapshot, not layout state.
   const current = useSessions((s) => s.current)
   const [expandedProjects, setExpandedProjects] = useState<string[]>([])
   const [expandedSessions, setExpandedSessions] = useState<string[]>([])

+ 5 - 16
packages/client/ui-sidebar/src/client/index.ts

@@ -1,30 +1,19 @@
-/**
- * Sidebar plugin, browser half: SidebarRoot registered into the layout-owned
- * sidebar slot. Pure consumer — the session list arrives through the
- * standard useSessions prop, tree rows derive in the component, and the
- * inject surface is plain cross-service callbacks closed over the plugin's
- * own ctx (slot design sections 5 and 6); props composition in
- * contract/slots.ts. Export discipline: packages/client/AGENTS.md.
- */
+/** Registers the sidebar UI into the layout-owned slot. */
 import type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'
 import type { SidebarRootInjected } from './contract/slots.ts'
 import { SidebarRoot } from './SidebarRoot.tsx'
 
 export type { SidebarRootComponentProps, SidebarRootInjected } from './contract/slots.ts'
 
-/** Required services (cordis fiber inject — the loader passes the whole export surface as an object plugin). */
+/** Services required by the sidebar plugin. */
 export const inject = ['slots', 'layout', 'sessions']
 
-/**
- * Client plugin body: register SidebarRoot into the sidebar slot. The inject
- * factory returns service callbacks only (no hooks, no store lines) — all
- * data reads ride the framework's standard useSessions delivery.
- * @param ctx - client root context.
+/** Registers the sidebar component and its service callbacks.
+ * @param ctx - Client root context.
  */
 export function apply(ctx: ClientContext): void {
   const injectProps = (): SidebarRootInjected => ({
-    // Selection lives with the runtime sessions service (current rides the
-    // list snapshot); layout keeps only panel geometry.
+    // Selection belongs to the sessions service; layout owns only panel geometry.
     onOpen: (id) => { ctx.sessions.open(id) },
     onCreate: (cwd) => {
       // Top-level New Session / New Workspace: clear selection so AppFrame

+ 1 - 8
packages/client/ui-sidebar/src/client/tree.ts

@@ -1,11 +1,4 @@
-/**
- * Pure sidebar tree derivation: session list snapshot -> flat render rows.
- * Groups sessions by project directory (cwd), builds the per-group session
- * tree from parentId links, sorts by recency, and applies search filtering
- * with forced ancestor visibility. Derived data is a pure function (slot
- * design section 6): the component feeds the useSessions snapshot plus its
- * local viewing state through useMemo — no materializing store.
- */
+/** Pure derivation of flat sidebar rows from sessions and local view state. */
 import type { SessionId, SessionListState, SessionSummary } from '@deepseek-ai/dsh-client-runtime/client'
 
 /** Group key for sessions without a project directory. */

+ 2 - 8
packages/client/ui-sidebar/src/index.ts

@@ -1,10 +1,4 @@
-/**
- * Sidebar plugin, node half. Pure UI plugin: the empty apply exists so the
- * plugin appears in the host cordis.yml / Loader (load and lifecycle follow
- * the host; the browser half ships via exports["./client"], discovered
- * through the package.json dshClient declaration). Contract: api-contracts
- * v3 sections 0.3 and 6.
- */
+/** Host loader entry for the browser-only sidebar plugin. */
 
-/** Host plugin body — no host-side behavior for the sidebar plugin. */
+/** Provides no host-side behavior. */
 export function apply(): void {}

+ 1 - 2
packages/client/ui-sidebar/tests/apply.spec.tsx

@@ -36,8 +36,7 @@ async function bench() {
   ctx.provide('sessions', sessions)
   ctx.provide('layout', layout)
   const slots = ctx.get('slots') as SlotsService
-  // Stand-in for ui-layout's root entry: the sidebar slot only exists while
-  // a live entry declares it in children (declaration account: design §2.2).
+  // The sidebar slot exists only while its declaring entry is live.
   slots.register(
     { name: 'root', children: { 'sidebar': { kind: 'single', scope: 'root' } } } as never,
     () => null,

+ 1 - 2
packages/client/ui-sidebar/tests/sidebar-root.spec.tsx

@@ -10,8 +10,7 @@
 import { afterEach, describe, expect, it, vi } from 'vitest'
 import { cleanup, fireEvent, render, screen } from '@testing-library/react'
 import { act, useSyncExternalStore } from 'react'
-// Engine home: runtime/client since the store migration; the engine carries
-// no hook (runtime is React-free), so the spec binds the selector locally.
+// Runtime is React-free, so the spec binds its selector locally.
 import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
 import type { SessionId, SessionListState, SessionSummary } from '@deepseek-ai/dsh-client-runtime/client'
 import { SidebarRoot } from '../src/client/SidebarRoot.tsx'

+ 4 - 9
packages/client/ui-slots/src/index.ts

@@ -142,13 +142,9 @@ export interface SessionAreaProps {
 }
 
 /**
- * The framework-wired session area component (slot terminal design §7):
- * subscribes to the current-session selection internally (design fiat ① —
- * selection authority lives with runtime sessions) and switches between the
- * session body and the empty branch. Delivered as a standard seat to every
- * entry whose children declaration contains a session-scope slot (the
- * derivation rides {@link PropsRenderSlots}); the value is injected by the
- * installed renderer — business code never imports it.
+ * Framework-wired session area component. It subscribes to runtime-owned
+ * session selection and is injected into entries that declare session-scoped
+ * children; business code does not import it directly.
  */
 export type SessionProviderComponent = (props: SessionAreaProps) => ReactNode
 
@@ -352,8 +348,7 @@ export class SlotCore {
 
   /**
    * Contribute a component to a declared slot and (optionally) declare child
-   * slots, a store seat, and the registrant's business face — the single
-   * composition API (the separate define API is retired).
+   * slots, a store seat, and the registrant's business face.
    *
    * Load-time validation (misconfiguration fails loud; the render hot path
    * re-checks nothing): registering into an undeclared slot throws; declaring

+ 2 - 10
packages/client/ui-slots/src/renderer.ts

@@ -1,10 +1,4 @@
-/**
- * Renderer install seam (slot terminal design §8): the SlotRenderer interface
- * web-react's machinery implements, the host surface the runtime SlotsService
- * presents to the installed renderer, and the render-path authorization
- * errors. Pure types plus two error classes — this package stays React-free
- * at runtime (React types only).
- */
+/** React-free contracts between the slot host and an installed renderer. */
 import type { ReactNode } from 'react'
 import type { SlotEntryDef, SlotSpec, StoredEntry } from './index.ts'
 
@@ -22,7 +16,6 @@ export interface HostObservable<T> {
  * typing lands at the component seam via {@link PropsStore}.
  */
 export interface StoreInstanceLike {
-  /** Current state snapshot (uSES getSnapshot side). */
   getSnapshot(): unknown
   /**
    * Subscribe to state changes (uSES subscribe side).
@@ -30,7 +23,6 @@ export interface StoreInstanceLike {
    * @returns unsubscribe.
    */
   subscribe(fn: () => void): () => void
-  /** Baked write callbacks (delivered to components as `actions`). */
   readonly actions: Record<string, (...params: never[]) => void>
 }
 
@@ -97,7 +89,7 @@ export interface SlotRendererHost {
   sessions: {
     /** Session list source backing the useSessions standard hook. */
     list: HostObservable<unknown>
-    /** Current-session source backing SessionProvider's self-wiring (design fiat ①). */
+    /** Current-session source used by SessionProvider. */
     current: HostObservable<string | undefined>
     /**
      * Resolve the session standard kit.

+ 1 - 15
packages/client/ui-slots/src/store.ts

@@ -1,12 +1,4 @@
-/**
- * Store-seat type family (slot terminal design §4): a registrant declares its
- * shared/exclusive business store as data — schema (`init`), optional
- * persistence key, and the complete write set (`actions`) — and the framework
- * owns instance lifecycle (scope derives from the mounting entry's slot).
- * ui-slots ships the contract types only; the engine-backed `defineStore`
- * value lives in web-react (the snapshot-store engine's home) and must
- * satisfy {@link DefineStore}.
- */
+/** Framework-neutral store contracts for slot registrations and the runtime engine. */
 
 /**
  * Typed selector hook over a snapshot source. Canonical shape for the whole
@@ -41,11 +33,8 @@ export type BakedActions<T, A extends ActionsDecl<T>> = {
  * and the actions write set.
  */
 export interface StoreSpec<T, A extends ActionsDecl<T>> {
-  /** Initial-state factory; called once per framework-created instance. */
   init: () => T
-  /** Opt-in persistence key (storage mechanics belong to the engine). */
   persist?: string
-  /** Complete write set: pure draft transforms. */
   actions: A
 }
 
@@ -58,9 +47,7 @@ export interface StoreSpec<T, A extends ActionsDecl<T>> {
  * call create() themselves — instance lifecycle is the framework's.
  */
 export interface StoreInstance<T, A extends ActionsDecl<T>> {
-  /** Baked write callbacks (delivered to components as `actions`). */
   readonly actions: BakedActions<T, A>
-  /** Current state snapshot (uSES getSnapshot side; test assertions). */
   getSnapshot(): T
   /**
    * Subscribe to state changes (uSES subscribe side).
@@ -84,7 +71,6 @@ export interface StoreInstance<T, A extends ActionsDecl<T>> {
  * identity is a disguised singleton across plugin reloads.
  */
 export interface StoreHandle<T, A extends ActionsDecl<T>> {
-  /** The inert declaration this handle was defined from. */
   readonly spec: StoreSpec<T, A>
   /**
    * Create a live engine instance (framework machinery and tests only).

+ 2 - 6
packages/client/ui-theme/src/client/index.ts

@@ -1,10 +1,6 @@
 /**
- * Theme plugin, browser half: ThemeService over the --dsw-* token base
- * stylesheets in src/styles/ (the sole token source; components must not
- * hardcode colors). apply(id) toggles body[data-ds-dark-theme] — theming is
- * CSS cascade, zero React renders. Contract: api-contracts v3 section 8.
- * The base stylesheets ship separately (the web shell imports them as base
- * CSS); this plugin only owns the registry and the body-attribute switch.
+ * Browser theme registry over the `--dsw-*` token stylesheets. Theme changes
+ * update CSS variables and `body[data-ds-dark-theme]` without React renders.
  */
 import type { Context } from 'cordis'
 

+ 1 - 8
packages/client/ui-theme/src/index.ts

@@ -1,11 +1,4 @@
-/**
- * Theme plugin, node half. Pure UI plugin: the empty apply exists so the
- * plugin appears in the host cordis.yml / Loader (load and lifecycle follow
- * the host; the browser half ships via exports["./client"], discovered
- * through the package.json dshClient declaration). ThemeService and its
- * types live in the client half; consumers import the /client subpath.
- * Contract: api-contracts v3 section 8.
- */
+/** Host loader entry for the browser implementation exported from `./client`. */
 
 /** Host plugin body — no host-side behavior for the theme plugin. */
 export function apply(): void {}

+ 3 - 7
packages/client/ui-trajectory/src/client/index.ts

@@ -1,9 +1,6 @@
 /**
- * Trajectory/Waterfall plugin, browser half: contributes the two placeholder
- * views into the conversation view ring (the 'conversation.view' list slot
- * declared by ui-conversation). Pure consumer — no ctx service, no Context
- * declaration merge; the minimal-plugin exemplar. Contract: api-contracts v3
- * section 8.
+ * Browser trajectory plugin contributing two entries to the conversation
+ * view slot without defining a service.
  */
 import type { Context } from 'cordis'
 // Type-only: the 'conversation.view' SlotMap row (declared by the slot's
@@ -24,8 +21,7 @@ export const inject = ['slots', 'conversation']
 /**
  * Client plugin body: register the trajectory and waterfall view tabs. The
  * registrations ride the slot service's effect wrapper (plugin unload
- * removes both tabs). Trajectory owns its turn list in-body; Waterfall keeps
- * the span stats header inside its body (chrome attachment retired).
+ * removes both tabs).
  * @param ctx - client root context.
  */
 export function apply(ctx: Context): void {

+ 2 - 8
packages/client/ui-trajectory/src/index.ts

@@ -1,10 +1,4 @@
-/**
- * Trajectory plugin, node half. Pure UI plugin: the empty apply exists so
- * the plugin appears in the host cordis.yml / Loader (load and lifecycle
- * follow the host; the browser half ships via exports["./client"], discovered
- * through the package.json dshClient declaration). Contract: api-contracts
- * v3 sections 0.3 and 8.
- */
+/** Host loader entry for the browser-only trajectory plugin. */
 
-/** Host plugin body — no host-side behavior for the trajectory plugin. */
+/** Provides no host-side behavior. */
 export function apply(): void {}

+ 1 - 2
packages/client/ui-trajectory/tests/views.spec.tsx

@@ -59,7 +59,7 @@ function fakeSession(nodes: ConversationSnapshot['nodes']) {
   return { store, useSession: bindSnapshotSelector(store) as unknown as UseSession<ConversationSnapshot> }
 }
 
-/** Empty sessions-list hook stub (breadcrumbs fall back to the raw id; engines carry no hook since the store migration — bind here). */
+/** Empty sessions-list hook; breadcrumbs therefore fall back to the raw id. */
 function emptySessions() {
   const store = createSnapshotStore<SessionListState>(
     { ids: [], byId: {}, current: undefined } as SessionListState)
@@ -173,7 +173,6 @@ describe('tab switching in ConversationRoot', () => {
     expect(screen.getAllByRole('tab').map((t) => t.textContent)).toEqual(['Chat', 'Trajectory', 'Waterfall'])
 
     fireEvent.click(screen.getByRole('tab', { name: 'Trajectory' }))
-    // Trajectory no longer mounts the span stats bar; the turn-list chrome owns the body.
     expect(screen.queryByText(/turns ·/)).toBeNull()
     expect(screen.getByText('Turn 1')).toBeTruthy()
     expect(screen.getByText('Turn 2')).toBeTruthy()

+ 1 - 12
packages/client/web-react/src/index.ts

@@ -1,13 +1,4 @@
-/**
- * Shell-side React glue (slot terminal design §8): createSlotRenderer (the
- * install-seam implementation), SessionProvider (framework-wired render
- * prop, also delivered as a standard seat to session-area entries),
- * bindSnapshotSelector (the one hook constructor), and useInvoke. The
- * snapshot-store engine and defineStore live in runtime (store relocation);
- * contract types are ui-slots authority — this face re-exports only what its
- * own values traffic in. React contexts stay in-package: business components
- * see none.
- */
+/** React bindings for the framework-neutral slot and snapshot contracts. */
 import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'
 
 export { bindSnapshotSelector } from './bind.ts'
@@ -20,7 +11,6 @@ export { bindSnapshotSelector } from './bind.ts'
  */
 export type UseSession<Snap extends object = object> = SnapshotSelectorHook<Snap>
 
-// -- renderer: the install-seam implementation; contract lives in ui-slots --
 export type {
   ChainRenderOpts, HostObservable, RenderOpts, SessionCell, SnapshotSelectorHook,
   SlotRenderer, SlotRendererHost, StoreInstanceLike,
@@ -28,7 +18,6 @@ export type {
 export { SlotOwnershipError, StaleAuthorizationError } from '@deepseek-ai/dsh-client-ui-slots'
 export { createSlotRenderer } from './scoped-slots.tsx'
 
-// -- session area: the framework-wired provider; binding contexts stay internal --
 export { SessionProvider, SlotAssemblyError, type SessionProviderProps } from './session-provider.tsx'
 
 export { useInvoke } from './use-invoke.ts'

+ 2 - 17
packages/client/web-react/src/scoped-slots.tsx

@@ -1,19 +1,6 @@
 /**
- * createSlotRenderer(): the outlet machinery behind the runtime install seam
- * (slot terminal design §8). renderRoot mounts the host channel and renders
- * the built-in 'root' key; every deeper slot renders through a per-entry
- * renderSlot binding synthesized from the entry's children declaration.
- * Standard-kit synthesis per entry: the global useSessions hook, the session
- * pair (useSession + sessionId) under SessionProvider, the store pair
- * (useStore + actions) for store-declaring entries, the renderSlot binding
- * (entry-identity bound, stale-checked) for children-declaring entries, and
- * the renderSlotChain binding for entries declaring a chain-kind child
- * (selector-routed: first non-null select elects and its value joins the
- * props as `matched`; all-null falls to the owner fallback).
- * Inject factories run inside the entry component bodies ON PURPOSE
- * — the per-entry error boundary contains a throwing factory to its own
- * entry; parameters follow the declaration (sessionId for session slots,
- * baked actions when a store is declared).
+ * React renderer for declarative slots. Per-entry bindings enforce child
+ * authorization, and entry boundaries contain registrant failures.
  */
 import { Component, useSyncExternalStore, type FC, type ReactNode } from 'react'
 import {
@@ -27,10 +14,8 @@ import {
 
 type InjectedProps = Record<string, unknown>
 
-/** Owner-facing renderSlot binding shape (typed narrowing lands on the wave-1 props seam). */
 type RenderSlotBinding = (key: string, owner: object, opts?: RenderOpts) => ReactNode
 
-/** Owner-facing renderSlotChain binding shape (typed narrowing lands on the props seam). */
 type RenderSlotChainBinding = (key: string, owner: object, opts?: ChainRenderOpts) => ReactNode
 
 /**

+ 6 - 15
packages/client/web-react/src/session-provider.tsx

@@ -1,11 +1,4 @@
-/**
- * SessionProvider (framework-wired render prop, slot terminal design §7) plus
- * the two internal channels the render machinery shares: the renderer host
- * context (written once by createSlotRenderer's root) and the per-session
- * binding context (written here, read by session-scope outlets). Both
- * contexts are in-package machinery — they are NOT exported from the package
- * index; business components see zero React contexts.
- */
+/** Internal React bindings for the renderer host and active session cell. */
 import { createContext, useContext, type ReactNode } from 'react'
 import type {
   HostObservable, SessionCell, SlotRendererHost, SnapshotSelectorHook,
@@ -20,7 +13,7 @@ import { bindSnapshotSelector } from './bind.ts'
  */
 export class SlotAssemblyError extends Error {}
 
-/** Renderer host channel: written by createSlotRenderer's root element (in-package machinery only). */
+/** In-package renderer host context. */
 export const HostContext = createContext<SlotRendererHost | null>(null)
 
 /**
@@ -34,7 +27,6 @@ export function useHost(): SlotRendererHost {
   return host
 }
 
-/** Per-session binding channel for the subtree under SessionProvider (in-package machinery only). */
 const BindingContext = createContext<SessionCell | null>(null)
 
 /**
@@ -75,11 +67,10 @@ export interface SessionProviderProps {
 
 /**
  * Framework-wired session area: subscribes to the host's current-session
- * source (design fiat ① — selection authority lives with runtime sessions),
- * resolves the session cell, and remounts the body under key={sessionId} so
- * a session switch rebuilds the whole session subtree. Ids speak plain
- * string at this dependency-inverted layer; branding lands on the component
- * props seam (PropsRuntime).
+ * source, resolves the session cell, and remounts the body under
+ * `key={sessionId}` so a session switch rebuilds the session subtree. This
+ * dependency-inverted layer uses plain string ids; `PropsRuntime` applies the
+ * branded type at the component boundary.
  */
 export function SessionProvider({ empty, children }: SessionProviderProps) {
   const host = useHost()

+ 2 - 4
packages/client/web-react/tests/bind.spec.tsx

@@ -5,10 +5,8 @@ import { act, render } from '@testing-library/react'
 import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react'
 import type { HostObservable as ObservableSnapshot, SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'
 
-// Local one-level equality: the engine's shallowEqual moved to runtime with
-// the store relocation, and web-react tests must not import runtime (the
-// dependency direction is runtime → web-react). The eq PARAMETER contract is
-// what this suite asserts, not any specific equality implementation.
+// Keep equality local: this suite asserts the eq parameter contract without
+// adding a reverse dependency from web-react to runtime.
 const shallowEqual = (a: Record<string, unknown>, b: Record<string, unknown>): boolean =>
   Object.keys(a).length === Object.keys(b).length && Object.keys(a).every((k) => Object.is(a[k], b[k]))
 

+ 2 - 4
packages/client/web-react/tests/stale-authorization.spec.tsx

@@ -1,9 +1,7 @@
 // @vitest-environment jsdom
 /**
- * Stale renderSlot bindings (slot terminal design §9): a binding dies with
- * its entry — a retained closure invoked after the entry's disposal throws
- * StaleAuthorizationError off the ledger check, and an HMR-style reload (new
- * entry, same key) mints a NEW binding rather than reviving the old one.
+ * A retained render binding dies with its entry. Re-registering the same key
+ * creates a new binding rather than reviving the stale closure.
  */
 import { describe, expect, it } from 'vitest'
 import { act, render } from '@testing-library/react'

+ 5 - 14
packages/client/web/src/app-shell.ts

@@ -1,13 +1,6 @@
 /**
- * App-shell assembly plugin (design §3.4): the shell's ONLY composition
- * responsibility, packaged as a normal static-arrival entry so the host graph
- * stays the single composition authority. It rides the same entry lifecycle
- * as every other plugin — the fiber waits on slots/sessions/layout, so by the
- * time apply runs the layout entry is mounted and its export surface is
- * readable from the governance side (module loadCache, design §2.6).
- *
- * The pseudo package id exists only in the host graph and the shell's static
- * registry; there is no npm package behind it.
+ * App-shell assembly plugin. Its pseudo package id exists only in the host
+ * graph and shell registry; there is no npm package behind it.
  */
 import type { ReactNode } from 'react'
 import type { Context } from 'cordis'
@@ -33,13 +26,11 @@ declare module 'cordis' {
 /** Cordis plugin name. */
 export const name = 'app-shell'
 
-/** Required services: the product services the assembly closes over (layout registers the 'root' slot entry). */
+/** Services required before shell assembly. */
 export const inject = ['slots', 'sessions', 'layout']
 
-/**
- * Plugin body: install the React renderer into the slot system and provide
- * the renderApp face (one ctx-level renderSlot('root') call).
- * @param ctx - plugin context (inject set active).
+/** Installs the React renderer and exposes the assembled application.
+ * @param ctx - Plugin context.
  */
 export function apply(ctx: Context): void {
   // The renderer install is shell territory (web-react is shell-bundled),

+ 2 - 6
packages/client/web/src/platform.ts

@@ -1,10 +1,6 @@
 /**
- * Platform singletons the shell shares into the module table.
- * Single source of truth (design §3.3, contract C1): seed keys = tsdown
- * client externals = the shared surface. The three projections import this
- * module — the seed table ({@link ../seed.ts}), the tsdown client preset's
- * external judgement (packages/client/tsdown.client.ts), and the vite alias
- * check — so the list cannot drift between them.
+ * Shared browser platform modules. Seeding, bundling externals, and Vite
+ * aliases consume this list so their module identities cannot drift.
  * @module @deepseek-ai/dsh-client-web/src/platform
  */
 

+ 1 - 8
packages/core/agent-loop/tests/agent.spec.ts

@@ -188,15 +188,12 @@ describe('Agent', () => {
     const ctx = await harness(adapter)
     const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
 
-    // Simulate an OPEN turn in the log while the agent is idle (status is not a
-    // reliable open-turn signal). inject must append into that open turn, NOT
-    // wrap a new one.
+    // Status is idle while the log has an open turn; enclosure must follow the log.
     agent.session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
     agent.inject([{ type: 'text', text: 'mid' }], { source: { kind: 'plugin', plugin: 'p' } })
     expect(agent.session.events.filter(e => e.type === 'turn/start')).toHaveLength(1)
     expect(agent.session.events.at(-1)!.type).toBe('user/message')
 
-    // Close the turn; now inject must wrap its own one-shot injection turn.
     agent.session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
     agent.inject([{ type: 'text', text: 'after' }], { source: { kind: 'plugin', plugin: 'p' } })
     const starts = agent.session.events.filter(e => e.type === 'turn/start')
@@ -342,11 +339,9 @@ describe('Agent', () => {
     const ctx = await harness(adapter)
     const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
 
-    // steer while idle delegates to send
     agent.steer([{ type: 'text', text: 'steer idle' }], { source: { kind: 'plugin', plugin: 'test' } })
     await waitForIdle(ctx, agent)
 
-    // The message was recorded as a user-level message (send path)
     expect(agent.session.events.some(e => e.type === 'user/message')).toBe(true)
     expect(adapter.requests).toHaveLength(1)
   })
@@ -369,12 +364,10 @@ describe('Agent', () => {
     prepared.markPublished()
     const dispose = prepared.startDriver()
 
-    // First dispose
     const firstDisposal = dispose()
     expect(agent.status).toBe('disposed')
     await firstDisposal
 
-    // Second dispose — idempotent, no throw
     await expect(dispose()).resolves.toBeUndefined()
     expect(agent.status).toBe('disposed')
   })

+ 0 - 2
packages/core/agent-loop/tests/cancel.spec.ts

@@ -142,8 +142,6 @@ describe('Agent.cancel()', () => {
 
     agent.queue([{ type: 'text', text: 'quiet' }])
     const idle = agent.whenIdle()
-    // Cancel reaches quiescence with no status transition and no waking send;
-    // whenIdle must still resolve (previously it hung until the next send).
     agent.cancel({ kind: 'user' })
     await idle
     expect(agent.session.events.some(e => e.type === 'turn/start')).toBe(false)

+ 0 - 2
packages/core/agent-loop/tests/contract-regressions.spec.ts

@@ -1096,7 +1096,6 @@ describe('step boundary publication order', () => {
     const ctx = await harness(adapter)
     const agent = ctx.agentLoop.create(SessionId('a-step-order'), { provider: 'mock', model: 'mock' })
 
-    // Append commits before observers run.
     const observed: { turn: number; step: number; lastEventType: string | undefined; sawStepStart: boolean }[] = []
     ctx.on('session/event', (subject, event) => {
       if (subject !== agent.session || event.type !== 'step/start') return
@@ -1704,7 +1703,6 @@ describe('disposal and cancellation during pre-step assembly', () => {
     send(agent, 'go')
     await new Promise(r => setTimeout(r, 50))
 
-    // Start disposal, then release the block, then await disposal.
     const disposalDone = fiber.dispose()
     releasePreStep()
     await disposalDone

+ 0 - 4
packages/core/session/tests/surface.spec.ts

@@ -283,20 +283,17 @@ describe('SurfaceManager', () => {
 
   it('empty surface yields empty nodes', () => {
     const s = new Session(SessionId('empty'))
-    // Only turn boundaries, no surface nodes.
     s.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
     s.append('step/start', { turn: 1, step: 1 })
     s.append('step/end', { turn: 1, step: 1 })
     s.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
     expect(s.surface.nodes.length).toBe(0)
-    // deriveMessages returns empty array
     expect(s.deriveMessages()).toEqual([])
   })
 
   it('picks up new events incrementally (delta processing)', () => {
     const s = surfaceSession()
     expect(s.surface.nodes.length).toBe(2)
-    // Append another surface node
     s.append('tool/result', { turn: 1, step: 1, callId: CallId('c1'), content: [{ type: 'text', text: 'ok' }], isError: false }, { surfaceOp: 'append' })
     expect(s.surface.nodes.length).toBe(3)
     expect(s.surface.nodes[2]!).toBe(4) // seq 4: after turn/end at seq 3
@@ -306,7 +303,6 @@ describe('SurfaceManager', () => {
     const original = surfaceSession()
     original.append('tool/result', { turn: 1, step: 1, callId: CallId('c1'), content: [{ type: 'text', text: 'ok' }], isError: false }, { surfaceOp: 'append' })
     const replayed = new Session(SessionId('replay'), [...original.events])
-    // Surface rebuilds from the seeded log's markers.
     expect(replayed.surface.nodes).toEqual([1, 2, 4])
     expect(replayed.deriveMessages()).toEqual(original.deriveMessages())
   })

+ 0 - 3
packages/core/tools/tests/tools.spec.ts

@@ -1893,7 +1893,6 @@ describe('ToolRegistry', () => {
     const ctx = await setup()
     ctx.tools.register(echoTool)
 
-    // Register a second tool and call its returned disposer directly
     const dispose = ctx.tools.register({ ...echoTool, name: 'disposable' })
     expect(ctx.tools.schemas().map(t => t.name)).toEqual(['echo', 'disposable'])
 
@@ -2055,9 +2054,7 @@ describe('defineTool / schema DSL', () => {
       parameters: { a: { type: 'string' as const, required: true as const }, b: { type: 'number' as const } },
       output: { schema: { type: 'string' }, render: () => [] },
       async execute(args) {
-        // Verify types at runtime via typeof
         expect(typeof args.a).toBe('string')
-        // args.b should be undefined when not provided
         void args
         return args.a
       },

+ 2 - 2
packages/fs/fs-local/src/fsio.ts

@@ -472,8 +472,8 @@ export async function writeFileAtomic(
       try {
         await replaceFile(absolutePath, tempPath)
       } catch (error: unknown) {
-        // Preserve the old behavior when an external actor removes the observed target during
-        // staging: the temp already carries that target's protected DACL, so rename recreates it.
+        // If the observed target disappears during staging, the protected DACL
+        // already copied to the temp remains authoritative for recreation.
         if (!isENOENT(error)) throw error
         await rename(tempPath, absolutePath)
       }

+ 2 - 9
packages/fs/tool-fs/tests/fs-tools.e2e.ts

@@ -6,14 +6,7 @@ import type { Context } from 'cordis'
 import { SessionId } from '@deepseek-ai/dsh-session'
 import { fsHarness, waitForIdle } from './harness.ts'
 
-/**
- * With-key smoke for the filesystem tools: a REAL model drives the REAL
- * read/write/edit tools (over the real local backend + policy gate), and we
- * verify the WORLD — the file on disk — not the agent's self-report. This is the
- * "green units, broken product" guard: mocks prove the plumbing, only a real
- * model proves the tools actually work end-to-end. Key-gated (self-skips without
- * DEEPSEEK_API_KEY).
- */
+/** Key-gated smoke for a real model driving the local read/write/edit tools. */
 
 let ctx: Context | undefined
 let workdir: string | undefined
@@ -42,7 +35,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('fs tools with-key smoke', () =>
       + 'Tell me when done.' }])
     await waitForIdle(ctx, agent)
 
-    // Verify the WORLD: the edit landed on disk.
+    // Assert the filesystem effect independently of the model response.
     const content = await readFile(join(workdir, 'note.txt'), 'utf8')
     expect(content).toContain('status: final')
     expect(content).not.toContain('draft')

+ 1 - 2
packages/guard/repeat-tool-guard/tests/repeat-tool-guard.spec.ts

@@ -321,10 +321,9 @@ describe('fold onto the downstream decision', () => {
 
     const found = reminders(agent)
     expect(found).toHaveLength(3)
-    // Call 1: below threshold — the downstream context passes through untouched.
+    // Only the repeated call adds guard context; downstream provenance survives.
     expect(found[0]!.text).toBe('downstream-ctx')
     expect(found[0]!.source).toEqual({ kind: 'plugin', plugin: 'test' })
-    // Call 2: reminder and downstream context retain separate provenance.
     expect(found[1]!.text).toContain('repeating the exact same tool call')
     expect(found[1]!.source).toEqual(GUARD_SOURCE)
     expect(found[2]).toEqual({ text: 'downstream-ctx', source: { kind: 'plugin', plugin: 'test' } })

+ 0 - 5
packages/hooks/hooks-claude/tests/coverage-cases.ts

@@ -150,7 +150,6 @@ export function defineCoverageCases(group: CoverageGroup): void {
       const ctx = await harness(path, new MockAdapter([]))
       let ran = false
       ctx.tools.register(defineContentToolFixture({ name: 'echo', description: 'e', parameters: {}, async execute() { ran = true; return [{ type: 'text', text: 'x' }] } }))
-      // Call execute() directly with NO agent — the bridge's no-agent/no-turn path.
       const { CallId } = await import('@deepseek-ai/dsh-llm')
       const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'echo', arguments: {} })
       expect(ran).toBe(false)
@@ -159,7 +158,6 @@ export function defineCoverageCases(group: CoverageGroup): void {
 
     it('a long stderr is truncated in the hook/result summary', async () => {
       const d = dir()
-      // Emit >500 chars of stderr then exit 2.
       const s = sh(d, 'long.sh', '#!/usr/bin/env bash\nprintf "x%.0s" {1..600} >&2\nexit 2\n')
       const path = hooks(d, { PreToolUse: [{ hooks: [{ type: 'command', command: s }] }] })
       const adapter = new MockAdapter([toolCallResponse('c1', 'echo', {}), textResponse('done')])
@@ -236,7 +234,6 @@ export function defineCoverageCases(group: CoverageGroup): void {
       const s = sh(d, 'sa.sh', '#!/usr/bin/env bash\necho \'{"hookSpecificOutput":{"hookEventName":"SubagentStart","additionalContext":"child guidance"}}\'\n')
       const path = hooks(d, { SubagentStart: [{ hooks: [{ type: 'command', command: s }] }] })
       const ctx = await harness(path, new MockAdapter([]))
-      // Register a fake child agent under the id the event carries.
       const injected: string[] = []
       const child = { id: SessionId('child-x'), inject: (content: { type: string; text?: string }[]) => { injected.push(content.map(b => b.text ?? '').join('')) }, session: { id: SessionId('child-x'), header: { id: 'child-x' } } } as unknown as Parameters<typeof ctx.agents.register>[0]
       ctx.agents.register(child)
@@ -693,7 +690,6 @@ export function defineCoverageCases(group: CoverageGroup): void {
       await ctx.plugin(HooksClaude, { configPath: join(serverDir, 'hooks.json') })
       ctx.llm.registerAdapter(['mock'], new MockAdapter([]))
 
-      // Register a live child on its own session cwd; emit subagent/end with its id.
       const { SessionId } = await import('@deepseek-ai/dsh-session')
       const childHandle = await ctx.agents.create({ sessionId: SessionId('child-stop-session'), meta: { cwd: childDir }, agentOptions: { provider: 'mock', model: 'mock' } })
       ctx.emit(subagentCarrier(ctx), 'subagent/end', { runId: SubagentRunId('run-stop'), provider: 'inproc', id: childHandle.agent.id, local: true, stopReason: 'completed' })
@@ -735,7 +731,6 @@ export function defineCoverageCases(group: CoverageGroup): void {
       const adapter = new MockAdapter([textResponse('ok')])
       const ctx = await harness(path, adapter)
       const agent = ctx.agentLoop.create(SessionId('a1'), { provider: 'mock', model: 'mock' })
-      // Send immediately — do NOT wait for the session-start inject.
       agent.followup([{ type: 'text', text: 'go' }])
       await waitForIdle(ctx, agent)
       expect(adapter.requests).toHaveLength(1) // the turn ran regardless of hook timing

+ 0 - 1
packages/host/webserver/tests/web-plugins.spec.ts

@@ -258,7 +258,6 @@ describe('createHostWebPluginRegistry', () => {
     expect(errors).toHaveLength(1)
     expect(registry.graph().entries.map(row => row.id)).toEqual(['late-loader'])
 
-    // After dispose, further fiber events no longer rescan.
     registry.dispose()
     entries.pop()
     ctx.emit('internal/plugin', ctx.fiber)

+ 9 - 9
packages/mcp/mcp-client/src/index.ts

@@ -51,7 +51,7 @@ const activeServerNames = new WeakMap<Context, Set<string>>()
 
 /** Config for connecting to an MCP server via a spawned child process over stdio. */
 export interface StdioConfig {
-  /** Transport type: spawn a child process and communicate over stdio. */
+  /** Selects child-process stdio transport. */
   transport: 'stdio'
   /**
    * Stable local namespace for this server's model-facing tool names
@@ -59,21 +59,21 @@ export interface StdioConfig {
    * unique across live mcp-client instances.
    */
   serverName: string
-  /** Executable to spawn. */
+  /** Executable used to start the server. */
   command: string
-  /** Arguments passed to the command. */
+  /** Arguments passed directly, without shell interpolation. */
   args: string[]
   /** Extra env vars merged on top of scrubbed ambient env. */
   env: Record<string, string>
   /** Working directory for the child process. */
   cwd: string
-  /** Timeout per callTool invocation (ms). */
+  /** Per-tool-call timeout in milliseconds. */
   toolCallTimeoutMs: number
 }
 
 /** Config for connecting to an MCP server over Streamable HTTP (SSE). */
 export interface StreamableHttpConfig {
-  /** Transport type: connect to an MCP server over Streamable HTTP (SSE). */
+  /** Selects Streamable HTTP transport. */
   transport: 'streamable-http'
   /**
    * Stable local namespace for this server's model-facing tool names
@@ -81,15 +81,15 @@ export interface StreamableHttpConfig {
    * unique across live mcp-client instances.
    */
   serverName: string
-  /** MCP server URL. */
+  /** MCP endpoint URL. */
   url: string
-  /** Extra headers (e.g. auth tokens). */
+  /** Additional headers attached to MCP requests. */
   headers: Record<string, string>
-  /** Timeout per callTool invocation (ms). */
+  /** Per-tool-call timeout in milliseconds. */
   toolCallTimeoutMs: number
 }
 
-/** Discriminated union of all supported MCP transport configurations. */
+/** Configuration for one stdio or Streamable HTTP MCP server. */
 export type Config = StdioConfig | StreamableHttpConfig
 
 export const Config = z.union([

+ 0 - 2
packages/mcp/mcp-client/tests/apply.spec.ts

@@ -213,13 +213,11 @@ describe('apply (plugin lifecycle)', () => {
 
     expect(ctx.tools.get('mcp__srv__remote')).toBeDefined()
 
-    // Simulate the notification handler being invoked with a new tool list.
     mockListTools.mockResolvedValue({
       tools: [{ name: 'updated', inputSchema: { type: 'object' } }],
       nextCursor: undefined,
     })
 
-    // Extract and call the notification handler.
     const handler = mockSetNotificationHandler.mock.calls[0]![1] as () => Promise<void>
     await handler()
 

+ 1 - 4
packages/mcp/mcp-client/tests/mcp-client.e2e.ts

@@ -314,18 +314,16 @@ describe('server-filesystem — real filesystem operations', () => {
     const filePath = join(tempDir, 'test.txt')
     const content = 'Hello from MCP e2e test!'
 
-    // Write via MCP tool
     const writeResult = await ctx.tools.execute({
       signal: testToolSignal,
       callId: nextCallId(), name: 'mcp__filesystem__write_file', arguments: { path: filePath, content },
     })
     expect(writeResult.isError).toBe(false)
 
-    // Verify file was actually written (world verification)
+    // Assert the filesystem effect independently of the tool result.
     const onDisk = await readFile(filePath, 'utf8')
     expect(onDisk).toBe(content)
 
-    // Read back via MCP tool
     const readResult = await ctx.tools.execute({
       signal: testToolSignal,
       callId: nextCallId(), name: 'mcp__filesystem__read_file', arguments: { path: filePath },
@@ -335,7 +333,6 @@ describe('server-filesystem — real filesystem operations', () => {
   })
 
   it('list_directory shows written file', async () => {
-    // Ensure a file exists
     await writeFile(join(tempDir, 'listed.txt'), 'listed')
 
     const result = await ctx.tools.execute({

+ 0 - 1
packages/mcp/mcp-client/tests/mcp-client.spec.ts

@@ -811,7 +811,6 @@ describe('tool execution — non-object args fallback', () => {
     )
 
     await syncTools(client as never, ctx, defaultOpts, new Map())
-    // Simulate model emitting `null` as tool arguments (malformed).
     await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'mcp__srv__coerce', arguments: null })
 
     expect(client.callTool).toHaveBeenCalledWith(

+ 1 - 1
packages/subagent/subagent-acp/tests/subagent-acp.e2e.ts

@@ -102,7 +102,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('ACP backend with-key e2e (drive
     await run.dispose()
 
     expect(result.stopReason).toBe('completed')
-    // Verify the WORLD: the child process actually wrote the file in its cwd.
+    // Assert the filesystem effect independently of the model response.
     const proof = await readFile(join(workdir, 'proof.txt'), 'utf8')
     expect(proof).toContain('ACP_CHILD_WAS_HERE')
   }, 180_000)

+ 2 - 9
packages/subagent/subagent-spawn/tests/spawn.e2e.ts

@@ -6,14 +6,7 @@ import type { Context } from 'cordis'
 import { spawnHarness, waitForIdle } from './harness.ts'
 import { SessionId } from '@deepseek-ai/dsh-session'
 
-/**
- * With-key smoke for the in-process spawn backend: a REAL parent agent delegates
- * to a REAL child (via the `subagent` tool → spawn backend) that uses the REAL
- * bash tool to write a file, and we verify the WORLD (the file on disk) — not
- * the agent's self-report. This is the "green units, broken product" guard:
- * mocks prove the plumbing, only a real model proves a parent can actually drive
- * a child to do real work. Key-gated (self-skips without DEEPSEEK_API_KEY).
- */
+/** Key-gated smoke for a real parent delegating filesystem work to a real child. */
 
 let ctx: Context | undefined
 let workdir: string | undefined
@@ -37,7 +30,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('spawn backend with-key smoke', (
       + 'After the subagent finishes, tell me it is done.' }])
     await waitForIdle(ctx, parent)
 
-    // Verify the WORLD: the child actually wrote the file.
+    // Assert the filesystem effect independently of the model response.
     const proof = await readFile(join(workdir, 'proof.txt'), 'utf8')
     expect(proof).toContain('SUBAGENT_WAS_HERE')
 

+ 12 - 31
packages/subagent/subagent/src/types.ts

@@ -1,7 +1,5 @@
 /**
- * Subagent seam vocabulary: the request/result/capability types a
- * {@link SubagentProvider} consumes and produces. No runtime code — types
- * only, per the package convention.
+ * Request, result, and capability contracts for {@link SubagentProvider}.
  *
  * @module @deepseek-ai/dsh-subagent/types
  */
@@ -17,7 +15,7 @@ export type SubagentRunId = Branded<'SubagentRunId'>
 
 /**
  * Brand a string as a {@link SubagentRunId}.
- * @param id - the raw id string (the service mints UUIDs; tests may pass fixtures).
+ * @param id - the raw run id.
  * @returns the same string, branded.
  */
 export function SubagentRunId(id: string): SubagentRunId {
@@ -33,13 +31,9 @@ export function SubagentRunId(id: string): SubagentRunId {
  * is the capability.
  */
 export interface SubagentCapabilities {
-  /** Honor {@link SubagentStartRequest.outputSchema} (structured final output). */
   readonly outputSchema: boolean
-  /** Enforce {@link SubagentStartRequest.maxDepth} (recursion cap). */
   readonly depthLimit: boolean
-  /** Enforce {@link SubagentStartRequest.toolFilter} (child tool scoping). */
   readonly toolFilter: boolean
-  /** Honor {@link SubagentStartRequest.persona} (a per-child persona). */
   readonly persona: boolean
 }
 
@@ -50,16 +44,11 @@ export interface SubagentCapabilities {
  * passes it to {@link SubagentProvider.start}.
  */
 export interface SubagentStartRequest {
-  /** The task/prompt for the child agent (a user message in the child session). */
+  /** Content delivered as the child's user message. */
   readonly prompt: ContentBlock[]
   /**
-   * The spawning ("parent") agent — the one whose tool call started this
-   * subagent. REQUIRED: in-process backends read `parent.session.header` for
-   * the working directory, the `parentSession` lineage to stamp on the child,
-   * and the parent's delegation depth. The out-of-process backend (ACP) reads
-   * exactly one field — the session header's cwd, the child's workspace when
-   * no deployment `cwd` override is configured; nothing else crosses the
-   * process boundary.
+   * The spawning agent. In-process providers derive workspace, lineage, and
+   * delegation depth from its durable session state; ACP uses only its cwd.
    */
   readonly parent: Agent
   /**
@@ -70,7 +59,6 @@ export interface SubagentStartRequest {
    * afterward.
    */
   readonly signal: AbortSignal
-  /** Per-child agent options (model and plugin-defined extension fields). */
   readonly agentOptions?: AgentOptions
   /**
    * Object-rooted JSON Schema within `assertObjectJsonSchema`'s enforced subset. Start rejects
@@ -110,15 +98,12 @@ export interface SubagentStartRequest {
  * non-`completed` result to an `isError` tool result.
  */
 export interface SubagentStopReasonMap {
-  /** The child finished its turn normally. */
   completed: 'completed'
-  /** The run was cancelled by its request signal or by disposal. */
+  /** Cancelled through the request signal or disposal. */
   aborted: 'aborted'
-  /** The child failed (model error, transport error). */
+  /** Model or transport failure. */
   error: 'error'
-  /** The child hit its token ceiling before finishing. */
   'max-tokens': 'max-tokens'
-  /** The child declined the task. */
   refusal: 'refusal'
 }
 
@@ -170,9 +155,8 @@ export interface SubagentRun {
    */
   readonly result: Promise<SubagentResult>
   /**
-   * Cancel remaining work, reach child quiescence, and release the run's
-   * resources (in-process: dispose the owned agent and remove its session;
-   * ACP: kill and reap the subprocess). Idempotent.
+   * Cancel remaining work, reach child quiescence, and release resources.
+   * Idempotent.
    */
   dispose(): Promise<void>
   /**
@@ -188,12 +172,9 @@ export interface SubagentRun {
 }
 
 /**
- * A subagent backend: one transport for running a child agent (in-process
- * spawn/fork, ACP to another process, …). Implementations register under a
- * unique name via {@link SubagentService.registerProvider}; multiple providers
- * coexist in one context (unlike the single-implementation bash seam). The
- * Providers are trusted same-process implementations; callers treat their
- * descriptors and returned values as borrowed immutable data.
+ * One registered transport for running child agents. Providers are trusted
+ * same-process implementations; callers treat descriptors and returned values
+ * as borrowed immutable data.
  */
 export interface SubagentProvider {
   /** Unique registry name (e.g. `spawn`, `fork`, `acp`). */

+ 18 - 77
packages/ui/acp/src/index.ts

@@ -61,26 +61,16 @@ import {
 import type {} from '@deepseek-ai/dsh-commands'
 import { encodeSessionReferenceUri } from '@deepseek-ai/dsh-session-reference'
 import { displayPromptContent, SessionId, type JsonValue } from '@deepseek-ai/dsh-session'
-// Side-effect type import: resolves `ctx.get('permission')` to the service.
+// Empty type imports activate the service and event declaration merges used
+// through ctx.get() and ctx.on().
 import type {} from '@deepseek-ai/dsh-permission'
 import type { SessionEvent, TodoItem, TurnEndReason } from '@deepseek-ai/dsh-session'
-// Side-effect type import: adds the log-only session/title event translated below.
 import type {} from '@deepseek-ai/dsh-session-title'
 import type { ToolCallView, ToolRegistry, ToolResultView, TerminalResultView } from '@deepseek-ai/dsh-tools'
-// Side-effect type import: declaration-merges `ctx.sessionPersistence` onto
-// Context (the bridge injects it and reads `list()` for load cwd validation).
 import type {} from '@deepseek-ai/dsh-session-persistence'
-// Side-effect type import: declaration-merges the exact-read service used by
-// session/list for live-preferred title folding.
 import type {} from '@deepseek-ai/dsh-session-query'
-// Type-only edge: resolves `ctx.get('planMode')` when dsh-plan-mode is composed;
-// the runtime read stays opportunistic.
 import type {} from '@deepseek-ai/dsh-plan-mode'
-// Side-effect type import: declaration-merges prompt assembly onto Context and
-// the scoped waterfall used to keep persona variables aligned with requests.
 import type {} from '@deepseek-ai/dsh-system-prompt'
-// Side-effect type import: declaration-merges the `approval/request` waterfall
-// the bridge answers for its own agents (see the approval answerer below).
 import type {} from '@deepseek-ai/dsh-user-approval'
 import {
   UserInteractionError,
@@ -871,18 +861,8 @@ export function apply(ctx: Context, config: AcpConfig): void {
         // slot is released in `finally` so a rejected load never wedges the id.
         loadingIds.add(sessionId)
         try {
-          // Validate the PERSISTED cwd BEFORE resuming — `list()` is a
-          // metadata-only read (no full-log parse), so this rejects a session we
-          // can't honor WITHOUT ever constructing/registering an agent (a
-          // post-resume reject would leak the registered agent — cancel() does not
-          // unregister it — and wedge the id against re-load). The session's bash
-          // workdir is derived from its persisted `header.cwd` and the request
-          // `cwd` does NOT override it (resume takes no cwd), so a session with no
-          // absolute persisted cwd would silently run bash in the SERVER's launch
-          // dir, not the client's workspace. A session created by this bridge
-          // always has a cwd (session/new requires it); reject the rest loudly.
-          // (An id unknown to `list()` falls through to resume, which rejects with
-          // the backend's not-found error.)
+          // Validate metadata before resume so rejection cannot leave a
+          // registered agent. Persisted cwd is authoritative for resumed work.
           const meta = (await sessionPersistence.list()).find(m => m.id === sessionId)
           if (meta !== undefined) {
             const persistedCwd = meta.cwd
@@ -903,12 +883,8 @@ export function apply(ctx: Context, config: AcpConfig): void {
             agentOptions: agentOptions(config),
             setup: (agentCtx) => { installTarget(agentCtx, target) },
           })
-          // The bridge may have torn down (disposal / client disconnect) while
-          // resume() was pending. Its listeners are gone, so installing a record
-          // now would resurrect a live agent the bridge can no longer drive. Bail —
-          // and tear down the just-resumed agent (unregister + stop + remove its
-          // session) before throwing, so it does not leak: it has no SessionRecord,
-          // so quiesce() would never see it.
+          // Closure can race resume. Dispose the unpublished agent before
+          // rejecting because quiesce() tracks only installed records.
           /* v8 ignore next 4 -- the in-memory test transport rejects the in-flight
              session/load request the instant it closes (before this post-await
              code runs), so the guard can't be hit in tests; it protects the real
@@ -937,19 +913,9 @@ export function apply(ctx: Context, config: AcpConfig): void {
             pendingSwitches: {},
           }
           sessions.set(sessionId, record)
-          // Replay the persisted event log to the client as session/update. Use
-          // the raw event log (NOT deriveMessages, which drops assistant/chunk
-          // and trace events): RFC 010's load contract reconstructs the streamed
-          // turns — user prompts (user/message → user_message_chunk), assistant
-          // text and reasoning (assistant/chunk), and tool calls/results.
-          //
-          // Replay through a THROWAWAY presenter, NOT `record.presenter`: a
-          // historical turn that was interrupted mid-tool (a `tool/call` with no
-          // matching `tool/result` in the persisted log) would otherwise leave a
-          // stale in-flight entry on the live presenter, which then serves all
-          // future live events for this session. The throwaway pairs call→result
-          // as the log replays in order (same as live) and is discarded after,
-          // so the record's presenter starts clean for the post-load live stream.
+          // Replay the raw log because deriveMessages omits update-bearing
+          // chunks and trace events. A disposable presenter prevents an
+          // incomplete historical tool call from polluting live presentation.
           const replayPresenter = makePresenter(agent)
           const replayTerminal: TerminalRendering = {
             enabled: terminalEnabled,
@@ -1081,11 +1047,9 @@ export function apply(ctx: Context, config: AcpConfig): void {
           }
           assertOpen()
         }
-        // Install the in-flight slot BEFORE followup() (followup does not synchronously
-        // flip status to running; the session/event listener records the turn
-        // number and settle/rejects it). Capture the log length now as the
-        // A turn that ends in error rejects this promise (the codec never
-        // produces an error stop reason).
+        // Install before followup(), which does not synchronously enter running;
+        // the session listener associates and settles the message-triggered turn.
+        // Error turns reject because ACP has no error stop reason.
         const stopReason = await new Promise<StopReason>((resolve, reject) => {
           rec.inflight = { resolve, reject, turn: undefined }
           rec.agent.followup(preparedContent, { contexts: preparedContexts })
@@ -1096,18 +1060,8 @@ export function apply(ctx: Context, config: AcpConfig): void {
       cancel(params: CancelNotification): Promise<void> {
         const rec = sessions.get(SessionId(params.sessionId))
         if (rec === undefined) return Promise.resolve()
-        // session/cancel maps to the queue-aware agent.cancel({ kind: 'user' }): it aborts
-        // a RUNNING step, clears the queued + steering FIFOs, and drops a
-        // turn that is about to start (the pre-step window) — so a queued-but-
-        // not-yet-started prompt never runs, while a prompt accepted afterward
-        // remains a separate queued turn. Scoped to THIS session's
-        // agent — a cancel in one session never touches another's stream or
-        // pending prompt (multi-session isolation).
-        // We ALSO settle the in-flight prompt
-        // as cancelled directly here: do NOT rely on the resulting turn/end to
-        // settle it, because cancel() may drop the turn before any turn/end is
-        // emitted, and removing this direct settle would move the RPC's
-        // resolution onto a later observer path, changing its timing.
+        // Agent cancellation may drop a pre-step turn without emitting
+        // turn/end, so the bridge settles its prompt directly.
         if (rec.promptPreparation !== undefined) {
           rec.promptPreparation.abort(new Error('session/cancel'))
         } else if (rec.commandAbort !== undefined) {
@@ -1266,7 +1220,6 @@ export function apply(ctx: Context, config: AcpConfig): void {
 /**
  * Build per-agent options from the plugin config, omitting absent fields
  * (exactOptionalPropertyTypes: never assign `undefined` to an optional key).
- * Exported for unit coverage of both the present and absent branches.
  * @param config - the plugin config carrying the optional provider/model target.
  * @returns the per-agent options, with each configured target field present.
  */
@@ -1278,22 +1231,10 @@ export function agentOptions(config: AcpConfig): { provider?: string; model?: st
 }
 
 /**
- * Validate the `cwd`/`additionalDirectories` contract shared by `session/new`
- * and `session/load`: `cwd` must be absolute (a relative path would be ambiguous
- * as a workspace root). The persisted-cwd equality check for `session/load`
- * happens after the metadata lookup; this validator only enforces request shape:
- *  - `session/new`: the validated `cwd` becomes the session's `SessionHeader.cwd`
- *    (via `agents.create({meta:{cwd}})`) and thus the default bash workdir.
- *  - `session/load`: the request `cwd` must be absolute AND must match the
- *    PERSISTED `header.cwd`, which stays authoritative for the bash workdir —
- *    the request cwd does not override it.
- * Any absolute path is accepted (the per-session cwd flows to the bash executor
- * — see `dsh-tool-bash`), so the server no longer has to launch in the
- * workspace. `additionalDirectories` must still be empty: widening the
- * tool/filesystem scope beyond the single cwd is a separate, unimplemented
- * concern (a sandbox seam), and silently ignoring extra roots would desync the
- * client's filesystem-scope UI. Both request shapes carry `cwd: string` and
- * `additionalDirectories?: string[]`, so one validator covers both.
+ * Validate the workspace shape shared by `session/new` and `session/load`.
+ * `cwd` is an absolute per-session workspace; load separately requires it to
+ * match persisted metadata. Additional roots are rejected because ignoring
+ * them would desynchronize the client's displayed filesystem scope.
  */
 function validateWorkspaceParams(params: { cwd: string; additionalDirectories?: string[] }): void {
   if (!isAbsolute(params.cwd)) {

+ 0 - 5
packages/ui/acp/tests/bridge.spec.ts

@@ -277,15 +277,10 @@ describe('acp bridge', () => {
   it('rejects a non-absolute cwd but accepts any absolute cwd (per-session workspace)', async () => {
     harness = await makeBridgeHarness({ storageDir })
     await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
-    // Relative cwd is still rejected (it becomes the session header / bash workdir).
     await expect(harness.client.newSession({ cwd: 'relative/path', mcpServers: [] }))
       .rejects.toThrow(/absolute/)
-    // An absolute cwd that differs from the server launch dir is now ACCEPTED —
-    // the per-session cwd is honored (routed to the bash workdir), so the server
-    // no longer has to launch in the workspace.
     const res = await harness.client.newSession({ cwd: '/tmp', mcpServers: [] })
     expect(res.sessionId).toBeTruthy()
-    // The session header records that cwd, so its bash tools run there.
     expect(harness.ctx.agents.get(SessionId(res.sessionId))!.session.header.cwd).toBe('/tmp')
   })
 

+ 1 - 1
packages/ui/acp/tests/config-options.spec.ts

@@ -338,7 +338,7 @@ describe('acp bridge — session config options', () => {
   it('a knob drifted outside the table derives a visible-but-untargetable custom current', async () => {
     h = await presetStack()
     const { sessionId } = await h.client.newSession({ cwd: process.cwd(), mcpServers: [] })
-    // Simulate a plugin calling the public knob setter inside a valid turn.
+    // A plugin may write a valid state not represented by a named preset.
     const agent = h.ctx.agents.list()[0]
     if (agent === undefined) throw new Error('expected an agent')
     agent.session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })

+ 4 - 25
packages/ui/acp/tests/dispose.spec.ts

@@ -18,14 +18,11 @@ describe('acp bridge — disposal & HMR safety', () => {
     const { sessionId } = await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] })
     const agent = harness.ctx.agents.get(SessionId(sessionId))!
 
-    // Start a prompt that hangs in the model stream.
     const promptDone = harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'go' }] })
     await new Promise(r => setTimeout(r, 30))
     expect(agent.status).toBe('running')
 
-    // Dispose the whole context. The bridge's teardown must abort the agent and
-    // AWAIT whenIdle() — so right after dispose resolves, the agent is settled
-    // (not still running). Proves disposal waited, not just requested.
+    // Fiber disposal must not resolve until the running agent is quiescent.
     await harness.ctx.fiber.dispose()
     expect(agent.status).not.toBe('running')
 
@@ -84,35 +81,21 @@ describe('acp bridge — disposal & HMR safety', () => {
   })
 
   it('a client disconnect mid-prompt disposes the session (no registered agent left)', async () => {
-    // The ACP transport closes (editor quits) while a turn runs. The bridge must
-    // settle the in-flight prompt cancelled and DISPOSE the agent (the session's
-    // per-agent AgentHandle teardown) rather than leaving an orphaned running —
-    // or even idled-but-still-registered — agent whose updates are swallowed.
+    // Transport closure owns the agent handle; idle-but-registered is also a leak.
     const harness = await makeBridgeHarness({ storageDir, script: ['hang'] })
     await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
     const { sessionId } = await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] })
     const agent = harness.ctx.agents.get(SessionId(sessionId))!
-    // Start a prompt that hangs in the model stream. The prompt RPC will never
-    // return (its transport is severed), so do not await it.
+    // The transport will sever this RPC, so do not await it.
     void harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'go' }] }).catch(() => {})
     await new Promise(r => setTimeout(r, 30))
     expect(agent.status).toBe('running')
 
-    // Sever the transport — the bridge's conn.closed teardown runs and drives the
-    // agent's AgentHandle dispose to quiescence on its OWN (before any dispose()).
     await harness.closeClientTransport()
     await agent.whenIdle()
-    // The agent's loop has stopped: status `disposed`.
     expect(agent.status).toBe('disposed')
 
-    // Await the bridge teardown to completion WITHOUT tearing down the root
-    // agents/sessions services (so we can still query them). acpFiber.dispose()
-    // invokes the SAME memoized quiesce() the disconnect started and awaits its
-    // promise — which resolves only after every rec.dispose() (loop exit +
-    // session removal) has finished, closing the whenIdle()/owned.dispose()
-    // microtask race. The AgentHandle dispose has run: the agent is unregistered
-    // and its session removed from the store, not merely idled (the old
-    // behavior). The services live on the root ctx, so they survive this.
+    // Fiber disposal joins the disconnect teardown while root services remain queryable.
     await harness.acpFiber.dispose()
     expect(harness.ctx.agents.get(SessionId(sessionId))).toBeUndefined()
     expect(harness.ctx.sessions.get(SessionId(sessionId))).toBeUndefined()
@@ -148,8 +131,6 @@ describe('acp bridge — disposal & HMR safety', () => {
 
     await harness.ctx.fiber.dispose()
     const before = harness.updates.length
-    // Append an event directly to the (now-detached) session: the bridge's
-    // session/event listener should have been disposed, so no update fires.
     session.append('turn/start', { turn: 99, trigger: { kind: 'message', source: { kind: 'user' } } })
     await new Promise(r => setTimeout(r, 10))
     expect(harness.updates.length).toBe(before)
@@ -239,11 +220,9 @@ describe('acp bridge — disposal & HMR safety', () => {
     expect(harness.ctx.agents.get(SessionId('sib-b'))).toBe(handleB.agent)
 
     await handleA.dispose()
-    // A is gone — unregistered AND its session removed from the store.
     expect(harness.ctx.agents.get(SessionId('sib-a'))).toBeUndefined()
     expect(harness.ctx.sessions.get(SessionId('sib-a'))).toBeUndefined()
     expect(handleA.agent.status).toBe('disposed')
-    // B is wholly unaffected.
     expect(harness.ctx.agents.get(SessionId('sib-b'))).toBe(handleB.agent)
     expect(harness.ctx.sessions.get(SessionId('sib-b'))).toBeDefined()
     expect(handleB.agent.status).not.toBe('disposed')

+ 2 - 5
packages/ui/acp/tests/properties.spec.ts

@@ -1,9 +1,6 @@
 /**
- * Property-based protocol-shape tests for the ACP update stream (RFC 001 → ADR 0013
- * precedent). Fuzz arbitrary harness `SessionEvent` sequences through the pure
- * `streamSessionEventUpdate` translator and assert legal update variants, call-before-result order
- * per tool id, and deterministic event-to-update translation. Keeping this pure makes live and
- * replay equivalence deterministic rather than a timing property.
+ * Property tests exercise the pure event translator so live/replay equivalence
+ * and per-call ordering remain deterministic rather than timing-dependent.
  */
 
 import { describe, expect, it } from 'vitest'

+ 1 - 4
packages/ui/tui/tests/tui.spec.ts

@@ -3484,8 +3484,7 @@ describe('terminal mounting', () => {
     await tick()
     expect(result.terminal.output.length).toBe(beforeSameScheme)
 
-    // Simulate the terminal responding with a light color scheme report
-    // (ESC [?997;2n = light, ESC [?997;1n = dark).
+    // ESC [?997;2n reports light; ESC [?997;1n reports dark.
     result.terminal.send('\x1b[?997;2n')
     await tick()
     await tick()
@@ -3497,11 +3496,9 @@ describe('terminal mounting', () => {
     // uses ANSI 90 for the same header text.
     expect(result.terminal.output).toContain('\x1b[90mdeepseek-v4-flash')
 
-    // Switch back to dark scheme.
     result.terminal.send('\x1b[?997;1n')
     await tick()
     await tick()
-    // After switching back, a new write uses SGR 2 for the header detail.
     expect(result.terminal.output).toContain('\x1b[2mdeepseek-v4-flash')
     await dispose(result)
   })