dsh-plugin-guide 0.3.3 → 0.3.5
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/README.es.md +5 -1
- package/README.hi.md +5 -1
- package/README.md +5 -1
- package/README.pt.md +5 -1
- package/README.zh.md +5 -1
- package/dist/dsh-plugin-dev.js +3 -3
- package/guide/plugin-dev-guide.md +2 -2
- package/guide/quick-reference.es.md +2 -2
- package/guide/quick-reference.hi.md +1 -1
- package/guide/quick-reference.md +2 -2
- package/guide/quick-reference.pt.md +2 -2
- package/guide/quick-reference.zh-CN.md +2 -2
- package/package.json +5 -4
- package/references/official-docs/AGENTS.md +2 -2
- package/references/official-docs/SNAPSHOT.md +3 -3
- package/references/official-docs/THIRD_PARTY_NOTICES.md +4 -6
- package/references/official-docs/docs/agent-lifecycle.i18n.yaml +2 -2
- package/references/official-docs/docs/agent-lifecycle.md +5 -3
- package/references/official-docs/docs/agent-lifecycle.zh.md +5 -3
- package/references/official-docs/docs/architecture.i18n.yaml +2 -2
- package/references/official-docs/docs/architecture.md +7 -3
- package/references/official-docs/docs/architecture.zh.md +7 -3
- package/references/official-docs/docs/capability-seams.i18n.yaml +2 -2
- package/references/official-docs/docs/capability-seams.md +5 -0
- package/references/official-docs/docs/capability-seams.zh.md +5 -0
- package/references/official-docs/docs/config-catalog.i18n.yaml +2 -2
- package/references/official-docs/docs/config-catalog.md +15 -21
- package/references/official-docs/docs/config-catalog.zh.md +15 -21
- package/references/official-docs/docs/cookbook/extension-cookbook.i18n.yaml +2 -2
- package/references/official-docs/docs/cookbook/extension-cookbook.md +12 -10
- package/references/official-docs/docs/cookbook/extension-cookbook.zh.md +12 -10
- package/references/official-docs/docs/deepseek-llm-api-wire-extensions.i18n.yaml +2 -2
- package/references/official-docs/docs/deepseek-llm-api-wire-extensions.md +16 -12
- package/references/official-docs/docs/deepseek-llm-api-wire-extensions.zh.md +16 -12
- package/references/official-docs/docs/event-producer-consumer.i18n.yaml +2 -2
- package/references/official-docs/docs/event-producer-consumer.md +28 -27
- package/references/official-docs/docs/event-producer-consumer.zh.md +32 -31
- package/references/official-docs/docs/module-graph.i18n.yaml +2 -2
- package/references/official-docs/docs/module-graph.md +36 -14
- package/references/official-docs/docs/module-graph.zh.md +36 -14
- package/references/official-docs/docs/persistence-catalog.i18n.yaml +2 -2
- package/references/official-docs/docs/persistence-catalog.md +49 -39
- package/references/official-docs/docs/persistence-catalog.zh.md +39 -29
- package/references/official-docs/docs/subsystems/attachment.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/attachment.md +157 -4
- package/references/official-docs/docs/subsystems/attachment.zh.md +157 -4
- package/references/official-docs/docs/subsystems/commands.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/commands.md +24 -15
- package/references/official-docs/docs/subsystems/commands.zh.md +24 -15
- package/references/official-docs/docs/subsystems/conversation.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/conversation.md +4 -4
- package/references/official-docs/docs/subsystems/conversation.zh.md +4 -4
- package/references/official-docs/docs/subsystems/core.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/core.md +62 -1
- package/references/official-docs/docs/subsystems/core.zh.md +62 -1
- package/references/official-docs/docs/subsystems/llm-streaming.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/llm-streaming.md +23 -2
- package/references/official-docs/docs/subsystems/llm-streaming.zh.md +23 -2
- package/references/official-docs/docs/subsystems/persistence.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/persistence.md +10 -10
- package/references/official-docs/docs/subsystems/persistence.zh.md +10 -10
- package/references/official-docs/docs/subsystems/session-projection.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-projection.md +19 -0
- package/references/official-docs/docs/subsystems/session-projection.zh.md +19 -0
- package/references/official-docs/docs/subsystems/session-reference.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-reference.md +25 -1
- package/references/official-docs/docs/subsystems/session-reference.zh.md +25 -1
- package/references/official-docs/docs/subsystems/session-telemetry.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-telemetry.md +6 -5
- package/references/official-docs/docs/subsystems/session-telemetry.zh.md +6 -5
- package/references/official-docs/docs/subsystems/session.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session.md +52 -40
- package/references/official-docs/docs/subsystems/session.zh.md +52 -40
- package/references/official-docs/docs/subsystems/web-client.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/web-client.md +1 -1
- package/references/official-docs/docs/subsystems/web-client.zh.md +1 -1
- package/references/official-docs/docs/subsystems/workspace.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/workspace.md +8 -8
- package/references/official-docs/docs/subsystems/workspace.zh.md +8 -8
- package/references/official-docs/docs/testing.i18n.yaml +2 -2
- package/references/official-docs/docs/testing.md +2 -2
- package/references/official-docs/docs/testing.zh.md +2 -2
- package/references/official-docs/docs/user/guide/network-proxy.i18n.yaml +6 -0
- package/references/official-docs/docs/user/guide/network-proxy.md +85 -0
- package/references/official-docs/docs/user/guide/network-proxy.zh.md +85 -0
- package/references/official-docs/docs/user/guide/providers.i18n.yaml +2 -2
- package/references/official-docs/docs/user/guide/providers.md +70 -17
- package/references/official-docs/docs/user/guide/providers.zh.md +70 -17
- package/references/official-docs/docs/web-styling.i18n.yaml +2 -2
- package/references/official-docs/docs/web-styling.md +1 -0
- package/references/official-docs/docs/web-styling.zh.md +1 -0
- package/references/official-docs/website-docs.ts +8 -0
- package/templates/js/README.es.md +1 -1
- package/templates/js/README.hi.md +1 -1
- package/templates/js/README.md +1 -1
- package/templates/js/README.pt.md +1 -1
- package/templates/js/README.zh.md +1 -1
- package/templates/ts/README.es.md +1 -1
- package/templates/ts/README.hi.md +1 -1
- package/templates/ts/README.md +1 -1
- package/templates/ts/README.pt.md +1 -1
- package/templates/ts/README.zh.md +1 -1
|
@@ -21,8 +21,8 @@ interface UserMessage extends Message {
|
|
|
21
21
|
/**
|
|
22
22
|
* The merge-extensible, append-only source of truth for an agent interaction.
|
|
23
23
|
* Message history is derived from this log. Every event is lossless JSON and
|
|
24
|
-
* sequence numbers stay contiguous
|
|
25
|
-
*
|
|
24
|
+
* sequence numbers stay contiguous. Assistant attempt events embed their exact
|
|
25
|
+
* compact raw streams so persistence stores one durable settlement per attempt.
|
|
26
26
|
*/
|
|
27
27
|
interface SessionEventMap {
|
|
28
28
|
/**
|
|
@@ -53,8 +53,6 @@ interface SessionEventMap {
|
|
|
53
53
|
* project their `content` verbatim; `source` tells them apart.
|
|
54
54
|
*/
|
|
55
55
|
'user/message': UserMessage
|
|
56
|
-
/** Raw stream chunk — token-level replay fidelity. */
|
|
57
|
-
'assistant/chunk': { turn: number; step: number; chunk: StreamChunk }
|
|
58
56
|
/**
|
|
59
57
|
* Assembled assistant message for one step (derived history uses this).
|
|
60
58
|
* Carries the step's `usage` when the adapter reported token accounting, so
|
|
@@ -65,7 +63,21 @@ interface SessionEventMap {
|
|
|
65
63
|
* marker distinguishes that prefix without re-deriving interruption from turn
|
|
66
64
|
* boundaries. An aborted turn with no such event streamed no visible content.
|
|
67
65
|
*/
|
|
68
|
-
'assistant/message': {
|
|
66
|
+
'assistant/message': {
|
|
67
|
+
turn: number
|
|
68
|
+
step: number
|
|
69
|
+
message: AssistantMessage
|
|
70
|
+
/** Exact timed model stream, compacted without joining delta boundaries. */
|
|
71
|
+
stream: AssistantStreamRecord[]
|
|
72
|
+
usage?: TokenUsage
|
|
73
|
+
interrupted?: true
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* One model attempt that committed no surface message. The embedded stream
|
|
77
|
+
* preserves a failed, retried, cancelled, or stream-error attempt that
|
|
78
|
+
* reached settlement without fabricating model-visible history.
|
|
79
|
+
*/
|
|
80
|
+
'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[] }
|
|
69
81
|
/**
|
|
70
82
|
* The model requested one tool invocation: `name` with the raw `arguments`
|
|
71
83
|
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
|
|
@@ -109,12 +121,12 @@ interface SessionEventMap {
|
|
|
109
121
|
* Marks the end of a constructor seed. Events before it have smaller seq
|
|
110
122
|
* values and came from the seed (resume, fork, or replay); this lifecycle
|
|
111
123
|
* produced none of them. This log-only event is the durable projection of
|
|
112
|
-
* {@link Session.firstLiveSeq}.
|
|
113
|
-
* carry the meaning.
|
|
124
|
+
* {@link Session.firstLiveSeq}.
|
|
114
125
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
126
|
+
* A fresh fork child owns one `{ inherited: true }` marker at its exact
|
|
127
|
+
* inherited-prefix cut, even when that prefix ends in an ancestor marker.
|
|
128
|
+
* The last tagged marker is the current Session's cut; untagged markers keep
|
|
129
|
+
* ordinary restore and replay lifecycle boundaries.
|
|
118
130
|
*
|
|
119
131
|
* `Session`'s constructor is the only legitimate writer. The invariant
|
|
120
132
|
* companion deliberately constrains nothing here, so a plugin appending one
|
|
@@ -127,7 +139,7 @@ interface SessionEventMap {
|
|
|
127
139
|
* writers — a concurrently live session holds its own boundary elsewhere,
|
|
128
140
|
* so tolerating concurrent writers needs a signal beyond the log.
|
|
129
141
|
*/
|
|
130
|
-
'session/end-seed':
|
|
142
|
+
'session/end-seed': { inherited?: true }
|
|
131
143
|
}
|
|
132
144
|
```
|
|
133
145
|
|
|
@@ -211,7 +223,7 @@ type OptionalSessionSeq = SessionSeq | null
|
|
|
211
223
|
* The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
|
|
212
224
|
* they only exist on {@link SurfaceEventType} variants (`user/message`,
|
|
213
225
|
* `assistant/message`, `tool/result`).
|
|
214
|
-
* Non-surface events (boundary markers,
|
|
226
|
+
* Non-surface events (boundary markers, attempts, errors) never carry
|
|
215
227
|
* surface metadata — the compiler enforces this at `Session.append()`
|
|
216
228
|
* call sites.
|
|
217
229
|
*/
|
|
@@ -236,12 +248,9 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
236
248
|
ignorable?: true
|
|
237
249
|
} & (K extends SurfaceEventType ? {
|
|
238
250
|
/**
|
|
239
|
-
* Seq numbers of earlier events that this event cites as sources
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* `assistant/message` may carry a present empty array for a known empty
|
|
243
|
-
* provider stream; when the field is absent, the event does not record which
|
|
244
|
-
* earlier events produced the message.
|
|
251
|
+
* Seq numbers of earlier events that this event cites as sources, such as
|
|
252
|
+
* the surface nodes shadowed by a compaction replacement. A v2
|
|
253
|
+
* `assistant/message` embeds its provider stream and cannot carry this field.
|
|
245
254
|
*/
|
|
246
255
|
sourceEventSeqs?: SessionSeq[]
|
|
247
256
|
/** How this event entered the surface; absent for non-surface events. */
|
|
@@ -252,7 +261,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
252
261
|
|
|
253
262
|
`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`.
|
|
254
263
|
|
|
255
|
-
|
|
264
|
+
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.
|
|
256
265
|
|
|
257
266
|
## Surface types
|
|
258
267
|
|
|
@@ -264,7 +273,8 @@ The three message-producing types (`SurfaceEventType` — `user/message`, `assis
|
|
|
264
273
|
/**
|
|
265
274
|
* The subset of {@link SessionEventType} values whose events produce LLM
|
|
266
275
|
* messages and are eligible to appear on the ordered surface. Only these
|
|
267
|
-
* event types may carry {@link SurfaceOp} and
|
|
276
|
+
* event types may carry {@link SurfaceOp}; user and tool events may also cite
|
|
277
|
+
* earlier sources through {@link SessionEvent.sourceEventSeqs}.
|
|
268
278
|
*/
|
|
269
279
|
type SurfaceEventType =
|
|
270
280
|
| 'user/message'
|
|
@@ -302,21 +312,20 @@ type SurfaceOp =
|
|
|
302
312
|
* Surface placement and cited source-event seqs for {@link Session.append}. Required on
|
|
303
313
|
* message-producing events and forbidden on log-only events.
|
|
304
314
|
*/
|
|
305
|
-
|
|
315
|
+
type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
|
|
306
316
|
surfaceOp: SurfaceOp
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
*/
|
|
317
|
+
} & (T extends 'assistant/message' ? {
|
|
318
|
+
/** V2 Assistant messages embed their provider stream instead of citing source events. */
|
|
319
|
+
sourceEventSeqs?: never
|
|
320
|
+
} : {
|
|
321
|
+
/** Complete non-empty set of known earlier source-event seqs. */
|
|
313
322
|
sourceEventSeqs?: SessionSeq[]
|
|
314
|
-
}
|
|
323
|
+
})
|
|
315
324
|
```
|
|
316
325
|
|
|
317
326
|
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](../../packages/core/session/README.md)). Non-surface types reject it at compile time.
|
|
318
327
|
|
|
319
|
-
|
|
328
|
+
`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.
|
|
320
329
|
|
|
321
330
|
### `SessionSurface` — the live readonly surface projection
|
|
322
331
|
|
|
@@ -395,9 +404,10 @@ declare class Session {
|
|
|
395
404
|
* The first seq appended IN THIS PROCESS: the length of the constructor
|
|
396
405
|
* seed (0 without one). Events with smaller seq values entered through
|
|
397
406
|
* construction — replay, fork, or resume — and were never published on the
|
|
398
|
-
* `session/event` firehose (constructor seeds do not emit)
|
|
399
|
-
*
|
|
400
|
-
*
|
|
407
|
+
* `session/event` firehose (constructor seeds do not emit). This offset marks
|
|
408
|
+
* the constructor-input boundary for lifecycle ownership and persistence
|
|
409
|
+
* adoption; consumers that need complete canonical history still start at
|
|
410
|
+
* seq 0. Distinct from {@link inheritedEventCount}, the DURABLE
|
|
401
411
|
* fork-lineage cut: a resumed session's constructor seed is its full stored
|
|
402
412
|
* log, while the inherited count keeps the original fork value — this field is the
|
|
403
413
|
* in-process construction fact.
|
|
@@ -493,7 +503,8 @@ declare class Session {
|
|
|
493
503
|
* declare how it joins the surface, the sole source of derived model
|
|
494
504
|
* history) and
|
|
495
505
|
* rejected by the compiler for non-surface types like `turn/start` or
|
|
496
|
-
* `assistant/
|
|
506
|
+
* `assistant/attempt`. Assistant messages embed their exact provider
|
|
507
|
+
* stream and cannot cite top-level source events.
|
|
497
508
|
* @returns the logged event — its assigned `seq`/`time` plus the SNAPSHOT of
|
|
498
509
|
* `data` that entered the log, so reading `event.data` back sees the logged
|
|
499
510
|
* value, never the caller's still-mutable input.
|
|
@@ -514,7 +525,7 @@ declare class Session {
|
|
|
514
525
|
append<T extends SessionEventType>(
|
|
515
526
|
type: T,
|
|
516
527
|
data: SessionEventMap[T],
|
|
517
|
-
...opts: T extends SurfaceEventType ? [opts: SurfaceIntent] : []
|
|
528
|
+
...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
|
|
518
529
|
): SessionEvent<T>;
|
|
519
530
|
/**
|
|
520
531
|
* The {@link EpochHeader} in force after the log's last header event — the
|
|
@@ -565,11 +576,11 @@ declare class Session {
|
|
|
565
576
|
`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:
|
|
566
577
|
|
|
567
578
|
- `user/message` → a user message carrying exact `content`; an optional envelope remains log-only display metadata.
|
|
568
|
-
- `assistant/message` → an assistant message with the provider and model that produced it plus optional adapter-private replay state.
|
|
579
|
+
- `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.
|
|
569
580
|
- `tool/result` → a user message carrying a `tool-result` block.
|
|
570
581
|
- `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.
|
|
571
582
|
|
|
572
|
-
Everything else (`turn/*`, `step/*`, plugin-owned `llm/retry`) is structural and does not project into a message. Token accounting
|
|
583
|
+
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.
|
|
573
584
|
|
|
574
585
|
## Live-session fork API
|
|
575
586
|
|
|
@@ -626,9 +637,9 @@ The optional `dsh-session/invariant` companion enforces the relations owned by c
|
|
|
626
637
|
|
|
627
638
|
## The end-seed boundary: `session/end-seed`
|
|
628
639
|
|
|
629
|
-
A
|
|
640
|
+
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.
|
|
630
641
|
|
|
631
|
-
|
|
642
|
+
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.
|
|
632
643
|
|
|
633
644
|
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/*`.
|
|
634
645
|
|
|
@@ -644,7 +655,7 @@ The hook bridges' `hook/invoked` / `hook/result` pairs (from `@deepseek-ai/dsh-h
|
|
|
644
655
|
|
|
645
656
|
## Durability contract
|
|
646
657
|
|
|
647
|
-
What a persistence backend relies on: the durable log persists every event losslessly,
|
|
658
|
+
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.
|
|
648
659
|
|
|
649
660
|
The backends that consume this contract are on [persistence.md](persistence.md).
|
|
650
661
|
|
|
@@ -790,7 +801,8 @@ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<SessionInspectio
|
|
|
790
801
|
* Follow one Session log from its opening or resume cursor.
|
|
791
802
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
792
803
|
* @param signal - cancellation owned by the Remote stream carrier.
|
|
793
|
-
* @returns a complete opening snapshot followed by gap-free event
|
|
804
|
+
* @returns a complete opening snapshot followed by gap-free durable event
|
|
805
|
+
* frames and optional cursorless assistant-stream frames.
|
|
794
806
|
*/
|
|
795
807
|
@Remote({ mode: 'stream' }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
|
|
796
808
|
|
|
@@ -21,8 +21,8 @@ interface UserMessage extends Message {
|
|
|
21
21
|
/**
|
|
22
22
|
* The merge-extensible, append-only source of truth for an agent interaction.
|
|
23
23
|
* Message history is derived from this log. Every event is lossless JSON and
|
|
24
|
-
* sequence numbers stay contiguous
|
|
25
|
-
*
|
|
24
|
+
* sequence numbers stay contiguous. Assistant attempt events embed their exact
|
|
25
|
+
* compact raw streams so persistence stores one durable settlement per attempt.
|
|
26
26
|
*/
|
|
27
27
|
interface SessionEventMap {
|
|
28
28
|
/**
|
|
@@ -53,8 +53,6 @@ interface SessionEventMap {
|
|
|
53
53
|
* project their `content` verbatim; `source` tells them apart.
|
|
54
54
|
*/
|
|
55
55
|
'user/message': UserMessage
|
|
56
|
-
/** Raw stream chunk — token-level replay fidelity. */
|
|
57
|
-
'assistant/chunk': { turn: number; step: number; chunk: StreamChunk }
|
|
58
56
|
/**
|
|
59
57
|
* Assembled assistant message for one step (derived history uses this).
|
|
60
58
|
* Carries the step's `usage` when the adapter reported token accounting, so
|
|
@@ -65,7 +63,21 @@ interface SessionEventMap {
|
|
|
65
63
|
* marker distinguishes that prefix without re-deriving interruption from turn
|
|
66
64
|
* boundaries. An aborted turn with no such event streamed no visible content.
|
|
67
65
|
*/
|
|
68
|
-
'assistant/message': {
|
|
66
|
+
'assistant/message': {
|
|
67
|
+
turn: number
|
|
68
|
+
step: number
|
|
69
|
+
message: AssistantMessage
|
|
70
|
+
/** Exact timed model stream, compacted without joining delta boundaries. */
|
|
71
|
+
stream: AssistantStreamRecord[]
|
|
72
|
+
usage?: TokenUsage
|
|
73
|
+
interrupted?: true
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* One model attempt that committed no surface message. The embedded stream
|
|
77
|
+
* preserves a failed, retried, cancelled, or stream-error attempt that
|
|
78
|
+
* reached settlement without fabricating model-visible history.
|
|
79
|
+
*/
|
|
80
|
+
'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[] }
|
|
69
81
|
/**
|
|
70
82
|
* The model requested one tool invocation: `name` with the raw `arguments`
|
|
71
83
|
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
|
|
@@ -109,12 +121,12 @@ interface SessionEventMap {
|
|
|
109
121
|
* Marks the end of a constructor seed. Events before it have smaller seq
|
|
110
122
|
* values and came from the seed (resume, fork, or replay); this lifecycle
|
|
111
123
|
* produced none of them. This log-only event is the durable projection of
|
|
112
|
-
* {@link Session.firstLiveSeq}.
|
|
113
|
-
* carry the meaning.
|
|
124
|
+
* {@link Session.firstLiveSeq}.
|
|
114
125
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
126
|
+
* A fresh fork child owns one `{ inherited: true }` marker at its exact
|
|
127
|
+
* inherited-prefix cut, even when that prefix ends in an ancestor marker.
|
|
128
|
+
* The last tagged marker is the current Session's cut; untagged markers keep
|
|
129
|
+
* ordinary restore and replay lifecycle boundaries.
|
|
118
130
|
*
|
|
119
131
|
* `Session`'s constructor is the only legitimate writer. The invariant
|
|
120
132
|
* companion deliberately constrains nothing here, so a plugin appending one
|
|
@@ -127,7 +139,7 @@ interface SessionEventMap {
|
|
|
127
139
|
* writers — a concurrently live session holds its own boundary elsewhere,
|
|
128
140
|
* so tolerating concurrent writers needs a signal beyond the log.
|
|
129
141
|
*/
|
|
130
|
-
'session/end-seed':
|
|
142
|
+
'session/end-seed': { inherited?: true }
|
|
131
143
|
}
|
|
132
144
|
```
|
|
133
145
|
|
|
@@ -211,7 +223,7 @@ type OptionalSessionSeq = SessionSeq | null
|
|
|
211
223
|
* The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
|
|
212
224
|
* they only exist on {@link SurfaceEventType} variants (`user/message`,
|
|
213
225
|
* `assistant/message`, `tool/result`).
|
|
214
|
-
* Non-surface events (boundary markers,
|
|
226
|
+
* Non-surface events (boundary markers, attempts, errors) never carry
|
|
215
227
|
* surface metadata — the compiler enforces this at `Session.append()`
|
|
216
228
|
* call sites.
|
|
217
229
|
*/
|
|
@@ -236,12 +248,9 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
236
248
|
ignorable?: true
|
|
237
249
|
} & (K extends SurfaceEventType ? {
|
|
238
250
|
/**
|
|
239
|
-
* Seq numbers of earlier events that this event cites as sources
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* `assistant/message` may carry a present empty array for a known empty
|
|
243
|
-
* provider stream; when the field is absent, the event does not record which
|
|
244
|
-
* earlier events produced the message.
|
|
251
|
+
* Seq numbers of earlier events that this event cites as sources, such as
|
|
252
|
+
* the surface nodes shadowed by a compaction replacement. A v2
|
|
253
|
+
* `assistant/message` embeds its provider stream and cannot carry this field.
|
|
245
254
|
*/
|
|
246
255
|
sourceEventSeqs?: SessionSeq[]
|
|
247
256
|
/** How this event entered the surface; absent for non-surface events. */
|
|
@@ -252,7 +261,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
252
261
|
|
|
253
262
|
`SessionEventType = keyof SessionEventMap`。由于 `SessionEventMap` 可通过合并扩展,对 `SessionEvent` 的 switch 语句禁止使用 `assertNever`:插件添加的变体是合法的未知值;处理已知 case 后在 `default` 中放行。
|
|
254
263
|
|
|
255
|
-
|
|
264
|
+
V2 `assistant/message` 嵌入 provider stream,不能携带 `sourceEventSeqs`。User 与 tool surface event 可以在 provenance 或 replacement operation 需要时引用完整且非空的唯一较早 event 集合。
|
|
256
265
|
|
|
257
266
|
<a id="surface-types"></a>
|
|
258
267
|
|
|
@@ -266,7 +275,8 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
266
275
|
/**
|
|
267
276
|
* The subset of {@link SessionEventType} values whose events produce LLM
|
|
268
277
|
* messages and are eligible to appear on the ordered surface. Only these
|
|
269
|
-
* event types may carry {@link SurfaceOp} and
|
|
278
|
+
* event types may carry {@link SurfaceOp}; user and tool events may also cite
|
|
279
|
+
* earlier sources through {@link SessionEvent.sourceEventSeqs}.
|
|
270
280
|
*/
|
|
271
281
|
type SurfaceEventType =
|
|
272
282
|
| 'user/message'
|
|
@@ -304,21 +314,20 @@ type SurfaceOp =
|
|
|
304
314
|
* Surface placement and cited source-event seqs for {@link Session.append}. Required on
|
|
305
315
|
* message-producing events and forbidden on log-only events.
|
|
306
316
|
*/
|
|
307
|
-
|
|
317
|
+
type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
|
|
308
318
|
surfaceOp: SurfaceOp
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
*/
|
|
319
|
+
} & (T extends 'assistant/message' ? {
|
|
320
|
+
/** V2 Assistant messages embed their provider stream instead of citing source events. */
|
|
321
|
+
sourceEventSeqs?: never
|
|
322
|
+
} : {
|
|
323
|
+
/** Complete non-empty set of known earlier source-event seqs. */
|
|
315
324
|
sourceEventSeqs?: SessionSeq[]
|
|
316
|
-
}
|
|
325
|
+
})
|
|
317
326
|
```
|
|
318
327
|
|
|
319
328
|
对 `SurfaceEventType` 事件必填:每个产生消息的事件都必须声明它如何加入 surface(派生模型历史的唯一来源)。面向人类的 transcript(文本记录)是另一个投影,读取的是日志中追加来源的事件,因为 surface 会有意遮蔽替换所概括的范围(见 [dsh-session](../../packages/core/session/README.zh.md) 的 `isAppendSurfaceEvent`)。非 surface 类型在编译期拒绝此参数。
|
|
320
329
|
|
|
321
|
-
|
|
330
|
+
`assistant/message` 不能携带 `sourceEventSeqs`;它的 `stream` 拥有精确 provider 证据。其他 surface event 不引用较早 event 时省略该字段,需要引用时使用完整非空 list。
|
|
322
331
|
|
|
323
332
|
### `SessionSurface`:实时只读 surface 投影
|
|
324
333
|
|
|
@@ -397,9 +406,10 @@ declare class Session {
|
|
|
397
406
|
* The first seq appended IN THIS PROCESS: the length of the constructor
|
|
398
407
|
* seed (0 without one). Events with smaller seq values entered through
|
|
399
408
|
* construction — replay, fork, or resume — and were never published on the
|
|
400
|
-
* `session/event` firehose (constructor seeds do not emit)
|
|
401
|
-
*
|
|
402
|
-
*
|
|
409
|
+
* `session/event` firehose (constructor seeds do not emit). This offset marks
|
|
410
|
+
* the constructor-input boundary for lifecycle ownership and persistence
|
|
411
|
+
* adoption; consumers that need complete canonical history still start at
|
|
412
|
+
* seq 0. Distinct from {@link inheritedEventCount}, the DURABLE
|
|
403
413
|
* fork-lineage cut: a resumed session's constructor seed is its full stored
|
|
404
414
|
* log, while the inherited count keeps the original fork value — this field is the
|
|
405
415
|
* in-process construction fact.
|
|
@@ -495,7 +505,8 @@ declare class Session {
|
|
|
495
505
|
* declare how it joins the surface, the sole source of derived model
|
|
496
506
|
* history) and
|
|
497
507
|
* rejected by the compiler for non-surface types like `turn/start` or
|
|
498
|
-
* `assistant/
|
|
508
|
+
* `assistant/attempt`. Assistant messages embed their exact provider
|
|
509
|
+
* stream and cannot cite top-level source events.
|
|
499
510
|
* @returns the logged event — its assigned `seq`/`time` plus the SNAPSHOT of
|
|
500
511
|
* `data` that entered the log, so reading `event.data` back sees the logged
|
|
501
512
|
* value, never the caller's still-mutable input.
|
|
@@ -516,7 +527,7 @@ declare class Session {
|
|
|
516
527
|
append<T extends SessionEventType>(
|
|
517
528
|
type: T,
|
|
518
529
|
data: SessionEventMap[T],
|
|
519
|
-
...opts: T extends SurfaceEventType ? [opts: SurfaceIntent] : []
|
|
530
|
+
...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
|
|
520
531
|
): SessionEvent<T>;
|
|
521
532
|
/**
|
|
522
533
|
* The {@link EpochHeader} in force after the log's last header event — the
|
|
@@ -567,11 +578,11 @@ declare class Session {
|
|
|
567
578
|
`Session.deriveMessages()` 将事件日志投影为模型看到的 `Message[]`。它是缓存的(每个 surface 节点在首次出现时投影一次;surface 重写触发重建)且冻结的(每次调用返回一个新数组,引用共享的深冻结消息,因此通过投影修改已记录的历史在类型上不可表达)。`deriveEventMessage(event)` 是折叠所应用的逐节点纯函数,公开暴露以便外部重建器和开发不变式检查能以完全相同的规则投影日志前缀,不会与缓存产生分歧。投影规则:
|
|
568
579
|
|
|
569
580
|
- `user/message` → 一条携带确切 `content` 的 user 消息;可选 envelope 仅作为日志中的展示元数据保留。
|
|
570
|
-
- `assistant/message` → 一条 assistant
|
|
581
|
+
- `assistant/message` → 一条 assistant 消息,包含生成它的提供方和模型,以及可选的适配器私有回放状态。其嵌入式紧凑 stream 是回放、usage 与 UI 证据,而不是第二条 message。**内容为空的** `assistant/message` 也会跳过:因 max-tokens 而截断且无内容的步骤仍会记录一条 `assistant/message` 来保存 stream、usage、提供方和模型,但无内容的 assistant 轮次不得进入提供方 transcript(文本记录)。
|
|
571
582
|
- `tool/result` → 一条携带 `tool-result` 块的 user 消息。
|
|
572
583
|
- `user/message`(注入上下文,即非 `user` 来源)→ 按时间顺序在相应位置生成一条 user-role 消息,并原样承载其 `content`;其类型化 source 标明生产方,并携带所有生产方专用数据。
|
|
573
584
|
|
|
574
|
-
其余所有事件(`turn/*`、`step
|
|
585
|
+
其余所有事件(`turn/*`、`step/*`、`assistant/attempt`、插件所属的 `llm/retry`)均为结构信息,不会投影为消息。token 记账会展开每个 `assistant/message` 或 `assistant/attempt` 的嵌入式 stream,message 顶层 `usage` 存在时仍是已提交 message 的权威。失败的模型请求 attempt 因此可以保留提供方 usage,而无需虚构 assistant message。当前逻辑校验会拒绝没有提供方/模型的 request header 和 assistant 消息,而不会猜测路由;受支持的历史表示会在当前 Session 存在前,由其相邻格式迁移边归一化并校验。
|
|
575
586
|
|
|
576
587
|
## 活跃会话 fork API
|
|
577
588
|
|
|
@@ -630,9 +641,9 @@ interface TurnEndReasonMap {
|
|
|
630
641
|
|
|
631
642
|
## 种子结束边界:`session/end-seed`
|
|
632
643
|
|
|
633
|
-
|
|
644
|
+
新 fork constructor 要求 seed 等于 inherited prefix,并在精确持久 cut 追加 `session/end-seed { inherited: true }`。restore 会保留该 tagged marker,并且只在完整 stored seed 尚未以 marker 结尾时追加普通 `session/end-seed {}`。两种形式都只进入 log 且不产生 message;`Session` constructor 是唯一合法 writer。
|
|
634
645
|
|
|
635
|
-
|
|
646
|
+
对于 fork lineage,定位 payload 携带 `inherited: true` 的最后一个 marker;v2 decoding 只在 `SessionHeader.isSeeded` 为 true 时要求该 marker,并从其 seq 推导 `inheritedEventCount`。对于 lifecycle ownership,定位任一形式的最后一个 `session/end-seed`。重新打开已经以任一 marker 结尾的 seed 时,不会再追加普通 marker。
|
|
636
647
|
|
|
637
648
|
它之所以必要,是因为种子历史与实时工作在字节层面完全相同,这会让任何拥有独立开/闭括号的插件失效:一个未配对的 `compaction/start`,无论写入方是在压缩中途崩溃、还是此刻正在压缩,读起来都一样。在 `session/end-seed` 之前的开启标记来自构造种子,并且属于一个已结束的生命周期,无论结束原因为何(崩溃、进程接替,或从仍在运行的父会话 fork 出来),因此其所有方可以视之为已死。这只覆盖*本*会话继承的括号:另一个并发存活的会话可能在同一段历史上持有开放括号,而它自己的边界在别处,因此容忍并发写入方还需要日志之外的存活信号。核心写入该边界但不从中读取任何内容——括号的词汇表仍归其所属插件,这也正是崩溃修复只关闭轮次/步骤/工具边界而从不处理 `compaction/*` 的原因。
|
|
638
649
|
|
|
@@ -648,7 +659,7 @@ interface TurnEndReasonMap {
|
|
|
648
659
|
|
|
649
660
|
## 持久性约定
|
|
650
661
|
|
|
651
|
-
|
|
662
|
+
持久化后端依赖的约定如下:持久日志无损保存每个事件,每个 Assistant attempt 都是一个 `assistant/message` 或 `assistant/attempt`,其嵌入式紧凑 stream 会保留原始带时间 chunk。`seq` 在这些 settlement 与所有交错事件之间保持连续。后端可以为事件批次选择自己的存储 framing,只要句柄的 `read()` 返回与追加时完全一致的事件即可;当前 JSONL v2 每个事件写一行(见 [persistence.md](persistence.zh.md))。所有 `event.data` 都必须可序列化为 JSON;`Session.append` 会从源头强制这一要求(遇到不可序列化数据时抛出),因此错误事件绝不会进入日志,`session.snapshotEvents()` 始终与后端可持久化的内容一致。新增会携带不可序列化数据、破坏核心执行嵌套或违反事件所有方声明关系的事件类型,都会构成磁盘格式的破坏性变更。
|
|
652
663
|
|
|
653
664
|
消费此约定的后端见 [persistence.md](persistence.zh.md)。
|
|
654
665
|
|
|
@@ -794,7 +805,8 @@ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<SessionInspectio
|
|
|
794
805
|
* Follow one Session log from its opening or resume cursor.
|
|
795
806
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
796
807
|
* @param signal - cancellation owned by the Remote stream carrier.
|
|
797
|
-
* @returns a complete opening snapshot followed by gap-free event
|
|
808
|
+
* @returns a complete opening snapshot followed by gap-free durable event
|
|
809
|
+
* frames and optional cursorless assistant-stream frames.
|
|
798
810
|
*/
|
|
799
811
|
@Remote({ mode: 'stream' }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
|
|
800
812
|
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write docs/subsystems/web-client.md
|
|
5
|
-
web-client.md:
|
|
6
|
-
web-client.zh.md:
|
|
5
|
+
web-client.md: 7e7f483131585dc66c45dbb147d40e848f40d6d1
|
|
6
|
+
web-client.zh.md: 603af505dbd1fc6e2f9fd3bf9542ce19cb63732f
|
|
@@ -55,7 +55,7 @@ This pairing is not a second source of business truth. Host controllers decide d
|
|
|
55
55
|
|
|
56
56
|
`ui-session` installs the `session` scope adapter and publishes `useSessions`, `useSession`, `sessionId`, and `useProjection`. Domain adapters add further standard sources without putting React hooks on the model objects.
|
|
57
57
|
|
|
58
|
-
`ui-conversation` binds once to each `SessionBinding.eventSource`. Its event registry correlates
|
|
58
|
+
`ui-conversation` binds once to each `SessionBinding.eventSource`. Its event registry correlates durable Session events and Client-only `assistant/live-chunk` updates into stable business Contexts, and its view registry materializes target snapshots. Chat Assistant, Trajectory Assistant, and Turn Tail interpret both live chunks and the compact streams embedded in durable settlements, so reconnect and paged history reproduce the same Assistant state without durable token rows. `ui-chat` and `ui-trajectory` register separate Definitions and builders: they may interpret the same event family, but they do not import or share each other's final display model. The shell selects a registered view and passes its snapshot through standard hooks and Slots. [Conversation](conversation.md) defines Context identity, replay, Location data, target builders, and keyed renderers.
|
|
59
59
|
|
|
60
60
|
`ui-slots` provides the typed registry and lifecycle ledger; `ui-renderer` is the only package that binds bare observables through `useSyncExternalStore`, owns React contexts, and renders the root tree. Feature components receive framework hooks, owner props, store actions, and explicit injection through their derived props. [Web Client Slots](slots.md) lists those inputs, extension APIs, and the current Slot hierarchy.
|
|
61
61
|
|
|
@@ -55,7 +55,7 @@ Connection 拥有 request correlation、`/api` carrier、trust check、精确 Fe
|
|
|
55
55
|
|
|
56
56
|
`ui-session` 安装 `session` scope adapter,并提供 `useSessions`、`useSession`、`sessionId` 和 `useProjection`。领域 adapter 可以继续添加标准 source,但不会把 React hook 放进 model object。
|
|
57
57
|
|
|
58
|
-
`ui-conversation` 对每个 `SessionBinding.eventSource` 只绑定一次。它的 event registry
|
|
58
|
+
`ui-conversation` 对每个 `SessionBinding.eventSource` 只绑定一次。它的 event registry 把持久 Session event 与 Client-only `assistant/live-chunk` update 关联成稳定的业务 Context,view registry 则 materialize target snapshot。Chat Assistant、Trajectory Assistant 与 Turn Tail 同时解释 live chunk 和持久 settlement 中嵌入的紧凑 stream,因此重连与分页历史无需持久 token 行即可复现相同 Assistant 状态。`ui-chat` 与 `ui-trajectory` 分别注册自己的 Definition 和 builder:它们可以解释同一 event family,但不会导入或共享彼此的最终 display model。Shell 选择一个已注册 view,再通过标准 hook 与 Slot 交付其 snapshot。[Conversation](conversation.zh.md)定义 Context identity、replay、Location data、target builder 与 keyed renderer。
|
|
59
59
|
|
|
60
60
|
`ui-slots` 提供类型化 registry 与 lifecycle ledger;`ui-renderer` 是唯一通过 `useSyncExternalStore` 绑定裸 observable、拥有 React context 并渲染 root tree 的包。功能 component 通过推导出的 props 接收 framework hook、owner prop、store action 与显式 injection。[Web Client Slots](slots.zh.md)列出这些输入、扩展 API 与当前 Slot 层级。
|
|
61
61
|
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write docs/subsystems/workspace.md
|
|
5
|
-
workspace.md:
|
|
6
|
-
workspace.zh.md:
|
|
5
|
+
workspace.md: 3c53300a5be6678802da950d22854da76fa46bf1
|
|
6
|
+
workspace.zh.md: b651a21f7641619c28bad9d3fdf368c718ca2c29
|
|
@@ -40,7 +40,7 @@ interface Workspace {
|
|
|
40
40
|
*/
|
|
41
41
|
readonly path: string
|
|
42
42
|
|
|
43
|
-
/** Display title. Defaults to
|
|
43
|
+
/** Display title. Defaults to the final path segment, or a filesystem root's own spelling; duplicates are allowed. */
|
|
44
44
|
readonly title: string
|
|
45
45
|
|
|
46
46
|
/** ISO-8601 creation instant, stamped at create and never rewritten. */
|
|
@@ -117,7 +117,7 @@ Ownership truth is the record's ordered `sessionIds`, never derived from session
|
|
|
117
117
|
|
|
118
118
|
## The registry: `ctx.workspaceRegistry`
|
|
119
119
|
|
|
120
|
-
`WorkspaceRegistry` ([signatures](#ctxworkspaceregistry--workspaceregistry)) owns registration and resolution. `create(path, title?)`
|
|
120
|
+
`WorkspaceRegistry` ([signatures](#ctxworkspaceregistry--workspaceregistry)) owns registration and resolution. `create(path, title?)` requires a fully qualified path, canonicalizes it, rejects a nonexistent path (the original `ENOENT`) or a non-directory, returns the existing entity unchanged when the canonical path is already owned, and otherwise creates a record with `title ?? defaultWorkspaceTitle(path)` prepended to the durable registry order (different canonical paths may share a display title, and a path with no final segment uses its root spelling). `get(id)` and the ordered `list()` are synchronous cache reads; `resolveByPath(path)` applies the same fully qualified realpath canon without creating. `delete(id)` removes only the registration, order entry, and session account — the directory, user files, live sessions, and persisted logs are never touched, so those sessions become Ungrouped ([decision](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md)); unknown ids return `false`. Create and delete persist a pending-mutation marker before their two writes (record + order) can diverge; startup resolves exactly the marked mutation — by deleting the marked table row, which completes an interrupted delete and rolls back an interrupted create (the registration is re-creatable, so rollback is the safe direction) — and an unmarked order/table mismatch fails loud as corruption.
|
|
121
121
|
|
|
122
122
|
Sessions get their cwd at create time from whoever creates them, not from this registry — the API gateway resolves a new session's cwd from the chosen workspace's `path` (falling back to an explicit or default cwd), creates the session so the cwd lands in its immutable [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log), then calls `attachSession`, which re-validates that stored header cwd against the workspace path. On the first successful start, the registry bootstraps history from persisted headers alone (`id`, `cwd`, `createdAt` — never event bodies), grouping sessions with a valid canonical cwd into per-directory workspaces, newest first; the initialized marker is written last so an interrupted bootstrap resumes safely. The bootstrap is one-time: cwd-less legacy sessions stay Ungrouped, and sessions created afterwards join a workspace only through `attachSession`.
|
|
123
123
|
|
|
@@ -250,13 +250,13 @@ Durable workspace registry. Startup waits for `sessionPersistence`, builds one c
|
|
|
250
250
|
|
|
251
251
|
```ts cordis-catalog
|
|
252
252
|
/**
|
|
253
|
-
* Create or reuse a workspace for an existing directory. The
|
|
254
|
-
* canonicalized through `fs.realpath`; a nonexistent
|
|
255
|
-
*
|
|
256
|
-
*
|
|
253
|
+
* Create or reuse a workspace for an existing directory. The fully qualified
|
|
254
|
+
* path is canonicalized through `fs.realpath`; a relative, nonexistent, or
|
|
255
|
+
* non-directory path rejects. Repeated calls for the same canonical path
|
|
256
|
+
* return the existing entity without changing its title.
|
|
257
257
|
* A newly created workspace is prepended to the durable registry order.
|
|
258
258
|
* Different canonical paths may share a display title.
|
|
259
|
-
* @param path - Existing directory to own, in
|
|
259
|
+
* @param path - Existing directory to own, in a fully qualified path spelling.
|
|
260
260
|
* @param title - Display title used only when a new record is created.
|
|
261
261
|
* @returns the existing or newly durable workspace.
|
|
262
262
|
*/
|
|
@@ -309,7 +309,7 @@ archiveSession(sessionId: SessionId): Promise<void>
|
|
|
309
309
|
* Resolve by canonical directory path without creating or mutating a
|
|
310
310
|
* workspace. A missing path rejects during `realpath`; an existing unowned
|
|
311
311
|
* directory returns `undefined`.
|
|
312
|
-
* @param path - Existing directory path in
|
|
312
|
+
* @param path - Existing directory path in a fully qualified spelling.
|
|
313
313
|
* @returns the workspace owning the canonical path, when one exists.
|
|
314
314
|
*/
|
|
315
315
|
async resolveByPath(path: string): Promise<Workspace | undefined>
|