@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/README.md +0 -2
- package/dist/client-entry.d.mts +3 -3
- package/dist/client-entry.mjs +1 -1
- package/dist/index.d.mts +28 -145
- package/dist/index.mjs +205 -577
- package/dist/stdio-transport.d.mts +2 -1
- package/dist/{transport-BZwi2YZL.d.mts → transport-Bhf0h9FY.d.mts} +36 -65
- package/dist/{websocket-transport-os0mfcLi.mjs → websocket-transport-Bod0puog.mjs} +18 -60
- package/dist/{websocket-transport-CZpGt4Py.d.mts → websocket-transport-D_Awo7zN.d.mts} +9 -22
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { a as cloneBlocks, i as applyTranscriptPatches, n as createWebSocketServerTransport,
|
|
2
|
-
import { AbortError, abortable, asError, asRecord, asString,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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(
|
|
406
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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 >
|
|
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
|
|
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
|
-
|
|
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
|
|
1022
|
-
*
|
|
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
|
|
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
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
-
|
|
1073
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
1278
|
-
if (params.
|
|
1279
|
-
const
|
|
1280
|
-
|
|
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:
|
|
1283
|
-
cwd:
|
|
1171
|
+
model: snapshot.model,
|
|
1172
|
+
cwd: snapshot.cwd,
|
|
1284
1173
|
runtime: params.runtime,
|
|
1285
|
-
transcript:
|
|
1286
|
-
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
|
|
1303
|
-
this.deliverYieldWakeup(wakeupId
|
|
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
|
|
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(
|
|
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
|
|
1555
|
-
const wakeupId = this.yields.schedule(durationMs
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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(
|
|
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"
|
|
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
|
-
*
|
|
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.
|
|
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
|
|
1970
|
+
async persistSnapshot() {
|
|
2131
1971
|
if (!this.store || !this.persistDirty) return;
|
|
2132
1972
|
this.persistDirty = false;
|
|
2133
|
-
await this.store.
|
|
2134
|
-
transcript: this.transcriptLog.
|
|
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.
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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.
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
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
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
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
|
-
|
|
2567
|
-
kind: "
|
|
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
|
|
2643
|
-
|
|
2644
|
-
constructor(
|
|
2645
|
-
super(`runCommandLine:
|
|
2646
|
-
this.
|
|
2647
|
-
this.name = "
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
2714
|
-
const
|
|
2715
|
-
if (
|
|
2716
|
-
|
|
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
|
|
2724
|
-
* proceeds, so two connections never write the same
|
|
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
|
-
|
|
2462
|
+
prepareSessionShell;
|
|
2747
2463
|
sessions;
|
|
2748
2464
|
session = null;
|
|
2749
2465
|
currentAgent = null;
|
|
2750
|
-
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
|
2670
|
+
const provisionalHost = agent.host({
|
|
2961
2671
|
state: initialState,
|
|
2962
2672
|
cwd: frame.cwd
|
|
2963
|
-
})
|
|
2964
|
-
const
|
|
2965
|
-
const
|
|
2966
|
-
const
|
|
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
|
|
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
|
|
2978
|
-
scheduleYield: (
|
|
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
|
|
2701
|
+
return sessionRef.scheduleYieldWakeup(durationMs);
|
|
2981
2702
|
}
|
|
2982
2703
|
});
|
|
2983
|
-
const commandsPrompt = commandRegistry.
|
|
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.
|
|
2717
|
+
const session = restoring ? AgentSession.fromSnapshot({
|
|
2997
2718
|
provider,
|
|
2998
2719
|
runtime,
|
|
2999
|
-
|
|
3000
|
-
...
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2758
|
+
sendTranscriptSnapshot(session) {
|
|
3039
2759
|
const transcript = session.transcript();
|
|
3040
2760
|
this.send({
|
|
3041
|
-
type: "
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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("/
|
|
3150
|
-
const
|
|
3151
|
-
if (!
|
|
3152
|
-
conversations.push(summarizeConversation(key.slice(15, -
|
|
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
|
-
|
|
3162
|
-
|
|
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(
|
|
3176
|
-
const environment = this.
|
|
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
|
|
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
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3348
|
-
this.send({
|
|
2978
|
+
this.send(code ? {
|
|
3349
2979
|
type: "error",
|
|
3350
2980
|
message: normalized.message,
|
|
3351
|
-
|
|
3352
|
-
|
|
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
|
-
|
|
3364
|
-
return this.store.writeJson(`agent-sessions/${this.agentSessionId}/
|
|
2995
|
+
saveSnapshot(snapshot) {
|
|
2996
|
+
return this.store.writeJson(`agent-sessions/${this.agentSessionId}/snapshot.json`, snapshot);
|
|
3365
2997
|
}
|
|
3366
|
-
|
|
3367
|
-
return this.store.readJson(`agent-sessions/${this.agentSessionId}/
|
|
2998
|
+
loadSnapshot() {
|
|
2999
|
+
return this.store.readJson(`agent-sessions/${this.agentSessionId}/snapshot.json`);
|
|
3368
3000
|
}
|
|
3369
3001
|
};
|
|
3370
|
-
function summarizeConversation(id,
|
|
3371
|
-
const 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 (!
|
|
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
|
-
|
|
3041
|
+
snapshot: progress
|
|
3410
3042
|
};
|
|
3411
3043
|
}
|
|
3412
|
-
function
|
|
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,
|
|
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 };
|