dsh-code 1.0.7 → 1.2.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/README.en.md +70 -24
- package/README.md +71 -25
- package/bin/deepseek.mjs +202 -39
- package/cordis.patch.yml +13 -4
- package/lib/index.mjs +4063 -844
- package/lib/session-query.mjs +3 -2
- package/lib/startup.mjs +4 -4
- package/lib/{theme-DCT8Y2xf.mjs → theme-7u5Qo3dF.mjs} +657 -20
- package/lib/types/app.d.ts +100 -63
- package/lib/types/authorization-panel.d.ts +3 -3
- package/lib/types/git-workflow.d.ts +91 -2
- package/lib/types/i18n.d.ts +39 -0
- package/lib/types/index.d.ts +73 -1
- package/lib/types/input-split.d.ts +1 -1
- package/lib/types/kernel-panels.d.ts +86 -31
- package/lib/types/language-panel.d.ts +12 -0
- package/lib/types/locales/en.d.ts +450 -0
- package/lib/types/locales/zh.d.ts +9 -0
- package/lib/types/mentions.d.ts +7 -3
- package/lib/types/models.d.ts +14 -0
- package/lib/types/panel-accent.d.ts +28 -0
- package/lib/types/rainbow.d.ts +69 -0
- package/lib/types/render/animations.d.ts +42 -0
- package/lib/types/render/inspector.d.ts +26 -0
- package/lib/types/render/lines.d.ts +21 -1
- package/lib/types/render/markdown.d.ts +1 -1
- package/lib/types/render/projection.d.ts +95 -4
- package/lib/types/render/status.d.ts +8 -8
- package/lib/types/render/text.d.ts +6 -0
- package/lib/types/render/usage.d.ts +113 -0
- package/lib/types/session-directory.d.ts +17 -0
- package/lib/types/startup.d.ts +1 -1
- package/lib/types/terminal-title.d.ts +8 -0
- package/lib/types/theme-panel.d.ts +2 -2
- package/lib/types/theme.d.ts +271 -52
- package/lib/types/update-panel.d.ts +5 -5
- package/lib/types/update.d.ts +10 -1
- package/lib/types/version.d.ts +4 -3
- package/package.json +24 -7
- package/src/app.ts +1155 -478
- package/src/approval.ts +166 -166
- package/src/authorization-panel.ts +19 -16
- package/src/editor-keys.ts +371 -371
- package/src/git-workflow.ts +229 -3
- package/src/i18n.ts +68 -0
- package/src/index.ts +412 -76
- package/src/input-split.ts +3 -3
- package/src/kernel-panels.ts +471 -89
- package/src/keyboard.ts +5 -4
- package/src/language-panel.ts +53 -0
- package/src/locales/en.ts +489 -0
- package/src/locales/zh.ts +488 -0
- package/src/mentions.ts +8 -4
- package/src/models.ts +264 -212
- package/src/panel-accent.ts +41 -0
- package/src/presets.ts +1 -1
- package/src/provider-settings.ts +1 -1
- package/src/rainbow.ts +208 -0
- package/src/render/animations.ts +104 -6
- package/src/render/editor.ts +20 -20
- package/src/render/export.ts +116 -95
- package/src/render/inspector.ts +42 -0
- package/src/render/lines.ts +628 -415
- package/src/render/markdown.ts +15 -3
- package/src/render/projection.ts +429 -19
- package/src/render/status.ts +41 -35
- package/src/render/text.ts +14 -0
- package/src/render/tool-preview.ts +77 -77
- package/src/render/usage.ts +430 -0
- package/src/render/width.ts +2 -2
- package/src/session-directory.ts +8 -6
- package/src/session-query.ts +8 -4
- package/src/startup.ts +3 -3
- package/src/subagents.ts +229 -229
- package/src/terminal-title.ts +22 -5
- package/src/theme-panel.ts +17 -21
- package/src/theme.ts +281 -33
- package/src/update-panel.ts +37 -27
- package/src/update.ts +19 -3
- package/src/version.ts +58 -20
- package/src/whale-glyph.ts +23 -23
package/src/render/projection.ts
CHANGED
|
@@ -63,6 +63,12 @@ export interface UserEntry {
|
|
|
63
63
|
images?: readonly ImageBlock['attachment'][]
|
|
64
64
|
/** Durable file references carried by this prompt (0.1.5 file blocks). */
|
|
65
65
|
files?: readonly FileAttachmentRef[]
|
|
66
|
+
/**
|
|
67
|
+
* How the prompt reached the agent, when it did not arrive as an ordinary
|
|
68
|
+
* submission: `queued` waited for this turn, `steered` joined it mid-flight.
|
|
69
|
+
* Absent for a prompt typed straight into an idle composer.
|
|
70
|
+
*/
|
|
71
|
+
delivery?: 'queued' | 'steered'
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
/** One user message waiting in the agent inbox (the web's queued-message row). */
|
|
@@ -122,6 +128,169 @@ export interface ToolEntry {
|
|
|
122
128
|
* result lands and only when something renderable exists.
|
|
123
129
|
*/
|
|
124
130
|
detail: ToolDetail | undefined
|
|
131
|
+
/**
|
|
132
|
+
* Nested PTC sub-dispatches (`run_code`) in start order, bounded to the
|
|
133
|
+
* newest {@link MAX_TOOL_SUB_DISPATCHES} rows.
|
|
134
|
+
*/
|
|
135
|
+
subs: readonly ToolSubDispatch[]
|
|
136
|
+
/** Sub-dispatches evicted from the bounded window. */
|
|
137
|
+
subsDropped: number
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* One nested PTC sub-dispatch under a `run_code` parent call: the durable
|
|
142
|
+
* `tool/ptc-dispatch-start`/`tool/ptc-dispatch` pair folded to one bounded
|
|
143
|
+
* row (upstream contract: pair by `subCallId`, every start settles, and the
|
|
144
|
+
* settle carries `tool/result`'s own vocabulary).
|
|
145
|
+
*/
|
|
146
|
+
export interface ToolSubDispatch {
|
|
147
|
+
/** Opaque sub-call id pairing the start with its settle. */
|
|
148
|
+
subCallId: string
|
|
149
|
+
/** Sub-call tool name. */
|
|
150
|
+
name: string
|
|
151
|
+
/** Bounded arguments preview. */
|
|
152
|
+
preview: string
|
|
153
|
+
/** Lifecycle; `running` until the paired settle lands. */
|
|
154
|
+
state: 'running' | 'done' | 'error'
|
|
155
|
+
/** Bounded first text block of the settle content, '' until it lands. */
|
|
156
|
+
summary: string
|
|
157
|
+
/** Wall-clock duration (settle − start), 0 while running. */
|
|
158
|
+
durationMs: number
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Bounded sub-dispatch window per tool card (display budget only). */
|
|
162
|
+
export const MAX_TOOL_SUB_DISPATCHES = 12
|
|
163
|
+
|
|
164
|
+
/** Bounded member window per workflow run card (display budget only). */
|
|
165
|
+
export const MAX_WORKFLOW_MEMBERS = 12
|
|
166
|
+
|
|
167
|
+
/** One workflow member (an `agent()` call inside a `workflow` script). */
|
|
168
|
+
export interface WorkflowMember {
|
|
169
|
+
/** Member sequence within the run (the agent-start/agent-end pairing key). */
|
|
170
|
+
seq: number
|
|
171
|
+
/** Display label. */
|
|
172
|
+
label: string
|
|
173
|
+
/** Declared phase title, '' when none. */
|
|
174
|
+
phase: string
|
|
175
|
+
/** Child session id (cross-links the subagent feed's rows). */
|
|
176
|
+
childId: string
|
|
177
|
+
/** Settlement; `running` until the paired `tool-workflow/agent-end`. */
|
|
178
|
+
outcome: 'running' | 'completed' | 'failed' | 'cancelled'
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* One durable workflow run (the `tool-workflow/*` record a `workflow` or
|
|
183
|
+
* `ralph` tool appends to the parent session): run identity plus its
|
|
184
|
+
* bounded member list, live until `tool-workflow/run-end` settles.
|
|
185
|
+
*/
|
|
186
|
+
export interface WorkflowEntry {
|
|
187
|
+
kind: 'workflow'
|
|
188
|
+
/** Stable run identity shared by every event of the run. */
|
|
189
|
+
runId: string
|
|
190
|
+
/** Display name of the run. */
|
|
191
|
+
name: string
|
|
192
|
+
/** Members in sequence order, bounded to the newest window. */
|
|
193
|
+
members: readonly WorkflowMember[]
|
|
194
|
+
/** Members evicted from the bounded window. */
|
|
195
|
+
membersDropped: number
|
|
196
|
+
/** Run settlement; `running` until `tool-workflow/run-end`. */
|
|
197
|
+
state: 'running' | 'completed' | 'cancelled' | 'error'
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Payload of `tool-workflow/run-start` (dsh-tool-workflow's map merge). */
|
|
201
|
+
interface WorkflowRunStartData {
|
|
202
|
+
runId: string
|
|
203
|
+
name: string
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Payload of `tool-workflow/agent-start`. */
|
|
207
|
+
interface WorkflowAgentStartData {
|
|
208
|
+
runId: string
|
|
209
|
+
seq: number
|
|
210
|
+
label: string
|
|
211
|
+
phase?: string
|
|
212
|
+
childId: string
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Payload of `tool-workflow/agent-end`. */
|
|
216
|
+
interface WorkflowAgentEndData {
|
|
217
|
+
runId: string
|
|
218
|
+
seq: number
|
|
219
|
+
outcome: 'completed' | 'failed' | 'cancelled'
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Payload of `tool-workflow/run-end`. */
|
|
223
|
+
interface WorkflowRunEndData {
|
|
224
|
+
runId: string
|
|
225
|
+
stopReason: 'completed' | 'cancelled' | 'error'
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Append one member to a run entry, evicting past the bounded window. */
|
|
229
|
+
function pushWorkflowMember(entry: WorkflowEntry, member: WorkflowMember): WorkflowEntry {
|
|
230
|
+
if (entry.members.length >= MAX_WORKFLOW_MEMBERS) {
|
|
231
|
+
return { ...entry, members: [...entry.members.slice(1), member], membersDropped: entry.membersDropped + 1 }
|
|
232
|
+
}
|
|
233
|
+
return { ...entry, members: [...entry.members, member] }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Settle one member by its sequence number (pure; unmatched stays a no-op). */
|
|
237
|
+
function settleWorkflowMember(entry: WorkflowEntry, data: WorkflowAgentEndData): WorkflowEntry {
|
|
238
|
+
let paired = false
|
|
239
|
+
const members = entry.members.map(member => {
|
|
240
|
+
if (member.seq !== data.seq) return member
|
|
241
|
+
paired = true
|
|
242
|
+
return { ...member, outcome: data.outcome }
|
|
243
|
+
})
|
|
244
|
+
return paired ? { ...entry, members } : entry
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Payload of `tool/ptc-dispatch-start` (`@deepseek-ai/dsh-tools`'s
|
|
249
|
+
* SessionEventMap merge — the bundle does not depend on that package, so
|
|
250
|
+
* the fold guards the discriminator by string instead of by type).
|
|
251
|
+
*/
|
|
252
|
+
interface PtcDispatchStartData {
|
|
253
|
+
rootCallId: string
|
|
254
|
+
subCallId: string
|
|
255
|
+
name: string
|
|
256
|
+
arguments: unknown
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Payload of `tool/ptc-dispatch` (a start plus its settled outcome). */
|
|
260
|
+
interface PtcDispatchData extends PtcDispatchStartData {
|
|
261
|
+
isError: boolean
|
|
262
|
+
content: readonly ContentBlock[]
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** JSON-stringify one sub-dispatch argument value for the preview helper. */
|
|
266
|
+
function subDispatchArguments(arguments_: unknown): string {
|
|
267
|
+
if (typeof arguments_ === 'string') return arguments_
|
|
268
|
+
try {
|
|
269
|
+
return JSON.stringify(arguments_) ?? ''
|
|
270
|
+
} catch {
|
|
271
|
+
return ''
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Append one running sub-dispatch under its parent, evicting past the cap. */
|
|
276
|
+
function pushSubDispatch(entry: ToolEntry, sub: ToolSubDispatch): ToolEntry {
|
|
277
|
+
if (entry.subs.length >= MAX_TOOL_SUB_DISPATCHES) {
|
|
278
|
+
return { ...entry, subs: [...entry.subs.slice(1), sub], subsDropped: entry.subsDropped + 1 }
|
|
279
|
+
}
|
|
280
|
+
return { ...entry, subs: [...entry.subs, sub] }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Fold one settled sub-dispatch into its matching row (pure). */
|
|
284
|
+
function settleSubDispatch(entry: ToolEntry, data: PtcDispatchData, summary: string, durationMs: number): ToolEntry {
|
|
285
|
+
let paired = false
|
|
286
|
+
const subs = entry.subs.map(sub => {
|
|
287
|
+
if (sub.subCallId !== data.subCallId) return sub
|
|
288
|
+
paired = true
|
|
289
|
+
return { ...sub, state: data.isError === true ? 'error' as const : 'done' as const, summary, durationMs }
|
|
290
|
+
})
|
|
291
|
+
// A settle without a live row (the sub left the bounded window, or the
|
|
292
|
+
// parent entry was never seen) stays a provable no-op.
|
|
293
|
+
return paired ? { ...entry, subs } : entry
|
|
125
294
|
}
|
|
126
295
|
|
|
127
296
|
/** One slash-command execution dispatched through `ctx.commands`. */
|
|
@@ -194,7 +363,7 @@ export interface FilesEntry {
|
|
|
194
363
|
}
|
|
195
364
|
|
|
196
365
|
/** Ordered transcript items the renderer draws. */
|
|
197
|
-
export type TranscriptEntry = UserEntry | PendingEntry | AssistantEntry | ToolEntry | CommandEntry | ErrorEntry | TurnMarkerEntry | CompactionEntry | RetryEntry | FilesEntry
|
|
366
|
+
export type TranscriptEntry = UserEntry | PendingEntry | AssistantEntry | ToolEntry | CommandEntry | ErrorEntry | TurnMarkerEntry | CompactionEntry | RetryEntry | FilesEntry | WorkflowEntry
|
|
198
367
|
|
|
199
368
|
/** The live goal the status line badges, folded from `goal/change`. */
|
|
200
369
|
export interface GoalFold {
|
|
@@ -209,14 +378,21 @@ export interface GoalFold {
|
|
|
209
378
|
blocked: string
|
|
210
379
|
}
|
|
211
380
|
|
|
212
|
-
/**
|
|
381
|
+
/**
|
|
382
|
+
* Cumulative token accounting folded from `assistant/message` usage reports.
|
|
383
|
+
* The buckets are disjoint and mirror the provider's report, so the prompt
|
|
384
|
+
* side is never double counted: reasoning tokens are already inside
|
|
385
|
+
* `outputTokens`, and cache reads are never folded into the uncached input.
|
|
386
|
+
*/
|
|
213
387
|
export interface UsageTotals {
|
|
214
|
-
/** Prompt-side
|
|
215
|
-
|
|
388
|
+
/** Prompt-side tokens billed outside the cache. */
|
|
389
|
+
uncachedInputTokens: number
|
|
216
390
|
/** Completion-side tokens over the whole log. */
|
|
217
391
|
outputTokens: number
|
|
218
392
|
/** Cache-read tokens over the whole log (0 when the adapter reports none). */
|
|
219
393
|
cacheReadTokens: number
|
|
394
|
+
/** Cache-write tokens over the whole log (0 when the adapter reports none). */
|
|
395
|
+
cacheWriteTokens: number
|
|
220
396
|
}
|
|
221
397
|
|
|
222
398
|
/**
|
|
@@ -408,6 +584,14 @@ export interface TranscriptView {
|
|
|
408
584
|
* coordinates later removals resolve against.
|
|
409
585
|
*/
|
|
410
586
|
pending: { 'next-turn': readonly string[]; 'next-step': readonly string[] }
|
|
587
|
+
/**
|
|
588
|
+
* In-flight inbox messages by the list they were inserted into, kept until
|
|
589
|
+
* the durable user message that claims them lands. The claim itself is a
|
|
590
|
+
* plain splice that empties the pending row first, so this map — not
|
|
591
|
+
* {@link pending} — is what lets a settled prompt say it was queued or
|
|
592
|
+
* steered rather than typed into an idle composer.
|
|
593
|
+
*/
|
|
594
|
+
claimOrigin: ReadonlyMap<string, 'next-turn' | 'next-step'>
|
|
411
595
|
/**
|
|
412
596
|
* Fold-internal timing anchors, never rendered: open step and tool-call
|
|
413
597
|
* start timestamps the next `assistant/message` / `tool/result` resolves
|
|
@@ -419,6 +603,8 @@ export interface TranscriptView {
|
|
|
419
603
|
readonly anchors: {
|
|
420
604
|
stepStart: Map<string, number>
|
|
421
605
|
toolStart: Map<string, number>
|
|
606
|
+
/** Open PTC sub-dispatch starts by `subCallId` (duration anchors). */
|
|
607
|
+
subStart: Map<string, number>
|
|
422
608
|
firstChunkAt: Map<string, number>
|
|
423
609
|
compactionTokens: Map<string, number>
|
|
424
610
|
lastPruneTokens: number
|
|
@@ -482,6 +668,7 @@ function cloneViewAnchors(anchors: TranscriptView['anchors']): TranscriptView['a
|
|
|
482
668
|
return {
|
|
483
669
|
stepStart: new Map(anchors.stepStart),
|
|
484
670
|
toolStart: new Map(anchors.toolStart),
|
|
671
|
+
subStart: new Map(anchors.subStart),
|
|
485
672
|
firstChunkAt: new Map(anchors.firstChunkAt),
|
|
486
673
|
compactionTokens: new Map(anchors.compactionTokens),
|
|
487
674
|
lastPruneTokens: anchors.lastPruneTokens,
|
|
@@ -576,8 +763,9 @@ export function createTranscriptView(): TranscriptView {
|
|
|
576
763
|
goal: undefined,
|
|
577
764
|
schedules: [],
|
|
578
765
|
pending: { 'next-turn': [], 'next-step': [] },
|
|
579
|
-
|
|
580
|
-
|
|
766
|
+
claimOrigin: new Map(),
|
|
767
|
+
stats: { turns: 0, steps: 0, llmMs: 0, toolMs: 0, usage: { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, lastPromptTokens: 0, contextWindow: 0, contextSegments: { system: 0, prompt: 0, assistant: 0, thinking: 0, tools: 0 }, ttftMs: 0, ttftSteps: 0, decodeMs: 0, decodeTokens: 0, reasoningEffort: '' },
|
|
768
|
+
anchors: { stepStart: new Map(), toolStart: new Map(), subStart: new Map(), firstChunkAt: new Map(), compactionTokens: new Map(), lastPruneTokens: 0, turnFiles: new Map(), turnSteps: new Map(), turnTools: new Map(), systemNodes: new Map() },
|
|
581
769
|
}
|
|
582
770
|
}
|
|
583
771
|
|
|
@@ -622,6 +810,18 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
622
810
|
pending = { ...pending, [target]: pending[target].filter((_, i) => i !== index) }
|
|
623
811
|
entries = entries.filter(entry => !(entry.kind === 'pending' && entry.messageId === message.id))
|
|
624
812
|
}
|
|
813
|
+
// How this message reached the agent. The claim that precedes this event
|
|
814
|
+
// is a plain splice (no `canceled` outcome), so it has already emptied
|
|
815
|
+
// the pending row above; `claimOrigin` is what survives to say whether
|
|
816
|
+
// the prompt was queued for this turn or steered into it.
|
|
817
|
+
const origin = view.claimOrigin.get(message.id)
|
|
818
|
+
let claimOrigin = view.claimOrigin
|
|
819
|
+
if (origin !== undefined) {
|
|
820
|
+
const next = new Map(claimOrigin)
|
|
821
|
+
next.delete(message.id)
|
|
822
|
+
claimOrigin = next
|
|
823
|
+
}
|
|
824
|
+
const delivery = origin === undefined ? {} : { delivery: origin === 'next-turn' ? 'queued' as const : 'steered' as const }
|
|
625
825
|
// Injected context (plugin/model-continuation sources) stays collapsed
|
|
626
826
|
// to a bounded notice row, exactly like collapsed transcript context
|
|
627
827
|
// elsewhere in the product; only direct human prompts render in full.
|
|
@@ -632,7 +832,8 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
632
832
|
return {
|
|
633
833
|
...view,
|
|
634
834
|
pending,
|
|
635
|
-
|
|
835
|
+
claimOrigin,
|
|
836
|
+
entries: [...entries, { kind: 'user', text, notice: false, ...delivery, ...(images.length === 0 ? {} : { images }), ...(files.length === 0 ? {} : { files }) }],
|
|
636
837
|
stats: {
|
|
637
838
|
...view.stats,
|
|
638
839
|
contextSegments: {
|
|
@@ -649,6 +850,7 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
649
850
|
return {
|
|
650
851
|
...view,
|
|
651
852
|
pending,
|
|
853
|
+
claimOrigin,
|
|
652
854
|
entries,
|
|
653
855
|
stats: {
|
|
654
856
|
...view.stats,
|
|
@@ -663,7 +865,8 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
663
865
|
return {
|
|
664
866
|
...view,
|
|
665
867
|
pending,
|
|
666
|
-
|
|
868
|
+
claimOrigin,
|
|
869
|
+
entries: [...entries, { kind: 'user', text, notice: false, ...delivery, ...(images.length === 0 ? {} : { images }), ...(files.length === 0 ? {} : { files }) }],
|
|
667
870
|
stats: {
|
|
668
871
|
...view.stats,
|
|
669
872
|
contextSegments: {
|
|
@@ -682,6 +885,7 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
682
885
|
return {
|
|
683
886
|
...view,
|
|
684
887
|
pending,
|
|
888
|
+
claimOrigin,
|
|
685
889
|
entries: [...entries, { kind: 'user', text: summary, notice: true }],
|
|
686
890
|
stats: {
|
|
687
891
|
...view.stats,
|
|
@@ -696,9 +900,29 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
696
900
|
// The durable inbox mutation (web queue-mirror contract, event-sourced):
|
|
697
901
|
// removals drop the projected rows at their inbox coordinates, inserted
|
|
698
902
|
// messages gain a pending row at their log position.
|
|
699
|
-
const { target, start, removedCount = 0, inserted } = event.data
|
|
903
|
+
const { target, start, removedCount = 0, inserted, outcome } = event.data
|
|
700
904
|
const ids = view.pending[target]
|
|
701
905
|
const removed = ids.slice(start, start + removedCount)
|
|
906
|
+
// Which list each in-flight message came from, so the durable user
|
|
907
|
+
// message it later becomes can say how it was delivered. Only messages
|
|
908
|
+
// that arrived while a turn was already running count: `followup` is
|
|
909
|
+
// the ordinary submission path too, so an idle submission lands in
|
|
910
|
+
// next-turn exactly like a queued one and must stay unmarked. A claim
|
|
911
|
+
// (no `outcome`) keeps the entry because the user message is still
|
|
912
|
+
// coming; a real cancellation retires it.
|
|
913
|
+
const inFlight = view.busy
|
|
914
|
+
let claimOrigin: ReadonlyMap<string, 'next-turn' | 'next-step'> = view.claimOrigin
|
|
915
|
+
let mutableOrigins: Map<string, 'next-turn' | 'next-step'> | undefined
|
|
916
|
+
const remember = (id: string): void => {
|
|
917
|
+
mutableOrigins ??= new Map(claimOrigin)
|
|
918
|
+
mutableOrigins.set(id, target)
|
|
919
|
+
claimOrigin = mutableOrigins
|
|
920
|
+
}
|
|
921
|
+
if (outcome === 'canceled' && removed.length > 0) {
|
|
922
|
+
const next = new Map(claimOrigin)
|
|
923
|
+
for (const id of removed) next.delete(id)
|
|
924
|
+
claimOrigin = next
|
|
925
|
+
}
|
|
702
926
|
// In-place upstream semantics: the kernel's authoritative fold is
|
|
703
927
|
// `inbox.splice(start, removedCount, ...inserted)` — inserted ids land
|
|
704
928
|
// AT the splice position (prepend/replace shapes), never at the tail.
|
|
@@ -717,6 +941,7 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
717
941
|
!(entry.kind === 'pending' && entry.target === target && removedSet.has(entry.messageId)))
|
|
718
942
|
}
|
|
719
943
|
for (const message of inserted) {
|
|
944
|
+
if (inFlight) remember(message.id)
|
|
720
945
|
entries = [...entries, {
|
|
721
946
|
kind: 'pending',
|
|
722
947
|
messageId: message.id,
|
|
@@ -726,7 +951,7 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
726
951
|
...filesOf(message.content).length === 0 ? {} : { files: filesOf(message.content) },
|
|
727
952
|
}]
|
|
728
953
|
}
|
|
729
|
-
return { ...view, entries, pending: { ...view.pending, [target]: nextIds } }
|
|
954
|
+
return { ...view, entries, claimOrigin, pending: { ...view.pending, [target]: nextIds } }
|
|
730
955
|
}
|
|
731
956
|
case 'system/message': {
|
|
732
957
|
// Session-log v3 carries the system prompt as surface nodes (the
|
|
@@ -810,18 +1035,25 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
810
1035
|
const totals = stats.usage
|
|
811
1036
|
const text = textOf(event.data.message.content)
|
|
812
1037
|
const reasoning = reasoningOf(event.data.message.content)
|
|
1038
|
+
// A tool-only step settles with no text, no reasoning, and (unless the
|
|
1039
|
+
// attempt was interrupted) nothing to render: appending an entry would
|
|
1040
|
+
// create an invisible zero-line card that pads the inspector's ←→
|
|
1041
|
+
// walk and an empty block in /export. The event still updates timing
|
|
1042
|
+
// and usage above; only the transcript entry is skipped.
|
|
1043
|
+
const renderable = text !== '' || reasoning !== '' || event.data.interrupted === true
|
|
813
1044
|
return {
|
|
814
1045
|
...view,
|
|
815
1046
|
streaming: '',
|
|
816
1047
|
streamingReasoning: '',
|
|
817
|
-
entries: [...view.entries, { kind: 'assistant', text, reasoning, interrupted: event.data.interrupted === true ? true : undefined }],
|
|
1048
|
+
...(renderable ? { entries: [...view.entries, { kind: 'assistant', text, reasoning, interrupted: event.data.interrupted === true ? true : undefined }] } : {}),
|
|
818
1049
|
stats: {
|
|
819
1050
|
...stats,
|
|
820
1051
|
llmMs: stats.llmMs + (started === undefined ? 0 : Math.max(0, event.time - started)),
|
|
821
1052
|
usage: usage === undefined ? totals : {
|
|
822
|
-
|
|
1053
|
+
uncachedInputTokens: totals.uncachedInputTokens + usage.inputTokens,
|
|
823
1054
|
outputTokens: totals.outputTokens + usage.outputTokens,
|
|
824
1055
|
cacheReadTokens: totals.cacheReadTokens + (usage.cacheReadTokens ?? 0),
|
|
1056
|
+
cacheWriteTokens: totals.cacheWriteTokens + (usage.cacheWriteTokens ?? 0),
|
|
825
1057
|
},
|
|
826
1058
|
lastPromptTokens: usage === undefined ? stats.lastPromptTokens
|
|
827
1059
|
: usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0),
|
|
@@ -862,6 +1094,8 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
862
1094
|
state: 'running',
|
|
863
1095
|
summary: '',
|
|
864
1096
|
detail: undefined,
|
|
1097
|
+
subs: [],
|
|
1098
|
+
subsDropped: 0,
|
|
865
1099
|
}],
|
|
866
1100
|
stats: {
|
|
867
1101
|
...view.stats,
|
|
@@ -1165,8 +1399,86 @@ export function projectEvent(view: TranscriptView, event: SessionEvent): Transcr
|
|
|
1165
1399
|
})
|
|
1166
1400
|
return { ...view, entries }
|
|
1167
1401
|
}
|
|
1168
|
-
default:
|
|
1402
|
+
default: {
|
|
1403
|
+
// `tool/ptc-dispatch*` live in @deepseek-ai/dsh-tools' map merge,
|
|
1404
|
+
// which this bundle does not depend on; the string guards keep the
|
|
1405
|
+
// fold decoupled while following the upstream contract exactly (pair
|
|
1406
|
+
// by subCallId; every start settles; settle speaks tool/result's own
|
|
1407
|
+
// vocabulary). Log-only upstream: deriveMessages ignores them, so the
|
|
1408
|
+
// sub rows are pure display state on the parent run_code card.
|
|
1409
|
+
const type = event.type as string
|
|
1410
|
+
if (type === 'tool/ptc-dispatch-start') {
|
|
1411
|
+
const data = event.data as PtcDispatchStartData
|
|
1412
|
+
view.anchors.subStart.set(data.subCallId, event.time)
|
|
1413
|
+
const entries = view.entries.map(entry => entry.kind !== 'tool' || entry.callId !== data.rootCallId
|
|
1414
|
+
? entry
|
|
1415
|
+
: pushSubDispatch(entry, {
|
|
1416
|
+
subCallId: data.subCallId,
|
|
1417
|
+
name: data.name,
|
|
1418
|
+
preview: toolArgumentsPreview(subDispatchArguments(data.arguments), data.name),
|
|
1419
|
+
state: 'running',
|
|
1420
|
+
summary: '',
|
|
1421
|
+
durationMs: 0,
|
|
1422
|
+
}))
|
|
1423
|
+
return { ...view, entries }
|
|
1424
|
+
}
|
|
1425
|
+
if (type === 'tool/ptc-dispatch') {
|
|
1426
|
+
const data = event.data as PtcDispatchData
|
|
1427
|
+
const started = view.anchors.subStart.get(data.subCallId)
|
|
1428
|
+
view.anchors.subStart.delete(data.subCallId)
|
|
1429
|
+
const summary = boundContextSummary(textOf(data.content))
|
|
1430
|
+
const durationMs = started === undefined ? 0 : Math.max(0, event.time - started)
|
|
1431
|
+
const entries = view.entries.map(entry => entry.kind !== 'tool' || entry.callId !== data.rootCallId
|
|
1432
|
+
? entry
|
|
1433
|
+
: settleSubDispatch(entry, data, summary, durationMs))
|
|
1434
|
+
return { ...view, entries }
|
|
1435
|
+
}
|
|
1436
|
+
// The durable workflow record (`tool-workflow/*`, appended by the
|
|
1437
|
+
// workflow/ralph tools): one bounded card per run, members paired by
|
|
1438
|
+
// their sequence number, the run settling on run-end.
|
|
1439
|
+
if (type === 'tool-workflow/run-start') {
|
|
1440
|
+
const data = event.data as WorkflowRunStartData
|
|
1441
|
+
return {
|
|
1442
|
+
...view,
|
|
1443
|
+
entries: [...view.entries, {
|
|
1444
|
+
kind: 'workflow',
|
|
1445
|
+
runId: data.runId,
|
|
1446
|
+
name: data.name,
|
|
1447
|
+
members: [],
|
|
1448
|
+
membersDropped: 0,
|
|
1449
|
+
state: 'running',
|
|
1450
|
+
}],
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
if (type === 'tool-workflow/agent-start') {
|
|
1454
|
+
const data = event.data as WorkflowAgentStartData
|
|
1455
|
+
const entries = view.entries.map(entry => entry.kind !== 'workflow' || entry.runId !== data.runId
|
|
1456
|
+
? entry
|
|
1457
|
+
: pushWorkflowMember(entry, {
|
|
1458
|
+
seq: data.seq,
|
|
1459
|
+
label: data.label,
|
|
1460
|
+
phase: data.phase ?? '',
|
|
1461
|
+
childId: data.childId,
|
|
1462
|
+
outcome: 'running',
|
|
1463
|
+
}))
|
|
1464
|
+
return { ...view, entries }
|
|
1465
|
+
}
|
|
1466
|
+
if (type === 'tool-workflow/agent-end') {
|
|
1467
|
+
const data = event.data as WorkflowAgentEndData
|
|
1468
|
+
const entries = view.entries.map(entry => entry.kind !== 'workflow' || entry.runId !== data.runId
|
|
1469
|
+
? entry
|
|
1470
|
+
: settleWorkflowMember(entry, data))
|
|
1471
|
+
return { ...view, entries }
|
|
1472
|
+
}
|
|
1473
|
+
if (type === 'tool-workflow/run-end') {
|
|
1474
|
+
const data = event.data as WorkflowRunEndData
|
|
1475
|
+
const entries = view.entries.map(entry => entry.kind !== 'workflow' || entry.runId !== data.runId
|
|
1476
|
+
? entry
|
|
1477
|
+
: { ...entry, state: data.stopReason })
|
|
1478
|
+
return { ...view, entries }
|
|
1479
|
+
}
|
|
1169
1480
|
return view
|
|
1481
|
+
}
|
|
1170
1482
|
}
|
|
1171
1483
|
}
|
|
1172
1484
|
|
|
@@ -1201,11 +1513,15 @@ export interface ReplayAccumulator {
|
|
|
1201
1513
|
retryIndex: Map<string, number[]>
|
|
1202
1514
|
/** messageId → every index into `entries` holding a `pending` row with that id. */
|
|
1203
1515
|
pendingIndex: Map<string, number[]>
|
|
1516
|
+
/** runId → every index into `entries` holding a `workflow` row with that id. */
|
|
1517
|
+
workflowIndex: Map<string, number[]>
|
|
1204
1518
|
/** Tombstone count; zero means `entries` is already the final array. */
|
|
1205
1519
|
removedCount: number
|
|
1206
1520
|
/** Mutable inbox id lists, mirroring `view.pending` order per target. */
|
|
1207
1521
|
pendingTurn: string[]
|
|
1208
1522
|
pendingStep: string[]
|
|
1523
|
+
/** Mutable mirror of `view.claimOrigin` (see the reducer's field doc). */
|
|
1524
|
+
claimOrigin: Map<string, 'next-turn' | 'next-step'>
|
|
1209
1525
|
streaming: string
|
|
1210
1526
|
streamingReasoning: string
|
|
1211
1527
|
todos: readonly TodoItem[]
|
|
@@ -1224,6 +1540,8 @@ export interface ReplayAccumulator {
|
|
|
1224
1540
|
stats: TranscriptStats
|
|
1225
1541
|
stepStart: Map<string, number>
|
|
1226
1542
|
toolStart: Map<string, number>
|
|
1543
|
+
/** Open PTC sub-dispatch starts by `subCallId` (duration anchors). */
|
|
1544
|
+
subStart: Map<string, number>
|
|
1227
1545
|
firstChunkAt: Map<string, number>
|
|
1228
1546
|
compactionTokens: Map<string, number>
|
|
1229
1547
|
lastPruneTokens: number
|
|
@@ -1244,9 +1562,11 @@ export function createReplayAccumulator(): ReplayAccumulator {
|
|
|
1244
1562
|
commandIndex: new Map(),
|
|
1245
1563
|
retryIndex: new Map(),
|
|
1246
1564
|
pendingIndex: new Map(),
|
|
1565
|
+
workflowIndex: new Map(),
|
|
1247
1566
|
removedCount: 0,
|
|
1248
1567
|
pendingTurn: [],
|
|
1249
1568
|
pendingStep: [],
|
|
1569
|
+
claimOrigin: new Map(),
|
|
1250
1570
|
streaming: '',
|
|
1251
1571
|
streamingReasoning: '',
|
|
1252
1572
|
todos: [],
|
|
@@ -1261,9 +1581,10 @@ export function createReplayAccumulator(): ReplayAccumulator {
|
|
|
1261
1581
|
sandbox: '',
|
|
1262
1582
|
goal: undefined,
|
|
1263
1583
|
schedules: [],
|
|
1264
|
-
stats: { turns: 0, steps: 0, llmMs: 0, toolMs: 0, usage: {
|
|
1584
|
+
stats: { turns: 0, steps: 0, llmMs: 0, toolMs: 0, usage: { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, lastPromptTokens: 0, contextWindow: 0, contextSegments: { system: 0, prompt: 0, assistant: 0, thinking: 0, tools: 0 }, ttftMs: 0, ttftSteps: 0, decodeMs: 0, decodeTokens: 0, reasoningEffort: '' },
|
|
1265
1585
|
stepStart: new Map(),
|
|
1266
1586
|
toolStart: new Map(),
|
|
1587
|
+
subStart: new Map(),
|
|
1267
1588
|
firstChunkAt: new Map(),
|
|
1268
1589
|
compactionTokens: new Map(),
|
|
1269
1590
|
lastPruneTokens: 0,
|
|
@@ -1394,8 +1715,14 @@ export function replayProjectEvent(acc: ReplayAccumulator, event: SessionEvent):
|
|
|
1394
1715
|
const text = textOf(message.content)
|
|
1395
1716
|
const images = imagesOf(message.content)
|
|
1396
1717
|
const files = filesOf(message.content)
|
|
1718
|
+
// Same delivery-origin rule as the reducer: the claim splice that
|
|
1719
|
+
// precedes this event never carried the origin, so `claimOrigin` is the
|
|
1720
|
+
// only surviving record of how the prompt was submitted.
|
|
1721
|
+
const origin = acc.claimOrigin.get(message.id)
|
|
1722
|
+
const delivery = origin === undefined ? {} : { delivery: origin === 'next-turn' ? 'queued' as const : 'steered' as const }
|
|
1723
|
+
acc.claimOrigin.delete(message.id)
|
|
1397
1724
|
if (message.source.kind === 'user' || (message.source.kind === 'plugin' && REMINDER_PLUGINS.has(message.source.plugin))) {
|
|
1398
|
-
appendReplayEntry(acc, { kind: 'user', text, notice: false, ...(images.length === 0 ? {} : { images }), ...(files.length === 0 ? {} : { files }) })
|
|
1725
|
+
appendReplayEntry(acc, { kind: 'user', text, notice: false, ...delivery, ...(images.length === 0 ? {} : { images }), ...(files.length === 0 ? {} : { files }) })
|
|
1399
1726
|
acc.stats = {
|
|
1400
1727
|
...acc.stats,
|
|
1401
1728
|
contextSegments: {
|
|
@@ -1432,12 +1759,15 @@ export function replayProjectEvent(acc: ReplayAccumulator, event: SessionEvent):
|
|
|
1432
1759
|
return true
|
|
1433
1760
|
}
|
|
1434
1761
|
case 'agent/inbox/spliced': {
|
|
1435
|
-
const { target, start, removedCount = 0, inserted } = event.data
|
|
1762
|
+
const { target, start, removedCount = 0, inserted, outcome } = event.data
|
|
1436
1763
|
const ids = target === 'next-turn' ? acc.pendingTurn : acc.pendingStep
|
|
1437
1764
|
const removed = ids.slice(start, start + removedCount)
|
|
1438
1765
|
acc.ops += removed.length
|
|
1439
1766
|
ids.splice(start, removedCount)
|
|
1440
1767
|
acc.ops += removed.length
|
|
1768
|
+
// A claim keeps the origin for the user message still to come; a real
|
|
1769
|
+
// cancellation retires it with the row.
|
|
1770
|
+
if (outcome === 'canceled') for (const id of removed) acc.claimOrigin.delete(id)
|
|
1441
1771
|
for (const id of removed) {
|
|
1442
1772
|
const list = acc.pendingIndex.get(id)
|
|
1443
1773
|
if (list === undefined) continue
|
|
@@ -1454,6 +1784,9 @@ export function replayProjectEvent(acc: ReplayAccumulator, event: SessionEvent):
|
|
|
1454
1784
|
// later inbox event.
|
|
1455
1785
|
ids.splice(start, 0, ...inserted.map(message => message.id))
|
|
1456
1786
|
for (const message of inserted) {
|
|
1787
|
+
// Same rule as the reducer: only a submission that arrived while a
|
|
1788
|
+
// turn was running is "queued"/"steered" rather than ordinary.
|
|
1789
|
+
if (acc.busy) acc.claimOrigin.set(message.id, target)
|
|
1457
1790
|
const images = imagesOf(message.content)
|
|
1458
1791
|
const files = filesOf(message.content)
|
|
1459
1792
|
appendReplayEntry(acc, { kind: 'pending', messageId: message.id, target, text: pendingText(message.content), ...(images.length === 0 ? {} : { images }), ...(files.length === 0 ? {} : { files }) })
|
|
@@ -1530,14 +1863,19 @@ export function replayProjectEvent(acc: ReplayAccumulator, event: SessionEvent):
|
|
|
1530
1863
|
const reasoning = reasoningOf(event.data.message.content)
|
|
1531
1864
|
acc.streaming = ''
|
|
1532
1865
|
acc.streamingReasoning = ''
|
|
1533
|
-
|
|
1866
|
+
// Same zero-line guard as the live fold: tool-only settlements carry
|
|
1867
|
+
// timing/usage but no renderable transcript entry.
|
|
1868
|
+
if (text !== '' || reasoning !== '' || event.data.interrupted === true) {
|
|
1869
|
+
appendReplayEntry(acc, { kind: 'assistant', text, reasoning, interrupted: event.data.interrupted === true ? true : undefined })
|
|
1870
|
+
}
|
|
1534
1871
|
acc.stats = {
|
|
1535
1872
|
...acc.stats,
|
|
1536
1873
|
llmMs: acc.stats.llmMs + (started === undefined ? 0 : Math.max(0, event.time - started)),
|
|
1537
1874
|
usage: usage === undefined ? totals : {
|
|
1538
|
-
|
|
1875
|
+
uncachedInputTokens: totals.uncachedInputTokens + usage.inputTokens,
|
|
1539
1876
|
outputTokens: totals.outputTokens + usage.outputTokens,
|
|
1540
1877
|
cacheReadTokens: totals.cacheReadTokens + (usage.cacheReadTokens ?? 0),
|
|
1878
|
+
cacheWriteTokens: totals.cacheWriteTokens + (usage.cacheWriteTokens ?? 0),
|
|
1541
1879
|
},
|
|
1542
1880
|
lastPromptTokens: usage === undefined ? acc.stats.lastPromptTokens
|
|
1543
1881
|
: usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0),
|
|
@@ -1569,6 +1907,8 @@ export function replayProjectEvent(acc: ReplayAccumulator, event: SessionEvent):
|
|
|
1569
1907
|
state: 'running',
|
|
1570
1908
|
summary: '',
|
|
1571
1909
|
detail: undefined,
|
|
1910
|
+
subs: [],
|
|
1911
|
+
subsDropped: 0,
|
|
1572
1912
|
})
|
|
1573
1913
|
indexList(acc.toolIndex, data.callId).push(acc.entries.length - 1)
|
|
1574
1914
|
acc.stats = {
|
|
@@ -1804,8 +2144,75 @@ export function replayProjectEvent(acc: ReplayAccumulator, event: SessionEvent):
|
|
|
1804
2144
|
updateReplayById<CommandEntry>(acc, acc.commandIndex, data.commandId, entry => entry.commandId === data.commandId, update)
|
|
1805
2145
|
return true
|
|
1806
2146
|
}
|
|
1807
|
-
default:
|
|
2147
|
+
default: {
|
|
2148
|
+
// PTC sub-dispatch pair, string-guarded like the live fold above; the
|
|
2149
|
+
// parent run_code row is addressed through the same call-id index the
|
|
2150
|
+
// tool/result case uses.
|
|
2151
|
+
const type = event.type as string
|
|
2152
|
+
if (type === 'tool/ptc-dispatch-start') {
|
|
2153
|
+
const data = event.data as PtcDispatchStartData
|
|
2154
|
+
acc.subStart.set(data.subCallId, event.time)
|
|
2155
|
+
updateReplayById<ToolEntry>(acc, acc.toolIndex, data.rootCallId, entry => entry.callId === data.rootCallId, entry =>
|
|
2156
|
+
pushSubDispatch(entry, {
|
|
2157
|
+
subCallId: data.subCallId,
|
|
2158
|
+
name: data.name,
|
|
2159
|
+
preview: toolArgumentsPreview(subDispatchArguments(data.arguments), data.name),
|
|
2160
|
+
state: 'running',
|
|
2161
|
+
summary: '',
|
|
2162
|
+
durationMs: 0,
|
|
2163
|
+
}))
|
|
2164
|
+
return true
|
|
2165
|
+
}
|
|
2166
|
+
if (type === 'tool/ptc-dispatch') {
|
|
2167
|
+
const data = event.data as PtcDispatchData
|
|
2168
|
+
const started = acc.subStart.get(data.subCallId)
|
|
2169
|
+
acc.subStart.delete(data.subCallId)
|
|
2170
|
+
const summary = boundContextSummary(textOf(data.content))
|
|
2171
|
+
const durationMs = started === undefined ? 0 : Math.max(0, event.time - started)
|
|
2172
|
+
updateReplayById<ToolEntry>(acc, acc.toolIndex, data.rootCallId, entry => entry.callId === data.rootCallId, entry =>
|
|
2173
|
+
settleSubDispatch(entry, data, summary, durationMs))
|
|
2174
|
+
return true
|
|
2175
|
+
}
|
|
2176
|
+
// Durable workflow record, string-guarded like the live fold above.
|
|
2177
|
+
if (type === 'tool-workflow/run-start') {
|
|
2178
|
+
const data = event.data as WorkflowRunStartData
|
|
2179
|
+
appendReplayEntry(acc, {
|
|
2180
|
+
kind: 'workflow',
|
|
2181
|
+
runId: data.runId,
|
|
2182
|
+
name: data.name,
|
|
2183
|
+
members: [],
|
|
2184
|
+
membersDropped: 0,
|
|
2185
|
+
state: 'running',
|
|
2186
|
+
})
|
|
2187
|
+
indexList(acc.workflowIndex, data.runId).push(acc.entries.length - 1)
|
|
2188
|
+
return true
|
|
2189
|
+
}
|
|
2190
|
+
if (type === 'tool-workflow/agent-start') {
|
|
2191
|
+
const data = event.data as WorkflowAgentStartData
|
|
2192
|
+
updateReplayById<WorkflowEntry>(acc, acc.workflowIndex, data.runId, entry => entry.runId === data.runId, entry =>
|
|
2193
|
+
pushWorkflowMember(entry, {
|
|
2194
|
+
seq: data.seq,
|
|
2195
|
+
label: data.label,
|
|
2196
|
+
phase: data.phase ?? '',
|
|
2197
|
+
childId: data.childId,
|
|
2198
|
+
outcome: 'running',
|
|
2199
|
+
}))
|
|
2200
|
+
return true
|
|
2201
|
+
}
|
|
2202
|
+
if (type === 'tool-workflow/agent-end') {
|
|
2203
|
+
const data = event.data as WorkflowAgentEndData
|
|
2204
|
+
updateReplayById<WorkflowEntry>(acc, acc.workflowIndex, data.runId, entry => entry.runId === data.runId, entry =>
|
|
2205
|
+
settleWorkflowMember(entry, data))
|
|
2206
|
+
return true
|
|
2207
|
+
}
|
|
2208
|
+
if (type === 'tool-workflow/run-end') {
|
|
2209
|
+
const data = event.data as WorkflowRunEndData
|
|
2210
|
+
updateReplayById<WorkflowEntry>(acc, acc.workflowIndex, data.runId, entry => entry.runId === data.runId, entry =>
|
|
2211
|
+
({ ...entry, state: data.stopReason }))
|
|
2212
|
+
return true
|
|
2213
|
+
}
|
|
1808
2214
|
return false
|
|
2215
|
+
}
|
|
1809
2216
|
}
|
|
1810
2217
|
}
|
|
1811
2218
|
|
|
@@ -1857,12 +2264,14 @@ function materializeReplayView(acc: ReplayAccumulator, copy: boolean): Transcrip
|
|
|
1857
2264
|
goal: acc.goal,
|
|
1858
2265
|
schedules: acc.schedules,
|
|
1859
2266
|
pending: { 'next-turn': [...acc.pendingTurn], 'next-step': [...acc.pendingStep] },
|
|
2267
|
+
claimOrigin: new Map(acc.claimOrigin),
|
|
1860
2268
|
stats: acc.stats,
|
|
1861
2269
|
// Handed-out views get their own anchors snapshot: the accumulator keeps
|
|
1862
2270
|
// folding its live containers, and no consumer may observe that.
|
|
1863
2271
|
anchors: {
|
|
1864
2272
|
stepStart: new Map(acc.stepStart),
|
|
1865
2273
|
toolStart: new Map(acc.toolStart),
|
|
2274
|
+
subStart: new Map(acc.subStart),
|
|
1866
2275
|
firstChunkAt: new Map(acc.firstChunkAt),
|
|
1867
2276
|
compactionTokens: new Map(acc.compactionTokens),
|
|
1868
2277
|
lastPruneTokens: acc.lastPruneTokens,
|
|
@@ -1969,6 +2378,7 @@ export function settledEntryCount(entries: readonly TranscriptEntry[]): number {
|
|
|
1969
2378
|
if (entry.kind === 'tool' && entry.state === 'running') return index
|
|
1970
2379
|
if (entry.kind === 'retry' && entry.state === 'running') return index
|
|
1971
2380
|
if (entry.kind === 'command' && entry.state === 'running') return index
|
|
2381
|
+
if (entry.kind === 'workflow' && entry.state === 'running') return index
|
|
1972
2382
|
}
|
|
1973
2383
|
return entries.length
|
|
1974
2384
|
}
|