@demicodes/agent 0.10.2 → 0.10.3

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/dist/index.mjs CHANGED
@@ -1,15 +1,14 @@
1
- import { a as cloneBlocks, i as applyTranscriptPatches, n as createWebSocketServerTransport, o as ProviderStreamError, r as AgentClient, s as isContextLengthExceeded, t as createWebSocketClientTransport } from "./websocket-transport-os0mfcLi.mjs";
2
- import { AbortError, abortable, asError, asRecord, asString, bytesToBase64, createId, delay, isAbortError, isRecord, noop, parseJsonOrString, safeJsonStringify, throwIfAborted, truncate } from "@demicodes/utils";
1
+ import { a as cloneBlocks, i as applyTranscriptPatches, n as createWebSocketServerTransport, r as AgentClient, t as createWebSocketClientTransport } from "./websocket-transport-Bod0puog.mjs";
2
+ import { AbortError, abortable, asError, asRecord, asString, createId, delay, isAbortError, isRecord, noop, parseJsonOrString, safeJsonStringify, throwIfAborted, truncate } from "@demicodes/utils";
3
3
  import { BashEnvironment, CommandRegistry, MAX_TIMEOUT_MS, heredocDelimiter, shellQuote } from "@demicodes/shell";
4
4
  import { providerRuntime } from "@demicodes/provider";
5
- import { modelAcceptsMediaType, sniffModelMediaType } from "@demicodes/core";
6
5
  //#region src/transcript.ts
7
6
  const DEFAULT_MODEL_TEXT_HEAD_CHARS = 8e3;
8
7
  const DEFAULT_MODEL_TEXT_TAIL_CHARS = 8e3;
9
8
  const IMAGE_BASE_TOKENS = 1600;
10
9
  const IMAGE_BYTES_PER_TOKEN = 1e3;
11
10
  const DOCUMENT_BYTES_PER_TOKEN = 4;
12
- var TranscriptLog = class {
11
+ var Transcript = class {
13
12
  blocks;
14
13
  idFactory;
15
14
  now;
@@ -24,7 +23,7 @@ var TranscriptLog = class {
24
23
  this.replayHeadChars = options.replayTextBounds?.headChars ?? DEFAULT_MODEL_TEXT_HEAD_CHARS;
25
24
  this.replayTailChars = options.replayTextBounds?.tailChars ?? DEFAULT_MODEL_TEXT_TAIL_CHARS;
26
25
  }
27
- toJSON() {
26
+ snapshot() {
28
27
  return { blocks: structuredClone(this.blocks) };
29
28
  }
30
29
  /** Monotonic revision, advanced once per drained patch batch. */
@@ -149,22 +148,6 @@ var TranscriptLog = class {
149
148
  }
150
149
  return null;
151
150
  }
152
- /**
153
- * Drops the blocks from `index` onward, returning true when anything went.
154
- * Used to unwind a turn to its resume point before re-inferring.
155
- */
156
- truncateFrom(index) {
157
- let changed = false;
158
- for (let i = this.blocks.length - 1; i >= index; i -= 1) {
159
- this.blocks.splice(i, 1);
160
- this.record({
161
- op: "remove",
162
- path: ["blocks", i]
163
- });
164
- changed = true;
165
- }
166
- return changed;
167
- }
168
151
  applyProviderEvent(model, event) {
169
152
  switch (event.type) {
170
153
  case "thinking_start": return this.appendThinking(model, "");
@@ -189,7 +172,7 @@ var TranscriptLog = class {
189
172
  status: "executing",
190
173
  streamingOutput: [],
191
174
  output: [],
192
- view: null
175
+ metadata: null
193
176
  });
194
177
  case "response": return this.appendBlock({
195
178
  type: "response",
@@ -204,20 +187,19 @@ var TranscriptLog = class {
204
187
  createdAt: this.now(),
205
188
  model,
206
189
  message: event.message,
207
- code: event.code,
208
- ...event.diagnostics ? { diagnostics: event.diagnostics } : {}
190
+ code: event.code
209
191
  });
210
192
  case "abort": return this.pushAbort(model);
211
193
  }
212
194
  }
213
- completeToolCall(toolUseId, output, isError = false, view = null) {
195
+ completeToolCall(toolUseId, output, isError = false, metadata = null) {
214
196
  const index = findPendingToolCallIndex(this.blocks, toolUseId);
215
197
  if (index === null) return null;
216
198
  const block = this.blocks[index];
217
199
  block.status = isError ? "error" : "completed";
218
200
  block.output = output;
219
201
  block.streamingOutput = [];
220
- block.view = view;
202
+ block.metadata = metadata;
221
203
  this.recordBlockReplace(index);
222
204
  return block;
223
205
  }
@@ -396,15 +378,9 @@ var TranscriptLog = class {
396
378
  * history), it anchors the estimate and only blocks streamed after it are
397
379
  * char-estimated; otherwise the whole replay window is estimated at ~4
398
380
  * chars/token with fixed weights for images and documents.
399
- *
400
- * When `contextWindow` is given, an anchor larger than the window is
401
- * discarded: a single request's usage physically cannot exceed the window,
402
- * so such a value is a provider violation of the response-usage contract
403
- * (e.g. a turn-cumulative total) and would poison the estimate.
404
381
  */
405
- estimateContextTokens(contextWindow) {
406
- let anchor = this.usageAnchor();
407
- if (anchor !== null && contextWindow !== void 0 && contextWindow > 0 && anchor.tokens > contextWindow) anchor = null;
382
+ estimateContextTokens() {
383
+ const anchor = this.usageAnchor();
408
384
  if (anchor === null) return this.replayableBlocks().reduce((total, block) => total + estimateBlockTokens(block), 0);
409
385
  let total = anchor.tokens;
410
386
  for (let i = anchor.blockIndex + 1; i < this.blocks.length; i += 1) total += estimateBlockTokens(this.blocks[i]);
@@ -555,7 +531,6 @@ function stringifyUserContent(content) {
555
531
  switch (content.type) {
556
532
  case "text": return content.text;
557
533
  case "image": return content.source.type === "url" ? content.source.url : content.source.mediaType;
558
- case "video": return content.source.type === "url" ? content.source.url : content.source.mediaType;
559
534
  case "document": return `${content.source.fileName} ${content.source.mediaType}`;
560
535
  case "reference": return content.reference;
561
536
  }
@@ -564,7 +539,6 @@ function stringifyToolResult(content) {
564
539
  switch (content.type) {
565
540
  case "text": return content.text;
566
541
  case "image": return content.source.mediaType;
567
- case "video": return content.source.mediaType;
568
542
  }
569
543
  }
570
544
  function boundUserContent(content, headChars, tailChars) {
@@ -620,57 +594,6 @@ function retryDelayMs(policy, attempt, retryAfterMs) {
620
594
  return Math.floor(Math.random() * ceiling);
621
595
  }
622
596
  //#endregion
623
- //#region src/recovery.ts
624
- /**
625
- * Whether a block is a leftover of the failed attempt that nobody can have acted on.
626
- *
627
- * Everything else has to be assumed acted on. Transcript blocks stream outward as
628
- * they are produced and products turn them into effects that cannot be recalled —
629
- * rendering them, posting them to a chat, executing the tool they describe. A tool
630
- * call counts whatever its status: one still marked executing outlived the process
631
- * that was running it, so whether its effect landed is unknown, and unknown has to
632
- * be treated as landed. An abort block is history the user created, not a leftover.
633
- *
634
- * Thinking is the interesting case: products display it, but nothing keys off it and
635
- * a rerun simply reasons again, so it is discardable. A `response` is not — it
636
- * records a provider request that did complete, and its usage anchors the context
637
- * estimate for everything after it.
638
- */
639
- function isDiscardableLeftover(block) {
640
- switch (block.type) {
641
- case "thinking":
642
- case "redacted_thinking":
643
- case "error": return true;
644
- case "text": return block.text.trim().length === 0;
645
- default: return false;
646
- }
647
- }
648
- /**
649
- * Finds how far back an unfinished turn can be unwound before re-inferring.
650
- *
651
- * This is the single decision behind both recovery paths. A transient provider
652
- * failure and a human asking to continue a dead round ask the same question — how
653
- * do we finish this turn — and the answer depends only on what has already left
654
- * the process, never on which of the two asked or on how the turn died.
655
- */
656
- function findResumePoint(blocks) {
657
- for (let i = blocks.length - 1; i >= 0; i -= 1) {
658
- const block = blocks[i];
659
- if (block.type === "user") return {
660
- cut: i + 1,
661
- isFullRerun: true
662
- };
663
- if (!isDiscardableLeftover(block)) return {
664
- cut: i + 1,
665
- isFullRerun: false
666
- };
667
- }
668
- return {
669
- cut: blocks.length,
670
- isFullRerun: false
671
- };
672
- }
673
- //#endregion
674
597
  //#region src/yield-scheduler.ts
675
598
  /**
676
599
  * Tracks scheduled `yield` wakeups and their timers. This class owns only the
@@ -689,15 +612,14 @@ var YieldScheduler = class {
689
612
  return this.pending.length > 0;
690
613
  }
691
614
  /** Registers a new (unarmed) wakeup and returns its id. */
692
- schedule(durationMs, metadata) {
615
+ schedule(durationMs) {
693
616
  const id = this.idFactory();
694
617
  this.pending.push({
695
618
  id,
696
619
  durationMs,
697
620
  timer: null,
698
621
  dueAt: null,
699
- armed: false,
700
- metadata
622
+ armed: false
701
623
  });
702
624
  return id;
703
625
  }
@@ -708,7 +630,7 @@ var YieldScheduler = class {
708
630
  if (wakeup.armed) continue;
709
631
  wakeup.armed = true;
710
632
  wakeup.dueAt = now + wakeup.durationMs;
711
- wakeup.timer = setTimeout(() => this.onFire(wakeup.id, wakeup.metadata), wakeup.durationMs);
633
+ wakeup.timer = setTimeout(() => this.onFire(wakeup.id), wakeup.durationMs);
712
634
  }
713
635
  }
714
636
  /** Removes the wakeup with `wakeupId` (clearing its timer); returns whether it existed. */
@@ -867,6 +789,21 @@ function buildCompactionSummaryRequest(rendered, context) {
867
789
  };
868
790
  }
869
791
  //#endregion
792
+ //#region src/provider-stream-error.ts
793
+ /** Error carrying a provider stream's error code (e.g. `context_length_exceeded`). */
794
+ var ProviderStreamError = class extends Error {
795
+ code;
796
+ constructor(message, code) {
797
+ super(message);
798
+ this.name = "ProviderStreamError";
799
+ this.code = code;
800
+ }
801
+ };
802
+ /** Whether an error is a provider stream error reporting the context window was exceeded. */
803
+ function isContextLengthExceeded(error) {
804
+ return error instanceof ProviderStreamError && error.code === "context_length_exceeded";
805
+ }
806
+ //#endregion
870
807
  //#region src/compaction-controller.ts
871
808
  /**
872
809
  * Owns the compaction algorithm: pick a window of old transcript blocks, summarize
@@ -883,9 +820,9 @@ var CompactionController = class {
883
820
  const contextWindow = targetModel.model.contextWindow;
884
821
  if (contextWindow <= 0) return;
885
822
  const threshold = Math.floor(contextWindow * this.host.thresholdRatio);
886
- if (this.host.transcript.estimateContextTokens(contextWindow) < threshold) return;
823
+ if (this.host.transcript.estimateContextTokens() < threshold) return;
887
824
  await this.host.runWithCompactingPhase(async () => {
888
- for (let attempt = 0; attempt < 8 && this.host.transcript.estimateContextTokens(contextWindow) >= threshold; attempt += 1) if (!await this.run()) break;
825
+ for (let attempt = 0; attempt < 8 && this.host.transcript.estimateContextTokens() >= threshold; attempt += 1) if (!await this.run()) break;
889
826
  });
890
827
  }
891
828
  /** Runs one compaction pass before a turn when the current model is over threshold. */
@@ -893,7 +830,7 @@ var CompactionController = class {
893
830
  const contextWindow = this.host.model.model.contextWindow;
894
831
  if (contextWindow <= 0) return;
895
832
  const threshold = Math.floor(contextWindow * this.host.thresholdRatio);
896
- if (this.host.transcript.estimateContextTokens(contextWindow) < threshold) return;
833
+ if (this.host.transcript.estimateContextTokens() < threshold) return;
897
834
  await this.host.runWithCompactingPhase(() => this.run());
898
835
  }
899
836
  /** Runs one compaction pass; returns whether it compacted anything. */
@@ -901,16 +838,9 @@ var CompactionController = class {
901
838
  const transcript = this.host.transcript;
902
839
  if (transcript.pendingToolCalls().length > 0) return false;
903
840
  const window = transcript.findCompactionWindow(this.host.keepRecentTokens);
904
- if (window === null) return false;
905
- let minCutPoint = window.startIndex;
906
- while (minCutPoint < window.cutPoint) {
907
- const type = transcript.blocks[minCutPoint]?.type;
908
- if (type !== "compaction_boundary" && type !== "compaction_marker") break;
909
- minCutPoint += 1;
910
- }
911
- if (window.cutPoint <= minCutPoint) return false;
841
+ if (window === null || window.cutPoint <= window.startIndex) return false;
912
842
  let cutPoint = window.cutPoint;
913
- while (cutPoint > minCutPoint) {
843
+ while (cutPoint > window.startIndex) {
914
844
  const compactedBlocks = transcript.blocks.slice(window.startIndex, cutPoint);
915
845
  const compactedTokens = compactedBlocks.reduce((total, block) => total + estimateTranscriptBlockTokens(block), 0);
916
846
  try {
@@ -930,7 +860,7 @@ var CompactionController = class {
930
860
  return false;
931
861
  }
932
862
  async generateSummary(blocks) {
933
- const rendered = renderItemsForSummary(new TranscriptLog(blocks).collectInferenceItems());
863
+ const rendered = renderItemsForSummary(new Transcript(blocks).collectInferenceItems());
934
864
  const policy = this.host.retryPolicy;
935
865
  for (let attempt = 1;; attempt += 1) {
936
866
  const request = buildCompactionSummaryRequest(rendered, {
@@ -949,16 +879,10 @@ var CompactionController = class {
949
879
  if (event.type === "text_delta") summary += event.text;
950
880
  if (event.type === "abort") throw new AbortError();
951
881
  if (event.type === "error") {
952
- const diagnostics = {
953
- source: event.diagnostics?.source ?? "unknown",
954
- ...event.diagnostics,
955
- clientRequestId: request.requestId
956
- };
957
- if (!(summary.length === 0 && attempt < policy.maxAttempts && isRetryableCode(policy, event.code))) throw new ProviderStreamError(event.message, event.code, diagnostics);
882
+ if (!(summary.length === 0 && attempt < policy.maxAttempts && isRetryableCode(policy, event.code))) throw new ProviderStreamError(event.message, event.code);
958
883
  transient = {
959
884
  code: event.code,
960
- retryAfterMs: event.retryAfterMs ?? null,
961
- diagnostics
885
+ retryAfterMs: event.retryAfterMs ?? null
962
886
  };
963
887
  break;
964
888
  }
@@ -969,8 +893,7 @@ var CompactionController = class {
969
893
  type: "retry_scheduled",
970
894
  attempt,
971
895
  delayMs,
972
- code: transient.code,
973
- diagnostics: transient.diagnostics
896
+ code: transient.code
974
897
  });
975
898
  await abortable(delay(delayMs), request.cancel);
976
899
  }
@@ -993,7 +916,6 @@ var ProviderTurnLoop = class {
993
916
  let autoCompactions = 0;
994
917
  while (true) {
995
918
  throwIfAborted(this.host.currentSignal());
996
- await this.host.materializePendingSteers();
997
919
  const steerContinuationBeforeStream = this.host.steerContinuationCount;
998
920
  const shouldAutoRecover = await this.streamProviderOnce();
999
921
  throwIfAborted(this.host.currentSignal());
@@ -1018,9 +940,8 @@ var ProviderTurnLoop = class {
1018
940
  }
1019
941
  /**
1020
942
  * Streams one provider response, silently retrying transient failures
1021
- * (rate_limit/overloaded) with backoff. A retry is taken only when everything
1022
- * the failed attempt put in the transcript can be unwound — the same question
1023
- * `resume` asks, so an automatic recovery and a human-driven one never disagree.
943
+ * (rate_limit/overloaded) with backoff but only while the attempt has
944
+ * produced no transcript content, so a retry can never duplicate output.
1024
945
  * The turn stays in provider_streaming across backoff waits so steers keep
1025
946
  * queueing instead of being rejected.
1026
947
  */
@@ -1035,8 +956,7 @@ var ProviderTurnLoop = class {
1035
956
  type: "retry_scheduled",
1036
957
  attempt: outcome.attempt,
1037
958
  delayMs: outcome.delayMs,
1038
- code: outcome.code,
1039
- diagnostics: outcome.diagnostics
959
+ code: outcome.code
1040
960
  });
1041
961
  await abortable(delay(outcome.delayMs), this.host.currentSignal());
1042
962
  }
@@ -1046,47 +966,26 @@ var ProviderTurnLoop = class {
1046
966
  }
1047
967
  async streamAttempt(attempt, policy) {
1048
968
  const request = this.buildInferenceRequest();
1049
- const attemptStart = this.host.transcript.blocks.length;
1050
969
  const run = this.host.provider.run(request);
1051
970
  let shouldAutoRecover = false;
1052
- let hasPendingThinkingStart = false;
971
+ let produced = false;
1053
972
  this.host.setActiveProviderRun(run);
1054
973
  try {
1055
974
  for await (const event of this.host.streamProvider(request, run)) {
1056
975
  throwIfAborted(request.cancel);
1057
976
  if (event.type === "abort") throw new AbortError();
1058
- if (event.type === "thinking_start") {
1059
- hasPendingThinkingStart = true;
1060
- continue;
1061
- }
1062
977
  if (event.type === "error") {
1063
- const diagnostics = {
1064
- source: event.diagnostics?.source ?? "unknown",
1065
- ...event.diagnostics,
1066
- clientRequestId: request.requestId
1067
- };
1068
- const errorEvent = {
1069
- ...event,
1070
- diagnostics
978
+ if (!produced && attempt < policy.maxAttempts && isRetryableCode(policy, event.code)) return {
979
+ type: "retry",
980
+ attempt,
981
+ delayMs: retryDelayMs(policy, attempt, event.retryAfterMs ?? null),
982
+ code: event.code
1071
983
  };
1072
- if (findResumePoint(this.host.transcript.blocks).cut <= attemptStart && attempt < policy.maxAttempts && isRetryableCode(policy, errorEvent.code)) {
1073
- if (this.host.transcript.truncateFrom(attemptStart)) await this.host.commitTranscript();
1074
- return {
1075
- type: "retry",
1076
- attempt,
1077
- delayMs: retryDelayMs(policy, attempt, errorEvent.retryAfterMs ?? null),
1078
- code: errorEvent.code,
1079
- diagnostics: errorEvent.diagnostics
1080
- };
1081
- }
1082
- await this.applyProviderEvent(errorEvent);
1083
- throw new ProviderStreamError(errorEvent.message, errorEvent.code, errorEvent.diagnostics);
1084
- }
1085
- if (hasPendingThinkingStart) {
1086
- await this.applyProviderEvent({ type: "thinking_start" });
1087
- hasPendingThinkingStart = false;
984
+ await this.applyProviderEvent(event);
985
+ throw new ProviderStreamError(event.message, event.code);
1088
986
  }
1089
987
  await this.applyProviderEvent(event);
988
+ produced = true;
1090
989
  if (event.type === "response" && this.isUsageNearLimit(event.usage)) shouldAutoRecover = true;
1091
990
  }
1092
991
  } finally {
@@ -1124,7 +1023,7 @@ var ProviderTurnLoop = class {
1124
1023
  }
1125
1024
  const input = parseJsonOrString(toolCall.input);
1126
1025
  const result = await this.invokeToolAsResult(tool, toolCall.toolUseId, input);
1127
- this.host.transcript.completeToolCall(toolCall.toolUseId, result.output, result.isError ?? false, result.view ?? null);
1026
+ this.host.transcript.completeToolCall(toolCall.toolUseId, result.output, result.isError ?? false, result.metadata ?? result.continuation ?? null);
1128
1027
  await this.host.runtime.lifecycle?.({
1129
1028
  type: "after_tool_call",
1130
1029
  agentSessionId: this.host.agentSessionId,
@@ -1132,8 +1031,7 @@ var ProviderTurnLoop = class {
1132
1031
  transcript: this.host.transcript,
1133
1032
  toolCallId: toolCall.toolUseId,
1134
1033
  toolName: toolCall.toolName,
1135
- result,
1136
- metadata: this.host.metadata
1034
+ result
1137
1035
  });
1138
1036
  await this.host.commitTranscript();
1139
1037
  if (!options.deferSteerMaterialization) await this.host.materializeSteersArrivedSince(steerContinuationBeforeTool);
@@ -1156,7 +1054,6 @@ var ProviderTurnLoop = class {
1156
1054
  model: this.host.model,
1157
1055
  toolCallId,
1158
1056
  signal,
1159
- metadata: this.host.metadata,
1160
1057
  emitProgress: (progress) => {
1161
1058
  this.host.emit({
1162
1059
  type: "tool_progress",
@@ -1179,10 +1076,7 @@ var ProviderTurnLoop = class {
1179
1076
  text: `Tool failed: ${normalized.message}`
1180
1077
  }],
1181
1078
  isError: true,
1182
- view: {
1183
- kind: "tool_error",
1184
- error: normalized.message
1185
- }
1079
+ metadata: { error: normalized.message }
1186
1080
  };
1187
1081
  }
1188
1082
  }
@@ -1190,8 +1084,7 @@ var ProviderTurnLoop = class {
1190
1084
  return this.host.runtime.tools({
1191
1085
  agentSessionId: this.host.agentSessionId,
1192
1086
  state: this.host.agentState,
1193
- cwd: this.host.cwd,
1194
- metadata: this.host.metadata
1087
+ cwd: this.host.cwd
1195
1088
  });
1196
1089
  }
1197
1090
  buildInferenceRequest() {
@@ -1245,9 +1138,6 @@ var AgentSession = class AgentSession {
1245
1138
  listeners = /* @__PURE__ */ new Set();
1246
1139
  pendingActions = [];
1247
1140
  queued = [];
1248
- get modelSelection() {
1249
- return this.model;
1250
- }
1251
1141
  transcriptLog;
1252
1142
  agentState;
1253
1143
  currentPhase = "idle";
@@ -1257,7 +1147,6 @@ var AgentSession = class AgentSession {
1257
1147
  activeTurnId = null;
1258
1148
  activeTurnPhase = null;
1259
1149
  activeProviderRun = null;
1260
- activeMetadata = null;
1261
1150
  steerQueue = new PendingSteerQueue();
1262
1151
  yields;
1263
1152
  compaction;
@@ -1269,27 +1158,22 @@ var AgentSession = class AgentSession {
1269
1158
  persistTimer = null;
1270
1159
  persistDirty = false;
1271
1160
  /**
1272
- * Restores a session from a checkpoint. Ownership of the checkpoint (including
1161
+ * Restores a session from a snapshot. Ownership of the snapshot (including
1273
1162
  * `state`) transfers to the session: the caller must not mutate it afterwards.
1274
1163
  * Passing state by reference — not a clone — lets the caller share the same
1275
1164
  * object with harness closures (host/commands), keeping one live state.
1276
1165
  */
1277
- static fromCheckpoint(params, options = {}) {
1278
- if (params.checkpoint.harnessName !== params.runtime.harnessName) throw new Error(`AgentSession: checkpoint harness "${params.checkpoint.harnessName}" does not match "${params.runtime.harnessName}"`);
1279
- const checkpoint = params.checkpoint;
1280
- const session = new AgentSession({
1166
+ static fromSnapshot(params, options = {}) {
1167
+ if (params.snapshot.harnessName !== params.runtime.harnessName) throw new Error(`AgentSession: snapshot harness "${params.snapshot.harnessName}" does not match "${params.runtime.harnessName}"`);
1168
+ const snapshot = params.snapshot;
1169
+ return new AgentSession({
1281
1170
  provider: params.provider,
1282
- model: checkpoint.model,
1283
- cwd: checkpoint.cwd,
1171
+ model: snapshot.model,
1172
+ cwd: snapshot.cwd,
1284
1173
  runtime: params.runtime,
1285
- transcript: checkpoint.transcript,
1286
- state: checkpoint.state
1174
+ transcript: snapshot.transcript,
1175
+ state: snapshot.state
1287
1176
  }, options);
1288
- for (const toolCall of session.transcriptLog.pendingToolCalls()) session.transcriptLog.completeToolCall(toolCall.toolUseId, [{
1289
- type: "text",
1290
- text: `Tool call interrupted: ${toolCall.toolName} (the process died before a result was recorded)`
1291
- }], true);
1292
- return session;
1293
1177
  }
1294
1178
  constructor(params, options = {}) {
1295
1179
  this.provider = params.provider;
@@ -1299,8 +1183,8 @@ var AgentSession = class AgentSession {
1299
1183
  this.agentState = params.state === void 0 ? params.runtime.initialState() : params.state;
1300
1184
  this.agentSessionId = options.agentSessionId ?? createId();
1301
1185
  this.idFactory = options.idFactory ?? createId;
1302
- this.yields = new YieldScheduler(this.idFactory, (wakeupId, metadata) => {
1303
- this.deliverYieldWakeup(wakeupId, metadata);
1186
+ this.yields = new YieldScheduler(this.idFactory, (wakeupId) => {
1187
+ this.deliverYieldWakeup(wakeupId);
1304
1188
  });
1305
1189
  this.store = options.store;
1306
1190
  this.persistIntervalMs = options.persistIntervalMs ?? DEFAULT_PERSIST_INTERVAL_MS;
@@ -1311,7 +1195,7 @@ var AgentSession = class AgentSession {
1311
1195
  idFactory: this.idFactory,
1312
1196
  now: options.now
1313
1197
  };
1314
- this.transcriptLog = params.transcript instanceof TranscriptLog ? params.transcript : new TranscriptLog(params.transcript?.blocks ?? [], transcriptOptions);
1198
+ this.transcriptLog = params.transcript instanceof Transcript ? params.transcript : new Transcript(params.transcript?.blocks ?? [], transcriptOptions);
1315
1199
  const self = this;
1316
1200
  const compactionHost = {
1317
1201
  get transcript() {
@@ -1378,9 +1262,6 @@ var AgentSession = class AgentSession {
1378
1262
  get retryPolicy() {
1379
1263
  return self.retryPolicy;
1380
1264
  },
1381
- get metadata() {
1382
- return self.activeMetadata;
1383
- },
1384
1265
  currentSignal: () => self.currentSignal(),
1385
1266
  currentTurnId: () => self.currentTurnId(),
1386
1267
  nextRequestId: () => self.idFactory(),
@@ -1398,8 +1279,7 @@ var AgentSession = class AgentSession {
1398
1279
  runWithCompactingPhase: (fn) => self.runWithCompactingPhase(fn),
1399
1280
  commitTranscript: () => self.commitTranscript(),
1400
1281
  emit: (event) => self.emit(event),
1401
- materializeSteersArrivedSince: (count) => self.materializePendingSteersArrivedSince(count),
1402
- materializePendingSteers: () => self.materializePendingSteersForCurrentTurn()
1282
+ materializeSteersArrivedSince: (count) => self.materializePendingSteersArrivedSince(count)
1403
1283
  };
1404
1284
  this.turnLoop = new ProviderTurnLoop(turnLoopHost);
1405
1285
  }
@@ -1409,46 +1289,27 @@ var AgentSession = class AgentSession {
1409
1289
  type: "send",
1410
1290
  id,
1411
1291
  content,
1412
- metadata: cloneMetadata(options.metadata),
1413
1292
  resolve: noop,
1414
1293
  reject: noop
1415
1294
  });
1416
1295
  }
1417
- /**
1418
- * Discards the whole latest turn and runs it again from the user's input.
1419
- *
1420
- * This is "regenerate": it is only meaningful when the caller wants a different
1421
- * answer to the same question and knows the turn's effects can be repeated. It is
1422
- * NOT the way to recover a failed turn — use `resume`, which unwinds only as far
1423
- * as is safe.
1424
- */
1425
- retry(options = {}) {
1296
+ retry() {
1426
1297
  return this.enqueue({
1427
1298
  type: "retry",
1428
- metadata: cloneMetadata(options.metadata),
1429
1299
  resolve: noop,
1430
1300
  reject: noop
1431
1301
  });
1432
1302
  }
1433
- /**
1434
- * Finishes a turn that did not finish, after an abort or a terminal provider
1435
- * error. Unwinds to the turn's resume point — dropping the failed attempt's
1436
- * leftovers, keeping everything that already left the process — and re-infers
1437
- * from there. Callers do not choose the granularity and do not need to know how
1438
- * the turn died.
1439
- */
1440
- resume(options = {}) {
1303
+ resume() {
1441
1304
  return this.enqueue({
1442
1305
  type: "resume",
1443
- metadata: cloneMetadata(options.metadata),
1444
1306
  resolve: noop,
1445
1307
  reject: noop
1446
1308
  });
1447
1309
  }
1448
- compact(options = {}) {
1310
+ compact() {
1449
1311
  return this.enqueue({
1450
1312
  type: "compact",
1451
- metadata: cloneMetadata(options.metadata),
1452
1313
  resolve: noop,
1453
1314
  reject: noop
1454
1315
  });
@@ -1551,8 +1412,8 @@ var AgentSession = class AgentSession {
1551
1412
  canAbortAgain: false
1552
1413
  };
1553
1414
  }
1554
- scheduleYieldWakeup(durationMs, metadata = this.activeMetadata) {
1555
- const wakeupId = this.yields.schedule(durationMs, metadata);
1415
+ scheduleYieldWakeup(durationMs) {
1416
+ const wakeupId = this.yields.schedule(durationMs);
1556
1417
  return {
1557
1418
  output: [{
1558
1419
  type: "text",
@@ -1562,7 +1423,7 @@ var AgentSession = class AgentSession {
1562
1423
  `durationMs: ${durationMs}`
1563
1424
  ].join("\n")
1564
1425
  }],
1565
- view: {
1426
+ metadata: {
1566
1427
  kind: "yield_wakeup",
1567
1428
  wakeupId,
1568
1429
  durationMs
@@ -1711,13 +1572,13 @@ var AgentSession = class AgentSession {
1711
1572
  action.resolve();
1712
1573
  }
1713
1574
  }
1714
- async deliverYieldWakeup(wakeupId, metadata) {
1575
+ async deliverYieldWakeup(wakeupId) {
1715
1576
  if (!this.yields.take(wakeupId)) return;
1716
1577
  const content = [{
1717
1578
  type: "text",
1718
1579
  text: "Scheduled yield wakeup fired. Continue the previous work and inspect any running command with shell_status when needed."
1719
1580
  }];
1720
- if (metadata === this.activeMetadata && this.canAcceptInternalSteer()) try {
1581
+ if (this.canAcceptInternalSteer()) try {
1721
1582
  await this.steerInternal(content, wakeupId, true);
1722
1583
  return;
1723
1584
  } catch (error) {
@@ -1726,7 +1587,7 @@ var AgentSession = class AgentSession {
1726
1587
  error: asError(error)
1727
1588
  });
1728
1589
  }
1729
- this.enqueueHiddenSend(content, metadata);
1590
+ this.enqueueHiddenSend(content);
1730
1591
  }
1731
1592
  canAcceptInternalSteer() {
1732
1593
  try {
@@ -1758,12 +1619,11 @@ var AgentSession = class AgentSession {
1758
1619
  hidden
1759
1620
  });
1760
1621
  }
1761
- enqueueHiddenSend(content, metadata) {
1622
+ enqueueHiddenSend(content) {
1762
1623
  this.enqueue({
1763
1624
  type: "send",
1764
1625
  id: this.idFactory(),
1765
1626
  content,
1766
- metadata,
1767
1627
  hidden: true,
1768
1628
  resolve: noop,
1769
1629
  reject: noop
@@ -1772,13 +1632,13 @@ var AgentSession = class AgentSession {
1772
1632
  steerDelivery() {
1773
1633
  if (!this.activeTurnId || !this.currentAbortController || !this.activeTurnPhase) throw new Error("AgentSession: no active turn to steer");
1774
1634
  if (this.currentAbortController.signal.aborted) throw new Error("AgentSession: active turn is aborted");
1775
- if (this.activeTurnPhase === "finalizing") throw new Error("AgentSession: active turn cannot accept steering while finalizing");
1635
+ if (this.activeTurnPhase === "compacting" || this.activeTurnPhase === "finalizing") throw new Error(`AgentSession: active turn cannot accept steering while ${this.activeTurnPhase}`);
1776
1636
  const run = this.activeProviderRun;
1777
1637
  if (run?.steer) return {
1778
1638
  type: "provider",
1779
1639
  run
1780
1640
  };
1781
- if (this.activeTurnPhase === "tool_executing" || this.activeTurnPhase === "provider_streaming" || this.activeTurnPhase === "compacting") return { type: "next_provider_continuation" };
1641
+ if (this.activeTurnPhase === "tool_executing" || this.activeTurnPhase === "provider_streaming") return { type: "next_provider_continuation" };
1782
1642
  throw new Error("AgentSession: active turn cannot accept steering now");
1783
1643
  }
1784
1644
  enqueue(action) {
@@ -1811,7 +1671,6 @@ var AgentSession = class AgentSession {
1811
1671
  if (action.type === "send") this.removeQueuedMessage(action.id);
1812
1672
  this.currentAbortController = new AbortController();
1813
1673
  this.activeTurnId = action.type === "send" ? action.id : this.idFactory();
1814
- this.activeMetadata = action.metadata;
1815
1674
  this.abortRecorded = false;
1816
1675
  try {
1817
1676
  await this.executeAction(action);
@@ -1853,7 +1712,6 @@ var AgentSession = class AgentSession {
1853
1712
  this.discardPendingSteersForCurrentTurn();
1854
1713
  this.activeTurnPhase = "finalizing";
1855
1714
  this.activeProviderRun = null;
1856
- this.activeMetadata = null;
1857
1715
  this.currentAbortController = null;
1858
1716
  this.activeTurnId = null;
1859
1717
  this.activeTurnPhase = null;
@@ -1887,7 +1745,6 @@ var AgentSession = class AgentSession {
1887
1745
  this.setPhase("compacting");
1888
1746
  this.activeTurnPhase = "compacting";
1889
1747
  await this.compaction.run();
1890
- await this.materializePendingSteersForCurrentTurn();
1891
1748
  return;
1892
1749
  }
1893
1750
  }
@@ -1928,8 +1785,7 @@ var AgentSession = class AgentSession {
1928
1785
  agentSessionId: this.agentSessionId,
1929
1786
  state: this.agentState,
1930
1787
  transcript: this.transcriptLog,
1931
- content,
1932
- metadata: this.activeMetadata
1788
+ content
1933
1789
  });
1934
1790
  const resolvedContent = await this.resolveReferences(content);
1935
1791
  await this.applyPendingModelSwitch();
@@ -1948,8 +1804,7 @@ var AgentSession = class AgentSession {
1948
1804
  agentSessionId: this.agentSessionId,
1949
1805
  cwd: this.cwd,
1950
1806
  transcript: this.transcriptLog,
1951
- signal,
1952
- metadata: this.activeMetadata
1807
+ signal
1953
1808
  }, content)), signal);
1954
1809
  }
1955
1810
  async executeRetry() {
@@ -1961,30 +1816,16 @@ var AgentSession = class AgentSession {
1961
1816
  agentSessionId: this.agentSessionId,
1962
1817
  state: this.agentState,
1963
1818
  transcript: this.transcriptLog,
1964
- reason: "retry",
1965
- metadata: this.activeMetadata
1819
+ reason: "retry"
1966
1820
  });
1967
1821
  await this.commitTranscript();
1968
1822
  await this.applyPendingModelSwitch();
1969
1823
  await this.compaction.preflight();
1970
1824
  await this.turnLoop.run();
1971
1825
  }
1972
- /**
1973
- * Finishes an unfinished turn. Unwinds to its resume point — dropping the failed
1974
- * attempt's leftovers, keeping everything that already left the process — and
1975
- * re-infers from there. When the whole turn turns out to be discardable this is
1976
- * a plain rerun, which spares the model a continuation boundary attached to a
1977
- * stub of its own aborted output.
1978
- */
1979
1826
  async executeResume() {
1980
- const { cut, isFullRerun } = findResumePoint(this.transcriptLog.blocks);
1981
- if (isFullRerun) {
1982
- await this.executeRetry();
1983
- return;
1984
- }
1985
- this.transcriptLog.truncateFrom(cut);
1986
- this.transcriptLog.markLatestAbortResumed();
1987
1827
  await this.applyPendingModelSwitch();
1828
+ this.transcriptLog.markLatestAbortResumed();
1988
1829
  this.transcriptLog.pushResumeTurn(this.currentTurnId(), this.model);
1989
1830
  await this.commitTranscript();
1990
1831
  await this.compaction.preflight();
@@ -2011,8 +1852,7 @@ var AgentSession = class AgentSession {
2011
1852
  agentSessionId: this.agentSessionId,
2012
1853
  state: this.agentState,
2013
1854
  cwd: this.cwd,
2014
- transcript: this.transcriptLog,
2015
- metadata: this.activeMetadata
1855
+ transcript: this.transcriptLog
2016
1856
  };
2017
1857
  }
2018
1858
  currentSignal() {
@@ -2102,7 +1942,7 @@ var AgentSession = class AgentSession {
2102
1942
  /**
2103
1943
  * Publishes transcript changes: drains the mutation journal into a patch
2104
1944
  * event (O(changed content), not O(transcript)) and schedules a throttled
2105
- * checkpoint write. Boundaries (action end, abort, dispose) flush the write.
1945
+ * snapshot write. Boundaries (action end, abort, dispose) flush the write.
2106
1946
  */
2107
1947
  async commitTranscript() {
2108
1948
  const drained = this.transcriptLog.takePatches();
@@ -2119,7 +1959,7 @@ var AgentSession = class AgentSession {
2119
1959
  if (this.persistTimer) return;
2120
1960
  this.persistTimer = setTimeout(() => {
2121
1961
  this.persistTimer = null;
2122
- this.persistCheckpoint().catch((error) => {
1962
+ this.persistSnapshot().catch((error) => {
2123
1963
  this.emit({
2124
1964
  type: "error",
2125
1965
  error: asError(error)
@@ -2127,11 +1967,11 @@ var AgentSession = class AgentSession {
2127
1967
  });
2128
1968
  }, this.persistIntervalMs);
2129
1969
  }
2130
- async persistCheckpoint() {
1970
+ async persistSnapshot() {
2131
1971
  if (!this.store || !this.persistDirty) return;
2132
1972
  this.persistDirty = false;
2133
- await this.store.saveCheckpoint({
2134
- transcript: this.transcriptLog.toJSON(),
1973
+ await this.store.saveSnapshot({
1974
+ transcript: this.transcriptLog.snapshot(),
2135
1975
  state: structuredClone(this.agentState),
2136
1976
  phase: this.currentPhase,
2137
1977
  queue: structuredClone(this.queuedMessages()),
@@ -2145,7 +1985,7 @@ var AgentSession = class AgentSession {
2145
1985
  clearTimeout(this.persistTimer);
2146
1986
  this.persistTimer = null;
2147
1987
  }
2148
- await this.persistCheckpoint();
1988
+ await this.persistSnapshot();
2149
1989
  }
2150
1990
  emit(event) {
2151
1991
  for (const listener of this.listeners) listener(event);
@@ -2159,9 +1999,6 @@ var AgentSession = class AgentSession {
2159
1999
  function textContentSummary(content) {
2160
2000
  return truncate(content.map((block) => block.type === "text" ? block.text : `[${block.type}]`).join("\n").trim(), 120, "...");
2161
2001
  }
2162
- function cloneMetadata(metadata) {
2163
- return metadata === void 0 ? null : structuredClone(metadata);
2164
- }
2165
2002
  async function readProviderIterator(iterator, signal) {
2166
2003
  try {
2167
2004
  return await abortable(iterator.next(), signal);
@@ -2237,6 +2074,7 @@ const APPROX_CHARS_PER_TOKEN = 4;
2237
2074
  const TOOL_DESCRIPTION_FIELD = "Concise title for the concrete user-visible state or result to make visible or confirm. Do not describe waiting, pausing, tool mechanics, generic actions, object labels, steps, tool names, ids, internals, or reasons.";
2238
2075
  const execRepeatStates = /* @__PURE__ */ new WeakMap();
2239
2076
  function createStandardAgentTools(options) {
2077
+ const { environment } = options;
2240
2078
  return [
2241
2079
  {
2242
2080
  name: "shell_exec",
@@ -2261,7 +2099,6 @@ function createStandardAgentTools(options) {
2261
2099
  },
2262
2100
  invoke: async (ctx, input) => {
2263
2101
  const parsed = parseShellExecInput(input);
2264
- const environment = await resolveEnvironment(options.environment, ctx, { shellId: parsed.shellId });
2265
2102
  const repeatGuard = repeatedShellExecResult(environment, ctx.agentSessionId, parsed.script);
2266
2103
  if (repeatGuard) return repeatGuard;
2267
2104
  const result = await environment.exec({
@@ -2289,9 +2126,7 @@ function createStandardAgentTools(options) {
2289
2126
  }
2290
2127
  },
2291
2128
  invoke: async (ctx, input) => {
2292
- const parsed = parseShellStatusInput(input);
2293
- const environment = await resolveEnvironment(options.environment, ctx, { commandId: parsed.commandId });
2294
- const result = await environment.status(parsed);
2129
+ const result = await environment.status(parseShellStatusInput(input));
2295
2130
  ctx.emitProgress(result);
2296
2131
  return finishShellToolResult(environment, result, ctx);
2297
2132
  }
@@ -2313,10 +2148,8 @@ function createStandardAgentTools(options) {
2313
2148
  }
2314
2149
  },
2315
2150
  invoke: async (ctx, input) => {
2316
- const parsed = parseShellWriteInput(input);
2317
- const environment = await resolveEnvironment(options.environment, ctx, { commandId: parsed.commandId });
2318
2151
  const result = await environment.write({
2319
- ...parsed,
2152
+ ...parseShellWriteInput(input),
2320
2153
  signal: ctx.signal
2321
2154
  });
2322
2155
  ctx.emitProgress(result);
@@ -2339,9 +2172,7 @@ function createStandardAgentTools(options) {
2339
2172
  }
2340
2173
  },
2341
2174
  invoke: async (ctx, input) => {
2342
- const parsed = parseShellAbortInput(input);
2343
- const environment = await resolveEnvironment(options.environment, ctx, { commandId: parsed.commandId });
2344
- const result = await environment.abort(parsed);
2175
+ const result = await environment.abort(parseShellAbortInput(input));
2345
2176
  ctx.emitProgress(result);
2346
2177
  return {
2347
2178
  ...await finishShellToolResult(environment, result, ctx),
@@ -2372,136 +2203,31 @@ function createStandardAgentTools(options) {
2372
2203
  }
2373
2204
  ];
2374
2205
  }
2375
- function resolveEnvironment(source, ctx, handle) {
2376
- return typeof source === "function" ? source(ctx, handle) : source;
2377
- }
2378
- /**
2379
- * How many bytes of each modality a tool may hand to the model.
2380
- *
2381
- * One number cannot serve both, because bytes buy wildly different amounts of
2382
- * context per modality — measured against a frontier model, a KiB of video
2383
- * costs ~2 tokens where a KiB of image costs ~50. A cap generous enough to show
2384
- * a five-minute clip would let a single still eat a six-figure token budget.
2385
- *
2386
- * image (4 MiB): well past any sane still — a 4000x3000 PNG lands under it — so
2387
- * crossing this line is a mistake, not a use case.
2388
- * video (16 MiB): roughly ten minutes at a viewing-grade encoding, and
2389
- * deliberately under the ~20 MB inline-payload ceiling the major APIs enforce.
2390
- * A larger cap buys no reach, only a rejection further downstream where the
2391
- * reason is harder to read.
2392
- *
2393
- * Bytes are a proxy, not a budget: for video they track cost reasonably at a
2394
- * fixed encoding, but an image's real driver is its pixel dimensions.
2395
- */
2396
- const DEFAULT_MAX_MEDIA_BYTES = {
2397
- image: 4 * 1024 * 1024,
2398
- video: 16 * 1024 * 1024
2399
- };
2400
2206
  function shellPreviewBudgetTokens(contextWindow) {
2401
2207
  return contextWindow >= LARGE_CONTEXT_THRESHOLD_TOKENS ? LARGE_CONTEXT_PREVIEW_TOKENS : SMALL_CONTEXT_PREVIEW_TOKENS;
2402
2208
  }
2403
- function toShellToolResult(result, options = {}) {
2209
+ function toShellToolResult(result, toolCallId = "", options = {}) {
2404
2210
  const output = [{
2405
2211
  type: "text",
2406
2212
  text: formatShellToolResult(result, options)
2407
2213
  }];
2408
- if (result.status === "exited" && result.binaryStdout) {
2409
- const verdict = binaryStreamVerdict(result.binaryStdout, result.commandId, options.model, {
2410
- ...DEFAULT_MAX_MEDIA_BYTES,
2411
- ...options.maxMediaBytes
2412
- });
2413
- if (verdict.block) output.push(verdict.block);
2414
- if (verdict.note) output.push({
2415
- type: "text",
2416
- text: verdict.note
2417
- });
2418
- }
2214
+ if (result.status === "exited" && result.assets) for (const asset of result.assets) output.push({
2215
+ type: "image",
2216
+ source: {
2217
+ mediaType: asset.mediaType,
2218
+ data: asset.data
2219
+ }
2220
+ });
2419
2221
  return {
2420
2222
  output,
2421
2223
  isError: false,
2422
- view: shellToolView(result)
2423
- };
2424
- }
2425
- /** Character budget for a shell view's render window (tail-biased). */
2426
- const SHELL_VIEW_MAX_CHARS = 32768;
2427
- function shellToolView(result) {
2428
- const window = tailChunkWindow(result.output.chunks, SHELL_VIEW_MAX_CHARS);
2429
- const view = {
2430
- kind: "shell",
2431
- status: result.status,
2432
- shellId: result.shellId,
2433
- commandId: result.commandId,
2434
- runningMs: result.runningMs,
2435
- idleMs: result.idleMs,
2436
- chunks: window.chunks,
2437
- viewTruncated: window.truncated || result.output.truncated
2438
- };
2439
- if (result.status === "exited") {
2440
- view.exitCode = result.exitCode;
2441
- if (result.audit.length > 0) view.audit = result.audit;
2442
- if (result.commandMetadata && result.commandMetadata.length > 0) view.commandMeta = result.commandMetadata;
2443
- }
2444
- return view;
2445
- }
2446
- function tailChunkWindow(chunks, maxChars) {
2447
- const kept = [];
2448
- let total = 0;
2449
- for (let i = chunks.length - 1; i >= 0; i -= 1) {
2450
- const chunk = chunks[i];
2451
- if (chunk.text.length === 0) continue;
2452
- const remaining = maxChars - total;
2453
- if (remaining <= 0) return {
2454
- chunks: kept,
2455
- truncated: true
2456
- };
2457
- if (chunk.text.length <= remaining) {
2458
- kept.unshift({
2459
- stream: chunk.stream,
2460
- text: chunk.text
2461
- });
2462
- total += chunk.text.length;
2463
- } else {
2464
- kept.unshift({
2465
- stream: chunk.stream,
2466
- text: chunk.text.slice(chunk.text.length - remaining)
2467
- });
2468
- return {
2469
- chunks: kept,
2470
- truncated: true
2471
- };
2472
- }
2473
- }
2474
- return {
2475
- chunks: kept,
2476
- truncated: false
2477
- };
2478
- }
2479
- /**
2480
- * Boundary decision for a binary final stream: attach as native media when the
2481
- * magic matches the closed model-media set, the model accepts the type, and
2482
- * the stream was not truncated; otherwise explain why nothing was attached.
2483
- */
2484
- function binaryStreamVerdict(binary, commandId, model, maxMediaBytes) {
2485
- const media = sniffModelMediaType(binary.data);
2486
- const binPath = `/@/commands/${commandId}/stdout.bin`;
2487
- if (binary.truncated) return { note: `Binary stdout (${binary.totalBytes} bytes${media ? `, ${media.mediaType}` : ""}) exceeded the shell's ${binary.limitBytes}-byte binary limit (maxBinaryBytes) and was not attached; the raw bytes remain readable at ${binPath}. Produce a smaller version and re-run.` };
2488
- if (!media) return { note: `Binary stdout does not match any model-viewable media type; the raw bytes remain readable at ${binPath}.` };
2489
- if (!model || !modelAcceptsMediaType(model, media.mediaType)) return { note: `Binary stdout is ${media.mediaType}, which this model does not accept natively; the raw bytes remain readable at ${binPath}.` };
2490
- const cap = maxMediaBytes[media.kind];
2491
- if (binary.totalBytes > cap) return { note: `Binary stdout is ${media.mediaType} (${binary.totalBytes} bytes), over the ${cap}-byte ${media.kind} cap; it was not attached and the raw bytes remain readable at ${binPath}. Produce a smaller version — for video, fewer frames or a lower resolution — and re-run.` };
2492
- const source = {
2493
- mediaType: media.mediaType,
2494
- data: bytesToBase64(binary.data)
2495
- };
2496
- return {
2497
- block: media.kind === "video" ? {
2498
- type: "video",
2499
- source
2500
- } : {
2501
- type: "image",
2502
- source
2503
- },
2504
- note: `Attached stdout as ${media.mediaType} (${binary.totalBytes} bytes).`
2224
+ metadata: result,
2225
+ continuation: result.status === "running" ? {
2226
+ toolCallId,
2227
+ shellId: result.shellId,
2228
+ commandId: result.commandId,
2229
+ status: "running"
2230
+ } : void 0
2505
2231
  };
2506
2232
  }
2507
2233
  function parseShellExecInput(input) {
@@ -2563,8 +2289,8 @@ function repeatedShellExecResult(environment, agentSessionId, script) {
2563
2289
  ].join("\n")
2564
2290
  }],
2565
2291
  isError: true,
2566
- view: {
2567
- kind: "repeated_shell_exec",
2292
+ metadata: {
2293
+ kind: "repeated_identical_shell_exec",
2568
2294
  script,
2569
2295
  count
2570
2296
  }
@@ -2622,11 +2348,10 @@ function boundedPreview(text, budgetTokens) {
2622
2348
  async function finishShellToolResult(environment, result, ctx) {
2623
2349
  const previewBudgetTokens = shellPreviewBudgetTokens(ctx.model.model.contextWindow);
2624
2350
  const exposeCommandHandle = shellCommandHandleRequired(result, previewBudgetTokens);
2625
- const toolResult = toShellToolResult(result, {
2351
+ const toolResult = toShellToolResult(result, ctx.toolCallId, {
2626
2352
  includePreview: true,
2627
2353
  previewBudgetTokens,
2628
- exposeCommandHandle,
2629
- model: ctx.model.model
2354
+ exposeCommandHandle
2630
2355
  });
2631
2356
  if (!exposeCommandHandle) await environment.releaseCommand(result.commandId);
2632
2357
  return toolResult;
@@ -2639,20 +2364,12 @@ function shellCommandHandleRequired(result, budgetTokens) {
2639
2364
  }
2640
2365
  //#endregion
2641
2366
  //#region src/server.ts
2642
- var RunCommandLineShellNotFoundError = class extends Error {
2643
- shellId;
2644
- constructor(shellId) {
2645
- super(`runCommandLine: shell "${shellId}" is not open in this process`);
2646
- this.shellId = shellId;
2647
- this.name = "RunCommandLineShellNotFoundError";
2648
- }
2649
- };
2650
- var RunCommandLineCommandNotRegisteredError = class extends Error {
2651
- commandName;
2652
- constructor(commandName) {
2653
- super(`runCommandLine: command "${commandName}" is not registered for this session`);
2654
- this.commandName = commandName;
2655
- this.name = "RunCommandLineCommandNotRegisteredError";
2367
+ var RunCommandLineSessionNotFoundError = class extends Error {
2368
+ agentSessionId;
2369
+ constructor(agentSessionId) {
2370
+ super(`runCommandLine: no session "${agentSessionId}" is open in this process`);
2371
+ this.agentSessionId = agentSessionId;
2372
+ this.name = "RunCommandLineSessionNotFoundError";
2656
2373
  }
2657
2374
  };
2658
2375
  var RunCommandLineTimeoutError = class extends Error {
@@ -2672,7 +2389,7 @@ var AgentServer = class {
2672
2389
  providers;
2673
2390
  shellOptions;
2674
2391
  sessionOptions;
2675
- prepareShell;
2392
+ prepareSessionShell;
2676
2393
  bindings = /* @__PURE__ */ new Set();
2677
2394
  sessionOwnership = new SessionOwnershipRegistry();
2678
2395
  constructor(options) {
@@ -2680,7 +2397,7 @@ var AgentServer = class {
2680
2397
  this.providers = createProviderMap(options.providers);
2681
2398
  this.shellOptions = options.shell ?? {};
2682
2399
  this.sessionOptions = options.session ?? {};
2683
- this.prepareShell = options.prepareShell ?? null;
2400
+ this.prepareSessionShell = options.prepareSessionShell ?? null;
2684
2401
  }
2685
2402
  client() {
2686
2403
  const transports = createInProcessTransportPair();
@@ -2694,7 +2411,7 @@ var AgentServer = class {
2694
2411
  providers: this.providers,
2695
2412
  shell: this.shellOptions,
2696
2413
  session: this.sessionOptions,
2697
- prepareShell: this.prepareShell,
2414
+ prepareSessionShell: this.prepareSessionShell,
2698
2415
  sessions: this.sessionOwnership
2699
2416
  });
2700
2417
  this.bindings.add(binding);
@@ -2710,18 +2427,17 @@ var AgentServer = class {
2710
2427
  * Transport-agnostic: callers (e.g. LocalHost command bridge) supply their
2711
2428
  * own IPC; AgentServer only knows how to exec against the open session shell.
2712
2429
  */
2713
- async runCommandLine(shellId, name, args, opts) {
2714
- const owners = [...this.bindings].filter((binding) => binding.hasShell(shellId));
2715
- if (owners.length === 0) throw new RunCommandLineShellNotFoundError(shellId);
2716
- if (owners.length > 1) throw new Error(`runCommandLine: shell id "${shellId}" is not unique`);
2717
- return owners[0].runCommandLine(shellId, name, args, opts);
2430
+ async runCommandLine(agentSessionId, name, args, opts) {
2431
+ const binding = this.sessionOwnership.get(agentSessionId);
2432
+ if (!binding) throw new RunCommandLineSessionNotFoundError(agentSessionId);
2433
+ return binding.runCommandLine(name, args, opts);
2718
2434
  }
2719
2435
  };
2720
2436
  /**
2721
2437
  * Tracks which transport binding currently owns each client-provided session
2722
2438
  * id. Opening a session id that is already owned takes it over: the previous
2723
- * binding's session is closed (flushing its checkpoint) before the new open
2724
- * proceeds, so two connections never write the same checkpoint key concurrently.
2439
+ * binding's session is closed (flushing its snapshot) before the new open
2440
+ * proceeds, so two connections never write the same snapshot key concurrently.
2725
2441
  */
2726
2442
  var SessionOwnershipRegistry = class {
2727
2443
  holders = /* @__PURE__ */ new Map();
@@ -2743,17 +2459,14 @@ var AgentTransportBindingImpl = class {
2743
2459
  providers;
2744
2460
  shellOptions;
2745
2461
  sessionOptions;
2746
- prepareShell;
2462
+ prepareSessionShell;
2747
2463
  sessions;
2748
2464
  session = null;
2749
2465
  currentAgent = null;
2750
- environmentsByHost = /* @__PURE__ */ new Map();
2751
- pendingEnvironmentsByHost = /* @__PURE__ */ new Map();
2752
- currentCommandRegistry = null;
2466
+ currentEnvironment = null;
2753
2467
  currentCwd = null;
2754
2468
  currentProviderId = null;
2755
2469
  currentSessionId = null;
2756
- currentCommandNames = /* @__PURE__ */ new Set();
2757
2470
  unsubscribeSession = null;
2758
2471
  unsubscribeTransport = null;
2759
2472
  closed = false;
@@ -2763,7 +2476,7 @@ var AgentTransportBindingImpl = class {
2763
2476
  this.providers = options.providers;
2764
2477
  this.shellOptions = options.shell ?? {};
2765
2478
  this.sessionOptions = options.session ?? {};
2766
- this.prepareShell = options.prepareShell;
2479
+ this.prepareSessionShell = options.prepareSessionShell;
2767
2480
  this.sessions = options.sessions;
2768
2481
  this.unsubscribeTransport = this.transport.onFrame((frame) => {
2769
2482
  this.handleFrame(frame);
@@ -2800,10 +2513,7 @@ var AgentTransportBindingImpl = class {
2800
2513
  case "send": {
2801
2514
  const session = this.sessionFor("send");
2802
2515
  if (!session) return;
2803
- this.observeSessionAction(session.send(frame.content, {
2804
- id: frame.messageId,
2805
- metadata: frame.metadata
2806
- }));
2516
+ this.observeSessionAction(session.send(frame.content, { id: frame.messageId }));
2807
2517
  return;
2808
2518
  }
2809
2519
  case "dequeue_message": {
@@ -2896,19 +2606,19 @@ var AgentTransportBindingImpl = class {
2896
2606
  case "retry": {
2897
2607
  const session = this.sessionFor("retry");
2898
2608
  if (!session || this.rejectIfBusy(session, "retry")) return;
2899
- this.observeSessionAction(session.retry({ metadata: frame.metadata }));
2609
+ this.observeSessionAction(session.retry());
2900
2610
  return;
2901
2611
  }
2902
2612
  case "resume": {
2903
2613
  const session = this.sessionFor("resume");
2904
2614
  if (!session || this.rejectIfBusy(session, "resume")) return;
2905
- this.observeSessionAction(session.resume({ metadata: frame.metadata }));
2615
+ this.observeSessionAction(session.resume());
2906
2616
  return;
2907
2617
  }
2908
2618
  case "compact": {
2909
2619
  const session = this.sessionFor("compact");
2910
2620
  if (!session || this.rejectIfBusy(session, "compact")) return;
2911
- this.observeSessionAction(session.compact({ metadata: frame.metadata }));
2621
+ this.observeSessionAction(session.compact());
2912
2622
  return;
2913
2623
  }
2914
2624
  case "abort": {
@@ -2930,7 +2640,7 @@ var AgentTransportBindingImpl = class {
2930
2640
  case "sync_transcript": {
2931
2641
  const session = this.sessionFor("sync_transcript");
2932
2642
  if (!session) return;
2933
- this.sendTranscriptReset(session);
2643
+ this.sendTranscriptSnapshot(session);
2934
2644
  return;
2935
2645
  }
2936
2646
  case "close":
@@ -2957,30 +2667,41 @@ var AgentTransportBindingImpl = class {
2957
2667
  await this.sessions.claim(agentSessionId, this);
2958
2668
  this.currentSessionId = agentSessionId;
2959
2669
  const initialState = agent.initialState();
2960
- const store = new HostAgentSessionStore((await agent.host({
2670
+ const provisionalHost = agent.host({
2961
2671
  state: initialState,
2962
2672
  cwd: frame.cwd
2963
- })).store, agentSessionId);
2964
- const checkpoint = await store.loadCheckpoint();
2965
- const restoring = checkpoint !== null && checkpoint.harnessName === agent.name;
2966
- const state = restoring ? structuredClone(checkpoint.state) : initialState;
2673
+ });
2674
+ const store = new HostAgentSessionStore(provisionalHost.store, agentSessionId);
2675
+ const snapshot = await store.loadSnapshot();
2676
+ const restoring = snapshot !== null && snapshot.harnessName === agent.name;
2677
+ const state = restoring ? structuredClone(snapshot.state) : initialState;
2967
2678
  const harnessContext = {
2968
2679
  state,
2969
2680
  cwd: frame.cwd
2970
2681
  };
2682
+ const host = restoring ? agent.host(harnessContext) : provisionalHost;
2971
2683
  const commands = agent.commands?.(harnessContext) ?? [];
2972
2684
  const commandRegistry = new CommandRegistry();
2973
2685
  for (const command of commands) commandRegistry.register(command);
2974
- const commandNames = commandRegistry.list().map((command) => command.name);
2686
+ const environment = new BashEnvironment({
2687
+ ...this.prepareSessionShell ? await this.prepareSessionShell({
2688
+ agentSessionId,
2689
+ host,
2690
+ commandNames: commandRegistry.list().map((command) => command.name),
2691
+ shell: this.shellOptions
2692
+ }) : this.shellOptions,
2693
+ host,
2694
+ commands: commandRegistry
2695
+ });
2975
2696
  let sessionRef = null;
2976
2697
  const tools = createStandardAgentTools({
2977
- environment: (ctx, handle) => this.resolveEnvironment(ctx, handle),
2978
- scheduleYield: (ctx, durationMs) => {
2698
+ environment,
2699
+ scheduleYield: (_ctx, durationMs) => {
2979
2700
  if (!sessionRef) throw new Error("AgentServer: session is not ready for yield scheduling");
2980
- return sessionRef.scheduleYieldWakeup(durationMs, ctx.metadata);
2701
+ return sessionRef.scheduleYieldWakeup(durationMs);
2981
2702
  }
2982
2703
  });
2983
- const commandsPrompt = commandRegistry.renderHelp();
2704
+ const commandsPrompt = commandRegistry.renderPrompt();
2984
2705
  const runtime = {
2985
2706
  harnessName: agent.name,
2986
2707
  initialState: () => agent.initialState(),
@@ -2993,11 +2714,11 @@ var AgentTransportBindingImpl = class {
2993
2714
  lifecycle: (event) => agent.lifecycle?.(event),
2994
2715
  tools: () => tools
2995
2716
  };
2996
- const session = restoring ? AgentSession.fromCheckpoint({
2717
+ const session = restoring ? AgentSession.fromSnapshot({
2997
2718
  provider,
2998
2719
  runtime,
2999
- checkpoint: {
3000
- ...checkpoint,
2720
+ snapshot: {
2721
+ ...snapshot,
3001
2722
  state
3002
2723
  }
3003
2724
  }, {
@@ -3018,14 +2739,13 @@ var AgentTransportBindingImpl = class {
3018
2739
  sessionRef = session;
3019
2740
  this.session = session;
3020
2741
  this.currentAgent = agent;
3021
- this.currentCommandRegistry = commandRegistry;
2742
+ this.currentEnvironment = environment;
3022
2743
  this.currentCwd = frame.cwd;
3023
2744
  this.currentProviderId = frame.provider.providerId;
3024
- this.currentCommandNames = new Set(commandNames);
3025
2745
  if (restoring) session.updateModel(null, frame.provider.model);
3026
2746
  this.unsubscribeSession = this.session.subscribe((event) => this.handleSessionEvent(event));
3027
2747
  this.send({ type: "opened" });
3028
- this.sendTranscriptReset(session);
2748
+ this.sendTranscriptSnapshot(session);
3029
2749
  this.send({
3030
2750
  type: "phase",
3031
2751
  phase: this.session.phase()
@@ -3035,10 +2755,10 @@ var AgentTransportBindingImpl = class {
3035
2755
  queue: this.session.queuedMessages()
3036
2756
  });
3037
2757
  }
3038
- sendTranscriptReset(session) {
2758
+ sendTranscriptSnapshot(session) {
3039
2759
  const transcript = session.transcript();
3040
2760
  this.send({
3041
- type: "transcript_reset",
2761
+ type: "transcript_snapshot",
3042
2762
  blocks: cloneBlocks(transcript.blocks),
3043
2763
  revision: transcript.revision
3044
2764
  });
@@ -3072,8 +2792,7 @@ var AgentTransportBindingImpl = class {
3072
2792
  type: "retry_scheduled",
3073
2793
  attempt: event.attempt,
3074
2794
  delayMs: event.delayMs,
3075
- code: event.code,
3076
- diagnostics: event.diagnostics
2795
+ code: event.code
3077
2796
  });
3078
2797
  return;
3079
2798
  case "error":
@@ -3108,13 +2827,11 @@ var AgentTransportBindingImpl = class {
3108
2827
  async closeSession() {
3109
2828
  const session = this.session;
3110
2829
  const agent = this.currentAgent;
2830
+ const environment = this.currentEnvironment;
3111
2831
  const cwd = this.currentCwd;
3112
2832
  try {
3113
2833
  if (session) await session.dispose();
3114
- const pendingEnvironments = await Promise.allSettled(this.pendingEnvironmentsByHost.values());
3115
- const environments = new Set(this.environmentsByHost.values());
3116
- for (const result of pendingEnvironments) if (result.status === "fulfilled") environments.add(result.value);
3117
- await Promise.all([...environments].map((environment) => environment.disposeAllShells()));
2834
+ if (environment) await environment.disposeAllShells();
3118
2835
  if (session && agent && cwd) await agent.dispose?.({
3119
2836
  agentSessionId: session.id(),
3120
2837
  state: session.state(),
@@ -3126,12 +2843,9 @@ var AgentTransportBindingImpl = class {
3126
2843
  this.unsubscribeSession = null;
3127
2844
  this.session = null;
3128
2845
  this.currentAgent = null;
3129
- this.environmentsByHost.clear();
3130
- this.pendingEnvironmentsByHost.clear();
3131
- this.currentCommandRegistry = null;
2846
+ this.currentEnvironment = null;
3132
2847
  this.currentCwd = null;
3133
2848
  this.currentProviderId = null;
3134
- this.currentCommandNames = /* @__PURE__ */ new Set();
3135
2849
  if (this.currentSessionId) {
3136
2850
  this.sessions.release(this.currentSessionId, this);
3137
2851
  this.currentSessionId = null;
@@ -3139,17 +2853,17 @@ var AgentTransportBindingImpl = class {
3139
2853
  }
3140
2854
  }
3141
2855
  async listConversations(cwd) {
3142
- const host = await this.agent.host({
2856
+ const host = this.agent.host({
3143
2857
  state: this.agent.initialState(),
3144
2858
  cwd
3145
2859
  });
3146
2860
  const keys = await host.store.list("agent-sessions/");
3147
2861
  const conversations = [];
3148
2862
  for (const key of keys) {
3149
- if (!key.endsWith("/checkpoint.json")) continue;
3150
- const checkpoint = await host.store.readJson(key);
3151
- if (!checkpoint || checkpoint.cwd !== cwd) continue;
3152
- conversations.push(summarizeConversation(key.slice(15, -16), checkpoint));
2863
+ if (!key.endsWith("/snapshot.json")) continue;
2864
+ const snapshot = await host.store.readJson(key);
2865
+ if (!snapshot || snapshot.cwd !== cwd) continue;
2866
+ conversations.push(summarizeConversation(key.slice(15, -14), snapshot));
3153
2867
  }
3154
2868
  conversations.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
3155
2869
  this.send({
@@ -3158,122 +2872,39 @@ var AgentTransportBindingImpl = class {
3158
2872
  });
3159
2873
  }
3160
2874
  async handleShellWrite(frame) {
3161
- const session = this.sessionFor("shell_write");
3162
- if (!session || !this.currentCwd) return;
3163
- const result = await (await this.resolveEnvironment({
3164
- agentSessionId: session.id(),
3165
- state: session.state(),
3166
- cwd: this.currentCwd,
3167
- metadata: frame.metadata ?? null
3168
- }, { commandId: frame.commandId })).write({
2875
+ if (!this.sessionFor("shell_write") || !this.currentEnvironment || !this.currentCwd) return;
2876
+ const result = await this.currentEnvironment.write({
3169
2877
  commandId: frame.commandId,
3170
2878
  stdin: frame.stdin
3171
2879
  });
3172
2880
  this.sendShellWriteResult(frame.commandId, result);
3173
2881
  }
3174
2882
  /** Backs `AgentServer.runCommandLine` — see its doc comment for the contract. */
3175
- async runCommandLine(shellId, name, args, opts) {
3176
- const environment = this.environmentForShell(shellId);
2883
+ async runCommandLine(name, args, opts) {
2884
+ const environment = this.currentEnvironment;
3177
2885
  const agentSessionId = this.currentSessionId;
3178
2886
  if (!environment || !agentSessionId) throw new Error("runCommandLine: session has no active shell environment");
3179
- if (!this.currentCommandNames.has(name)) throw new RunCommandLineCommandNotRegisteredError(name);
3180
2887
  const words = [name, ...args].map(shellQuote).join(" ");
3181
2888
  let script = words;
3182
2889
  if (opts.stdin.length > 0) {
3183
2890
  const delimiter = heredocDelimiter(opts.stdin);
3184
- script = `${words} <<'${delimiter}'\n${opts.stdin.endsWith("\n") ? opts.stdin : `${opts.stdin}\n`}${delimiter}`;
2891
+ script = `${words} <<'${delimiter}'\n${opts.stdin}\n${delimiter}`;
3185
2892
  }
2893
+ const cdScript = `cd ${shellQuote(opts.cwd)} && ${script}`;
3186
2894
  const result = await environment.exec({
3187
2895
  agentSessionId,
3188
- script,
2896
+ script: cdScript,
3189
2897
  timeoutMs: MAX_TIMEOUT_MS,
3190
- signal: opts.signal,
3191
- ephemeral: true,
3192
- cwd: opts.cwd
3193
- });
3194
- try {
3195
- if (result.status === "exited") {
3196
- if (result.binaryStdout) {
3197
- const truncationNote = result.binaryStdout.truncated ? `command bridge: binary stdout truncated at the output limit (${result.binaryStdout.data.length} of ${result.binaryStdout.totalBytes} bytes)\n` : "";
3198
- return {
3199
- exitCode: result.exitCode,
3200
- stdout: bytesToBase64(result.binaryStdout.data),
3201
- stdoutEncoding: "base64",
3202
- stderr: `${result.stderr.delta}${truncationNote}`
3203
- };
3204
- }
3205
- return {
3206
- exitCode: result.exitCode,
3207
- stdout: result.stdout.delta,
3208
- stderr: result.stderr.delta
3209
- };
3210
- }
3211
- if (result.status === "aborted") throw new Error(`runCommandLine: call for "${name}" was cancelled before it completed`);
3212
- const aborted = await environment.abort({ commandId: result.commandId });
3213
- throw new RunCommandLineTimeoutError(result.commandId, aborted.status === "aborted" ? aborted.stdout.delta : "", aborted.status === "aborted" ? aborted.stderr.delta : "");
3214
- } finally {
3215
- await environment.disposeShell(result.shellId).catch(() => {});
3216
- }
3217
- }
3218
- hasShell(shellId) {
3219
- return this.environmentForShell(shellId) !== null;
3220
- }
3221
- async resolveEnvironment(ctx, handle) {
3222
- const agent = this.currentAgent;
3223
- const commandRegistry = this.currentCommandRegistry;
3224
- if (!agent || !commandRegistry) throw new Error("Shell environment is not available before session open");
3225
- const host = await agent.host({
3226
- agentSessionId: ctx.agentSessionId,
3227
- state: ctx.state,
3228
- cwd: ctx.cwd,
3229
- metadata: ctx.metadata
3230
- });
3231
- const environment = await this.environmentForHost(host, commandRegistry);
3232
- const owner = handle.shellId ? this.environmentForShell(handle.shellId) : handle.commandId ? this.environmentForCommand(handle.commandId) : null;
3233
- if (owner && owner !== environment) {
3234
- const id = handle.shellId ?? handle.commandId;
3235
- throw new Error(`Shell handle "${id}" belongs to a different Host`);
3236
- }
3237
- return environment;
3238
- }
3239
- async environmentForHost(host, commands) {
3240
- const existing = this.environmentsByHost.get(host);
3241
- if (existing) return existing;
3242
- const pending = this.pendingEnvironmentsByHost.get(host);
3243
- if (pending) return pending;
3244
- const creation = this.createEnvironment(host, commands);
3245
- this.pendingEnvironmentsByHost.set(host, creation);
3246
- try {
3247
- const environment = await creation;
3248
- this.environmentsByHost.set(host, environment);
3249
- return environment;
3250
- } finally {
3251
- this.pendingEnvironmentsByHost.delete(host);
3252
- }
3253
- }
3254
- async createEnvironment(host, commands) {
3255
- const agentSessionId = this.currentSessionId;
3256
- if (!agentSessionId) throw new Error("Shell environment is not available before session open");
3257
- return new BashEnvironment({
3258
- ...this.prepareShell ? await this.prepareShell({
3259
- agentSessionId,
3260
- host,
3261
- commandNames: commands.list().map((command) => command.name),
3262
- shell: this.shellOptions
3263
- }) : this.shellOptions,
3264
- host,
3265
- commands
2898
+ signal: opts.signal
3266
2899
  });
3267
- }
3268
- environmentForShell(shellId) {
3269
- const matches = [...this.environmentsByHost.values()].filter((environment) => environment.getShell(shellId));
3270
- if (matches.length > 1) throw new Error(`Shell id "${shellId}" is not unique in this session`);
3271
- return matches[0] ?? null;
3272
- }
3273
- environmentForCommand(commandId) {
3274
- const matches = [...this.environmentsByHost.values()].filter((environment) => environment.hasCommand(commandId));
3275
- if (matches.length > 1) throw new Error(`Command id "${commandId}" is not unique in this session`);
3276
- return matches[0] ?? null;
2900
+ if (result.status === "exited") return {
2901
+ exitCode: result.exitCode,
2902
+ stdout: result.stdout.delta,
2903
+ stderr: result.stderr.delta
2904
+ };
2905
+ if (result.status === "aborted") throw new Error(`runCommandLine: call for "${name}" was cancelled before it completed`);
2906
+ const aborted = await environment.abort({ commandId: result.commandId });
2907
+ throw new RunCommandLineTimeoutError(result.commandId, aborted.status === "aborted" ? aborted.stdout.delta : "", aborted.status === "aborted" ? aborted.stderr.delta : "");
3277
2908
  }
3278
2909
  sessionFor(command) {
3279
2910
  if (!this.session) {
@@ -3308,7 +2939,7 @@ var AgentTransportBindingImpl = class {
3308
2939
  type: "shell_output",
3309
2940
  shellId: shell.shellId,
3310
2941
  commandId: shell.commandId,
3311
- status: shell.status
2942
+ snapshot: shell.snapshot
3312
2943
  });
3313
2944
  const audit = progressToAudit(progress);
3314
2945
  if (audit.length > 0) this.send({
@@ -3322,7 +2953,7 @@ var AgentTransportBindingImpl = class {
3322
2953
  type: "shell_output",
3323
2954
  shellId: shell.shellId,
3324
2955
  commandId: shell.commandId,
3325
- status: shell.status
2956
+ snapshot: shell.snapshot
3326
2957
  });
3327
2958
  const audit = progressToAudit(progress);
3328
2959
  if (audit.length > 0) this.send({
@@ -3344,12 +2975,13 @@ var AgentTransportBindingImpl = class {
3344
2975
  sendError(error) {
3345
2976
  const normalized = error instanceof Error ? error : new Error(String(error));
3346
2977
  const code = errorCode(error);
3347
- const diagnostics = errorDiagnostics(error);
3348
- this.send({
2978
+ this.send(code ? {
3349
2979
  type: "error",
3350
2980
  message: normalized.message,
3351
- ...code ? { code } : {},
3352
- ...diagnostics ? { diagnostics } : {}
2981
+ code
2982
+ } : {
2983
+ type: "error",
2984
+ message: normalized.message
3353
2985
  });
3354
2986
  }
3355
2987
  };
@@ -3360,15 +2992,15 @@ var HostAgentSessionStore = class {
3360
2992
  this.store = store;
3361
2993
  this.agentSessionId = agentSessionId;
3362
2994
  }
3363
- saveCheckpoint(checkpoint) {
3364
- return this.store.writeJson(`agent-sessions/${this.agentSessionId}/checkpoint.json`, checkpoint);
2995
+ saveSnapshot(snapshot) {
2996
+ return this.store.writeJson(`agent-sessions/${this.agentSessionId}/snapshot.json`, snapshot);
3365
2997
  }
3366
- loadCheckpoint() {
3367
- return this.store.readJson(`agent-sessions/${this.agentSessionId}/checkpoint.json`);
2998
+ loadSnapshot() {
2999
+ return this.store.readJson(`agent-sessions/${this.agentSessionId}/snapshot.json`);
3368
3000
  }
3369
3001
  };
3370
- function summarizeConversation(id, checkpoint) {
3371
- const blocks = checkpoint.transcript.blocks;
3002
+ function summarizeConversation(id, snapshot) {
3003
+ const blocks = snapshot.transcript.blocks;
3372
3004
  const first = blocks[0];
3373
3005
  const last = blocks[blocks.length - 1];
3374
3006
  return {
@@ -3402,14 +3034,14 @@ function progressToShellOutput(progress) {
3402
3034
  if (!isRecord(progress.stdout) || !isRecord(progress.stderr)) return null;
3403
3035
  const stdout = progress.stdout;
3404
3036
  const stderr = progress.stderr;
3405
- if (!isShellStreamView(stdout) || !isShellStreamView(stderr) || typeof progress.runningMs !== "number" || typeof progress.idleMs !== "number") return null;
3037
+ if (!isStreamArtifact(stdout) || !isStreamArtifact(stderr) || typeof progress.runningMs !== "number" || typeof progress.idleMs !== "number") return null;
3406
3038
  return {
3407
3039
  shellId: progress.shellId,
3408
3040
  commandId: progress.commandId,
3409
- status: progress
3041
+ snapshot: progress
3410
3042
  };
3411
3043
  }
3412
- function isShellStreamView(value) {
3044
+ function isStreamArtifact(value) {
3413
3045
  return typeof value.path === "string" && typeof value.offset === "number" && typeof value.delta === "string" && typeof value.tail === "string" && typeof value.bytes === "number" && typeof value.truncated === "boolean";
3414
3046
  }
3415
3047
  function progressToAudit(progress) {
@@ -3430,10 +3062,6 @@ function errorCode(error) {
3430
3062
  if (!isRecord(error) || typeof error.code !== "string") return void 0;
3431
3063
  return error.code;
3432
3064
  }
3433
- function errorDiagnostics(error) {
3434
- if (!(error instanceof ProviderStreamError)) return void 0;
3435
- return error.diagnostics;
3436
- }
3437
3065
  function createProviderMap(providers) {
3438
3066
  const map = /* @__PURE__ */ new Map();
3439
3067
  for (const provider of providers) {
@@ -3443,4 +3071,4 @@ function createProviderMap(providers) {
3443
3071
  return map;
3444
3072
  }
3445
3073
  //#endregion
3446
- export { AgentClient, AgentServer, AgentSession, DEFAULT_MAX_MEDIA_BYTES, DEFAULT_TURN_RETRY_POLICY, ProviderStreamError, RunCommandLineCommandNotRegisteredError, RunCommandLineShellNotFoundError, RunCommandLineTimeoutError, SHELL_VIEW_MAX_CHARS, TranscriptLog, applyTranscriptPatches, cloneBlocks, createInProcessTransportPair, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, findResumePoint, finishShellToolResult, isContextLengthExceeded, isRetryableCode, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, toShellToolResult };
3074
+ export { AgentClient, AgentServer, AgentSession, DEFAULT_TURN_RETRY_POLICY, RunCommandLineSessionNotFoundError, RunCommandLineTimeoutError, Transcript, applyTranscriptPatches, cloneBlocks, createInProcessTransportPair, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, finishShellToolResult, isRetryableCode, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, toShellToolResult };