dsh-plugin-dev-kb 1.0.8 → 1.1.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.
- package/CHANGELOG.md +21 -0
- package/README.en.md +6 -6
- package/README.md +6 -6
- package/kb/INDEX.md +21 -5
- package/kb/README.md +11 -10
- package/kb/extra/AGENTS.md +4 -4
- package/kb/extra/cookbook/adding-a-remote-api.md +197 -0
- package/kb/extra/cookbook/adding-a-remote-api.zh.md +197 -0
- package/kb/extra/cookbook/adding-a-vendored-package.md +2 -2
- package/kb/extra/cookbook/adding-a-vendored-package.zh.md +2 -2
- package/kb/extra/deepseek-llm-api-wire-extensions.md +163 -0
- package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +163 -0
- package/kb/extra/development.md +8 -14
- package/kb/extra/development.zh.md +8 -14
- package/kb/extra/event-producer-consumer.md +55 -48
- package/kb/extra/event-producer-consumer.zh.md +58 -51
- package/kb/extra/glossary.md +1 -1
- package/kb/extra/glossary.zh.md +1 -1
- package/kb/extra/graph-atlas.md +0 -2
- package/kb/extra/graph-atlas.zh.md +0 -2
- package/kb/extra/i18n/README.md +4 -4
- package/kb/extra/i18n/README.zh.md +4 -4
- package/kb/extra/i18n/style-samples.md +2 -2
- package/kb/extra/module-graph.md +646 -926
- package/kb/extra/module-graph.zh.md +648 -928
- package/kb/extra/postmortem/0001-acp-default-export-drops-inject.md +2 -2
- package/kb/extra/postmortem/0001-acp-default-export-drops-inject.zh.md +2 -2
- package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.md +2 -2
- package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +2 -2
- package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.md +2 -2
- package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.zh.md +2 -2
- package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +1 -1
- package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +1 -1
- package/kb/extra/rescope.md +2 -2
- package/kb/extra/rescope.zh.md +2 -2
- package/kb/extra/subsystems/agent-team.md +28 -4
- package/kb/extra/subsystems/agent-team.zh.md +28 -4
- package/kb/extra/subsystems/attachment.md +168 -7
- package/kb/extra/subsystems/attachment.zh.md +168 -7
- package/kb/extra/subsystems/extensions.md +18 -0
- package/kb/extra/subsystems/extensions.zh.md +18 -0
- package/kb/extra/subsystems/feedback.md +4 -4
- package/kb/extra/subsystems/feedback.zh.md +4 -4
- package/kb/extra/subsystems/todo.md +32 -0
- package/kb/extra/subsystems/todo.zh.md +32 -0
- package/kb/extra/subsystems/webhook.md +70 -0
- package/kb/extra/subsystems/webhook.zh.md +70 -0
- package/kb/extra/testing.md +15 -10
- package/kb/extra/testing.zh.md +13 -8
- package/kb/extra/web-styling.md +4 -0
- package/kb/extra/web-styling.zh.md +4 -0
- package/kb/meta/search-index.json +309 -177
- package/kb/meta/site-pages.txt +183 -167
- package/kb/meta/source.json +5 -5
- package/kb/meta/topics.md +14 -6
- package/kb/site/develop/basic/publish.md +2 -2
- package/kb/site/develop/basic/tool.md +1 -1
- package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +5 -4
- package/kb/site/develop/framework/events.md +1 -1
- package/kb/site/develop/practice/dynamic-cordis.md +17 -0
- package/kb/site/develop/practice/llm-adapter.md +4 -3
- package/kb/site/en/develop/basic/publish.md +2 -2
- package/kb/site/en/develop/basic/tool.md +1 -1
- package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +5 -4
- package/kb/site/en/develop/framework/events.md +1 -1
- package/kb/site/en/develop/practice/dynamic-cordis.md +17 -0
- package/kb/site/en/develop/practice/llm-adapter.md +4 -3
- package/kb/site/en/guide/github-review.md +104 -0
- package/kb/site/en/guide/mcp-memory.md +103 -0
- package/kb/site/en/guide/network-proxy.md +87 -0
- package/kb/site/en/guide/providers.md +70 -17
- package/kb/site/en/guide/python-sdk.md +80 -34
- package/kb/site/en/guide/schedule.md +23 -0
- package/kb/site/en/reference/agent-lifecycle.md +6 -4
- package/kb/{extra → site/en/reference}/api-gateway.md +12 -10
- package/kb/site/en/reference/capability-seams.md +128 -73
- package/kb/site/en/reference/config-catalog.md +481 -360
- package/kb/site/en/reference/cookbook/adding-a-package.md +3 -4
- package/kb/site/en/reference/cookbook/adding-a-settings-card.md +12 -10
- package/kb/site/en/reference/cookbook/adding-a-tool.md +11 -4
- package/kb/site/en/reference/cookbook/adding-an-llm-adapter.md +1 -1
- package/kb/site/en/reference/cookbook/extension-cookbook.md +20 -17
- package/kb/site/en/reference/cordis-api/inherited.md +1 -1
- package/kb/site/en/reference/cordis-primer.md +2 -1
- package/kb/site/en/reference/index.md +30 -11
- package/kb/site/en/reference/persistence-catalog.md +148 -80
- package/kb/site/en/reference/subsystems/approval.md +10 -10
- package/kb/site/en/reference/subsystems/client-modules.md +58 -16
- package/kb/site/en/reference/subsystems/code-runtime.md +10 -6
- package/kb/site/en/reference/subsystems/commands.md +25 -16
- package/kb/site/en/reference/subsystems/compaction.md +11 -11
- package/kb/site/en/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md} +50 -24
- package/kb/site/en/reference/subsystems/core.md +156 -17
- package/kb/site/en/reference/subsystems/credentials.md +44 -3
- package/kb/site/en/reference/subsystems/filesystem.md +12 -2
- package/kb/site/en/reference/subsystems/goal.md +1 -1
- package/kb/site/en/reference/subsystems/index.md +7 -2
- package/kb/site/en/reference/subsystems/jobs.md +1 -1
- package/kb/site/en/reference/subsystems/llm-streaming.md +154 -12
- package/kb/site/en/reference/subsystems/permission-presets.md +6 -6
- package/kb/site/en/reference/subsystems/persistence.md +185 -175
- package/kb/site/en/reference/subsystems/plan.md +2 -2
- package/kb/site/en/reference/subsystems/sandbox.md +2 -0
- package/kb/site/en/reference/subsystems/schedule.md +9 -3
- package/kb/site/en/reference/subsystems/session-projection.md +115 -48
- package/kb/site/en/reference/subsystems/session-query.md +28 -14
- package/kb/site/en/reference/subsystems/session-reference.md +53 -8
- package/kb/site/en/reference/subsystems/session-telemetry.md +8 -8
- package/kb/site/en/reference/subsystems/session-title.md +6 -6
- package/kb/site/en/reference/subsystems/session.md +401 -99
- package/kb/site/en/reference/subsystems/settings.md +101 -6
- package/kb/site/en/reference/subsystems/skills.md +23 -0
- package/kb/site/en/reference/subsystems/slots.md +178 -0
- package/kb/site/en/reference/subsystems/spill.md +2 -2
- package/kb/site/en/reference/subsystems/storage.md +34 -3
- package/kb/site/en/reference/subsystems/subagent.md +122 -109
- package/kb/site/en/reference/subsystems/system-prompt.md +17 -4
- package/kb/site/en/reference/subsystems/token-meter.md +27 -12
- package/kb/site/en/reference/subsystems/tools.md +39 -39
- package/kb/site/en/reference/subsystems/typert.md +62 -55
- package/kb/site/en/reference/subsystems/user-questions.md +33 -33
- package/kb/site/en/reference/subsystems/web-client.md +98 -0
- package/kb/site/en/reference/subsystems/web-server.md +11 -5
- package/kb/site/en/reference/subsystems/web.md +7 -1
- package/kb/site/en/reference/subsystems/workspace.md +102 -9
- package/kb/site/en/reference/tool-catalog.md +86 -82
- package/kb/site/en/reference/tool-execution-pipeline.md +1 -1
- package/kb/site/guide/github-review.md +104 -0
- package/kb/site/guide/mcp-memory.md +103 -0
- package/kb/site/guide/network-proxy.md +87 -0
- package/kb/site/guide/providers.md +70 -17
- package/kb/site/guide/python-sdk.md +87 -41
- package/kb/site/guide/schedule.md +23 -0
- package/kb/site/reference/agent-lifecycle.md +6 -4
- package/kb/{extra/api-gateway.zh.md → site/reference/api-gateway.md} +12 -10
- package/kb/site/reference/capability-seams.md +128 -73
- package/kb/site/reference/config-catalog.md +481 -360
- package/kb/site/reference/cookbook/adding-a-package.md +3 -4
- package/kb/site/reference/cookbook/adding-a-settings-card.md +12 -10
- package/kb/site/reference/cookbook/adding-a-tool.md +11 -4
- package/kb/site/reference/cookbook/adding-an-llm-adapter.md +1 -1
- package/kb/site/reference/cookbook/extension-cookbook.md +20 -17
- package/kb/site/reference/cordis-api/inherited.md +1 -1
- package/kb/site/reference/cordis-primer.md +2 -1
- package/kb/site/reference/index.md +30 -11
- package/kb/site/reference/persistence-catalog.md +148 -80
- package/kb/site/reference/subsystems/approval.md +10 -10
- package/kb/site/reference/subsystems/client-modules.md +58 -16
- package/kb/site/reference/subsystems/code-runtime.md +10 -6
- package/kb/site/reference/subsystems/commands.md +25 -16
- package/kb/site/reference/subsystems/compaction.md +11 -11
- package/kb/site/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md} +50 -24
- package/kb/site/reference/subsystems/core.md +156 -17
- package/kb/site/reference/subsystems/credentials.md +44 -3
- package/kb/site/reference/subsystems/filesystem.md +12 -2
- package/kb/site/reference/subsystems/goal.md +1 -1
- package/kb/site/reference/subsystems/index.md +7 -2
- package/kb/site/reference/subsystems/jobs.md +1 -1
- package/kb/site/reference/subsystems/llm-streaming.md +154 -12
- package/kb/site/reference/subsystems/permission-presets.md +5 -5
- package/kb/site/reference/subsystems/persistence.md +184 -174
- package/kb/site/reference/subsystems/plan.md +2 -2
- package/kb/site/reference/subsystems/schedule.md +9 -3
- package/kb/site/reference/subsystems/session-projection.md +115 -48
- package/kb/site/reference/subsystems/session-query.md +28 -14
- package/kb/site/reference/subsystems/session-reference.md +53 -8
- package/kb/site/reference/subsystems/session-telemetry.md +8 -8
- package/kb/site/reference/subsystems/session-title.md +6 -6
- package/kb/site/reference/subsystems/session.md +401 -99
- package/kb/site/reference/subsystems/settings.md +101 -6
- package/kb/site/reference/subsystems/skills.md +23 -0
- package/kb/site/reference/subsystems/slots.md +178 -0
- package/kb/site/reference/subsystems/spill.md +2 -2
- package/kb/site/reference/subsystems/storage.md +34 -3
- package/kb/site/reference/subsystems/subagent.md +122 -109
- package/kb/site/reference/subsystems/system-prompt.md +17 -4
- package/kb/site/reference/subsystems/token-meter.md +27 -12
- package/kb/site/reference/subsystems/tools.md +39 -39
- package/kb/site/reference/subsystems/typert.md +62 -55
- package/kb/site/reference/subsystems/user-questions.md +33 -33
- package/kb/site/reference/subsystems/web-client.md +98 -0
- package/kb/site/reference/subsystems/web-server.md +11 -5
- package/kb/site/reference/subsystems/web.md +7 -1
- package/kb/site/reference/subsystems/workspace.md +102 -9
- package/kb/site/reference/tool-catalog.md +85 -81
- package/kb/site/reference/tool-execution-pipeline.md +1 -1
- package/package.json +2 -2
- package/skills/dsh-plugin-dev-kb.md +8 -6
|
@@ -33,7 +33,7 @@ type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable'
|
|
|
33
33
|
|
|
34
34
|
## Per-session policy
|
|
35
35
|
|
|
36
|
-
`ApprovalPolicy` determines what happens before interactive answerers run. `ask` delegates to the composed answerer chain, whose no-answer default is `unavailable`; `never` deterministically returns `rejected` without dispatching any answerer. The effective value is the last `approval/policy` event in the session log, falling back to the service config. `setApprovalPolicy(session, policy)` is the single write path, so replay reconstructs the override.
|
|
36
|
+
`ApprovalPolicy` determines what happens before interactive answerers run. `ask` delegates to the composed answerer chain, whose no-answer default is `unavailable`; `never` deterministically returns `rejected` without dispatching any answerer. The effective value is the last `approval/policy` event in the session log, falling back to the service config. Consumers read it with `ctx.approval.effectivePolicy(session)`; `setApprovalPolicy(session, policy)` is the single write path, so replay reconstructs the override.
|
|
37
37
|
|
|
38
38
|
```ts type-equiv
|
|
39
39
|
/**
|
|
@@ -60,7 +60,7 @@ Both policies contribute their complete current meaning to the cache-safe runtim
|
|
|
60
60
|
* Readonly same-process permission question. `callId` links to an already
|
|
61
61
|
* presented tool call, so arguments are not duplicated here.
|
|
62
62
|
*/
|
|
63
|
-
interface ApprovalRequest {
|
|
63
|
+
interface ApprovalRequest extends ApprovalRequestEvent {
|
|
64
64
|
/**
|
|
65
65
|
* The agent on whose behalf the question is asked. Routes the question (a
|
|
66
66
|
* UI answerer only answers for agents it owns) and receives the audit
|
|
@@ -73,7 +73,7 @@ interface ApprovalRequest {
|
|
|
73
73
|
* The exact tool call being decided, when the asker has one — lets a UI
|
|
74
74
|
* attach the prompt to the tool call it already streamed.
|
|
75
75
|
*/
|
|
76
|
-
readonly callId?:
|
|
76
|
+
readonly callId?: ToolCallId
|
|
77
77
|
/** The asker's human-readable explanation of WHY it is asking. */
|
|
78
78
|
readonly reason?: string
|
|
79
79
|
/**
|
|
@@ -154,20 +154,20 @@ Source: [`packages/interaction/user-approval/src/index.ts`](https://github.com/d
|
|
|
154
154
|
|
|
155
155
|
#### `approval/request` — waterfall
|
|
156
156
|
|
|
157
|
-
Ask composed answerers for one decision. Return an outcome to claim the request or call `next()
|
|
157
|
+
Ask composed answerers for one decision. Return an outcome to claim the request or call `next()` to delegate. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
|
158
158
|
|
|
159
159
|
```ts cordis-catalog
|
|
160
160
|
/**
|
|
161
161
|
* Ask composed answerers for one decision. Return an outcome to claim the
|
|
162
|
-
* request or call `next()
|
|
163
|
-
*
|
|
164
|
-
* @param req -
|
|
162
|
+
* request or call `next()` to delegate. Scope-filtered dispatch
|
|
163
|
+
* (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
|
164
|
+
* @param req - pending approval request.
|
|
165
165
|
* @mode waterfall
|
|
166
166
|
*/
|
|
167
|
-
'approval/request'(this: Scoped<
|
|
167
|
+
'approval/request'( this: Scoped<Agent>, req: ApprovalRequestEvent, next: () => Promise<ApprovalOutcome>, ): Promise<ApprovalOutcome>
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
Types: [Scoped](./scope.md)
|
|
170
|
+
Types: [Agent](./core.md) · [Scoped](./scope.md)
|
|
171
171
|
|
|
172
|
-
Source: [`packages/interaction/user-approval/src/
|
|
172
|
+
Source: [`packages/interaction/user-approval/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/interaction/user-approval/src/types.ts)
|
|
173
173
|
<!-- END GENERATED cordis-surface -->
|
|
@@ -5,32 +5,31 @@ outline: [2,3]
|
|
|
5
5
|
|
|
6
6
|
# Client Modules
|
|
7
7
|
|
|
8
|
-
The web plugin table: the Node half of the client module system in [dsh-client-modules](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/modules), provided as `ctx.clientModules` (`ClientModuleRegistry`). It scans the host Loader's entries for packages declaring `dsh.client`, composes the `window.__DSH_BOOT__` entry graph, serves
|
|
8
|
+
The web plugin table: the Node half of the client module system in [dsh-client-modules](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/modules), provided as `ctx.clientModules` (`ClientModuleRegistry`). It scans the host Loader's entries for packages declaring `dsh.client`, composes the `window.__DSH_BOOT__` entry graph, serves versioned one-or-more-resource combo scripts under `/plugins`, and answers every index-injection collection with the boot protocol rows — the four faces of one service. It is an optional capability of the web GUI stack, not part of the agent-loop spine, and it is a consumer of [dsh-host-webserver](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/host/webserver): the carrier described in [web-server.md](./web-server.md) supplies the prefix route and the `webserver/index-inject` event this service answers. The same package's browser half (`ctx.modules`, the lazy-CJS module table that fetches and materializes these bundles) is kernel machinery documented in the [package README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/modules/README.md), not here.
|
|
9
9
|
|
|
10
10
|
Source: [`packages/client/modules/src/client/manifest.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/modules/src/client/manifest.ts)
|
|
11
11
|
|
|
12
12
|
## The wire
|
|
13
13
|
|
|
14
|
-
The graph is the wire single source between the Node and browser halves
|
|
14
|
+
The graph is the wire single source between the Node and browser halves. The host composes `WebBootEntry` rows and `WebBootBatch` descriptors from scanned packages, then contributes the registration facade, application preloads, bootstrap scripts, and graph global to the structured index-injection table before the Vite entry. The `global` row renders as `globalThis["__DSH_BOOT__"]` with `<` escaped so plugin-controlled strings cannot break out of the script element. A page without a valid manifest cannot boot: the browser parser rejects malformed rows or batches, unknown members, and entries without exactly one initial combo descriptor.
|
|
15
15
|
|
|
16
16
|
```ts type-equiv
|
|
17
17
|
/**
|
|
18
18
|
* One composed client entry pushed by the host (a graph row). Wire
|
|
19
19
|
* single source: the host node half (package root) produces this same shape.
|
|
20
|
-
* `immediately` marks stage-one prefetch
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `require` is synchronous (see {@link WebBootGraph.entries}).
|
|
20
|
+
* `immediately` marks stage-one prefetch. `inject` names package rows whose
|
|
21
|
+
* factories must arrive before this row materializes, while Cordis separately
|
|
22
|
+
* uses the same package edges to compose entries. `external` carries exact
|
|
23
|
+
* non-inject module requests (see {@link WebBootGraph.entries}).
|
|
25
24
|
*/
|
|
26
25
|
interface WebBootEntry {
|
|
27
26
|
/** Entry name == package name. */
|
|
28
27
|
id: string
|
|
29
|
-
/**
|
|
28
|
+
/** Revisioned single-resource combo endpoint used by HMR. */
|
|
30
29
|
url: string
|
|
31
|
-
/**
|
|
30
|
+
/** Opaque plugin-artifact revision used for HMR cache busting. */
|
|
32
31
|
rev: string
|
|
33
|
-
/** Package-name dependency edges
|
|
32
|
+
/** Package-name dependency edges used for factory arrival and plugin composition. */
|
|
34
33
|
inject?: string[]
|
|
35
34
|
/** Stage-one prefetch mark: load the script for factory registration during module-face boot. */
|
|
36
35
|
immediately?: boolean
|
|
@@ -39,6 +38,25 @@ interface WebBootEntry {
|
|
|
39
38
|
}
|
|
40
39
|
```
|
|
41
40
|
|
|
41
|
+
```ts type-equiv
|
|
42
|
+
/** Initial scheduling phase for one content-addressed combo script. */
|
|
43
|
+
type WebBootBatchPhase = 'bootstrap' | 'application'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```ts type-equiv
|
|
47
|
+
/** One initial combo script; a scheduling phase may span several descriptors. */
|
|
48
|
+
interface WebBootBatch {
|
|
49
|
+
/** Parser-blocking bootstrap or preloaded application scheduling. */
|
|
50
|
+
phase: WebBootBatchPhase
|
|
51
|
+
/** Content-addressed combo script endpoint. */
|
|
52
|
+
url: string
|
|
53
|
+
/** Revision over the combined plugin script bytes and indexed source map. */
|
|
54
|
+
rev: string
|
|
55
|
+
/** Graph entry ids whose factories the script registers, in execution order. */
|
|
56
|
+
entries: string[]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
42
60
|
```ts type-equiv
|
|
43
61
|
/** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */
|
|
44
62
|
interface WebBootGraph {
|
|
@@ -50,28 +68,42 @@ interface WebBootGraph {
|
|
|
50
68
|
* unrelated and remains owned by fiber service waiting.
|
|
51
69
|
*/
|
|
52
70
|
entries: WebBootEntry[]
|
|
71
|
+
/** Initial combo descriptors; every entry belongs to exactly one descriptor. */
|
|
72
|
+
batches: WebBootBatch[]
|
|
53
73
|
}
|
|
54
74
|
```
|
|
55
75
|
|
|
56
|
-
Each row's `rev` is
|
|
76
|
+
Each initial row's `rev` is an opaque process nonce plus sequence, so graph composition does not hash every plugin artifact. After HMR observes a change, that row's revision becomes the hash of its new bundle and available source map. The initial descriptors partition rows into bootstrap and application scheduling phases, and either phase may contain several descriptors. Their URLs contain only the ordered package-resource list and revision; phase names do not enter the route. Graph composition preserves row order while greedily splitting before the map-form URL exceeds 3 KiB. Startup combo revisions hash the combined plugin script bytes and indexed source map, and the graph revision hashes both rows and descriptors. `immediately` marks the stage-one registration barrier; rows within one combo share its script transport, while separate combos load independently.
|
|
57
77
|
|
|
58
78
|
## The scan
|
|
59
79
|
|
|
60
|
-
A package joins the table by declaring `dsh.client` (`platform: 'web'`, optional `inject` edges, optional `immediately`) in its package.json and exporting its built bundle at `exports["./client"]`.
|
|
80
|
+
A package joins the table by declaring `dsh.client` (`platform: 'web'`, optional `inject` edges, optional `immediately`) in its package.json and exporting its built bundle at `exports["./client"]`. Each live row resolves from its own Loader specifier and owning-tree `baseUrl`, through the same `loader.internal.resolveSync` implementation that imports its Host face when available. The nearest owning package manifest supplies the browser module id, so relative source and built overlays retain the package identity. Distinct active Loader sources resolving to one package name fail composition; after one source unloads, the surviving source supplies the row without a fiber restart.
|
|
61
81
|
|
|
62
82
|
Scanning is incremental per package; there is no full-rescan code path. Every cordis `internal/plugin` emission (fiber construction or disposal) marks the fiber's entry name dirty, and a microtask flush reconciles each dirty name against the live loader entries. The activation pass seeds the same dirty set with all current entries and flushes synchronously, so first scan and steady state share one implementation — with opposite failure postures. At activation, a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud `AggregateError` listing every broken package: the fiber FAILS and the boot's fail-loud sweep reports it. In steady state, a broken package logs a warning and must not poison the others.
|
|
63
83
|
|
|
64
|
-
Package metadata — including the negative "not a client package" verdict — is cached per
|
|
84
|
+
Package metadata — including the negative "not a client package" verdict — is cached per Loader specifier and owning-tree base URL until restart. A fiber restart from the same source reuses its row and rev untouched; bundle content changes reach the graph only through `rebuilt()`.
|
|
65
85
|
|
|
66
86
|
## The bundle route and index injection
|
|
67
87
|
|
|
68
|
-
`GET`/`HEAD /plugins
|
|
88
|
+
`GET`/`HEAD /plugins/??<package-a>/client.js,<package-b>/client.js&rev=<rev>` serves an exact generated combo script; a one-resource request uses the same form and is the HMR path. Its absolute `sourceMappingURL` changes every resource suffix in parallel, yielding `/plugins/??<package-a>/client.js.map,<package-b>/client.js.map&rev=<rev>`. The map is Indexed Source Map v3 even for one resource. An authored component map supplies its section; a component without one receives an identity section whose `sourcesContent` is the generated bundle and whose source name is its packaged `sourceURL` or plugin route. Every startup request URL is at most 3 KiB measured as UTF-8 bytes; partitioning uses the longer map form. All application URLs are preloaded, and all bootstrap URLs execute before the graph global and Vite entry. All advertised responses use long-lived immutable caching. Unknown or altered resource lists, missing revisions, and stale revisions answer 404 rather than serving different bytes or letting the SPA fallback return HTML as JavaScript; other methods are 405. The injection rows carry the current graph on every index render, so a reload always boots against the live composition.
|
|
69
89
|
|
|
70
90
|
## The service
|
|
71
91
|
|
|
72
|
-
|
|
92
|
+
```ts type-equiv
|
|
93
|
+
/** Filesystem baseline captured before a client artifact snapshot is read. */
|
|
94
|
+
interface ClientArtifactBaseline {
|
|
95
|
+
/** Absolute path of the client bundle. */
|
|
96
|
+
readonly path: string
|
|
97
|
+
/** Bundle modification time in milliseconds. */
|
|
98
|
+
readonly mtimeMs: number
|
|
99
|
+
/** Bundle size in bytes. */
|
|
100
|
+
readonly size: number
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`ClientModuleRegistry` (`ctx.clientModules`, defined in [`packages/client/modules/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/modules/src/index.ts)) exposes reads and the rebuild face; signatures are in the generated [service catalog](#ctxclientmodules--clientmoduleregistry). `graph()` returns the current composed graph (a stable object between changes), `clientPath(id)` returns the bundle's absolute path, and `artifactBaseline(id)` returns the bundle stat values captured before the current snapshot was read. `rebuilt(id)` is the only entry point through which changed bundle content reaches the graph: it re-hashes the bundle together with its current source map, and only a real rev change recomposes the graph and notifies. `onRebuilt` fires per changed bundle with the new rev; `onGraphChanged` fires after any flush that recomposed the graph (row added or removed, or a rebuilt rev change) and is pull-model — listeners re-read `graph()`. Both notification paths contain listener exceptions so one throwing subscriber cannot skip later subscribers or kill whatever triggered the flush.
|
|
73
105
|
|
|
74
|
-
In development, [dsh-client-hmr](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/hmr/README.md) is the registry's watch driver: its node half stat-polls every graph row's bundle from
|
|
106
|
+
In development, [dsh-client-hmr](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/hmr/README.md) is the registry's watch driver: its node half stat-polls every graph row's bundle from the module host's pre-read baseline, calls `rebuilt(id)` only for a changed or dirty row, resyncs its watch set through `onGraphChanged`, and broadcasts rev changes to the browser half over SSE. Source-map changes alone do not trigger a reload; the current map joins the snapshot when a bundle change does. Production graphs omit the HMR row entirely; the module host itself never watches files.
|
|
75
107
|
|
|
76
108
|
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
|
77
109
|
|
|
@@ -101,6 +133,16 @@ graph(): WebBootGraph
|
|
|
101
133
|
*/
|
|
102
134
|
clientPath(id: string): string | undefined
|
|
103
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Filesystem baseline captured before an entry's current bytes were read.
|
|
138
|
+
* HMR compares it with the live files when installing a watch, so a write
|
|
139
|
+
* between startup composition and watch installation cannot disappear into
|
|
140
|
+
* the watcher's initial state.
|
|
141
|
+
* @param id - entry id (package name).
|
|
142
|
+
* @returns the path and baseline, or undefined for an unknown id.
|
|
143
|
+
*/
|
|
144
|
+
artifactBaseline(id: string): ClientArtifactBaseline | undefined
|
|
145
|
+
|
|
104
146
|
/**
|
|
105
147
|
* Re-hash one bundle (the HMR watch's registration hook — the only entry
|
|
106
148
|
* point through which bundle content changes reach the graph).
|
|
@@ -5,7 +5,7 @@ outline: [2,3]
|
|
|
5
5
|
|
|
6
6
|
# Code Runtime
|
|
7
7
|
|
|
8
|
-
The code-execution seam — a [capability seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md) whose Service Definition ([dsh-code-runtime](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/code-runtime/code-runtime), `ctx.codeRuntime`) runs one model-written program against host-provided async bindings and reports what it printed and returned. Code execution is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](./core.md). Backends differ by execution substrate and source language, both readonly descriptors on the service; the worker-thread Service Provider and tool-registry Consumer are specified by the [
|
|
8
|
+
The code-execution seam — a [capability seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md) whose Service Definition ([dsh-code-runtime](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/code-runtime/code-runtime), `ctx.codeRuntime`) runs one model-written program against host-provided async bindings and reports what it printed and returned. Code execution is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](./core.md). Backends differ by execution substrate and source language, both readonly descriptors on the service; the worker-thread Service Provider and tool-registry Consumer are specified by the [PTC mode foundation](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-06-15-ptc.md) and [typed-return contract](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.md).
|
|
9
9
|
|
|
10
10
|
Source: [`packages/code-runtime/code-runtime/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/code-runtime/code-runtime/src/types.ts)
|
|
11
11
|
|
|
@@ -55,7 +55,11 @@ interface CodeRunResult {
|
|
|
55
55
|
* rendered string; a failed or value-less run leaves this absent.
|
|
56
56
|
*/
|
|
57
57
|
value?: CodeJsonValue
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Captured text. Each source channel preserves emission order; interleaving
|
|
60
|
+
* across independent channels is backend-dependent. Bounded only as part of
|
|
61
|
+
* the outer result.
|
|
62
|
+
*/
|
|
59
63
|
logs: string[]
|
|
60
64
|
/** Present iff the run failed; see {@link CodeRunFailure} for the taxonomy. */
|
|
61
65
|
error?: CodeRunFailure
|
|
@@ -64,7 +68,7 @@ interface CodeRunResult {
|
|
|
64
68
|
|
|
65
69
|
## Bindings: host functions as program globals
|
|
66
70
|
|
|
67
|
-
Each `CodeBindingNamespace` becomes one global object of async callables inside the program (the
|
|
71
|
+
Each `CodeBindingNamespace` becomes one global object of async callables inside the program (the PTC mode consumer passes one: `tools`). Arguments and resolutions must be lossless JSON and cross without a seam-level byte cap; the runtime may bridge them through structured clone. A namespace may declare a program-visible error class without making the runtime know the consumer's names: the runtime injects the real constructor and turns rejected calls into its instances. A runtime also treats binding names as hostile input (`__proto__` is an ordinary own property, never a prototype collision):
|
|
68
72
|
|
|
69
73
|
```ts type-equiv
|
|
70
74
|
/**
|
|
@@ -72,7 +76,7 @@ Each `CodeBindingNamespace` becomes one global object of async callables inside
|
|
|
72
76
|
* injects a real error constructor under `name`; rejected member calls become
|
|
73
77
|
* its instances and expose the exact member name through
|
|
74
78
|
* `memberNameProperty`. Both strings are runtime data rather than knowledge
|
|
75
|
-
* of a particular consumer such as
|
|
79
|
+
* of a particular consumer such as PTC mode.
|
|
76
80
|
*/
|
|
77
81
|
interface CodeBindingErrorClass {
|
|
78
82
|
/** Constructor global and resulting `Error.name`; same portable identifier rule as {@link CodeBindingNamespace.global}. */
|
|
@@ -134,7 +138,7 @@ type CodeBindingFunction = (args: unknown) => Promise<CodeJsonValue>
|
|
|
134
138
|
|
|
135
139
|
## Captured output and the failure taxonomy
|
|
136
140
|
|
|
137
|
-
Logs are plain strings
|
|
141
|
+
Logs are plain strings. Each source channel preserves emission order, while interleaving across independent channels is backend-dependent because channel metadata is not part of the seam. The runtime captures the program's console and stream output, and consumers render only the text. Implementations cap the serialized outer log-array plus completion-value or failure-message payload; fixed result-envelope syntax and consumer presentation whitespace are not part of that variable-payload ledger. Overflow is an explicit failure rather than in-band value substitution.
|
|
138
142
|
|
|
139
143
|
Failure kinds are **orthogonal outcomes reported independently** (per [defensive-patterns](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/defensive-patterns.md)): a budget expiry is not an exception, an abort is not a timeout, and a substrate death (e.g. OOM) is neither:
|
|
140
144
|
|
|
@@ -161,7 +165,7 @@ interface CodeRunFailure {
|
|
|
161
165
|
|
|
162
166
|
## The service
|
|
163
167
|
|
|
164
|
-
`CodeRuntime` (`ctx.codeRuntime`, abstract — defined in [`packages/code-runtime/code-runtime/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/code-runtime/code-runtime/src/index.ts)) is `run(request)` plus two readonly descriptors: `language` (what the program must be written in — `'typescript'` and `'python'` are the well-known values, those `dsh-tools` presents, and
|
|
168
|
+
`CodeRuntime` (`ctx.codeRuntime`, abstract — defined in [`packages/code-runtime/code-runtime/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/code-runtime/code-runtime/src/index.ts)) is `run(request)` plus two readonly descriptors: `language` (what the program must be written in — `'typescript'` and `'python'` are the well-known values, those `dsh-tools` presents, the TypeScript backend released and the Python backend experimental and private (not published); a consumer generating language-specific presentation switches on it and fails loud on one it cannot present) and `isolation` (the execution substrate — `'worker-thread'`, `'process'`, `'container'`; a diagnostic label, **not a security claim**). Implementations must keep runs isolated from each other (no cross-run state) and dispose to quiescence: in-flight runs are terminated and awaited before teardown completes.
|
|
165
169
|
|
|
166
170
|
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
|
167
171
|
|
|
@@ -11,7 +11,7 @@ Source: [`packages/interaction/commands/src/index.ts`](https://github.com/deepse
|
|
|
11
11
|
|
|
12
12
|
## Input metadata
|
|
13
13
|
|
|
14
|
-
The service exposes one optional unstructured-input descriptor: a hint plus an
|
|
14
|
+
The service exposes one optional unstructured-input descriptor: a hint plus an attachment-acceptance flag. Command availability follows plugin composition: every adapter consuming the registry sees every effective definition.
|
|
15
15
|
|
|
16
16
|
```ts type-equiv
|
|
17
17
|
/** Immutable metadata for a command's optional unstructured input. */
|
|
@@ -19,13 +19,13 @@ interface CommandInputDescriptor {
|
|
|
19
19
|
/** Placeholder shown before the user supplies free-form input. */
|
|
20
20
|
readonly hint: string
|
|
21
21
|
/**
|
|
22
|
-
* Whether composer
|
|
23
|
-
* false = the executor rejects an invocation carrying
|
|
22
|
+
* Whether composer attachments may accompany an invocation. Absent or
|
|
23
|
+
* false = the executor rejects an invocation carrying attachments and capable
|
|
24
24
|
* composers refuse the submission before dispatch. A declaring command's
|
|
25
25
|
* handler receives the admitted durable blocks and owns every further
|
|
26
26
|
* grammar decision, including rejecting sub-commands that cannot use them.
|
|
27
27
|
*/
|
|
28
|
-
readonly
|
|
28
|
+
readonly attachments?: boolean
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
@@ -67,13 +67,13 @@ interface CommandInvocation {
|
|
|
67
67
|
/** Exact text following the registered command name, including separator whitespace. */
|
|
68
68
|
readonly rawInput: string
|
|
69
69
|
/**
|
|
70
|
-
* Durably admitted image blocks accompanying this invocation, in submission
|
|
71
|
-
* order; empty unless the definition declares `input.
|
|
70
|
+
* Durably admitted image and file blocks accompanying this invocation, in submission
|
|
71
|
+
* order; empty unless the definition declares `input.attachments`. The handler
|
|
72
72
|
* owns their model-visible use — the registry never schedules them itself —
|
|
73
73
|
* and a handler whose grammar cannot use them in this invocation returns an
|
|
74
74
|
* error so the dispatching composer retains the originals.
|
|
75
75
|
*/
|
|
76
|
-
readonly attachments: readonly ImageBlock[]
|
|
76
|
+
readonly attachments: readonly (ImageBlock | FileBlock)[]
|
|
77
77
|
/** Cancellation signal owned by the dispatching UI request. */
|
|
78
78
|
readonly signal: AbortSignal
|
|
79
79
|
}
|
|
@@ -86,7 +86,7 @@ type CommandResult =
|
|
|
86
86
|
readonly kind: 'success'
|
|
87
87
|
readonly text?: string
|
|
88
88
|
/** Earlier authoritative domain event that owns a richer presentation. */
|
|
89
|
-
readonly sourceEventSeq?:
|
|
89
|
+
readonly sourceEventSeq?: SessionSeq
|
|
90
90
|
}
|
|
91
91
|
| { readonly kind: 'error'; readonly text: string }
|
|
92
92
|
```
|
|
@@ -141,6 +141,13 @@ Human-command registry. Plain-context definitions are global; definitions regist
|
|
|
141
141
|
*/
|
|
142
142
|
register(definition: CommandDefinition): () => void
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Register the sole authority that resolves staged file receipts for command submissions.
|
|
146
|
+
* @param resolver - Session-aware receipt resolver.
|
|
147
|
+
* @returns disposer that removes this exact resolver.
|
|
148
|
+
*/
|
|
149
|
+
registerFileReceiptResolver(resolver: CommandFileReceiptResolver): () => void
|
|
150
|
+
|
|
144
151
|
/**
|
|
145
152
|
* List the effective immutable command descriptors for one agent.
|
|
146
153
|
* @param agent - exact receiving agent and scoped-layer key.
|
|
@@ -169,23 +176,25 @@ find(agent: Agent, name: string): CommandDefinition | undefined
|
|
|
169
176
|
* handler-failure path is contained so the handler's own error stays the
|
|
170
177
|
* reported failure.
|
|
171
178
|
*
|
|
172
|
-
*
|
|
173
|
-
* command that does not declare `input.
|
|
174
|
-
* and an exceeded
|
|
175
|
-
* the handler runs
|
|
179
|
+
* Attachment admission is enforced here, not in the composer: attachments sent to a
|
|
180
|
+
* command that does not declare `input.attachments`, an absent attachment store,
|
|
181
|
+
* and an exceeded image limit each settle as an error result before
|
|
182
|
+
* the handler runs. Validation rejection starts no attachment writes;
|
|
183
|
+
* a storage failure can leave only unreachable content-addressed objects
|
|
184
|
+
* for deferred collection.
|
|
176
185
|
*
|
|
177
186
|
* @param agent - exact receiving agent.
|
|
178
187
|
* @param line - complete slash-command line.
|
|
179
|
-
* @param
|
|
180
|
-
* submission order; empty for a plain invocation.
|
|
188
|
+
* @param submittedAttachments - encoded images and staged file receipts accompanying the line,
|
|
189
|
+
* in submission order; empty for a plain invocation.
|
|
181
190
|
* @param signal - cancellation signal owned by the UI request.
|
|
182
191
|
* @returns the settled execution (result + lifecycle pairing id), or
|
|
183
192
|
* `undefined` when syntax or name does not resolve.
|
|
184
193
|
*/
|
|
185
|
-
@Remote async execute( agent: Agent, line: string,
|
|
194
|
+
@Remote async execute( agent: Agent, line: string, submittedAttachments: readonly CommandSubmitAttachment[], signal: AbortSignal, ): Promise<CommandExecution | undefined>
|
|
186
195
|
```
|
|
187
196
|
|
|
188
|
-
Types: [Agent](./core.md)
|
|
197
|
+
Types: [Agent](./core.md)
|
|
189
198
|
|
|
190
199
|
Source: [`packages/interaction/commands/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/interaction/commands/src/index.ts)
|
|
191
200
|
|
|
@@ -37,11 +37,11 @@ interface CompactionResult {
|
|
|
37
37
|
/** Human command that initiated this compaction, when it was manual. */
|
|
38
38
|
sourceCommandId?: CommandId
|
|
39
39
|
/** The seq of the appended `compaction/start` event. */
|
|
40
|
-
startSeq:
|
|
40
|
+
startSeq: SessionSeq
|
|
41
41
|
/** The seq of the appended `compaction/summary` event. */
|
|
42
|
-
summarySeq:
|
|
42
|
+
summarySeq: SessionSeq
|
|
43
43
|
/** The seq of the appended `compaction/end` event. */
|
|
44
|
-
endSeq:
|
|
44
|
+
endSeq: SessionSeq
|
|
45
45
|
/** The summary content blocks produced by the backend. */
|
|
46
46
|
summary: ContentBlock[]
|
|
47
47
|
/**
|
|
@@ -52,9 +52,9 @@ interface CompactionResult {
|
|
|
52
52
|
* can be GREATER than `end`. {@link CompactionResult.shadowedSeqs} is the
|
|
53
53
|
* authoritative set of shadowed nodes, in surface order.
|
|
54
54
|
*/
|
|
55
|
-
shadowedRange: { start:
|
|
55
|
+
shadowedRange: { start: SessionSeq; end: SessionSeq }
|
|
56
56
|
/** The seqs of all shadowed surface nodes, in surface order. */
|
|
57
|
-
shadowedSeqs:
|
|
57
|
+
shadowedSeqs: SessionSeq[]
|
|
58
58
|
/** Estimated token count of the shadowed content. */
|
|
59
59
|
shadowedTokenCount: number
|
|
60
60
|
}
|
|
@@ -86,7 +86,7 @@ type ManualCompactionErrorCode =
|
|
|
86
86
|
|
|
87
87
|
`changed` and `summary` leave the conversation surface unchanged but still close and persist the failed attempt in the log. `commit` may follow partial mutation; `persistence` means the in-memory bracket closed but its flush failed. Cancellation remains separate and throws the exact abort reason after required cleanup.
|
|
88
88
|
|
|
89
|
-
Pressure compaction runs at
|
|
89
|
+
Pressure compaction runs at the `agent/pre-step` waterfall before request derivation. Once pressure or canonical overflow qualifies, compaction-basic invokes optional [`ctx.toolResultPruner`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/compaction/compaction-tool-result-pruner/README.md) before range selection, remeasures through `ctx.tokenMeter`, and can advance the surface without a summary. Failed-request recovery runs through `agent/request-error` after the failed step closes and returns a retry action only when the surface replacement generation advances, even if later summary work throws after pruning; cancellation still wins. Region boundaries preserve tool-call/result pairing but not whole turns, allowing early closed steps of one oversized turn to compact. `dsh-compaction-basic` owns thresholds, retained-tail policy, overflow caps, and failure handling.
|
|
90
90
|
|
|
91
91
|
The Service Definition exports `toolPairingBalancedBefore(session, seq)` and `toolPairingBalancedAfter(session, seq)` for the tool-call/result pairing checks before and after a seq. Both validate current surface membership and reject missing seqs and orphan results; the [package contract](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/compaction/compaction/README.md#tool-pairing-boundaries) defines their cache behavior.
|
|
92
92
|
|
|
@@ -98,11 +98,11 @@ The optional tool-result pruning service reports each durable content replacemen
|
|
|
98
98
|
/** Cited source event and size accounting for one landed surface replacement. */
|
|
99
99
|
interface PrunedEntry {
|
|
100
100
|
/** Full-fidelity tool-result event shadowed by the replacement. */
|
|
101
|
-
readonly originalSeq:
|
|
101
|
+
readonly originalSeq: SessionSeq
|
|
102
102
|
/** Newly appended pruned tool-result event. */
|
|
103
|
-
readonly replacementSeq:
|
|
103
|
+
readonly replacementSeq: SessionSeq
|
|
104
104
|
/** Tool call shared by the original and replacement. */
|
|
105
|
-
readonly callId:
|
|
105
|
+
readonly callId: ToolCallId
|
|
106
106
|
/** Original text size in Unicode code points. */
|
|
107
107
|
readonly charsBefore: number
|
|
108
108
|
/** Replacement text size in Unicode code points. */
|
|
@@ -190,10 +190,10 @@ abstract compactNow( agent: ManualCompactAgentContext, signal: AbortSignal, sour
|
|
|
190
190
|
* @throws when compaction is active or the range is missing, reversed, or unbalanced.
|
|
191
191
|
* @returns the appended event seqs, summary, replaced range, and token accounting.
|
|
192
192
|
*/
|
|
193
|
-
abstract compactRegion( start:
|
|
193
|
+
abstract compactRegion( start: SessionSeq, end: SessionSeq, agent: CompactionAgentContext, signal?: AbortSignal, ): Promise<CompactionResult>
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
Types: [CommandId](./commands.md)
|
|
196
|
+
Types: [CommandId](./commands.md) · [SessionSeq](./session.md)
|
|
197
197
|
|
|
198
198
|
Source: [`packages/compaction/compaction/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/compaction/compaction/src/index.ts)
|
|
199
199
|
|
package/kb/site/en/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md}
RENAMED
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
---
|
|
2
|
-
editSource: "docs/
|
|
2
|
+
editSource: "docs/subsystems/conversation.md"
|
|
3
|
+
outline: [2,3]
|
|
3
4
|
---
|
|
4
5
|
|
|
5
|
-
#
|
|
6
|
+
# Conversation assembly
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Conversation is the target-neutral assembly layer between a Client `SessionEventLikeEntry` window and browser views. [`ui-conversation`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-conversation/README.md) owns the event and view registries, one identity-stable binding per `SessionBinding`, Turn/Step locations, incremental Context assembly, target sources, the shared shell, and input orchestration. Target packages such as [`ui-chat`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/README.md) and [`ui-trajectory`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-trajectory/README.md) own their Definitions, final snapshots, and rendering.
|
|
8
9
|
|
|
9
|
-
The [Conversation Node assembly decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md) owns
|
|
10
|
+
This page defines the data model and the extension path for a business-owned Conversation node. The [Web Client architecture](./web-client.md) places the subsystem between Client models and Slots; the [Conversation Node assembly decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md) owns its rationale.
|
|
10
11
|
|
|
11
|
-
##
|
|
12
|
+
## Data model and ownership
|
|
13
|
+
|
|
14
|
+
The Session Controller owns the contiguous loaded logical-event window. Each `SessionEventLikeEntry` is either `{ type: 'event', event: SessionEvent }` for one durable event or `{ type: 'transient', event: AssistantLiveChunkEvent }` for one Client-only `assistant/live-chunk` presentation. Both inner events expose `type`, `seq`, `time`, and `data`. `ui-conversation` passes these entries to the assembler without opening a second history stream. One `ConversationNodeAssembler` per Session applies every registered Definition and publishes an independent source for each registered view target.
|
|
15
|
+
|
|
16
|
+
| Concept | Owner and purpose |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Event Definition | A business package matches one durable or Client-only transient event at a time, correlates it by stable `(kind, id)`, folds deterministic State, and optionally materializes one target node. |
|
|
19
|
+
| Context | The engine-owned ordered Matches and current State for one `(kind, id)`. A transient event occupies one update Match; update-only evidence may remain pending until pagination supplies its unique durable start. |
|
|
20
|
+
| Location | The engine-owned Session, Turn, or Step coordinates derived from durable boundary events. Definitions may publish typed data onto one Turn or Step. |
|
|
21
|
+
| View Definition | A target package creates one incremental builder per Session and owns the final snapshot type for that target. |
|
|
22
|
+
| View | A Slot entry such as Chat or Trajectory reads only its target snapshot and renders target-owned nodes. |
|
|
23
|
+
|
|
24
|
+
Chat and Trajectory may recognize the same durable event family, but each keeps its own Definition State and final node payload. Shared target-neutral machinery is limited to identity routing, ordered replay, Location data, predecessor dependencies, and publication cadence.
|
|
25
|
+
|
|
26
|
+
## Target activation
|
|
27
|
+
|
|
28
|
+
Each Session keeps a monotonic set of active targets. Creating or reading a target source does not activate it. The shell explicitly activates its persisted or newly selected View, while another consumer activates a target through its first source subscription. First activation creates that target's builder and calls `replace()` once from the current target-indexed Contexts. Later flushes call `apply()` for every active target, and unsubscription does not remove one.
|
|
29
|
+
|
|
30
|
+
The shell owns View selection and resolves the registered preferred View or Chat fallback before rendering when a binding is created or selected as current, and after View-roster changes. The assembler receives only the resolved target id and does not select Chat or another default target. A third-party View participates through the same selection and activation operations.
|
|
31
|
+
|
|
32
|
+
## Replayable event families
|
|
12
33
|
|
|
13
34
|
Choose one stable business id before writing the Definition. Every event that contributes to the same Node must carry that id or derive it independently from its own payload; the client must never assign an update to “the latest unfinished” Context.
|
|
14
35
|
|
|
@@ -24,18 +45,21 @@ Use the producer-owned branded id type across the process boundary. Put the `Ses
|
|
|
24
45
|
|
|
25
46
|
Incremental events are supported. Prefer whole-value checkpoints when the producer can emit them cheaply, because they remain useful when the start is outside the loaded window. Each delta must carry the stable id and produce deterministic State when replayed in ascending log `seq`; it must not depend on live-only memory. If the current history window contains only updates, the assembler keeps a pending Context and builds no State until an older page supplies the start. If the product must render before the start is loaded, a terminal or checkpoint event must carry enough whole fallback state for the Definition to build that result directly; do not recover it by scanning unrelated events.
|
|
26
47
|
|
|
27
|
-
|
|
48
|
+
Live Assistant deltas arrive as Client-only `assistant/live-chunk` updates. Reconnect baselines expand the active process-local compact stream into the same transient events, while durable `assistant/message` and `assistant/attempt` events embed complete compact streams for history replay. Transient events can only be updates; `start()` receives a standard `SessionEvent`. A Definition that consumes Assistant output handles live chunks and durable settlements in the same `match()` and `update()` methods, while unrelated Definitions return `null` without expanding a stream.
|
|
49
|
+
|
|
50
|
+
## Definition and typed Chat payload
|
|
28
51
|
|
|
29
52
|
The example keeps the producer declarations and client contribution in one block so the complete relationship is visible. In a package family, keep the branded id and `SessionEventMap` declaration with the event producer, and keep the Definition, Chat data merge, and renderer in the client plugin.
|
|
30
53
|
|
|
31
54
|
```ts ignore-check
|
|
32
55
|
import { createElement } from 'react'
|
|
56
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
|
33
57
|
import type { Branded } from '@deepseek-ai/dsh-brand'
|
|
34
58
|
import type {
|
|
35
|
-
|
|
59
|
+
ConversationLocation, ConversationNodeContext,
|
|
36
60
|
ConversationNodeDefinition,
|
|
37
|
-
} from '@deepseek-ai/dsh-client-
|
|
38
|
-
import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-
|
|
61
|
+
} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
62
|
+
import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-chat/client'
|
|
39
63
|
|
|
40
64
|
type ReviewId = Branded<'ReviewId'>
|
|
41
65
|
|
|
@@ -90,13 +114,13 @@ interface ReviewChatData {
|
|
|
90
114
|
readonly summary?: string
|
|
91
115
|
}
|
|
92
116
|
|
|
93
|
-
declare module '@deepseek-ai/dsh-client-ui-
|
|
117
|
+
declare module '@deepseek-ai/dsh-client-ui-chat/client' {
|
|
94
118
|
interface ChatNodeDataMap {
|
|
95
119
|
'review-job': ReviewChatData
|
|
96
120
|
}
|
|
97
121
|
}
|
|
98
122
|
|
|
99
|
-
declare module '@deepseek-ai/dsh-client-
|
|
123
|
+
declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
|
|
100
124
|
interface ConversationStepDataMap {
|
|
101
125
|
'review-job': ReviewChatData
|
|
102
126
|
}
|
|
@@ -184,10 +208,10 @@ function ReviewNodeView({ node }: ChatNodeViewProps<'review-job'>) {
|
|
|
184
208
|
return createElement('p', null, text)
|
|
185
209
|
}
|
|
186
210
|
|
|
187
|
-
export const inject = ['
|
|
211
|
+
export const inject = ['uiConversation', 'slots']
|
|
188
212
|
|
|
189
213
|
export function apply(ctx: ClientContext): void {
|
|
190
|
-
ctx.
|
|
214
|
+
ctx.uiConversation.events.register(reviewDefinition)
|
|
191
215
|
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
|
|
192
216
|
name: 'conversation.chat.node',
|
|
193
217
|
key: 'review-job',
|
|
@@ -195,33 +219,33 @@ export function apply(ctx: ClientContext): void {
|
|
|
195
219
|
}
|
|
196
220
|
```
|
|
197
221
|
|
|
198
|
-
`match(event)` is an identity extractor, not a fold: it receives only the current
|
|
222
|
+
`match(event)` is an identity extractor, not a fold: it receives only the current `SessionEventLike` and returns the Definition-local id and lifecycle role. After a match, the assembler locates the Context by `(kind, id)` and calls `start` once for a standard event or `update` for a standard or packed event. Both functions return the State that the engine adopts; returning a new immutable value is preferred, but a function that mutates and returns the same object has the same adoption semantics.
|
|
199
223
|
|
|
200
224
|
`buildLocationData(context, scope)` optionally publishes Definition-owned data onto an engine-owned Turn or Step. Use declaration merging to give each key a precise value type. Another Node in the same Location can consume that value through its constrained slot hook, such as `useTurnData(key)`, without receiving the Session or scanning `snapshot.chat.nodes`.
|
|
201
225
|
|
|
202
226
|
`target` and `buildViewNode(context)` declare one target-owned rendering contribution and must appear together. Preserve `context.key` as the React-facing identity, choose `anchorSeq` from durable ordering evidence, and return only renderer-ready data. Once a target Node has been published, keep returning the same key; use `visibility: 'hidden'` when it must temporarily leave the visible flow rather than withdrawing it with `null`.
|
|
203
227
|
|
|
204
|
-
##
|
|
228
|
+
## Predecessor reads
|
|
205
229
|
|
|
206
230
|
Some Definitions need the latest earlier State of another business kind. `start` receives a `ConversationContextReader`; call `reader.previous<State>(kind)` there instead of accepting a Context collection or scanning events. The reader returns the nearest started Context before the current start `seq` as read-only data.
|
|
207
231
|
|
|
208
232
|
The assembler records that dependency. If an older prepend later supplies a nearer predecessor, closes a previously unknown window gap, or revises the predecessor State, it reruns the dependent Context from `start` and replays its updates in ascending `seq`. The queried Definition remains responsible for writing useful State; the reader exposes no business-specific query methods and grants no mutation authority over another Context.
|
|
209
233
|
|
|
210
|
-
##
|
|
234
|
+
## Window update paths
|
|
211
235
|
|
|
212
|
-
History may be requested from the tail backward one page at a time
|
|
236
|
+
History may be requested from the tail backward one page at a time. The Session journal validates non-overlapping logical sequence ranges first; the Assembler then orders accepted inputs by their first `seq` before State replay.
|
|
213
237
|
|
|
214
238
|
| Path | Engine work | Definition-visible behavior |
|
|
215
239
|
|---|---|---|
|
|
216
|
-
| Replace on open, resync, or gap repair | Rebuild the loaded window, match every event once per Definition, then replay each started Context | `start`, followed by its updates in ascending `seq`; pending update-only Contexts remain without State |
|
|
217
|
-
| Prepend one older page | Match only fresh older
|
|
218
|
-
| Append one live event | Call each Definition's `match` once, look up the matched Context by key, and update only that Context | One `update` and one requested publication for a matching post-start event; no existing Context scan |
|
|
240
|
+
| Replace on open, resync, or gap repair | Rebuild the loaded window, match every standard event or packed run once per Definition, then replay each started Context | `start`, followed by its updates in ascending logical `seq`; pending update-only Contexts remain without State |
|
|
241
|
+
| Prepend one older page | Match only fresh older inputs, merge them into Contexts by `(kind, id)`, preserve existing keyed nodes, and replay only affected Contexts and dependencies | A newly found scalar start activates its collected scalar and packed updates; a changed Location or predecessor may rerun the Context |
|
|
242
|
+
| Append one live event | Call each Definition's `match` once, look up the matched Context by key, and update only that Context | One scalar `update` and one requested publication for a matching post-start event; no existing Context scan |
|
|
219
243
|
|
|
220
|
-
With `D` registered Definitions, one incoming event performs `D` current-
|
|
244
|
+
With `D` registered Definitions, one incoming scalar event or packed run performs `D` current-input matches and constant-time Context-key lookup after a match. Definition code must preserve that property: do not traverse the complete event window, every Context, `context.matches`, or the rendered Node collection on the normal append path. Use State for accumulated facts, Location data for same-Turn/Step sharing, and `reader.previous()` for indexed predecessor dependencies.
|
|
221
245
|
|
|
222
|
-
`publication` controls when changed State is materialized. Use `immediate` for structural or terminal changes, `animation-frame` for high-frequency visible deltas, and `none` when the State change feeds only a later publication. The engine
|
|
246
|
+
`publication` controls when changed State is materialized. Use `immediate` for structural or terminal changes, `animation-frame` for high-frequency visible deltas, and `none` when the State change feeds only a later publication. The engine applies every scalar update in log order and every packed run in one batch update; cadence only coalesces view publication.
|
|
223
247
|
|
|
224
|
-
##
|
|
248
|
+
## Verification obligations
|
|
225
249
|
|
|
226
250
|
Add focused tests that establish these outcomes:
|
|
227
251
|
|
|
@@ -231,5 +255,7 @@ Add focused tests that establish these outcomes:
|
|
|
231
255
|
4. Prepending an older page adds earlier rows without replacing existing keyed Node values whose data did not change.
|
|
232
256
|
5. Repeated visible deltas preserve `context.key` and publish at most once per animation frame when requested.
|
|
233
257
|
6. The keyed renderer consumes `node.data` and constrained Location hooks only; it does not scan the Session event window, Contexts, or Chat Nodes.
|
|
258
|
+
7. Scalar and packed Assistant history produce the same final State, timing boundaries, and target snapshot, while one packed run remains one Match through replace, prepend, Location replay, and registry rebuild.
|
|
259
|
+
8. Creating a target source performs no builder work; explicit selection or the first subscription performs one complete replacement, later updates reach every active target, and repeated activation performs no replacement.
|
|
234
260
|
|
|
235
|
-
Use [`packages/client/ui-
|
|
261
|
+
Use [`packages/client/ui-chat/src/client/conversation-nodes/assistant.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/src/client/conversation-nodes/assistant.ts) for streaming and interruption, [`inbox.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/src/client/conversation-nodes/inbox.ts) plus [`message.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/src/client/conversation-nodes/message.ts) for predecessor queries, and [`packages/client/ui-deliverables`](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-deliverables) for a Definition that publishes Turn data without creating its own Node.
|