@cydm/pie 2.7.11 → 2.7.12
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/dist/builtin/extensions/ask-user/index.js +3 -3
- package/dist/builtin/extensions/plan-mode/index.js +37 -12
- package/dist/builtin/extensions/subagent/index.js +10 -4
- package/dist/builtin/extensions/todo/index.js +3 -3
- package/dist/chunks/{chunk-XVF6V6Q5.js → chunk-2NBUFVUQ.js} +48 -2
- package/dist/chunks/{chunk-XIRBNP2S.js → chunk-AFPRX5XI.js} +684 -263
- package/dist/chunks/{chunk-3VEIP2VY.js → chunk-EN47TIBG.js} +46 -21
- package/dist/chunks/{chunk-3NFKVBGY.js → chunk-YI3CSQGD.js} +156 -2
- package/dist/chunks/{src-2RCV3I3P.js → src-XDNJ3PHL.js} +2 -2
- package/dist/chunks/{test-stream-Y2S3H7DK.js → test-stream-LUUGOJMP.js} +1 -1
- package/dist/cli.js +1254 -490
- package/package.json +2 -2
|
@@ -2,7 +2,7 @@ import { createRequire as __createRequire } from "node:module"; const require =
|
|
|
2
2
|
import {
|
|
3
3
|
Agent,
|
|
4
4
|
getToolCallerIdentity
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2NBUFVUQ.js";
|
|
6
6
|
import {
|
|
7
7
|
AssistantMessageEventStream,
|
|
8
8
|
FileSystemGateway,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
getPlatformConfig,
|
|
13
13
|
hashText,
|
|
14
14
|
streamSimple
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-EN47TIBG.js";
|
|
16
16
|
import {
|
|
17
17
|
Type
|
|
18
18
|
} from "./chunk-XXGYUYXI.js";
|
|
@@ -1003,7 +1003,7 @@ var PLAN_POWERSHELL_READ_COMMANDS = new Set([
|
|
|
1003
1003
|
].map((name) => name.toLowerCase()));
|
|
1004
1004
|
var PLAN_POWERSHELL_READ_ALIASES = /* @__PURE__ */ new Set(["cat", "ls", "pwd", "echo", "sort"]);
|
|
1005
1005
|
var PLAN_POWERSHELL_SAFE_PARAMETERS = /^(?:-(?:Raw|TotalCount|Head|Tail|First|Last|Wait|LiteralPath|Path|Name|Pattern|Recurse|Filter|Include|Exclude|SimpleMatch|CaseSensitive|Quiet|Word|Line|Character|Encoding|ErrorAction|WarningAction))$/i;
|
|
1006
|
-
var PLAN_POWERSHELL_SEPARATORS = /[;\n\r\t]
|
|
1006
|
+
var PLAN_POWERSHELL_SEPARATORS = /[;\n\r\t&<>|]/;
|
|
1007
1007
|
var PLAN_POWERSHELL_NON_ASCII = /[^\x20-\x7E]/;
|
|
1008
1008
|
var PLAN_POWERSHELL_UNSAFE_TOKENS = /\$\(|`|%\{|[(){}\[\]]|\bsource\b|\.\s+[/~]|\b(bash|sh|zsh|pwsh|powershell|cmd|npm|npx|node|python3?)\b/i;
|
|
1009
1009
|
function isPlanModeSafePowerShellCommand(command) {
|
|
@@ -1331,7 +1331,9 @@ var DEFAULT_COMPACTION_CONFIG = {
|
|
|
1331
1331
|
summaryPrefix: SUMMARY_PREFIX
|
|
1332
1332
|
};
|
|
1333
1333
|
var APPROX_BYTES_PER_TOKEN = 4;
|
|
1334
|
-
var
|
|
1334
|
+
var IMAGE_MEDIA_BYTES_ESTIMATE = 7373;
|
|
1335
|
+
var AUDIO_MEDIA_BYTES_ESTIMATE = 6800;
|
|
1336
|
+
var VIDEO_MEDIA_BYTES_ESTIMATE = 16e3;
|
|
1335
1337
|
function estimateTokens(message) {
|
|
1336
1338
|
return approximateBytesAsTokens(estimateModelVisibleBytes(message));
|
|
1337
1339
|
}
|
|
@@ -1456,20 +1458,16 @@ function planRetainedTail(summaries, options = {}) {
|
|
|
1456
1458
|
if (boundaries.length === 0) {
|
|
1457
1459
|
return nonePlan("no-boundary");
|
|
1458
1460
|
}
|
|
1459
|
-
const prefix = new Array(summaries.length + 1).fill(0);
|
|
1460
|
-
for (let i = 0; i < summaries.length; i++) {
|
|
1461
|
-
prefix[i + 1] = prefix[i] + Math.max(0, Math.floor(summaries[i].estTokens || 0));
|
|
1462
|
-
}
|
|
1463
1461
|
const groupEnd = boundaries.map((_, index) => boundaries[index + 1] ?? summaries.length);
|
|
1464
1462
|
const planFrom = (startIndex, reason) => ({
|
|
1465
1463
|
retainedTail: { kind: "from-entry", entryId: summaries[startIndex].id },
|
|
1466
1464
|
entryIds: summaries.slice(startIndex).map((summary) => summary.id),
|
|
1467
|
-
tailTokens:
|
|
1465
|
+
tailTokens: estimateRetainedTailTokens(summaries, { kind: "from-entry", entryId: summaries[startIndex].id }),
|
|
1468
1466
|
reason
|
|
1469
1467
|
});
|
|
1470
1468
|
let accumulated = 0;
|
|
1471
1469
|
for (let k = boundaries.length - 1; k >= 0; k--) {
|
|
1472
|
-
const groupTokens =
|
|
1470
|
+
const groupTokens = summaries.slice(boundaries[k], groupEnd[k]).reduce((sum, summary) => sum + Math.max(0, Math.floor(summary.estTokens || 0)), 0);
|
|
1473
1471
|
if (groupTokens > budget) {
|
|
1474
1472
|
if (accumulated === 0) {
|
|
1475
1473
|
return nonePlan("single-group-over-budget");
|
|
@@ -1545,6 +1543,9 @@ function estimateCompactionGain(params) {
|
|
|
1545
1543
|
const tokensBefore = Math.max(0, params.tokensBefore);
|
|
1546
1544
|
const tokenSavings = Math.max(0, tokensBefore - projectedTokens);
|
|
1547
1545
|
const estimate = { summaryTokens, tailTokens, projectedTokens, tokenSavings };
|
|
1546
|
+
if (!Number.isFinite(projectedTokens)) {
|
|
1547
|
+
return { ...estimate, ok: false, reason: "retained context has unknown model-visible size; supply a compaction modelView" };
|
|
1548
|
+
}
|
|
1548
1549
|
if (params.budgetTokens !== void 0 && params.budgetTokens > 0 && projectedTokens > params.budgetTokens) {
|
|
1549
1550
|
return {
|
|
1550
1551
|
...estimate,
|
|
@@ -1825,7 +1826,7 @@ function normalizeUserContentBlock(block) {
|
|
|
1825
1826
|
return { type: "text", text: block.text };
|
|
1826
1827
|
}
|
|
1827
1828
|
if (block.type === "hostContext") {
|
|
1828
|
-
return { type: "
|
|
1829
|
+
return { type: "hostContext" };
|
|
1829
1830
|
}
|
|
1830
1831
|
if (block.type === "image") {
|
|
1831
1832
|
return { type: "image", mimeType: block.mimeType };
|
|
@@ -1857,22 +1858,16 @@ function estimateMessageMediaBytes(message) {
|
|
|
1857
1858
|
}
|
|
1858
1859
|
function estimateUserContentBlockMediaBytes(block) {
|
|
1859
1860
|
if (block.type === "image") {
|
|
1860
|
-
return
|
|
1861
|
+
return IMAGE_MEDIA_BYTES_ESTIMATE;
|
|
1861
1862
|
}
|
|
1862
1863
|
if (block.type === "audio") {
|
|
1863
|
-
return
|
|
1864
|
+
return AUDIO_MEDIA_BYTES_ESTIMATE;
|
|
1864
1865
|
}
|
|
1865
1866
|
if (block.type === "video") {
|
|
1866
|
-
return
|
|
1867
|
+
return VIDEO_MEDIA_BYTES_ESTIMATE;
|
|
1867
1868
|
}
|
|
1868
1869
|
return 0;
|
|
1869
1870
|
}
|
|
1870
|
-
function estimateImageContentBytes(block) {
|
|
1871
|
-
return Math.max(RESIZED_IMAGE_BYTES_ESTIMATE, estimateInlineDataBytes(block));
|
|
1872
|
-
}
|
|
1873
|
-
function estimateInlineDataBytes(block) {
|
|
1874
|
-
return utf8ByteLength(block.data || "");
|
|
1875
|
-
}
|
|
1876
1871
|
function approximateBytesAsTokens(bytes) {
|
|
1877
1872
|
return Math.ceil(bytes / APPROX_BYTES_PER_TOKEN);
|
|
1878
1873
|
}
|
|
@@ -4623,10 +4618,26 @@ function prepareSessionCompaction(params) {
|
|
|
4623
4618
|
const agentOnlyFailure = excludedFailure === null && requireExclusion ? agentTail ?? null : null;
|
|
4624
4619
|
const summaries = sessionManager.getActiveContextSummaries();
|
|
4625
4620
|
const entries = sessionManager.getResidentContextEntries();
|
|
4626
|
-
const
|
|
4621
|
+
const sourceSummaries = excludedFailure ? summaries.filter((summary) => summary.id !== excludedFailure.entryId) : summaries;
|
|
4627
4622
|
const effectiveEntries = excludedFailure ? entries.filter((entry) => entry.id !== excludedFailure.entryId) : entries;
|
|
4628
4623
|
const summaryInput = excludedFailure || agentOnlyFailure ? messages.slice(0, -1) : messages;
|
|
4629
|
-
const
|
|
4624
|
+
const estimates = /* @__PURE__ */ new Map();
|
|
4625
|
+
const estimate = (message) => {
|
|
4626
|
+
const cached = estimates.get(message);
|
|
4627
|
+
if (cached !== void 0) return cached;
|
|
4628
|
+
const visible = params.modelView ? params.modelView(message) : message;
|
|
4629
|
+
const unknown = visible.role === "user" && Array.isArray(visible.content) && visible.content.some((block) => block.type === "hostContext");
|
|
4630
|
+
const tokens = unknown ? Infinity : estimateTokens(visible);
|
|
4631
|
+
estimates.set(message, tokens);
|
|
4632
|
+
return tokens;
|
|
4633
|
+
};
|
|
4634
|
+
const bodies = new Map(effectiveEntries.map((entry) => [entry.id, entry]));
|
|
4635
|
+
const effectiveSummaries = sourceSummaries.map((summary) => {
|
|
4636
|
+
const entry = bodies.get(summary.id);
|
|
4637
|
+
return { ...summary, estTokens: entry?.message ? estimate(entry.message) : summary.type === "message" ? Infinity : summary.estTokens };
|
|
4638
|
+
});
|
|
4639
|
+
const inputEstimate = summaryInput.reduce((sum, message) => sum + estimate(message), 0);
|
|
4640
|
+
const inputTokens = Number.isFinite(inputEstimate) ? inputEstimate : calculateTotalTokens(summaryInput);
|
|
4630
4641
|
const reductionLimit = inputTokens >= 1e3 ? inputTokens - Math.ceil(inputTokens * 0.1) : Infinity;
|
|
4631
4642
|
const contextLimit = Math.min(
|
|
4632
4643
|
reductionLimit,
|
|
@@ -4640,7 +4651,7 @@ function prepareSessionCompaction(params) {
|
|
|
4640
4651
|
let planned = planRetainedTail(effectiveSummaries, { ...params.compactionConfig, keepRecentTokens: tailBudget });
|
|
4641
4652
|
if (planned.retainedTail.kind !== "none") {
|
|
4642
4653
|
const validation = validateRetainedTailEntries({ entries: effectiveEntries, retainedTail: planned.retainedTail });
|
|
4643
|
-
planned = validation.ok ? { ...planned, entryIds: validation.entryIds, tailTokens:
|
|
4654
|
+
planned = validation.ok ? { ...planned, entryIds: validation.entryIds, tailTokens: estimateRetainedTailTokens(effectiveSummaries, planned.retainedTail) } : { retainedTail: { kind: "none" }, entryIds: [], tailTokens: 0, reason: "pairing" };
|
|
4644
4655
|
}
|
|
4645
4656
|
return {
|
|
4646
4657
|
sessionId: sessionManager.getActiveSessionId(),
|
|
@@ -4696,7 +4707,7 @@ function verifyPreparedCompactionCommit(params) {
|
|
|
4696
4707
|
const gain = estimateCompactionGain({
|
|
4697
4708
|
summary: checkpointSummary,
|
|
4698
4709
|
tailTokens: estimateRetainedTailTokens(params.preparation.summaries, retainedTail),
|
|
4699
|
-
tokensBefore: params.
|
|
4710
|
+
tokensBefore: params.preparation.inputTokens,
|
|
4700
4711
|
budgetTokens: params.preparation.budgetTokens
|
|
4701
4712
|
});
|
|
4702
4713
|
if (!gain.ok) {
|
|
@@ -5176,7 +5187,13 @@ async function preparePromptMessages(params) {
|
|
|
5176
5187
|
|
|
5177
5188
|
// ../../packages/agent-framework/src/session/controller/pre-request-policy.ts
|
|
5178
5189
|
async function runPreRequestAutoCompact(params) {
|
|
5179
|
-
|
|
5190
|
+
const boundary = params.toolContinuation;
|
|
5191
|
+
const cancelled = () => params.isCurrent?.() === false || boundary?.signal.aborted === true;
|
|
5192
|
+
if (boundary) {
|
|
5193
|
+
await boundary.sync();
|
|
5194
|
+
if (cancelled()) return;
|
|
5195
|
+
}
|
|
5196
|
+
if (params.disposed || params.runtimeGuardTriggered || params.isStreaming && !boundary || params.recoveryActive() || params.agent.state.messages.length === 0) {
|
|
5180
5197
|
return;
|
|
5181
5198
|
}
|
|
5182
5199
|
const decision = params.getAutoCompactDecision?.({
|
|
@@ -5185,12 +5202,13 @@ async function runPreRequestAutoCompact(params) {
|
|
|
5185
5202
|
cwd: params.cwd,
|
|
5186
5203
|
messages: params.agent.state.messages.slice()
|
|
5187
5204
|
});
|
|
5188
|
-
if (decision?.reason !== "threshold") {
|
|
5205
|
+
if (cancelled() || decision?.reason !== "threshold") {
|
|
5189
5206
|
return;
|
|
5190
5207
|
}
|
|
5191
5208
|
try {
|
|
5192
5209
|
await params.runAutoCompact();
|
|
5193
|
-
} catch {
|
|
5210
|
+
} catch (error) {
|
|
5211
|
+
if (boundary) throw error;
|
|
5194
5212
|
if (params.isCurrent?.() !== false) params.setRecoveryState("none");
|
|
5195
5213
|
}
|
|
5196
5214
|
}
|
|
@@ -5444,12 +5462,7 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5444
5462
|
return false;
|
|
5445
5463
|
}
|
|
5446
5464
|
this.options.emit({ type: "queued_turn_dispatched", queuedInput: cloneQueuedInput(dispatched) });
|
|
5447
|
-
if (dispatched.deliveredToAgent
|
|
5448
|
-
if (!dispatched.deliveredToAgent) {
|
|
5449
|
-
this.options.queue.deliverToAgent(dispatched);
|
|
5450
|
-
}
|
|
5451
|
-
await this.options.continueTurn();
|
|
5452
|
-
} else {
|
|
5465
|
+
if (!dispatched.deliveredToAgent) {
|
|
5453
5466
|
const prepared = await this.options.preparePromptMessages(dispatched.messages, {
|
|
5454
5467
|
source: dispatched.source,
|
|
5455
5468
|
streamingBehavior: dispatched.mode,
|
|
@@ -5459,13 +5472,20 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5459
5472
|
visibility: dispatched.visibility
|
|
5460
5473
|
});
|
|
5461
5474
|
if (generation !== this.generation) return false;
|
|
5462
|
-
if (
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5475
|
+
if (prepared.handled) return true;
|
|
5476
|
+
if (prepared.systemPrompt !== void 0) {
|
|
5477
|
+
this.options.agent.setSystemPrompt(prepared.systemPrompt);
|
|
5478
|
+
}
|
|
5479
|
+
dispatched.messages = prepared.messages;
|
|
5480
|
+
}
|
|
5481
|
+
if (dispatched.deliveredToAgent || this.options.agent.state.messages.at(-1)?.role === "assistant") {
|
|
5482
|
+
if (!dispatched.deliveredToAgent) {
|
|
5483
|
+
this.options.queue.deliverToAgent(dispatched);
|
|
5468
5484
|
}
|
|
5485
|
+
await this.options.continueTurn();
|
|
5486
|
+
} else {
|
|
5487
|
+
await this.options.agent.prompt(dispatched.messages);
|
|
5488
|
+
await this.options.syncNewAgentMessages();
|
|
5469
5489
|
}
|
|
5470
5490
|
return true;
|
|
5471
5491
|
}
|
|
@@ -5586,6 +5606,95 @@ var ReplayStallCircuit = class {
|
|
|
5586
5606
|
}
|
|
5587
5607
|
};
|
|
5588
5608
|
|
|
5609
|
+
// ../../packages/agent-framework/src/session/controller/retry-decision.ts
|
|
5610
|
+
function getAgentSessionRetryLimit(retry, failure) {
|
|
5611
|
+
return Math.min(
|
|
5612
|
+
(failure !== void 0 ? retry?.maxRetriesForFailure?.(failure) : void 0) ?? retry?.maxRetries ?? 3,
|
|
5613
|
+
failure?.kind === "provider_confirmation" ? 1 : Infinity
|
|
5614
|
+
);
|
|
5615
|
+
}
|
|
5616
|
+
var AgentSessionRetryEpisode = class {
|
|
5617
|
+
constructor(retry) {
|
|
5618
|
+
this.retry = retry;
|
|
5619
|
+
this.replayCircuit = new ReplayStallCircuit(retry?.systemicReplayStallLimit);
|
|
5620
|
+
}
|
|
5621
|
+
retry;
|
|
5622
|
+
retryAttempts = /* @__PURE__ */ new Map();
|
|
5623
|
+
total = 0;
|
|
5624
|
+
replayCircuit;
|
|
5625
|
+
get totalRetryAttempts() {
|
|
5626
|
+
return this.total;
|
|
5627
|
+
}
|
|
5628
|
+
/** A healthy response ends the current episode and proves endpoint liveness. */
|
|
5629
|
+
succeed() {
|
|
5630
|
+
this.replayCircuit.recordHealthyTurn();
|
|
5631
|
+
const attempts = this.total;
|
|
5632
|
+
this.resetAttempts();
|
|
5633
|
+
return attempts;
|
|
5634
|
+
}
|
|
5635
|
+
/** Cancellation/invalidation resets counters, never the endpoint-health circuit. */
|
|
5636
|
+
resetAttempts() {
|
|
5637
|
+
this.retryAttempts.clear();
|
|
5638
|
+
this.total = 0;
|
|
5639
|
+
}
|
|
5640
|
+
next(failure) {
|
|
5641
|
+
if (!this.retry || !failure.retryable) {
|
|
5642
|
+
if (failure.kind !== "unknown" && failure.kind !== "internal_error") {
|
|
5643
|
+
this.replayCircuit.recordHealthyTurn();
|
|
5644
|
+
}
|
|
5645
|
+
const totalRetryAttempts2 = this.total;
|
|
5646
|
+
this.resetAttempts();
|
|
5647
|
+
return { action: "terminal", failure, totalRetryAttempts: totalRetryAttempts2 };
|
|
5648
|
+
}
|
|
5649
|
+
if (failure.replayExempt === true && this.replayCircuit.isOpen) {
|
|
5650
|
+
const openFailure = asTerminalFailure(
|
|
5651
|
+
failure,
|
|
5652
|
+
`${failure.message} (replay circuit open: ${this.replayCircuit.consecutiveTurnStalls} consecutive turns ended by timeout-replay exhaustion)`
|
|
5653
|
+
);
|
|
5654
|
+
const totalRetryAttempts2 = this.total;
|
|
5655
|
+
this.resetAttempts();
|
|
5656
|
+
return { action: "exhausted", failure: openFailure, maxRetries: 0, totalRetryAttempts: totalRetryAttempts2, reason: "circuit_open" };
|
|
5657
|
+
}
|
|
5658
|
+
const maxRetries = getAgentSessionRetryLimit(this.retry, failure);
|
|
5659
|
+
const kindAttempts = (this.retryAttempts.get(failure.kind) ?? 0) + 1;
|
|
5660
|
+
this.retryAttempts.set(failure.kind, kindAttempts);
|
|
5661
|
+
this.total += 1;
|
|
5662
|
+
const totalRetryAttempts = this.total;
|
|
5663
|
+
const totalBudget = this.retry.maxTotalRetries;
|
|
5664
|
+
const kindExhausted = kindAttempts > maxRetries;
|
|
5665
|
+
const totalExhausted = totalBudget !== void 0 && totalRetryAttempts > totalBudget;
|
|
5666
|
+
const delayMs = resolveRetryDelay(
|
|
5667
|
+
(this.retry.baseDelayMs ?? 2e3) * 2 ** (kindAttempts - 1),
|
|
5668
|
+
this.retry.maxDelayMs,
|
|
5669
|
+
failure.retryAfterMs
|
|
5670
|
+
);
|
|
5671
|
+
if (kindExhausted || totalExhausted || delayMs === void 0) {
|
|
5672
|
+
const terminalFailure = asTerminalFailure(
|
|
5673
|
+
failure,
|
|
5674
|
+
delayMs === void 0 ? `${failure.message} (Retry-After exceeds the allowed wait; automatic recovery paused)` : failure.message
|
|
5675
|
+
);
|
|
5676
|
+
const circuit = failure.replayExempt === true ? this.replayCircuit.recordReplayExhaustion() : void 0;
|
|
5677
|
+
this.resetAttempts();
|
|
5678
|
+
return {
|
|
5679
|
+
action: "exhausted",
|
|
5680
|
+
failure: terminalFailure,
|
|
5681
|
+
maxRetries: totalExhausted ? totalBudget : maxRetries,
|
|
5682
|
+
totalRetryAttempts,
|
|
5683
|
+
reason: delayMs === void 0 ? "retry_after" : totalExhausted ? "total_budget" : "kind_budget",
|
|
5684
|
+
...circuit ? { circuit } : {}
|
|
5685
|
+
};
|
|
5686
|
+
}
|
|
5687
|
+
return {
|
|
5688
|
+
action: "retry",
|
|
5689
|
+
failure,
|
|
5690
|
+
attempt: kindAttempts,
|
|
5691
|
+
maxRetries,
|
|
5692
|
+
delayMs,
|
|
5693
|
+
totalRetryAttempts
|
|
5694
|
+
};
|
|
5695
|
+
}
|
|
5696
|
+
};
|
|
5697
|
+
|
|
5589
5698
|
// ../../packages/agent-framework/src/session/controller/retry-policy.ts
|
|
5590
5699
|
function defaultSetTimeout4(callback, delay) {
|
|
5591
5700
|
return setTimeout(callback, delay);
|
|
@@ -5600,21 +5709,14 @@ var AgentSessionRetryController = class {
|
|
|
5600
5709
|
this.removeTrailingAssistantError = removeTrailingAssistantError2;
|
|
5601
5710
|
this.continueTurn = continueTurn;
|
|
5602
5711
|
this.setRecoveryState = setRecoveryState;
|
|
5603
|
-
this.
|
|
5712
|
+
this.episode = new AgentSessionRetryEpisode(retry);
|
|
5604
5713
|
}
|
|
5605
5714
|
retry;
|
|
5606
5715
|
emit;
|
|
5607
5716
|
removeTrailingAssistantError;
|
|
5608
5717
|
continueTurn;
|
|
5609
5718
|
setRecoveryState;
|
|
5610
|
-
|
|
5611
|
-
* 10-attempt network budget must not be eaten by interleaved provider
|
|
5612
|
-
* errors, and vice versa), so each kind counts its own attempts. */
|
|
5613
|
-
retryAttempts = /* @__PURE__ */ new Map();
|
|
5614
|
-
/** Total attempts across kinds in this recovery episode, for reporting. */
|
|
5615
|
-
totalRetryAttempts = 0;
|
|
5616
|
-
/** L2 systemic-stall circuit (see replay-stall-circuit.ts). */
|
|
5617
|
-
replayCircuit;
|
|
5719
|
+
episode;
|
|
5618
5720
|
retryTimer;
|
|
5619
5721
|
retryEpoch = 0;
|
|
5620
5722
|
pendingRetry;
|
|
@@ -5628,72 +5730,45 @@ var AgentSessionRetryController = class {
|
|
|
5628
5730
|
waitForRetry() {
|
|
5629
5731
|
return this.pendingRetry ?? Promise.resolve();
|
|
5630
5732
|
}
|
|
5631
|
-
/** L2 circuit: healthy turn (success or an actively-answered failure). */
|
|
5632
|
-
noteEndpointHealthy() {
|
|
5633
|
-
this.replayCircuit.recordHealthyTurn();
|
|
5634
|
-
}
|
|
5635
5733
|
async handleTurnEnd(message, failure) {
|
|
5636
5734
|
if (!this.retry || !message || message.role !== "assistant") {
|
|
5637
5735
|
this.settleStrandedRetry(failure);
|
|
5638
5736
|
return;
|
|
5639
5737
|
}
|
|
5640
5738
|
if (!failure) {
|
|
5641
|
-
this.
|
|
5642
|
-
if (
|
|
5643
|
-
this.emit({ type: "retry_succeeded", attempts
|
|
5644
|
-
this.emit({ type: "turn_retry_succeeded", attempts
|
|
5645
|
-
this.resetAttempts();
|
|
5739
|
+
const attempts = this.episode.succeed();
|
|
5740
|
+
if (attempts > 0) {
|
|
5741
|
+
this.emit({ type: "retry_succeeded", attempts });
|
|
5742
|
+
this.emit({ type: "turn_retry_succeeded", attempts });
|
|
5646
5743
|
this.setRecoveryState("none");
|
|
5647
5744
|
this.resolvePending();
|
|
5648
5745
|
}
|
|
5649
5746
|
return;
|
|
5650
5747
|
}
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
}
|
|
5655
|
-
if (this.totalRetryAttempts > 0) {
|
|
5656
|
-
this.resetAttempts();
|
|
5748
|
+
const decision = this.episode.next(failure);
|
|
5749
|
+
if (decision.action === "terminal") {
|
|
5750
|
+
if (decision.totalRetryAttempts > 0) {
|
|
5657
5751
|
this.setRecoveryState("none");
|
|
5658
5752
|
this.resolvePending();
|
|
5659
5753
|
}
|
|
5660
5754
|
return;
|
|
5661
5755
|
}
|
|
5662
|
-
if (
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
this.resolvePending();
|
|
5670
|
-
return;
|
|
5671
|
-
}
|
|
5672
|
-
const maxRetries = Math.min(this.retry.maxRetriesForFailure?.(failure) ?? this.retry.maxRetries ?? 3, failure.kind === "provider_confirmation" ? 1 : Infinity);
|
|
5673
|
-
const kindAttempts = (this.retryAttempts.get(failure.kind) ?? 0) + 1;
|
|
5674
|
-
this.retryAttempts.set(failure.kind, kindAttempts);
|
|
5675
|
-
this.totalRetryAttempts += 1;
|
|
5676
|
-
const totalBudget = this.retry.maxTotalRetries;
|
|
5677
|
-
const kindExhausted = kindAttempts > maxRetries;
|
|
5678
|
-
const totalExhausted = totalBudget !== void 0 && this.totalRetryAttempts > totalBudget;
|
|
5679
|
-
const delayMs = resolveRetryDelay((this.retry.baseDelayMs ?? 2e3) * 2 ** (kindAttempts - 1), this.retry.maxDelayMs, failure.retryAfterMs);
|
|
5680
|
-
if (kindExhausted || totalExhausted || delayMs === void 0) {
|
|
5681
|
-
const terminalFailure = asTerminalFailure(failure, delayMs === void 0 ? `${failure.message} (Retry-After exceeds the allowed wait; automatic recovery paused)` : failure.message);
|
|
5682
|
-
if (failure.replayExempt === true) {
|
|
5683
|
-
const outcome = this.replayCircuit.recordReplayExhaustion();
|
|
5684
|
-
if (outcome.opened) {
|
|
5685
|
-
this.emit({ type: "retry_exhausted", errorMessage: `systemic timeout-stall: ${outcome.consecutive} consecutive turns ended by timeout-replay exhaustion`, maxRetries: totalExhausted ? totalBudget : maxRetries });
|
|
5686
|
-
}
|
|
5756
|
+
if (decision.action === "exhausted") {
|
|
5757
|
+
if (decision.circuit?.opened) {
|
|
5758
|
+
this.emit({
|
|
5759
|
+
type: "retry_exhausted",
|
|
5760
|
+
errorMessage: `systemic timeout-stall: ${decision.circuit.consecutive} consecutive turns ended by timeout-replay exhaustion`,
|
|
5761
|
+
maxRetries: decision.maxRetries
|
|
5762
|
+
});
|
|
5687
5763
|
}
|
|
5688
|
-
this.emit({ type: "retry_exhausted", errorMessage:
|
|
5689
|
-
this.emit({ type: "turn_failed_terminal", failure:
|
|
5764
|
+
this.emit({ type: "retry_exhausted", errorMessage: decision.failure.message, maxRetries: decision.maxRetries });
|
|
5765
|
+
this.emit({ type: "turn_failed_terminal", failure: decision.failure, message });
|
|
5690
5766
|
this.emit({ type: "turn_settled" });
|
|
5691
|
-
this.resetAttempts();
|
|
5692
5767
|
this.setRecoveryState("terminal");
|
|
5693
5768
|
this.resolvePending();
|
|
5694
5769
|
return;
|
|
5695
5770
|
}
|
|
5696
|
-
const attempt =
|
|
5771
|
+
const { attempt, maxRetries, delayMs } = decision;
|
|
5697
5772
|
this.ensurePending();
|
|
5698
5773
|
const epoch = this.retryEpoch;
|
|
5699
5774
|
const pending = this.pendingRetry;
|
|
@@ -5720,7 +5795,7 @@ var AgentSessionRetryController = class {
|
|
|
5720
5795
|
this.emit({ type: "retry_exhausted", errorMessage, maxRetries });
|
|
5721
5796
|
this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
|
|
5722
5797
|
this.emit({ type: "turn_settled" });
|
|
5723
|
-
this.resetAttempts();
|
|
5798
|
+
this.episode.resetAttempts();
|
|
5724
5799
|
this.setRecoveryState("terminal");
|
|
5725
5800
|
this.resolvePending(pending);
|
|
5726
5801
|
});
|
|
@@ -5745,24 +5820,20 @@ var AgentSessionRetryController = class {
|
|
|
5745
5820
|
userAbort: false,
|
|
5746
5821
|
terminal: true
|
|
5747
5822
|
});
|
|
5748
|
-
const maxRetries =
|
|
5823
|
+
const maxRetries = getAgentSessionRetryLimit(this.retry, failure);
|
|
5749
5824
|
this.emit({ type: "retry_exhausted", errorMessage: terminalFailure.message, maxRetries });
|
|
5750
5825
|
this.emit({ type: "turn_failed_terminal", failure: terminalFailure });
|
|
5751
5826
|
this.emit({ type: "turn_settled" });
|
|
5752
|
-
this.resetAttempts();
|
|
5827
|
+
this.episode.resetAttempts();
|
|
5753
5828
|
this.setRecoveryState("terminal");
|
|
5754
5829
|
this.resolvePending();
|
|
5755
5830
|
}
|
|
5756
5831
|
cancel() {
|
|
5757
5832
|
this.retryEpoch++;
|
|
5758
5833
|
this.cancelTimerOnly();
|
|
5759
|
-
this.resetAttempts();
|
|
5834
|
+
this.episode.resetAttempts();
|
|
5760
5835
|
this.resolvePending();
|
|
5761
5836
|
}
|
|
5762
|
-
resetAttempts() {
|
|
5763
|
-
this.retryAttempts.clear();
|
|
5764
|
-
this.totalRetryAttempts = 0;
|
|
5765
|
-
}
|
|
5766
5837
|
cancelTimerOnly() {
|
|
5767
5838
|
if (this.retryTimer === void 0) {
|
|
5768
5839
|
return;
|
|
@@ -6101,7 +6172,8 @@ async function runAgentSessionCompaction(params) {
|
|
|
6101
6172
|
const preparation = prepareSessionCompaction({
|
|
6102
6173
|
sessionManager: params.sessionManager,
|
|
6103
6174
|
messages: params.agent.state.messages,
|
|
6104
|
-
budgetTokens: params.compactionBudgetTokens?.()
|
|
6175
|
+
budgetTokens: params.compactionBudgetTokens?.(),
|
|
6176
|
+
modelView: params.compaction.modelView
|
|
6105
6177
|
});
|
|
6106
6178
|
let result = await params.compaction.compact({
|
|
6107
6179
|
reason: params.reason,
|
|
@@ -6380,6 +6452,8 @@ var AgentSessionController = class {
|
|
|
6380
6452
|
autoContinue;
|
|
6381
6453
|
unsubscribeAgent;
|
|
6382
6454
|
unsubscribeSemantic;
|
|
6455
|
+
unsubscribeToolContinuation;
|
|
6456
|
+
compactionOwner;
|
|
6383
6457
|
lastSyncedMessageCount = 0;
|
|
6384
6458
|
disposed = false;
|
|
6385
6459
|
transitioning = false;
|
|
@@ -6483,6 +6557,7 @@ var AgentSessionController = class {
|
|
|
6483
6557
|
});
|
|
6484
6558
|
this.syncFromActiveSession();
|
|
6485
6559
|
this.unsubscribeAgent = this.agent.subscribe((event) => this.handleAgentEvent(event));
|
|
6560
|
+
this.unsubscribeToolContinuation = this.agent.setBeforeToolContinuation((signal) => this.runPreRequestAutoCompactIfNeeded(signal));
|
|
6486
6561
|
this.unsubscribeSemantic = this.agent.subscribeSemantic((event) => {
|
|
6487
6562
|
this.emit({ type: "semantic_event", event });
|
|
6488
6563
|
});
|
|
@@ -6533,6 +6608,7 @@ var AgentSessionController = class {
|
|
|
6533
6608
|
this.transitioning = true;
|
|
6534
6609
|
try {
|
|
6535
6610
|
this.generation++;
|
|
6611
|
+
if (this.isStreaming) this.agent.resetProcessingState();
|
|
6536
6612
|
this.cancelScheduledWork();
|
|
6537
6613
|
this.recoveryState = "none";
|
|
6538
6614
|
this.pendingCompactionFailure = void 0;
|
|
@@ -6664,48 +6740,61 @@ var AgentSessionController = class {
|
|
|
6664
6740
|
const generation = this.generation;
|
|
6665
6741
|
if (this.disposed || this.transitioning) throw new Error("Controller disposed or transitioning");
|
|
6666
6742
|
const isCurrent = () => generation === this.generation && !this.disposed;
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6743
|
+
if (this.compactionOwner) throw new Error("Compaction already running");
|
|
6744
|
+
const owner = this.compactionOwner = {};
|
|
6745
|
+
try {
|
|
6746
|
+
return await this.convergence.run(() => runAgentSessionControllerAutoCompact({
|
|
6747
|
+
isCurrent,
|
|
6748
|
+
reason,
|
|
6749
|
+
willRetry,
|
|
6750
|
+
signal: options?.signal,
|
|
6751
|
+
compaction: this.compaction,
|
|
6752
|
+
agent: this.agent,
|
|
6753
|
+
sessionManager: this.sessionManager,
|
|
6754
|
+
cwd: this.cwd,
|
|
6755
|
+
pendingFailure: this.pendingCompactionFailure,
|
|
6756
|
+
emit: (event) => {
|
|
6757
|
+
if (isCurrent()) this.emit(event);
|
|
6758
|
+
},
|
|
6759
|
+
setRecoveryState: (state) => {
|
|
6760
|
+
this.recoveryState = state;
|
|
6761
|
+
},
|
|
6762
|
+
getRecoveryState: () => this.recoveryState,
|
|
6763
|
+
getDisposed: () => this.disposed,
|
|
6764
|
+
cancelRetry: () => this.retryController.cancel(),
|
|
6765
|
+
clearPendingFailure: () => {
|
|
6766
|
+
this.pendingCompactionFailure = void 0;
|
|
6767
|
+
},
|
|
6768
|
+
getCompactionBudgetTokens: this.compactionSettings.getCompactionBudgetTokens,
|
|
6769
|
+
removeTrailingAssistantError: () => this.removeTrailingAssistantError(),
|
|
6770
|
+
replaceMessagesAndMarkSynced: (messages) => this.replaceMessagesAndMarkSynced(messages),
|
|
6771
|
+
markSyncedToAgentState: () => this.markSyncedToAgentState(),
|
|
6772
|
+
continueTurn: async () => {
|
|
6773
|
+
if (!isCurrent() || options?.signal?.aborted) return;
|
|
6774
|
+
if (this.compactionOwner === owner) this.compactionOwner = void 0;
|
|
6775
|
+
this.recoveryState = "none";
|
|
6776
|
+
await this.continue();
|
|
6777
|
+
},
|
|
6778
|
+
handleAgentEndHook: (messages) => this.handleAgentEndHook(messages),
|
|
6779
|
+
emitSettled: options?.emitSettled,
|
|
6780
|
+
runAfterAgentEndHook: options?.runAfterAgentEndHook,
|
|
6781
|
+
cancelOnAbort: options?.cancelOnAbort
|
|
6782
|
+
}));
|
|
6783
|
+
} finally {
|
|
6784
|
+
if (this.compactionOwner === owner) this.compactionOwner = void 0;
|
|
6785
|
+
}
|
|
6699
6786
|
}
|
|
6700
6787
|
dispose() {
|
|
6701
6788
|
if (this.disposed) return;
|
|
6702
6789
|
this.disposed = true;
|
|
6790
|
+
if (this.isStreaming) this.agent.resetProcessingState();
|
|
6703
6791
|
this.agent.releaseTransport?.();
|
|
6704
6792
|
this.generation++;
|
|
6705
6793
|
this.pendingEndOperations.clear();
|
|
6706
6794
|
this.cancelScheduledWork();
|
|
6707
6795
|
this.unsubscribeAgent?.();
|
|
6708
6796
|
this.unsubscribeSemantic?.();
|
|
6797
|
+
this.unsubscribeToolContinuation?.();
|
|
6709
6798
|
this.queue.clear();
|
|
6710
6799
|
this.convergence.invalidate("dispose");
|
|
6711
6800
|
this.listeners.clear();
|
|
@@ -6729,19 +6818,26 @@ var AgentSessionController = class {
|
|
|
6729
6818
|
});
|
|
6730
6819
|
}
|
|
6731
6820
|
}
|
|
6732
|
-
async runPreRequestAutoCompactIfNeeded() {
|
|
6821
|
+
async runPreRequestAutoCompactIfNeeded(signal) {
|
|
6733
6822
|
const generation = this.generation;
|
|
6734
6823
|
return runPreRequestAutoCompact({
|
|
6735
6824
|
isCurrent: () => generation === this.generation && !this.disposed,
|
|
6736
6825
|
disposed: this.disposed,
|
|
6737
6826
|
runtimeGuardTriggered: this.runtimeGuard.triggered,
|
|
6738
6827
|
isStreaming: this.isStreaming,
|
|
6739
|
-
recoveryActive: this.isRecoveryActive(),
|
|
6828
|
+
recoveryActive: () => this.isRecoveryActive(),
|
|
6740
6829
|
agent: this.agent,
|
|
6741
6830
|
sessionManager: this.sessionManager,
|
|
6742
6831
|
cwd: this.cwd,
|
|
6743
6832
|
getAutoCompactDecision: this.compactionSettings.getAutoCompactDecision,
|
|
6744
|
-
|
|
6833
|
+
toolContinuation: signal ? { signal, sync: async () => {
|
|
6834
|
+
await this.pendingEndOperations.wait();
|
|
6835
|
+
if (generation === this.generation && !this.disposed && !signal.aborted) await this.syncNewAgentMessages();
|
|
6836
|
+
} } : void 0,
|
|
6837
|
+
runAutoCompact: async () => {
|
|
6838
|
+
const result = await this.runAutoCompact("threshold", false, { signal, emitSettled: false, runAfterAgentEndHook: false, cancelOnAbort: false });
|
|
6839
|
+
if (signal && (!result || result.aborted)) throw new Error("Tool-continuation compaction was aborted");
|
|
6840
|
+
},
|
|
6745
6841
|
setRecoveryState: (state) => {
|
|
6746
6842
|
this.recoveryState = state;
|
|
6747
6843
|
}
|
|
@@ -6886,6 +6982,7 @@ var AgentSessionController = class {
|
|
|
6886
6982
|
return this.recoveryState !== "none" || this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact;
|
|
6887
6983
|
}
|
|
6888
6984
|
cancelScheduledWork() {
|
|
6985
|
+
this.compactionOwner = void 0;
|
|
6889
6986
|
this.retryController.cancel();
|
|
6890
6987
|
this.autoCompact.cancel();
|
|
6891
6988
|
this.queueDispatcher.cancel();
|
|
@@ -8241,6 +8338,9 @@ function withSubagentRunContext(stream, hooks) {
|
|
|
8241
8338
|
${SUBAGENT_FINALIZATION_HINT}` } : context, options);
|
|
8242
8339
|
};
|
|
8243
8340
|
}
|
|
8341
|
+
function isRetryableSubagentFailure(message) {
|
|
8342
|
+
return isRetryableSubagentFailureFrom({ errorMessage: message });
|
|
8343
|
+
}
|
|
8244
8344
|
function isRetryableSubagentFailureFrom(facts) {
|
|
8245
8345
|
if (facts.retryable === false) return false;
|
|
8246
8346
|
if (facts.replayClass === "stream_lost" || facts.replayClass === "stream_lost_partial") return true;
|
|
@@ -8262,90 +8362,366 @@ function cancellableBackoff(ms, signal) {
|
|
|
8262
8362
|
if (signal?.aborted) finish();
|
|
8263
8363
|
});
|
|
8264
8364
|
}
|
|
8365
|
+
function factsFromMessage(message) {
|
|
8366
|
+
return {
|
|
8367
|
+
errorMessage: message.errorMessage,
|
|
8368
|
+
retryable: message.retryable,
|
|
8369
|
+
replayClass: message.replayClass,
|
|
8370
|
+
replayEligible: message.replayEligible,
|
|
8371
|
+
remoteFailure: message.remoteFailure,
|
|
8372
|
+
stopReason: message.stopReason
|
|
8373
|
+
};
|
|
8374
|
+
}
|
|
8265
8375
|
function factsOf(error) {
|
|
8266
8376
|
if (error instanceof Error) {
|
|
8267
8377
|
const carrier = error;
|
|
8268
|
-
return {
|
|
8378
|
+
return {
|
|
8379
|
+
errorMessage: carrier.subagentFailure?.errorMessage ?? error.message,
|
|
8380
|
+
retryable: carrier.subagentFailure?.retryable ?? carrier.retryable,
|
|
8381
|
+
replayClass: carrier.subagentFailure?.replayClass ?? carrier.replayClass,
|
|
8382
|
+
replayEligible: carrier.subagentFailure?.replayEligible ?? carrier.replayEligible,
|
|
8383
|
+
remoteFailure: carrier.subagentFailure?.remoteFailure ?? carrier.remoteFailure,
|
|
8384
|
+
stopReason: carrier.subagentFailure?.stopReason ?? carrier.stopReason ?? (carrier.subagentFailure?.classification?.userAbort ? "aborted" : "error")
|
|
8385
|
+
};
|
|
8269
8386
|
}
|
|
8270
|
-
return { errorMessage: String(error) };
|
|
8387
|
+
return { errorMessage: String(error), stopReason: "error" };
|
|
8271
8388
|
}
|
|
8272
|
-
function
|
|
8389
|
+
function assistantFromFacts(facts) {
|
|
8390
|
+
return {
|
|
8391
|
+
role: "assistant",
|
|
8392
|
+
api: "unknown",
|
|
8393
|
+
provider: "unknown",
|
|
8394
|
+
model: "unknown",
|
|
8395
|
+
content: [],
|
|
8396
|
+
timestamp: 0,
|
|
8397
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
|
|
8398
|
+
stopReason: facts.stopReason ?? "error",
|
|
8399
|
+
errorMessage: facts.errorMessage,
|
|
8400
|
+
...facts.retryable !== void 0 ? { retryable: facts.retryable } : {},
|
|
8401
|
+
...facts.replayClass !== void 0 ? { replayClass: facts.replayClass } : {},
|
|
8402
|
+
...facts.replayEligible !== void 0 ? { replayEligible: facts.replayEligible } : {},
|
|
8403
|
+
...facts.remoteFailure !== void 0 ? { remoteFailure: facts.remoteFailure } : {}
|
|
8404
|
+
};
|
|
8405
|
+
}
|
|
8406
|
+
function boundedClassification(failure) {
|
|
8407
|
+
return { ...failure, message: failure.message.slice(0, 2e3) };
|
|
8408
|
+
}
|
|
8409
|
+
function failureDetails(facts, classification, recovery) {
|
|
8410
|
+
return {
|
|
8411
|
+
errorMessage: (classification.message || facts.errorMessage || "Subagent response failed").slice(0, 2e3),
|
|
8412
|
+
...facts.retryable !== void 0 ? { retryable: facts.retryable } : {},
|
|
8413
|
+
...facts.replayClass !== void 0 ? { replayClass: facts.replayClass } : {},
|
|
8414
|
+
...facts.replayEligible !== void 0 ? { replayEligible: facts.replayEligible } : {},
|
|
8415
|
+
...facts.remoteFailure !== void 0 ? {
|
|
8416
|
+
remoteFailure: {
|
|
8417
|
+
category: facts.remoteFailure.category,
|
|
8418
|
+
...typeof facts.remoteFailure.retryAfterMs === "number" && Number.isFinite(facts.remoteFailure.retryAfterMs) && facts.remoteFailure.retryAfterMs >= 0 ? { retryAfterMs: facts.remoteFailure.retryAfterMs } : {}
|
|
8419
|
+
}
|
|
8420
|
+
} : {},
|
|
8421
|
+
...facts.stopReason === "aborted" ? { stopReason: facts.stopReason } : {},
|
|
8422
|
+
classification: boundedClassification(classification),
|
|
8423
|
+
recovery
|
|
8424
|
+
};
|
|
8425
|
+
}
|
|
8426
|
+
function legacyRecovery(backoffMs) {
|
|
8427
|
+
return {
|
|
8428
|
+
policy: {
|
|
8429
|
+
isRetryableError: (message) => isRetryableSubagentFailure(message),
|
|
8430
|
+
maxRetries: 2,
|
|
8431
|
+
baseDelayMs: backoffMs
|
|
8432
|
+
},
|
|
8433
|
+
classifyFailure: (context) => {
|
|
8434
|
+
const facts = context.message?.role === "assistant" ? factsFromMessage(context.message) : { errorMessage: context.errorMessage, stopReason: "error" };
|
|
8435
|
+
const retryable = isRetryableSubagentFailureFrom({ ...facts, errorMessage: context.errorMessage });
|
|
8436
|
+
return {
|
|
8437
|
+
kind: retryable ? "provider_transient" : "unknown",
|
|
8438
|
+
retryable,
|
|
8439
|
+
compactable: false,
|
|
8440
|
+
userAbort: false,
|
|
8441
|
+
terminal: !retryable,
|
|
8442
|
+
message: context.errorMessage
|
|
8443
|
+
};
|
|
8444
|
+
}
|
|
8445
|
+
};
|
|
8446
|
+
}
|
|
8447
|
+
function resolveRecovery(options, backoffMs) {
|
|
8448
|
+
if (!options) return legacyRecovery(backoffMs);
|
|
8449
|
+
const fallback = legacyRecovery(backoffMs);
|
|
8450
|
+
return {
|
|
8451
|
+
policy: options.policy ?? fallback.policy,
|
|
8452
|
+
// An explicitly supplied policy without a classifier must reach its own
|
|
8453
|
+
// isRetryableError predicate, exactly as in the main controller. Only the
|
|
8454
|
+
// no-host compatible default installs the legacy prose classifier.
|
|
8455
|
+
classifyFailure: options.classifyFailure ?? (options.policy ? void 0 : fallback.classifyFailure)
|
|
8456
|
+
};
|
|
8457
|
+
}
|
|
8458
|
+
function classify(facts, recovery) {
|
|
8459
|
+
const message = assistantFromFacts(facts);
|
|
8460
|
+
return createFailureClassification({
|
|
8461
|
+
message,
|
|
8462
|
+
errorMessage: facts.errorMessage ?? "Subagent response failed",
|
|
8463
|
+
phase: "stream"
|
|
8464
|
+
}, recovery.policy, recovery.classifyFailure);
|
|
8465
|
+
}
|
|
8466
|
+
var DEFAULT_ITERATOR_CLEANUP_MS = 2e3;
|
|
8467
|
+
async function closeIterator(iterator, hooks, signal) {
|
|
8468
|
+
const cleanup = Promise.resolve().then(() => iterator.return?.()).then(() => void 0).catch(() => void 0);
|
|
8469
|
+
const remaining = hooks?.hardDeadlineAt === void 0 ? DEFAULT_ITERATOR_CLEANUP_MS : Math.max(0, hooks.hardDeadlineAt - Date.now());
|
|
8470
|
+
const waitMs = Math.min(DEFAULT_ITERATOR_CLEANUP_MS, remaining);
|
|
8471
|
+
if (waitMs <= 0 || signal?.aborted) return;
|
|
8472
|
+
let timer;
|
|
8473
|
+
let onAbort;
|
|
8474
|
+
try {
|
|
8475
|
+
await Promise.race([
|
|
8476
|
+
cleanup,
|
|
8477
|
+
new Promise((resolve2) => {
|
|
8478
|
+
timer = setTimeout(resolve2, waitMs);
|
|
8479
|
+
onAbort = () => resolve2();
|
|
8480
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
8481
|
+
})
|
|
8482
|
+
]);
|
|
8483
|
+
} finally {
|
|
8484
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
8485
|
+
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
8486
|
+
}
|
|
8487
|
+
}
|
|
8488
|
+
function singleResponse(message) {
|
|
8489
|
+
const result = new AssistantMessageEventStream();
|
|
8490
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
8491
|
+
result.push({ type: "error", reason: message.stopReason, error: { ...message, content: [] } });
|
|
8492
|
+
} else {
|
|
8493
|
+
result.push({ type: "done", reason: message.stopReason, message });
|
|
8494
|
+
}
|
|
8495
|
+
result.end();
|
|
8496
|
+
return result;
|
|
8497
|
+
}
|
|
8498
|
+
function throwWithDetails(error, details) {
|
|
8499
|
+
const failure = new Error(details.classification?.message ?? (error instanceof Error ? error.message : String(error)));
|
|
8500
|
+
failure.cause = error;
|
|
8501
|
+
failure.subagentFailure = details;
|
|
8502
|
+
throw failure;
|
|
8503
|
+
}
|
|
8504
|
+
function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivity, hooks, recoveryOptions) {
|
|
8505
|
+
const recovery = resolveRecovery(recoveryOptions, backoffMs);
|
|
8506
|
+
const episode = recoveryOptions?.episode ?? hooks?.recoveryEpisode ?? new AgentSessionRetryEpisode(recovery.policy);
|
|
8507
|
+
if (hooks) hooks.recoveryEpisode = episode;
|
|
8273
8508
|
return async (model, context, options) => {
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8509
|
+
let attempts = 0;
|
|
8510
|
+
let retries = 0;
|
|
8511
|
+
if (hooks) hooks.failure = void 0;
|
|
8512
|
+
try {
|
|
8513
|
+
for (; ; ) {
|
|
8514
|
+
if (hooks) {
|
|
8515
|
+
hooks.terminalText = void 0;
|
|
8516
|
+
hooks.terminalStopReason = void 0;
|
|
8517
|
+
}
|
|
8518
|
+
if (options?.signal?.aborted) {
|
|
8519
|
+
episode.resetAttempts();
|
|
8520
|
+
throw new Error("Subagent aborted by parent");
|
|
8521
|
+
}
|
|
8522
|
+
if (hooks?.hardDeadlineAt !== void 0 && Date.now() >= hooks.hardDeadlineAt) throw new Error("Subagent hard deadline reached");
|
|
8523
|
+
let message;
|
|
8524
|
+
const finishUsage = hooks?.usage ? beginSubagentUsage(hooks.usage, model) : void 0;
|
|
8525
|
+
attempts += 1;
|
|
8287
8526
|
try {
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8527
|
+
const response = await stream(model, context, options);
|
|
8528
|
+
let terminal;
|
|
8529
|
+
const iterator = response[Symbol.asyncIterator]();
|
|
8530
|
+
try {
|
|
8531
|
+
for (; ; ) {
|
|
8532
|
+
const next = await iterator.next();
|
|
8533
|
+
if (next.done) break;
|
|
8534
|
+
try {
|
|
8535
|
+
onActivity?.(next.value.type);
|
|
8536
|
+
} catch {
|
|
8537
|
+
}
|
|
8538
|
+
if (next.value.type === "done") {
|
|
8539
|
+
terminal = next.value.message;
|
|
8540
|
+
finishUsage?.(terminal);
|
|
8541
|
+
}
|
|
8542
|
+
if (next.value.type === "error") {
|
|
8543
|
+
terminal = next.value.error;
|
|
8544
|
+
finishUsage?.(terminal);
|
|
8545
|
+
}
|
|
8546
|
+
if (next.value.type === "done" || next.value.type === "error") {
|
|
8547
|
+
if (hooks && terminal) {
|
|
8548
|
+
const reportTerminal = ["stop", "length", "error", "aborted"].includes(terminal.stopReason) && !terminal.content.some((block) => block.type === "toolCall");
|
|
8549
|
+
hooks.terminalStopReason = reportTerminal ? terminal.stopReason : void 0;
|
|
8550
|
+
hooks.terminalText = reportTerminal ? terminal.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").slice(0, 24001) : void 0;
|
|
8551
|
+
}
|
|
8552
|
+
break;
|
|
8553
|
+
}
|
|
8307
8554
|
}
|
|
8555
|
+
} finally {
|
|
8556
|
+
await closeIterator(iterator, hooks, options?.signal);
|
|
8308
8557
|
}
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8558
|
+
if (!terminal) throw new Error("Provider protocol error: response ended without a terminal event");
|
|
8559
|
+
message = terminal;
|
|
8560
|
+
} catch (error) {
|
|
8561
|
+
finishUsage?.();
|
|
8562
|
+
if (options?.signal?.aborted) {
|
|
8563
|
+
episode.resetAttempts();
|
|
8564
|
+
throw error;
|
|
8313
8565
|
}
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8566
|
+
const facts2 = factsOf(error);
|
|
8567
|
+
if (facts2.stopReason === "aborted") {
|
|
8568
|
+
episode.resetAttempts();
|
|
8569
|
+
const classification3 = {
|
|
8570
|
+
kind: "user_abort",
|
|
8571
|
+
retryable: false,
|
|
8572
|
+
compactable: false,
|
|
8573
|
+
userAbort: true,
|
|
8574
|
+
terminal: true,
|
|
8575
|
+
message: facts2.errorMessage ?? "Subagent aborted"
|
|
8576
|
+
};
|
|
8577
|
+
const details = failureDetails(facts2, classification3, { outcome: "terminal", attempts, retries, reason: "cancelled" });
|
|
8578
|
+
if (hooks) hooks.failure = details;
|
|
8579
|
+
throwWithDetails(error, details);
|
|
8323
8580
|
}
|
|
8324
|
-
|
|
8325
|
-
|
|
8581
|
+
const classification2 = classify(facts2, recovery);
|
|
8582
|
+
const failure2 = await resolveFailure({
|
|
8583
|
+
facts: facts2,
|
|
8584
|
+
classification: classification2,
|
|
8585
|
+
attempts,
|
|
8586
|
+
retries,
|
|
8587
|
+
episode,
|
|
8588
|
+
hooks,
|
|
8589
|
+
onRetry,
|
|
8590
|
+
options
|
|
8591
|
+
});
|
|
8592
|
+
if (failure2.retry) {
|
|
8593
|
+
retries += 1;
|
|
8594
|
+
continue;
|
|
8595
|
+
}
|
|
8596
|
+
throwWithDetails(error, failure2.details);
|
|
8326
8597
|
}
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
if (message.stopReason === "error" && attempt < 2 && isRetryableSubagentFailureFrom({ errorMessage: message.errorMessage, retryable: message.retryable, replayClass: message.replayClass })) {
|
|
8331
|
-
try {
|
|
8332
|
-
onRetry?.();
|
|
8333
|
-
} catch {
|
|
8598
|
+
if (options?.signal?.aborted) {
|
|
8599
|
+
episode.resetAttempts();
|
|
8600
|
+
throw new Error("Subagent aborted by parent");
|
|
8334
8601
|
}
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8602
|
+
if (hooks?.hardDeadlineAt !== void 0 && Date.now() >= hooks.hardDeadlineAt) {
|
|
8603
|
+
episode.resetAttempts();
|
|
8604
|
+
throw new Error("Subagent hard deadline reached");
|
|
8605
|
+
}
|
|
8606
|
+
if (message.stopReason !== "error" && message.stopReason !== "aborted") {
|
|
8607
|
+
episode.succeed();
|
|
8608
|
+
if (hooks) hooks.failure = void 0;
|
|
8609
|
+
return singleResponse(message);
|
|
8610
|
+
}
|
|
8611
|
+
if (message.stopReason === "aborted") {
|
|
8612
|
+
episode.resetAttempts();
|
|
8613
|
+
if (hooks) hooks.failure = void 0;
|
|
8614
|
+
return singleResponse(message);
|
|
8615
|
+
}
|
|
8616
|
+
const facts = factsFromMessage(message);
|
|
8617
|
+
const classification = classify(facts, recovery);
|
|
8618
|
+
const failure = await resolveFailure({
|
|
8619
|
+
facts,
|
|
8620
|
+
classification,
|
|
8621
|
+
attempts,
|
|
8622
|
+
retries,
|
|
8623
|
+
episode,
|
|
8624
|
+
hooks,
|
|
8625
|
+
onRetry,
|
|
8626
|
+
options
|
|
8627
|
+
});
|
|
8628
|
+
if (failure.retry) {
|
|
8629
|
+
retries += 1;
|
|
8630
|
+
continue;
|
|
8631
|
+
}
|
|
8632
|
+
const finalMessage = failure.details.classification?.message && failure.details.classification.message !== message.errorMessage ? { ...message, errorMessage: failure.details.classification.message } : message;
|
|
8633
|
+
return singleResponse(finalMessage);
|
|
8343
8634
|
}
|
|
8344
|
-
|
|
8345
|
-
|
|
8635
|
+
} finally {
|
|
8636
|
+
episode.resetAttempts();
|
|
8346
8637
|
}
|
|
8347
8638
|
};
|
|
8348
8639
|
}
|
|
8640
|
+
async function resolveFailure(params) {
|
|
8641
|
+
const { facts, classification, attempts, retries, episode, hooks, onRetry, options } = params;
|
|
8642
|
+
if (options?.signal?.aborted) {
|
|
8643
|
+
episode.resetAttempts();
|
|
8644
|
+
throw new Error("Subagent aborted by parent");
|
|
8645
|
+
}
|
|
8646
|
+
if (classification.userAbort) {
|
|
8647
|
+
episode.resetAttempts();
|
|
8648
|
+
const cancellation = {
|
|
8649
|
+
...classification,
|
|
8650
|
+
kind: "user_abort",
|
|
8651
|
+
retryable: false,
|
|
8652
|
+
compactable: false,
|
|
8653
|
+
userAbort: true,
|
|
8654
|
+
terminal: true
|
|
8655
|
+
};
|
|
8656
|
+
const details2 = failureDetails({ ...facts, stopReason: "aborted" }, cancellation, {
|
|
8657
|
+
outcome: "terminal",
|
|
8658
|
+
attempts,
|
|
8659
|
+
retries,
|
|
8660
|
+
reason: "cancelled"
|
|
8661
|
+
});
|
|
8662
|
+
if (hooks) hooks.failure = details2;
|
|
8663
|
+
return { retry: false, details: details2 };
|
|
8664
|
+
}
|
|
8665
|
+
if (classification.compactable) {
|
|
8666
|
+
const details2 = failureDetails(facts, classification, {
|
|
8667
|
+
outcome: "overflow",
|
|
8668
|
+
attempts,
|
|
8669
|
+
retries,
|
|
8670
|
+
reason: "compaction_unavailable"
|
|
8671
|
+
});
|
|
8672
|
+
if (hooks) hooks.failure = details2;
|
|
8673
|
+
return { retry: false, details: details2 };
|
|
8674
|
+
}
|
|
8675
|
+
let decision = episode.next(classification);
|
|
8676
|
+
if (decision.action === "retry") {
|
|
8677
|
+
const remaining = hooks?.hardDeadlineAt === void 0 ? Infinity : hooks.hardDeadlineAt - Date.now();
|
|
8678
|
+
if (remaining <= 0) {
|
|
8679
|
+
episode.resetAttempts();
|
|
8680
|
+
throw new Error("Subagent hard deadline reached");
|
|
8681
|
+
}
|
|
8682
|
+
if (decision.delayMs > remaining) {
|
|
8683
|
+
const deadlineClassification = {
|
|
8684
|
+
...classification,
|
|
8685
|
+
retryable: false,
|
|
8686
|
+
terminal: true,
|
|
8687
|
+
message: `${classification.message} (recovery delay ${decision.delayMs}ms exceeds the remaining subagent deadline ${Math.max(0, Math.round(remaining))}ms)`
|
|
8688
|
+
};
|
|
8689
|
+
const details2 = failureDetails(facts, deadlineClassification, {
|
|
8690
|
+
outcome: "exhausted",
|
|
8691
|
+
attempts,
|
|
8692
|
+
retries,
|
|
8693
|
+
maxRetries: decision.maxRetries,
|
|
8694
|
+
reason: "deadline"
|
|
8695
|
+
});
|
|
8696
|
+
if (hooks) hooks.failure = details2;
|
|
8697
|
+
return { retry: false, details: details2 };
|
|
8698
|
+
}
|
|
8699
|
+
try {
|
|
8700
|
+
onRetry?.();
|
|
8701
|
+
} catch {
|
|
8702
|
+
}
|
|
8703
|
+
await cancellableBackoff(decision.delayMs, options?.signal);
|
|
8704
|
+
if (options?.signal?.aborted) {
|
|
8705
|
+
episode.resetAttempts();
|
|
8706
|
+
throw new Error("Subagent aborted by parent");
|
|
8707
|
+
}
|
|
8708
|
+
if (hooks?.hardDeadlineAt !== void 0 && Date.now() >= hooks.hardDeadlineAt) {
|
|
8709
|
+
episode.resetAttempts();
|
|
8710
|
+
throw new Error("Subagent hard deadline reached");
|
|
8711
|
+
}
|
|
8712
|
+
return { retry: true };
|
|
8713
|
+
}
|
|
8714
|
+
const finalClassification = decision.action === "exhausted" ? decision.failure : classification;
|
|
8715
|
+
const details = failureDetails(facts, finalClassification, {
|
|
8716
|
+
outcome: decision.action === "exhausted" ? "exhausted" : "terminal",
|
|
8717
|
+
attempts,
|
|
8718
|
+
retries,
|
|
8719
|
+
...decision.action !== "terminal" ? { maxRetries: decision.maxRetries } : {},
|
|
8720
|
+
...decision.action === "exhausted" ? { reason: decision.reason } : {}
|
|
8721
|
+
});
|
|
8722
|
+
if (hooks) hooks.failure = details;
|
|
8723
|
+
return { retry: false, details };
|
|
8724
|
+
}
|
|
8349
8725
|
|
|
8350
8726
|
// ../../packages/shared-headless-capabilities/src/subagent-sessions.ts
|
|
8351
8727
|
var SUBAGENT_SESSION_RECORD_VERSION = 1;
|
|
@@ -8525,14 +8901,14 @@ function lastPipelineCommand(command) {
|
|
|
8525
8901
|
const segments = command.split("|").map((part) => part.trim()).filter(Boolean);
|
|
8526
8902
|
return segments[segments.length - 1] || command;
|
|
8527
8903
|
}
|
|
8528
|
-
function splitShellSegments(command) {
|
|
8904
|
+
function splitShellSegments(command, shell = "bash") {
|
|
8529
8905
|
const segments = [];
|
|
8530
8906
|
let current = "";
|
|
8531
8907
|
let quote;
|
|
8532
8908
|
for (let index = 0; index < command.length; index += 1) {
|
|
8533
8909
|
const char = command[index];
|
|
8534
8910
|
if (quote) {
|
|
8535
|
-
if (quote === '"' && char === "\\" && index + 1 < command.length) {
|
|
8911
|
+
if (quote === '"' && char === (shell === "powershell" ? "`" : "\\") && index + 1 < command.length) {
|
|
8536
8912
|
current += char;
|
|
8537
8913
|
current += command[index + 1];
|
|
8538
8914
|
index += 1;
|
|
@@ -8547,13 +8923,13 @@ function splitShellSegments(command) {
|
|
|
8547
8923
|
current += char;
|
|
8548
8924
|
continue;
|
|
8549
8925
|
}
|
|
8550
|
-
if (char === "\\" && index + 1 < command.length) {
|
|
8926
|
+
if (char === (shell === "powershell" ? "`" : "\\") && index + 1 < command.length) {
|
|
8551
8927
|
current += char;
|
|
8552
8928
|
current += command[index + 1];
|
|
8553
8929
|
index += 1;
|
|
8554
8930
|
continue;
|
|
8555
8931
|
}
|
|
8556
|
-
if (char === "&" || char === "|" || char === ";" || char === "\n") {
|
|
8932
|
+
if (char === "&" || char === "|" || char === ";" || char === "\n" || shell === "powershell" && /[(){}]/.test(char)) {
|
|
8557
8933
|
if ((char === "&" || char === "|") && command[index + 1] === char) {
|
|
8558
8934
|
index += 1;
|
|
8559
8935
|
}
|
|
@@ -8566,7 +8942,7 @@ function splitShellSegments(command) {
|
|
|
8566
8942
|
segments.push(current);
|
|
8567
8943
|
return segments.map((part) => part.trim()).filter(Boolean);
|
|
8568
8944
|
}
|
|
8569
|
-
function splitShellWords(segment) {
|
|
8945
|
+
function splitShellWords(segment, shell = "bash") {
|
|
8570
8946
|
const words = [];
|
|
8571
8947
|
let current = "";
|
|
8572
8948
|
let quote;
|
|
@@ -8575,7 +8951,7 @@ function splitShellWords(segment) {
|
|
|
8575
8951
|
if (quote) {
|
|
8576
8952
|
if (char === quote) {
|
|
8577
8953
|
quote = void 0;
|
|
8578
|
-
} else if (quote === '"' && char === "\\" && index + 1 < segment.length) {
|
|
8954
|
+
} else if (quote === '"' && char === (shell === "powershell" ? "`" : "\\") && index + 1 < segment.length) {
|
|
8579
8955
|
current += segment[index + 1];
|
|
8580
8956
|
index += 1;
|
|
8581
8957
|
} else {
|
|
@@ -8587,7 +8963,7 @@ function splitShellWords(segment) {
|
|
|
8587
8963
|
quote = char;
|
|
8588
8964
|
continue;
|
|
8589
8965
|
}
|
|
8590
|
-
if (char === "\\") {
|
|
8966
|
+
if (char === (shell === "powershell" ? "`" : "\\")) {
|
|
8591
8967
|
if (index + 1 >= segment.length) return void 0;
|
|
8592
8968
|
current += segment[index + 1];
|
|
8593
8969
|
index += 1;
|
|
@@ -8796,7 +9172,7 @@ function classifyShellCommand(command, shell = "bash", options) {
|
|
|
8796
9172
|
const warnings = [];
|
|
8797
9173
|
let commandClass = "unknown_shell";
|
|
8798
9174
|
let destructive = false;
|
|
8799
|
-
const destructivePatterns = shell === "
|
|
9175
|
+
const destructivePatterns = shell === "cmd" ? [...CMD_DESTRUCTIVE_PATTERNS, ...DESTRUCTIVE_PATTERNS] : DESTRUCTIVE_PATTERNS;
|
|
8800
9176
|
const removeWarnings = recursiveRemoveWarnings(command);
|
|
8801
9177
|
if (removeWarnings.length > 0) {
|
|
8802
9178
|
if (shell === "bash" && isStaticallyScopedRecursiveForceRemove(command, options)) {
|
|
@@ -8813,6 +9189,37 @@ function classifyShellCommand(command, shell = "bash", options) {
|
|
|
8813
9189
|
commandClass = "unknown_shell";
|
|
8814
9190
|
destructive = true;
|
|
8815
9191
|
}
|
|
9192
|
+
if (shell === "powershell") {
|
|
9193
|
+
const segments = splitShellSegments(command, shell);
|
|
9194
|
+
const simple = !/[$`#@{}()[\]=<>]/.test(command) && segments.every((segment) => {
|
|
9195
|
+
const words2 = splitShellWords(segment, shell);
|
|
9196
|
+
if (!words2?.length) return false;
|
|
9197
|
+
const head = words2[0] === "." ? words2[1] : words2[0];
|
|
9198
|
+
return !!head && /^[A-Za-z0-9_.\\/][A-Za-z0-9_.\\/:-]*$/.test(head) && !/^(?:return|throw|exit|if|elseif|else|switch|foreach|for|while|do|until|try|catch|finally|function|filter|class|begin|process|end|trap|data|using|workflow|configuration|break|continue|inlinescript|parallel|sequence)$/i.test(head);
|
|
9199
|
+
});
|
|
9200
|
+
if (!simple) {
|
|
9201
|
+
for (const entry of POWERSHELL_DESTRUCTIVE_PATTERNS) {
|
|
9202
|
+
if (!entry.pattern.test(command)) continue;
|
|
9203
|
+
warnings.push(entry.warning);
|
|
9204
|
+
commandClass = entry.commandClass;
|
|
9205
|
+
destructive = true;
|
|
9206
|
+
}
|
|
9207
|
+
}
|
|
9208
|
+
for (const segment of segments) {
|
|
9209
|
+
const words2 = splitShellWords(segment, shell) ?? segment.split(/\s+/);
|
|
9210
|
+
if (words2[0] === ".") words2.shift();
|
|
9211
|
+
if (!words2[0]) continue;
|
|
9212
|
+
words2[0] = executableName(words2[0]);
|
|
9213
|
+
for (const entry of POWERSHELL_DESTRUCTIVE_PATTERNS) {
|
|
9214
|
+
const pattern = new RegExp(`^(?:${entry.pattern.source})`, entry.pattern.flags);
|
|
9215
|
+
if (pattern.test(words2.join(" "))) {
|
|
9216
|
+
warnings.push(entry.warning);
|
|
9217
|
+
commandClass = entry.commandClass;
|
|
9218
|
+
destructive = true;
|
|
9219
|
+
}
|
|
9220
|
+
}
|
|
9221
|
+
}
|
|
9222
|
+
}
|
|
8816
9223
|
for (const entry of destructivePatterns) {
|
|
8817
9224
|
if (entry.pattern.test(command)) {
|
|
8818
9225
|
warnings.push(entry.warning);
|
|
@@ -8828,8 +9235,6 @@ function classifyShellCommand(command, shell = "bash", options) {
|
|
|
8828
9235
|
const primary = words[0] || firstWord(normalized);
|
|
8829
9236
|
if (shell === "powershell" && /\b(invoke-webrequest|iwr|invoke-restmethod|irm)\b/.test(normalized)) {
|
|
8830
9237
|
commandClass = "network";
|
|
8831
|
-
} else if (shell === "powershell" && /\b(new-item|set-content|add-content|copy-item|move-item|rename-item)\b/.test(normalized)) {
|
|
8832
|
-
commandClass = "filesystem_write";
|
|
8833
9238
|
} else if (shell === "cmd" && /\b(curl|bitsadmin|certutil)\b/.test(normalized)) {
|
|
8834
9239
|
commandClass = "network";
|
|
8835
9240
|
} else if (shell === "cmd" && /\b(copy|xcopy|robocopy|move|mkdir|md|ren|rename)\b/.test(normalized)) {
|
|
@@ -9131,7 +9536,7 @@ function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingL
|
|
|
9131
9536
|
hooks.observation.lastWorkAt = Date.now();
|
|
9132
9537
|
hooks.observation.lastActivity = "response_event";
|
|
9133
9538
|
}
|
|
9134
|
-
}, hooks);
|
|
9539
|
+
}, hooks, deps.recovery);
|
|
9135
9540
|
if (deps.createAgent) {
|
|
9136
9541
|
const agent2 = deps.createAgent({
|
|
9137
9542
|
streamFn,
|
|
@@ -9605,10 +10010,21 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
|
|
|
9605
10010
|
throw new Error(`Subagent output token limit reached (stopReason=length): the provider cut the final response before the report was produced${messages.some(messageHasOwnText) ? " (earlier-turn narration discarded)" : ", no text response"}. The output/thinking budget was too small for this task at its reasoning level. Rerun with a narrower task or a lower thinkingLevel.`);
|
|
9606
10011
|
}
|
|
9607
10012
|
const detail = terminalError ? `: ${terminalError.slice(0, 200)}` : "";
|
|
9608
|
-
const
|
|
10013
|
+
const hooks2 = agentRunHooks.get(subagent);
|
|
10014
|
+
const fallbackFailure = lastAssistant && (lastAssistant.retryable !== void 0 || lastAssistant.replayClass !== void 0 || lastAssistant.replayEligible !== void 0 || lastAssistant.remoteFailure !== void 0) ? {
|
|
10015
|
+
errorMessage: terminalError,
|
|
10016
|
+
retryable: lastAssistant.retryable,
|
|
10017
|
+
replayClass: lastAssistant.replayClass,
|
|
10018
|
+
replayEligible: lastAssistant.replayEligible,
|
|
10019
|
+
remoteFailure: lastAssistant.remoteFailure
|
|
10020
|
+
} : void 0;
|
|
10021
|
+
const subagentFailure = hooks2?.failure ?? fallbackFailure;
|
|
10022
|
+
const overflow = subagentFailure?.recovery?.outcome === "overflow";
|
|
10023
|
+
const failure = new Error(overflow ? `Subagent context overflow requires compaction, but no subagent compactor is configured${detail}` : `Subagent response failed (provider/transport failure before the report was produced, stopReason=${terminalStopReason ?? "none"}${collectedResponse ? ", partial transcript discarded" : ", no text response"})${detail}`);
|
|
9609
10024
|
const carrier = failure;
|
|
9610
|
-
carrier.retryable =
|
|
9611
|
-
carrier.replayClass =
|
|
10025
|
+
carrier.retryable = subagentFailure?.retryable;
|
|
10026
|
+
carrier.replayClass = subagentFailure?.replayClass;
|
|
10027
|
+
carrier.subagentFailure = subagentFailure;
|
|
9612
10028
|
throw failure;
|
|
9613
10029
|
}
|
|
9614
10030
|
let response;
|
|
@@ -9655,8 +10071,15 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
|
|
|
9655
10071
|
phaseProgress(task.observation.phase);
|
|
9656
10072
|
task.error = errorMessage.slice(0, 2e3);
|
|
9657
10073
|
const failureCarrier = error;
|
|
9658
|
-
if (failureCarrier
|
|
9659
|
-
task.failure =
|
|
10074
|
+
if (failureCarrier?.subagentFailure) {
|
|
10075
|
+
task.failure = failureCarrier.subagentFailure;
|
|
10076
|
+
} else if (failureCarrier && (failureCarrier.retryable !== void 0 || failureCarrier.replayClass !== void 0 || failureCarrier.replayEligible !== void 0 || failureCarrier.remoteFailure !== void 0)) {
|
|
10077
|
+
task.failure = {
|
|
10078
|
+
...failureCarrier.retryable !== void 0 ? { retryable: failureCarrier.retryable } : {},
|
|
10079
|
+
...failureCarrier.replayClass !== void 0 ? { replayClass: failureCarrier.replayClass } : {},
|
|
10080
|
+
...failureCarrier.replayEligible !== void 0 ? { replayEligible: failureCarrier.replayEligible } : {},
|
|
10081
|
+
...failureCarrier.remoteFailure !== void 0 ? { remoteFailure: failureCarrier.remoteFailure } : {}
|
|
10082
|
+
};
|
|
9660
10083
|
} else {
|
|
9661
10084
|
task.failure = void 0;
|
|
9662
10085
|
}
|
|
@@ -9741,6 +10164,11 @@ function buildParallelResult(tasks, totalDuration) {
|
|
|
9741
10164
|
`;
|
|
9742
10165
|
if (task.observation) resultText += `Tools: ${task.observation.toolStarts} started, ${task.observation.toolEnds} ended, ${task.observation.toolErrors} errors
|
|
9743
10166
|
`;
|
|
10167
|
+
if (task.failure?.recovery) {
|
|
10168
|
+
const recovery = task.failure.recovery;
|
|
10169
|
+
resultText += `Recovery: ${task.failure.classification?.kind ?? "unclassified"}; outcome=${recovery.outcome}${recovery.reason ? ` (${recovery.reason})` : ""}; attempts=${recovery.attempts}; retries=${recovery.retries}${recovery.maxRetries !== void 0 ? `; budget=${recovery.maxRetries}` : ""}
|
|
10170
|
+
`;
|
|
10171
|
+
}
|
|
9744
10172
|
if (task.status === "completed" && task.result) {
|
|
9745
10173
|
resultText += `${task.result}
|
|
9746
10174
|
`;
|
|
@@ -9992,40 +10420,8 @@ ${SPAWN_USAGE_HINT}` }],
|
|
|
9992
10420
|
toolContext.log?.(`Spawning ${tasks.length} parallel subagents: ${tasks.map((task) => task.name).join(", ")}`);
|
|
9993
10421
|
toolContext.reportProgress?.({ message: `Launching ${tasks.length} parallel subagents...`, increment: 5 });
|
|
9994
10422
|
const startTime = Date.now();
|
|
9995
|
-
const runTask = async (task) => {
|
|
9996
|
-
await run(task, toolContext.reportProgress, signal);
|
|
9997
|
-
if (task.wasRecall || task.mode !== "read" || task.status !== "failed" || !task.error) return;
|
|
9998
|
-
if (signal?.aborted) return;
|
|
9999
|
-
if (!isRetryableSubagentFailureFrom({ errorMessage: task.error, retryable: task.failure?.retryable, replayClass: task.failure?.replayClass })) return;
|
|
10000
|
-
observeDelegationPhase(task.observation, "task_replay");
|
|
10001
|
-
const settleMs = Math.max(0, (deps.transientRetryBackoffMs ?? 2e3) * 10);
|
|
10002
|
-
toolContext.reportProgress?.({ message: `[${task.name}] Provider/transport failure \u2014 replaying the read task once after ${Math.round(settleMs / 1e3)}s`, increment: 0 });
|
|
10003
|
-
await cancellableBackoff(settleMs, signal);
|
|
10004
|
-
if (signal.aborted) {
|
|
10005
|
-
task.status = "cancelled";
|
|
10006
|
-
task.error = "Subagent cancelled during replay backoff";
|
|
10007
|
-
task.failure = void 0;
|
|
10008
|
-
observeDelegationPhase(task.observation, "cancelled");
|
|
10009
|
-
task.completedAt = Date.now();
|
|
10010
|
-
return;
|
|
10011
|
-
}
|
|
10012
|
-
task.evidence = [];
|
|
10013
|
-
task.confidenceSignals = [];
|
|
10014
|
-
task.runtimeEvidence = [];
|
|
10015
|
-
task.reportedEvidence = [];
|
|
10016
|
-
task.error = void 0;
|
|
10017
|
-
task.failure = void 0;
|
|
10018
|
-
task.report = void 0;
|
|
10019
|
-
task.result = void 0;
|
|
10020
|
-
task.details = void 0;
|
|
10021
|
-
task.parentHandoffRequired = false;
|
|
10022
|
-
task.sessionId = void 0;
|
|
10023
|
-
task.requestedMode = void 0;
|
|
10024
|
-
task.wasRecall = void 0;
|
|
10025
|
-
await run(task, toolContext.reportProgress, signal);
|
|
10026
|
-
};
|
|
10027
10423
|
const batch = Promise.resolve().then(() => runWithConcurrencyLimit(
|
|
10028
|
-
tasks.map((task) => () =>
|
|
10424
|
+
tasks.map((task) => () => run(task, toolContext.reportProgress, signal)),
|
|
10029
10425
|
Math.max(1, deps.maxConcurrentSubagents ?? DEFAULT_MAX_CONCURRENT_SUBAGENTS)
|
|
10030
10426
|
));
|
|
10031
10427
|
pending.add(batch);
|
|
@@ -10068,9 +10464,8 @@ ${SPAWN_USAGE_HINT}` }],
|
|
|
10068
10464
|
observation: task.observation,
|
|
10069
10465
|
parentHandoffRequired: task.parentHandoffRequired,
|
|
10070
10466
|
failureReason: task.error,
|
|
10071
|
-
// Structured provider
|
|
10072
|
-
//
|
|
10073
|
-
// provider vetoes (auth/quota/config — not recoverable here).
|
|
10467
|
+
// Structured provider facts plus the actual recovery outcome. Failed-
|
|
10468
|
+
// response tool calls never execute; do not infer their absence.
|
|
10074
10469
|
failure: task.failure,
|
|
10075
10470
|
details: task.details
|
|
10076
10471
|
})),
|
|
@@ -10293,7 +10688,11 @@ function executeManageTodoList(args, currentTodos) {
|
|
|
10293
10688
|
};
|
|
10294
10689
|
}
|
|
10295
10690
|
default:
|
|
10296
|
-
return rejectTodoAction(
|
|
10691
|
+
return rejectTodoAction(
|
|
10692
|
+
"read",
|
|
10693
|
+
`unknown action: ${args.action}. Valid actions: create, read, complete_current, clear.`,
|
|
10694
|
+
safeCurrentTodos
|
|
10695
|
+
);
|
|
10297
10696
|
}
|
|
10298
10697
|
}
|
|
10299
10698
|
|
|
@@ -11142,6 +11541,7 @@ var resolveToCwd = resolveInSandbox;
|
|
|
11142
11541
|
var resolveReadPath = resolveInSandbox;
|
|
11143
11542
|
|
|
11144
11543
|
// ../../packages/shared-headless-capabilities/src/builtin/fs/schema-utils.ts
|
|
11544
|
+
var SCOPE_BASELINE_SENTENCE = "Absolute paths outside the sandbox are accepted only within configured access roots or when the current runtime mode's allowlist permits them; the effective scope is stated in the runtime-mode context, not in this description.";
|
|
11145
11545
|
function formatPrefixes(prefixes) {
|
|
11146
11546
|
if (!prefixes || prefixes.length === 0) {
|
|
11147
11547
|
return "";
|
|
@@ -11151,6 +11551,11 @@ function formatPrefixes(prefixes) {
|
|
|
11151
11551
|
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
11152
11552
|
}).join(", ");
|
|
11153
11553
|
}
|
|
11554
|
+
function arraysEqual(a, b) {
|
|
11555
|
+
if (a === b) return true;
|
|
11556
|
+
if (a.length !== b.length) return false;
|
|
11557
|
+
return a.every((value, index) => value === b[index]);
|
|
11558
|
+
}
|
|
11154
11559
|
function buildRootParameterSchema(sandboxRoot, options) {
|
|
11155
11560
|
const roots = getAvailableRoots(options);
|
|
11156
11561
|
if (roots.length === 0) {
|
|
@@ -11174,8 +11579,11 @@ function buildRootParameterSchema(sandboxRoot, options) {
|
|
|
11174
11579
|
function buildPathResolutionDescription(sandboxRoot, options) {
|
|
11175
11580
|
const roots = getAvailableRoots(options);
|
|
11176
11581
|
const allowlistedDirs = options?.allowlistedDirs ?? [];
|
|
11177
|
-
const
|
|
11178
|
-
const
|
|
11582
|
+
const descriptionOverride = options?.descriptionAllowlistedDirs;
|
|
11583
|
+
const describedAllowlist = descriptionOverride ?? allowlistedDirs;
|
|
11584
|
+
const viewsEqual = arraysEqual(describedAllowlist, allowlistedDirs);
|
|
11585
|
+
const hasGlobalAllowlist = describedAllowlist.some((dir) => dir.trim() === "/") && viewsEqual;
|
|
11586
|
+
const allowlistSentence = hasGlobalAllowlist ? "Absolute paths anywhere on the local filesystem are accepted in the current runtime mode." : viewsEqual ? describedAllowlist.length > 0 ? `Absolute paths inside allowlisted directories are accepted: ${describedAllowlist.join(", ")}.` : "" : SCOPE_BASELINE_SENTENCE;
|
|
11179
11587
|
if (roots.length === 0) {
|
|
11180
11588
|
return ["Relative paths resolve against the workspace root.", allowlistSentence].filter(Boolean).join(" ");
|
|
11181
11589
|
}
|
|
@@ -11700,9 +12108,10 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
11700
12108
|
return;
|
|
11701
12109
|
}
|
|
11702
12110
|
if (magicInfo?.kind === "image") {
|
|
12111
|
+
const inlineSupported = INLINE_IMAGE_MIME_TYPES.has(magicInfo.mimeType);
|
|
11703
12112
|
resolve2({
|
|
11704
|
-
content: [{ type: "text", text: `Cannot attach image file ${path3}: unsupported image payload ${magicInfo.mimeType}.` }],
|
|
11705
|
-
details: { understanding: { kind: "image", mimeType: magicInfo.mimeType, fallbackReason: "unsupported inline image format" } },
|
|
12113
|
+
content: [{ type: "text", text: inlineSupported ? `Cannot attach image file ${path3}: the host filesystem bridge returned no usable image data for ${magicInfo.mimeType} (see logs for readImageInfo diagnostics).` : `Cannot attach image file ${path3}: unsupported image payload ${magicInfo.mimeType}.` }],
|
|
12114
|
+
details: { understanding: { kind: "image", mimeType: magicInfo.mimeType, fallbackReason: inlineSupported ? "image bridge read failed" : "unsupported inline image format" } },
|
|
11706
12115
|
isError: true
|
|
11707
12116
|
});
|
|
11708
12117
|
return;
|
|
@@ -12109,11 +12518,10 @@ function detectTextOnlyBinary(input, filePath, prefixMayBeTruncated) {
|
|
|
12109
12518
|
async function readImageInfo(path3, fs2) {
|
|
12110
12519
|
try {
|
|
12111
12520
|
let base64 = "";
|
|
12112
|
-
const size = (await fs2.stat(path3).catch(() => void 0))?.size ?? 0;
|
|
12113
12521
|
if (typeof fs2.readFile === "function") {
|
|
12114
12522
|
base64 = await fs2.readFile(path3, "base64").catch(() => "");
|
|
12115
12523
|
}
|
|
12116
|
-
if (!base64 &&
|
|
12524
|
+
if (!base64 && typeof fs2.readFileSync === "function") {
|
|
12117
12525
|
base64 = fs2.readFileSync(path3, "base64");
|
|
12118
12526
|
}
|
|
12119
12527
|
if (!base64) {
|
|
@@ -12141,6 +12549,8 @@ async function readImageInfo(path3, fs2) {
|
|
|
12141
12549
|
if (bytes.length >= 12 && asciiAt(bytes, 0, 4) === "RIFF" && asciiAt(bytes, 8, 12 - 8) === "WEBP") {
|
|
12142
12550
|
return { mimeType: "image/webp", base64, ...parseWebpDimensions(bytes) };
|
|
12143
12551
|
}
|
|
12552
|
+
console.error(`[pie] readImageInfo decoded ${bytes.length} bytes for ${path3} but no known image signature matched`);
|
|
12553
|
+
return void 0;
|
|
12144
12554
|
} catch (error) {
|
|
12145
12555
|
console.error(`[pie] readImageInfo failed for ${path3}: ${error instanceof Error ? error.message : String(error)}`);
|
|
12146
12556
|
return void 0;
|
|
@@ -12148,7 +12558,7 @@ async function readImageInfo(path3, fs2) {
|
|
|
12148
12558
|
return void 0;
|
|
12149
12559
|
}
|
|
12150
12560
|
function decodeBase64ToBytes(base64) {
|
|
12151
|
-
if (
|
|
12561
|
+
if (hasUsableNodeBuffer()) {
|
|
12152
12562
|
return Buffer.from(base64, "base64");
|
|
12153
12563
|
}
|
|
12154
12564
|
const clean = base64.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
@@ -12167,6 +12577,17 @@ function decodeBase64ToBytes(base64) {
|
|
|
12167
12577
|
}
|
|
12168
12578
|
return bytes.subarray(0, at);
|
|
12169
12579
|
}
|
|
12580
|
+
function hasUsableNodeBuffer() {
|
|
12581
|
+
if (typeof Buffer === "undefined" || typeof Buffer.from !== "function") {
|
|
12582
|
+
return false;
|
|
12583
|
+
}
|
|
12584
|
+
try {
|
|
12585
|
+
const probe = Buffer.from("aGk=", "base64");
|
|
12586
|
+
return probe.length === 2 && probe[0] === 104;
|
|
12587
|
+
} catch {
|
|
12588
|
+
return false;
|
|
12589
|
+
}
|
|
12590
|
+
}
|
|
12170
12591
|
function readUIntBE(bytes, offset, length) {
|
|
12171
12592
|
let value = 0;
|
|
12172
12593
|
for (let i = 0; i < length; i += 1) {
|