@dreb/coding-agent 2.72.0 → 2.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/core/agent-session.d.ts +34 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +207 -98
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +77 -6
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +312 -64
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +6 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +9 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +7 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +11 -2
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/agent-models.md +1 -1
- package/docs/dashboard.md +46 -3
- package/docs/json.md +2 -2
- package/docs/rpc.md +24 -5
- package/docs/settings.md +1 -1
- package/package.json +1 -1
|
@@ -41,7 +41,7 @@ import { DEFAULT_BG_PARENT_TURN_LIMIT, DEFAULT_MAX_CONCURRENT_SUBAGENTS, } from
|
|
|
41
41
|
import { createSyntheticSourceInfo } from "./source-info.js";
|
|
42
42
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
43
43
|
import { resolveThinkingDisplay } from "./thinking.js";
|
|
44
|
-
import { createAllToolDefinitions, createSubagentConcurrencyGate, discoverAgentTypes, getRunningBackgroundAgents, } from "./tools/index.js";
|
|
44
|
+
import { createAllToolDefinitions, createSubagentConcurrencyGate, discoverAgentTypes, formatAgentOutputBlock, getRunningBackgroundAgents, } from "./tools/index.js";
|
|
45
45
|
import { expandSkillContent } from "./tools/skill.js";
|
|
46
46
|
import { createToolDefinitionFromAgentTool, wrapToolDefinition } from "./tools/tool-definition-wrapper.js";
|
|
47
47
|
// ============================================================================
|
|
@@ -84,6 +84,11 @@ export class AgentSession {
|
|
|
84
84
|
_unsubscribeAgent;
|
|
85
85
|
_eventListeners = [];
|
|
86
86
|
_agentEventQueue = Promise.resolve();
|
|
87
|
+
/**
|
|
88
|
+
* Agent events queued on `_agentEventQueue` that haven't finished processing yet.
|
|
89
|
+
* Persistence happens there, so the session log lags agent state until this is 0.
|
|
90
|
+
*/
|
|
91
|
+
_agentEventsInFlight = 0;
|
|
87
92
|
/** Tracks pending steering messages for UI display. Removed when delivered. */
|
|
88
93
|
_steeringMessages = [];
|
|
89
94
|
/** Tracks pending follow-up messages for UI display. Removed when delivered. */
|
|
@@ -108,6 +113,14 @@ export class AgentSession {
|
|
|
108
113
|
// Bash execution state
|
|
109
114
|
_bashAbortController = undefined;
|
|
110
115
|
_pendingBashMessages = [];
|
|
116
|
+
/**
|
|
117
|
+
* Background-agent cancellation notices that arrived while the parent was busy (see
|
|
118
|
+
* `_canDeliverCancellationNotices`). Appending them then could land a user message
|
|
119
|
+
* between an assistant tool call and its result, or ahead of messages not yet persisted.
|
|
120
|
+
* They are held until the parent is idle again, or are persisted to their session before
|
|
121
|
+
* it is replaced.
|
|
122
|
+
*/
|
|
123
|
+
_pendingCancellationNotices = [];
|
|
111
124
|
// Extension system
|
|
112
125
|
_extensionRunner = undefined;
|
|
113
126
|
_turnIndex = 0;
|
|
@@ -646,11 +659,19 @@ export class AgentSession {
|
|
|
646
659
|
if (cancelled) {
|
|
647
660
|
parts.push("This agent was cancelled by the user.");
|
|
648
661
|
}
|
|
649
|
-
|
|
662
|
+
const failed = !cancelled && (result.exitCode !== 0 || !!result.errorMessage);
|
|
663
|
+
if (failed) {
|
|
650
664
|
parts.push(`Error: ${result.errorMessage || "unknown"}`);
|
|
651
665
|
}
|
|
666
|
+
if (result.notes && result.notes.length > 0) {
|
|
667
|
+
parts.push(...result.notes);
|
|
668
|
+
}
|
|
652
669
|
if (result.output) {
|
|
653
|
-
|
|
670
|
+
// The agent's own words are delimited from the system lines around them. A chain's
|
|
671
|
+
// output is already a per-step report with each step's output delimited
|
|
672
|
+
// (formatSingleResult), so it is not wrapped again.
|
|
673
|
+
const isChain = result.steps !== undefined;
|
|
674
|
+
parts.push(isChain ? result.output : formatAgentOutputBlock(result.output, cancelled || failed));
|
|
654
675
|
}
|
|
655
676
|
if (result.sessionFile) {
|
|
656
677
|
parts.push(`Session log: ${result.sessionFile}`);
|
|
@@ -674,13 +695,15 @@ export class AgentSession {
|
|
|
674
695
|
timestamp: Date.now(),
|
|
675
696
|
};
|
|
676
697
|
if (cancelled) {
|
|
677
|
-
// Cancelled by user (Esc) — add to
|
|
678
|
-
// but do NOT trigger a response
|
|
698
|
+
// Cancelled by the user (single-agent stop, Esc, or the parent stop) — add to
|
|
699
|
+
// context and render in chat, but do NOT trigger a response: the user chose to
|
|
700
|
+
// stop this work, and waking the parent could relaunch it behind their back.
|
|
701
|
+
// While the parent is busy (streaming, still persisting the run, retrying, or
|
|
702
|
+
// compacting) the notice is deferred, so it can never split an assistant tool call
|
|
703
|
+
// from its result.
|
|
704
|
+
this._pendingCancellationNotices.push(message);
|
|
679
705
|
try {
|
|
680
|
-
this.
|
|
681
|
-
this.sessionManager.appendMessage(message);
|
|
682
|
-
this._emit({ type: "message_start", message });
|
|
683
|
-
this._emit({ type: "message_end", message });
|
|
706
|
+
this._flushPendingCancellationNotices();
|
|
684
707
|
}
|
|
685
708
|
catch (err) {
|
|
686
709
|
log.warn(`[subagent] Failed to deliver cancellation message for agent ${agentId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -696,6 +719,8 @@ export class AgentSession {
|
|
|
696
719
|
this.agent.steer(message);
|
|
697
720
|
}
|
|
698
721
|
else {
|
|
722
|
+
// Earlier deferred cancellation notices precede this completion in context.
|
|
723
|
+
this._flushPendingCancellationNotices();
|
|
699
724
|
// Fallback: if streaming started between the isStreaming check and this call, deliver as follow-up
|
|
700
725
|
this.agent.prompt(message).catch((promptErr) => {
|
|
701
726
|
log.warn(`[subagent] prompt() failed for background agent ${agentId}: ${promptErr instanceof Error ? promptErr.message : String(promptErr)}`);
|
|
@@ -714,7 +739,8 @@ export class AgentSession {
|
|
|
714
739
|
type: "background_agent_end",
|
|
715
740
|
agentId,
|
|
716
741
|
agentType: result.agent,
|
|
717
|
-
success: result.exitCode === 0,
|
|
742
|
+
success: !cancelled && result.exitCode === 0,
|
|
743
|
+
cancelled,
|
|
718
744
|
model: result.model,
|
|
719
745
|
thinking: result.thinking,
|
|
720
746
|
steps: result.steps,
|
|
@@ -744,6 +770,7 @@ export class AgentSession {
|
|
|
744
770
|
// _processAgentEvent, slow earlier queued events can delay agent_end processing
|
|
745
771
|
// and waitForRetry() can miss the in-flight retry.
|
|
746
772
|
this._createRetryPromiseForAgentEnd(event);
|
|
773
|
+
this._agentEventsInFlight++;
|
|
747
774
|
this._agentEventQueue = this._agentEventQueue.then(() => this._processAgentEvent(event), () => {
|
|
748
775
|
// Prior event failed — already warned by the .catch() below.
|
|
749
776
|
// Swallow the old rejection and continue with the current event.
|
|
@@ -847,101 +874,119 @@ export class AgentSession {
|
|
|
847
874
|
return undefined;
|
|
848
875
|
}
|
|
849
876
|
async _processAgentEvent(event) {
|
|
850
|
-
//
|
|
851
|
-
//
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
if (
|
|
856
|
-
|
|
857
|
-
const
|
|
858
|
-
if (
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
877
|
+
// The finally runs inline: an extra async wrapper here would add microtask hops to every
|
|
878
|
+
// queued event and delay event delivery relative to prompt() resolution.
|
|
879
|
+
try {
|
|
880
|
+
// When a user message starts, check if it's from either queue and remove it BEFORE emitting
|
|
881
|
+
// This ensures the UI sees the updated queue state
|
|
882
|
+
if (event.type === "message_start" && event.message.role === "user") {
|
|
883
|
+
this._overflowRecoveryAttempted = false;
|
|
884
|
+
const messageText = this._getUserMessageText(event.message);
|
|
885
|
+
if (messageText) {
|
|
886
|
+
// Check steering queue first
|
|
887
|
+
const steeringIndex = this._steeringMessages.findIndex((message) => message.text === messageText);
|
|
888
|
+
if (steeringIndex !== -1) {
|
|
889
|
+
this._steeringMessages.splice(steeringIndex, 1);
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
// Check follow-up queue
|
|
893
|
+
const followUpIndex = this._followUpMessages.findIndex((message) => message.text === messageText);
|
|
894
|
+
if (followUpIndex !== -1) {
|
|
895
|
+
this._followUpMessages.splice(followUpIndex, 1);
|
|
896
|
+
}
|
|
866
897
|
}
|
|
867
898
|
}
|
|
868
899
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
this.sessionManager.appendCustomMessageEntry(event.message.customType, event.message.content, event.message.display, event.message.details);
|
|
891
|
-
}
|
|
892
|
-
else if (event.message.role === "user" ||
|
|
893
|
-
event.message.role === "assistant" ||
|
|
894
|
-
event.message.role === "toolResult") {
|
|
895
|
-
// Regular LLM message - persist as SessionMessageEntry
|
|
896
|
-
this.sessionManager.appendMessage(event.message);
|
|
897
|
-
}
|
|
898
|
-
// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
|
|
899
|
-
// Track assistant message for auto-compaction (checked on agent_end)
|
|
900
|
-
if (event.message.role === "assistant") {
|
|
901
|
-
this._lastAssistantMessage = event.message;
|
|
902
|
-
const assistantMsg = event.message;
|
|
903
|
-
if (assistantMsg.stopReason !== "error") {
|
|
904
|
-
this._overflowRecoveryAttempted = false;
|
|
900
|
+
// Emit to extensions first
|
|
901
|
+
await this._emitExtensionEvent(event);
|
|
902
|
+
// Notify all listeners
|
|
903
|
+
this._emit(event);
|
|
904
|
+
// Keep a small, mode-independent rolling context for the headless arbiter.
|
|
905
|
+
if (event.type === "message_end") {
|
|
906
|
+
this._dispatchArbiter.onMessageEnd(event.message);
|
|
907
|
+
}
|
|
908
|
+
else if (event.type === "tool_execution_end") {
|
|
909
|
+
this._dispatchArbiter.onToolEnd({
|
|
910
|
+
toolName: event.toolName,
|
|
911
|
+
isError: event.isError,
|
|
912
|
+
result: event.result,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
// Handle session persistence
|
|
916
|
+
if (event.type === "message_end") {
|
|
917
|
+
// Check if this is a custom message from extensions
|
|
918
|
+
if (event.message.role === "custom") {
|
|
919
|
+
// Persist as CustomMessageEntry
|
|
920
|
+
this.sessionManager.appendCustomMessageEntry(event.message.customType, event.message.content, event.message.display, event.message.details);
|
|
905
921
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
success: true,
|
|
912
|
-
attempt: this._retryAttempt,
|
|
913
|
-
});
|
|
914
|
-
this._retryAttempt = 0;
|
|
922
|
+
else if (event.message.role === "user" ||
|
|
923
|
+
event.message.role === "assistant" ||
|
|
924
|
+
event.message.role === "toolResult") {
|
|
925
|
+
// Regular LLM message - persist as SessionMessageEntry
|
|
926
|
+
this.sessionManager.appendMessage(event.message);
|
|
915
927
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
928
|
+
// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
|
|
929
|
+
// Track assistant message for auto-compaction (checked on agent_end)
|
|
930
|
+
if (event.message.role === "assistant") {
|
|
931
|
+
this._lastAssistantMessage = event.message;
|
|
932
|
+
const assistantMsg = event.message;
|
|
933
|
+
if (assistantMsg.stopReason !== "error") {
|
|
934
|
+
this._overflowRecoveryAttempted = false;
|
|
935
|
+
}
|
|
936
|
+
// Reset retry counter immediately on successful assistant response
|
|
937
|
+
// This prevents accumulation across multiple LLM calls within a turn
|
|
938
|
+
if (assistantMsg.stopReason !== "error" && this._retryAttempt > 0) {
|
|
939
|
+
this._emit({
|
|
940
|
+
type: "auto_retry_end",
|
|
941
|
+
success: true,
|
|
942
|
+
attempt: this._retryAttempt,
|
|
943
|
+
});
|
|
944
|
+
this._retryAttempt = 0;
|
|
945
|
+
}
|
|
946
|
+
const durationMs = assistantMsg.durationMs ?? 0;
|
|
947
|
+
if (assistantMsg.stopReason !== "error" &&
|
|
948
|
+
assistantMsg.stopReason !== "aborted" &&
|
|
949
|
+
assistantMsg.usage.output > 0 &&
|
|
950
|
+
durationMs >= MIN_PERFORMANCE_DURATION_MS) {
|
|
951
|
+
this.performanceTracker.record({
|
|
952
|
+
timestamp: new Date().toISOString(),
|
|
953
|
+
sessionId: this.sessionId,
|
|
954
|
+
provider: assistantMsg.provider,
|
|
955
|
+
modelId: assistantMsg.model,
|
|
956
|
+
outputTokens: assistantMsg.usage.output,
|
|
957
|
+
durationMs,
|
|
958
|
+
tps: (assistantMsg.usage.output * 1000) / durationMs,
|
|
959
|
+
});
|
|
960
|
+
}
|
|
930
961
|
}
|
|
931
962
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
963
|
+
// Check auto-retry and auto-compaction after agent completes
|
|
964
|
+
if (event.type === "agent_end") {
|
|
965
|
+
if (this._lastAssistantMessage) {
|
|
966
|
+
const msg = this._lastAssistantMessage;
|
|
967
|
+
this._lastAssistantMessage = undefined;
|
|
968
|
+
// Check for retryable errors first (overloaded, rate limit, server errors)
|
|
969
|
+
if (this._isRetryableError(msg)) {
|
|
970
|
+
const didRetry = await this._handleRetryableError(msg);
|
|
971
|
+
// Retry was initiated, don't proceed to compaction. Deferred cancellation
|
|
972
|
+
// notices stay held while the retry is pending (isRetrying) and are delivered
|
|
973
|
+
// after the retried run's agent_end.
|
|
974
|
+
if (didRetry)
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
this._resolveRetry();
|
|
978
|
+
await this._checkCompaction(msg);
|
|
979
|
+
}
|
|
980
|
+
// Deferred cancellation notices are delivered in the finally below once this
|
|
981
|
+
// agent_end has finished processing (after any retry decision or compaction above).
|
|
942
982
|
}
|
|
943
|
-
|
|
944
|
-
|
|
983
|
+
}
|
|
984
|
+
finally {
|
|
985
|
+
this._agentEventsInFlight--;
|
|
986
|
+
// This event is handled and persisted. If the parent is now idle, deliver cancellation
|
|
987
|
+
// notices deferred while it was busy. After a run's final agent_end, that places them
|
|
988
|
+
// after everything the run persisted.
|
|
989
|
+
this._flushPendingCancellationNotices();
|
|
945
990
|
}
|
|
946
991
|
}
|
|
947
992
|
/** Resolve the pending retry promise */
|
|
@@ -1404,8 +1449,9 @@ export class AgentSession {
|
|
|
1404
1449
|
}
|
|
1405
1450
|
return;
|
|
1406
1451
|
}
|
|
1407
|
-
// Flush any pending bash messages before the new prompt
|
|
1452
|
+
// Flush any pending bash messages and cancellation notices before the new prompt
|
|
1408
1453
|
this._flushPendingBashMessages();
|
|
1454
|
+
this._flushPendingCancellationNotices();
|
|
1409
1455
|
// Validate model
|
|
1410
1456
|
if (!this.model) {
|
|
1411
1457
|
throw new Error("No model selected.\n\n" +
|
|
@@ -1769,6 +1815,7 @@ export class AgentSession {
|
|
|
1769
1815
|
}
|
|
1770
1816
|
this._disconnectFromAgent();
|
|
1771
1817
|
await this.abort();
|
|
1818
|
+
await this._persistPendingCancellationNoticesBeforeSessionChange();
|
|
1772
1819
|
this.agent.reset();
|
|
1773
1820
|
this._dispatchArbiter.clearContext();
|
|
1774
1821
|
this.sessionManager.newSession({ parentSession: options?.parentSession, cwd: this.cwd });
|
|
@@ -2119,6 +2166,9 @@ export class AgentSession {
|
|
|
2119
2166
|
finally {
|
|
2120
2167
|
this._compactionAbortController = undefined;
|
|
2121
2168
|
this._reconnectToAgent();
|
|
2169
|
+
// Cancellation notices that arrived while compacting were held back; deliver them
|
|
2170
|
+
// now that the compacted context is in place.
|
|
2171
|
+
this._flushPendingCancellationNotices();
|
|
2122
2172
|
}
|
|
2123
2173
|
}
|
|
2124
2174
|
/**
|
|
@@ -3117,6 +3167,60 @@ export class AgentSession {
|
|
|
3117
3167
|
get hasPendingBashMessages() {
|
|
3118
3168
|
return this._pendingBashMessages.length > 0;
|
|
3119
3169
|
}
|
|
3170
|
+
/**
|
|
3171
|
+
* Whether deferred cancellation notices can be appended now without landing inside
|
|
3172
|
+
* work that is still changing the transcript. Not while the agent streams, while
|
|
3173
|
+
* earlier agent events are still being processed or persisted (the session log would
|
|
3174
|
+
* get the notice ahead of them), while an auto-retry is pending (its backoff or the
|
|
3175
|
+
* retried run), or while compaction or branch summarization runs.
|
|
3176
|
+
*/
|
|
3177
|
+
_canDeliverCancellationNotices() {
|
|
3178
|
+
return !this.agent.state.isStreaming && this._agentEventsInFlight === 0 && !this.isRetrying && !this.isCompacting;
|
|
3179
|
+
}
|
|
3180
|
+
/**
|
|
3181
|
+
* Append deferred background-agent cancellation notices to agent state and the session
|
|
3182
|
+
* log, in arrival order, and render them, if the parent is idle (see
|
|
3183
|
+
* `_canDeliverCancellationNotices`). Otherwise they stay queued. They are retried after
|
|
3184
|
+
* each processed agent event (`_processAgentEvent`), before the next prompt, and after manual compaction or
|
|
3185
|
+
* branch navigation. Never triggers a response.
|
|
3186
|
+
*/
|
|
3187
|
+
_flushPendingCancellationNotices() {
|
|
3188
|
+
if (this._pendingCancellationNotices.length === 0 || !this._canDeliverCancellationNotices())
|
|
3189
|
+
return;
|
|
3190
|
+
this._appendPendingCancellationNotices();
|
|
3191
|
+
}
|
|
3192
|
+
_appendPendingCancellationNotices() {
|
|
3193
|
+
while (this._pendingCancellationNotices.length > 0) {
|
|
3194
|
+
const message = this._pendingCancellationNotices.shift();
|
|
3195
|
+
this.agent.appendMessage(message);
|
|
3196
|
+
this.sessionManager.appendMessage(message);
|
|
3197
|
+
this._emit({ type: "message_start", message });
|
|
3198
|
+
this._emit({ type: "message_end", message });
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
/**
|
|
3202
|
+
* Persist cancellation notices still deferred for the current session before it is replaced
|
|
3203
|
+
* (new session, resume/switch, fork). The run they wait on will never end in this
|
|
3204
|
+
* session, so they are written now instead of being dropped. First every event that session
|
|
3205
|
+
* already produced is processed, so the notices land after the last message it persisted.
|
|
3206
|
+
*
|
|
3207
|
+
* Callers that abort the run first (new session, switch) get a normal append to agent state
|
|
3208
|
+
* and the log. A fork keeps the run going, so if it is still streaming the notices are only
|
|
3209
|
+
* written to the outgoing session's log. Live context is rebuilt from the fork, and must
|
|
3210
|
+
* not get a user message between a tool call and its result.
|
|
3211
|
+
*/
|
|
3212
|
+
async _persistPendingCancellationNoticesBeforeSessionChange() {
|
|
3213
|
+
if (this._pendingCancellationNotices.length === 0)
|
|
3214
|
+
return;
|
|
3215
|
+
await this._agentEventQueue.catch(() => undefined);
|
|
3216
|
+
if (this.agent.state.isStreaming) {
|
|
3217
|
+
for (const message of this._pendingCancellationNotices.splice(0)) {
|
|
3218
|
+
this.sessionManager.appendMessage(message);
|
|
3219
|
+
}
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
this._appendPendingCancellationNotices();
|
|
3223
|
+
}
|
|
3120
3224
|
/**
|
|
3121
3225
|
* Flush pending bash messages to agent state and session.
|
|
3122
3226
|
* Called after agent turn completes to maintain proper message ordering.
|
|
@@ -3167,6 +3271,7 @@ export class AgentSession {
|
|
|
3167
3271
|
}
|
|
3168
3272
|
this._disconnectFromAgent();
|
|
3169
3273
|
await this.abort();
|
|
3274
|
+
await this._persistPendingCancellationNoticesBeforeSessionChange();
|
|
3170
3275
|
this._steeringMessages = [];
|
|
3171
3276
|
this._followUpMessages = [];
|
|
3172
3277
|
this._pendingNextTurnMessages = [];
|
|
@@ -3297,6 +3402,8 @@ export class AgentSession {
|
|
|
3297
3402
|
}
|
|
3298
3403
|
skipConversationRestore = result?.skipConversationRestore ?? false;
|
|
3299
3404
|
}
|
|
3405
|
+
// Cancellation notices belong to the outgoing session: persist them there.
|
|
3406
|
+
await this._persistPendingCancellationNoticesBeforeSessionChange();
|
|
3300
3407
|
// Clear pending messages (bound to old session state)
|
|
3301
3408
|
this._pendingNextTurnMessages = [];
|
|
3302
3409
|
branch(previousSessionFile);
|
|
@@ -3508,6 +3615,8 @@ export class AgentSession {
|
|
|
3508
3615
|
}
|
|
3509
3616
|
finally {
|
|
3510
3617
|
this._branchSummaryAbortController = undefined;
|
|
3618
|
+
// Deliver cancellation notices held back while the branch summary was generated.
|
|
3619
|
+
this._flushPendingCancellationNotices();
|
|
3511
3620
|
}
|
|
3512
3621
|
}
|
|
3513
3622
|
/**
|