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
|
@@ -24,8 +24,8 @@ interface UserMessage extends Message {
|
|
|
24
24
|
/**
|
|
25
25
|
* The merge-extensible, append-only source of truth for an agent interaction.
|
|
26
26
|
* Message history is derived from this log. Every event is lossless JSON and
|
|
27
|
-
* sequence numbers stay contiguous
|
|
28
|
-
*
|
|
27
|
+
* sequence numbers stay contiguous. Assistant attempt events embed their exact
|
|
28
|
+
* compact raw streams so persistence stores one durable settlement per attempt.
|
|
29
29
|
*/
|
|
30
30
|
interface SessionEventMap {
|
|
31
31
|
/**
|
|
@@ -56,8 +56,6 @@ interface SessionEventMap {
|
|
|
56
56
|
* project their `content` verbatim; `source` tells them apart.
|
|
57
57
|
*/
|
|
58
58
|
'user/message': UserMessage
|
|
59
|
-
/** Raw stream chunk — token-level replay fidelity. */
|
|
60
|
-
'assistant/chunk': { turn: number; step: number; chunk: StreamChunk }
|
|
61
59
|
/**
|
|
62
60
|
* Assembled assistant message for one step (derived history uses this).
|
|
63
61
|
* Carries the step's `usage` when the adapter reported token accounting, so
|
|
@@ -68,13 +66,27 @@ interface SessionEventMap {
|
|
|
68
66
|
* marker distinguishes that prefix without re-deriving interruption from turn
|
|
69
67
|
* boundaries. An aborted turn with no such event streamed no visible content.
|
|
70
68
|
*/
|
|
71
|
-
'assistant/message': {
|
|
69
|
+
'assistant/message': {
|
|
70
|
+
turn: number
|
|
71
|
+
step: number
|
|
72
|
+
message: AssistantMessage
|
|
73
|
+
/** Exact timed model stream, compacted without joining delta boundaries. */
|
|
74
|
+
stream: AssistantStreamRecord[]
|
|
75
|
+
usage?: TokenUsage
|
|
76
|
+
interrupted?: true
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* One model attempt that committed no surface message. The embedded stream
|
|
80
|
+
* preserves a failed, retried, cancelled, or stream-error attempt that
|
|
81
|
+
* reached settlement without fabricating model-visible history.
|
|
82
|
+
*/
|
|
83
|
+
'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[] }
|
|
72
84
|
/**
|
|
73
85
|
* The model requested one tool invocation: `name` with the raw `arguments`
|
|
74
86
|
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
|
|
75
87
|
* call with its `tool/result`.
|
|
76
88
|
*/
|
|
77
|
-
'tool/call': { turn: number; step: number; callId:
|
|
89
|
+
'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
|
|
78
90
|
/**
|
|
79
91
|
* A completed tool call's model-facing result, optional internal failure
|
|
80
92
|
* identity, and optional tool-private `meta` presentation payload. `meta` is
|
|
@@ -93,13 +105,16 @@ interface SessionEventMap {
|
|
|
93
105
|
error?: { name: string; code: string }
|
|
94
106
|
meta?: JsonValue
|
|
95
107
|
}
|
|
96
|
-
/** Whole-list snapshot; latest write wins on replay. Log-only UI state; never derived history. */
|
|
97
|
-
'todo/write': { todos: TodoItem[] }
|
|
98
108
|
/**
|
|
99
109
|
* Full header for the next request, appended inside its step before dispatch.
|
|
100
110
|
* It is log-only; the latest snapshot reconstructs the request header.
|
|
101
111
|
*/
|
|
102
|
-
'request/header': {
|
|
112
|
+
'request/header': {
|
|
113
|
+
header: EpochHeader
|
|
114
|
+
reason: RequestHeaderReason
|
|
115
|
+
/** A changed header also begins a distinct model-message series. */
|
|
116
|
+
startsSeries?: true
|
|
117
|
+
}
|
|
103
118
|
/**
|
|
104
119
|
* Route metadata for the next request, logged only when the route or capacity
|
|
105
120
|
* changes. It does not participate in request reconstruction or header equality.
|
|
@@ -109,12 +124,12 @@ interface SessionEventMap {
|
|
|
109
124
|
* Marks the end of a constructor seed. Events before it have smaller seq
|
|
110
125
|
* values and came from the seed (resume, fork, or replay); this lifecycle
|
|
111
126
|
* produced none of them. This log-only event is the durable projection of
|
|
112
|
-
* {@link Session.firstLiveSeq}.
|
|
113
|
-
* carry the meaning.
|
|
127
|
+
* {@link Session.firstLiveSeq}.
|
|
114
128
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
129
|
+
* A fresh fork child owns one `{ inherited: true }` marker at its exact
|
|
130
|
+
* inherited-prefix cut, even when that prefix ends in an ancestor marker.
|
|
131
|
+
* The last tagged marker is the current Session's cut; untagged markers keep
|
|
132
|
+
* ordinary restore and replay lifecycle boundaries.
|
|
118
133
|
*
|
|
119
134
|
* `Session`'s constructor is the only legitimate writer. The invariant
|
|
120
135
|
* companion deliberately constrains nothing here, so a plugin appending one
|
|
@@ -127,40 +142,17 @@ interface SessionEventMap {
|
|
|
127
142
|
* writers — a concurrently live session holds its own boundary elsewhere,
|
|
128
143
|
* so tolerating concurrent writers needs a signal beyond the log.
|
|
129
144
|
*/
|
|
130
|
-
'session/end-seed':
|
|
145
|
+
'session/end-seed': { inherited?: true }
|
|
131
146
|
}
|
|
132
147
|
```
|
|
133
148
|
|
|
134
149
|
`UserMessage` is the identified, frozen user-role value shared by ordinary prompts, injected context, steering, and live inbox events. Event wrappers add only event-local position or outcome facts; the loop adds only driver-owned routing state while an item remains pending.
|
|
135
150
|
|
|
136
|
-
### `TodoItem` — one todo-list entry
|
|
137
|
-
|
|
138
|
-
The unit of the `todo/write` event's whole-list snapshot. Deliberately minimal — a `content` line and a three-state `status` (no id, priority, or `activeForm`): the list is replaced wholesale on every write, so entries need no stable identity. See the [todo_write Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-06-29-todo-write-tool.md).
|
|
139
|
-
|
|
140
|
-
```ts type-equiv
|
|
141
|
-
/**
|
|
142
|
-
* One entry in an agent's todo list — the unit of the `todo/write`
|
|
143
|
-
* {@link SessionEventMap} event's whole-list snapshot.
|
|
144
|
-
*
|
|
145
|
-
* Deliberately minimal: a human-readable `content` line and a three-state
|
|
146
|
-
* `status`. No id, priority, or `activeForm` — the list is replaced wholesale
|
|
147
|
-
* on every write (last-write-wins), so entries need no stable identity. The
|
|
148
|
-
* three statuses describe the complete portable lifecycle needed by model and
|
|
149
|
-
* UI consumers.
|
|
150
|
-
*/
|
|
151
|
-
interface TodoItem {
|
|
152
|
-
/** What this task is — a short imperative line shown in the UI. */
|
|
153
|
-
content: string
|
|
154
|
-
/** Lifecycle state. `in_progress` marks a task being worked now; parallel work may mark several. */
|
|
155
|
-
status: 'pending' | 'in_progress' | 'completed'
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
151
|
<a id="the-request-header-event-requestheader"></a>
|
|
160
152
|
|
|
161
153
|
### The request header event: `request/header`
|
|
162
154
|
|
|
163
|
-
The request envelope — the `EpochHeader` (call config + markers for adapter-supplied defaults + rendered system prompt + assembled tool schemas) — is logged session state, so every conversation request is a pure function of the log (the reconstructability Agent Note). A full `request/header` snapshot with reason `'initial'` or `'resume'` records each loop-instance boundary; a
|
|
155
|
+
The request envelope — the `EpochHeader` (call config + markers for adapter-supplied defaults + rendered system prompt + assembled tool schemas) — is logged session state, so every conversation request is a pure function of the log (the reconstructability Agent Note). A full `request/header` snapshot with reason `'initial'` or `'resume'` records each loop-instance boundary; a changed request appends a snapshot with reason `'change'`; and an unchanged envelope beginning an explicitly declared message series or following a surface replacement appends a snapshot with reason `'series'`. A changed snapshot carries `startsSeries: true` when that request also begins a series. Ordinary append-only later Turns, further Steps, and retries in the same model-message series inherit the latest snapshot. `foldRequestHeader(events)` reconstructs the header by selecting the latest snapshot. The event is not a `SurfaceEventType`: it produces no LLM message.
|
|
164
156
|
|
|
165
157
|
```ts type-equiv
|
|
166
158
|
/**
|
|
@@ -202,6 +194,28 @@ interface RequestContext {
|
|
|
202
194
|
|
|
203
195
|
A proper discriminated union over `type` (not independent `type`/`data` unions), so `switch (event.type)` narrows `event.data` without casts. `seq` is the monotonic position in the log (`seq = log.length`); `time` is epoch ms.
|
|
204
196
|
|
|
197
|
+
```ts type-equiv
|
|
198
|
+
/** Sequence number of one existing event in a Session log. */
|
|
199
|
+
type SessionSeq = BrandedNumber<'SessionSeq'>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```ts type-equiv
|
|
203
|
+
/** A Session log gap, prefix length, or read offset, which may equal the event count. */
|
|
204
|
+
type SessionLogOffset = BrandedNumber<'SessionLogOffset'>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```ts type-equiv
|
|
208
|
+
/** Inclusive Session event watermark, or `-1` before any event exists. */
|
|
209
|
+
type SessionSeqCursor = SessionSeq | -1
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```ts type-equiv
|
|
213
|
+
/** One existing Session event position, or explicit absence. */
|
|
214
|
+
type OptionalSessionSeq = SessionSeq | null
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`SessionSeq(value)` and `SessionLogOffset(value)` admit only non-negative safe integers and reject negative zero. They add compile-time brands without changing the serialized number; arithmetic returns an ordinary `number` that callers must admit again through the constructor for its intended role.
|
|
218
|
+
|
|
205
219
|
```ts type-equiv
|
|
206
220
|
/**
|
|
207
221
|
* One immutable entry in the session log.
|
|
@@ -212,7 +226,7 @@ A proper discriminated union over `type` (not independent `type`/`data` unions),
|
|
|
212
226
|
* The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
|
|
213
227
|
* they only exist on {@link SurfaceEventType} variants (`user/message`,
|
|
214
228
|
* `assistant/message`, `tool/result`).
|
|
215
|
-
* Non-surface events (boundary markers,
|
|
229
|
+
* Non-surface events (boundary markers, attempts, errors) never carry
|
|
216
230
|
* surface metadata — the compiler enforces this at `Session.append()`
|
|
217
231
|
* call sites.
|
|
218
232
|
*/
|
|
@@ -220,7 +234,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
220
234
|
[K in SessionEventType]: {
|
|
221
235
|
type: K
|
|
222
236
|
/** Monotonic sequence number within the session. */
|
|
223
|
-
seq:
|
|
237
|
+
seq: SessionSeq
|
|
224
238
|
/** Unix epoch milliseconds. */
|
|
225
239
|
time: number
|
|
226
240
|
data: SessionEventMap[K]
|
|
@@ -237,14 +251,11 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
237
251
|
ignorable?: true
|
|
238
252
|
} & (K extends SurfaceEventType ? {
|
|
239
253
|
/**
|
|
240
|
-
* Seq numbers of earlier events that this event cites as sources
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* `assistant/message` may carry a present empty array for a known empty
|
|
244
|
-
* provider stream; when the field is absent, the event does not record which
|
|
245
|
-
* earlier events produced the message.
|
|
254
|
+
* Seq numbers of earlier events that this event cites as sources, such as
|
|
255
|
+
* the surface nodes shadowed by a compaction replacement. A v2
|
|
256
|
+
* `assistant/message` embeds its provider stream and cannot carry this field.
|
|
246
257
|
*/
|
|
247
|
-
sourceEventSeqs?:
|
|
258
|
+
sourceEventSeqs?: SessionSeq[]
|
|
248
259
|
/** How this event entered the surface; absent for non-surface events. */
|
|
249
260
|
surfaceOp?: SurfaceOp
|
|
250
261
|
} : object)
|
|
@@ -253,7 +264,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
253
264
|
|
|
254
265
|
`SessionEventType = keyof SessionEventMap`. Because `SessionEventMap` is merge-extensible, switches over `SessionEvent` must NOT use `assertNever` — a plugin-added variant is a valid unknown value; handle the known cases and fall through `default`.
|
|
255
266
|
|
|
256
|
-
|
|
267
|
+
V2 `assistant/message` embeds its provider stream and cannot carry `sourceEventSeqs`. User and tool surface events may cite a complete non-empty set of unique earlier events when their provenance or replacement operation requires it.
|
|
257
268
|
|
|
258
269
|
## Surface types
|
|
259
270
|
|
|
@@ -265,7 +276,8 @@ The three message-producing types (`SurfaceEventType` — `user/message`, `assis
|
|
|
265
276
|
/**
|
|
266
277
|
* The subset of {@link SessionEventType} values whose events produce LLM
|
|
267
278
|
* messages and are eligible to appear on the ordered surface. Only these
|
|
268
|
-
* event types may carry {@link SurfaceOp} and
|
|
279
|
+
* event types may carry {@link SurfaceOp}; user and tool events may also cite
|
|
280
|
+
* earlier sources through {@link SessionEvent.sourceEventSeqs}.
|
|
269
281
|
*/
|
|
270
282
|
type SurfaceEventType =
|
|
271
283
|
| 'user/message'
|
|
@@ -291,7 +303,7 @@ type SurfaceEventType =
|
|
|
291
303
|
*/
|
|
292
304
|
type SurfaceOp =
|
|
293
305
|
| 'append'
|
|
294
|
-
| { op: 'replace'; start:
|
|
306
|
+
| { op: 'replace'; start: SessionSeq; end: SessionSeq }
|
|
295
307
|
```
|
|
296
308
|
|
|
297
309
|
`'append'` is the normal tail-append path. `replace` shadows surface entries from `start` through `end` inclusive (both must be valid surface seqs; `start === end` replaces a single entry) and inserts the new event in their place.
|
|
@@ -303,21 +315,20 @@ type SurfaceOp =
|
|
|
303
315
|
* Surface placement and cited source-event seqs for {@link Session.append}. Required on
|
|
304
316
|
* message-producing events and forbidden on log-only events.
|
|
305
317
|
*/
|
|
306
|
-
|
|
318
|
+
type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
|
|
307
319
|
surfaceOp: SurfaceOp
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
320
|
+
} & (T extends 'assistant/message' ? {
|
|
321
|
+
/** V2 Assistant messages embed their provider stream instead of citing source events. */
|
|
322
|
+
sourceEventSeqs?: never
|
|
323
|
+
} : {
|
|
324
|
+
/** Complete non-empty set of known earlier source-event seqs. */
|
|
325
|
+
sourceEventSeqs?: SessionSeq[]
|
|
326
|
+
})
|
|
316
327
|
```
|
|
317
328
|
|
|
318
329
|
Required for `SurfaceEventType` events — every message-producing event must declare how it joins the surface, the sole source of derived model history. A human-facing transcript is the other projection and reads the log's append-origin events instead, because the surface deliberately shadows the ranges a replacement summarizes (`isAppendSurfaceEvent` in [dsh-session](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/README.md)). Non-surface types reject it at compile time.
|
|
319
330
|
|
|
320
|
-
|
|
331
|
+
`assistant/message` cannot carry `sourceEventSeqs`; its `stream` owns exact provider evidence. Other surface events omit the field when they cite no earlier event and use a complete non-empty list when they do.
|
|
321
332
|
|
|
322
333
|
### `SessionSurface` — the live readonly surface projection
|
|
323
334
|
|
|
@@ -329,7 +340,7 @@ Only `assistant/message` may carry a present empty `sourceEventSeqs`; when the f
|
|
|
329
340
|
/** Readonly live projection of the message-producing session events. */
|
|
330
341
|
interface SessionSurface {
|
|
331
342
|
/** Current surface event sequences in model-visible order. */
|
|
332
|
-
readonly nodes: readonly
|
|
343
|
+
readonly nodes: readonly SessionSeq[]
|
|
333
344
|
/** Monotonic count of committed positional replacements. */
|
|
334
345
|
readonly replaceGeneration: number
|
|
335
346
|
}
|
|
@@ -343,13 +354,13 @@ interface SessionSurface {
|
|
|
343
354
|
/** One replacement operation observed while folding a session surface. */
|
|
344
355
|
interface SurfaceFoldReplacement {
|
|
345
356
|
/** Seq of the event that replaced the prior surface range. */
|
|
346
|
-
seq:
|
|
357
|
+
seq: SessionSeq
|
|
347
358
|
/** Declared inclusive start seq of the replaced surface range. */
|
|
348
|
-
start:
|
|
359
|
+
start: SessionSeq
|
|
349
360
|
/** Declared inclusive end seq of the replaced surface range. */
|
|
350
|
-
end:
|
|
361
|
+
end: SessionSeq
|
|
351
362
|
/** Actual surface entries removed by the operation, in surface order. */
|
|
352
|
-
shadowedSeqs:
|
|
363
|
+
shadowedSeqs: SessionSeq[]
|
|
353
364
|
}
|
|
354
365
|
```
|
|
355
366
|
|
|
@@ -357,7 +368,7 @@ interface SurfaceFoldReplacement {
|
|
|
357
368
|
/** Complete result of replaying the surface operations in a session log. */
|
|
358
369
|
interface SurfaceFoldResult {
|
|
359
370
|
/** Current surface event sequences in model-visible order. */
|
|
360
|
-
nodes:
|
|
371
|
+
nodes: SessionSeq[]
|
|
361
372
|
/** Replacement operations in event order. */
|
|
362
373
|
replacements: SurfaceFoldReplacement[]
|
|
363
374
|
}
|
|
@@ -381,24 +392,27 @@ declare class Session {
|
|
|
381
392
|
get surface(): SessionSurface;
|
|
382
393
|
/**
|
|
383
394
|
* Detached, deep-frozen creation metadata (format version, cwd, lineage,
|
|
384
|
-
*
|
|
395
|
+
* and whether fork history exists). Supplied by the store via `ctx.sessions.create()`. When a
|
|
385
396
|
* `Session` is created without a store-owned header, a minimal header is
|
|
386
397
|
* synthesized (stamped with the current {@link SESSION_FORMAT_VERSION}) so
|
|
387
398
|
* `session.header` is always present. Kept out of the event log — it is a
|
|
388
399
|
* storage concern, not replayable conversation state.
|
|
389
400
|
*/
|
|
390
401
|
readonly header: SessionHeader;
|
|
402
|
+
/** Number of leading events inherited from this Session's fork parent. */
|
|
403
|
+
readonly inheritedEventCount: SessionLogOffset;
|
|
391
404
|
/** The session identity, derived from its durable header's single copy. */
|
|
392
405
|
get id(): SessionId;
|
|
393
406
|
/**
|
|
394
407
|
* The first seq appended IN THIS PROCESS: the length of the constructor
|
|
395
408
|
* seed (0 without one). Events with smaller seq values entered through
|
|
396
409
|
* construction — replay, fork, or resume — and were never published on the
|
|
397
|
-
* `session/event` firehose (constructor seeds do not emit)
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
410
|
+
* `session/event` firehose (constructor seeds do not emit). This offset marks
|
|
411
|
+
* the constructor-input boundary for lifecycle ownership and persistence
|
|
412
|
+
* adoption; consumers that need complete canonical history still start at
|
|
413
|
+
* seq 0. Distinct from {@link inheritedEventCount}, the DURABLE
|
|
414
|
+
* fork-lineage cut: a resumed session's constructor seed is its full stored
|
|
415
|
+
* log, while the inherited count keeps the original fork value — this field is the
|
|
402
416
|
* in-process construction fact.
|
|
403
417
|
*
|
|
404
418
|
* Not persisted itself: a seeded session projects it into the log as the
|
|
@@ -411,17 +425,23 @@ declare class Session {
|
|
|
411
425
|
* When this lifecycle appends the marker, it occupies this seq before the
|
|
412
426
|
* store attaches and therefore does not publish either. Otherwise this seq
|
|
413
427
|
* holds an ordinary published write.
|
|
414
|
-
|
|
415
|
-
readonly firstLiveSeq:
|
|
428
|
+
*/
|
|
429
|
+
readonly firstLiveSeq: SessionLogOffset;
|
|
416
430
|
/**
|
|
417
431
|
* Create a detached session by validating and snapshotting borrowed seed
|
|
418
432
|
* events and storage metadata.
|
|
419
433
|
* @param id - session identity.
|
|
420
434
|
* @param seed - optional borrowed replay or fork events.
|
|
421
435
|
* @param header - optional borrowed storage metadata.
|
|
436
|
+
* @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
|
|
422
437
|
* @returns a detached session.
|
|
423
438
|
*/
|
|
424
|
-
static create(
|
|
439
|
+
static create(
|
|
440
|
+
id: SessionId,
|
|
441
|
+
seed?: readonly SessionEvent[],
|
|
442
|
+
header?: SessionHeader,
|
|
443
|
+
inheritedEventCount?: SessionLogOffset,
|
|
444
|
+
): Session;
|
|
425
445
|
/**
|
|
426
446
|
* Restore a detached session by taking ownership of fresh persistence values.
|
|
427
447
|
* The storage format, event envelopes, sequence continuity, surface transitions,
|
|
@@ -429,18 +449,46 @@ declare class Session {
|
|
|
429
449
|
* @param id - restored session identity.
|
|
430
450
|
* @param seed - fresh detached events whose ownership is transferred.
|
|
431
451
|
* @param header - fresh detached metadata whose ownership is transferred.
|
|
452
|
+
* @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
|
|
432
453
|
* @returns a restored detached session.
|
|
433
454
|
*/
|
|
434
|
-
static fromRestore(
|
|
455
|
+
static fromRestore(
|
|
456
|
+
id: SessionId,
|
|
457
|
+
seed: readonly SessionEvent[],
|
|
458
|
+
header: SessionHeader,
|
|
459
|
+
inheritedEventCount: SessionLogOffset,
|
|
460
|
+
): Session;
|
|
461
|
+
/**
|
|
462
|
+
* Return the immutable event stored at one exact sequence number.
|
|
463
|
+
* @param seq - event sequence number.
|
|
464
|
+
* @returns the accepted event, or undefined when the log does not contain it.
|
|
465
|
+
*/
|
|
466
|
+
eventAt(seq: SessionSeq): SessionEvent | undefined;
|
|
467
|
+
/**
|
|
468
|
+
* Materialize an immutable snapshot of a half-open event sequence range.
|
|
469
|
+
* A full current snapshot is reused until the next append; every previously
|
|
470
|
+
* returned snapshot remains stable after later appends.
|
|
471
|
+
* @param fromSeq - non-negative inclusive sequence number; defaults to the log start.
|
|
472
|
+
* @param toSeqExclusive - non-negative exclusive sequence number; defaults to the current end.
|
|
473
|
+
* @returns a frozen array of the selected deeply frozen events.
|
|
474
|
+
*/
|
|
475
|
+
snapshotEvents(
|
|
476
|
+
fromSeq: SessionLogOffset = SessionLogOffset(0),
|
|
477
|
+
toSeqExclusive: SessionLogOffset = this.seq,
|
|
478
|
+
): readonly SessionEvent[];
|
|
435
479
|
/**
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* Events and their nested data are deep-frozen at acceptance, so neither a
|
|
439
|
-
* cast nor ordinary JavaScript can rewrite durable history.
|
|
480
|
+
* Return this Session's events after its fork-inherited prefix.
|
|
481
|
+
* @returns a fresh array containing child-owned events in log order.
|
|
440
482
|
*/
|
|
441
|
-
|
|
483
|
+
ownEvents(): readonly SessionEvent[];
|
|
484
|
+
/**
|
|
485
|
+
* Whether one existing event position is outside the fork-inherited prefix.
|
|
486
|
+
* @param seq - event position in this Session.
|
|
487
|
+
* @returns true when the event belongs to this Session rather than its parent.
|
|
488
|
+
*/
|
|
489
|
+
isOwnSeq(seq: SessionSeq): boolean;
|
|
442
490
|
/** The next event's sequence number — always the log length (the `seq = log.length` contiguity contract). */
|
|
443
|
-
get seq():
|
|
491
|
+
get seq(): SessionLogOffset;
|
|
444
492
|
/**
|
|
445
493
|
* Append one typed event to the log and synchronously notify observers via
|
|
446
494
|
* the store-owned, module-private publication hooks. The hot path never blocks
|
|
@@ -458,7 +506,8 @@ declare class Session {
|
|
|
458
506
|
* declare how it joins the surface, the sole source of derived model
|
|
459
507
|
* history) and
|
|
460
508
|
* rejected by the compiler for non-surface types like `turn/start` or
|
|
461
|
-
* `assistant/
|
|
509
|
+
* `assistant/attempt`. Assistant messages embed their exact provider
|
|
510
|
+
* stream and cannot cite top-level source events.
|
|
462
511
|
* @returns the logged event — its assigned `seq`/`time` plus the SNAPSHOT of
|
|
463
512
|
* `data` that entered the log, so reading `event.data` back sees the logged
|
|
464
513
|
* value, never the caller's still-mutable input.
|
|
@@ -468,7 +517,7 @@ declare class Session {
|
|
|
468
517
|
* Map/Set/Date/class instance), or when the candidate violates the
|
|
469
518
|
* canonical surface contract (marker shape and eligibility, unique
|
|
470
519
|
* earlier source-event references, positional replacement validity, and complete
|
|
471
|
-
* shadowed-node coverage). One
|
|
520
|
+
* shadowed-node coverage). One iterative pass reads, validates, and
|
|
472
521
|
* copies each nested value once, so a stateful getter cannot supply one value
|
|
473
522
|
* to validation and another to storage. The event log is the durable source
|
|
474
523
|
* of truth, so a bad event fails at the append site rather than later during
|
|
@@ -479,13 +528,13 @@ declare class Session {
|
|
|
479
528
|
append<T extends SessionEventType>(
|
|
480
529
|
type: T,
|
|
481
530
|
data: SessionEventMap[T],
|
|
482
|
-
...opts: T extends SurfaceEventType ? [opts: SurfaceIntent] : []
|
|
531
|
+
...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
|
|
483
532
|
): SessionEvent<T>;
|
|
484
533
|
/**
|
|
485
534
|
* The {@link EpochHeader} in force after the log's last header event — the
|
|
486
535
|
* header the NEXT request will be compared against — or undefined before
|
|
487
536
|
* the first `request/header` snapshot. The live, incrementally-maintained
|
|
488
|
-
* form of `foldRequestHeader(session.
|
|
537
|
+
* form of `foldRequestHeader(session.snapshotEvents())`: each header event is folded
|
|
489
538
|
* once, when first seen, so a per-step read costs O(new events).
|
|
490
539
|
* @returns the folded header, or undefined when no header event exists yet.
|
|
491
540
|
*/
|
|
@@ -530,17 +579,17 @@ declare class Session {
|
|
|
530
579
|
`Session.deriveMessages()` projects the event log into the `Message[]` the model sees — cached (each surface node projected once, when first seen; a surface rewrite rebuilds) and frozen (a fresh array per call over shared, deep-frozen messages, so mutating logged history through a projection is unrepresentable). `deriveEventMessage(event)` is the per-node pure function the fold applies — public so external reconstructors and the dev invariant project a log prefix with exactly the same rules and cannot disagree with the cache. The projection rules:
|
|
531
580
|
|
|
532
581
|
- `user/message` → a user message carrying exact `content`; an optional envelope remains log-only display metadata.
|
|
533
|
-
- `assistant/message` → an assistant message with the provider and model that produced it plus optional adapter-private replay state.
|
|
582
|
+
- `assistant/message` → an assistant message with the provider and model that produced it plus optional adapter-private replay state. Its embedded compact stream is replay, usage, and UI evidence rather than a second message. An **empty-content** `assistant/message` is also skipped — a max-tokens step cut off with no content still records an `assistant/message` to hold its stream, usage, provider, and model, but a content-less assistant turn must not enter the provider transcript.
|
|
534
583
|
- `tool/result` → a user message carrying a `tool-result` block.
|
|
535
584
|
- `user/message` (injected context, i.e. non-`user` source) → a user-role message carrying its `content` verbatim at its chronological position; its typed source names the producer and carries any producer-specific data.
|
|
536
585
|
|
|
537
|
-
Everything else (`turn/*`, `step/*`, plugin-owned `llm/retry`) is structural and does not project into a message. Token accounting
|
|
586
|
+
Everything else (`turn/*`, `step/*`, `assistant/attempt`, plugin-owned `llm/retry`) is structural and does not project into a message. Token accounting expands the embedded stream on each `assistant/message` or `assistant/attempt`, while the message's top-level `usage` remains the committed-message authority when present. A failed model-request attempt therefore retains its provider usage without fabricating an assistant message. Current logical validation rejects request headers and assistant messages that omit provider/model instead of guessing a route; supported historical representations are normalized and validated by their adjacent format edge before a current Session exists.
|
|
538
587
|
|
|
539
588
|
## Live-session fork API
|
|
540
589
|
|
|
541
590
|
`ctx.sessions.create(id, { seed, meta })` is the low-level replay/fork primitive. For ordinary live-session forks, `SessionStore` exposes one policy API:
|
|
542
591
|
|
|
543
|
-
- `fork(source, boundary?, childSessionId?)` accepts a live `Session` object or live `SessionId`, selects source events through the inclusive `
|
|
592
|
+
- `fork(source, boundary?, childSessionId?)` accepts a live `Session` object or live `SessionId`, selects source events through the inclusive `SessionSeq` boundary (default: current last event), requires the selected prefix to end outside an open turn, then creates a live child session with deep-cloned seed events, `parentSession`, `isSeeded: true`, the exact `inheritedEventCount`, and inherited `cwd`.
|
|
544
593
|
|
|
545
594
|
An explicit `boundary` lets callers fork from any stable between-turn position, including a previous `turn/end` or a later standalone log-only event, even if the source has newer events or an open current turn. The API rejects a prefix that ends inside an open turn instead of clipping silently. Broader execution-relation sanity stays in the existing `dsh-invariants` plugin and persistence repair path rather than being duplicated in `fork()`. `dsh-subagent-fork-in-process` keeps its completed-prefix clipping because tool-time delegation usually starts while the parent turn is open; ordinary session branching should make the requested boundary explicit.
|
|
546
595
|
|
|
@@ -572,8 +621,10 @@ interface TurnEndReasonMap {
|
|
|
572
621
|
/** At least one step reached its output-token ceiling, even if a plugin continued the turn. */
|
|
573
622
|
'max-tokens': { kind: 'max-tokens' }
|
|
574
623
|
/**
|
|
575
|
-
* A
|
|
576
|
-
*
|
|
624
|
+
* A crash-orphaned turn was closed after the fact: agent-loop resume appends
|
|
625
|
+
* this closer for a stored log whose last turn never ended, and session-query
|
|
626
|
+
* synthesizes it on cold reads. The loop never emits this marker live, and
|
|
627
|
+
* the events recorded before the crash remain intact.
|
|
577
628
|
*/
|
|
578
629
|
interrupted: { kind: 'interrupted' }
|
|
579
630
|
}
|
|
@@ -589,9 +640,9 @@ The optional `dsh-session/invariant` companion enforces the relations owned by c
|
|
|
589
640
|
|
|
590
641
|
## The end-seed boundary: `session/end-seed`
|
|
591
642
|
|
|
592
|
-
A
|
|
643
|
+
A fresh fork constructor requires its seed to equal the inherited prefix and appends `session/end-seed { inherited: true }` at the exact durable cut. A restore retains that tagged marker and appends an ordinary `session/end-seed {}` only when its complete stored seed does not already end in a marker. Both forms are log-only and produce no message; `Session`'s constructor is the only legitimate writer.
|
|
593
644
|
|
|
594
|
-
|
|
645
|
+
For fork lineage, locate the LAST marker whose payload carries `inherited: true`; v2 decoding requires it exactly when `SessionHeader.isSeeded` is true and derives `inheritedEventCount` from its seq. For lifecycle ownership, locate the last `session/end-seed` of either form. Reopening a seed that already ends in any marker does not append another ordinary marker.
|
|
595
646
|
|
|
596
647
|
It exists because seed history and live work are otherwise byte-identical, which defeats any plugin owning a standalone open/close bracket: an unmatched `compaction/start` reads the same whether the writer crashed mid-compaction or is compacting right now. An opening marker before `session/end-seed` came from the constructor seed and belongs to an ended lifecycle, whatever ended it (a crash, a succeeding process, or a fork out of a still-running parent), so its owner may treat it as dead. That covers only brackets *this* session inherited: a concurrently live session holding an open bracket over the same history has its own boundary elsewhere, so tolerating concurrent writers needs a liveness signal beyond the log. Core writes the boundary and reads nothing from it — a bracket's vocabulary stays with its owning plugin, which is why crash repair closes turn/step/tool boundaries and never `compaction/*`.
|
|
597
648
|
|
|
@@ -599,18 +650,24 @@ Consumers that order Sessions by human activity exclude this boundary: picking a
|
|
|
599
650
|
|
|
600
651
|
## Plugin-contributed log-only events
|
|
601
652
|
|
|
602
|
-
A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns, and enforces any relation in its own invariant companion. The generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event
|
|
653
|
+
A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns, and enforces any relation in its own invariant companion. The generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event; the compaction seam's `compaction/*` semantics are discussed on [compaction.md](./compaction.md).
|
|
603
654
|
|
|
604
|
-
When several events in one plugin-owned family assemble into one Web Client Conversation Node, every start, update, result, resource, or interruption event in that family carries or independently derives the same stable business id. This requirement applies to correlated Node families, not to every Session event; it lets the client group each event without guessing from adjacency or scanning history. See the [Conversation
|
|
655
|
+
When several events in one plugin-owned family assemble into one Web Client Conversation Node, every start, update, result, resource, or interruption event in that family carries or independently derives the same stable business id. This requirement applies to correlated Node families, not to every Session event; it lets the client group each event without guessing from adjacency or scanning history. See the [Conversation subsystem](./conversation.md).
|
|
605
656
|
|
|
606
657
|
The hook bridges' `hook/invoked` / `hook/result` pairs (from `@deepseek-ai/dsh-hook-protocol`) correlate by `handlerId`. `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, and `Stop` fire inside the loop's open turn, so their `hook/*` records are turn-enclosed by construction. `SessionStart` gets no `hook/*` record because it runs before turn 1; its context remains pending in the inbox until a waking delivery opens a turn (see [the hook-bridges Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-06-30-hook-bridges.md)).
|
|
607
658
|
|
|
608
659
|
## Durability contract
|
|
609
660
|
|
|
610
|
-
What a persistence backend relies on: the durable log persists every event losslessly,
|
|
661
|
+
What a persistence backend relies on: the durable log persists every event losslessly, and every Assistant attempt is one `assistant/message` or `assistant/attempt` whose embedded compact stream preserves the original timed chunks. `seq` stays contiguous across these settlements and all interleaved events. A backend may choose its own storage framing for an event batch as long as a handle's `read()` returns the exact appended events; current JSONL v2 writes one row per event (see [persistence.md](./persistence.md)). All `event.data` must be JSON-serializable; `Session.append` enforces this at the source (throwing on non-serializable data), so a bad event never enters the log and `session.snapshotEvents()` always equals what a backend can persist. Adding an event type that carries non-serializable data, corrupts core execution nesting, or violates its owner's declared relation is a breaking change to the on-disk format.
|
|
611
662
|
|
|
612
663
|
The backends that consume this contract are on [persistence.md](./persistence.md).
|
|
613
664
|
|
|
665
|
+
## Remote catalog and workspace opening
|
|
666
|
+
|
|
667
|
+
`ModelCatalog` is the Host-generation model directory returned by `session/modelCatalog`: it carries the deployment default, routable provider ids, successful provider groups, and isolated provider failures. It is not derived from one Session and remains separate from Session projections.
|
|
668
|
+
|
|
669
|
+
`SessionOpenWorkspacePathRequest` carries an absolute or workspace-resolved `path`. `SessionOpenWorkspacePathValue` confirms that the Host accepted the native handoff. A Session-aware Client resolves relative paths against its current Session cwd when known; the controller hands the path to the opener unchanged and reports invalid requests, cancellation, and opener failures through the Session Remote error vocabulary.
|
|
670
|
+
|
|
614
671
|
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
|
615
672
|
|
|
616
673
|
<a id="cordis-surface"></a>
|
|
@@ -619,13 +676,158 @@ The backends that consume this contract are on [persistence.md](./persistence.md
|
|
|
619
676
|
|
|
620
677
|
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
|
|
621
678
|
|
|
679
|
+
<a id="ctxsessioncontroller--sessioncontroller"></a>
|
|
680
|
+
|
|
681
|
+
### `ctx.sessionController` — `SessionController`
|
|
682
|
+
|
|
683
|
+
Host service backing the generated `ctx.remote.session` namespace.
|
|
684
|
+
|
|
685
|
+
```ts cordis-catalog
|
|
686
|
+
/**
|
|
687
|
+
* Resolve or resume one ordinary Session for another Host API domain.
|
|
688
|
+
* @param sessionId - Session identity whose Agent owns the operation.
|
|
689
|
+
* @returns the live Agent or the stable Session-domain failure.
|
|
690
|
+
*/
|
|
691
|
+
resolveAgent(sessionId: SessionId): Promise<ApiSessionAgentResult>
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Inspect one attached or persisted Session without activating its Agent.
|
|
695
|
+
* @param sessionId - durable Session identity.
|
|
696
|
+
* @param signal - optional caller cancellation for persistence reads.
|
|
697
|
+
* @returns the current attached state or persisted header and event prefix.
|
|
698
|
+
*/
|
|
699
|
+
inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<SessionInspection>
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Read all visible Session rows without resuming an Agent.
|
|
703
|
+
* @param _request - reserved empty list request.
|
|
704
|
+
* @param signal - cancellation for persistence reads.
|
|
705
|
+
* @returns visible Session summaries ordered by activity.
|
|
706
|
+
*/
|
|
707
|
+
@Remote('list') async list(_request: SessionListRequest, signal: AbortSignal): Promise<SessionListValue>
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Search visible Session content without resuming an Agent.
|
|
711
|
+
* @param request - literal message-content query.
|
|
712
|
+
* @param signal - cancellation for list and search reads.
|
|
713
|
+
* @returns authorized bounded Session search results.
|
|
714
|
+
*/
|
|
715
|
+
@Remote('search') search(request: SessionSearchRequest, signal: AbortSignal): Promise<SessionSearchValue>
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Create or idempotently adopt one ordinary Session.
|
|
719
|
+
* @param request - requested identity, location, and Agent preset.
|
|
720
|
+
* @returns the Session identity and resolved preset when configured.
|
|
721
|
+
*/
|
|
722
|
+
@Remote('create') create(request: SessionCreateRequest): Promise<SessionCreateValue>
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Select one Session-local model after explicitly resuming the Session.
|
|
726
|
+
* @param request - Session identity and requested model selection.
|
|
727
|
+
* @returns the normalized selection installed for the Session.
|
|
728
|
+
*/
|
|
729
|
+
@Remote('selectModel') selectModel(request: SessionSelectModelRequest): Promise<SessionSelectModelValue>
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Describe every currently routable model for Host-generation selectors.
|
|
733
|
+
* @returns provider-grouped models, the deployment default, and isolated provider failures.
|
|
734
|
+
*/
|
|
735
|
+
@Remote('modelCatalog') modelCatalog(): Promise<ModelCatalog>
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Report whether this deployment can hand a Session workspace path to a native desktop.
|
|
739
|
+
* @returns true when the matching open operation is available.
|
|
740
|
+
*/
|
|
741
|
+
@Remote canOpenWorkspacePath(): boolean
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Open one path prepared by a Session-aware caller on the Host desktop.
|
|
745
|
+
* @param request - path after best-effort Session workspace resolution.
|
|
746
|
+
* @param signal - caller lifetime; abort terminates the native command.
|
|
747
|
+
* @returns confirmation after the native opener accepts the path.
|
|
748
|
+
* @throws RemoteError when the request is invalid, cancelled, or the opener fails.
|
|
749
|
+
*/
|
|
750
|
+
@Remote('openWorkspacePath') async openWorkspacePath( request: SessionOpenWorkspacePathRequest, signal: AbortSignal, ): Promise<SessionOpenWorkspacePathValue>
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Rename one Session after explicitly resuming it.
|
|
754
|
+
* @param request - Session identity and proposed title.
|
|
755
|
+
* @returns the accepted title and durable event sequence.
|
|
756
|
+
*/
|
|
757
|
+
@Remote('rename') rename(request: SessionRenameRequest): Promise<SessionRenameValue>
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Fork one cold-readable completed-turn prefix into a new Session.
|
|
761
|
+
* @param request - source Session and optional event anchor.
|
|
762
|
+
* @returns the new Session identity.
|
|
763
|
+
*/
|
|
764
|
+
@Remote('fork') fork(request: SessionForkRequest): Promise<SessionForkValue>
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Admit one prompt after explicitly resuming its Session.
|
|
768
|
+
* @param request - Session identity, prompt content, source metadata, and delivery mode.
|
|
769
|
+
* @param signal - caller cancellation before prompt admission begins.
|
|
770
|
+
* @returns acknowledgement that the Agent accepted the prompt.
|
|
771
|
+
*/
|
|
772
|
+
@Remote('prompt') prompt(request: SessionPromptRequest, signal: AbortSignal): Promise<SessionPromptValue>
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Read one image proven reachable from the addressed Session log.
|
|
776
|
+
* @param request - Session and attachment identities used for authorization.
|
|
777
|
+
* @returns the durable attachment reference and base64-encoded bytes.
|
|
778
|
+
*/
|
|
779
|
+
@Remote('attachment') attachment(request: SessionAttachmentRequest): Promise<SessionAttachmentValue>
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Mutate one still-pending queue occurrence on a live Agent.
|
|
783
|
+
* @param request - Session, queue item, and requested mutation.
|
|
784
|
+
* @returns acknowledgement that the queue mutation was applied.
|
|
785
|
+
*/
|
|
786
|
+
@Remote('updateQueue') updateQueue(request: SessionUpdateQueueRequest): SessionUpdateQueueValue
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Cancel one active Agent turn without dropping its pending inbox.
|
|
790
|
+
* @param request - Session whose active Agent turn is cancelled.
|
|
791
|
+
* @returns acknowledgement that cancellation was requested.
|
|
792
|
+
*/
|
|
793
|
+
@Remote('cancel') cancel(request: SessionCancelRequest): SessionCancelValue
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Read one cold-safe, message-aligned Session history page.
|
|
797
|
+
* @param request - durable address, backward cursor, and page budget.
|
|
798
|
+
* @param signal - cancellation for persistence reads.
|
|
799
|
+
* @returns one chronological page.
|
|
800
|
+
*/
|
|
801
|
+
@Remote('page') page(request: SessionPageRequest, signal: AbortSignal): Promise<SessionPage>
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Follow one Session log from its opening or resume cursor.
|
|
805
|
+
* @param request - durable address and last committed sequence already held by the caller.
|
|
806
|
+
* @param signal - cancellation owned by the Remote stream carrier.
|
|
807
|
+
* @returns a complete opening snapshot followed by gap-free durable event
|
|
808
|
+
* frames and optional cursorless assistant-stream frames.
|
|
809
|
+
*/
|
|
810
|
+
@Remote({ mode: 'stream' }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Stream a complete live-control baseline followed by replacement frames.
|
|
814
|
+
* @param signal - cancellation owned by the Remote stream carrier.
|
|
815
|
+
* @returns one complete baseline followed by live replacement frames.
|
|
816
|
+
*/
|
|
817
|
+
@Remote({ mode: 'stream' }) control(signal: AbortSignal): AsyncIterable<SessionControlFrame>
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
Types: [SessionId](./core.md) · [SessionInspection](./persistence.md) · [SessionSearchRequest](./session-query.md)
|
|
821
|
+
|
|
822
|
+
Source: [`packages/api/session-controller/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/index.ts)
|
|
823
|
+
|
|
622
824
|
<a id="ctxsessions--sessionstore"></a>
|
|
623
825
|
|
|
624
826
|
### `ctx.sessions` — `SessionStore`
|
|
625
827
|
|
|
626
828
|
In-memory session store (`ctx.sessions`).
|
|
627
829
|
|
|
628
|
-
Persistence is intentionally not implemented here —
|
|
830
|
+
Persistence is intentionally not implemented here — the agent lifecycle attaches a session-log writer to each published session's write handle; a session published outside that lifecycle persists nothing.
|
|
629
831
|
|
|
630
832
|
```ts cordis-catalog
|
|
631
833
|
/**
|
|
@@ -748,13 +950,113 @@ list(): Session[]
|
|
|
748
950
|
* `SessionStore`'s id policy.
|
|
749
951
|
* @returns The created live child session.
|
|
750
952
|
*/
|
|
751
|
-
fork(source: SessionForkSource, boundary?:
|
|
953
|
+
fork(source: SessionForkSource, boundary?: SessionSeq, childSessionId?: SessionId): Session
|
|
752
954
|
```
|
|
753
955
|
|
|
754
956
|
Types: [CreateSessionOptions](./persistence.md) · [PrepareSessionOptions](./persistence.md) · [SessionId](./core.md)
|
|
755
957
|
|
|
756
958
|
Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
|
|
757
959
|
|
|
960
|
+
<a id="api-session-events"></a>
|
|
961
|
+
|
|
962
|
+
### `api-session/*` events
|
|
963
|
+
|
|
964
|
+
<a id="api-sessionactivity--emit"></a>
|
|
965
|
+
|
|
966
|
+
#### `api-session/activity` — emit
|
|
967
|
+
|
|
968
|
+
One user-authored durable message advanced Session list activity.
|
|
969
|
+
|
|
970
|
+
```ts cordis-catalog
|
|
971
|
+
/**
|
|
972
|
+
* One user-authored durable message advanced Session list activity.
|
|
973
|
+
* @mode emit
|
|
974
|
+
* @param sessionId - addressed Session identity.
|
|
975
|
+
* @param updatedAt - durable message time used for list ordering.
|
|
976
|
+
*/
|
|
977
|
+
'api-session/activity'(sessionId: SessionId, updatedAt: number): void
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
Types: [SessionId](./core.md)
|
|
981
|
+
|
|
982
|
+
Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
|
|
983
|
+
|
|
984
|
+
<a id="api-sessionadded--emit"></a>
|
|
985
|
+
|
|
986
|
+
#### `api-session/added` — emit
|
|
987
|
+
|
|
988
|
+
A Session became visible to Session list consumers.
|
|
989
|
+
|
|
990
|
+
```ts cordis-catalog
|
|
991
|
+
/**
|
|
992
|
+
* A Session became visible to Session list consumers.
|
|
993
|
+
* @mode emit
|
|
994
|
+
* @param summary - initial list row for the Session.
|
|
995
|
+
*/
|
|
996
|
+
'api-session/added'(summary: SessionSummary): void
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
|
|
1000
|
+
|
|
1001
|
+
<a id="api-sessionerror--emit"></a>
|
|
1002
|
+
|
|
1003
|
+
#### `api-session/error` — emit
|
|
1004
|
+
|
|
1005
|
+
One Agent failed outside a durable turn position.
|
|
1006
|
+
|
|
1007
|
+
```ts cordis-catalog
|
|
1008
|
+
/**
|
|
1009
|
+
* One Agent failed outside a durable turn position.
|
|
1010
|
+
* @mode emit
|
|
1011
|
+
* @param sessionId - Agent and Session identity.
|
|
1012
|
+
* @param message - user-safe failure chain.
|
|
1013
|
+
*/
|
|
1014
|
+
'api-session/error'(sessionId: SessionId, message: string): void
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
Types: [SessionId](./core.md)
|
|
1018
|
+
|
|
1019
|
+
Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
|
|
1020
|
+
|
|
1021
|
+
<a id="api-sessionremoved--emit"></a>
|
|
1022
|
+
|
|
1023
|
+
#### `api-session/removed` — emit
|
|
1024
|
+
|
|
1025
|
+
A Session left the live Host registry.
|
|
1026
|
+
|
|
1027
|
+
```ts cordis-catalog
|
|
1028
|
+
/**
|
|
1029
|
+
* A Session left the live Host registry.
|
|
1030
|
+
* @mode emit
|
|
1031
|
+
* @param sessionId - removed Session identity.
|
|
1032
|
+
*/
|
|
1033
|
+
'api-session/removed'(sessionId: SessionId): void
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
Types: [SessionId](./core.md)
|
|
1037
|
+
|
|
1038
|
+
Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
|
|
1039
|
+
|
|
1040
|
+
<a id="api-sessionstatus--emit"></a>
|
|
1041
|
+
|
|
1042
|
+
#### `api-session/status` — emit
|
|
1043
|
+
|
|
1044
|
+
One Agent changed running state.
|
|
1045
|
+
|
|
1046
|
+
```ts cordis-catalog
|
|
1047
|
+
/**
|
|
1048
|
+
* One Agent changed running state.
|
|
1049
|
+
* @mode emit
|
|
1050
|
+
* @param sessionId - Agent and Session identity.
|
|
1051
|
+
* @param running - whether the Agent is running.
|
|
1052
|
+
*/
|
|
1053
|
+
'api-session/status'(sessionId: SessionId, running: boolean): void
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
Types: [SessionId](./core.md)
|
|
1057
|
+
|
|
1058
|
+
Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
|
|
1059
|
+
|
|
758
1060
|
<a id="session-events"></a>
|
|
759
1061
|
|
|
760
1062
|
### `session/*` events
|