@leanandmean/coding-agent 0.74.1-scramjet.72 → 0.74.1-scramjet.74
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 +8 -1
- package/dist/core/agent-session.d.ts +19 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +293 -68
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +18 -5
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +9 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +7 -3
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +155 -34
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +35 -16
- package/dist/modes/print-mode.js.map +1 -1
- package/docs/compaction.md +6 -0
- package/docs/custom-provider.md +3 -1
- package/docs/extensions.md +13 -1
- package/docs/keybindings.md +1 -1
- package/docs/sdk.md +27 -0
- package/docs/settings.md +2 -0
- package/docs/terminal-setup.md +8 -4
- package/docs/terminal-verification.md +48 -0
- package/docs/tui.md +8 -0
- package/package.json +4 -4
|
@@ -86,6 +86,15 @@ function rejectDeferred(deferred, error) {
|
|
|
86
86
|
function settleDeferred(deferred, error) {
|
|
87
87
|
return error ? rejectDeferred(deferred, error) : resolveDeferred(deferred, undefined);
|
|
88
88
|
}
|
|
89
|
+
function createRetryChain() {
|
|
90
|
+
return {
|
|
91
|
+
...createDeferred(),
|
|
92
|
+
result: createDeferred(),
|
|
93
|
+
executions: 0,
|
|
94
|
+
terminal: false,
|
|
95
|
+
cancelled: false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
89
98
|
function validRecordedCost(value) {
|
|
90
99
|
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
91
100
|
}
|
|
@@ -320,6 +329,8 @@ export class AgentSession {
|
|
|
320
329
|
this._agentEventQueue = processing;
|
|
321
330
|
if (harnessAck)
|
|
322
331
|
harnessAck.persistenceTail = processing;
|
|
332
|
+
if (runSettlement && this.agent.getRunExecution())
|
|
333
|
+
runSettlement.processingTail = processing;
|
|
323
334
|
// Keep queue alive if an event handler fails
|
|
324
335
|
processing.catch(() => { });
|
|
325
336
|
// SCRAMJET-DIVERGENCE: turn_end tool/prompt changes must settle before Agent snapshots
|
|
@@ -396,11 +407,44 @@ export class AgentSession {
|
|
|
396
407
|
if (event.type === "agent_start") {
|
|
397
408
|
// A retry continuation's run adopts the chain its originating prompt awaits; the chain stays
|
|
398
409
|
// bound to the retry phase until this start consumes it, however late Agent emits it.
|
|
399
|
-
const chain = this.
|
|
410
|
+
const chain = this._pendingAgentRunStart?.chain ??
|
|
411
|
+
(this._retry?.phase === "continuing" ? this._retry.chain : createRetryChain());
|
|
400
412
|
this._unsettledRetryChains.add(chain);
|
|
401
|
-
const run = {
|
|
413
|
+
const run = {
|
|
414
|
+
...createDeferred(),
|
|
415
|
+
chain,
|
|
416
|
+
execution: this._pendingAgentRunStart?.execution ?? createDeferred(),
|
|
417
|
+
processingTail: Promise.resolve(),
|
|
418
|
+
};
|
|
419
|
+
if (!this._pendingAgentRunStart) {
|
|
420
|
+
chain.executions++;
|
|
421
|
+
void this.agent
|
|
422
|
+
.getRunExecution()
|
|
423
|
+
.catch((error) => {
|
|
424
|
+
chain.error ??= error instanceof Error ? error : new Error(String(error));
|
|
425
|
+
})
|
|
426
|
+
.then(async () => {
|
|
427
|
+
resolveDeferred(run.execution, undefined);
|
|
428
|
+
try {
|
|
429
|
+
await run.processingTail;
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
chain.error ??= error instanceof Error ? error : new Error(String(error));
|
|
433
|
+
}
|
|
434
|
+
finally {
|
|
435
|
+
chain.executions--;
|
|
436
|
+
this._trySettleRetryChain(chain);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
402
440
|
this._activeAgentRunSettlement = run;
|
|
441
|
+
void run.execution.promise.then(() => {
|
|
442
|
+
if (this._activeAgentRunSettlement === run)
|
|
443
|
+
this._activeAgentRunSettlement = undefined;
|
|
444
|
+
});
|
|
403
445
|
this._unsettledAgentRuns.add(run);
|
|
446
|
+
if (chain.cancelled)
|
|
447
|
+
this.agent.abort();
|
|
404
448
|
const reservation = this._pendingAgentRunStart;
|
|
405
449
|
if (reservation) {
|
|
406
450
|
this._pendingAgentRunStart = undefined;
|
|
@@ -410,9 +454,9 @@ export class AgentSession {
|
|
|
410
454
|
return run;
|
|
411
455
|
}
|
|
412
456
|
const run = this._activeAgentRunSettlement;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
457
|
+
// An awaited end listener can still fail and emit another end before execution unwinds.
|
|
458
|
+
if (event.type === "agent_end" && run)
|
|
459
|
+
run.chain.terminal = false;
|
|
416
460
|
return run;
|
|
417
461
|
}
|
|
418
462
|
_settleAgentRun(run, error) {
|
|
@@ -420,17 +464,50 @@ export class AgentSession {
|
|
|
420
464
|
this._unsettledAgentRuns.delete(run);
|
|
421
465
|
}
|
|
422
466
|
_settleRetryChain(chain, error) {
|
|
423
|
-
|
|
467
|
+
chain.terminal = true;
|
|
468
|
+
if (error)
|
|
469
|
+
chain.error = error;
|
|
470
|
+
this._trySettleRetryChain(chain);
|
|
471
|
+
}
|
|
472
|
+
_trySettleRetryChain(chain) {
|
|
473
|
+
if (!chain.terminal || chain.executions > 0 || chain.handoff)
|
|
474
|
+
return;
|
|
475
|
+
if (chain.error || chain.resultError) {
|
|
476
|
+
rejectDeferred(chain.result, chain.error ?? chain.resultError);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
const assistantMessage = chain.assistantMessage;
|
|
480
|
+
const result = chain.cancelled || assistantMessage?.stopReason === "aborted"
|
|
481
|
+
? { status: "cancelled", assistantMessage }
|
|
482
|
+
: chain.errorMessage || !assistantMessage || assistantMessage.stopReason === "error"
|
|
483
|
+
? {
|
|
484
|
+
status: "failed",
|
|
485
|
+
assistantMessage,
|
|
486
|
+
errorMessage: chain.errorMessage ??
|
|
487
|
+
assistantMessage?.errorMessage ??
|
|
488
|
+
"Run ended without a provider assistant result.",
|
|
489
|
+
}
|
|
490
|
+
: { status: "completed", assistantMessage };
|
|
491
|
+
resolveDeferred(chain.result, result);
|
|
492
|
+
}
|
|
493
|
+
if (settleDeferred(chain, chain.error))
|
|
424
494
|
this._unsettledRetryChains.delete(chain);
|
|
425
495
|
}
|
|
426
496
|
_completeAgentRun(run, error) {
|
|
427
497
|
this._settleAgentRun(run, error);
|
|
428
498
|
this._settleRetryChain(run.chain, error);
|
|
429
499
|
}
|
|
430
|
-
_reserveAgentRunStart() {
|
|
500
|
+
_reserveAgentRunStart(chain = createRetryChain()) {
|
|
501
|
+
if (this._disposed)
|
|
502
|
+
throw new Error("AgentSession disposed before run start.");
|
|
431
503
|
if (this._pendingAgentRunStart)
|
|
432
504
|
throw new Error("An Agent prompt is already waiting to start.");
|
|
433
|
-
|
|
505
|
+
this._unsettledRetryChains.add(chain);
|
|
506
|
+
const reservation = {
|
|
507
|
+
...createDeferred(),
|
|
508
|
+
chain,
|
|
509
|
+
execution: createDeferred(),
|
|
510
|
+
};
|
|
434
511
|
this._pendingAgentRunStart = reservation;
|
|
435
512
|
return reservation;
|
|
436
513
|
}
|
|
@@ -438,6 +515,7 @@ export class AgentSession {
|
|
|
438
515
|
if (this._pendingAgentRunStart === reservation)
|
|
439
516
|
this._pendingAgentRunStart = undefined;
|
|
440
517
|
rejectDeferred(reservation, error);
|
|
518
|
+
this._settleRetryChain(reservation.chain, error);
|
|
441
519
|
}
|
|
442
520
|
// SCRAMJET-DIVERGENCE: harness-tool persisted settlement (#341). Synchronous ownership keeps queued
|
|
443
521
|
// invocation events attributed after early failures; public settlement awaits execution and the final persistence tail.
|
|
@@ -525,7 +603,7 @@ export class AgentSession {
|
|
|
525
603
|
}
|
|
526
604
|
}
|
|
527
605
|
// Emit to extensions first
|
|
528
|
-
await this._emitExtensionEvent(event);
|
|
606
|
+
await this._emitExtensionEvent(event, fromHarnessInvocation ? undefined : run?.chain.result.promise);
|
|
529
607
|
if (event.type === "message_end" && event.message.role === "toolResult" && toolCost?.cost !== undefined) {
|
|
530
608
|
event.message.cost = toolCost.cost;
|
|
531
609
|
}
|
|
@@ -617,6 +695,14 @@ export class AgentSession {
|
|
|
617
695
|
}
|
|
618
696
|
const msg = run.persistedAssistantSnapshot;
|
|
619
697
|
run.persistedAssistantSnapshot = undefined;
|
|
698
|
+
if (msg && msg.origin !== "harness")
|
|
699
|
+
run.chain.assistantMessage = msg;
|
|
700
|
+
if (run.chain.cancelled) {
|
|
701
|
+
if (this._retry)
|
|
702
|
+
this._finishCancelledRetry("cancelled_during_continuation");
|
|
703
|
+
this._completeAgentRun(run);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
620
706
|
if (!msg) {
|
|
621
707
|
this._releaseRetryForFailedRun(new Error("Retry continuation ended without an assistant message."));
|
|
622
708
|
this._completeAgentRun(run);
|
|
@@ -628,6 +714,8 @@ export class AgentSession {
|
|
|
628
714
|
return;
|
|
629
715
|
}
|
|
630
716
|
if (msg.stopReason !== "error") {
|
|
717
|
+
if (msg.stopReason === "aborted")
|
|
718
|
+
run.chain.cancelled = true;
|
|
631
719
|
if (this._retry) {
|
|
632
720
|
const attempt = this._runRetryCount;
|
|
633
721
|
this._finishRetry({
|
|
@@ -637,11 +725,17 @@ export class AgentSession {
|
|
|
637
725
|
cumulativeErrors: this._bounded(attempt),
|
|
638
726
|
}, { type: "auto_retry_end", success: true, attempt });
|
|
639
727
|
}
|
|
728
|
+
await this._checkCompaction(msg, "settlement", run);
|
|
640
729
|
this._completeAgentRun(run);
|
|
641
|
-
await this._checkCompaction(msg);
|
|
642
730
|
return;
|
|
643
731
|
}
|
|
644
732
|
const classification = this._classifyRetry(msg);
|
|
733
|
+
if (msg.origin === "harness" && classification.kind !== "context_overflow") {
|
|
734
|
+
this._releaseRetryForFailedRun(new Error(msg.errorMessage ?? "Agent execution failed."));
|
|
735
|
+
run.chain.resultError = new Error(msg.errorMessage ?? "Agent execution failed.");
|
|
736
|
+
this._completeAgentRun(run);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
645
739
|
if (classification.kind === "context_overflow") {
|
|
646
740
|
this._finishRetryWithoutSuccess({
|
|
647
741
|
schemaVersion: 1,
|
|
@@ -649,8 +743,8 @@ export class AgentSession {
|
|
|
649
743
|
reason: "context_overflow_compaction",
|
|
650
744
|
evidence: "context_overflow",
|
|
651
745
|
}, msg);
|
|
746
|
+
await this._checkCompaction(msg, "settlement", run);
|
|
652
747
|
this._completeAgentRun(run);
|
|
653
|
-
await this._checkCompaction(msg);
|
|
654
748
|
return;
|
|
655
749
|
}
|
|
656
750
|
const settings = this.settingsManager.getRetrySettings();
|
|
@@ -661,12 +755,12 @@ export class AgentSession {
|
|
|
661
755
|
reason: "retry_disabled",
|
|
662
756
|
evidence: classification.evidence,
|
|
663
757
|
}, msg);
|
|
758
|
+
await this._checkCompaction(msg, "settlement", run);
|
|
664
759
|
this._completeAgentRun(run);
|
|
665
|
-
await this._checkCompaction(msg);
|
|
666
760
|
return;
|
|
667
761
|
}
|
|
668
762
|
if (classification.kind === "retry") {
|
|
669
|
-
const didRetry =
|
|
763
|
+
const didRetry = this._handleRetryableError(msg, classification.evidence, run);
|
|
670
764
|
if (didRetry)
|
|
671
765
|
return;
|
|
672
766
|
}
|
|
@@ -674,8 +768,8 @@ export class AgentSession {
|
|
|
674
768
|
const { kind: _kind, ...cause } = classification;
|
|
675
769
|
this._finishRetryWithoutSuccess({ schemaVersion: 1, outcome: "not_attempted", ...cause }, msg);
|
|
676
770
|
}
|
|
771
|
+
await this._checkCompaction(msg, "settlement", run);
|
|
677
772
|
this._completeAgentRun(run);
|
|
678
|
-
await this._checkCompaction(msg);
|
|
679
773
|
}
|
|
680
774
|
}
|
|
681
775
|
/** Extract text content from a message */
|
|
@@ -714,13 +808,19 @@ export class AgentSession {
|
|
|
714
808
|
Object.assign(targetRecord, replacement);
|
|
715
809
|
}
|
|
716
810
|
/** Emit extension events based on agent events */
|
|
717
|
-
async _emitExtensionEvent(event) {
|
|
811
|
+
async _emitExtensionEvent(event, settlement) {
|
|
812
|
+
// SCRAMJET-DIVERGENCE: message provenance is independent of Session persistence acknowledgements (#611).
|
|
813
|
+
if ((event.type === "message_start" || event.type === "message_update" || event.type === "message_end") &&
|
|
814
|
+
event.message.role === "assistant" &&
|
|
815
|
+
event.message.origin === "harness") {
|
|
816
|
+
settlement = undefined;
|
|
817
|
+
}
|
|
718
818
|
if (event.type === "agent_start") {
|
|
719
819
|
this._turnIndex = 0;
|
|
720
|
-
await this._extensionRunner.emit({ type: "agent_start" });
|
|
820
|
+
await this._extensionRunner.emit({ type: "agent_start" }, settlement);
|
|
721
821
|
}
|
|
722
822
|
else if (event.type === "agent_end") {
|
|
723
|
-
await this._extensionRunner.emit({ type: "agent_end", messages: event.messages });
|
|
823
|
+
await this._extensionRunner.emit({ type: "agent_end", messages: event.messages }, settlement);
|
|
724
824
|
}
|
|
725
825
|
else if (event.type === "turn_start") {
|
|
726
826
|
const extensionEvent = {
|
|
@@ -728,7 +828,7 @@ export class AgentSession {
|
|
|
728
828
|
turnIndex: this._turnIndex,
|
|
729
829
|
timestamp: Date.now(),
|
|
730
830
|
};
|
|
731
|
-
await this._extensionRunner.emit(extensionEvent);
|
|
831
|
+
await this._extensionRunner.emit(extensionEvent, settlement);
|
|
732
832
|
}
|
|
733
833
|
else if (event.type === "turn_end") {
|
|
734
834
|
const extensionEvent = {
|
|
@@ -737,7 +837,7 @@ export class AgentSession {
|
|
|
737
837
|
message: event.message,
|
|
738
838
|
toolResults: event.toolResults,
|
|
739
839
|
};
|
|
740
|
-
await this._extensionRunner.emit(extensionEvent);
|
|
840
|
+
await this._extensionRunner.emit(extensionEvent, settlement);
|
|
741
841
|
this._turnIndex++;
|
|
742
842
|
}
|
|
743
843
|
else if (event.type === "message_start") {
|
|
@@ -745,7 +845,7 @@ export class AgentSession {
|
|
|
745
845
|
type: "message_start",
|
|
746
846
|
message: event.message,
|
|
747
847
|
};
|
|
748
|
-
await this._extensionRunner.emit(extensionEvent);
|
|
848
|
+
await this._extensionRunner.emit(extensionEvent, settlement);
|
|
749
849
|
}
|
|
750
850
|
else if (event.type === "message_update") {
|
|
751
851
|
const extensionEvent = {
|
|
@@ -753,14 +853,14 @@ export class AgentSession {
|
|
|
753
853
|
message: event.message,
|
|
754
854
|
assistantMessageEvent: event.assistantMessageEvent,
|
|
755
855
|
};
|
|
756
|
-
await this._extensionRunner.emit(extensionEvent);
|
|
856
|
+
await this._extensionRunner.emit(extensionEvent, settlement);
|
|
757
857
|
}
|
|
758
858
|
else if (event.type === "message_end") {
|
|
759
859
|
const extensionEvent = {
|
|
760
860
|
type: "message_end",
|
|
761
861
|
message: event.message,
|
|
762
862
|
};
|
|
763
|
-
const replacement = await this._extensionRunner.emitMessageEnd(extensionEvent);
|
|
863
|
+
const replacement = await this._extensionRunner.emitMessageEnd(extensionEvent, settlement);
|
|
764
864
|
if (replacement) {
|
|
765
865
|
this._replaceMessageInPlace(event.message, replacement);
|
|
766
866
|
}
|
|
@@ -772,7 +872,7 @@ export class AgentSession {
|
|
|
772
872
|
toolName: event.toolName,
|
|
773
873
|
args: event.args,
|
|
774
874
|
};
|
|
775
|
-
await this._extensionRunner.emit(extensionEvent);
|
|
875
|
+
await this._extensionRunner.emit(extensionEvent, settlement);
|
|
776
876
|
}
|
|
777
877
|
else if (event.type === "tool_execution_update") {
|
|
778
878
|
const extensionEvent = {
|
|
@@ -782,7 +882,7 @@ export class AgentSession {
|
|
|
782
882
|
args: event.args,
|
|
783
883
|
partialResult: event.partialResult,
|
|
784
884
|
};
|
|
785
|
-
await this._extensionRunner.emit(extensionEvent);
|
|
885
|
+
await this._extensionRunner.emit(extensionEvent, settlement);
|
|
786
886
|
}
|
|
787
887
|
else if (event.type === "tool_execution_end") {
|
|
788
888
|
const extensionEvent = {
|
|
@@ -792,7 +892,7 @@ export class AgentSession {
|
|
|
792
892
|
result: event.result,
|
|
793
893
|
isError: event.isError,
|
|
794
894
|
};
|
|
795
|
-
await this._extensionRunner.emit(extensionEvent);
|
|
895
|
+
await this._extensionRunner.emit(extensionEvent, settlement);
|
|
796
896
|
}
|
|
797
897
|
}
|
|
798
898
|
/**
|
|
@@ -844,6 +944,13 @@ export class AgentSession {
|
|
|
844
944
|
this._pendingToolCosts.clear();
|
|
845
945
|
this._resetOutputThroughput();
|
|
846
946
|
const retryDisposeError = new Error("AgentSession disposed before retry settlement completed.");
|
|
947
|
+
for (const chain of this._unsettledRetryChains) {
|
|
948
|
+
if (chain.handoff)
|
|
949
|
+
clearTimeout(chain.handoff);
|
|
950
|
+
chain.handoff = undefined;
|
|
951
|
+
rejectDeferred(chain.result, retryDisposeError);
|
|
952
|
+
}
|
|
953
|
+
this._autoCompactionAbortController?.abort();
|
|
847
954
|
const unsettledRuns = [...this._unsettledAgentRuns];
|
|
848
955
|
const retry = this._retry;
|
|
849
956
|
const unclassifiedError = unsettledRuns.find((run) => run.persistedAssistantSnapshot?.stopReason === "error")?.persistedAssistantSnapshot;
|
|
@@ -896,8 +1003,10 @@ export class AgentSession {
|
|
|
896
1003
|
retryChains.add(pendingRetryContinuation);
|
|
897
1004
|
for (const run of unsettledRuns)
|
|
898
1005
|
this._settleAgentRun(run, retryDisposeError);
|
|
899
|
-
for (const chain of retryChains)
|
|
900
|
-
|
|
1006
|
+
for (const chain of retryChains) {
|
|
1007
|
+
settleDeferred(chain, retryDisposeError);
|
|
1008
|
+
this._unsettledRetryChains.delete(chain);
|
|
1009
|
+
}
|
|
901
1010
|
this._disconnectFromAgent();
|
|
902
1011
|
this._eventListeners = [];
|
|
903
1012
|
cleanupSessionResources(this.sessionId);
|
|
@@ -1387,7 +1496,7 @@ export class AgentSession {
|
|
|
1387
1496
|
}
|
|
1388
1497
|
acceptInput?.();
|
|
1389
1498
|
preflightResult?.(true);
|
|
1390
|
-
await this._runAgentPrompt(messages);
|
|
1499
|
+
await this._runAgentPrompt(messages, options?.onRunSettlement);
|
|
1391
1500
|
}
|
|
1392
1501
|
/**
|
|
1393
1502
|
* Try to execute an extension command. Returns true if command was found and executed.
|
|
@@ -2003,6 +2112,7 @@ export class AgentSession {
|
|
|
2003
2112
|
* Cancel in-progress compaction (manual or auto).
|
|
2004
2113
|
*/
|
|
2005
2114
|
abortCompaction() {
|
|
2115
|
+
this._cancelRunSettlements();
|
|
2006
2116
|
this._compactionAbortController?.abort();
|
|
2007
2117
|
this._autoCompactionAbortController?.abort();
|
|
2008
2118
|
}
|
|
@@ -2022,8 +2132,8 @@ export class AgentSession {
|
|
|
2022
2132
|
*
|
|
2023
2133
|
* @param assistantMessage The assistant message to check
|
|
2024
2134
|
*/
|
|
2025
|
-
async _checkCompaction(assistantMessage, trigger = "settlement") {
|
|
2026
|
-
if (this._manualCompactionDrain)
|
|
2135
|
+
async _checkCompaction(assistantMessage, trigger = "settlement", run) {
|
|
2136
|
+
if (this._manualCompactionDrain || run?.chain.cancelled)
|
|
2027
2137
|
return;
|
|
2028
2138
|
const settings = this.settingsManager.getCompactionSettings();
|
|
2029
2139
|
if (!settings.enabled)
|
|
@@ -2055,7 +2165,7 @@ export class AgentSession {
|
|
|
2055
2165
|
const window = this.model?.contextWindow ?? 0;
|
|
2056
2166
|
if (shouldCompact(tokens, window, settings) ||
|
|
2057
2167
|
(window > settings.reserveTokens && tokens > (this.model?.maxInputTokens ?? Infinity)))
|
|
2058
|
-
await this._runAutoCompaction("threshold", false);
|
|
2168
|
+
await this._runAutoCompaction("threshold", false, undefined, false);
|
|
2059
2169
|
return;
|
|
2060
2170
|
}
|
|
2061
2171
|
// SCRAMJET-DIVERGENCE: Total context and independent input limits are separate constraints.
|
|
@@ -2094,7 +2204,7 @@ export class AgentSession {
|
|
|
2094
2204
|
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
2095
2205
|
this.agent.state.messages = messages.slice(0, -1);
|
|
2096
2206
|
}
|
|
2097
|
-
await this._runAutoCompaction("overflow", true);
|
|
2207
|
+
await this._runAutoCompaction("overflow", true, run);
|
|
2098
2208
|
return;
|
|
2099
2209
|
}
|
|
2100
2210
|
// Case 2: Threshold - context is getting large
|
|
@@ -2122,14 +2232,19 @@ export class AgentSession {
|
|
|
2122
2232
|
}
|
|
2123
2233
|
if (shouldCompact(contextTokens, contextWindow, settings) ||
|
|
2124
2234
|
(contextWindow > settings.reserveTokens && contextTokens > (this.model?.maxInputTokens ?? Infinity))) {
|
|
2125
|
-
await this._runAutoCompaction("threshold", false);
|
|
2235
|
+
await this._runAutoCompaction("threshold", false, run);
|
|
2126
2236
|
}
|
|
2127
2237
|
}
|
|
2128
2238
|
/**
|
|
2129
2239
|
* Internal: Run auto-compaction with events.
|
|
2130
2240
|
*/
|
|
2131
|
-
async _runAutoCompaction(reason, willRetry) {
|
|
2241
|
+
async _runAutoCompaction(reason, willRetry, run, continueQueued = true) {
|
|
2132
2242
|
const settings = this.settingsManager.getCompactionSettings();
|
|
2243
|
+
const chain = run?.chain;
|
|
2244
|
+
let compacted = false;
|
|
2245
|
+
let persisting = false;
|
|
2246
|
+
if (chain?.cancelled || this._disposed)
|
|
2247
|
+
return;
|
|
2133
2248
|
// SCRAMJET-DIVERGENCE: reentrant subscribers must observe compaction ownership (#598).
|
|
2134
2249
|
this._autoCompactionAbortController = new AbortController();
|
|
2135
2250
|
try {
|
|
@@ -2177,7 +2292,7 @@ export class AgentSession {
|
|
|
2177
2292
|
branchEntries: pathEntries,
|
|
2178
2293
|
customInstructions: undefined,
|
|
2179
2294
|
signal: this._autoCompactionAbortController.signal,
|
|
2180
|
-
}));
|
|
2295
|
+
}, chain?.result.promise));
|
|
2181
2296
|
if (extensionResult?.cancel) {
|
|
2182
2297
|
this._emit({
|
|
2183
2298
|
type: "compaction_end",
|
|
@@ -2212,7 +2327,7 @@ export class AgentSession {
|
|
|
2212
2327
|
tokensBefore = compactResult.tokensBefore;
|
|
2213
2328
|
details = compactResult.details;
|
|
2214
2329
|
}
|
|
2215
|
-
if (this._autoCompactionAbortController.signal.aborted) {
|
|
2330
|
+
if (this._autoCompactionAbortController.signal.aborted || chain?.cancelled || this._disposed) {
|
|
2216
2331
|
this._emit({
|
|
2217
2332
|
type: "compaction_end",
|
|
2218
2333
|
reason,
|
|
@@ -2222,7 +2337,9 @@ export class AgentSession {
|
|
|
2222
2337
|
});
|
|
2223
2338
|
return;
|
|
2224
2339
|
}
|
|
2340
|
+
persisting = true;
|
|
2225
2341
|
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
2342
|
+
compacted = true;
|
|
2226
2343
|
const newEntries = this.sessionManager.getEntries();
|
|
2227
2344
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
2228
2345
|
this.agent.state.messages = sessionContext.messages;
|
|
@@ -2233,7 +2350,7 @@ export class AgentSession {
|
|
|
2233
2350
|
type: "session_compact",
|
|
2234
2351
|
compactionEntry: savedCompactionEntry,
|
|
2235
2352
|
fromExtension,
|
|
2236
|
-
});
|
|
2353
|
+
}, chain?.result.promise);
|
|
2237
2354
|
}
|
|
2238
2355
|
const result = {
|
|
2239
2356
|
summary,
|
|
@@ -2248,20 +2365,23 @@ export class AgentSession {
|
|
|
2248
2365
|
if (lastMsg?.role === "assistant" && lastMsg.stopReason === "error") {
|
|
2249
2366
|
this.agent.state.messages = messages.slice(0, -1);
|
|
2250
2367
|
}
|
|
2251
|
-
setTimeout(() => {
|
|
2252
|
-
this.agent.continue().catch(() => { });
|
|
2253
|
-
}, 100);
|
|
2254
2368
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
this.agent.continue().catch(() => { });
|
|
2260
|
-
}, 100);
|
|
2369
|
+
if (!chain?.cancelled &&
|
|
2370
|
+
!this._disposed &&
|
|
2371
|
+
(willRetry || (continueQueued && this.agent.hasQueuedMessages()))) {
|
|
2372
|
+
this._scheduleCompactionContinuation(chain, run?.execution.promise);
|
|
2261
2373
|
}
|
|
2262
2374
|
}
|
|
2263
2375
|
catch (error) {
|
|
2264
2376
|
const errorMessage = error instanceof Error ? error.message : "compaction failed";
|
|
2377
|
+
if (chain) {
|
|
2378
|
+
if (persisting)
|
|
2379
|
+
chain.error = error instanceof Error ? error : new Error(String(error));
|
|
2380
|
+
chain.errorMessage =
|
|
2381
|
+
reason === "overflow"
|
|
2382
|
+
? `Context overflow recovery failed: ${errorMessage}`
|
|
2383
|
+
: `Auto-compaction failed: ${errorMessage}`;
|
|
2384
|
+
}
|
|
2265
2385
|
this._emit({
|
|
2266
2386
|
type: "compaction_end",
|
|
2267
2387
|
reason,
|
|
@@ -2274,9 +2394,34 @@ export class AgentSession {
|
|
|
2274
2394
|
});
|
|
2275
2395
|
}
|
|
2276
2396
|
finally {
|
|
2397
|
+
if (chain) {
|
|
2398
|
+
if (this._autoCompactionAbortController?.signal.aborted)
|
|
2399
|
+
chain.cancelled = true;
|
|
2400
|
+
if (willRetry && !compacted && !chain.cancelled)
|
|
2401
|
+
chain.errorMessage ??=
|
|
2402
|
+
"Context overflow recovery could not compact. Reduce context or select a larger-context model.";
|
|
2403
|
+
}
|
|
2277
2404
|
this._autoCompactionAbortController = undefined;
|
|
2278
2405
|
}
|
|
2279
2406
|
}
|
|
2407
|
+
_scheduleCompactionContinuation(chain, execution) {
|
|
2408
|
+
const owner = chain ?? createRetryChain();
|
|
2409
|
+
this._unsettledRetryChains.add(owner);
|
|
2410
|
+
owner.handoff = setTimeout(() => {
|
|
2411
|
+
void (async () => {
|
|
2412
|
+
await execution;
|
|
2413
|
+
owner.handoff = undefined;
|
|
2414
|
+
if (owner.cancelled || this._disposed || this._manualCompactionDrain || owner.error) {
|
|
2415
|
+
this._settleRetryChain(owner);
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
owner.terminal = false;
|
|
2419
|
+
await this._continueAgentRun(owner);
|
|
2420
|
+
})().catch((error) => {
|
|
2421
|
+
this._settleRetryChain(owner, this._reportRetryFailure(error));
|
|
2422
|
+
});
|
|
2423
|
+
}, 100);
|
|
2424
|
+
}
|
|
2280
2425
|
/**
|
|
2281
2426
|
* Toggle auto-compaction setting.
|
|
2282
2427
|
*/
|
|
@@ -2454,6 +2599,7 @@ export class AgentSession {
|
|
|
2454
2599
|
})();
|
|
2455
2600
|
},
|
|
2456
2601
|
getSystemPrompt: () => this.systemPrompt,
|
|
2602
|
+
sendMessage: (message, options) => this.sendCustomMessage(message, options),
|
|
2457
2603
|
dispatchUserInput: (input, options) => this.dispatchUserInput(input, options),
|
|
2458
2604
|
}, {
|
|
2459
2605
|
registerProvider: (name, config) => {
|
|
@@ -2736,7 +2882,7 @@ export class AgentSession {
|
|
|
2736
2882
|
});
|
|
2737
2883
|
return normalized;
|
|
2738
2884
|
}
|
|
2739
|
-
|
|
2885
|
+
_handleRetryableError(message, evidence, run) {
|
|
2740
2886
|
const settings = this.settingsManager.getRetrySettings();
|
|
2741
2887
|
const attempt = (this._retry?.attempt ?? 0) + 1;
|
|
2742
2888
|
const cumulativeCap = Math.min(Number.MAX_SAFE_INTEGER, settings.maxRetries * 2);
|
|
@@ -2808,51 +2954,72 @@ export class AgentSession {
|
|
|
2808
2954
|
errorMessage: message.errorMessage || "Unknown error",
|
|
2809
2955
|
cumulativeErrors: this._runRetryCount,
|
|
2810
2956
|
});
|
|
2811
|
-
if (controller.signal.aborted || this._disposed) {
|
|
2957
|
+
if (controller.signal.aborted || run.chain.cancelled || this._disposed) {
|
|
2812
2958
|
return this._finishCancelledRetry("cancelled_during_backoff", backoff);
|
|
2813
2959
|
}
|
|
2814
2960
|
const messages = this.agent.state.messages;
|
|
2815
2961
|
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
2816
2962
|
this.agent.state.messages = messages.slice(0, -1);
|
|
2817
2963
|
}
|
|
2964
|
+
// Execution unwind may need queued failure events, so retry waiting cannot occupy that queue.
|
|
2965
|
+
void this._resumeRetry(backoff, delayMs).catch((error) => {
|
|
2966
|
+
if (this._disposed)
|
|
2967
|
+
return;
|
|
2968
|
+
const failure = this._reportRetryFailure(error);
|
|
2969
|
+
if (this._retry === backoff)
|
|
2970
|
+
this._releaseRetryForFailedRun(failure);
|
|
2971
|
+
this._completeAgentRun(run, failure);
|
|
2972
|
+
});
|
|
2973
|
+
return true;
|
|
2974
|
+
}
|
|
2975
|
+
async _resumeRetry(backoff, delayMs) {
|
|
2976
|
+
const { controller, run, attempt } = backoff;
|
|
2818
2977
|
try {
|
|
2819
2978
|
await sleep(delayMs, controller.signal);
|
|
2820
2979
|
await sleep(0, controller.signal);
|
|
2821
2980
|
}
|
|
2822
2981
|
catch {
|
|
2823
|
-
|
|
2982
|
+
this._finishCancelledRetry("cancelled_during_backoff", backoff);
|
|
2983
|
+
this._completeAgentRun(run);
|
|
2984
|
+
return;
|
|
2824
2985
|
}
|
|
2825
|
-
|
|
2826
|
-
|
|
2986
|
+
await run.execution.promise;
|
|
2987
|
+
await this._drainAgentEventQueue();
|
|
2988
|
+
if (this._disposed || this._retry !== backoff)
|
|
2989
|
+
return;
|
|
2990
|
+
if (run.chain.error || run.chain.resultError)
|
|
2991
|
+
throw run.chain.error ?? run.chain.resultError;
|
|
2992
|
+
if (controller.signal.aborted || run.chain.cancelled || this._manualCompactionDrain) {
|
|
2993
|
+
this._finishCancelledRetry("cancelled_during_backoff", backoff);
|
|
2994
|
+
this._completeAgentRun(run);
|
|
2995
|
+
return;
|
|
2827
2996
|
}
|
|
2828
|
-
if (this._manualCompactionDrain)
|
|
2829
|
-
return this._finishCancelledRetry("cancelled_during_backoff", backoff);
|
|
2830
2997
|
// Hand the chain to the continuation before `continue()`; the run that eventually emits
|
|
2831
2998
|
// `agent_start` adopts it (see `_captureAgentRunSettlement`), however long Agent defers that start.
|
|
2832
2999
|
const continuing = { phase: "continuing", attempt, chain: run.chain };
|
|
2833
3000
|
this._retry = continuing;
|
|
2834
|
-
const continuation = this.
|
|
3001
|
+
const continuation = this._continueAgentRun(run.chain);
|
|
2835
3002
|
this._settleAgentRun(run);
|
|
2836
3003
|
continuation.catch((error) => {
|
|
2837
|
-
if (this._disposed
|
|
3004
|
+
if (this._disposed)
|
|
2838
3005
|
return;
|
|
2839
3006
|
const continuationError = this._reportRetryFailure(error);
|
|
2840
3007
|
let settlementError = continuationError;
|
|
2841
3008
|
try {
|
|
2842
|
-
this.
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
3009
|
+
if (this._retry === continuing)
|
|
3010
|
+
this._finishRetry({
|
|
3011
|
+
schemaVersion: 1,
|
|
3012
|
+
outcome: "failed",
|
|
3013
|
+
reason: "continuation_rejected",
|
|
3014
|
+
attempt: this._bounded(attempt),
|
|
3015
|
+
cumulativeErrors: this._bounded(this._runRetryCount),
|
|
3016
|
+
}, { type: "auto_retry_end", success: false, attempt, finalError: continuationError.message });
|
|
2849
3017
|
}
|
|
2850
3018
|
catch (persistenceError) {
|
|
2851
3019
|
settlementError = new AggregateError([continuationError, persistenceError], "Retry continuation and outcome persistence both failed.", { cause: continuationError });
|
|
2852
3020
|
}
|
|
2853
3021
|
this._settleRetryChain(run.chain, settlementError);
|
|
2854
3022
|
});
|
|
2855
|
-
return true;
|
|
2856
3023
|
}
|
|
2857
3024
|
_finishCancelledRetry(reason, expected) {
|
|
2858
3025
|
if (this._disposed) {
|
|
@@ -2876,29 +3043,46 @@ export class AgentSession {
|
|
|
2876
3043
|
* Cancel in-progress retry.
|
|
2877
3044
|
*/
|
|
2878
3045
|
abortRetry() {
|
|
3046
|
+
this._cancelRunSettlements();
|
|
2879
3047
|
const retry = this._retry;
|
|
2880
|
-
if (!retry)
|
|
3048
|
+
if (!retry) {
|
|
3049
|
+
this.agent.abort();
|
|
2881
3050
|
return;
|
|
3051
|
+
}
|
|
2882
3052
|
if (retry.phase === "backoff")
|
|
2883
3053
|
retry.controller.abort();
|
|
2884
3054
|
this.agent.abort();
|
|
2885
3055
|
}
|
|
2886
3056
|
/** Agent's own `activeRun` guard is false during backoff; the retry phase owns the idle Agent then. */
|
|
2887
3057
|
_assertRetryNotOwningIdleAgent() {
|
|
2888
|
-
if (this._retry && !this.isStreaming) {
|
|
3058
|
+
if ((this._retry || [...this._unsettledRetryChains].some((chain) => chain.handoff)) && !this.isStreaming) {
|
|
2889
3059
|
throw new Error("Automatic retry is in progress. Cancel the retry before starting another prompt.");
|
|
2890
3060
|
}
|
|
2891
3061
|
}
|
|
2892
|
-
async _runAgentPrompt(messages) {
|
|
3062
|
+
async _runAgentPrompt(messages, onRunSettlement) {
|
|
2893
3063
|
this._assertRetryNotOwningIdleAgent();
|
|
2894
3064
|
const start = this._reserveAgentRunStart();
|
|
3065
|
+
try {
|
|
3066
|
+
onRunSettlement?.(start.chain.result.promise);
|
|
3067
|
+
}
|
|
3068
|
+
catch (error) {
|
|
3069
|
+
this._rejectAgentRunStart(start, error instanceof Error ? error : new Error(String(error)));
|
|
3070
|
+
throw error;
|
|
3071
|
+
}
|
|
2895
3072
|
this._runRetryCount = 0;
|
|
2896
3073
|
let promptFailure;
|
|
3074
|
+
start.chain.executions++;
|
|
2897
3075
|
try {
|
|
2898
3076
|
await this.agent.prompt(messages);
|
|
2899
3077
|
}
|
|
2900
3078
|
catch (error) {
|
|
2901
3079
|
promptFailure = { error };
|
|
3080
|
+
start.chain.error ??= error instanceof Error ? error : new Error(String(error));
|
|
3081
|
+
}
|
|
3082
|
+
finally {
|
|
3083
|
+
start.chain.executions--;
|
|
3084
|
+
resolveDeferred(start.execution, undefined);
|
|
3085
|
+
this._trySettleRetryChain(start.chain);
|
|
2902
3086
|
}
|
|
2903
3087
|
if (!start.run && this._pendingAgentRunStart === start) {
|
|
2904
3088
|
const startError = promptFailure
|
|
@@ -2918,7 +3102,7 @@ export class AgentSession {
|
|
|
2918
3102
|
catch (error) {
|
|
2919
3103
|
settlementFailure = { error };
|
|
2920
3104
|
}
|
|
2921
|
-
if (promptFailure && settlementFailure) {
|
|
3105
|
+
if (promptFailure && settlementFailure && promptFailure.error !== settlementFailure.error) {
|
|
2922
3106
|
throw new AggregateError([promptFailure.error, settlementFailure.error], "Agent prompt and retry settlement both failed.", { cause: promptFailure.error });
|
|
2923
3107
|
}
|
|
2924
3108
|
if (promptFailure)
|
|
@@ -2926,12 +3110,53 @@ export class AgentSession {
|
|
|
2926
3110
|
if (settlementFailure)
|
|
2927
3111
|
throw settlementFailure.error;
|
|
2928
3112
|
}
|
|
3113
|
+
async _continueAgentRun(chain) {
|
|
3114
|
+
const start = this._reserveAgentRunStart(chain);
|
|
3115
|
+
chain.executions++;
|
|
3116
|
+
let failure;
|
|
3117
|
+
try {
|
|
3118
|
+
if (!chain.cancelled)
|
|
3119
|
+
await this.agent.continue();
|
|
3120
|
+
}
|
|
3121
|
+
catch (error) {
|
|
3122
|
+
failure = error instanceof Error ? error : new Error(String(error));
|
|
3123
|
+
throw error;
|
|
3124
|
+
}
|
|
3125
|
+
finally {
|
|
3126
|
+
chain.executions--;
|
|
3127
|
+
if (failure)
|
|
3128
|
+
chain.error ??= failure;
|
|
3129
|
+
resolveDeferred(start.execution, undefined);
|
|
3130
|
+
if (!start.run) {
|
|
3131
|
+
if (failure) {
|
|
3132
|
+
if (this._pendingAgentRunStart === start)
|
|
3133
|
+
this._pendingAgentRunStart = undefined;
|
|
3134
|
+
rejectDeferred(start, failure);
|
|
3135
|
+
}
|
|
3136
|
+
else {
|
|
3137
|
+
this._rejectAgentRunStart(start, new Error("Agent continuation completed before agent_start."));
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
if (!failure)
|
|
3141
|
+
this._trySettleRetryChain(chain);
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
_cancelRunSettlements() {
|
|
3145
|
+
for (const chain of this._unsettledRetryChains) {
|
|
3146
|
+
chain.cancelled = true;
|
|
3147
|
+
if (chain.handoff) {
|
|
3148
|
+
clearTimeout(chain.handoff);
|
|
3149
|
+
chain.handoff = undefined;
|
|
3150
|
+
this._settleRetryChain(chain);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
this._autoCompactionAbortController?.abort();
|
|
3154
|
+
}
|
|
2929
3155
|
async waitForRetry(settlement) {
|
|
2930
3156
|
if (!settlement)
|
|
2931
3157
|
return;
|
|
2932
3158
|
await settlement.promise;
|
|
2933
3159
|
await settlement.chain.promise;
|
|
2934
|
-
await this.agent.waitForIdle();
|
|
2935
3160
|
}
|
|
2936
3161
|
/** Whether auto-retry is currently in progress */
|
|
2937
3162
|
get isRetrying() {
|