@code-yeongyu/senpi 2026.8.5 → 2026.8.7
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/CHANGELOG.md +93 -0
- package/dist/core/agent-abort-provenance.d.ts +20 -0
- package/dist/core/agent-abort-provenance.d.ts.map +1 -0
- package/dist/core/agent-abort-provenance.js +83 -0
- package/dist/core/agent-abort-provenance.js.map +1 -0
- package/dist/core/agent-session.d.ts +15 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +123 -80
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-settled-delivery.d.ts +9 -0
- package/dist/core/agent-settled-delivery.d.ts.map +1 -0
- package/dist/core/agent-settled-delivery.js +25 -0
- package/dist/core/agent-settled-delivery.js.map +1 -0
- package/dist/core/extensions/builtin/bash-timeout/index.d.ts +2 -2
- package/dist/core/extensions/builtin/bash-timeout/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/bash-timeout/index.js +17 -20
- package/dist/core/extensions/builtin/bash-timeout/index.js.map +1 -1
- package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts +7 -12
- package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts.map +1 -1
- package/dist/core/extensions/builtin/bash-timeout/timeout.js +11 -19
- package/dist/core/extensions/builtin/bash-timeout/timeout.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/index.js +3 -3
- package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/per-turn-cap.d.ts +8 -8
- package/dist/core/extensions/builtin/compaction/per-turn-cap.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/per-turn-cap.js +9 -16
- package/dist/core/extensions/builtin/compaction/per-turn-cap.js.map +1 -1
- package/dist/core/extensions/builtin/goal/agent-end-continuation.d.ts +11 -0
- package/dist/core/extensions/builtin/goal/agent-end-continuation.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal/agent-end-continuation.js +13 -0
- package/dist/core/extensions/builtin/goal/agent-end-continuation.js.map +1 -0
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts +0 -2
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm.js +0 -34
- package/dist/core/extensions/builtin/goal/cache-warm.js.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.d.ts +1 -1
- package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.js +2 -0
- package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/index.js +17 -19
- package/dist/core/extensions/builtin/goal/index.js.map +1 -1
- package/dist/core/extensions/builtin/goal/last-assistant-message.d.ts +5 -0
- package/dist/core/extensions/builtin/goal/last-assistant-message.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal/last-assistant-message.js +9 -0
- package/dist/core/extensions/builtin/goal/last-assistant-message.js.map +1 -0
- package/dist/core/extensions/builtin/goal/monitor-continuation-types.d.ts +23 -0
- package/dist/core/extensions/builtin/goal/monitor-continuation-types.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal/monitor-continuation-types.js +2 -0
- package/dist/core/extensions/builtin/goal/monitor-continuation-types.js.map +1 -0
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts +4 -10
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +31 -21
- package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/terminal-provider-error.d.ts +3 -0
- package/dist/core/extensions/builtin/goal/terminal-provider-error.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal/terminal-provider-error.js +10 -0
- package/dist/core/extensions/builtin/goal/terminal-provider-error.js.map +1 -0
- package/dist/core/extensions/builtin/gpt-apply-patch/apply.d.ts.map +1 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/apply.js +55 -11
- package/dist/core/extensions/builtin/gpt-apply-patch/apply.js.map +1 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.d.ts.map +1 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.js +8 -5
- package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.js.map +1 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/preview.d.ts +2 -0
- package/dist/core/extensions/builtin/gpt-apply-patch/preview.d.ts.map +1 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/preview.js +39 -7
- package/dist/core/extensions/builtin/gpt-apply-patch/preview.js.map +1 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/types.d.ts +1 -0
- package/dist/core/extensions/builtin/gpt-apply-patch/types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/gpt-apply-patch/types.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.js +22 -10
- package/dist/core/extensions/builtin/terminal/tools/bash.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/foreground-window.d.ts +11 -0
- package/dist/core/extensions/builtin/terminal/tools/foreground-window.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/foreground-window.js +16 -0
- package/dist/core/extensions/builtin/terminal/tools/foreground-window.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/sleep-wait.d.ts +28 -0
- package/dist/core/extensions/builtin/terminal/tools/sleep-wait.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/sleep-wait.js +75 -0
- package/dist/core/extensions/builtin/terminal/tools/sleep-wait.js.map +1 -0
- package/dist/core/extensions/builtin/tps.d.ts.map +1 -1
- package/dist/core/extensions/builtin/tps.js +3 -3
- package/dist/core/extensions/builtin/tps.js.map +1 -1
- package/dist/core/extensions/builtin/ttsr/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/ttsr/index.js +35 -34
- package/dist/core/extensions/builtin/ttsr/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +2 -2
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/retry-fallback/hint-policy.d.ts +14 -0
- package/dist/core/retry-fallback/hint-policy.d.ts.map +1 -1
- package/dist/core/retry-fallback/hint-policy.js +14 -0
- package/dist/core/retry-fallback/hint-policy.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +0 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +8 -128
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/assistant-render-descriptors.d.ts +17 -0
- package/dist/modes/interactive/components/assistant-render-descriptors.d.ts.map +1 -0
- package/dist/modes/interactive/components/assistant-render-descriptors.js +133 -0
- package/dist/modes/interactive/components/assistant-render-descriptors.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +41 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +2 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +30 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +30 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/anthropic.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +14 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -24,6 +24,8 @@ import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
|
|
|
24
24
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
25
25
|
import { resolvePath } from "../utils/paths.js";
|
|
26
26
|
import { sleep } from "../utils/sleep.js";
|
|
27
|
+
import { AgentAbortProvenance } from "./agent-abort-provenance.js";
|
|
28
|
+
import { AgentSettledDelivery } from "./agent-settled-delivery.js";
|
|
27
29
|
import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
|
|
28
30
|
import { executeBashWithOperations } from "./bash-executor.js";
|
|
29
31
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
@@ -46,7 +48,7 @@ import { isBillingErrorMessage } from "./retry-fallback/billing.js";
|
|
|
46
48
|
import { formatSelector } from "./retry-fallback/chains.js";
|
|
47
49
|
import { RetryFallbackController } from "./retry-fallback/controller.js";
|
|
48
50
|
import { SelectorCooldowns } from "./retry-fallback/cooldown.js";
|
|
49
|
-
import { classifyRateLimitedWait, nextInTurnDelayMs, probeBackSchedule, } from "./retry-fallback/hint-policy.js";
|
|
51
|
+
import { classifyRateLimitedWait, degradeWithoutFallback, nextInTurnDelayMs, probeBackSchedule, } from "./retry-fallback/hint-policy.js";
|
|
50
52
|
import { createFallbackLogger } from "./retry-fallback/log.js";
|
|
51
53
|
import { ProbeBackScheduler } from "./retry-fallback/probe-scheduler.js";
|
|
52
54
|
import { validateFallbackChains } from "./retry-fallback/validate.js";
|
|
@@ -109,7 +111,9 @@ function describeCompactionRejection(cause) {
|
|
|
109
111
|
case "circuit-breaker":
|
|
110
112
|
return "Compaction rejected: the compaction circuit breaker is open after repeated failures. Wait for the cooldown and retry.";
|
|
111
113
|
case "per-turn-cap":
|
|
112
|
-
|
|
114
|
+
// Historical cause identifier kept for extension-API stability; since the
|
|
115
|
+
// per-turn soft cap was removed it fires only at the absolute session cap.
|
|
116
|
+
return "Compaction rejected: absolute compaction cap reached for this session.";
|
|
113
117
|
case "stale-revision":
|
|
114
118
|
return "Compaction rejected: the session changed while the summary was being prepared. Retry compaction against the latest context.";
|
|
115
119
|
}
|
|
@@ -245,8 +249,10 @@ export class AgentSession {
|
|
|
245
249
|
this._retryPromise = undefined;
|
|
246
250
|
this._retryResolve = undefined;
|
|
247
251
|
this._userAbortPromise = undefined;
|
|
248
|
-
this.
|
|
252
|
+
this._abortProvenance = new AgentAbortProvenance();
|
|
253
|
+
this._agentSettledDelivery = new AgentSettledDelivery();
|
|
249
254
|
this._suppressQueuedContinuationAfterUserAbort = false;
|
|
255
|
+
this._userAbortGeneration = 0;
|
|
250
256
|
/** Set when clearQueue({ abortWillFollow: true }) drains queues immediately before abort(). */
|
|
251
257
|
this._hadClearedQueuedMessages = false;
|
|
252
258
|
this._extensionEventSignal = undefined;
|
|
@@ -796,17 +802,27 @@ export class AgentSession {
|
|
|
796
802
|
await this.agent.waitForIdle();
|
|
797
803
|
}
|
|
798
804
|
if (!this._isAgentRunActive) {
|
|
805
|
+
this._abortProvenance.closeAgentEndBoundary();
|
|
799
806
|
this._resolveIdleWaitIfIdle();
|
|
800
807
|
return;
|
|
801
808
|
}
|
|
802
809
|
this._isAgentRunActive = false;
|
|
810
|
+
let deferredActions = [];
|
|
811
|
+
this._agentSettledDelivery.begin(this._userAbortGeneration);
|
|
803
812
|
try {
|
|
804
813
|
await this._extensionRunner.emit({ type: "agent_settled" });
|
|
805
814
|
this._emit({ type: "agent_settled" });
|
|
815
|
+
if (this._abortProvenance.takeLateUserJoin())
|
|
816
|
+
await this._emitSessionAbort();
|
|
817
|
+
deferredActions = this._agentSettledDelivery.finish(this._userAbortGeneration);
|
|
806
818
|
}
|
|
807
819
|
finally {
|
|
820
|
+
this._agentSettledDelivery.cancel();
|
|
821
|
+
this._abortProvenance.closeAgentEndBoundary();
|
|
808
822
|
this._resolveIdleWaitIfIdle();
|
|
809
823
|
}
|
|
824
|
+
for (const action of deferredActions)
|
|
825
|
+
action();
|
|
810
826
|
}
|
|
811
827
|
async _promptAgent(messages) {
|
|
812
828
|
this._isAgentRunActive = true;
|
|
@@ -1084,8 +1100,14 @@ export class AgentSession {
|
|
|
1084
1100
|
finally {
|
|
1085
1101
|
this._extensionEventSignal = undefined;
|
|
1086
1102
|
}
|
|
1103
|
+
if (event.type === "agent_end" && this._abortProvenance.takeLateUserJoin())
|
|
1104
|
+
await this._emitSessionAbort();
|
|
1087
1105
|
// Notify all listeners
|
|
1088
1106
|
this._emit(event.type === "agent_end" ? { ...event, willRetry: agentEndWillRetry } : event);
|
|
1107
|
+
if (event.type === "agent_end") {
|
|
1108
|
+
if (this._abortProvenance.takeLateUserJoin())
|
|
1109
|
+
await this._emitSessionAbort();
|
|
1110
|
+
}
|
|
1089
1111
|
// Handle session persistence
|
|
1090
1112
|
if (event.type === "message_end") {
|
|
1091
1113
|
// Check if this is a custom message from extensions
|
|
@@ -1154,7 +1176,9 @@ export class AgentSession {
|
|
|
1154
1176
|
// Retry transient failures normally and eligible hard errors only through a fallback.
|
|
1155
1177
|
const retryableError = this._isRetryableError(msg);
|
|
1156
1178
|
const hardErrorFallbackEligible = this._isHardErrorFallbackEligible(msg);
|
|
1157
|
-
const retryCanAdmitProvider =
|
|
1179
|
+
const retryCanAdmitProvider = !userAbortSuppressedQueuedContinuation &&
|
|
1180
|
+
this.settingsManager.getRetrySettings().enabled &&
|
|
1181
|
+
(retryableError || hardErrorFallbackEligible);
|
|
1158
1182
|
let compactedBeforeRetry = false;
|
|
1159
1183
|
if (retryCanAdmitProvider &&
|
|
1160
1184
|
requiredAutoCompaction &&
|
|
@@ -1164,7 +1188,7 @@ export class AgentSession {
|
|
|
1164
1188
|
retryContinuationBlocked = !compactedBeforeRetry;
|
|
1165
1189
|
}
|
|
1166
1190
|
let retryOutcome = "not-handled";
|
|
1167
|
-
if (!retryContinuationBlocked) {
|
|
1191
|
+
if (!retryContinuationBlocked && !userAbortSuppressedQueuedContinuation) {
|
|
1168
1192
|
if (retryableError) {
|
|
1169
1193
|
retryOutcome = await this._handleRetryableError(msg);
|
|
1170
1194
|
}
|
|
@@ -1172,11 +1196,13 @@ export class AgentSession {
|
|
|
1172
1196
|
retryOutcome = await this._handleRetryableError(msg, { hardErrorFallback: true });
|
|
1173
1197
|
}
|
|
1174
1198
|
}
|
|
1175
|
-
if (retryOutcome === "continued")
|
|
1199
|
+
if (retryOutcome === "continued") {
|
|
1200
|
+
this._abortProvenance.closeAgentEndBoundary();
|
|
1176
1201
|
return;
|
|
1202
|
+
}
|
|
1177
1203
|
this._resolveRetry();
|
|
1178
1204
|
retryContinuationBlocked ||= retryOutcome === "blocked";
|
|
1179
|
-
if (!retryContinuationBlocked) {
|
|
1205
|
+
if (!retryContinuationBlocked && !userAbortSuppressedQueuedContinuation) {
|
|
1180
1206
|
if (compactedBeforeRetry && this.agent.hasQueuedMessages()) {
|
|
1181
1207
|
// Accepted recovery supersedes the stored admission rejection: the
|
|
1182
1208
|
// queued continuation is about to run, so the originating prompt must
|
|
@@ -1223,6 +1249,9 @@ export class AgentSession {
|
|
|
1223
1249
|
if (!launchedContinuation) {
|
|
1224
1250
|
await this._emitAgentSettled();
|
|
1225
1251
|
}
|
|
1252
|
+
else {
|
|
1253
|
+
this._abortProvenance.closeAgentEndBoundary();
|
|
1254
|
+
}
|
|
1226
1255
|
}
|
|
1227
1256
|
}
|
|
1228
1257
|
/** Resolve the pending retry promise */
|
|
@@ -1238,6 +1267,10 @@ export class AgentSession {
|
|
|
1238
1267
|
this._hintDeadlineMs = undefined;
|
|
1239
1268
|
this._cumulativeHintedWaitMs = 0;
|
|
1240
1269
|
}
|
|
1270
|
+
async _emitSessionAbort() {
|
|
1271
|
+
await this._extensionRunner.emit({ type: "session_abort" });
|
|
1272
|
+
this._emit({ type: "session_abort" });
|
|
1273
|
+
}
|
|
1241
1274
|
/**
|
|
1242
1275
|
* Arm the probe-back scheduler for a tier-2 demoted selector. The scheduler
|
|
1243
1276
|
* will fire at most two probes (half-hint, then deadline) and clear the
|
|
@@ -1377,19 +1410,12 @@ export class AgentSession {
|
|
|
1377
1410
|
await this._extensionRunner.emit({ type: "agent_start" });
|
|
1378
1411
|
}
|
|
1379
1412
|
else if (event.type === "agent_end") {
|
|
1380
|
-
const
|
|
1381
|
-
const aborted = abortSource !== undefined || this._findLastAssistantInMessages(event.messages)?.stopReason === "aborted";
|
|
1413
|
+
const extensionEvent = this._abortProvenance.beginAgentEnd(event.messages, agentEndWillRetry, this._findLastAssistantInMessages(event.messages)?.stopReason === "aborted");
|
|
1382
1414
|
try {
|
|
1383
|
-
await this._extensionRunner.emit(
|
|
1384
|
-
type: "agent_end",
|
|
1385
|
-
messages: event.messages,
|
|
1386
|
-
willRetry: agentEndWillRetry,
|
|
1387
|
-
...(aborted ? { aborted: true } : {}),
|
|
1388
|
-
...(abortSource === undefined ? {} : { abortSource }),
|
|
1389
|
-
});
|
|
1415
|
+
await this._extensionRunner.emit(extensionEvent);
|
|
1390
1416
|
}
|
|
1391
1417
|
finally {
|
|
1392
|
-
this.
|
|
1418
|
+
this._abortProvenance.endAgentEnd(extensionEvent);
|
|
1393
1419
|
}
|
|
1394
1420
|
}
|
|
1395
1421
|
else if (event.type === "turn_start") {
|
|
@@ -2406,6 +2432,7 @@ export class AgentSession {
|
|
|
2406
2432
|
* @param options.deliverAs Delivery mode: "steer", "followUp", or "nextTurn"
|
|
2407
2433
|
*/
|
|
2408
2434
|
async sendCustomMessage(message, options) {
|
|
2435
|
+
const userAbortGeneration = this._userAbortGeneration;
|
|
2409
2436
|
const appMessage = {
|
|
2410
2437
|
role: "custom",
|
|
2411
2438
|
customType: message.customType,
|
|
@@ -2425,6 +2452,8 @@ export class AgentSession {
|
|
|
2425
2452
|
try {
|
|
2426
2453
|
if (waitForExistingSessionWork) {
|
|
2427
2454
|
await this._waitForSettledSessionWork();
|
|
2455
|
+
if (userAbortGeneration !== this._userAbortGeneration)
|
|
2456
|
+
return;
|
|
2428
2457
|
finishSessionWork = this._sessionWorkBarrier.begin();
|
|
2429
2458
|
}
|
|
2430
2459
|
if (options?.deliverAs === "nextTurn") {
|
|
@@ -2625,31 +2654,20 @@ export class AgentSession {
|
|
|
2625
2654
|
* Abort current operation and wait for agent to become idle.
|
|
2626
2655
|
*/
|
|
2627
2656
|
async abort() {
|
|
2628
|
-
//
|
|
2629
|
-
// A mid-run abort (actively streaming with no pending retry) is delivered via
|
|
2630
|
-
// agent_end (abortSource "user") — no session_abort needed. The gap case is when
|
|
2631
|
-
// no agent_end will fire to carry the abort signal:
|
|
2632
|
-
// - retry backoff (_retryAbortController defined — the error agent_end already
|
|
2633
|
-
// fired, agent.abort() during backoff is a no-op, no new agent_end)
|
|
2634
|
-
// - compaction (!isStreaming && isCompacting)
|
|
2635
|
-
// - queued continuation that was already cleared by the caller (TUI clears queues
|
|
2636
|
-
// before calling abort, so pendingMessageCount is 0 but _hadClearedQueuedMessages
|
|
2637
|
-
// records that messages were present)
|
|
2638
|
-
// Purely-idle defensive aborts (e.g. RPC session close on an idle session) must not
|
|
2639
|
-
// fire session_abort.
|
|
2657
|
+
// Streaming aborts are carried by agent_end provenance; only gaps need session_abort.
|
|
2640
2658
|
const wasMidRun = this.isStreaming && this._retryAbortController === undefined;
|
|
2641
2659
|
const hadRetryBackoff = this._retryAbortController !== undefined;
|
|
2642
2660
|
const hadCompactionOrPending = !this.isStreaming && (this.isCompacting || this.pendingMessageCount > 0);
|
|
2643
2661
|
const hadClearedQueues = this._hadClearedQueuedMessages;
|
|
2662
|
+
const joinedAgentEndBoundary = this._abortProvenance.hasOpenAgentEndBoundary;
|
|
2644
2663
|
this._hadClearedQueuedMessages = false;
|
|
2645
|
-
const shouldEmitAbort = !wasMidRun && (hadRetryBackoff || hadCompactionOrPending || hadClearedQueues);
|
|
2664
|
+
const shouldEmitAbort = !joinedAgentEndBoundary && !wasMidRun && (hadRetryBackoff || hadCompactionOrPending || hadClearedQueues);
|
|
2646
2665
|
this.abortCompaction();
|
|
2647
2666
|
await this._abortActiveAgentAndRetry("user");
|
|
2648
2667
|
if (!shouldEmitAbort)
|
|
2649
2668
|
return;
|
|
2650
2669
|
try {
|
|
2651
|
-
await this.
|
|
2652
|
-
this._emit({ type: "session_abort" });
|
|
2670
|
+
await this._emitSessionAbort();
|
|
2653
2671
|
}
|
|
2654
2672
|
catch {
|
|
2655
2673
|
// Extension runner may be torn down during RPC close — best-effort.
|
|
@@ -3038,18 +3056,32 @@ export class AgentSession {
|
|
|
3038
3056
|
this._pendingCompactionAdmission = undefined;
|
|
3039
3057
|
admission.finishSessionWork();
|
|
3040
3058
|
}
|
|
3059
|
+
_recordUserAbort() {
|
|
3060
|
+
this._suppressQueuedContinuationAfterUserAbort = true;
|
|
3061
|
+
this._userAbortGeneration += 1;
|
|
3062
|
+
}
|
|
3041
3063
|
async _abortActiveAgentAndRetry(source) {
|
|
3042
3064
|
this.abortRetry();
|
|
3043
3065
|
this.abortBranchSummary();
|
|
3066
|
+
if (this._userAbortPromise === undefined) {
|
|
3067
|
+
const boundaryJoin = this._abortProvenance.joinOpenBoundary(source);
|
|
3068
|
+
if (boundaryJoin !== undefined) {
|
|
3069
|
+
if (boundaryJoin.userOwned)
|
|
3070
|
+
this._recordUserAbort();
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3044
3074
|
if (this._userAbortPromise) {
|
|
3045
|
-
this.
|
|
3075
|
+
const joined = this._abortProvenance.join(source, this.isStreaming);
|
|
3076
|
+
if (joined.userOwned)
|
|
3077
|
+
this._recordUserAbort();
|
|
3078
|
+
if (joined.abortCurrentAgent)
|
|
3079
|
+
this.agent.abort();
|
|
3046
3080
|
await this._userAbortPromise;
|
|
3047
3081
|
return;
|
|
3048
3082
|
}
|
|
3049
|
-
if (this.isStreaming)
|
|
3050
|
-
this.
|
|
3051
|
-
this._agentAbortSource = source;
|
|
3052
|
-
}
|
|
3083
|
+
if (this.isStreaming && this._abortProvenance.begin(source))
|
|
3084
|
+
this._recordUserAbort();
|
|
3053
3085
|
const abortPromise = (async () => {
|
|
3054
3086
|
this.agent.abort();
|
|
3055
3087
|
await this.waitForIdle();
|
|
@@ -4178,13 +4210,16 @@ export class AgentSession {
|
|
|
4178
4210
|
};
|
|
4179
4211
|
runner.bindCore({
|
|
4180
4212
|
sendMessage: (message, options) => {
|
|
4181
|
-
this.sendCustomMessage(message, options).catch((err) => {
|
|
4213
|
+
const send = () => this.sendCustomMessage(message, options).catch((err) => {
|
|
4182
4214
|
runner.emitError({
|
|
4183
4215
|
extensionPath: RUNTIME_EXTENSION_PATH,
|
|
4184
4216
|
event: "send_message",
|
|
4185
4217
|
error: err instanceof Error ? err.message : String(err),
|
|
4186
4218
|
});
|
|
4187
4219
|
});
|
|
4220
|
+
if (this._agentSettledDelivery.defer(send))
|
|
4221
|
+
return;
|
|
4222
|
+
send();
|
|
4188
4223
|
},
|
|
4189
4224
|
sendUserMessage: (content, options) => {
|
|
4190
4225
|
this.sendUserMessage(content, options).catch((err) => {
|
|
@@ -4247,11 +4282,11 @@ export class AgentSession {
|
|
|
4247
4282
|
getAgentDir: () => this._agentDir,
|
|
4248
4283
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
4249
4284
|
getSignal: () => this._extensionEventSignal ?? this.agent.signal,
|
|
4250
|
-
abort: () => {
|
|
4251
|
-
if (
|
|
4252
|
-
this.
|
|
4253
|
-
|
|
4254
|
-
|
|
4285
|
+
abort: (source = "user") => {
|
|
4286
|
+
if (source === "system")
|
|
4287
|
+
return void this._abortActiveAgentAndRetry("system");
|
|
4288
|
+
if (this._extensionAbortHandler)
|
|
4289
|
+
return this._extensionAbortHandler();
|
|
4255
4290
|
void this.abort();
|
|
4256
4291
|
},
|
|
4257
4292
|
hasPendingMessages: () => this.pendingMessageCount > 0,
|
|
@@ -4663,6 +4698,41 @@ export class AgentSession {
|
|
|
4663
4698
|
},
|
|
4664
4699
|
};
|
|
4665
4700
|
}
|
|
4701
|
+
/**
|
|
4702
|
+
* A 429-class failure with no usable fallback candidate must not fail the
|
|
4703
|
+
* turn with zero attempts: a provider answering 429 is asking for a retry.
|
|
4704
|
+
* No-hint and tier2 waits degrade to same-model in-turn retries under the
|
|
4705
|
+
* normal retry budget (tier2 clamps the hinted wait to the in-turn cap);
|
|
4706
|
+
* only tier3 hour-plus waits stay terminal, with the requested wait named
|
|
4707
|
+
* in the final error. Returns the in-turn retry delay, or undefined after
|
|
4708
|
+
* emitting the terminal auto_retry_end.
|
|
4709
|
+
*/
|
|
4710
|
+
_degradeRateLimitedWithoutFallback(tier, hintMs, message, errorMessage) {
|
|
4711
|
+
const settings = this.settingsManager.getRetrySettings();
|
|
4712
|
+
const hintSettings = this.settingsManager.getHintPolicySettings();
|
|
4713
|
+
const finishTurn = (attempt, finalError) => {
|
|
4714
|
+
const exhaustedChainKey = this._retryFallback.exhaustedChainKey;
|
|
4715
|
+
if (exhaustedChainKey) {
|
|
4716
|
+
this._emit({ type: "retry_fallback_exhausted", chainKey: exhaustedChainKey, lastError: errorMessage });
|
|
4717
|
+
}
|
|
4718
|
+
this._emit({ type: "auto_retry_end", success: false, attempt, finalError });
|
|
4719
|
+
this._retryAttempt = 0;
|
|
4720
|
+
this._resetHintTierState();
|
|
4721
|
+
this._resolveRetry();
|
|
4722
|
+
};
|
|
4723
|
+
const degraded = degradeWithoutFallback(tier, hintMs, this._retryAttempt + 1, settings.baseDelayMs, hintSettings.hintedWaitCapMs);
|
|
4724
|
+
if (degraded.kind === "fail") {
|
|
4725
|
+
const waitSeconds = Math.ceil(degraded.hintMs / 1000);
|
|
4726
|
+
finishTurn(this._retryAttempt, `Provider requested a ${waitSeconds}s wait before retrying and no usable fallback model is available. ${message.errorMessage ?? ""}`);
|
|
4727
|
+
return undefined;
|
|
4728
|
+
}
|
|
4729
|
+
this._retryAttempt++;
|
|
4730
|
+
if (this._retryAttempt > settings.maxRetries) {
|
|
4731
|
+
finishTurn(this._retryAttempt - 1, message.errorMessage);
|
|
4732
|
+
return undefined;
|
|
4733
|
+
}
|
|
4734
|
+
return degraded.delayMs;
|
|
4735
|
+
}
|
|
4666
4736
|
/**
|
|
4667
4737
|
* Handle retryable errors with exponential backoff.
|
|
4668
4738
|
* @returns whether retry continuation started, was blocked by compaction, or was not handled
|
|
@@ -4760,30 +4830,17 @@ export class AgentSession {
|
|
|
4760
4830
|
const tier = classifyRateLimitedWait(hintMs, hintSettings);
|
|
4761
4831
|
is429TierRouted = true;
|
|
4762
4832
|
if (tier === "no-hint-fast-fallback") {
|
|
4763
|
-
//
|
|
4833
|
+
// Fall back immediately when a candidate exists; otherwise degrade
|
|
4834
|
+
// to same-model in-turn retries instead of failing the turn.
|
|
4764
4835
|
switchedFallback = await this._retryFallback.tryFallback("transient", { errorMessage });
|
|
4765
4836
|
if (switchedFallback) {
|
|
4766
4837
|
this._retryAttempt = 1;
|
|
4767
4838
|
}
|
|
4768
4839
|
else {
|
|
4769
|
-
const
|
|
4770
|
-
if (
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
chainKey: exhaustedChainKey,
|
|
4774
|
-
lastError: errorMessage,
|
|
4775
|
-
});
|
|
4776
|
-
}
|
|
4777
|
-
this._emit({
|
|
4778
|
-
type: "auto_retry_end",
|
|
4779
|
-
success: false,
|
|
4780
|
-
attempt: 0,
|
|
4781
|
-
finalError: message.errorMessage,
|
|
4782
|
-
});
|
|
4783
|
-
this._retryAttempt = 0;
|
|
4784
|
-
this._resetHintTierState();
|
|
4785
|
-
this._resolveRetry();
|
|
4786
|
-
return "not-handled";
|
|
4840
|
+
const degradedDelayMs = this._degradeRateLimitedWithoutFallback(tier, hintMs, message, errorMessage);
|
|
4841
|
+
if (degradedDelayMs === undefined)
|
|
4842
|
+
return "not-handled";
|
|
4843
|
+
hintTierDelayMs = degradedDelayMs;
|
|
4787
4844
|
}
|
|
4788
4845
|
}
|
|
4789
4846
|
else if (tier === "tier1-in-turn") {
|
|
@@ -4879,24 +4936,10 @@ export class AgentSession {
|
|
|
4879
4936
|
}
|
|
4880
4937
|
}
|
|
4881
4938
|
else {
|
|
4882
|
-
const
|
|
4883
|
-
if (
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
chainKey: exhaustedChainKey,
|
|
4887
|
-
lastError: errorMessage,
|
|
4888
|
-
});
|
|
4889
|
-
}
|
|
4890
|
-
this._emit({
|
|
4891
|
-
type: "auto_retry_end",
|
|
4892
|
-
success: false,
|
|
4893
|
-
attempt: 0,
|
|
4894
|
-
finalError: message.errorMessage,
|
|
4895
|
-
});
|
|
4896
|
-
this._retryAttempt = 0;
|
|
4897
|
-
this._resetHintTierState();
|
|
4898
|
-
this._resolveRetry();
|
|
4899
|
-
return "not-handled";
|
|
4939
|
+
const degradedDelayMs = this._degradeRateLimitedWithoutFallback(tier, hintMs, message, errorMessage);
|
|
4940
|
+
if (degradedDelayMs === undefined)
|
|
4941
|
+
return "not-handled";
|
|
4942
|
+
hintTierDelayMs = degradedDelayMs;
|
|
4900
4943
|
}
|
|
4901
4944
|
}
|
|
4902
4945
|
}
|