dsh-plugin-dev-kb 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.en.md +14 -8
  3. package/README.md +14 -8
  4. package/docs/banner.svg +25 -0
  5. package/kb/INDEX.md +6 -2
  6. package/kb/README.md +4 -4
  7. package/kb/extra/AGENTS.md +2 -2
  8. package/kb/extra/cookbook/adding-a-session-format-version.md +109 -0
  9. package/kb/extra/cookbook/adding-a-session-format-version.zh.md +109 -0
  10. package/kb/extra/cookbook/adding-a-vendored-package.md +1 -1
  11. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +1 -1
  12. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +1 -1
  13. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +1 -1
  14. package/kb/extra/deepseek-llm-api-wire-extensions.md +2 -2
  15. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +2 -2
  16. package/kb/extra/development.md +13 -3
  17. package/kb/extra/development.zh.md +13 -3
  18. package/kb/extra/event-producer-consumer.md +33 -31
  19. package/kb/extra/event-producer-consumer.zh.md +34 -32
  20. package/kb/extra/i18n/README.md +2 -1
  21. package/kb/extra/i18n/README.zh.md +3 -2
  22. package/kb/extra/i18n/translation-prompt.md +1 -1
  23. package/kb/extra/module-graph.md +62 -24
  24. package/kb/extra/module-graph.zh.md +62 -24
  25. package/kb/extra/rescope.md +1 -1
  26. package/kb/extra/rescope.zh.md +1 -1
  27. package/kb/extra/session-format-status.md +47 -0
  28. package/kb/extra/session-format-status.zh.md +47 -0
  29. package/kb/extra/subsystems/agent-team.md +1 -1
  30. package/kb/extra/subsystems/agent-team.zh.md +1 -1
  31. package/kb/extra/subsystems/feedback.md +164 -30
  32. package/kb/extra/subsystems/feedback.zh.md +164 -30
  33. package/kb/extra/testing.md +4 -3
  34. package/kb/extra/testing.zh.md +4 -3
  35. package/kb/extra/web-styling.md +1 -0
  36. package/kb/extra/web-styling.zh.md +1 -0
  37. package/kb/meta/search-index.json +151 -103
  38. package/kb/meta/site-pages.txt +188 -184
  39. package/kb/meta/source.json +5 -5
  40. package/kb/meta/topics.md +2 -2
  41. package/kb/site/develop/basic/publish.md +1 -1
  42. package/kb/site/en/develop/basic/publish.md +1 -1
  43. package/kb/site/en/guide/providers.md +1 -1
  44. package/kb/site/en/guide/python-sdk.md +19 -3
  45. package/kb/site/en/reference/agent-lifecycle.md +12 -5
  46. package/kb/site/en/reference/capability-seams.md +10 -3
  47. package/kb/site/en/reference/config-catalog.md +173 -42
  48. package/kb/site/en/reference/cookbook/adding-a-tool.md +1 -1
  49. package/kb/site/en/reference/cookbook/extension-cookbook.md +1 -1
  50. package/kb/site/en/reference/index.md +18 -8
  51. package/kb/site/en/reference/persistence-catalog.md +121 -48
  52. package/kb/site/en/reference/subsystems/approval.md +1 -1
  53. package/kb/site/en/reference/subsystems/client-modules.md +9 -0
  54. package/kb/site/en/reference/subsystems/client-resources.md +94 -0
  55. package/kb/site/en/reference/subsystems/compaction.md +1 -1
  56. package/kb/site/en/reference/subsystems/core.md +79 -16
  57. package/kb/site/en/reference/subsystems/filesystem.md +15 -0
  58. package/kb/site/en/reference/subsystems/goal.md +37 -1
  59. package/kb/site/en/reference/subsystems/index.md +2 -0
  60. package/kb/site/en/reference/subsystems/invariants.md +1 -1
  61. package/kb/site/en/reference/subsystems/llm-streaming.md +20 -10
  62. package/kb/site/en/reference/subsystems/lsp.md +1 -1
  63. package/kb/site/en/reference/subsystems/persistence.md +36 -15
  64. package/kb/site/en/reference/subsystems/schedule.md +1 -1
  65. package/kb/site/en/reference/subsystems/scope.md +1 -1
  66. package/kb/site/en/reference/subsystems/session-reference.md +4 -0
  67. package/kb/site/en/reference/subsystems/session-telemetry.md +22 -6
  68. package/kb/site/en/reference/subsystems/session.md +64 -42
  69. package/kb/site/en/reference/subsystems/shell.md +9 -6
  70. package/kb/site/en/reference/subsystems/sidebar-right.md +151 -0
  71. package/kb/site/en/reference/subsystems/slots.md +41 -33
  72. package/kb/site/en/reference/subsystems/spill.md +13 -6
  73. package/kb/site/en/reference/subsystems/subagent.md +20 -13
  74. package/kb/site/en/reference/subsystems/subprocess.md +25 -26
  75. package/kb/site/en/reference/subsystems/system-prompt.md +3 -1
  76. package/kb/site/en/reference/subsystems/token-meter.md +2 -1
  77. package/kb/site/en/reference/subsystems/tools.md +5 -5
  78. package/kb/site/en/reference/subsystems/workspace.md +80 -1
  79. package/kb/site/en/reference/tool-catalog.md +46 -2
  80. package/kb/site/en/reference/tool-execution-pipeline.md +2 -2
  81. package/kb/site/guide/providers.md +1 -1
  82. package/kb/site/guide/python-sdk.md +19 -3
  83. package/kb/site/reference/agent-lifecycle.md +12 -5
  84. package/kb/site/reference/capability-seams.md +10 -3
  85. package/kb/site/reference/config-catalog.md +173 -42
  86. package/kb/site/reference/cookbook/adding-a-tool.md +1 -1
  87. package/kb/site/reference/cookbook/extension-cookbook.md +1 -1
  88. package/kb/site/reference/index.md +18 -8
  89. package/kb/site/reference/persistence-catalog.md +127 -54
  90. package/kb/site/reference/subsystems/approval.md +1 -1
  91. package/kb/site/reference/subsystems/client-modules.md +9 -0
  92. package/kb/site/reference/subsystems/client-resources.md +94 -0
  93. package/kb/site/reference/subsystems/compaction.md +1 -1
  94. package/kb/site/reference/subsystems/core.md +79 -16
  95. package/kb/site/reference/subsystems/filesystem.md +15 -0
  96. package/kb/site/reference/subsystems/goal.md +37 -1
  97. package/kb/site/reference/subsystems/index.md +14 -12
  98. package/kb/site/reference/subsystems/invariants.md +1 -1
  99. package/kb/site/reference/subsystems/llm-streaming.md +20 -10
  100. package/kb/site/reference/subsystems/lsp.md +1 -1
  101. package/kb/site/reference/subsystems/persistence.md +36 -15
  102. package/kb/site/reference/subsystems/schedule.md +1 -1
  103. package/kb/site/reference/subsystems/scope.md +1 -1
  104. package/kb/site/reference/subsystems/session-reference.md +4 -0
  105. package/kb/site/reference/subsystems/session-telemetry.md +22 -6
  106. package/kb/site/reference/subsystems/session.md +64 -42
  107. package/kb/site/reference/subsystems/shell.md +9 -6
  108. package/kb/site/reference/subsystems/sidebar-right.md +151 -0
  109. package/kb/site/reference/subsystems/slots.md +41 -33
  110. package/kb/site/reference/subsystems/spill.md +13 -6
  111. package/kb/site/reference/subsystems/subagent.md +20 -13
  112. package/kb/site/reference/subsystems/subprocess.md +25 -26
  113. package/kb/site/reference/subsystems/system-prompt.md +3 -1
  114. package/kb/site/reference/subsystems/token-meter.md +2 -1
  115. package/kb/site/reference/subsystems/tools.md +5 -5
  116. package/kb/site/reference/subsystems/workspace.md +80 -1
  117. package/kb/site/reference/tool-catalog.md +46 -2
  118. package/kb/site/reference/tool-execution-pipeline.md +2 -2
  119. package/lib/index.js +25 -44
  120. package/package.json +3 -2
  121. package/skills/dsh-plugin-dev-kb.md +3 -3
@@ -48,6 +48,12 @@ Vendored CLIs, build-only and test-only executables, direct in-process plugin mo
48
48
 
49
49
  The Python SDK follows the same application architecture. Its runtime wheel packages the normal `dsh` CLI as `deepseek-harness-sdk-runtime-<platform>-<arch>`, and the client launches `dsh --profile sdk` with an explicit Harness home by default. The minimal example selects the shipped `sdk-minimal` profile. Python exposes profile selection and ordered patch files rather than a complete Cordis tree; persistent external plugins are installed through `dsh plugin`. The removed private direct-config carrier has no compatibility bin or fallback parser.
50
50
 
51
+ ## Desktop application
52
+
53
+ The [Electron desktop application](https://github.com/deepseek-ai/deepseek-harness/blob/master/apps/desktop/README.md) owns the reserved `$DSH_HOME/profiles/desktop` npm project. Each signed Electron release binds one exact dsh version and carries a first-party offline seed; startup installs that version into the writable profile with the bundled pnpm, while retaining exact desktop-plugin versions from the previous profile. CLI profiles share supported product data under `$DSH_HOME`, but never executable packages, plugin activation, lockfiles, or `node_modules` with Desktop.
54
+
55
+ Electron starts the private Desktop Host package under its bundled upstream Node.js process; that package loads the installed dsh backend and matching client graph from the reserved profile. Unary RPC, Remote streams, and version-matched client assets cross versioned framed byte pipes with Node IPC reserved for lifecycle control, then reach the renderer through the secure `dsh-app://` protocol; the desktop composition opens no Web server or loopback port. Only shell-owned UI can run plugin transactions through the bundled pnpm and its private `$DSH_HOME/desktop/pnpm/store`.
56
+
51
57
  ## Core packages
52
58
 
53
59
  Here are some core packages that contribute to the Cordis tree.
@@ -80,13 +86,15 @@ A **step** is one model request plus the tools it calls. A **turn** is zero or m
80
86
  ```text
81
87
  turn/start
82
88
  claim next-step input plus one queued message
83
- assemble prompt sections + tool schemas
89
+ assemble prompt sections + tool schemas; project runtime context
84
90
  -> agent/pre-step reject | enter(messages, startsRequestSeries?)
85
91
  reject, or a first enter rewritten empty -> close the turn with no step
86
92
  step/start
87
- append entered messages as user/message
88
- derive model history from the log
89
- agent/request -> llm/stream -> agent/assistant-stream start
93
+ agent/request -> prepareCall (cancellation commits neither system nor users)
94
+ reconcile system/message using the prepared call capability
95
+ append entered messages as user/message; log request/header and request/context as needed
96
+ derive and freeze model history from the log
97
+ stream the bound prepared call -> llm/stream -> agent/assistant-stream start
90
98
  agent/assistant-stream chunk*
91
99
  assistant/message | assistant/attempt -> agent/assistant-stream end
92
100
  tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*
@@ -96,11 +104,13 @@ turn/start
96
104
  turn/end
97
105
  ```
98
106
 
99
- `turn/*`, `step/*`, `user/message`, `assistant/message`, `assistant/attempt`, and `tool/*` are durable session events; the rest are live extension points across three domains. `agent/assistant-stream` publishes process-local start, transient chunk, and end frames. The loop commits the complete compact stream as one message or log-only attempt before a committed end frame, and the Web Session-follow adapter is the live event's only remote consumer. `agent/pre-step`, `agent/request`, `llm/stream`, and the three `tools/*` events are waterfalls, whose listeners must call `next()` to delegate; `agent/turn-stopping` is serial and has no `next()`.
107
+ `turn/*`, `step/*`, `system/message`, `user/message`, `assistant/message`, `assistant/attempt`, and `tool/*` are durable session events; the rest are live extension points across three domains. `agent/assistant-stream` publishes process-local start, transient chunk, and end frames. The loop commits the complete compact stream as one message or log-only attempt before a committed end frame, and the Web Session-follow adapter is the live event's only remote consumer. `agent/pre-step`, `agent/request`, `llm/stream`, and the three `tools/*` events are waterfalls, whose listeners must call `next()` to delegate; `agent/turn-stopping` is serial and has no `next()`.
100
108
 
101
109
  Input reaches the driver through one inbox. Some messages wake it immediately; injected context waits in the inbox until another message does.
102
110
 
103
- `agent/pre-step` decides what the model sees. Listeners may rewrite the claimed messages or reject them outright; a rejected or empty first claim still closes a durable turn that spent no step, so the log records the attempt. An enter decision may also set `startsRequestSeries` to begin a distinct model-message series: the loop then logs a fresh `request/header` (reason `series`, or `change` carrying `startsSeries: true` when the envelope changed too). A listener that rebuilds a downstream enter decision must spread it (`{ ...decision, messages }`) so the declaration survives. Each step reads the prompt sections and tool schemas that plugins registered.
111
+ `agent/pre-step` decides the accepted input. Listeners may rewrite or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, `agent/request` and `prepareCall()` resolve the actual route before the system prompt and accepted users are committed; cancellation during either async phase commits neither. The prepared call capability governs prompt admission, not the preceding `request/context`. Every attempt synchronously reconciles the same rendered assembly, appends users only on the first attempt, logs header/context as needed, and derives and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The first admitted step reserves the system head before user messages even for an empty prompt (no wire message). The prompt travels only as `system/message` history: an empty rendering clears all active system nodes, leaving no old prompt model-visible; capable routes can append non-empty updates after the cached prefix; incapable routes and new request series consolidate non-empty prompt text at the first system node, with logged empty replacements for non-empty later system nodes ([decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md); [decision rule](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/agent-loop/README.md#understand-the-implementation)).
112
+
113
+ The loop sends immutable requests while keeping cancellation live. It reuses message-freeze provenance only for identities it has fully frozen; [agent-loop](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/agent-loop/README.md) owns the request construction rules.
104
114
 
105
115
  Details: the [sequence diagram](./agent-lifecycle.md), the [tool pipeline](./tool-execution-pipeline.md), and [cancellation and error recovery](./subsystems/core.md#the-agent-handle).
106
116
 
@@ -108,7 +118,7 @@ Details: the [sequence diagram](./agent-lifecycle.md), the [tool pipeline](./too
108
118
 
109
119
  The session log is the source of the context the model sees. `deriveMessages()` projects model history from it. Each `assistant/message` embeds the exact compact timed stream that produced its assembled content; `assistant/attempt` retains settled failed, retried, cancelled, and stream-error attempts without adding model history. Fork, resume, transcripts, telemetry, and persistence all derive from these durable settlements, while live UI incrementality comes from `agent/assistant-stream`; a hard process loss before settlement leaves no durable attempt stream ([decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md)).
110
120
 
111
- Session consumers know only the current logical format. Header-only `stat` and `list` rescan each Session directory, select its numerically highest canonical generation, and translate a supported historical header without loading events or publishing a successor. A stored-session `open` selects that same generation, refuses a future version, or composes the static adjacent migration chain in memory, validates the final result, and exclusively publishes only that version-named successor beside the unchanged source before returning a handle; semantic interrupted-turn repair remains a handle consumer responsibility. JSONL v0 uses `session.jsonl[.zstd]`, v1 and later use lowercase `session.vN.jsonl[.zstd]`, and committed generation paths are never renamed, replaced, or deleted. The JSONL provider owns physical framing, compression, generation selection, and exclusive publication, while each adjacent migration package owns exactly one `vN -> vN+1` step ([decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md)).
121
+ Session consumers know only the current logical format. Header-only `stat` and `list` rescan each Session directory, select its numerically highest canonical generation, and translate a supported historical header without loading events or publishing a successor. A stored-session `open` selects that same generation, refuses a future version, or decodes and composes the static adjacent migration chain once before returning validated current logical events. A read open uses that in-memory result without publishing a successor; a write open first encodes, verifies, and exclusively publishes the final version-named successor beside the unchanged source. Ordinary repair of an unsealed interrupted tail remains a handle consumer responsibility; migration inserts a missing interrupted `turn/end` only for the bounded released restart already sealed by a later `turn/start`. JSONL v0 uses `session.jsonl[.zstd]`, v1 and later use lowercase `session.vN.jsonl[.zstd]`, and committed generation paths are never renamed, replaced, or deleted. The JSONL provider owns physical framing, compression, generation selection, and exclusive publication, while each adjacent migration package owns exactly one `vN -> vN+1` step ([decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md)).
112
122
 
113
123
  **Model-visible means logged.** Anything that reaches a model request must be reconstructable from the log, and a runtime invariant asserts it. This is why a new model-visible input requires a new session event: extend `SessionEventMap` and render from the log.
114
124
 
@@ -120,7 +130,7 @@ A **seam** is a swappable capability with three roles: a **Service Definition**
120
130
 
121
131
  Seams are why one provider swap changes the whole product. Filesystem and subprocess providers share one execution world, so pointing them at a remote sandbox moves Bash, PTY, and LSP with them, with no provider forks. [Subagent providers](./subsystems/subagent.md) vary just as widely behind one interface, from a fresh child agent to a delegated turn in another product.
122
132
 
123
- [Experimental Agent Teams](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/agent-team.md) is a private opt-in coordination seam on `ctx.agentTeams`, with a durable roster, task board, and mailbox layered over continuable subagents.
133
+ [Experimental Agent Teams](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/agent-team.md) is a published opt-in coordination seam on `ctx.agentTeams`, with a durable roster, task board, and mailbox layered over continuable subagents.
124
134
 
125
135
  ## Where new behavior goes
126
136
 
@@ -10,7 +10,7 @@ outline: "deep"
10
10
 
11
11
  Every event type that can appear in a session's durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge into `@deepseek-ai/dsh-session/types` in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](./subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](./subsystems/persistence.md) (how the log is made durable), and the generated region of [session.md](./subsystems/session.md#cordis-surface) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit).
12
12
 
13
- This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks retain the source declaration and nested property JSDoc, removing only the indentation imposed by a containing interface/module, and use a `ts persistence-catalog` fence (skipped by doc-typecheck because declarations reference types from their owning modules). Type names in a payload link to the page that documents them. See [the persistence-log-catalog Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md).
13
+ This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks retain the source declaration and nested property JSDoc, removing only the indentation imposed by a containing interface/module, and use a `ts persistence-catalog` fence (skipped by doc-typecheck because declarations reference types from their owning modules). Type names in a payload link to the page that documents them. The archived [persistence-log-catalog record](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md) documents the original catalog decision.
14
14
 
15
15
  The envelope declarations below compose each event's `type`, monotonic `seq`, epoch-ms `time`, `data`, the optional `ignorable` unknown-type skip marker, and the conditional `surfaceOp`/`sourceEventSeqs` fields. **surface** marks a `SurfaceEventType` member: it produces an LLM message and declares how it joins the surface list. **log-only** marks everything else: a durable, replayable record with no derived-history contribution. Every payload is JSON-serializable (enforced at `Session.append`). Current writers stamp `SESSION_FORMAT_VERSION`; supported historical artifacts reach this current vocabulary through the build-static adjacent migration catalog ([the version lifecycle](./subsystems/persistence.md)). Scope: the packages in this repo; a downstream plugin can merge further current-version event types, which are outside this catalog by construction and require an explicit disposition at a later format edge.
16
16
 
@@ -23,10 +23,11 @@ export type SessionEventType = keyof SessionEventMap
23
23
  /**
24
24
  * The subset of {@link SessionEventType} values whose events produce LLM
25
25
  * messages and are eligible to appear on the ordered surface. Only these
26
- * event types may carry {@link SurfaceOp}; user and tool events may also cite
26
+ * event types may carry {@link SurfaceOp}; system, user, and tool events may also cite
27
27
  * earlier sources through {@link SessionEvent.sourceEventSeqs}.
28
28
  */
29
29
  export type SurfaceEventType =
30
+ | 'system/message'
30
31
  | 'user/message'
31
32
  | 'assistant/message'
32
33
  | 'tool/result'
@@ -37,16 +38,16 @@ export type SurfaceEventType =
37
38
  *
38
39
  * - `'append'`: added to the tail — normal path for user/assistant/tool
39
40
  * messages.
40
- * - `{ op: 'replace', start, end }`: replaces surface nodes from `start`
41
- * (inclusive) through `end` (inclusive) with this node. Both must exist as
42
- * surface nodes in the current surface. `start === end` replaces a single
41
+ * - `{ op: 'replace', startSeq, endSeq }`: replaces surface nodes from `startSeq`
42
+ * (inclusive) through `endSeq` (inclusive) with this node. Both must exist as
43
+ * surface nodes in the current surface. `startSeq === endSeq` replaces a single
43
44
  * node. The node's {@link SessionEvent.sourceEventSeqs} must include every
44
45
  * shadowed surface node. Used by compaction; any surface-replacing producer
45
46
  * may use it.
46
47
  */
47
48
  export type SurfaceOp =
48
49
  | 'append'
49
- | { op: 'replace'; start: SessionSeq; end: SessionSeq }
50
+ | { op: 'replace'; startSeq: SessionSeq; endSeq: SessionSeq }
50
51
 
51
52
  /**
52
53
  * One immutable entry in the session log.
@@ -55,7 +56,7 @@ export type SurfaceOp =
55
56
  * unions), so `switch (event.type)` narrows `event.data` without casts.
56
57
  *
57
58
  * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
58
- * they only exist on {@link SurfaceEventType} variants (`user/message`,
59
+ * they only exist on {@link SurfaceEventType} variants (`system/message`, `user/message`,
59
60
  * `assistant/message`, `tool/result`).
60
61
  * Non-surface events (boundary markers, attempts, errors) never carry
61
62
  * surface metadata — the compiler enforces this at `Session.append()`
@@ -80,20 +81,14 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
80
81
  * inconvenience) rather than silently resuming a gutted session.
81
82
  */
82
83
  ignorable?: true
83
- } & (K extends SurfaceEventType ? {
84
- /**
85
- * Seq numbers of earlier events that this event cites as sources, such as
86
- * the surface nodes shadowed by a compaction replacement. A v2
87
- * `assistant/message` embeds its provider stream and cannot carry this field.
88
- */
89
- sourceEventSeqs?: SessionSeq[]
90
- /** How this event entered the surface; absent for non-surface events. */
91
- surfaceOp?: SurfaceOp
92
- } : object)
84
+ } & (K extends SurfaceEventType ? SurfaceIntent<K> : {
85
+ surfaceOp?: never
86
+ sourceEventSeqs?: never
87
+ })
93
88
  }[T]
94
89
  ```
95
90
 
96
- Sources: [`packages/core/session/src/types.ts:379`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:387`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:416`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:447`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
91
+ Sources: [`packages/core/session/src/types.ts:404`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:412`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:434`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:465`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
97
92
 
98
93
  ## Events
99
94
 
@@ -106,8 +101,8 @@ Sources: [`packages/core/session/src/types.ts:379`](https://github.com/deepseek-
106
101
  ```ts persistence-catalog
107
102
  /**
108
103
  * One normalized mutation of an agent's durable pending-message lists.
109
- * Live dispatch precedes projection mutation, so synchronous observers may
110
- * read the pre-splice inbox to recover the removed messages.
104
+ * The session-projection registry applies the committed event before
105
+ * `Session.append()` returns; Inbox live notifications follow that commit.
111
106
  */
112
107
  'agent/inbox/spliced': {
113
108
  target: InboxTarget
@@ -118,7 +113,7 @@ Sources: [`packages/core/session/src/types.ts:379`](https://github.com/deepseek-
118
113
  }
119
114
  ```
120
115
 
121
- Source: [`packages/core/agent/src/types.ts:58`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/agent/src/types.ts)
116
+ Source: [`packages/core/agent/src/types.ts:87`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/agent/src/types.ts)
122
117
 
123
118
  ### `agent-preset/*`
124
119
 
@@ -220,7 +215,7 @@ Source: [`packages/interaction/user-approval/src/index.ts:33`](https://github.co
220
215
  'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[] }
221
216
  ```
222
217
 
223
- Source: [`packages/core/session/src/types.ts:313`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
218
+ Source: [`packages/core/session/src/types.ts:335`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
224
219
 
225
220
  <a id="assistantmessage--surface"></a>
226
221
 
@@ -250,7 +245,7 @@ Source: [`packages/core/session/src/types.ts:313`](https://github.com/deepseek-a
250
245
 
251
246
  Types: [TokenUsage](./subsystems/llm-streaming.md)
252
247
 
253
- Source: [`packages/core/session/src/types.ts:299`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
248
+ Source: [`packages/core/session/src/types.ts:321`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
254
249
 
255
250
  ### `command/*`
256
251
 
@@ -406,8 +401,45 @@ Types: [ContentBlock](./subsystems/core.md) · [TokenUsage](./subsystems/llm-str
406
401
 
407
402
  Source: [`packages/compaction/compaction/src/types.ts:34`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/compaction/compaction/src/types.ts)
408
403
 
404
+ ### `deliverables/*`
405
+
406
+ <a id="deliverablespresented--log-only"></a>
407
+
408
+ #### `deliverables/presented` — log-only
409
+
410
+ ```ts persistence-catalog
411
+ /** Declared filesystem files from a successful final present result, including nested calls. */
412
+ 'deliverables/presented': { turn: number; callId: ToolCallId; files: PresentedFile[] }
413
+ ```
414
+
415
+ Types: [ToolCallId](./subsystems/core.md)
416
+
417
+ Source: [`packages/fs/tool-present/src/types.ts:15`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/fs/tool-present/src/types.ts)
418
+
409
419
  ### `feedback/*`
410
420
 
421
+ <a id="feedbackmessage-delete--log-only"></a>
422
+
423
+ #### `feedback/message-delete` — log-only
424
+
425
+ ```ts persistence-catalog
426
+ /** Log-only deletion; earlier ratings and notes remain in the log. */
427
+ 'feedback/message-delete': MessageFeedbackDelete
428
+ ```
429
+
430
+ Source: [`packages/feedback/message-feedback/src/types.ts:58`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/feedback/message-feedback/src/types.ts)
431
+
432
+ <a id="feedbackmessage-put--log-only"></a>
433
+
434
+ #### `feedback/message-put` — log-only
435
+
436
+ ```ts persistence-catalog
437
+ /** Log-only human feedback; never enters model history. */
438
+ 'feedback/message-put': MessageFeedbackPut
439
+ ```
440
+
441
+ Source: [`packages/feedback/message-feedback/src/types.ts:56`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/feedback/message-feedback/src/types.ts)
442
+
411
443
  <a id="feedbackrecord--log-only"></a>
412
444
 
413
445
  #### `feedback/record` — log-only
@@ -417,10 +449,10 @@ Source: [`packages/compaction/compaction/src/types.ts:34`](https://github.com/de
417
449
  * One recorded human remark about this session. Log-only and independent
418
450
  * of its trigger; it never enters model context or derived history.
419
451
  */
420
- 'feedback/record': { text: string }
452
+ 'feedback/record': FeedbackRecord
421
453
  ```
422
454
 
423
- Source: [`packages/feedback/command-feedback/src/index.ts:62`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/feedback/command-feedback/src/index.ts)
455
+ Source: [`packages/feedback/command-feedback/src/types.ts:40`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/feedback/command-feedback/src/types.ts)
424
456
 
425
457
  ### `goal/*`
426
458
 
@@ -570,13 +602,15 @@ Source: [`packages/plan/plan-mode/src/index.ts:46`](https://github.com/deepseek-
570
602
 
571
603
  ```ts persistence-catalog
572
604
  /**
573
- * Route metadata for the next request, logged only when the route or capacity
574
- * changes. It does not participate in request reconstruction or header equality.
605
+ * Route metadata for the next request, logged only when the route, capacity,
606
+ * or system prompt update mode changes. It does not participate in request
607
+ * reconstruction or header equality. Prompt admission uses the bound prepared
608
+ * call's capability, not this snapshot from an earlier request.
575
609
  */
576
610
  'request/context': RequestContext
577
611
  ```
578
612
 
579
- Source: [`packages/core/session/src/types.ts:352`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
613
+ Source: [`packages/core/session/src/types.ts:377`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
580
614
 
581
615
  <a id="requestheader--log-only"></a>
582
616
 
@@ -595,7 +629,7 @@ Source: [`packages/core/session/src/types.ts:352`](https://github.com/deepseek-a
595
629
  }
596
630
  ```
597
631
 
598
- Source: [`packages/core/session/src/types.ts:342`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
632
+ Source: [`packages/core/session/src/types.ts:365`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
599
633
 
600
634
  ### `sandbox/*`
601
635
 
@@ -670,7 +704,7 @@ Source: [`packages/schedule/schedule/src/types.ts:219`](https://github.com/deeps
670
704
  'session/end-seed': { inherited?: true }
671
705
  ```
672
706
 
673
- Source: [`packages/core/session/src/types.ts:375`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
707
+ Source: [`packages/core/session/src/types.ts:400`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
674
708
 
675
709
  <a id="sessiontitle--log-only"></a>
676
710
 
@@ -719,7 +753,7 @@ Source: [`packages/session/session-title-llm/src/index.ts:45`](https://github.co
719
753
  }
720
754
  ```
721
755
 
722
- Source: [`packages/session/session-log-deepseek/src/types.ts:59`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-log-deepseek/src/types.ts)
756
+ Source: [`packages/session/session-log-deepseek/src/types.ts:81`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-log-deepseek/src/types.ts)
723
757
 
724
758
  ### `step/*`
725
759
 
@@ -732,7 +766,7 @@ Source: [`packages/session/session-log-deepseek/src/types.ts:59`](https://github
732
766
  'step/end': { turn: number; step: number }
733
767
  ```
734
768
 
735
- Source: [`packages/core/session/src/types.ts:280`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
769
+ Source: [`packages/core/session/src/types.ts:289`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
736
770
 
737
771
  <a id="stepstart--log-only"></a>
738
772
 
@@ -743,10 +777,24 @@ Source: [`packages/core/session/src/types.ts:280`](https://github.com/deepseek-a
743
777
  'step/start': { turn: number; step: number }
744
778
  ```
745
779
 
746
- Source: [`packages/core/session/src/types.ts:278`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
780
+ Source: [`packages/core/session/src/types.ts:287`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
747
781
 
748
782
  ### `subagent/*`
749
783
 
784
+ <a id="subagentcatalog--log-only"></a>
785
+
786
+ #### `subagent/catalog` — log-only
787
+
788
+ ```ts persistence-catalog
789
+ /**
790
+ * A direct child's complete discovery fact.
791
+ * @param data - versioned parent-owned catalog entry.
792
+ */
793
+ 'subagent/catalog': SubagentCatalogEvent
794
+ ```
795
+
796
+ Source: [`packages/subagent/subagent/src/catalog.ts:40`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subagent/subagent/src/catalog.ts)
797
+
750
798
  <a id="subagentdescriptor--log-only"></a>
751
799
 
752
800
  #### `subagent/descriptor` — log-only
@@ -783,6 +831,30 @@ Source: [`packages/subagent/subagent/src/descriptor.ts:38`](https://github.com/d
783
831
 
784
832
  Source: [`packages/subagent/tool-subagent/src/model-selection-state.ts:17`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subagent/tool-subagent/src/model-selection-state.ts)
785
833
 
834
+ ### `system/*`
835
+
836
+ <a id="systemmessage--surface"></a>
837
+
838
+ #### `system/message` — surface
839
+
840
+ ```ts persistence-catalog
841
+ /**
842
+ * The rendered system prompt on the model-visible surface. The loop appends
843
+ * the first one as surface node 0 before the step's first `user/message`.
844
+ * A prepared in-history route can append nonempty changes in a continuing
845
+ * series. An incapable route or new series normalizes text to the first system
846
+ * node. Normalization empties nonempty later nodes, then rewrites the head if
847
+ * needed, through logged per-node replacements. An empty rendering always
848
+ * clears all active system nodes, leaving no older instructions model-visible.
849
+ * Empty later nodes are dormant and project to no message; an empty head with
850
+ * no active later node records "no system prompt". Restored nonempty text follows
851
+ * the same route and series rule; empty nodes never restore older text.
852
+ */
853
+ 'system/message': { turn: number; step: number; message: SystemMessage }
854
+ ```
855
+
856
+ Source: [`packages/core/session/src/types.ts:310`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
857
+
786
858
  ### `team/*`
787
859
 
788
860
  <a id="teammember--log-only"></a>
@@ -874,16 +946,16 @@ Source: [`packages/todo/tool-todo/src/types.ts:31`](https://github.com/deepseek-
874
946
 
875
947
  Types: [ToolCallId](./subsystems/core.md)
876
948
 
877
- Source: [`packages/core/session/src/types.ts:319`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
949
+ Source: [`packages/core/session/src/types.ts:341`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
878
950
 
879
- <a id="toolcode-dispatch--log-only"></a>
951
+ <a id="toolptc-dispatch--log-only"></a>
880
952
 
881
- #### `tool/code-dispatch` — log-only
953
+ #### `tool/ptc-dispatch` — log-only
882
954
 
883
955
  ```ts persistence-catalog
884
956
  /**
885
957
  * One bridged sub-dispatch SETTLING: the pairing ids (matching the
886
- * `tool/code-dispatch-start` with the same `subCallId`), the tool `name`
958
+ * `tool/ptc-dispatch-start` with the same `subCallId`), the tool `name`
887
959
  * with the same JSON-normalized `arguments`, and the sub-call's complete
888
960
  * model-facing outcome in `tool/result`'s own vocabulary
889
961
  * (`content` + `isError`), so UIs render a sub-call through the exact
@@ -896,30 +968,30 @@ Source: [`packages/core/session/src/types.ts:319`](https://github.com/deepseek-a
896
968
  * before returning), so its execution-enclosure relation holds by
897
969
  * construction.
898
970
  */
899
- 'tool/code-dispatch': PtcDispatchEventData
971
+ 'tool/ptc-dispatch': PtcDispatchEventData
900
972
  ```
901
973
 
902
974
  Source: [`packages/core/tools/src/types.ts:56`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/tools/src/types.ts)
903
975
 
904
- <a id="toolcode-dispatch-start--log-only"></a>
976
+ <a id="toolptc-dispatch-start--log-only"></a>
905
977
 
906
- #### `tool/code-dispatch-start` — log-only
978
+ #### `tool/ptc-dispatch-start` — log-only
907
979
 
908
980
  ```ts persistence-catalog
909
981
  /**
910
982
  * One sub-dispatch STARTING inside a `run_code` program: the parent
911
- * `run_code` call id, the deterministic sub-call id (`<parent>:code:<n>`,
912
- * numbered in submission order), and the tool `name` with its
983
+ * `run_code` call id, the opaque sub-call id (new calls use
984
+ * `<parent>:ptc:<n>`, numbered in submission order), and the tool `name` with its
913
985
  * JSON-normalized `arguments` — the exact value dispatched, normalized
914
986
  * BEFORE dispatch, so this append can never fail on payload shape.
915
987
  * Appended when the scheduler actually starts the call (not at
916
988
  * submission), so a start means the tool body pipeline was entered; a
917
989
  * call abandoned in the queue logs nothing. Log-only: `deriveMessages()`
918
990
  * ignores it; UIs use it for live per-sub-call running state and pair it
919
- * with `tool/code-dispatch` by `subCallId` (timing = the two events'
991
+ * with `tool/ptc-dispatch` by `subCallId` (timing = the two events'
920
992
  * `time` fields).
921
993
  */
922
- 'tool/code-dispatch-start': PtcDispatchStartEventData
994
+ 'tool/ptc-dispatch-start': PtcDispatchStartEventData
923
995
  ```
924
996
 
925
997
  Source: [`packages/core/tools/src/types.ts:40`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/tools/src/types.ts)
@@ -944,12 +1016,13 @@ Source: [`packages/core/tools/src/types.ts:40`](https://github.com/deepseek-ai/d
944
1016
  turn: number
945
1017
  step: number
946
1018
  message: ToolResultMessage
1019
+ /** Optional failure identity; allowed only when the tool-result block has `isError: true`. */
947
1020
  error?: { name: string; code: string }
948
1021
  meta?: JsonValue
949
1022
  }
950
1023
  ```
951
1024
 
952
- Source: [`packages/core/session/src/types.ts:331`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
1025
+ Source: [`packages/core/session/src/types.ts:353`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
953
1026
 
954
1027
  ### `tool-workflow/*`
955
1028
 
@@ -1029,7 +1102,7 @@ Source: [`packages/workflow/tool-workflow/src/types.ts:47`](https://github.com/d
1029
1102
 
1030
1103
  Types: [TurnEndReason](./subsystems/session.md)
1031
1104
 
1032
- Source: [`packages/core/session/src/types.ts:276`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
1105
+ Source: [`packages/core/session/src/types.ts:285`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
1033
1106
 
1034
1107
  <a id="turnstart--log-only"></a>
1035
1108
 
@@ -1045,7 +1118,7 @@ Source: [`packages/core/session/src/types.ts:276`](https://github.com/deepseek-a
1045
1118
  'turn/start': { turn: number }
1046
1119
  ```
1047
1120
 
1048
- Source: [`packages/core/session/src/types.ts:267`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
1121
+ Source: [`packages/core/session/src/types.ts:276`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
1049
1122
 
1050
1123
  ### `user/*`
1051
1124
 
@@ -1064,7 +1137,7 @@ Source: [`packages/core/session/src/types.ts:267`](https://github.com/deepseek-a
1064
1137
  'user/message': UserMessage
1065
1138
  ```
1066
1139
 
1067
- Source: [`packages/core/session/src/types.ts:288`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
1140
+ Source: [`packages/core/session/src/types.ts:297`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
1068
1141
 
1069
1142
  ### `web/*`
1070
1143
 
@@ -49,7 +49,7 @@ type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable'
49
49
  type ApprovalPolicy = 'ask' | 'never'
50
50
  ```
51
51
 
52
- Both policies contribute their complete current meaning to the cache-safe runtime-context snapshot. The sourced `user/message` is the durable model-visible input; changing approval state appends a new full snapshot after retained history without rewriting the request header's system prompt.
52
+ Both policies contribute their complete current meaning to the cache-safe runtime-context snapshot. The sourced `user/message` is the durable model-visible input; changing approval state appends a new full snapshot after retained history without touching the `system/message` nodes that hold the rendered system prompt.
53
53
 
54
54
  ## Approval request
55
55
 
@@ -133,6 +133,15 @@ graph(): WebBootGraph
133
133
  */
134
134
  clientPath(id: string): string | undefined
135
135
 
136
+ /**
137
+ * Serve an advertised revisioned bundle or source map without a Web server.
138
+ * Unknown URLs return 404, unsupported methods return 405, and `HEAD`
139
+ * returns the same immutable headers without a body.
140
+ * @param request - shell-carrier request for a `/plugins` resource.
141
+ * @returns the exact response also exposed by the optional Web route.
142
+ */
143
+ fetchBundle(request: Request): Response
144
+
136
145
  /**
137
146
  * Filesystem baseline captured before an entry's current bytes were read.
138
147
  * HMR compares it with the live files when installing a watch, so a write
@@ -0,0 +1,94 @@
1
+ ---
2
+ editSource: "docs/subsystems/client-resources.md"
3
+ outline: [2,3]
4
+ ---
5
+
6
+ # Client Resources
7
+
8
+ The client resource model turns an address into live data for any Web Client component. [`dsh-client-resources`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.md) provides the `ctx.resources` service and the `useResource` global standard hook; a package that owns a kind of content registers one **provider** for its **protocol**, and a component reads the content's current state by **address** without importing the owner's runtime. The right Sidebar's tabs are the model's first consumer ([Right Sidebar](./sidebar-right.md)); the decision record is the [client resource model Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.md).
9
+
10
+ This page is the developer reference: how to write an address, how to register a provider, how to read a resource, what the states and failures mean, and how the model holds and releases a resource.
11
+
12
+ ## Addresses
13
+
14
+ A resource address is a `dsh-resource://<type>/…` URL. The host names the protocol and must be a key of `ResourceProtocolMap`; the path is the protocol's own, and its owner percent-encodes each segment. A protocol that needs a scope puts it in the path: the `file` protocol's addresses read `dsh-resource://file/session/<sessionId>/<path>`, where path is workspace-relative or absolute with its leading slashes preserved, built with `fileAddressFor(sessionId, cwd, path)` and read back with `parseFileAddress(address)` from [`dsh-util-workspace-path`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/util/workspace-path/README.md). The model itself reads only the scheme and the host: `protocolOf(address)` returns the lower-cased host of a `dsh-resource://` URL and `undefined` for anything else. Addresses under any other scheme — the Sidebar's `sidebar://guide` — name no resource and read as `none`.
15
+
16
+ | Address | Protocol key | Reads as |
17
+ |---|---|---|
18
+ | `dsh-resource://file/session/s1/notes/a.md` | `file` | the metadata of `notes/a.md` under session `s1`'s workspace root, when the `file` provider is registered |
19
+ | `dsh-resource://file/absolute/home/me/notes.md` | `file` | parseable but fails with `workspace-file/unknown-workspace`: no authorizing Session, and neither current nor Tab Session is borrowed |
20
+ | `DSH-RESOURCE://File/session/s1/a` | `file` | a distinct record: addresses compare as strings, and `openResource` accepts only the canonical lower-case spelling that `fileAddressFor` emits |
21
+ | `sidebar://guide` | — | `none`: a navigation address |
22
+ | `/home/me/notes.md` | — | `none`: not a URL |
23
+
24
+ ## Registering a provider
25
+
26
+ The owner of a protocol declares its value type on `ResourceProtocolMap` and registers one provider inside its own `ctx.effect`, so the protocol lives exactly as long as the plugin ([provide a protocol](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.md#provide-a-protocol)). `open(address, { signal })` returns a stream of `RemoteResult` frames — the current state first, then one frame per change — and must stop when `signal` aborts. A failure is an `ok: false` frame carrying a `RemoteFailure`; a throw inside the stream is a programming error and is not caught.
27
+
28
+ ```ts ignore-check
29
+ import type { Context } from '@deepseek-ai/cordis'
30
+ import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol'
31
+ import type {} from '@deepseek-ai/dsh-client-resources/client'
32
+
33
+ interface NoteView { readonly title: string; readonly updatedAt: string }
34
+
35
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
36
+ interface ResourceProtocolMap { note: NoteView }
37
+ }
38
+
39
+ export const inject = ['resources', 'remote']
40
+
41
+ export function apply(ctx: Context): void {
42
+ ctx.effect(() => ctx.resources.register<'note'>({
43
+ protocol: 'note',
44
+ async *open(address, { signal }): AsyncIterable<RemoteResult<NoteView>> {
45
+ const id = new URL(address).pathname.slice(1)
46
+ yield await ctx.remote.notes.read(id, signal)
47
+ for await (const change of ctx.remote.notes.follow(id, signal)) yield change
48
+ },
49
+ }), 'my-notes: note resource provider')
50
+ }
51
+ ```
52
+
53
+ A protocol has exactly one provider; a second registration throws. Registering while addresses of the protocol are already held opens their streams at once; disposing the provider ends those streams and the addresses read `none` until a provider returns.
54
+
55
+ ## Reading a resource
56
+
57
+ Every slot component receives `useResource` in its props, whatever its scope ([Slots](./slots.md)). `useResource<P>(address)` names the protocol as the type argument and returns the address's current snapshot; subscribing is what holds the resource open, and a component that mounts while another holder keeps the resource alive reads the latest value at once without reopening the stream ([read a resource](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.md#read-a-resource)).
58
+
59
+ | `status` | Meaning | `value` | `failure` |
60
+ |---|---|---|---|
61
+ | `none` | No provider is registered for the address's protocol, or the address is not a resource address | `undefined` | `undefined` |
62
+ | `loading` | The provider's stream is open and has not yielded yet | `undefined` | `undefined` |
63
+ | `live` | The latest frame succeeded | the latest `ok` value | `undefined` |
64
+ | `failed` | The latest frame reported a failure | the last `ok` value, kept | the frame's `RemoteFailure` |
65
+
66
+ ```tsx ignore-check
67
+ import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
68
+ import type {} from '@deepseek-ai/dsh-api-workspace-files/client'
69
+
70
+ type Props = PropsRuntime<'sidebar.right.pane.tab'>
71
+
72
+ export function FileHeader({ useTabInfo, useResource, t }: Props) {
73
+ const { tab } = useTabInfo()
74
+ const meta = useResource<'file'>(tab.contentId)
75
+ if (meta.status === 'failed') return <p role="alert">{t('failed', { code: meta.failure.code })}</p>
76
+ return (
77
+ <header>
78
+ {tab.title}
79
+ </header>
80
+ )
81
+ }
82
+ ```
83
+
84
+ A consumer presents `failed` itself: the model keeps the last value beside the failure so a body can show stale content with a notice rather than a blank, and the next `ok` frame clears the failure. Nothing in the model produces user-visible text.
85
+
86
+ ## Holding and releasing
87
+
88
+ A resource is alive while it has a holder: a subscribed `useResource`, or a pin. `ctx.resources.pin(address, signal)` keeps a resource open without subscribing until `signal` aborts, and an already-aborted signal pins nothing; the right Sidebar pins every open tab record's address for the record's life, so switching tabs unmounts a body without closing its stream. The first holder opens the provider's stream; the last release aborts it, discards the value, and returns the snapshot to `loading` (provider present) or `none` (absent). A frame the provider yields after that release is dropped, and the iterator is returned. `ctx.resources.source(address)` is the bare observable behind the hook, reference-stable per address, for callers outside React; reading its snapshot does not hold the resource ([lifecycle](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.md#lifecycle)).
89
+
90
+ Streams carry metadata, not content. The `file` provider's value is `WorkspaceFileStat { absolutePath, version, bytes? }`: the first frame comes from Host `stat`, and later observations update the version. A consumer reads content through the Workspace Files Remote namespace; Preview owns refresh independently per tab ([`dsh-api-workspace-files`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.md)).
91
+
92
+ ## Limits
93
+
94
+ Records live for the page lifetime: an address's record stays after its last holder leaves, holding no stream and no value, so memory grows with the number of distinct addresses ever read. A provider that ignores `signal` keeps running until its next frame. The failure type is the Remote face's `RemoteFailure`, so a provider whose source is not a Remote call mints one. A misspelled protocol or a malformed address reads as `none` with no other diagnostic.
@@ -11,7 +11,7 @@ Source: [`packages/compaction/compaction/src/types.ts`](https://github.com/deeps
11
11
 
12
12
  ## The `compaction/*` session events
13
13
 
14
- Compaction extends [`SessionEventMap`](./session.md) with three event types via declaration merging. All three are **log-only** — they record the lock, summary, selected range, shadowed event seqs, token count, and model call without joining the surface. `SurfaceEventType` is deliberately NOT extended (only message-producing events reach the model), so the summary itself rides on a separate `user/message` with `surfaceOp: { op: 'replace', start, end }` — the only surface mutation performed by summary compaction. The [Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md) owns the rationale for reusing `user/message`.
14
+ Compaction extends [`SessionEventMap`](./session.md) with three event types via declaration merging. All three are **log-only** — they record the lock, summary, selected range, shadowed event seqs, token count, and model call without joining the surface. `SurfaceEventType` is deliberately NOT extended (only message-producing events reach the model), so the summary itself rides on a separate `user/message` with `surfaceOp: { op: 'replace', startSeq, endSeq }` — the only surface mutation performed by summary compaction. The [Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md) owns the rationale for reusing `user/message`.
15
15
 
16
16
  | Event | Payload | Role |
17
17
  |---|---|---|