@khorsheed/dsh-file-preview 0.1.0 → 0.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.
@@ -93,7 +93,7 @@ export const TYPERT = {
93
93
  typeSymbol: '@khorsheed/dsh-file-preview/types#FilePreviewList',
94
94
  schema: _khorsheed_dsh_file_preview_filePreview_list_result$schema,
95
95
  },
96
- sourceLocation: {"file":"packages/file-preview/src/index.ts","line":316,"column":3},
96
+ sourceLocation: {"file":"packages/file-preview/src/index.ts","line":368,"column":9},
97
97
  },
98
98
  {
99
99
  id: '@khorsheed/dsh-file-preview#filePreview/read',
@@ -134,7 +134,7 @@ export const TYPERT = {
134
134
  typeSymbol: '@khorsheed/dsh-file-preview/types#FilePreviewRead',
135
135
  schema: _khorsheed_dsh_file_preview_filePreview_read_result$schema,
136
136
  },
137
- sourceLocation: {"file":"packages/file-preview/src/index.ts","line":392,"column":9},
137
+ sourceLocation: {"file":"packages/file-preview/src/index.ts","line":458,"column":9},
138
138
  },
139
139
  {
140
140
  id: '@khorsheed/dsh-file-preview#filePreview/reveal',
@@ -175,7 +175,7 @@ export const TYPERT = {
175
175
  typeSymbol: '@khorsheed/dsh-file-preview/types#FilePreviewReveal',
176
176
  schema: _khorsheed_dsh_file_preview_filePreview_reveal_result$schema,
177
177
  },
178
- sourceLocation: {"file":"packages/file-preview/src/index.ts","line":469,"column":9},
178
+ sourceLocation: {"file":"packages/file-preview/src/index.ts","line":535,"column":9},
179
179
  },
180
180
  {
181
181
  id: '@khorsheed/dsh-file-preview#filePreview/turnFiles',
@@ -205,32 +205,32 @@ export const TYPERT = {
205
205
  typeSymbol: '@khorsheed/dsh-file-preview/types#FilePreviewTurnMap',
206
206
  schema: _khorsheed_dsh_file_preview_filePreview_turnFiles_result$schema,
207
207
  },
208
- sourceLocation: {"file":"packages/file-preview/src/index.ts","line":347,"column":3},
208
+ sourceLocation: {"file":"packages/file-preview/src/index.ts","line":405,"column":9},
209
209
  },
210
210
  ],
211
211
  model: {
212
212
  "services": [
213
213
  {
214
- "description": "The `filePreview` Remote service: session-scoped read-only file inspection for web surfaces. `list` is a pure fold over the session log (no filesystem access); `read` resolves the path against the session cwd and serves current text content through `ctx.fs` (or a browser URL for images), capped by config. Image bytes ride a dedicated host route so the browser loads them natively without bloating the RPC channel.",
214
+ "description": "The `filePreview` Remote service: session-scoped read-only file inspection for web surfaces. `list` is a fold over the session log, then filtered to the paths that still exist on disk (resolved against the session cwd and stat-ed per call); `read` resolves the path against the session cwd and serves current text content through `ctx.fs` (or a browser URL for images), capped by config. Image bytes ride a dedicated host route so the browser loads them natively without bloating the RPC channel.",
215
215
  "summary": "The `filePreview` Remote service: session-scoped read-only file inspection for web surfaces.",
216
216
  "tags": [],
217
- "jsDoc": "/**\n * The `filePreview` Remote service: session-scoped read-only file inspection\n * for web surfaces. `list` is a pure fold over the session log (no filesystem\n * access); `read` resolves the path against the session cwd and serves current\n * text content through `ctx.fs` (or a browser URL for images), capped by\n * config. Image bytes ride a dedicated host route so the browser loads them\n * natively without bloating the RPC channel.\n */",
217
+ "jsDoc": "/**\n * The `filePreview` Remote service: session-scoped read-only file inspection\n * for web surfaces. `list` is a fold over the session log, then filtered to the\n * paths that still exist on disk (resolved against the session cwd and stat-ed\n * per call); `read` resolves the path against the session cwd and serves current\n * text content through `ctx.fs` (or a browser URL for images), capped by\n * config. Image bytes ride a dedicated host route so the browser loads them\n * natively without bloating the RPC channel.\n */",
218
218
  "key": "filePreview",
219
219
  "exportName": "FilePreviewService",
220
220
  "members": [
221
221
  {
222
222
  "kind": "method",
223
223
  "name": "list",
224
- "signature": "@Remote('list') list(agent: Agent): FilePreviewList",
224
+ "signature": "@Remote('list') async list(agent: Agent): Promise<FilePreviewList>",
225
225
  "summary": "List the files one session wrote or edited: the log fold plus any bash-written files the collector verified (the S2 seam), merged so a captured path the fold already knows keeps its log-derived entry.",
226
- "jsDoc": "/**\n * List the files one session wrote or edited: the log fold plus any\n * bash-written files the collector verified (the S2 seam), merged so a\n * captured path the fold already knows keeps its log-derived entry.\n * @param agent - owning live agent; its session log is the data source.\n * @returns first-seen files capped by `maxFiles`, with the log watermark.\n */"
226
+ "jsDoc": "/**\n * List the files one session wrote or edited: the log fold plus any\n * bash-written files the collector verified (the S2 seam), merged so a\n * captured path the fold already knows keeps its log-derived entry. Only\n * paths that currently exist on disk are returned — a file the session\n * wrote and later deleted is history, not a product.\n * @param agent - owning live agent; its session log is the data source.\n * @returns existing first-seen files capped by `maxFiles`, with the log watermark.\n */"
227
227
  },
228
228
  {
229
229
  "kind": "method",
230
230
  "name": "turnFiles",
231
- "signature": "@Remote('turnFiles') turnFiles(agent: Agent): FilePreviewTurnMap",
231
+ "signature": "@Remote('turnFiles') async turnFiles(agent: Agent): Promise<FilePreviewTurnMap>",
232
232
  "summary": "Every turn's file mutations for the turn-tail card — the single source of truth the card renders (the client's old write/edit-only fold retired).",
233
- "jsDoc": "/**\n * Every turn's file mutations for the turn-tail card — the single source of\n * truth the card renders (the client's old write/edit-only fold retired).\n * Built by folding the session log per turn (write/edit calls, Code Mode\n * dispatches borrowing the root call's turn, and render-intent paths from\n * result diff meta) and merging the collector's bash-written captures by\n * their own turn. The fold is cached per session and invalidated by the log\n * watermark, so repeated card fetches do not refold. A path touched in two\n * turns appears in BOTH turn groups — per-turn attribution is exact.\n * @param agent - owning live agent; its session log is the data source.\n * @returns per-turn file groups plus the scanned watermark.\n */"
233
+ "jsDoc": "/**\n * Every turn's file mutations for the turn-tail card — the single source of\n * truth the card renders (the client's old write/edit-only fold retired).\n * Built by folding the session log per turn (write/edit calls, Code Mode\n * dispatches borrowing the root call's turn, and render-intent paths from\n * result diff meta) and merging the collector's bash-written captures by\n * their own turn. The fold is cached per session and invalidated by the log\n * watermark, so repeated card fetches do not refold. A path touched in two\n * turns appears in BOTH turn groups — per-turn attribution is exact. Only\n * paths that currently exist on disk are returned (a deleted temp script is\n * history, not a product); existence is probed fresh on every call, so the\n * cached fold is filtered per request rather than cached.\n * @param agent - owning live agent; its session log is the data source.\n * @returns existing per-turn file groups plus the scanned watermark.\n */"
234
234
  },
235
235
  {
236
236
  "kind": "method",
@@ -256,14 +256,30 @@ export const TYPERT = {
256
256
  "name": "AgentCancelCause",
257
257
  "declaration": "export type AgentCancelCause = { readonly kind: 'user'; } | { readonly kind: 'parent'; } | { readonly kind: 'hook'; readonly reason: string; } | { readonly kind: 'disposed'; };"
258
258
  },
259
+ {
260
+ "name": "AgentMessageSource",
261
+ "declaration": "export interface AgentMessageSource {\n readonly kind: 'agent-message';\n readonly form: 'relay';\n readonly senderSessionId: SessionId;\n}"
262
+ },
259
263
  {
260
264
  "name": "AgentOptions",
261
- "declaration": "export interface AgentOptions {\n provider?: string;\n model?: string;\n maxTokens?: number;\n}"
265
+ "declaration": "export interface AgentOptions {\n provider?: string;\n model?: string;\n reasoningEffort?: ReasoningEffortId;\n maxTokens?: number;\n subagentDepth?: number;\n}"
262
266
  },
263
267
  {
264
268
  "name": "AgentStatus",
265
269
  "declaration": "export type AgentStatus = 'idle' | 'running';"
266
270
  },
271
+ {
272
+ "name": "ApprovalOutcome",
273
+ "declaration": "export type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable';"
274
+ },
275
+ {
276
+ "name": "ApprovalPolicy",
277
+ "declaration": "export type ApprovalPolicy = 'ask' | 'never';"
278
+ },
279
+ {
280
+ "name": "ApprovalRequestId",
281
+ "declaration": "export type ApprovalRequestId = Branded<'ApprovalRequestId'>;"
282
+ },
267
283
  {
268
284
  "name": "AssistantMessage",
269
285
  "declaration": "export interface AssistantMessage extends Message {\n readonly role: 'assistant';\n readonly source: ModelMessageSource;\n}"
@@ -281,20 +297,24 @@ export const TYPERT = {
281
297
  "declaration": "export type Branded<B extends string> = string & { readonly [BRAND]: B; };"
282
298
  },
283
299
  {
284
- "name": "CallId",
285
- "declaration": "export type CallId = Branded<'CallId'>;"
300
+ "name": "BrandedNumber",
301
+ "declaration": "export type BrandedNumber<B extends string> = number & { readonly [BRAND]: B; };"
286
302
  },
287
303
  {
288
304
  "name": "CancelOptions",
289
305
  "declaration": "export interface CancelOptions {\n keepInbox?: boolean | undefined;\n}"
290
306
  },
291
307
  {
292
- "name": "CodeDispatchEventData",
293
- "declaration": "export interface CodeDispatchEventData extends CodeDispatchStartEventData {\n isError: boolean;\n content: ContentBlock[];\n}"
308
+ "name": "CommandId",
309
+ "declaration": "export type CommandId = Branded<'CommandId'>;"
310
+ },
311
+ {
312
+ "name": "CommandSource",
313
+ "declaration": "export type CommandSource = CommandSourceMap[keyof CommandSourceMap];"
294
314
  },
295
315
  {
296
- "name": "CodeDispatchStartEventData",
297
- "declaration": "export interface CodeDispatchStartEventData {\n rootCallId: CallId;\n parentCallId: CallId;\n subCallId: CallId;\n name: string;\n arguments: unknown;\n}"
316
+ "name": "CommandSourceMap",
317
+ "declaration": "export interface CommandSourceMap {\n user: { kind: 'user'; };\n}"
298
318
  },
299
319
  {
300
320
  "name": "ContentBlock",
@@ -316,6 +336,10 @@ export const TYPERT = {
316
336
  "name": "ContextSnapshotSection",
317
337
  "declaration": "export interface ContextSnapshotSection {\n readonly name: string;\n readonly text: string;\n}"
318
338
  },
339
+ {
340
+ "name": "ContinuableSubagentDescriptorData",
341
+ "declaration": "export interface ContinuableSubagentDescriptorData extends SubagentDescriptorBase {\n readonly mode: 'continuable';\n readonly label: string;\n readonly agentProvider?: string;\n readonly agentModel?: string;\n readonly agentReasoningEffort?: ReasoningEffortId;\n readonly persona?: string;\n readonly toolFilter?: ToolRestriction;\n}"
342
+ },
319
343
  {
320
344
  "name": "EpochHeader",
321
345
  "declaration": "export interface EpochHeader {\n config: LlmCallConfig;\n adapterDefaults?: LlmCallConfigAdapterDefaults;\n system?: string;\n tools?: ToolSchema[];\n}"
@@ -418,16 +442,28 @@ export const TYPERT = {
418
442
  },
419
443
  {
420
444
  "name": "MessageSourceMap",
421
- "declaration": "export interface MessageSourceMap {\n user: { kind: 'user'; };\n plugin: { kind: 'plugin'; plugin: string; } & ContextFormed;\n model: ModelMessageSource;\n tool: ToolMessageSource;\n}"
445
+ "declaration": "export interface MessageSourceMap {\n user: { kind: 'user'; };\n plugin: { kind: 'plugin'; plugin: string; } & ContextFormed;\n model: ModelMessageSource;\n tool: ToolMessageSource;\n 'agent-message': AgentMessageSource;\n 'subagent-settled': SubagentSettledMessageSource;\n}"
422
446
  },
423
447
  {
424
448
  "name": "ModelMessageSource",
425
449
  "declaration": "export interface ModelMessageSource extends AssistantProvenance {\n kind: 'model';\n}"
426
450
  },
451
+ {
452
+ "name": "OneShotSubagentDescriptorData",
453
+ "declaration": "export interface OneShotSubagentDescriptorData extends SubagentDescriptorBase {\n readonly mode: 'one-shot';\n readonly label?: string;\n}"
454
+ },
427
455
  {
428
456
  "name": "ProviderRequestId",
429
457
  "declaration": "export type ProviderRequestId = Branded<'ProviderRequestId'>;"
430
458
  },
459
+ {
460
+ "name": "PtcDispatchEventData",
461
+ "declaration": "export interface PtcDispatchEventData extends PtcDispatchStartEventData {\n isError: boolean;\n content: ContentBlock[];\n}"
462
+ },
463
+ {
464
+ "name": "PtcDispatchStartEventData",
465
+ "declaration": "export interface PtcDispatchStartEventData {\n rootCallId: ToolCallId;\n parentCallId: ToolCallId;\n subCallId: ToolCallId;\n name: string;\n arguments: unknown;\n}"
466
+ },
431
467
  {
432
468
  "name": "ReasoningBlock",
433
469
  "declaration": "export interface ReasoningBlock {\n type: 'reasoning';\n text: string;\n}"
@@ -446,19 +482,79 @@ export const TYPERT = {
446
482
  },
447
483
  {
448
484
  "name": "RequestHeaderReason",
449
- "declaration": "export type RequestHeaderReason = 'initial' | 'resume' | 'change';"
485
+ "declaration": "export type RequestHeaderReason = 'initial' | 'resume' | 'change' | 'series';"
486
+ },
487
+ {
488
+ "name": "RoomCreatedEvent",
489
+ "declaration": "export interface RoomCreatedEvent {\n readonly version: 1;\n}"
490
+ },
491
+ {
492
+ "name": "RoomDispatchEvent",
493
+ "declaration": "export interface RoomDispatchEvent {\n readonly targets: readonly string[];\n readonly text: string;\n}"
494
+ },
495
+ {
496
+ "name": "RoomGoalEvent",
497
+ "declaration": "export interface RoomGoalEvent {\n readonly text: string;\n}"
498
+ },
499
+ {
500
+ "name": "RoomJsonValue",
501
+ "declaration": "export type RoomJsonValue = null | boolean | number | string | RoomJsonValue[] | { [key: string]: RoomJsonValue; };"
502
+ },
503
+ {
504
+ "name": "RoomMemberAddedEvent",
505
+ "declaration": "export interface RoomMemberAddedEvent {\n readonly name: string;\n readonly kind: 'main-agent' | 'cli';\n readonly provider?: string;\n readonly instructions?: string;\n readonly invitedBy: 'human' | 'agent';\n readonly cwd?: string;\n readonly childSessionId?: SessionId;\n}"
506
+ },
507
+ {
508
+ "name": "RoomMemberRemovedEvent",
509
+ "declaration": "export interface RoomMemberRemovedEvent {\n readonly name: string;\n}"
510
+ },
511
+ {
512
+ "name": "RoomMemberUpdatedEvent",
513
+ "declaration": "export interface RoomMemberUpdatedEvent {\n readonly name: string;\n readonly rename?: string;\n readonly instructions?: string | null;\n readonly cwd?: string | null;\n readonly childSessionId?: SessionId;\n}"
514
+ },
515
+ {
516
+ "name": "RoomRelayEvent",
517
+ "declaration": "export interface RoomRelayEvent {\n readonly id: string;\n readonly from: string;\n readonly to: string;\n readonly content: string;\n readonly provenance?: Record<string, RoomJsonValue>;\n}"
518
+ },
519
+ {
520
+ "name": "RoomRelayResolvedEvent",
521
+ "declaration": "export interface RoomRelayResolvedEvent {\n readonly id: string;\n readonly state: 'confirmed' | 'dismissed' | 'sent';\n}"
522
+ },
523
+ {
524
+ "name": "RoomRunStateEvent",
525
+ "declaration": "export interface RoomRunStateEvent {\n readonly member: string;\n readonly state: 'running' | 'done' | 'cancelled' | 'failed';\n readonly startedAt: number;\n readonly elapsedMs?: number;\n readonly error?: string;\n}"
526
+ },
527
+ {
528
+ "name": "RoomSpeechEvent",
529
+ "declaration": "export interface RoomSpeechEvent {\n readonly member: string;\n readonly text: string;\n readonly childSessionId?: SessionId;\n readonly durationMs?: number;\n}"
530
+ },
531
+ {
532
+ "name": "RoomTaskAddedEvent",
533
+ "declaration": "export interface RoomTaskAddedEvent {\n readonly id: string;\n readonly member: string;\n readonly title: string;\n readonly status: 'pending' | 'in_progress';\n readonly blockedBy?: string;\n}"
534
+ },
535
+ {
536
+ "name": "RoomTaskEditedEvent",
537
+ "declaration": "export interface RoomTaskEditedEvent {\n readonly id: string;\n readonly title?: string;\n readonly blockedBy?: string | null;\n}"
538
+ },
539
+ {
540
+ "name": "RoomTaskUpdatedEvent",
541
+ "declaration": "export interface RoomTaskUpdatedEvent {\n readonly id: string;\n readonly status: 'in_progress' | 'done' | 'cancelled' | 'failed';\n}"
542
+ },
543
+ {
544
+ "name": "SandboxMode",
545
+ "declaration": "export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';"
450
546
  },
451
547
  {
452
548
  "name": "Session",
453
- "declaration": "export class Session {\n get surface(): SessionSurface;\n readonly header: SessionHeader;\n get id(): SessionId;\n readonly firstLiveSeq: number;\n get events(): readonly SessionEvent[];\n get seq(): number;\n append<T extends SessionEventType>(type: T, data: SessionEventMap[T], ...opts: T extends SurfaceEventType ? [opts: SurfaceIntent] : []): SessionEvent<T>;\n requestHeader(): EpochHeader | undefined;\n requestContext(): RequestContext | undefined;\n deriveMessages(): Message[];\n deriveEventMessage(event: SessionEvent): Message | null;\n}"
549
+ "declaration": "export class Session {\n get surface(): SessionSurface;\n readonly header: SessionHeader;\n readonly inheritedEventCount: SessionLogOffset;\n get id(): SessionId;\n readonly firstLiveSeq: SessionLogOffset;\n eventAt(seq: SessionSeq): SessionEvent | undefined;\n snapshotEvents(fromSeq: SessionLogOffset = SessionLogOffset(0), toSeqExclusive: SessionLogOffset = this.seq): readonly SessionEvent[];\n ownEvents(): readonly SessionEvent[];\n isOwnSeq(seq: SessionSeq): boolean;\n get seq(): SessionLogOffset;\n append<T extends SessionEventType>(type: T, data: SessionEventMap[T], ...opts: T extends SurfaceEventType ? [opts: SurfaceIntent] : []): SessionEvent<T>;\n requestHeader(): EpochHeader | undefined;\n requestContext(): RequestContext | undefined;\n deriveMessages(): Message[];\n deriveEventMessage(event: SessionEvent): Message | null;\n}"
454
550
  },
455
551
  {
456
552
  "name": "SessionEvent",
457
- "declaration": "export type SessionEvent<T extends SessionEventType = SessionEventType> = { [K in SessionEventType]: { type: K; seq: number; time: number; data: SessionEventMap[K]; ignorable?: true; } & (K extends SurfaceEventType ? { sourceEventSeqs?: number[]; surfaceOp?: SurfaceOp; } : object) }[T];"
553
+ "declaration": "export type SessionEvent<T extends SessionEventType = SessionEventType> = { [K in SessionEventType]: { type: K; seq: SessionSeq; time: number; data: SessionEventMap[K]; ignorable?: true; } & (K extends SurfaceEventType ? { sourceEventSeqs?: SessionSeq[]; surfaceOp?: SurfaceOp; } : object) }[T];"
458
554
  },
459
555
  {
460
556
  "name": "SessionEventMap",
461
- "declaration": "export interface SessionEventMap {\n 'turn/start': { turn: number; };\n 'turn/end': { turn: number; reason: TurnEndReason; };\n 'step/start': { turn: number; step: number; };\n 'step/end': { turn: number; step: number; };\n 'user/message': UserMessage;\n 'assistant/chunk': { turn: number; step: number; chunk: StreamChunk; };\n 'assistant/message': { turn: number; step: number; message: AssistantMessage; usage?: TokenUsage; interrupted?: true; };\n 'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string; };\n 'tool/result': { turn: number; step: number; message: ToolResultMessage; error?: { name: string; code: string; }; meta?: JsonValue; };\n 'todo/write': { todos: TodoItem[]; };\n 'request/header': { header: EpochHeader; reason: RequestHeaderReason; };\n 'request/context': RequestContext;\n 'session/end-seed': Record<string, never>;\n 'tool/code-dispatch-start': CodeDispatchStartEventData;\n 'tool/code-dispatch': CodeDispatchEventData;\n 'agent/inbox/spliced': { target: InboxTarget; start: number; removedCount?: number; inserted: UserMessage[]; outcome?: 'canceled'; };\n}"
557
+ "declaration": "export interface SessionEventMap {\n 'turn/start': { turn: number; };\n 'turn/end': { turn: number; reason: TurnEndReason; };\n 'step/start': { turn: number; step: number; };\n 'step/end': { turn: number; step: number; };\n 'user/message': UserMessage;\n 'assistant/chunk': { turn: number; step: number; chunk: StreamChunk; };\n 'assistant/message': { turn: number; step: number; message: AssistantMessage; usage?: TokenUsage; interrupted?: true; };\n 'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string; };\n 'tool/result': { turn: number; step: number; message: ToolResultMessage; error?: { name: string; code: string; }; meta?: JsonValue; };\n 'request/header': { header: EpochHeader; reason: RequestHeaderReason; startsSeries?: true; };\n 'request/context': RequestContext;\n 'session/end-seed': Record<string, never>;\n 'agent/inbox/spliced': { target: InboxTarget; start: number; removedCount?: number; inserted: UserMessage[]; outcome?: 'canceled'; };\n 'approval/asked': { id: ApprovalRequestId; toolName: string; callId?: ToolCallId; reason?: string; };\n 'approval/decided': { id: ApprovalRequestId; outcome: ApprovalOutcome; };\n 'approval/policy': { policy: ApprovalPolicy; source?: 'delegation'; };\n 'tool/code-dispatch-start': PtcDispatchStartEventData;\n 'tool/code-dispatch': PtcDispatchEventData;\n 'command/run': { commandId: CommandId; name: string; args?: string; source: CommandSource; };\n 'command/done': { commandId: CommandId; kind: 'success' | 'error'; text?: string; sourceEventSeq?: import('@deepseek-ai/dsh-session/types').SessionSeq; };\n 'subagent/descriptor': SubagentDescriptorData;\n 'session/title': SessionTitleEventData;\n 'todo/write': { todos: TodoItem[]; };\n 'sandbox/mode': { mode: SandboxMode; source?: 'delegation'; };\n 'agent-preset/selected': { agentPreset: string; };\n 'room/created': RoomCreatedEvent;\n 'room/member-added': RoomMemberAddedEvent;\n 'room/member-updated': RoomMemberUpdatedEvent;\n 'room/member-removed': RoomMemberRemovedEvent;\n 'room/dispatch': RoomDispatchEvent;\n 'room/speech': RoomSpeechEvent;\n 'room/run-state': RoomRunStateEvent;\n 'room/relay': RoomRelayEvent;\n 'room/relay-resolved': RoomRelayResolvedEvent;\n 'room/task-added': RoomTaskAddedEvent;\n 'room/task-updated': RoomTaskUpdatedEvent;\n 'room/task-edited': RoomTaskEditedEvent;\n 'room/goal': RoomGoalEvent;\n}"
462
558
  },
463
559
  {
464
560
  "name": "SessionEventType",
@@ -466,19 +562,55 @@ export const TYPERT = {
466
562
  },
467
563
  {
468
564
  "name": "SessionHeader",
469
- "declaration": "export interface SessionHeader {\n readonly version: number;\n readonly id: SessionId;\n readonly createdAt: number;\n readonly cwd?: string;\n readonly parentSession?: SessionId;\n readonly seedLength?: number;\n readonly origin?: 'subagent';\n readonly delegationDepth?: number;\n readonly agentPreset?: string;\n}"
565
+ "declaration": "export interface SessionHeader {\n readonly version: number;\n readonly id: SessionId;\n readonly createdAt: number;\n readonly cwd?: string;\n readonly parentSession?: SessionId;\n readonly isSeeded: boolean;\n readonly origin?: 'subagent';\n readonly delegationDepth?: number;\n readonly agentPreset?: string;\n}"
470
566
  },
471
567
  {
472
568
  "name": "SessionId",
473
569
  "declaration": "export type SessionId = Branded<'SessionId'>;"
474
570
  },
571
+ {
572
+ "name": "SessionLogOffset",
573
+ "declaration": "export type SessionLogOffset = BrandedNumber<'SessionLogOffset'>;"
574
+ },
575
+ {
576
+ "name": "SessionSeq",
577
+ "declaration": "export type SessionSeq = BrandedNumber<'SessionSeq'>;"
578
+ },
475
579
  {
476
580
  "name": "SessionSurface",
477
- "declaration": "export interface SessionSurface {\n readonly nodes: readonly number[];\n readonly replaceGeneration: number;\n}"
581
+ "declaration": "export interface SessionSurface {\n readonly nodes: readonly SessionSeq[];\n readonly replaceGeneration: number;\n}"
582
+ },
583
+ {
584
+ "name": "SessionTitleEventData",
585
+ "declaration": "export interface SessionTitleEventData {\n readonly title: string;\n readonly messageSeqs: SessionSeq[];\n readonly source: SessionTitleSource;\n}"
586
+ },
587
+ {
588
+ "name": "SessionTitleModelProvenance",
589
+ "declaration": "export interface SessionTitleModelProvenance {\n readonly provider: string;\n readonly model: string;\n}"
590
+ },
591
+ {
592
+ "name": "SessionTitleProviderId",
593
+ "declaration": "export type SessionTitleProviderId = Branded<'SessionTitleProviderId'>;"
594
+ },
595
+ {
596
+ "name": "SessionTitleSource",
597
+ "declaration": "export type SessionTitleSource = { readonly kind: 'fallback'; } | { readonly kind: 'provider'; readonly provider: SessionTitleProviderId; readonly model?: SessionTitleModelProvenance; } | { readonly kind: 'user'; };"
478
598
  },
479
599
  {
480
600
  "name": "StreamChunk",
481
- "declaration": "export type StreamChunk = { type: 'block-start'; index: number; blockType: ContentBlockType; } | { type: 'text-delta'; index: number; text: string; } | { type: 'reasoning-delta'; index: number; text: string; } | { type: 'tool-call-delta'; index: number; id: CallId; name?: string; argumentsDelta: string; } | { type: 'block-end'; index: number; block: ContentBlock; } | { type: 'usage'; usage: TokenUsage; } | { type: 'finish'; reason: FinishReason; replayState?: ReplayEnvelope; };"
601
+ "declaration": "export type StreamChunk = { type: 'block-start'; index: number; blockType: ContentBlockType; } | { type: 'text-delta'; index: number; text: string; } | { type: 'reasoning-delta'; index: number; text: string; } | { type: 'tool-call-delta'; index: number; id: ToolCallId; name?: string; argumentsDelta: string; } | { type: 'block-end'; index: number; block: ContentBlock; } | { type: 'usage'; usage: TokenUsage; } | { type: 'finish'; reason: FinishReason; replayState?: ReplayEnvelope; };"
602
+ },
603
+ {
604
+ "name": "SubagentDescriptorBase",
605
+ "declaration": "export interface SubagentDescriptorBase {\n readonly version: number;\n readonly mode: 'one-shot' | 'continuable';\n readonly provider: string;\n}"
606
+ },
607
+ {
608
+ "name": "SubagentDescriptorData",
609
+ "declaration": "export type SubagentDescriptorData = OneShotSubagentDescriptorData | ContinuableSubagentDescriptorData;"
610
+ },
611
+ {
612
+ "name": "SubagentSettledMessageSource",
613
+ "declaration": "export interface SubagentSettledMessageSource {\n readonly kind: 'subagent-settled';\n readonly form: 'notice';\n readonly summary: string;\n readonly senderSessionId: SessionId;\n}"
482
614
  },
483
615
  {
484
616
  "name": "SurfaceEventType",
@@ -486,11 +618,11 @@ export const TYPERT = {
486
618
  },
487
619
  {
488
620
  "name": "SurfaceIntent",
489
- "declaration": "export interface SurfaceIntent {\n surfaceOp: SurfaceOp;\n sourceEventSeqs?: number[];\n}"
621
+ "declaration": "export interface SurfaceIntent {\n surfaceOp: SurfaceOp;\n sourceEventSeqs?: SessionSeq[];\n}"
490
622
  },
491
623
  {
492
624
  "name": "SurfaceOp",
493
- "declaration": "export type SurfaceOp = 'append' | { op: 'replace'; start: number; end: number; };"
625
+ "declaration": "export type SurfaceOp = 'append' | { op: 'replace'; start: SessionSeq; end: SessionSeq; };"
494
626
  },
495
627
  {
496
628
  "name": "TextBlock",
@@ -502,19 +634,27 @@ export const TYPERT = {
502
634
  },
503
635
  {
504
636
  "name": "TokenUsage",
505
- "declaration": "export interface TokenUsage {\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens?: number;\n cacheWriteTokens?: number;\n reasoningTokens?: number;\n}"
637
+ "declaration": "export interface TokenUsage {\n inputTokens: number;\n outputTokens: number;\n totalTokens?: number;\n cacheReadTokens?: number;\n cacheWriteTokens?: number;\n reasoningTokens?: number;\n}"
506
638
  },
507
639
  {
508
640
  "name": "ToolCallBlock",
509
- "declaration": "export interface ToolCallBlock {\n type: 'tool-call';\n id: CallId;\n name: string;\n arguments: string;\n}"
641
+ "declaration": "export interface ToolCallBlock {\n type: 'tool-call';\n id: ToolCallId;\n name: string;\n arguments: string;\n}"
642
+ },
643
+ {
644
+ "name": "ToolCallId",
645
+ "declaration": "export type ToolCallId = Branded<'ToolCallId'>;"
510
646
  },
511
647
  {
512
648
  "name": "ToolMessageSource",
513
- "declaration": "export interface ToolMessageSource {\n kind: 'tool';\n callId: CallId;\n}"
649
+ "declaration": "export interface ToolMessageSource {\n kind: 'tool';\n callId: ToolCallId;\n}"
650
+ },
651
+ {
652
+ "name": "ToolRestriction",
653
+ "declaration": "export interface ToolRestriction {\n readonly allow?: readonly string[];\n readonly deny?: readonly string[];\n}"
514
654
  },
515
655
  {
516
656
  "name": "ToolResultBlock",
517
- "declaration": "export interface ToolResultBlock {\n type: 'tool-result';\n toolCallId: CallId;\n content: ContentBlock[];\n isError?: boolean;\n}"
657
+ "declaration": "export interface ToolResultBlock {\n type: 'tool-result';\n toolCallId: ToolCallId;\n content: ContentBlock[];\n isError?: boolean;\n}"
518
658
  },
519
659
  {
520
660
  "name": "ToolResultMessage",
@@ -1 +1 @@
1
- {"version":3,"file":"typert.remote-client.d.ts","names":["list","read","reveal","turnFiles"],"sources":["../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;IA2TEA,I;IA4EMC,I;IA6EAC,M;IA1HNC,S;;;IA/BAH,kB;IA4EMC,kB;IA6EAC,oB;IA1HNC,uB;;;;;;IA/BAH,wB;IA4EMC,wB;IA6EAC,0B;IA1HNC,6B","ignoreList":[]}
1
+ {"version":3,"file":"typert.remote-client.d.ts","names":["list","read","reveal","turnFiles"],"sources":["../src/index.ts"],"sourcesContent":[null],"mappings":";;;;;;;;;;IA+WQA,I;IA0FAC,I;IA6EAC,M;IAlIAC,S;;;IArCAH,kB;IA0FAC,kB;IA6EAC,oB;IAlIAC,uB;;;;;;IArCAH,wB;IA0FAC,wB;IA6EAC,0B;IAlIAC,6B","ignoreList":[]}
@@ -90,7 +90,7 @@ export const TYPERT_REMOTE = {
90
90
  typeSymbol: '@khorsheed/dsh-file-preview/types#FilePreviewList',
91
91
  schema: _khorsheed_dsh_file_preview_filePreview_list_result$schema,
92
92
  },
93
- sourceLocation: {"file":"packages/file-preview/src/index.ts","line":316,"column":3},
93
+ sourceLocation: {"file":"packages/file-preview/src/index.ts","line":368,"column":9},
94
94
  },
95
95
  {
96
96
  id: '@khorsheed/dsh-file-preview#filePreview/read',
@@ -131,7 +131,7 @@ export const TYPERT_REMOTE = {
131
131
  typeSymbol: '@khorsheed/dsh-file-preview/types#FilePreviewRead',
132
132
  schema: _khorsheed_dsh_file_preview_filePreview_read_result$schema,
133
133
  },
134
- sourceLocation: {"file":"packages/file-preview/src/index.ts","line":392,"column":9},
134
+ sourceLocation: {"file":"packages/file-preview/src/index.ts","line":458,"column":9},
135
135
  },
136
136
  {
137
137
  id: '@khorsheed/dsh-file-preview#filePreview/reveal',
@@ -172,7 +172,7 @@ export const TYPERT_REMOTE = {
172
172
  typeSymbol: '@khorsheed/dsh-file-preview/types#FilePreviewReveal',
173
173
  schema: _khorsheed_dsh_file_preview_filePreview_reveal_result$schema,
174
174
  },
175
- sourceLocation: {"file":"packages/file-preview/src/index.ts","line":469,"column":9},
175
+ sourceLocation: {"file":"packages/file-preview/src/index.ts","line":535,"column":9},
176
176
  },
177
177
  {
178
178
  id: '@khorsheed/dsh-file-preview#filePreview/turnFiles',
@@ -202,7 +202,7 @@ export const TYPERT_REMOTE = {
202
202
  typeSymbol: '@khorsheed/dsh-file-preview/types#FilePreviewTurnMap',
203
203
  schema: _khorsheed_dsh_file_preview_filePreview_turnFiles_result$schema,
204
204
  },
205
- sourceLocation: {"file":"packages/file-preview/src/index.ts","line":347,"column":3},
205
+ sourceLocation: {"file":"packages/file-preview/src/index.ts","line":405,"column":9},
206
206
  },
207
207
  ],
208
208
  }
@@ -305,7 +305,7 @@ export class BashWriteCollector {
305
305
  #replay(session) {
306
306
  this.#pending.set(session.id, new Map());
307
307
  this.#captured.set(session.id, new Map());
308
- for (const event of session.events)
308
+ for (const event of session.snapshotEvents())
309
309
  this.#onEvent(session, event);
310
310
  }
311
311
  #onEvent(session, event) {
@@ -26,7 +26,8 @@ export declare function diffsFromResultMeta(meta: unknown): readonly FilePreview
26
26
  * first-seen order and deduplicated by display path; a later occurrence
27
27
  * refreshes the entry's op and location without moving it, and each write/edit
28
28
  * tool result appends its change's diff to the entry's `diffs` in event order.
29
- * Nested Code Mode file dispatches (`tool/code-dispatch` events) count too —
29
+ * Nested Code Mode file dispatches (`tool/code-dispatch` events, or
30
+ * `tool/ptc-dispatch` on seats running the PTC rename) count too —
30
31
  * they carry no turn/step of their own, so the entry borrows the enclosing
31
32
  * root call's location.
32
33
  * @param events - the session's events in ascending seq order.
@@ -55,11 +56,12 @@ export type TurnFilesByTurn = ReadonlyMap<number, ReadonlyMap<string, TurnFileFa
55
56
  * single source of truth. Unlike {@link foldFilePreview}, this does NOT dedupe
56
57
  * a path to its last occurrence: a file touched in two turns appears in BOTH
57
58
  * turn maps, so each turn's card lists exactly what that turn mutated.
58
- * write/edit calls register with their own turn; nested Code Mode dispatches
59
- * borrow the enclosing root call's turn (dispatch events carry none); a
60
- * `tool/result` whose presentation meta carries diffs registers its paths (the
61
- * render-intent vocabulary) with the result's turn, and per-turn line deltas
62
- * are summed from the diffs. A `write` whose result carries no diffs is a
59
+ * Reads leave no card entry (the card is the mutation vocabulary — write/edit
60
+ * only), so a pure-read call never registers; write/edit calls register with
61
+ * their own turn; nested Code Mode dispatches borrow the enclosing root call's
62
+ * turn (dispatch events carry none); a `tool/result` whose presentation meta
63
+ * carries diffs registers its paths (the render-intent vocabulary) with the
64
+ * result's turn, and per-turn line deltas are summed from the diffs. A `write` whose result carries no diffs is a
63
65
  * create (the write tool records no diffs when the file had no prior content):
64
66
  * its full written content — read from the call arguments, which the log keeps
65
67
  * — is the added-line count, once per file per turn.
@@ -1 +1 @@
1
- {"version":3,"file":"fold.d.ts","sourceRoot":"","sources":["../../src/fold.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAE9G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAG5D,OAAO,KAAK,EAAqC,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAQnG,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,uCAAuC;IACvC,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAA;IAC1B,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,2EAA2E;AAC3E,MAAM,MAAM,qBAAqB,GAAG;IAClC,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,gCAAgC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB,CAAA;AAmBD,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAOvG;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,qBAAqB,EAAE,GAAG,SAAS,CAc/F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe,CA8DlG;AAED,kGAAkG;AAClG,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,uFAAuF;IACvF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,4FAA4F;AAC5F,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;AAuCpF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,eAAe,CAyHtF"}
1
+ {"version":3,"file":"fold.d.ts","sourceRoot":"","sources":["../../src/fold.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAE9G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAG5D,OAAO,KAAK,EAAqC,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAiBnG,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,uCAAuC;IACvC,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAA;IAC1B,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,2EAA2E;AAC3E,MAAM,MAAM,qBAAqB,GAAG;IAClC,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,gCAAgC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB,CAAA;AAmBD,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAOvG;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,qBAAqB,EAAE,GAAG,SAAS,CAc/F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe,CAiElG;AAED,kGAAkG;AAClG,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,uFAAuF;IACvF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,4FAA4F;AAC5F,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;AAuCpF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,eAAe,CAiItF"}
package/lib/types/fold.js CHANGED
@@ -1,6 +1,14 @@
1
1
  /** Pure log fold over the files a session read, wrote, or edited. @module @khorsheed/dsh-file-preview/fold */
2
2
  /** Tool names whose calls record a touched file path. */
3
3
  const FILE_TOOL_NAMES = new Set(['read', 'write', 'edit']);
4
+ /**
5
+ * Settled nested-dispatch event names, matched by name (feature detection,
6
+ * never a version check): `tool/code-dispatch` is the Code Mode name every
7
+ * host through 0.1.2-rc.1 emits; `tool/ptc-dispatch` is the PTC rename later
8
+ * 0.1.2 seats move to. Both carry the same settled-dispatch payload, so one
9
+ * branch serves either.
10
+ */
11
+ const DISPATCH_EVENT_NAMES = new Set(['tool/code-dispatch', 'tool/ptc-dispatch']);
4
12
  /** The argument key carrying the touched path (shared by all three file tools). */
5
13
  const FILE_PATH_ARGUMENT = 'file_path';
6
14
  /**
@@ -57,7 +65,8 @@ export function diffsFromResultMeta(meta) {
57
65
  * first-seen order and deduplicated by display path; a later occurrence
58
66
  * refreshes the entry's op and location without moving it, and each write/edit
59
67
  * tool result appends its change's diff to the entry's `diffs` in event order.
60
- * Nested Code Mode file dispatches (`tool/code-dispatch` events) count too —
68
+ * Nested Code Mode file dispatches (`tool/code-dispatch` events, or
69
+ * `tool/ptc-dispatch` on seats running the PTC rename) count too —
61
70
  * they carry no turn/step of their own, so the entry borrows the enclosing
62
71
  * root call's location.
63
72
  * @param events - the session's events in ascending seq order.
@@ -88,16 +97,19 @@ export function foldFilePreview(events, maxFiles) {
88
97
  record(target, event.seq, event.data.turn, event.data.step);
89
98
  continue;
90
99
  }
91
- if (event.type === 'tool/code-dispatch') {
100
+ if (DISPATCH_EVENT_NAMES.has(event.type)) {
92
101
  // The dispatch event IS the settled outcome: a failed sub-call changed
93
- // nothing, so only successful file touches record.
94
- if (event.data.isError)
102
+ // nothing, so only successful file touches record. The PTC rename keeps
103
+ // the code-dispatch payload shape (the ptc name has no SessionEventMap
104
+ // row yet), so the data reads through the declared constituent.
105
+ const dispatch = event;
106
+ if (dispatch.data.isError)
95
107
  continue;
96
- const target = targetFromArguments(event.data.name, event.data.arguments);
108
+ const target = targetFromArguments(dispatch.data.name, dispatch.data.arguments);
97
109
  if (target === undefined)
98
110
  continue;
99
- const site = callSites.get(String(event.data.rootCallId)) ?? { turn: 0, step: 0 };
100
- record(target, event.seq, site.turn, site.step);
111
+ const site = callSites.get(String(dispatch.data.rootCallId)) ?? { turn: 0, step: 0 };
112
+ record(target, dispatch.seq, site.turn, site.step);
101
113
  continue;
102
114
  }
103
115
  if (event.type === 'tool/result') {
@@ -172,11 +184,12 @@ function resultCallFacts(message) {
172
184
  * single source of truth. Unlike {@link foldFilePreview}, this does NOT dedupe
173
185
  * a path to its last occurrence: a file touched in two turns appears in BOTH
174
186
  * turn maps, so each turn's card lists exactly what that turn mutated.
175
- * write/edit calls register with their own turn; nested Code Mode dispatches
176
- * borrow the enclosing root call's turn (dispatch events carry none); a
177
- * `tool/result` whose presentation meta carries diffs registers its paths (the
178
- * render-intent vocabulary) with the result's turn, and per-turn line deltas
179
- * are summed from the diffs. A `write` whose result carries no diffs is a
187
+ * Reads leave no card entry (the card is the mutation vocabulary — write/edit
188
+ * only), so a pure-read call never registers; write/edit calls register with
189
+ * their own turn; nested Code Mode dispatches borrow the enclosing root call's
190
+ * turn (dispatch events carry none); a `tool/result` whose presentation meta
191
+ * carries diffs registers its paths (the render-intent vocabulary) with the
192
+ * result's turn, and per-turn line deltas are summed from the diffs. A `write` whose result carries no diffs is a
180
193
  * create (the write tool records no diffs when the file had no prior content):
181
194
  * its full written content — read from the call arguments, which the log keeps
182
195
  * — is the added-line count, once per file per turn.
@@ -257,6 +270,11 @@ export function foldFilePreviewByTurn(events) {
257
270
  const target = pathFromToolCall(event.data.name, event.data.arguments);
258
271
  if (target === undefined)
259
272
  continue;
273
+ // The card's vocabulary is mutations only: a read call registers
274
+ // nothing (it may still anchor a nested code dispatch's location via
275
+ // callSites above).
276
+ if (target.op === 'read')
277
+ continue;
260
278
  if (target.op === 'write') {
261
279
  const content = contentFromWriteCall(event.data.arguments);
262
280
  if (content !== undefined) {
@@ -268,19 +286,24 @@ export function foldFilePreviewByTurn(events) {
268
286
  recordLocation(target.path, event.data.turn, event.seq, event.data.step);
269
287
  continue;
270
288
  }
271
- if (event.type === 'tool/code-dispatch') {
289
+ if (DISPATCH_EVENT_NAMES.has(event.type)) {
272
290
  // The dispatch event IS the settled outcome: a failed sub-call changed
273
291
  // nothing, so only successful file touches record. Code Mode writes are
274
292
  // uncountable (no result diffs reach the session), so they register the
275
- // path and poison the line-count totals (unknown wins).
276
- if (event.data.isError)
293
+ // path and poison the line-count totals (unknown wins). Same payload
294
+ // shape caveat as foldFilePreview above.
295
+ const dispatch = event;
296
+ if (dispatch.data.isError)
277
297
  continue;
278
- const target = targetFromArguments(event.data.name, event.data.arguments);
298
+ const target = targetFromArguments(dispatch.data.name, dispatch.data.arguments);
279
299
  if (target === undefined)
280
300
  continue;
281
- const site = callSites.get(String(event.data.rootCallId)) ?? { turn: 0, step: 0 };
301
+ // Reads leave no card entry here too — only mutations register.
302
+ if (target.op === 'read')
303
+ continue;
304
+ const site = callSites.get(String(dispatch.data.rootCallId)) ?? { turn: 0, step: 0 };
282
305
  sessionSeen.add(target.path);
283
- recordContribution(target.path, site.turn, event.seq, site.step, undefined, undefined, true);
306
+ recordContribution(target.path, site.turn, dispatch.seq, site.step, undefined, undefined, true);
284
307
  continue;
285
308
  }
286
309
  if (event.type === 'tool/result') {