@demicodes/agent 0.10.1 → 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 +202 -567
- 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. */
|
|
@@ -923,7 +860,7 @@ var CompactionController = class {
|
|
|
923
860
|
return false;
|
|
924
861
|
}
|
|
925
862
|
async generateSummary(blocks) {
|
|
926
|
-
const rendered = renderItemsForSummary(new
|
|
863
|
+
const rendered = renderItemsForSummary(new Transcript(blocks).collectInferenceItems());
|
|
927
864
|
const policy = this.host.retryPolicy;
|
|
928
865
|
for (let attempt = 1;; attempt += 1) {
|
|
929
866
|
const request = buildCompactionSummaryRequest(rendered, {
|
|
@@ -942,16 +879,10 @@ var CompactionController = class {
|
|
|
942
879
|
if (event.type === "text_delta") summary += event.text;
|
|
943
880
|
if (event.type === "abort") throw new AbortError();
|
|
944
881
|
if (event.type === "error") {
|
|
945
|
-
|
|
946
|
-
source: event.diagnostics?.source ?? "unknown",
|
|
947
|
-
...event.diagnostics,
|
|
948
|
-
clientRequestId: request.requestId
|
|
949
|
-
};
|
|
950
|
-
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);
|
|
951
883
|
transient = {
|
|
952
884
|
code: event.code,
|
|
953
|
-
retryAfterMs: event.retryAfterMs ?? null
|
|
954
|
-
diagnostics
|
|
885
|
+
retryAfterMs: event.retryAfterMs ?? null
|
|
955
886
|
};
|
|
956
887
|
break;
|
|
957
888
|
}
|
|
@@ -962,8 +893,7 @@ var CompactionController = class {
|
|
|
962
893
|
type: "retry_scheduled",
|
|
963
894
|
attempt,
|
|
964
895
|
delayMs,
|
|
965
|
-
code: transient.code
|
|
966
|
-
diagnostics: transient.diagnostics
|
|
896
|
+
code: transient.code
|
|
967
897
|
});
|
|
968
898
|
await abortable(delay(delayMs), request.cancel);
|
|
969
899
|
}
|
|
@@ -986,7 +916,6 @@ var ProviderTurnLoop = class {
|
|
|
986
916
|
let autoCompactions = 0;
|
|
987
917
|
while (true) {
|
|
988
918
|
throwIfAborted(this.host.currentSignal());
|
|
989
|
-
await this.host.materializePendingSteers();
|
|
990
919
|
const steerContinuationBeforeStream = this.host.steerContinuationCount;
|
|
991
920
|
const shouldAutoRecover = await this.streamProviderOnce();
|
|
992
921
|
throwIfAborted(this.host.currentSignal());
|
|
@@ -1011,9 +940,8 @@ var ProviderTurnLoop = class {
|
|
|
1011
940
|
}
|
|
1012
941
|
/**
|
|
1013
942
|
* Streams one provider response, silently retrying transient failures
|
|
1014
|
-
* (rate_limit/overloaded) with backoff
|
|
1015
|
-
*
|
|
1016
|
-
* `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.
|
|
1017
945
|
* The turn stays in provider_streaming across backoff waits so steers keep
|
|
1018
946
|
* queueing instead of being rejected.
|
|
1019
947
|
*/
|
|
@@ -1028,8 +956,7 @@ var ProviderTurnLoop = class {
|
|
|
1028
956
|
type: "retry_scheduled",
|
|
1029
957
|
attempt: outcome.attempt,
|
|
1030
958
|
delayMs: outcome.delayMs,
|
|
1031
|
-
code: outcome.code
|
|
1032
|
-
diagnostics: outcome.diagnostics
|
|
959
|
+
code: outcome.code
|
|
1033
960
|
});
|
|
1034
961
|
await abortable(delay(outcome.delayMs), this.host.currentSignal());
|
|
1035
962
|
}
|
|
@@ -1039,47 +966,26 @@ var ProviderTurnLoop = class {
|
|
|
1039
966
|
}
|
|
1040
967
|
async streamAttempt(attempt, policy) {
|
|
1041
968
|
const request = this.buildInferenceRequest();
|
|
1042
|
-
const attemptStart = this.host.transcript.blocks.length;
|
|
1043
969
|
const run = this.host.provider.run(request);
|
|
1044
970
|
let shouldAutoRecover = false;
|
|
1045
|
-
let
|
|
971
|
+
let produced = false;
|
|
1046
972
|
this.host.setActiveProviderRun(run);
|
|
1047
973
|
try {
|
|
1048
974
|
for await (const event of this.host.streamProvider(request, run)) {
|
|
1049
975
|
throwIfAborted(request.cancel);
|
|
1050
976
|
if (event.type === "abort") throw new AbortError();
|
|
1051
|
-
if (event.type === "thinking_start") {
|
|
1052
|
-
hasPendingThinkingStart = true;
|
|
1053
|
-
continue;
|
|
1054
|
-
}
|
|
1055
977
|
if (event.type === "error") {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const errorEvent = {
|
|
1062
|
-
...event,
|
|
1063
|
-
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
|
|
1064
983
|
};
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
return {
|
|
1068
|
-
type: "retry",
|
|
1069
|
-
attempt,
|
|
1070
|
-
delayMs: retryDelayMs(policy, attempt, errorEvent.retryAfterMs ?? null),
|
|
1071
|
-
code: errorEvent.code,
|
|
1072
|
-
diagnostics: errorEvent.diagnostics
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
await this.applyProviderEvent(errorEvent);
|
|
1076
|
-
throw new ProviderStreamError(errorEvent.message, errorEvent.code, errorEvent.diagnostics);
|
|
1077
|
-
}
|
|
1078
|
-
if (hasPendingThinkingStart) {
|
|
1079
|
-
await this.applyProviderEvent({ type: "thinking_start" });
|
|
1080
|
-
hasPendingThinkingStart = false;
|
|
984
|
+
await this.applyProviderEvent(event);
|
|
985
|
+
throw new ProviderStreamError(event.message, event.code);
|
|
1081
986
|
}
|
|
1082
987
|
await this.applyProviderEvent(event);
|
|
988
|
+
produced = true;
|
|
1083
989
|
if (event.type === "response" && this.isUsageNearLimit(event.usage)) shouldAutoRecover = true;
|
|
1084
990
|
}
|
|
1085
991
|
} finally {
|
|
@@ -1117,7 +1023,7 @@ var ProviderTurnLoop = class {
|
|
|
1117
1023
|
}
|
|
1118
1024
|
const input = parseJsonOrString(toolCall.input);
|
|
1119
1025
|
const result = await this.invokeToolAsResult(tool, toolCall.toolUseId, input);
|
|
1120
|
-
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);
|
|
1121
1027
|
await this.host.runtime.lifecycle?.({
|
|
1122
1028
|
type: "after_tool_call",
|
|
1123
1029
|
agentSessionId: this.host.agentSessionId,
|
|
@@ -1125,8 +1031,7 @@ var ProviderTurnLoop = class {
|
|
|
1125
1031
|
transcript: this.host.transcript,
|
|
1126
1032
|
toolCallId: toolCall.toolUseId,
|
|
1127
1033
|
toolName: toolCall.toolName,
|
|
1128
|
-
result
|
|
1129
|
-
metadata: this.host.metadata
|
|
1034
|
+
result
|
|
1130
1035
|
});
|
|
1131
1036
|
await this.host.commitTranscript();
|
|
1132
1037
|
if (!options.deferSteerMaterialization) await this.host.materializeSteersArrivedSince(steerContinuationBeforeTool);
|
|
@@ -1149,7 +1054,6 @@ var ProviderTurnLoop = class {
|
|
|
1149
1054
|
model: this.host.model,
|
|
1150
1055
|
toolCallId,
|
|
1151
1056
|
signal,
|
|
1152
|
-
metadata: this.host.metadata,
|
|
1153
1057
|
emitProgress: (progress) => {
|
|
1154
1058
|
this.host.emit({
|
|
1155
1059
|
type: "tool_progress",
|
|
@@ -1172,10 +1076,7 @@ var ProviderTurnLoop = class {
|
|
|
1172
1076
|
text: `Tool failed: ${normalized.message}`
|
|
1173
1077
|
}],
|
|
1174
1078
|
isError: true,
|
|
1175
|
-
|
|
1176
|
-
kind: "tool_error",
|
|
1177
|
-
error: normalized.message
|
|
1178
|
-
}
|
|
1079
|
+
metadata: { error: normalized.message }
|
|
1179
1080
|
};
|
|
1180
1081
|
}
|
|
1181
1082
|
}
|
|
@@ -1183,8 +1084,7 @@ var ProviderTurnLoop = class {
|
|
|
1183
1084
|
return this.host.runtime.tools({
|
|
1184
1085
|
agentSessionId: this.host.agentSessionId,
|
|
1185
1086
|
state: this.host.agentState,
|
|
1186
|
-
cwd: this.host.cwd
|
|
1187
|
-
metadata: this.host.metadata
|
|
1087
|
+
cwd: this.host.cwd
|
|
1188
1088
|
});
|
|
1189
1089
|
}
|
|
1190
1090
|
buildInferenceRequest() {
|
|
@@ -1238,9 +1138,6 @@ var AgentSession = class AgentSession {
|
|
|
1238
1138
|
listeners = /* @__PURE__ */ new Set();
|
|
1239
1139
|
pendingActions = [];
|
|
1240
1140
|
queued = [];
|
|
1241
|
-
get modelSelection() {
|
|
1242
|
-
return this.model;
|
|
1243
|
-
}
|
|
1244
1141
|
transcriptLog;
|
|
1245
1142
|
agentState;
|
|
1246
1143
|
currentPhase = "idle";
|
|
@@ -1250,7 +1147,6 @@ var AgentSession = class AgentSession {
|
|
|
1250
1147
|
activeTurnId = null;
|
|
1251
1148
|
activeTurnPhase = null;
|
|
1252
1149
|
activeProviderRun = null;
|
|
1253
|
-
activeMetadata = null;
|
|
1254
1150
|
steerQueue = new PendingSteerQueue();
|
|
1255
1151
|
yields;
|
|
1256
1152
|
compaction;
|
|
@@ -1262,27 +1158,22 @@ var AgentSession = class AgentSession {
|
|
|
1262
1158
|
persistTimer = null;
|
|
1263
1159
|
persistDirty = false;
|
|
1264
1160
|
/**
|
|
1265
|
-
* Restores a session from a
|
|
1161
|
+
* Restores a session from a snapshot. Ownership of the snapshot (including
|
|
1266
1162
|
* `state`) transfers to the session: the caller must not mutate it afterwards.
|
|
1267
1163
|
* Passing state by reference — not a clone — lets the caller share the same
|
|
1268
1164
|
* object with harness closures (host/commands), keeping one live state.
|
|
1269
1165
|
*/
|
|
1270
|
-
static
|
|
1271
|
-
if (params.
|
|
1272
|
-
const
|
|
1273
|
-
|
|
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({
|
|
1274
1170
|
provider: params.provider,
|
|
1275
|
-
model:
|
|
1276
|
-
cwd:
|
|
1171
|
+
model: snapshot.model,
|
|
1172
|
+
cwd: snapshot.cwd,
|
|
1277
1173
|
runtime: params.runtime,
|
|
1278
|
-
transcript:
|
|
1279
|
-
state:
|
|
1174
|
+
transcript: snapshot.transcript,
|
|
1175
|
+
state: snapshot.state
|
|
1280
1176
|
}, options);
|
|
1281
|
-
for (const toolCall of session.transcriptLog.pendingToolCalls()) session.transcriptLog.completeToolCall(toolCall.toolUseId, [{
|
|
1282
|
-
type: "text",
|
|
1283
|
-
text: `Tool call interrupted: ${toolCall.toolName} (the process died before a result was recorded)`
|
|
1284
|
-
}], true);
|
|
1285
|
-
return session;
|
|
1286
1177
|
}
|
|
1287
1178
|
constructor(params, options = {}) {
|
|
1288
1179
|
this.provider = params.provider;
|
|
@@ -1292,8 +1183,8 @@ var AgentSession = class AgentSession {
|
|
|
1292
1183
|
this.agentState = params.state === void 0 ? params.runtime.initialState() : params.state;
|
|
1293
1184
|
this.agentSessionId = options.agentSessionId ?? createId();
|
|
1294
1185
|
this.idFactory = options.idFactory ?? createId;
|
|
1295
|
-
this.yields = new YieldScheduler(this.idFactory, (wakeupId
|
|
1296
|
-
this.deliverYieldWakeup(wakeupId
|
|
1186
|
+
this.yields = new YieldScheduler(this.idFactory, (wakeupId) => {
|
|
1187
|
+
this.deliverYieldWakeup(wakeupId);
|
|
1297
1188
|
});
|
|
1298
1189
|
this.store = options.store;
|
|
1299
1190
|
this.persistIntervalMs = options.persistIntervalMs ?? DEFAULT_PERSIST_INTERVAL_MS;
|
|
@@ -1304,7 +1195,7 @@ var AgentSession = class AgentSession {
|
|
|
1304
1195
|
idFactory: this.idFactory,
|
|
1305
1196
|
now: options.now
|
|
1306
1197
|
};
|
|
1307
|
-
this.transcriptLog = params.transcript instanceof
|
|
1198
|
+
this.transcriptLog = params.transcript instanceof Transcript ? params.transcript : new Transcript(params.transcript?.blocks ?? [], transcriptOptions);
|
|
1308
1199
|
const self = this;
|
|
1309
1200
|
const compactionHost = {
|
|
1310
1201
|
get transcript() {
|
|
@@ -1371,9 +1262,6 @@ var AgentSession = class AgentSession {
|
|
|
1371
1262
|
get retryPolicy() {
|
|
1372
1263
|
return self.retryPolicy;
|
|
1373
1264
|
},
|
|
1374
|
-
get metadata() {
|
|
1375
|
-
return self.activeMetadata;
|
|
1376
|
-
},
|
|
1377
1265
|
currentSignal: () => self.currentSignal(),
|
|
1378
1266
|
currentTurnId: () => self.currentTurnId(),
|
|
1379
1267
|
nextRequestId: () => self.idFactory(),
|
|
@@ -1391,8 +1279,7 @@ var AgentSession = class AgentSession {
|
|
|
1391
1279
|
runWithCompactingPhase: (fn) => self.runWithCompactingPhase(fn),
|
|
1392
1280
|
commitTranscript: () => self.commitTranscript(),
|
|
1393
1281
|
emit: (event) => self.emit(event),
|
|
1394
|
-
materializeSteersArrivedSince: (count) => self.materializePendingSteersArrivedSince(count)
|
|
1395
|
-
materializePendingSteers: () => self.materializePendingSteersForCurrentTurn()
|
|
1282
|
+
materializeSteersArrivedSince: (count) => self.materializePendingSteersArrivedSince(count)
|
|
1396
1283
|
};
|
|
1397
1284
|
this.turnLoop = new ProviderTurnLoop(turnLoopHost);
|
|
1398
1285
|
}
|
|
@@ -1402,46 +1289,27 @@ var AgentSession = class AgentSession {
|
|
|
1402
1289
|
type: "send",
|
|
1403
1290
|
id,
|
|
1404
1291
|
content,
|
|
1405
|
-
metadata: cloneMetadata(options.metadata),
|
|
1406
1292
|
resolve: noop,
|
|
1407
1293
|
reject: noop
|
|
1408
1294
|
});
|
|
1409
1295
|
}
|
|
1410
|
-
|
|
1411
|
-
* Discards the whole latest turn and runs it again from the user's input.
|
|
1412
|
-
*
|
|
1413
|
-
* This is "regenerate": it is only meaningful when the caller wants a different
|
|
1414
|
-
* answer to the same question and knows the turn's effects can be repeated. It is
|
|
1415
|
-
* NOT the way to recover a failed turn — use `resume`, which unwinds only as far
|
|
1416
|
-
* as is safe.
|
|
1417
|
-
*/
|
|
1418
|
-
retry(options = {}) {
|
|
1296
|
+
retry() {
|
|
1419
1297
|
return this.enqueue({
|
|
1420
1298
|
type: "retry",
|
|
1421
|
-
metadata: cloneMetadata(options.metadata),
|
|
1422
1299
|
resolve: noop,
|
|
1423
1300
|
reject: noop
|
|
1424
1301
|
});
|
|
1425
1302
|
}
|
|
1426
|
-
|
|
1427
|
-
* Finishes a turn that did not finish, after an abort or a terminal provider
|
|
1428
|
-
* error. Unwinds to the turn's resume point — dropping the failed attempt's
|
|
1429
|
-
* leftovers, keeping everything that already left the process — and re-infers
|
|
1430
|
-
* from there. Callers do not choose the granularity and do not need to know how
|
|
1431
|
-
* the turn died.
|
|
1432
|
-
*/
|
|
1433
|
-
resume(options = {}) {
|
|
1303
|
+
resume() {
|
|
1434
1304
|
return this.enqueue({
|
|
1435
1305
|
type: "resume",
|
|
1436
|
-
metadata: cloneMetadata(options.metadata),
|
|
1437
1306
|
resolve: noop,
|
|
1438
1307
|
reject: noop
|
|
1439
1308
|
});
|
|
1440
1309
|
}
|
|
1441
|
-
compact(
|
|
1310
|
+
compact() {
|
|
1442
1311
|
return this.enqueue({
|
|
1443
1312
|
type: "compact",
|
|
1444
|
-
metadata: cloneMetadata(options.metadata),
|
|
1445
1313
|
resolve: noop,
|
|
1446
1314
|
reject: noop
|
|
1447
1315
|
});
|
|
@@ -1544,8 +1412,8 @@ var AgentSession = class AgentSession {
|
|
|
1544
1412
|
canAbortAgain: false
|
|
1545
1413
|
};
|
|
1546
1414
|
}
|
|
1547
|
-
scheduleYieldWakeup(durationMs
|
|
1548
|
-
const wakeupId = this.yields.schedule(durationMs
|
|
1415
|
+
scheduleYieldWakeup(durationMs) {
|
|
1416
|
+
const wakeupId = this.yields.schedule(durationMs);
|
|
1549
1417
|
return {
|
|
1550
1418
|
output: [{
|
|
1551
1419
|
type: "text",
|
|
@@ -1555,7 +1423,7 @@ var AgentSession = class AgentSession {
|
|
|
1555
1423
|
`durationMs: ${durationMs}`
|
|
1556
1424
|
].join("\n")
|
|
1557
1425
|
}],
|
|
1558
|
-
|
|
1426
|
+
metadata: {
|
|
1559
1427
|
kind: "yield_wakeup",
|
|
1560
1428
|
wakeupId,
|
|
1561
1429
|
durationMs
|
|
@@ -1704,13 +1572,13 @@ var AgentSession = class AgentSession {
|
|
|
1704
1572
|
action.resolve();
|
|
1705
1573
|
}
|
|
1706
1574
|
}
|
|
1707
|
-
async deliverYieldWakeup(wakeupId
|
|
1575
|
+
async deliverYieldWakeup(wakeupId) {
|
|
1708
1576
|
if (!this.yields.take(wakeupId)) return;
|
|
1709
1577
|
const content = [{
|
|
1710
1578
|
type: "text",
|
|
1711
1579
|
text: "Scheduled yield wakeup fired. Continue the previous work and inspect any running command with shell_status when needed."
|
|
1712
1580
|
}];
|
|
1713
|
-
if (
|
|
1581
|
+
if (this.canAcceptInternalSteer()) try {
|
|
1714
1582
|
await this.steerInternal(content, wakeupId, true);
|
|
1715
1583
|
return;
|
|
1716
1584
|
} catch (error) {
|
|
@@ -1719,7 +1587,7 @@ var AgentSession = class AgentSession {
|
|
|
1719
1587
|
error: asError(error)
|
|
1720
1588
|
});
|
|
1721
1589
|
}
|
|
1722
|
-
this.enqueueHiddenSend(content
|
|
1590
|
+
this.enqueueHiddenSend(content);
|
|
1723
1591
|
}
|
|
1724
1592
|
canAcceptInternalSteer() {
|
|
1725
1593
|
try {
|
|
@@ -1751,12 +1619,11 @@ var AgentSession = class AgentSession {
|
|
|
1751
1619
|
hidden
|
|
1752
1620
|
});
|
|
1753
1621
|
}
|
|
1754
|
-
enqueueHiddenSend(content
|
|
1622
|
+
enqueueHiddenSend(content) {
|
|
1755
1623
|
this.enqueue({
|
|
1756
1624
|
type: "send",
|
|
1757
1625
|
id: this.idFactory(),
|
|
1758
1626
|
content,
|
|
1759
|
-
metadata,
|
|
1760
1627
|
hidden: true,
|
|
1761
1628
|
resolve: noop,
|
|
1762
1629
|
reject: noop
|
|
@@ -1765,13 +1632,13 @@ var AgentSession = class AgentSession {
|
|
|
1765
1632
|
steerDelivery() {
|
|
1766
1633
|
if (!this.activeTurnId || !this.currentAbortController || !this.activeTurnPhase) throw new Error("AgentSession: no active turn to steer");
|
|
1767
1634
|
if (this.currentAbortController.signal.aborted) throw new Error("AgentSession: active turn is aborted");
|
|
1768
|
-
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}`);
|
|
1769
1636
|
const run = this.activeProviderRun;
|
|
1770
1637
|
if (run?.steer) return {
|
|
1771
1638
|
type: "provider",
|
|
1772
1639
|
run
|
|
1773
1640
|
};
|
|
1774
|
-
if (this.activeTurnPhase === "tool_executing" || this.activeTurnPhase === "provider_streaming"
|
|
1641
|
+
if (this.activeTurnPhase === "tool_executing" || this.activeTurnPhase === "provider_streaming") return { type: "next_provider_continuation" };
|
|
1775
1642
|
throw new Error("AgentSession: active turn cannot accept steering now");
|
|
1776
1643
|
}
|
|
1777
1644
|
enqueue(action) {
|
|
@@ -1804,7 +1671,6 @@ var AgentSession = class AgentSession {
|
|
|
1804
1671
|
if (action.type === "send") this.removeQueuedMessage(action.id);
|
|
1805
1672
|
this.currentAbortController = new AbortController();
|
|
1806
1673
|
this.activeTurnId = action.type === "send" ? action.id : this.idFactory();
|
|
1807
|
-
this.activeMetadata = action.metadata;
|
|
1808
1674
|
this.abortRecorded = false;
|
|
1809
1675
|
try {
|
|
1810
1676
|
await this.executeAction(action);
|
|
@@ -1846,7 +1712,6 @@ var AgentSession = class AgentSession {
|
|
|
1846
1712
|
this.discardPendingSteersForCurrentTurn();
|
|
1847
1713
|
this.activeTurnPhase = "finalizing";
|
|
1848
1714
|
this.activeProviderRun = null;
|
|
1849
|
-
this.activeMetadata = null;
|
|
1850
1715
|
this.currentAbortController = null;
|
|
1851
1716
|
this.activeTurnId = null;
|
|
1852
1717
|
this.activeTurnPhase = null;
|
|
@@ -1880,7 +1745,6 @@ var AgentSession = class AgentSession {
|
|
|
1880
1745
|
this.setPhase("compacting");
|
|
1881
1746
|
this.activeTurnPhase = "compacting";
|
|
1882
1747
|
await this.compaction.run();
|
|
1883
|
-
await this.materializePendingSteersForCurrentTurn();
|
|
1884
1748
|
return;
|
|
1885
1749
|
}
|
|
1886
1750
|
}
|
|
@@ -1921,8 +1785,7 @@ var AgentSession = class AgentSession {
|
|
|
1921
1785
|
agentSessionId: this.agentSessionId,
|
|
1922
1786
|
state: this.agentState,
|
|
1923
1787
|
transcript: this.transcriptLog,
|
|
1924
|
-
content
|
|
1925
|
-
metadata: this.activeMetadata
|
|
1788
|
+
content
|
|
1926
1789
|
});
|
|
1927
1790
|
const resolvedContent = await this.resolveReferences(content);
|
|
1928
1791
|
await this.applyPendingModelSwitch();
|
|
@@ -1941,8 +1804,7 @@ var AgentSession = class AgentSession {
|
|
|
1941
1804
|
agentSessionId: this.agentSessionId,
|
|
1942
1805
|
cwd: this.cwd,
|
|
1943
1806
|
transcript: this.transcriptLog,
|
|
1944
|
-
signal
|
|
1945
|
-
metadata: this.activeMetadata
|
|
1807
|
+
signal
|
|
1946
1808
|
}, content)), signal);
|
|
1947
1809
|
}
|
|
1948
1810
|
async executeRetry() {
|
|
@@ -1954,29 +1816,15 @@ var AgentSession = class AgentSession {
|
|
|
1954
1816
|
agentSessionId: this.agentSessionId,
|
|
1955
1817
|
state: this.agentState,
|
|
1956
1818
|
transcript: this.transcriptLog,
|
|
1957
|
-
reason: "retry"
|
|
1958
|
-
metadata: this.activeMetadata
|
|
1819
|
+
reason: "retry"
|
|
1959
1820
|
});
|
|
1960
1821
|
await this.commitTranscript();
|
|
1961
1822
|
await this.applyPendingModelSwitch();
|
|
1962
1823
|
await this.compaction.preflight();
|
|
1963
1824
|
await this.turnLoop.run();
|
|
1964
1825
|
}
|
|
1965
|
-
/**
|
|
1966
|
-
* Finishes an unfinished turn. Unwinds to its resume point — dropping the failed
|
|
1967
|
-
* attempt's leftovers, keeping everything that already left the process — and
|
|
1968
|
-
* re-infers from there. When the whole turn turns out to be discardable this is
|
|
1969
|
-
* a plain rerun, which spares the model a continuation boundary attached to a
|
|
1970
|
-
* stub of its own aborted output.
|
|
1971
|
-
*/
|
|
1972
1826
|
async executeResume() {
|
|
1973
|
-
const { cut, isFullRerun } = findResumePoint(this.transcriptLog.blocks);
|
|
1974
|
-
if (isFullRerun) {
|
|
1975
|
-
await this.executeRetry();
|
|
1976
|
-
return;
|
|
1977
|
-
}
|
|
1978
1827
|
await this.applyPendingModelSwitch();
|
|
1979
|
-
this.transcriptLog.truncateFrom(cut);
|
|
1980
1828
|
this.transcriptLog.markLatestAbortResumed();
|
|
1981
1829
|
this.transcriptLog.pushResumeTurn(this.currentTurnId(), this.model);
|
|
1982
1830
|
await this.commitTranscript();
|
|
@@ -2004,8 +1852,7 @@ var AgentSession = class AgentSession {
|
|
|
2004
1852
|
agentSessionId: this.agentSessionId,
|
|
2005
1853
|
state: this.agentState,
|
|
2006
1854
|
cwd: this.cwd,
|
|
2007
|
-
transcript: this.transcriptLog
|
|
2008
|
-
metadata: this.activeMetadata
|
|
1855
|
+
transcript: this.transcriptLog
|
|
2009
1856
|
};
|
|
2010
1857
|
}
|
|
2011
1858
|
currentSignal() {
|
|
@@ -2095,7 +1942,7 @@ var AgentSession = class AgentSession {
|
|
|
2095
1942
|
/**
|
|
2096
1943
|
* Publishes transcript changes: drains the mutation journal into a patch
|
|
2097
1944
|
* event (O(changed content), not O(transcript)) and schedules a throttled
|
|
2098
|
-
*
|
|
1945
|
+
* snapshot write. Boundaries (action end, abort, dispose) flush the write.
|
|
2099
1946
|
*/
|
|
2100
1947
|
async commitTranscript() {
|
|
2101
1948
|
const drained = this.transcriptLog.takePatches();
|
|
@@ -2112,7 +1959,7 @@ var AgentSession = class AgentSession {
|
|
|
2112
1959
|
if (this.persistTimer) return;
|
|
2113
1960
|
this.persistTimer = setTimeout(() => {
|
|
2114
1961
|
this.persistTimer = null;
|
|
2115
|
-
this.
|
|
1962
|
+
this.persistSnapshot().catch((error) => {
|
|
2116
1963
|
this.emit({
|
|
2117
1964
|
type: "error",
|
|
2118
1965
|
error: asError(error)
|
|
@@ -2120,11 +1967,11 @@ var AgentSession = class AgentSession {
|
|
|
2120
1967
|
});
|
|
2121
1968
|
}, this.persistIntervalMs);
|
|
2122
1969
|
}
|
|
2123
|
-
async
|
|
1970
|
+
async persistSnapshot() {
|
|
2124
1971
|
if (!this.store || !this.persistDirty) return;
|
|
2125
1972
|
this.persistDirty = false;
|
|
2126
|
-
await this.store.
|
|
2127
|
-
transcript: this.transcriptLog.
|
|
1973
|
+
await this.store.saveSnapshot({
|
|
1974
|
+
transcript: this.transcriptLog.snapshot(),
|
|
2128
1975
|
state: structuredClone(this.agentState),
|
|
2129
1976
|
phase: this.currentPhase,
|
|
2130
1977
|
queue: structuredClone(this.queuedMessages()),
|
|
@@ -2138,7 +1985,7 @@ var AgentSession = class AgentSession {
|
|
|
2138
1985
|
clearTimeout(this.persistTimer);
|
|
2139
1986
|
this.persistTimer = null;
|
|
2140
1987
|
}
|
|
2141
|
-
await this.
|
|
1988
|
+
await this.persistSnapshot();
|
|
2142
1989
|
}
|
|
2143
1990
|
emit(event) {
|
|
2144
1991
|
for (const listener of this.listeners) listener(event);
|
|
@@ -2152,9 +1999,6 @@ var AgentSession = class AgentSession {
|
|
|
2152
1999
|
function textContentSummary(content) {
|
|
2153
2000
|
return truncate(content.map((block) => block.type === "text" ? block.text : `[${block.type}]`).join("\n").trim(), 120, "...");
|
|
2154
2001
|
}
|
|
2155
|
-
function cloneMetadata(metadata) {
|
|
2156
|
-
return metadata === void 0 ? null : structuredClone(metadata);
|
|
2157
|
-
}
|
|
2158
2002
|
async function readProviderIterator(iterator, signal) {
|
|
2159
2003
|
try {
|
|
2160
2004
|
return await abortable(iterator.next(), signal);
|
|
@@ -2230,6 +2074,7 @@ const APPROX_CHARS_PER_TOKEN = 4;
|
|
|
2230
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.";
|
|
2231
2075
|
const execRepeatStates = /* @__PURE__ */ new WeakMap();
|
|
2232
2076
|
function createStandardAgentTools(options) {
|
|
2077
|
+
const { environment } = options;
|
|
2233
2078
|
return [
|
|
2234
2079
|
{
|
|
2235
2080
|
name: "shell_exec",
|
|
@@ -2254,7 +2099,6 @@ function createStandardAgentTools(options) {
|
|
|
2254
2099
|
},
|
|
2255
2100
|
invoke: async (ctx, input) => {
|
|
2256
2101
|
const parsed = parseShellExecInput(input);
|
|
2257
|
-
const environment = await resolveEnvironment(options.environment, ctx, { shellId: parsed.shellId });
|
|
2258
2102
|
const repeatGuard = repeatedShellExecResult(environment, ctx.agentSessionId, parsed.script);
|
|
2259
2103
|
if (repeatGuard) return repeatGuard;
|
|
2260
2104
|
const result = await environment.exec({
|
|
@@ -2282,9 +2126,7 @@ function createStandardAgentTools(options) {
|
|
|
2282
2126
|
}
|
|
2283
2127
|
},
|
|
2284
2128
|
invoke: async (ctx, input) => {
|
|
2285
|
-
const
|
|
2286
|
-
const environment = await resolveEnvironment(options.environment, ctx, { commandId: parsed.commandId });
|
|
2287
|
-
const result = await environment.status(parsed);
|
|
2129
|
+
const result = await environment.status(parseShellStatusInput(input));
|
|
2288
2130
|
ctx.emitProgress(result);
|
|
2289
2131
|
return finishShellToolResult(environment, result, ctx);
|
|
2290
2132
|
}
|
|
@@ -2306,10 +2148,8 @@ function createStandardAgentTools(options) {
|
|
|
2306
2148
|
}
|
|
2307
2149
|
},
|
|
2308
2150
|
invoke: async (ctx, input) => {
|
|
2309
|
-
const parsed = parseShellWriteInput(input);
|
|
2310
|
-
const environment = await resolveEnvironment(options.environment, ctx, { commandId: parsed.commandId });
|
|
2311
2151
|
const result = await environment.write({
|
|
2312
|
-
...
|
|
2152
|
+
...parseShellWriteInput(input),
|
|
2313
2153
|
signal: ctx.signal
|
|
2314
2154
|
});
|
|
2315
2155
|
ctx.emitProgress(result);
|
|
@@ -2332,9 +2172,7 @@ function createStandardAgentTools(options) {
|
|
|
2332
2172
|
}
|
|
2333
2173
|
},
|
|
2334
2174
|
invoke: async (ctx, input) => {
|
|
2335
|
-
const
|
|
2336
|
-
const environment = await resolveEnvironment(options.environment, ctx, { commandId: parsed.commandId });
|
|
2337
|
-
const result = await environment.abort(parsed);
|
|
2175
|
+
const result = await environment.abort(parseShellAbortInput(input));
|
|
2338
2176
|
ctx.emitProgress(result);
|
|
2339
2177
|
return {
|
|
2340
2178
|
...await finishShellToolResult(environment, result, ctx),
|
|
@@ -2365,136 +2203,31 @@ function createStandardAgentTools(options) {
|
|
|
2365
2203
|
}
|
|
2366
2204
|
];
|
|
2367
2205
|
}
|
|
2368
|
-
function resolveEnvironment(source, ctx, handle) {
|
|
2369
|
-
return typeof source === "function" ? source(ctx, handle) : source;
|
|
2370
|
-
}
|
|
2371
|
-
/**
|
|
2372
|
-
* How many bytes of each modality a tool may hand to the model.
|
|
2373
|
-
*
|
|
2374
|
-
* One number cannot serve both, because bytes buy wildly different amounts of
|
|
2375
|
-
* context per modality — measured against a frontier model, a KiB of video
|
|
2376
|
-
* costs ~2 tokens where a KiB of image costs ~50. A cap generous enough to show
|
|
2377
|
-
* a five-minute clip would let a single still eat a six-figure token budget.
|
|
2378
|
-
*
|
|
2379
|
-
* image (4 MiB): well past any sane still — a 4000x3000 PNG lands under it — so
|
|
2380
|
-
* crossing this line is a mistake, not a use case.
|
|
2381
|
-
* video (16 MiB): roughly ten minutes at a viewing-grade encoding, and
|
|
2382
|
-
* deliberately under the ~20 MB inline-payload ceiling the major APIs enforce.
|
|
2383
|
-
* A larger cap buys no reach, only a rejection further downstream where the
|
|
2384
|
-
* reason is harder to read.
|
|
2385
|
-
*
|
|
2386
|
-
* Bytes are a proxy, not a budget: for video they track cost reasonably at a
|
|
2387
|
-
* fixed encoding, but an image's real driver is its pixel dimensions.
|
|
2388
|
-
*/
|
|
2389
|
-
const DEFAULT_MAX_MEDIA_BYTES = {
|
|
2390
|
-
image: 4 * 1024 * 1024,
|
|
2391
|
-
video: 16 * 1024 * 1024
|
|
2392
|
-
};
|
|
2393
2206
|
function shellPreviewBudgetTokens(contextWindow) {
|
|
2394
2207
|
return contextWindow >= LARGE_CONTEXT_THRESHOLD_TOKENS ? LARGE_CONTEXT_PREVIEW_TOKENS : SMALL_CONTEXT_PREVIEW_TOKENS;
|
|
2395
2208
|
}
|
|
2396
|
-
function toShellToolResult(result, options = {}) {
|
|
2209
|
+
function toShellToolResult(result, toolCallId = "", options = {}) {
|
|
2397
2210
|
const output = [{
|
|
2398
2211
|
type: "text",
|
|
2399
2212
|
text: formatShellToolResult(result, options)
|
|
2400
2213
|
}];
|
|
2401
|
-
if (result.status === "exited" && result.
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
type: "text",
|
|
2409
|
-
text: verdict.note
|
|
2410
|
-
});
|
|
2411
|
-
}
|
|
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
|
+
});
|
|
2412
2221
|
return {
|
|
2413
2222
|
output,
|
|
2414
2223
|
isError: false,
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
const view = {
|
|
2423
|
-
kind: "shell",
|
|
2424
|
-
status: result.status,
|
|
2425
|
-
shellId: result.shellId,
|
|
2426
|
-
commandId: result.commandId,
|
|
2427
|
-
runningMs: result.runningMs,
|
|
2428
|
-
idleMs: result.idleMs,
|
|
2429
|
-
chunks: window.chunks,
|
|
2430
|
-
viewTruncated: window.truncated || result.output.truncated
|
|
2431
|
-
};
|
|
2432
|
-
if (result.status === "exited") {
|
|
2433
|
-
view.exitCode = result.exitCode;
|
|
2434
|
-
if (result.audit.length > 0) view.audit = result.audit;
|
|
2435
|
-
if (result.commandMetadata && result.commandMetadata.length > 0) view.commandMeta = result.commandMetadata;
|
|
2436
|
-
}
|
|
2437
|
-
return view;
|
|
2438
|
-
}
|
|
2439
|
-
function tailChunkWindow(chunks, maxChars) {
|
|
2440
|
-
const kept = [];
|
|
2441
|
-
let total = 0;
|
|
2442
|
-
for (let i = chunks.length - 1; i >= 0; i -= 1) {
|
|
2443
|
-
const chunk = chunks[i];
|
|
2444
|
-
if (chunk.text.length === 0) continue;
|
|
2445
|
-
const remaining = maxChars - total;
|
|
2446
|
-
if (remaining <= 0) return {
|
|
2447
|
-
chunks: kept,
|
|
2448
|
-
truncated: true
|
|
2449
|
-
};
|
|
2450
|
-
if (chunk.text.length <= remaining) {
|
|
2451
|
-
kept.unshift({
|
|
2452
|
-
stream: chunk.stream,
|
|
2453
|
-
text: chunk.text
|
|
2454
|
-
});
|
|
2455
|
-
total += chunk.text.length;
|
|
2456
|
-
} else {
|
|
2457
|
-
kept.unshift({
|
|
2458
|
-
stream: chunk.stream,
|
|
2459
|
-
text: chunk.text.slice(chunk.text.length - remaining)
|
|
2460
|
-
});
|
|
2461
|
-
return {
|
|
2462
|
-
chunks: kept,
|
|
2463
|
-
truncated: true
|
|
2464
|
-
};
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
return {
|
|
2468
|
-
chunks: kept,
|
|
2469
|
-
truncated: false
|
|
2470
|
-
};
|
|
2471
|
-
}
|
|
2472
|
-
/**
|
|
2473
|
-
* Boundary decision for a binary final stream: attach as native media when the
|
|
2474
|
-
* magic matches the closed model-media set, the model accepts the type, and
|
|
2475
|
-
* the stream was not truncated; otherwise explain why nothing was attached.
|
|
2476
|
-
*/
|
|
2477
|
-
function binaryStreamVerdict(binary, commandId, model, maxMediaBytes) {
|
|
2478
|
-
const media = sniffModelMediaType(binary.data);
|
|
2479
|
-
const binPath = `/@/commands/${commandId}/stdout.bin`;
|
|
2480
|
-
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.` };
|
|
2481
|
-
if (!media) return { note: `Binary stdout does not match any model-viewable media type; the raw bytes remain readable at ${binPath}.` };
|
|
2482
|
-
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}.` };
|
|
2483
|
-
const cap = maxMediaBytes[media.kind];
|
|
2484
|
-
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.` };
|
|
2485
|
-
const source = {
|
|
2486
|
-
mediaType: media.mediaType,
|
|
2487
|
-
data: bytesToBase64(binary.data)
|
|
2488
|
-
};
|
|
2489
|
-
return {
|
|
2490
|
-
block: media.kind === "video" ? {
|
|
2491
|
-
type: "video",
|
|
2492
|
-
source
|
|
2493
|
-
} : {
|
|
2494
|
-
type: "image",
|
|
2495
|
-
source
|
|
2496
|
-
},
|
|
2497
|
-
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
|
|
2498
2231
|
};
|
|
2499
2232
|
}
|
|
2500
2233
|
function parseShellExecInput(input) {
|
|
@@ -2556,8 +2289,8 @@ function repeatedShellExecResult(environment, agentSessionId, script) {
|
|
|
2556
2289
|
].join("\n")
|
|
2557
2290
|
}],
|
|
2558
2291
|
isError: true,
|
|
2559
|
-
|
|
2560
|
-
kind: "
|
|
2292
|
+
metadata: {
|
|
2293
|
+
kind: "repeated_identical_shell_exec",
|
|
2561
2294
|
script,
|
|
2562
2295
|
count
|
|
2563
2296
|
}
|
|
@@ -2615,11 +2348,10 @@ function boundedPreview(text, budgetTokens) {
|
|
|
2615
2348
|
async function finishShellToolResult(environment, result, ctx) {
|
|
2616
2349
|
const previewBudgetTokens = shellPreviewBudgetTokens(ctx.model.model.contextWindow);
|
|
2617
2350
|
const exposeCommandHandle = shellCommandHandleRequired(result, previewBudgetTokens);
|
|
2618
|
-
const toolResult = toShellToolResult(result, {
|
|
2351
|
+
const toolResult = toShellToolResult(result, ctx.toolCallId, {
|
|
2619
2352
|
includePreview: true,
|
|
2620
2353
|
previewBudgetTokens,
|
|
2621
|
-
exposeCommandHandle
|
|
2622
|
-
model: ctx.model.model
|
|
2354
|
+
exposeCommandHandle
|
|
2623
2355
|
});
|
|
2624
2356
|
if (!exposeCommandHandle) await environment.releaseCommand(result.commandId);
|
|
2625
2357
|
return toolResult;
|
|
@@ -2632,20 +2364,12 @@ function shellCommandHandleRequired(result, budgetTokens) {
|
|
|
2632
2364
|
}
|
|
2633
2365
|
//#endregion
|
|
2634
2366
|
//#region src/server.ts
|
|
2635
|
-
var
|
|
2636
|
-
|
|
2637
|
-
constructor(
|
|
2638
|
-
super(`runCommandLine:
|
|
2639
|
-
this.
|
|
2640
|
-
this.name = "
|
|
2641
|
-
}
|
|
2642
|
-
};
|
|
2643
|
-
var RunCommandLineCommandNotRegisteredError = class extends Error {
|
|
2644
|
-
commandName;
|
|
2645
|
-
constructor(commandName) {
|
|
2646
|
-
super(`runCommandLine: command "${commandName}" is not registered for this session`);
|
|
2647
|
-
this.commandName = commandName;
|
|
2648
|
-
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";
|
|
2649
2373
|
}
|
|
2650
2374
|
};
|
|
2651
2375
|
var RunCommandLineTimeoutError = class extends Error {
|
|
@@ -2665,7 +2389,7 @@ var AgentServer = class {
|
|
|
2665
2389
|
providers;
|
|
2666
2390
|
shellOptions;
|
|
2667
2391
|
sessionOptions;
|
|
2668
|
-
|
|
2392
|
+
prepareSessionShell;
|
|
2669
2393
|
bindings = /* @__PURE__ */ new Set();
|
|
2670
2394
|
sessionOwnership = new SessionOwnershipRegistry();
|
|
2671
2395
|
constructor(options) {
|
|
@@ -2673,7 +2397,7 @@ var AgentServer = class {
|
|
|
2673
2397
|
this.providers = createProviderMap(options.providers);
|
|
2674
2398
|
this.shellOptions = options.shell ?? {};
|
|
2675
2399
|
this.sessionOptions = options.session ?? {};
|
|
2676
|
-
this.
|
|
2400
|
+
this.prepareSessionShell = options.prepareSessionShell ?? null;
|
|
2677
2401
|
}
|
|
2678
2402
|
client() {
|
|
2679
2403
|
const transports = createInProcessTransportPair();
|
|
@@ -2687,7 +2411,7 @@ var AgentServer = class {
|
|
|
2687
2411
|
providers: this.providers,
|
|
2688
2412
|
shell: this.shellOptions,
|
|
2689
2413
|
session: this.sessionOptions,
|
|
2690
|
-
|
|
2414
|
+
prepareSessionShell: this.prepareSessionShell,
|
|
2691
2415
|
sessions: this.sessionOwnership
|
|
2692
2416
|
});
|
|
2693
2417
|
this.bindings.add(binding);
|
|
@@ -2703,18 +2427,17 @@ var AgentServer = class {
|
|
|
2703
2427
|
* Transport-agnostic: callers (e.g. LocalHost command bridge) supply their
|
|
2704
2428
|
* own IPC; AgentServer only knows how to exec against the open session shell.
|
|
2705
2429
|
*/
|
|
2706
|
-
async runCommandLine(
|
|
2707
|
-
const
|
|
2708
|
-
if (
|
|
2709
|
-
|
|
2710
|
-
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);
|
|
2711
2434
|
}
|
|
2712
2435
|
};
|
|
2713
2436
|
/**
|
|
2714
2437
|
* Tracks which transport binding currently owns each client-provided session
|
|
2715
2438
|
* id. Opening a session id that is already owned takes it over: the previous
|
|
2716
|
-
* binding's session is closed (flushing its
|
|
2717
|
-
* 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.
|
|
2718
2441
|
*/
|
|
2719
2442
|
var SessionOwnershipRegistry = class {
|
|
2720
2443
|
holders = /* @__PURE__ */ new Map();
|
|
@@ -2736,17 +2459,14 @@ var AgentTransportBindingImpl = class {
|
|
|
2736
2459
|
providers;
|
|
2737
2460
|
shellOptions;
|
|
2738
2461
|
sessionOptions;
|
|
2739
|
-
|
|
2462
|
+
prepareSessionShell;
|
|
2740
2463
|
sessions;
|
|
2741
2464
|
session = null;
|
|
2742
2465
|
currentAgent = null;
|
|
2743
|
-
|
|
2744
|
-
pendingEnvironmentsByHost = /* @__PURE__ */ new Map();
|
|
2745
|
-
currentCommandRegistry = null;
|
|
2466
|
+
currentEnvironment = null;
|
|
2746
2467
|
currentCwd = null;
|
|
2747
2468
|
currentProviderId = null;
|
|
2748
2469
|
currentSessionId = null;
|
|
2749
|
-
currentCommandNames = /* @__PURE__ */ new Set();
|
|
2750
2470
|
unsubscribeSession = null;
|
|
2751
2471
|
unsubscribeTransport = null;
|
|
2752
2472
|
closed = false;
|
|
@@ -2756,7 +2476,7 @@ var AgentTransportBindingImpl = class {
|
|
|
2756
2476
|
this.providers = options.providers;
|
|
2757
2477
|
this.shellOptions = options.shell ?? {};
|
|
2758
2478
|
this.sessionOptions = options.session ?? {};
|
|
2759
|
-
this.
|
|
2479
|
+
this.prepareSessionShell = options.prepareSessionShell;
|
|
2760
2480
|
this.sessions = options.sessions;
|
|
2761
2481
|
this.unsubscribeTransport = this.transport.onFrame((frame) => {
|
|
2762
2482
|
this.handleFrame(frame);
|
|
@@ -2793,10 +2513,7 @@ var AgentTransportBindingImpl = class {
|
|
|
2793
2513
|
case "send": {
|
|
2794
2514
|
const session = this.sessionFor("send");
|
|
2795
2515
|
if (!session) return;
|
|
2796
|
-
this.observeSessionAction(session.send(frame.content, {
|
|
2797
|
-
id: frame.messageId,
|
|
2798
|
-
metadata: frame.metadata
|
|
2799
|
-
}));
|
|
2516
|
+
this.observeSessionAction(session.send(frame.content, { id: frame.messageId }));
|
|
2800
2517
|
return;
|
|
2801
2518
|
}
|
|
2802
2519
|
case "dequeue_message": {
|
|
@@ -2889,19 +2606,19 @@ var AgentTransportBindingImpl = class {
|
|
|
2889
2606
|
case "retry": {
|
|
2890
2607
|
const session = this.sessionFor("retry");
|
|
2891
2608
|
if (!session || this.rejectIfBusy(session, "retry")) return;
|
|
2892
|
-
this.observeSessionAction(session.retry(
|
|
2609
|
+
this.observeSessionAction(session.retry());
|
|
2893
2610
|
return;
|
|
2894
2611
|
}
|
|
2895
2612
|
case "resume": {
|
|
2896
2613
|
const session = this.sessionFor("resume");
|
|
2897
2614
|
if (!session || this.rejectIfBusy(session, "resume")) return;
|
|
2898
|
-
this.observeSessionAction(session.resume(
|
|
2615
|
+
this.observeSessionAction(session.resume());
|
|
2899
2616
|
return;
|
|
2900
2617
|
}
|
|
2901
2618
|
case "compact": {
|
|
2902
2619
|
const session = this.sessionFor("compact");
|
|
2903
2620
|
if (!session || this.rejectIfBusy(session, "compact")) return;
|
|
2904
|
-
this.observeSessionAction(session.compact(
|
|
2621
|
+
this.observeSessionAction(session.compact());
|
|
2905
2622
|
return;
|
|
2906
2623
|
}
|
|
2907
2624
|
case "abort": {
|
|
@@ -2923,7 +2640,7 @@ var AgentTransportBindingImpl = class {
|
|
|
2923
2640
|
case "sync_transcript": {
|
|
2924
2641
|
const session = this.sessionFor("sync_transcript");
|
|
2925
2642
|
if (!session) return;
|
|
2926
|
-
this.
|
|
2643
|
+
this.sendTranscriptSnapshot(session);
|
|
2927
2644
|
return;
|
|
2928
2645
|
}
|
|
2929
2646
|
case "close":
|
|
@@ -2950,30 +2667,41 @@ var AgentTransportBindingImpl = class {
|
|
|
2950
2667
|
await this.sessions.claim(agentSessionId, this);
|
|
2951
2668
|
this.currentSessionId = agentSessionId;
|
|
2952
2669
|
const initialState = agent.initialState();
|
|
2953
|
-
const
|
|
2670
|
+
const provisionalHost = agent.host({
|
|
2954
2671
|
state: initialState,
|
|
2955
2672
|
cwd: frame.cwd
|
|
2956
|
-
})
|
|
2957
|
-
const
|
|
2958
|
-
const
|
|
2959
|
-
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;
|
|
2960
2678
|
const harnessContext = {
|
|
2961
2679
|
state,
|
|
2962
2680
|
cwd: frame.cwd
|
|
2963
2681
|
};
|
|
2682
|
+
const host = restoring ? agent.host(harnessContext) : provisionalHost;
|
|
2964
2683
|
const commands = agent.commands?.(harnessContext) ?? [];
|
|
2965
2684
|
const commandRegistry = new CommandRegistry();
|
|
2966
2685
|
for (const command of commands) commandRegistry.register(command);
|
|
2967
|
-
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
|
+
});
|
|
2968
2696
|
let sessionRef = null;
|
|
2969
2697
|
const tools = createStandardAgentTools({
|
|
2970
|
-
environment
|
|
2971
|
-
scheduleYield: (
|
|
2698
|
+
environment,
|
|
2699
|
+
scheduleYield: (_ctx, durationMs) => {
|
|
2972
2700
|
if (!sessionRef) throw new Error("AgentServer: session is not ready for yield scheduling");
|
|
2973
|
-
return sessionRef.scheduleYieldWakeup(durationMs
|
|
2701
|
+
return sessionRef.scheduleYieldWakeup(durationMs);
|
|
2974
2702
|
}
|
|
2975
2703
|
});
|
|
2976
|
-
const commandsPrompt = commandRegistry.
|
|
2704
|
+
const commandsPrompt = commandRegistry.renderPrompt();
|
|
2977
2705
|
const runtime = {
|
|
2978
2706
|
harnessName: agent.name,
|
|
2979
2707
|
initialState: () => agent.initialState(),
|
|
@@ -2986,11 +2714,11 @@ var AgentTransportBindingImpl = class {
|
|
|
2986
2714
|
lifecycle: (event) => agent.lifecycle?.(event),
|
|
2987
2715
|
tools: () => tools
|
|
2988
2716
|
};
|
|
2989
|
-
const session = restoring ? AgentSession.
|
|
2717
|
+
const session = restoring ? AgentSession.fromSnapshot({
|
|
2990
2718
|
provider,
|
|
2991
2719
|
runtime,
|
|
2992
|
-
|
|
2993
|
-
...
|
|
2720
|
+
snapshot: {
|
|
2721
|
+
...snapshot,
|
|
2994
2722
|
state
|
|
2995
2723
|
}
|
|
2996
2724
|
}, {
|
|
@@ -3011,14 +2739,13 @@ var AgentTransportBindingImpl = class {
|
|
|
3011
2739
|
sessionRef = session;
|
|
3012
2740
|
this.session = session;
|
|
3013
2741
|
this.currentAgent = agent;
|
|
3014
|
-
this.
|
|
2742
|
+
this.currentEnvironment = environment;
|
|
3015
2743
|
this.currentCwd = frame.cwd;
|
|
3016
2744
|
this.currentProviderId = frame.provider.providerId;
|
|
3017
|
-
this.currentCommandNames = new Set(commandNames);
|
|
3018
2745
|
if (restoring) session.updateModel(null, frame.provider.model);
|
|
3019
2746
|
this.unsubscribeSession = this.session.subscribe((event) => this.handleSessionEvent(event));
|
|
3020
2747
|
this.send({ type: "opened" });
|
|
3021
|
-
this.
|
|
2748
|
+
this.sendTranscriptSnapshot(session);
|
|
3022
2749
|
this.send({
|
|
3023
2750
|
type: "phase",
|
|
3024
2751
|
phase: this.session.phase()
|
|
@@ -3028,10 +2755,10 @@ var AgentTransportBindingImpl = class {
|
|
|
3028
2755
|
queue: this.session.queuedMessages()
|
|
3029
2756
|
});
|
|
3030
2757
|
}
|
|
3031
|
-
|
|
2758
|
+
sendTranscriptSnapshot(session) {
|
|
3032
2759
|
const transcript = session.transcript();
|
|
3033
2760
|
this.send({
|
|
3034
|
-
type: "
|
|
2761
|
+
type: "transcript_snapshot",
|
|
3035
2762
|
blocks: cloneBlocks(transcript.blocks),
|
|
3036
2763
|
revision: transcript.revision
|
|
3037
2764
|
});
|
|
@@ -3065,8 +2792,7 @@ var AgentTransportBindingImpl = class {
|
|
|
3065
2792
|
type: "retry_scheduled",
|
|
3066
2793
|
attempt: event.attempt,
|
|
3067
2794
|
delayMs: event.delayMs,
|
|
3068
|
-
code: event.code
|
|
3069
|
-
diagnostics: event.diagnostics
|
|
2795
|
+
code: event.code
|
|
3070
2796
|
});
|
|
3071
2797
|
return;
|
|
3072
2798
|
case "error":
|
|
@@ -3101,13 +2827,11 @@ var AgentTransportBindingImpl = class {
|
|
|
3101
2827
|
async closeSession() {
|
|
3102
2828
|
const session = this.session;
|
|
3103
2829
|
const agent = this.currentAgent;
|
|
2830
|
+
const environment = this.currentEnvironment;
|
|
3104
2831
|
const cwd = this.currentCwd;
|
|
3105
2832
|
try {
|
|
3106
2833
|
if (session) await session.dispose();
|
|
3107
|
-
|
|
3108
|
-
const environments = new Set(this.environmentsByHost.values());
|
|
3109
|
-
for (const result of pendingEnvironments) if (result.status === "fulfilled") environments.add(result.value);
|
|
3110
|
-
await Promise.all([...environments].map((environment) => environment.disposeAllShells()));
|
|
2834
|
+
if (environment) await environment.disposeAllShells();
|
|
3111
2835
|
if (session && agent && cwd) await agent.dispose?.({
|
|
3112
2836
|
agentSessionId: session.id(),
|
|
3113
2837
|
state: session.state(),
|
|
@@ -3119,12 +2843,9 @@ var AgentTransportBindingImpl = class {
|
|
|
3119
2843
|
this.unsubscribeSession = null;
|
|
3120
2844
|
this.session = null;
|
|
3121
2845
|
this.currentAgent = null;
|
|
3122
|
-
this.
|
|
3123
|
-
this.pendingEnvironmentsByHost.clear();
|
|
3124
|
-
this.currentCommandRegistry = null;
|
|
2846
|
+
this.currentEnvironment = null;
|
|
3125
2847
|
this.currentCwd = null;
|
|
3126
2848
|
this.currentProviderId = null;
|
|
3127
|
-
this.currentCommandNames = /* @__PURE__ */ new Set();
|
|
3128
2849
|
if (this.currentSessionId) {
|
|
3129
2850
|
this.sessions.release(this.currentSessionId, this);
|
|
3130
2851
|
this.currentSessionId = null;
|
|
@@ -3132,17 +2853,17 @@ var AgentTransportBindingImpl = class {
|
|
|
3132
2853
|
}
|
|
3133
2854
|
}
|
|
3134
2855
|
async listConversations(cwd) {
|
|
3135
|
-
const host =
|
|
2856
|
+
const host = this.agent.host({
|
|
3136
2857
|
state: this.agent.initialState(),
|
|
3137
2858
|
cwd
|
|
3138
2859
|
});
|
|
3139
2860
|
const keys = await host.store.list("agent-sessions/");
|
|
3140
2861
|
const conversations = [];
|
|
3141
2862
|
for (const key of keys) {
|
|
3142
|
-
if (!key.endsWith("/
|
|
3143
|
-
const
|
|
3144
|
-
if (!
|
|
3145
|
-
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));
|
|
3146
2867
|
}
|
|
3147
2868
|
conversations.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
3148
2869
|
this.send({
|
|
@@ -3151,122 +2872,39 @@ var AgentTransportBindingImpl = class {
|
|
|
3151
2872
|
});
|
|
3152
2873
|
}
|
|
3153
2874
|
async handleShellWrite(frame) {
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
const result = await (await this.resolveEnvironment({
|
|
3157
|
-
agentSessionId: session.id(),
|
|
3158
|
-
state: session.state(),
|
|
3159
|
-
cwd: this.currentCwd,
|
|
3160
|
-
metadata: frame.metadata ?? null
|
|
3161
|
-
}, { commandId: frame.commandId })).write({
|
|
2875
|
+
if (!this.sessionFor("shell_write") || !this.currentEnvironment || !this.currentCwd) return;
|
|
2876
|
+
const result = await this.currentEnvironment.write({
|
|
3162
2877
|
commandId: frame.commandId,
|
|
3163
2878
|
stdin: frame.stdin
|
|
3164
2879
|
});
|
|
3165
2880
|
this.sendShellWriteResult(frame.commandId, result);
|
|
3166
2881
|
}
|
|
3167
2882
|
/** Backs `AgentServer.runCommandLine` — see its doc comment for the contract. */
|
|
3168
|
-
async runCommandLine(
|
|
3169
|
-
const environment = this.
|
|
2883
|
+
async runCommandLine(name, args, opts) {
|
|
2884
|
+
const environment = this.currentEnvironment;
|
|
3170
2885
|
const agentSessionId = this.currentSessionId;
|
|
3171
2886
|
if (!environment || !agentSessionId) throw new Error("runCommandLine: session has no active shell environment");
|
|
3172
|
-
if (!this.currentCommandNames.has(name)) throw new RunCommandLineCommandNotRegisteredError(name);
|
|
3173
2887
|
const words = [name, ...args].map(shellQuote).join(" ");
|
|
3174
2888
|
let script = words;
|
|
3175
2889
|
if (opts.stdin.length > 0) {
|
|
3176
2890
|
const delimiter = heredocDelimiter(opts.stdin);
|
|
3177
|
-
script = `${words} <<'${delimiter}'\n${opts.stdin
|
|
2891
|
+
script = `${words} <<'${delimiter}'\n${opts.stdin}\n${delimiter}`;
|
|
3178
2892
|
}
|
|
2893
|
+
const cdScript = `cd ${shellQuote(opts.cwd)} && ${script}`;
|
|
3179
2894
|
const result = await environment.exec({
|
|
3180
2895
|
agentSessionId,
|
|
3181
|
-
script,
|
|
2896
|
+
script: cdScript,
|
|
3182
2897
|
timeoutMs: MAX_TIMEOUT_MS,
|
|
3183
|
-
signal: opts.signal
|
|
3184
|
-
ephemeral: true,
|
|
3185
|
-
cwd: opts.cwd
|
|
2898
|
+
signal: opts.signal
|
|
3186
2899
|
});
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
stderr: `${result.stderr.delta}${truncationNote}`
|
|
3196
|
-
};
|
|
3197
|
-
}
|
|
3198
|
-
return {
|
|
3199
|
-
exitCode: result.exitCode,
|
|
3200
|
-
stdout: result.stdout.delta,
|
|
3201
|
-
stderr: result.stderr.delta
|
|
3202
|
-
};
|
|
3203
|
-
}
|
|
3204
|
-
if (result.status === "aborted") throw new Error(`runCommandLine: call for "${name}" was cancelled before it completed`);
|
|
3205
|
-
const aborted = await environment.abort({ commandId: result.commandId });
|
|
3206
|
-
throw new RunCommandLineTimeoutError(result.commandId, aborted.status === "aborted" ? aborted.stdout.delta : "", aborted.status === "aborted" ? aborted.stderr.delta : "");
|
|
3207
|
-
} finally {
|
|
3208
|
-
await environment.disposeShell(result.shellId).catch(() => {});
|
|
3209
|
-
}
|
|
3210
|
-
}
|
|
3211
|
-
hasShell(shellId) {
|
|
3212
|
-
return this.environmentForShell(shellId) !== null;
|
|
3213
|
-
}
|
|
3214
|
-
async resolveEnvironment(ctx, handle) {
|
|
3215
|
-
const agent = this.currentAgent;
|
|
3216
|
-
const commandRegistry = this.currentCommandRegistry;
|
|
3217
|
-
if (!agent || !commandRegistry) throw new Error("Shell environment is not available before session open");
|
|
3218
|
-
const host = await agent.host({
|
|
3219
|
-
agentSessionId: ctx.agentSessionId,
|
|
3220
|
-
state: ctx.state,
|
|
3221
|
-
cwd: ctx.cwd,
|
|
3222
|
-
metadata: ctx.metadata
|
|
3223
|
-
});
|
|
3224
|
-
const environment = await this.environmentForHost(host, commandRegistry);
|
|
3225
|
-
const owner = handle.shellId ? this.environmentForShell(handle.shellId) : handle.commandId ? this.environmentForCommand(handle.commandId) : null;
|
|
3226
|
-
if (owner && owner !== environment) {
|
|
3227
|
-
const id = handle.shellId ?? handle.commandId;
|
|
3228
|
-
throw new Error(`Shell handle "${id}" belongs to a different Host`);
|
|
3229
|
-
}
|
|
3230
|
-
return environment;
|
|
3231
|
-
}
|
|
3232
|
-
async environmentForHost(host, commands) {
|
|
3233
|
-
const existing = this.environmentsByHost.get(host);
|
|
3234
|
-
if (existing) return existing;
|
|
3235
|
-
const pending = this.pendingEnvironmentsByHost.get(host);
|
|
3236
|
-
if (pending) return pending;
|
|
3237
|
-
const creation = this.createEnvironment(host, commands);
|
|
3238
|
-
this.pendingEnvironmentsByHost.set(host, creation);
|
|
3239
|
-
try {
|
|
3240
|
-
const environment = await creation;
|
|
3241
|
-
this.environmentsByHost.set(host, environment);
|
|
3242
|
-
return environment;
|
|
3243
|
-
} finally {
|
|
3244
|
-
this.pendingEnvironmentsByHost.delete(host);
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
async createEnvironment(host, commands) {
|
|
3248
|
-
const agentSessionId = this.currentSessionId;
|
|
3249
|
-
if (!agentSessionId) throw new Error("Shell environment is not available before session open");
|
|
3250
|
-
return new BashEnvironment({
|
|
3251
|
-
...this.prepareShell ? await this.prepareShell({
|
|
3252
|
-
agentSessionId,
|
|
3253
|
-
host,
|
|
3254
|
-
commandNames: commands.list().map((command) => command.name),
|
|
3255
|
-
shell: this.shellOptions
|
|
3256
|
-
}) : this.shellOptions,
|
|
3257
|
-
host,
|
|
3258
|
-
commands
|
|
3259
|
-
});
|
|
3260
|
-
}
|
|
3261
|
-
environmentForShell(shellId) {
|
|
3262
|
-
const matches = [...this.environmentsByHost.values()].filter((environment) => environment.getShell(shellId));
|
|
3263
|
-
if (matches.length > 1) throw new Error(`Shell id "${shellId}" is not unique in this session`);
|
|
3264
|
-
return matches[0] ?? null;
|
|
3265
|
-
}
|
|
3266
|
-
environmentForCommand(commandId) {
|
|
3267
|
-
const matches = [...this.environmentsByHost.values()].filter((environment) => environment.hasCommand(commandId));
|
|
3268
|
-
if (matches.length > 1) throw new Error(`Command id "${commandId}" is not unique in this session`);
|
|
3269
|
-
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 : "");
|
|
3270
2908
|
}
|
|
3271
2909
|
sessionFor(command) {
|
|
3272
2910
|
if (!this.session) {
|
|
@@ -3301,7 +2939,7 @@ var AgentTransportBindingImpl = class {
|
|
|
3301
2939
|
type: "shell_output",
|
|
3302
2940
|
shellId: shell.shellId,
|
|
3303
2941
|
commandId: shell.commandId,
|
|
3304
|
-
|
|
2942
|
+
snapshot: shell.snapshot
|
|
3305
2943
|
});
|
|
3306
2944
|
const audit = progressToAudit(progress);
|
|
3307
2945
|
if (audit.length > 0) this.send({
|
|
@@ -3315,7 +2953,7 @@ var AgentTransportBindingImpl = class {
|
|
|
3315
2953
|
type: "shell_output",
|
|
3316
2954
|
shellId: shell.shellId,
|
|
3317
2955
|
commandId: shell.commandId,
|
|
3318
|
-
|
|
2956
|
+
snapshot: shell.snapshot
|
|
3319
2957
|
});
|
|
3320
2958
|
const audit = progressToAudit(progress);
|
|
3321
2959
|
if (audit.length > 0) this.send({
|
|
@@ -3337,12 +2975,13 @@ var AgentTransportBindingImpl = class {
|
|
|
3337
2975
|
sendError(error) {
|
|
3338
2976
|
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
3339
2977
|
const code = errorCode(error);
|
|
3340
|
-
|
|
3341
|
-
this.send({
|
|
2978
|
+
this.send(code ? {
|
|
3342
2979
|
type: "error",
|
|
3343
2980
|
message: normalized.message,
|
|
3344
|
-
|
|
3345
|
-
|
|
2981
|
+
code
|
|
2982
|
+
} : {
|
|
2983
|
+
type: "error",
|
|
2984
|
+
message: normalized.message
|
|
3346
2985
|
});
|
|
3347
2986
|
}
|
|
3348
2987
|
};
|
|
@@ -3353,15 +2992,15 @@ var HostAgentSessionStore = class {
|
|
|
3353
2992
|
this.store = store;
|
|
3354
2993
|
this.agentSessionId = agentSessionId;
|
|
3355
2994
|
}
|
|
3356
|
-
|
|
3357
|
-
return this.store.writeJson(`agent-sessions/${this.agentSessionId}/
|
|
2995
|
+
saveSnapshot(snapshot) {
|
|
2996
|
+
return this.store.writeJson(`agent-sessions/${this.agentSessionId}/snapshot.json`, snapshot);
|
|
3358
2997
|
}
|
|
3359
|
-
|
|
3360
|
-
return this.store.readJson(`agent-sessions/${this.agentSessionId}/
|
|
2998
|
+
loadSnapshot() {
|
|
2999
|
+
return this.store.readJson(`agent-sessions/${this.agentSessionId}/snapshot.json`);
|
|
3361
3000
|
}
|
|
3362
3001
|
};
|
|
3363
|
-
function summarizeConversation(id,
|
|
3364
|
-
const blocks =
|
|
3002
|
+
function summarizeConversation(id, snapshot) {
|
|
3003
|
+
const blocks = snapshot.transcript.blocks;
|
|
3365
3004
|
const first = blocks[0];
|
|
3366
3005
|
const last = blocks[blocks.length - 1];
|
|
3367
3006
|
return {
|
|
@@ -3395,14 +3034,14 @@ function progressToShellOutput(progress) {
|
|
|
3395
3034
|
if (!isRecord(progress.stdout) || !isRecord(progress.stderr)) return null;
|
|
3396
3035
|
const stdout = progress.stdout;
|
|
3397
3036
|
const stderr = progress.stderr;
|
|
3398
|
-
if (!
|
|
3037
|
+
if (!isStreamArtifact(stdout) || !isStreamArtifact(stderr) || typeof progress.runningMs !== "number" || typeof progress.idleMs !== "number") return null;
|
|
3399
3038
|
return {
|
|
3400
3039
|
shellId: progress.shellId,
|
|
3401
3040
|
commandId: progress.commandId,
|
|
3402
|
-
|
|
3041
|
+
snapshot: progress
|
|
3403
3042
|
};
|
|
3404
3043
|
}
|
|
3405
|
-
function
|
|
3044
|
+
function isStreamArtifact(value) {
|
|
3406
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";
|
|
3407
3046
|
}
|
|
3408
3047
|
function progressToAudit(progress) {
|
|
@@ -3423,10 +3062,6 @@ function errorCode(error) {
|
|
|
3423
3062
|
if (!isRecord(error) || typeof error.code !== "string") return void 0;
|
|
3424
3063
|
return error.code;
|
|
3425
3064
|
}
|
|
3426
|
-
function errorDiagnostics(error) {
|
|
3427
|
-
if (!(error instanceof ProviderStreamError)) return void 0;
|
|
3428
|
-
return error.diagnostics;
|
|
3429
|
-
}
|
|
3430
3065
|
function createProviderMap(providers) {
|
|
3431
3066
|
const map = /* @__PURE__ */ new Map();
|
|
3432
3067
|
for (const provider of providers) {
|
|
@@ -3436,4 +3071,4 @@ function createProviderMap(providers) {
|
|
|
3436
3071
|
return map;
|
|
3437
3072
|
}
|
|
3438
3073
|
//#endregion
|
|
3439
|
-
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 };
|