@cydm/pie 2.7.11 → 2.7.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -14
- 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/builtin/skills/computer-use-axi/computer-use-axi-broker.js +13 -11
- package/dist/builtin/skills/computer-use-axi/computer-use-axi-runtime.js +1 -1
- package/dist/chunks/{chunk-XIRBNP2S.js → chunk-CIFC6QVB.js} +718 -277
- package/dist/chunks/{chunk-3NFKVBGY.js → chunk-D7RMNFVM.js} +156 -2
- package/dist/chunks/{chunk-XVF6V6Q5.js → chunk-HGUYS5SF.js} +71 -2
- package/dist/chunks/{chunk-3VEIP2VY.js → chunk-R46YPPTU.js} +57 -32
- package/dist/chunks/{src-2RCV3I3P.js → src-5JJZZ5H7.js} +6 -2
- package/dist/chunks/{test-stream-Y2S3H7DK.js → test-stream-ZBTSPU7J.js} +1 -1
- package/dist/cli.js +1404 -517
- package/package.json +2 -2
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
Agent,
|
|
4
|
+
createAgentOperationError,
|
|
5
|
+
getAgentFailureMetadata,
|
|
4
6
|
getToolCallerIdentity
|
|
5
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HGUYS5SF.js";
|
|
6
8
|
import {
|
|
7
9
|
AssistantMessageEventStream,
|
|
8
10
|
FileSystemGateway,
|
|
@@ -12,7 +14,7 @@ import {
|
|
|
12
14
|
getPlatformConfig,
|
|
13
15
|
hashText,
|
|
14
16
|
streamSimple
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-R46YPPTU.js";
|
|
16
18
|
import {
|
|
17
19
|
Type
|
|
18
20
|
} from "./chunk-XXGYUYXI.js";
|
|
@@ -1001,9 +1003,9 @@ var PLAN_POWERSHELL_READ_COMMANDS = new Set([
|
|
|
1001
1003
|
"Compare-Object",
|
|
1002
1004
|
"Test-Path"
|
|
1003
1005
|
].map((name) => name.toLowerCase()));
|
|
1004
|
-
var PLAN_POWERSHELL_READ_ALIASES = /* @__PURE__ */ new Set(["cat", "ls", "pwd", "echo"
|
|
1006
|
+
var PLAN_POWERSHELL_READ_ALIASES = /* @__PURE__ */ new Set(["cat", "ls", "pwd", "echo"]);
|
|
1005
1007
|
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]
|
|
1008
|
+
var PLAN_POWERSHELL_SEPARATORS = /[;\n\r\t&<>|]/;
|
|
1007
1009
|
var PLAN_POWERSHELL_NON_ASCII = /[^\x20-\x7E]/;
|
|
1008
1010
|
var PLAN_POWERSHELL_UNSAFE_TOKENS = /\$\(|`|%\{|[(){}\[\]]|\bsource\b|\.\s+[/~]|\b(bash|sh|zsh|pwsh|powershell|cmd|npm|npx|node|python3?)\b/i;
|
|
1009
1011
|
function isPlanModeSafePowerShellCommand(command) {
|
|
@@ -1331,7 +1333,9 @@ var DEFAULT_COMPACTION_CONFIG = {
|
|
|
1331
1333
|
summaryPrefix: SUMMARY_PREFIX
|
|
1332
1334
|
};
|
|
1333
1335
|
var APPROX_BYTES_PER_TOKEN = 4;
|
|
1334
|
-
var
|
|
1336
|
+
var IMAGE_MEDIA_BYTES_ESTIMATE = 7373;
|
|
1337
|
+
var AUDIO_MEDIA_BYTES_ESTIMATE = 6800;
|
|
1338
|
+
var VIDEO_MEDIA_BYTES_ESTIMATE = 16e3;
|
|
1335
1339
|
function estimateTokens(message) {
|
|
1336
1340
|
return approximateBytesAsTokens(estimateModelVisibleBytes(message));
|
|
1337
1341
|
}
|
|
@@ -1456,20 +1460,16 @@ function planRetainedTail(summaries, options = {}) {
|
|
|
1456
1460
|
if (boundaries.length === 0) {
|
|
1457
1461
|
return nonePlan("no-boundary");
|
|
1458
1462
|
}
|
|
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
1463
|
const groupEnd = boundaries.map((_, index) => boundaries[index + 1] ?? summaries.length);
|
|
1464
1464
|
const planFrom = (startIndex, reason) => ({
|
|
1465
1465
|
retainedTail: { kind: "from-entry", entryId: summaries[startIndex].id },
|
|
1466
1466
|
entryIds: summaries.slice(startIndex).map((summary) => summary.id),
|
|
1467
|
-
tailTokens:
|
|
1467
|
+
tailTokens: estimateRetainedTailTokens(summaries, { kind: "from-entry", entryId: summaries[startIndex].id }),
|
|
1468
1468
|
reason
|
|
1469
1469
|
});
|
|
1470
1470
|
let accumulated = 0;
|
|
1471
1471
|
for (let k = boundaries.length - 1; k >= 0; k--) {
|
|
1472
|
-
const groupTokens =
|
|
1472
|
+
const groupTokens = summaries.slice(boundaries[k], groupEnd[k]).reduce((sum, summary) => sum + Math.max(0, Math.floor(summary.estTokens || 0)), 0);
|
|
1473
1473
|
if (groupTokens > budget) {
|
|
1474
1474
|
if (accumulated === 0) {
|
|
1475
1475
|
return nonePlan("single-group-over-budget");
|
|
@@ -1545,6 +1545,9 @@ function estimateCompactionGain(params) {
|
|
|
1545
1545
|
const tokensBefore = Math.max(0, params.tokensBefore);
|
|
1546
1546
|
const tokenSavings = Math.max(0, tokensBefore - projectedTokens);
|
|
1547
1547
|
const estimate = { summaryTokens, tailTokens, projectedTokens, tokenSavings };
|
|
1548
|
+
if (!Number.isFinite(projectedTokens)) {
|
|
1549
|
+
return { ...estimate, ok: false, reason: "retained context has unknown model-visible size; supply a compaction modelView" };
|
|
1550
|
+
}
|
|
1548
1551
|
if (params.budgetTokens !== void 0 && params.budgetTokens > 0 && projectedTokens > params.budgetTokens) {
|
|
1549
1552
|
return {
|
|
1550
1553
|
...estimate,
|
|
@@ -1697,12 +1700,7 @@ Return a text-only successor handoff. Do not call any tools.` }],
|
|
|
1697
1700
|
).result();
|
|
1698
1701
|
throwIfAborted();
|
|
1699
1702
|
if (response.stopReason === "error" && (!response.content.some((block) => block.type === "toolCall") || classifyRemoteFailure(response.remoteFailure, response.errorMessage ?? "") !== void 0)) {
|
|
1700
|
-
|
|
1701
|
-
const explicitRetryable = response.retryable;
|
|
1702
|
-
if (typeof explicitRetryable === "boolean") providerError.retryable = explicitRetryable;
|
|
1703
|
-
if (response.replayEligible === true) providerError.replayEligible = true;
|
|
1704
|
-
providerError.remoteFailure = response.remoteFailure;
|
|
1705
|
-
throw providerError;
|
|
1703
|
+
throw Object.assign(new Error(response.errorMessage || "Compaction provider error"), getAgentFailureMetadata(response));
|
|
1706
1704
|
}
|
|
1707
1705
|
} catch (error) {
|
|
1708
1706
|
throwIfAborted();
|
|
@@ -1825,7 +1823,7 @@ function normalizeUserContentBlock(block) {
|
|
|
1825
1823
|
return { type: "text", text: block.text };
|
|
1826
1824
|
}
|
|
1827
1825
|
if (block.type === "hostContext") {
|
|
1828
|
-
return { type: "
|
|
1826
|
+
return { type: "hostContext" };
|
|
1829
1827
|
}
|
|
1830
1828
|
if (block.type === "image") {
|
|
1831
1829
|
return { type: "image", mimeType: block.mimeType };
|
|
@@ -1857,22 +1855,16 @@ function estimateMessageMediaBytes(message) {
|
|
|
1857
1855
|
}
|
|
1858
1856
|
function estimateUserContentBlockMediaBytes(block) {
|
|
1859
1857
|
if (block.type === "image") {
|
|
1860
|
-
return
|
|
1858
|
+
return IMAGE_MEDIA_BYTES_ESTIMATE;
|
|
1861
1859
|
}
|
|
1862
1860
|
if (block.type === "audio") {
|
|
1863
|
-
return
|
|
1861
|
+
return AUDIO_MEDIA_BYTES_ESTIMATE;
|
|
1864
1862
|
}
|
|
1865
1863
|
if (block.type === "video") {
|
|
1866
|
-
return
|
|
1864
|
+
return VIDEO_MEDIA_BYTES_ESTIMATE;
|
|
1867
1865
|
}
|
|
1868
1866
|
return 0;
|
|
1869
1867
|
}
|
|
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
1868
|
function approximateBytesAsTokens(bytes) {
|
|
1877
1869
|
return Math.ceil(bytes / APPROX_BYTES_PER_TOKEN);
|
|
1878
1870
|
}
|
|
@@ -4623,10 +4615,26 @@ function prepareSessionCompaction(params) {
|
|
|
4623
4615
|
const agentOnlyFailure = excludedFailure === null && requireExclusion ? agentTail ?? null : null;
|
|
4624
4616
|
const summaries = sessionManager.getActiveContextSummaries();
|
|
4625
4617
|
const entries = sessionManager.getResidentContextEntries();
|
|
4626
|
-
const
|
|
4618
|
+
const sourceSummaries = excludedFailure ? summaries.filter((summary) => summary.id !== excludedFailure.entryId) : summaries;
|
|
4627
4619
|
const effectiveEntries = excludedFailure ? entries.filter((entry) => entry.id !== excludedFailure.entryId) : entries;
|
|
4628
4620
|
const summaryInput = excludedFailure || agentOnlyFailure ? messages.slice(0, -1) : messages;
|
|
4629
|
-
const
|
|
4621
|
+
const estimates = /* @__PURE__ */ new Map();
|
|
4622
|
+
const estimate = (message) => {
|
|
4623
|
+
const cached = estimates.get(message);
|
|
4624
|
+
if (cached !== void 0) return cached;
|
|
4625
|
+
const visible = params.modelView ? params.modelView(message) : message;
|
|
4626
|
+
const unknown = visible.role === "user" && Array.isArray(visible.content) && visible.content.some((block) => block.type === "hostContext");
|
|
4627
|
+
const tokens = unknown ? Infinity : estimateTokens(visible);
|
|
4628
|
+
estimates.set(message, tokens);
|
|
4629
|
+
return tokens;
|
|
4630
|
+
};
|
|
4631
|
+
const bodies = new Map(effectiveEntries.map((entry) => [entry.id, entry]));
|
|
4632
|
+
const effectiveSummaries = sourceSummaries.map((summary) => {
|
|
4633
|
+
const entry = bodies.get(summary.id);
|
|
4634
|
+
return { ...summary, estTokens: entry?.message ? estimate(entry.message) : summary.type === "message" ? Infinity : summary.estTokens };
|
|
4635
|
+
});
|
|
4636
|
+
const inputEstimate = summaryInput.reduce((sum, message) => sum + estimate(message), 0);
|
|
4637
|
+
const inputTokens = Number.isFinite(inputEstimate) ? inputEstimate : calculateTotalTokens(summaryInput);
|
|
4630
4638
|
const reductionLimit = inputTokens >= 1e3 ? inputTokens - Math.ceil(inputTokens * 0.1) : Infinity;
|
|
4631
4639
|
const contextLimit = Math.min(
|
|
4632
4640
|
reductionLimit,
|
|
@@ -4640,7 +4648,7 @@ function prepareSessionCompaction(params) {
|
|
|
4640
4648
|
let planned = planRetainedTail(effectiveSummaries, { ...params.compactionConfig, keepRecentTokens: tailBudget });
|
|
4641
4649
|
if (planned.retainedTail.kind !== "none") {
|
|
4642
4650
|
const validation = validateRetainedTailEntries({ entries: effectiveEntries, retainedTail: planned.retainedTail });
|
|
4643
|
-
planned = validation.ok ? { ...planned, entryIds: validation.entryIds, tailTokens:
|
|
4651
|
+
planned = validation.ok ? { ...planned, entryIds: validation.entryIds, tailTokens: estimateRetainedTailTokens(effectiveSummaries, planned.retainedTail) } : { retainedTail: { kind: "none" }, entryIds: [], tailTokens: 0, reason: "pairing" };
|
|
4644
4652
|
}
|
|
4645
4653
|
return {
|
|
4646
4654
|
sessionId: sessionManager.getActiveSessionId(),
|
|
@@ -4696,7 +4704,7 @@ function verifyPreparedCompactionCommit(params) {
|
|
|
4696
4704
|
const gain = estimateCompactionGain({
|
|
4697
4705
|
summary: checkpointSummary,
|
|
4698
4706
|
tailTokens: estimateRetainedTailTokens(params.preparation.summaries, retainedTail),
|
|
4699
|
-
tokensBefore: params.
|
|
4707
|
+
tokensBefore: params.preparation.inputTokens,
|
|
4700
4708
|
budgetTokens: params.preparation.budgetTokens
|
|
4701
4709
|
});
|
|
4702
4710
|
if (!gain.ok) {
|
|
@@ -5176,7 +5184,13 @@ async function preparePromptMessages(params) {
|
|
|
5176
5184
|
|
|
5177
5185
|
// ../../packages/agent-framework/src/session/controller/pre-request-policy.ts
|
|
5178
5186
|
async function runPreRequestAutoCompact(params) {
|
|
5179
|
-
|
|
5187
|
+
const boundary = params.toolContinuation;
|
|
5188
|
+
const cancelled = () => params.isCurrent?.() === false || boundary?.signal.aborted === true;
|
|
5189
|
+
if (boundary) {
|
|
5190
|
+
await boundary.sync();
|
|
5191
|
+
if (cancelled()) return;
|
|
5192
|
+
}
|
|
5193
|
+
if (params.disposed || params.runtimeGuardTriggered || params.isStreaming && !boundary || params.recoveryActive() || params.agent.state.messages.length === 0) {
|
|
5180
5194
|
return;
|
|
5181
5195
|
}
|
|
5182
5196
|
const decision = params.getAutoCompactDecision?.({
|
|
@@ -5185,12 +5199,17 @@ async function runPreRequestAutoCompact(params) {
|
|
|
5185
5199
|
cwd: params.cwd,
|
|
5186
5200
|
messages: params.agent.state.messages.slice()
|
|
5187
5201
|
});
|
|
5188
|
-
if (decision?.reason !== "threshold") {
|
|
5202
|
+
if (cancelled() || decision?.reason !== "threshold") {
|
|
5189
5203
|
return;
|
|
5190
5204
|
}
|
|
5191
5205
|
try {
|
|
5192
5206
|
await params.runAutoCompact();
|
|
5193
|
-
} catch {
|
|
5207
|
+
} catch (error) {
|
|
5208
|
+
if (boundary) throw createAgentOperationError(error, {
|
|
5209
|
+
origin: "tool-continuation",
|
|
5210
|
+
operation: "compaction",
|
|
5211
|
+
terminal: true
|
|
5212
|
+
});
|
|
5194
5213
|
if (params.isCurrent?.() !== false) params.setRecoveryState("none");
|
|
5195
5214
|
}
|
|
5196
5215
|
}
|
|
@@ -5444,12 +5463,7 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5444
5463
|
return false;
|
|
5445
5464
|
}
|
|
5446
5465
|
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 {
|
|
5466
|
+
if (!dispatched.deliveredToAgent) {
|
|
5453
5467
|
const prepared = await this.options.preparePromptMessages(dispatched.messages, {
|
|
5454
5468
|
source: dispatched.source,
|
|
5455
5469
|
streamingBehavior: dispatched.mode,
|
|
@@ -5459,13 +5473,20 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5459
5473
|
visibility: dispatched.visibility
|
|
5460
5474
|
});
|
|
5461
5475
|
if (generation !== this.generation) return false;
|
|
5462
|
-
if (
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
}
|
|
5466
|
-
await this.options.agent.prompt(prepared.messages);
|
|
5467
|
-
await this.options.syncNewAgentMessages();
|
|
5476
|
+
if (prepared.handled) return true;
|
|
5477
|
+
if (prepared.systemPrompt !== void 0) {
|
|
5478
|
+
this.options.agent.setSystemPrompt(prepared.systemPrompt);
|
|
5468
5479
|
}
|
|
5480
|
+
dispatched.messages = prepared.messages;
|
|
5481
|
+
}
|
|
5482
|
+
if (dispatched.deliveredToAgent || this.options.agent.state.messages.at(-1)?.role === "assistant") {
|
|
5483
|
+
if (!dispatched.deliveredToAgent) {
|
|
5484
|
+
this.options.queue.deliverToAgent(dispatched);
|
|
5485
|
+
}
|
|
5486
|
+
await this.options.continueTurn();
|
|
5487
|
+
} else {
|
|
5488
|
+
await this.options.agent.prompt(dispatched.messages);
|
|
5489
|
+
await this.options.syncNewAgentMessages();
|
|
5469
5490
|
}
|
|
5470
5491
|
return true;
|
|
5471
5492
|
}
|
|
@@ -5497,7 +5518,8 @@ function isAssistantTerminalCompletion(message) {
|
|
|
5497
5518
|
return !message.content.some((block) => block.type === "toolCall");
|
|
5498
5519
|
}
|
|
5499
5520
|
function createFailureClassification(context, retry, classifyFailure) {
|
|
5500
|
-
|
|
5521
|
+
const terminalOperation = context.message?.role === "assistant" && context.message.operationFailure?.terminal === true;
|
|
5522
|
+
if (!terminalOperation && context.message?.role === "assistant" && context.message.replayEligible === true && context.message.stopReason === "error") {
|
|
5501
5523
|
return {
|
|
5502
5524
|
kind: "provider_transient",
|
|
5503
5525
|
retryable: true,
|
|
@@ -5508,7 +5530,7 @@ function createFailureClassification(context, retry, classifyFailure) {
|
|
|
5508
5530
|
replayExempt: true
|
|
5509
5531
|
};
|
|
5510
5532
|
}
|
|
5511
|
-
if (context.message?.role === "assistant" && context.message.retryable === false) {
|
|
5533
|
+
if (context.message?.role === "assistant" && (terminalOperation || context.message.retryable === false)) {
|
|
5512
5534
|
return { kind: "unknown", retryable: false, compactable: false, userAbort: context.message.stopReason === "aborted", terminal: true, message: context.errorMessage };
|
|
5513
5535
|
}
|
|
5514
5536
|
const remote = context.message?.role === "assistant" && context.message.stopReason === "error" ? classifyRemoteFailure(context.message.remoteFailure, context.errorMessage) : void 0;
|
|
@@ -5586,6 +5608,95 @@ var ReplayStallCircuit = class {
|
|
|
5586
5608
|
}
|
|
5587
5609
|
};
|
|
5588
5610
|
|
|
5611
|
+
// ../../packages/agent-framework/src/session/controller/retry-decision.ts
|
|
5612
|
+
function getAgentSessionRetryLimit(retry, failure) {
|
|
5613
|
+
return Math.min(
|
|
5614
|
+
(failure !== void 0 ? retry?.maxRetriesForFailure?.(failure) : void 0) ?? retry?.maxRetries ?? 3,
|
|
5615
|
+
failure?.kind === "provider_confirmation" ? 1 : Infinity
|
|
5616
|
+
);
|
|
5617
|
+
}
|
|
5618
|
+
var AgentSessionRetryEpisode = class {
|
|
5619
|
+
constructor(retry) {
|
|
5620
|
+
this.retry = retry;
|
|
5621
|
+
this.replayCircuit = new ReplayStallCircuit(retry?.systemicReplayStallLimit);
|
|
5622
|
+
}
|
|
5623
|
+
retry;
|
|
5624
|
+
retryAttempts = /* @__PURE__ */ new Map();
|
|
5625
|
+
total = 0;
|
|
5626
|
+
replayCircuit;
|
|
5627
|
+
get totalRetryAttempts() {
|
|
5628
|
+
return this.total;
|
|
5629
|
+
}
|
|
5630
|
+
/** A healthy response ends the current episode and proves endpoint liveness. */
|
|
5631
|
+
succeed() {
|
|
5632
|
+
this.replayCircuit.recordHealthyTurn();
|
|
5633
|
+
const attempts = this.total;
|
|
5634
|
+
this.resetAttempts();
|
|
5635
|
+
return attempts;
|
|
5636
|
+
}
|
|
5637
|
+
/** Cancellation/invalidation resets counters, never the endpoint-health circuit. */
|
|
5638
|
+
resetAttempts() {
|
|
5639
|
+
this.retryAttempts.clear();
|
|
5640
|
+
this.total = 0;
|
|
5641
|
+
}
|
|
5642
|
+
next(failure) {
|
|
5643
|
+
if (!this.retry || !failure.retryable) {
|
|
5644
|
+
if (failure.kind !== "unknown" && failure.kind !== "internal_error") {
|
|
5645
|
+
this.replayCircuit.recordHealthyTurn();
|
|
5646
|
+
}
|
|
5647
|
+
const totalRetryAttempts2 = this.total;
|
|
5648
|
+
this.resetAttempts();
|
|
5649
|
+
return { action: "terminal", failure, totalRetryAttempts: totalRetryAttempts2 };
|
|
5650
|
+
}
|
|
5651
|
+
if (failure.replayExempt === true && this.replayCircuit.isOpen) {
|
|
5652
|
+
const openFailure = asTerminalFailure(
|
|
5653
|
+
failure,
|
|
5654
|
+
`${failure.message} (replay circuit open: ${this.replayCircuit.consecutiveTurnStalls} consecutive turns ended by timeout-replay exhaustion)`
|
|
5655
|
+
);
|
|
5656
|
+
const totalRetryAttempts2 = this.total;
|
|
5657
|
+
this.resetAttempts();
|
|
5658
|
+
return { action: "exhausted", failure: openFailure, maxRetries: 0, totalRetryAttempts: totalRetryAttempts2, reason: "circuit_open" };
|
|
5659
|
+
}
|
|
5660
|
+
const maxRetries = getAgentSessionRetryLimit(this.retry, failure);
|
|
5661
|
+
const kindAttempts = (this.retryAttempts.get(failure.kind) ?? 0) + 1;
|
|
5662
|
+
this.retryAttempts.set(failure.kind, kindAttempts);
|
|
5663
|
+
this.total += 1;
|
|
5664
|
+
const totalRetryAttempts = this.total;
|
|
5665
|
+
const totalBudget = this.retry.maxTotalRetries;
|
|
5666
|
+
const kindExhausted = kindAttempts > maxRetries;
|
|
5667
|
+
const totalExhausted = totalBudget !== void 0 && totalRetryAttempts > totalBudget;
|
|
5668
|
+
const delayMs = resolveRetryDelay(
|
|
5669
|
+
(this.retry.baseDelayMs ?? 2e3) * 2 ** (kindAttempts - 1),
|
|
5670
|
+
this.retry.maxDelayMs,
|
|
5671
|
+
failure.retryAfterMs
|
|
5672
|
+
);
|
|
5673
|
+
if (kindExhausted || totalExhausted || delayMs === void 0) {
|
|
5674
|
+
const terminalFailure = asTerminalFailure(
|
|
5675
|
+
failure,
|
|
5676
|
+
delayMs === void 0 ? `${failure.message} (Retry-After exceeds the allowed wait; automatic recovery paused)` : failure.message
|
|
5677
|
+
);
|
|
5678
|
+
const circuit = failure.replayExempt === true ? this.replayCircuit.recordReplayExhaustion() : void 0;
|
|
5679
|
+
this.resetAttempts();
|
|
5680
|
+
return {
|
|
5681
|
+
action: "exhausted",
|
|
5682
|
+
failure: terminalFailure,
|
|
5683
|
+
maxRetries: totalExhausted ? totalBudget : maxRetries,
|
|
5684
|
+
totalRetryAttempts,
|
|
5685
|
+
reason: delayMs === void 0 ? "retry_after" : totalExhausted ? "total_budget" : "kind_budget",
|
|
5686
|
+
...circuit ? { circuit } : {}
|
|
5687
|
+
};
|
|
5688
|
+
}
|
|
5689
|
+
return {
|
|
5690
|
+
action: "retry",
|
|
5691
|
+
failure,
|
|
5692
|
+
attempt: kindAttempts,
|
|
5693
|
+
maxRetries,
|
|
5694
|
+
delayMs,
|
|
5695
|
+
totalRetryAttempts
|
|
5696
|
+
};
|
|
5697
|
+
}
|
|
5698
|
+
};
|
|
5699
|
+
|
|
5589
5700
|
// ../../packages/agent-framework/src/session/controller/retry-policy.ts
|
|
5590
5701
|
function defaultSetTimeout4(callback, delay) {
|
|
5591
5702
|
return setTimeout(callback, delay);
|
|
@@ -5600,21 +5711,14 @@ var AgentSessionRetryController = class {
|
|
|
5600
5711
|
this.removeTrailingAssistantError = removeTrailingAssistantError2;
|
|
5601
5712
|
this.continueTurn = continueTurn;
|
|
5602
5713
|
this.setRecoveryState = setRecoveryState;
|
|
5603
|
-
this.
|
|
5714
|
+
this.episode = new AgentSessionRetryEpisode(retry);
|
|
5604
5715
|
}
|
|
5605
5716
|
retry;
|
|
5606
5717
|
emit;
|
|
5607
5718
|
removeTrailingAssistantError;
|
|
5608
5719
|
continueTurn;
|
|
5609
5720
|
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;
|
|
5721
|
+
episode;
|
|
5618
5722
|
retryTimer;
|
|
5619
5723
|
retryEpoch = 0;
|
|
5620
5724
|
pendingRetry;
|
|
@@ -5628,72 +5732,45 @@ var AgentSessionRetryController = class {
|
|
|
5628
5732
|
waitForRetry() {
|
|
5629
5733
|
return this.pendingRetry ?? Promise.resolve();
|
|
5630
5734
|
}
|
|
5631
|
-
/** L2 circuit: healthy turn (success or an actively-answered failure). */
|
|
5632
|
-
noteEndpointHealthy() {
|
|
5633
|
-
this.replayCircuit.recordHealthyTurn();
|
|
5634
|
-
}
|
|
5635
5735
|
async handleTurnEnd(message, failure) {
|
|
5636
5736
|
if (!this.retry || !message || message.role !== "assistant") {
|
|
5637
5737
|
this.settleStrandedRetry(failure);
|
|
5638
5738
|
return;
|
|
5639
5739
|
}
|
|
5640
5740
|
if (!failure) {
|
|
5641
|
-
this.
|
|
5642
|
-
if (
|
|
5643
|
-
this.emit({ type: "retry_succeeded", attempts
|
|
5644
|
-
this.emit({ type: "turn_retry_succeeded", attempts
|
|
5645
|
-
this.resetAttempts();
|
|
5741
|
+
const attempts = this.episode.succeed();
|
|
5742
|
+
if (attempts > 0) {
|
|
5743
|
+
this.emit({ type: "retry_succeeded", attempts });
|
|
5744
|
+
this.emit({ type: "turn_retry_succeeded", attempts });
|
|
5646
5745
|
this.setRecoveryState("none");
|
|
5647
5746
|
this.resolvePending();
|
|
5648
5747
|
}
|
|
5649
5748
|
return;
|
|
5650
5749
|
}
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
}
|
|
5655
|
-
if (this.totalRetryAttempts > 0) {
|
|
5656
|
-
this.resetAttempts();
|
|
5750
|
+
const decision = this.episode.next(failure);
|
|
5751
|
+
if (decision.action === "terminal") {
|
|
5752
|
+
if (decision.totalRetryAttempts > 0) {
|
|
5657
5753
|
this.setRecoveryState("none");
|
|
5658
5754
|
this.resolvePending();
|
|
5659
5755
|
}
|
|
5660
5756
|
return;
|
|
5661
5757
|
}
|
|
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
|
-
}
|
|
5758
|
+
if (decision.action === "exhausted") {
|
|
5759
|
+
if (decision.circuit?.opened) {
|
|
5760
|
+
this.emit({
|
|
5761
|
+
type: "retry_exhausted",
|
|
5762
|
+
errorMessage: `systemic timeout-stall: ${decision.circuit.consecutive} consecutive turns ended by timeout-replay exhaustion`,
|
|
5763
|
+
maxRetries: decision.maxRetries
|
|
5764
|
+
});
|
|
5687
5765
|
}
|
|
5688
|
-
this.emit({ type: "retry_exhausted", errorMessage:
|
|
5689
|
-
this.emit({ type: "turn_failed_terminal", failure:
|
|
5766
|
+
this.emit({ type: "retry_exhausted", errorMessage: decision.failure.message, maxRetries: decision.maxRetries });
|
|
5767
|
+
this.emit({ type: "turn_failed_terminal", failure: decision.failure, message });
|
|
5690
5768
|
this.emit({ type: "turn_settled" });
|
|
5691
|
-
this.resetAttempts();
|
|
5692
5769
|
this.setRecoveryState("terminal");
|
|
5693
5770
|
this.resolvePending();
|
|
5694
5771
|
return;
|
|
5695
5772
|
}
|
|
5696
|
-
const attempt =
|
|
5773
|
+
const { attempt, maxRetries, delayMs } = decision;
|
|
5697
5774
|
this.ensurePending();
|
|
5698
5775
|
const epoch = this.retryEpoch;
|
|
5699
5776
|
const pending = this.pendingRetry;
|
|
@@ -5720,7 +5797,7 @@ var AgentSessionRetryController = class {
|
|
|
5720
5797
|
this.emit({ type: "retry_exhausted", errorMessage, maxRetries });
|
|
5721
5798
|
this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
|
|
5722
5799
|
this.emit({ type: "turn_settled" });
|
|
5723
|
-
this.resetAttempts();
|
|
5800
|
+
this.episode.resetAttempts();
|
|
5724
5801
|
this.setRecoveryState("terminal");
|
|
5725
5802
|
this.resolvePending(pending);
|
|
5726
5803
|
});
|
|
@@ -5745,24 +5822,20 @@ var AgentSessionRetryController = class {
|
|
|
5745
5822
|
userAbort: false,
|
|
5746
5823
|
terminal: true
|
|
5747
5824
|
});
|
|
5748
|
-
const maxRetries =
|
|
5825
|
+
const maxRetries = getAgentSessionRetryLimit(this.retry, failure);
|
|
5749
5826
|
this.emit({ type: "retry_exhausted", errorMessage: terminalFailure.message, maxRetries });
|
|
5750
5827
|
this.emit({ type: "turn_failed_terminal", failure: terminalFailure });
|
|
5751
5828
|
this.emit({ type: "turn_settled" });
|
|
5752
|
-
this.resetAttempts();
|
|
5829
|
+
this.episode.resetAttempts();
|
|
5753
5830
|
this.setRecoveryState("terminal");
|
|
5754
5831
|
this.resolvePending();
|
|
5755
5832
|
}
|
|
5756
5833
|
cancel() {
|
|
5757
5834
|
this.retryEpoch++;
|
|
5758
5835
|
this.cancelTimerOnly();
|
|
5759
|
-
this.resetAttempts();
|
|
5836
|
+
this.episode.resetAttempts();
|
|
5760
5837
|
this.resolvePending();
|
|
5761
5838
|
}
|
|
5762
|
-
resetAttempts() {
|
|
5763
|
-
this.retryAttempts.clear();
|
|
5764
|
-
this.totalRetryAttempts = 0;
|
|
5765
|
-
}
|
|
5766
5839
|
cancelTimerOnly() {
|
|
5767
5840
|
if (this.retryTimer === void 0) {
|
|
5768
5841
|
return;
|
|
@@ -6101,7 +6174,8 @@ async function runAgentSessionCompaction(params) {
|
|
|
6101
6174
|
const preparation = prepareSessionCompaction({
|
|
6102
6175
|
sessionManager: params.sessionManager,
|
|
6103
6176
|
messages: params.agent.state.messages,
|
|
6104
|
-
budgetTokens: params.compactionBudgetTokens?.()
|
|
6177
|
+
budgetTokens: params.compactionBudgetTokens?.(),
|
|
6178
|
+
modelView: params.compaction.modelView
|
|
6105
6179
|
});
|
|
6106
6180
|
let result = await params.compaction.compact({
|
|
6107
6181
|
reason: params.reason,
|
|
@@ -6239,7 +6313,8 @@ async function handleAgentSessionTurnEndRuntimePolicies(params) {
|
|
|
6239
6313
|
params.emit({ type: "turn_settled" });
|
|
6240
6314
|
return;
|
|
6241
6315
|
}
|
|
6242
|
-
const
|
|
6316
|
+
const recoveryClosed = failure?.terminal && params.message?.role === "assistant" && (params.message.retryable === false || params.message.operationFailure?.terminal === true);
|
|
6317
|
+
const compactDecision = recoveryClosed ? void 0 : params.getAutoCompactDecision?.({
|
|
6243
6318
|
agent: params.agent,
|
|
6244
6319
|
sessionManager: params.sessionManager,
|
|
6245
6320
|
cwd: params.cwd,
|
|
@@ -6380,6 +6455,8 @@ var AgentSessionController = class {
|
|
|
6380
6455
|
autoContinue;
|
|
6381
6456
|
unsubscribeAgent;
|
|
6382
6457
|
unsubscribeSemantic;
|
|
6458
|
+
unsubscribeToolContinuation;
|
|
6459
|
+
compactionOwner;
|
|
6383
6460
|
lastSyncedMessageCount = 0;
|
|
6384
6461
|
disposed = false;
|
|
6385
6462
|
transitioning = false;
|
|
@@ -6483,6 +6560,7 @@ var AgentSessionController = class {
|
|
|
6483
6560
|
});
|
|
6484
6561
|
this.syncFromActiveSession();
|
|
6485
6562
|
this.unsubscribeAgent = this.agent.subscribe((event) => this.handleAgentEvent(event));
|
|
6563
|
+
this.unsubscribeToolContinuation = this.agent.setBeforeToolContinuation((signal) => this.runPreRequestAutoCompactIfNeeded(signal));
|
|
6486
6564
|
this.unsubscribeSemantic = this.agent.subscribeSemantic((event) => {
|
|
6487
6565
|
this.emit({ type: "semantic_event", event });
|
|
6488
6566
|
});
|
|
@@ -6533,6 +6611,7 @@ var AgentSessionController = class {
|
|
|
6533
6611
|
this.transitioning = true;
|
|
6534
6612
|
try {
|
|
6535
6613
|
this.generation++;
|
|
6614
|
+
if (this.isStreaming) this.agent.resetProcessingState();
|
|
6536
6615
|
this.cancelScheduledWork();
|
|
6537
6616
|
this.recoveryState = "none";
|
|
6538
6617
|
this.pendingCompactionFailure = void 0;
|
|
@@ -6664,48 +6743,61 @@ var AgentSessionController = class {
|
|
|
6664
6743
|
const generation = this.generation;
|
|
6665
6744
|
if (this.disposed || this.transitioning) throw new Error("Controller disposed or transitioning");
|
|
6666
6745
|
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
|
-
|
|
6746
|
+
if (this.compactionOwner) throw new Error("Compaction already running");
|
|
6747
|
+
const owner = this.compactionOwner = {};
|
|
6748
|
+
try {
|
|
6749
|
+
return await this.convergence.run(() => runAgentSessionControllerAutoCompact({
|
|
6750
|
+
isCurrent,
|
|
6751
|
+
reason,
|
|
6752
|
+
willRetry,
|
|
6753
|
+
signal: options?.signal,
|
|
6754
|
+
compaction: this.compaction,
|
|
6755
|
+
agent: this.agent,
|
|
6756
|
+
sessionManager: this.sessionManager,
|
|
6757
|
+
cwd: this.cwd,
|
|
6758
|
+
pendingFailure: this.pendingCompactionFailure,
|
|
6759
|
+
emit: (event) => {
|
|
6760
|
+
if (isCurrent()) this.emit(event);
|
|
6761
|
+
},
|
|
6762
|
+
setRecoveryState: (state) => {
|
|
6763
|
+
this.recoveryState = state;
|
|
6764
|
+
},
|
|
6765
|
+
getRecoveryState: () => this.recoveryState,
|
|
6766
|
+
getDisposed: () => this.disposed,
|
|
6767
|
+
cancelRetry: () => this.retryController.cancel(),
|
|
6768
|
+
clearPendingFailure: () => {
|
|
6769
|
+
this.pendingCompactionFailure = void 0;
|
|
6770
|
+
},
|
|
6771
|
+
getCompactionBudgetTokens: this.compactionSettings.getCompactionBudgetTokens,
|
|
6772
|
+
removeTrailingAssistantError: () => this.removeTrailingAssistantError(),
|
|
6773
|
+
replaceMessagesAndMarkSynced: (messages) => this.replaceMessagesAndMarkSynced(messages),
|
|
6774
|
+
markSyncedToAgentState: () => this.markSyncedToAgentState(),
|
|
6775
|
+
continueTurn: async () => {
|
|
6776
|
+
if (!isCurrent() || options?.signal?.aborted) return;
|
|
6777
|
+
if (this.compactionOwner === owner) this.compactionOwner = void 0;
|
|
6778
|
+
this.recoveryState = "none";
|
|
6779
|
+
await this.continue();
|
|
6780
|
+
},
|
|
6781
|
+
handleAgentEndHook: (messages) => this.handleAgentEndHook(messages),
|
|
6782
|
+
emitSettled: options?.emitSettled,
|
|
6783
|
+
runAfterAgentEndHook: options?.runAfterAgentEndHook,
|
|
6784
|
+
cancelOnAbort: options?.cancelOnAbort
|
|
6785
|
+
}));
|
|
6786
|
+
} finally {
|
|
6787
|
+
if (this.compactionOwner === owner) this.compactionOwner = void 0;
|
|
6788
|
+
}
|
|
6699
6789
|
}
|
|
6700
6790
|
dispose() {
|
|
6701
6791
|
if (this.disposed) return;
|
|
6702
6792
|
this.disposed = true;
|
|
6793
|
+
if (this.isStreaming) this.agent.resetProcessingState();
|
|
6703
6794
|
this.agent.releaseTransport?.();
|
|
6704
6795
|
this.generation++;
|
|
6705
6796
|
this.pendingEndOperations.clear();
|
|
6706
6797
|
this.cancelScheduledWork();
|
|
6707
6798
|
this.unsubscribeAgent?.();
|
|
6708
6799
|
this.unsubscribeSemantic?.();
|
|
6800
|
+
this.unsubscribeToolContinuation?.();
|
|
6709
6801
|
this.queue.clear();
|
|
6710
6802
|
this.convergence.invalidate("dispose");
|
|
6711
6803
|
this.listeners.clear();
|
|
@@ -6729,19 +6821,26 @@ var AgentSessionController = class {
|
|
|
6729
6821
|
});
|
|
6730
6822
|
}
|
|
6731
6823
|
}
|
|
6732
|
-
async runPreRequestAutoCompactIfNeeded() {
|
|
6824
|
+
async runPreRequestAutoCompactIfNeeded(signal) {
|
|
6733
6825
|
const generation = this.generation;
|
|
6734
6826
|
return runPreRequestAutoCompact({
|
|
6735
6827
|
isCurrent: () => generation === this.generation && !this.disposed,
|
|
6736
6828
|
disposed: this.disposed,
|
|
6737
6829
|
runtimeGuardTriggered: this.runtimeGuard.triggered,
|
|
6738
6830
|
isStreaming: this.isStreaming,
|
|
6739
|
-
recoveryActive: this.isRecoveryActive(),
|
|
6831
|
+
recoveryActive: () => this.isRecoveryActive(),
|
|
6740
6832
|
agent: this.agent,
|
|
6741
6833
|
sessionManager: this.sessionManager,
|
|
6742
6834
|
cwd: this.cwd,
|
|
6743
6835
|
getAutoCompactDecision: this.compactionSettings.getAutoCompactDecision,
|
|
6744
|
-
|
|
6836
|
+
toolContinuation: signal ? { signal, sync: async () => {
|
|
6837
|
+
await this.pendingEndOperations.wait();
|
|
6838
|
+
if (generation === this.generation && !this.disposed && !signal.aborted) await this.syncNewAgentMessages();
|
|
6839
|
+
} } : void 0,
|
|
6840
|
+
runAutoCompact: async () => {
|
|
6841
|
+
const result = await this.runAutoCompact("threshold", false, { signal, emitSettled: false, runAfterAgentEndHook: false, cancelOnAbort: false });
|
|
6842
|
+
if (signal && (!result || result.aborted)) throw new Error("Tool-continuation compaction was aborted");
|
|
6843
|
+
},
|
|
6745
6844
|
setRecoveryState: (state) => {
|
|
6746
6845
|
this.recoveryState = state;
|
|
6747
6846
|
}
|
|
@@ -6886,6 +6985,7 @@ var AgentSessionController = class {
|
|
|
6886
6985
|
return this.recoveryState !== "none" || this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact;
|
|
6887
6986
|
}
|
|
6888
6987
|
cancelScheduledWork() {
|
|
6988
|
+
this.compactionOwner = void 0;
|
|
6889
6989
|
this.retryController.cancel();
|
|
6890
6990
|
this.autoCompact.cancel();
|
|
6891
6991
|
this.queueDispatcher.cancel();
|
|
@@ -8241,6 +8341,9 @@ function withSubagentRunContext(stream, hooks) {
|
|
|
8241
8341
|
${SUBAGENT_FINALIZATION_HINT}` } : context, options);
|
|
8242
8342
|
};
|
|
8243
8343
|
}
|
|
8344
|
+
function isRetryableSubagentFailure(message) {
|
|
8345
|
+
return isRetryableSubagentFailureFrom({ errorMessage: message });
|
|
8346
|
+
}
|
|
8244
8347
|
function isRetryableSubagentFailureFrom(facts) {
|
|
8245
8348
|
if (facts.retryable === false) return false;
|
|
8246
8349
|
if (facts.replayClass === "stream_lost" || facts.replayClass === "stream_lost_partial") return true;
|
|
@@ -8262,90 +8365,366 @@ function cancellableBackoff(ms, signal) {
|
|
|
8262
8365
|
if (signal?.aborted) finish();
|
|
8263
8366
|
});
|
|
8264
8367
|
}
|
|
8368
|
+
function factsFromMessage(message) {
|
|
8369
|
+
return {
|
|
8370
|
+
errorMessage: message.errorMessage,
|
|
8371
|
+
retryable: message.retryable,
|
|
8372
|
+
replayClass: message.replayClass,
|
|
8373
|
+
replayEligible: message.replayEligible,
|
|
8374
|
+
remoteFailure: message.remoteFailure,
|
|
8375
|
+
stopReason: message.stopReason
|
|
8376
|
+
};
|
|
8377
|
+
}
|
|
8265
8378
|
function factsOf(error) {
|
|
8266
8379
|
if (error instanceof Error) {
|
|
8267
8380
|
const carrier = error;
|
|
8268
|
-
return {
|
|
8381
|
+
return {
|
|
8382
|
+
errorMessage: carrier.subagentFailure?.errorMessage ?? error.message,
|
|
8383
|
+
retryable: carrier.subagentFailure?.retryable ?? carrier.retryable,
|
|
8384
|
+
replayClass: carrier.subagentFailure?.replayClass ?? carrier.replayClass,
|
|
8385
|
+
replayEligible: carrier.subagentFailure?.replayEligible ?? carrier.replayEligible,
|
|
8386
|
+
remoteFailure: carrier.subagentFailure?.remoteFailure ?? carrier.remoteFailure,
|
|
8387
|
+
stopReason: carrier.subagentFailure?.stopReason ?? carrier.stopReason ?? (carrier.subagentFailure?.classification?.userAbort ? "aborted" : "error")
|
|
8388
|
+
};
|
|
8269
8389
|
}
|
|
8270
|
-
return { errorMessage: String(error) };
|
|
8390
|
+
return { errorMessage: String(error), stopReason: "error" };
|
|
8271
8391
|
}
|
|
8272
|
-
function
|
|
8392
|
+
function assistantFromFacts(facts) {
|
|
8393
|
+
return {
|
|
8394
|
+
role: "assistant",
|
|
8395
|
+
api: "unknown",
|
|
8396
|
+
provider: "unknown",
|
|
8397
|
+
model: "unknown",
|
|
8398
|
+
content: [],
|
|
8399
|
+
timestamp: 0,
|
|
8400
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
|
|
8401
|
+
stopReason: facts.stopReason ?? "error",
|
|
8402
|
+
errorMessage: facts.errorMessage,
|
|
8403
|
+
...facts.retryable !== void 0 ? { retryable: facts.retryable } : {},
|
|
8404
|
+
...facts.replayClass !== void 0 ? { replayClass: facts.replayClass } : {},
|
|
8405
|
+
...facts.replayEligible !== void 0 ? { replayEligible: facts.replayEligible } : {},
|
|
8406
|
+
...facts.remoteFailure !== void 0 ? { remoteFailure: facts.remoteFailure } : {}
|
|
8407
|
+
};
|
|
8408
|
+
}
|
|
8409
|
+
function boundedClassification(failure) {
|
|
8410
|
+
return { ...failure, message: failure.message.slice(0, 2e3) };
|
|
8411
|
+
}
|
|
8412
|
+
function failureDetails(facts, classification, recovery) {
|
|
8413
|
+
return {
|
|
8414
|
+
errorMessage: (classification.message || facts.errorMessage || "Subagent response failed").slice(0, 2e3),
|
|
8415
|
+
...facts.retryable !== void 0 ? { retryable: facts.retryable } : {},
|
|
8416
|
+
...facts.replayClass !== void 0 ? { replayClass: facts.replayClass } : {},
|
|
8417
|
+
...facts.replayEligible !== void 0 ? { replayEligible: facts.replayEligible } : {},
|
|
8418
|
+
...facts.remoteFailure !== void 0 ? {
|
|
8419
|
+
remoteFailure: {
|
|
8420
|
+
category: facts.remoteFailure.category,
|
|
8421
|
+
...typeof facts.remoteFailure.retryAfterMs === "number" && Number.isFinite(facts.remoteFailure.retryAfterMs) && facts.remoteFailure.retryAfterMs >= 0 ? { retryAfterMs: facts.remoteFailure.retryAfterMs } : {}
|
|
8422
|
+
}
|
|
8423
|
+
} : {},
|
|
8424
|
+
...facts.stopReason === "aborted" ? { stopReason: facts.stopReason } : {},
|
|
8425
|
+
classification: boundedClassification(classification),
|
|
8426
|
+
recovery
|
|
8427
|
+
};
|
|
8428
|
+
}
|
|
8429
|
+
function legacyRecovery(backoffMs) {
|
|
8430
|
+
return {
|
|
8431
|
+
policy: {
|
|
8432
|
+
isRetryableError: (message) => isRetryableSubagentFailure(message),
|
|
8433
|
+
maxRetries: 2,
|
|
8434
|
+
baseDelayMs: backoffMs
|
|
8435
|
+
},
|
|
8436
|
+
classifyFailure: (context) => {
|
|
8437
|
+
const facts = context.message?.role === "assistant" ? factsFromMessage(context.message) : { errorMessage: context.errorMessage, stopReason: "error" };
|
|
8438
|
+
const retryable = isRetryableSubagentFailureFrom({ ...facts, errorMessage: context.errorMessage });
|
|
8439
|
+
return {
|
|
8440
|
+
kind: retryable ? "provider_transient" : "unknown",
|
|
8441
|
+
retryable,
|
|
8442
|
+
compactable: false,
|
|
8443
|
+
userAbort: false,
|
|
8444
|
+
terminal: !retryable,
|
|
8445
|
+
message: context.errorMessage
|
|
8446
|
+
};
|
|
8447
|
+
}
|
|
8448
|
+
};
|
|
8449
|
+
}
|
|
8450
|
+
function resolveRecovery(options, backoffMs) {
|
|
8451
|
+
if (!options) return legacyRecovery(backoffMs);
|
|
8452
|
+
const fallback = legacyRecovery(backoffMs);
|
|
8453
|
+
return {
|
|
8454
|
+
policy: options.policy ?? fallback.policy,
|
|
8455
|
+
// An explicitly supplied policy without a classifier must reach its own
|
|
8456
|
+
// isRetryableError predicate, exactly as in the main controller. Only the
|
|
8457
|
+
// no-host compatible default installs the legacy prose classifier.
|
|
8458
|
+
classifyFailure: options.classifyFailure ?? (options.policy ? void 0 : fallback.classifyFailure)
|
|
8459
|
+
};
|
|
8460
|
+
}
|
|
8461
|
+
function classify(facts, recovery) {
|
|
8462
|
+
const message = assistantFromFacts(facts);
|
|
8463
|
+
return createFailureClassification({
|
|
8464
|
+
message,
|
|
8465
|
+
errorMessage: facts.errorMessage ?? "Subagent response failed",
|
|
8466
|
+
phase: "stream"
|
|
8467
|
+
}, recovery.policy, recovery.classifyFailure);
|
|
8468
|
+
}
|
|
8469
|
+
var DEFAULT_ITERATOR_CLEANUP_MS = 2e3;
|
|
8470
|
+
async function closeIterator(iterator, hooks, signal) {
|
|
8471
|
+
const cleanup = Promise.resolve().then(() => iterator.return?.()).then(() => void 0).catch(() => void 0);
|
|
8472
|
+
const remaining = hooks?.hardDeadlineAt === void 0 ? DEFAULT_ITERATOR_CLEANUP_MS : Math.max(0, hooks.hardDeadlineAt - Date.now());
|
|
8473
|
+
const waitMs = Math.min(DEFAULT_ITERATOR_CLEANUP_MS, remaining);
|
|
8474
|
+
if (waitMs <= 0 || signal?.aborted) return;
|
|
8475
|
+
let timer;
|
|
8476
|
+
let onAbort;
|
|
8477
|
+
try {
|
|
8478
|
+
await Promise.race([
|
|
8479
|
+
cleanup,
|
|
8480
|
+
new Promise((resolve2) => {
|
|
8481
|
+
timer = setTimeout(resolve2, waitMs);
|
|
8482
|
+
onAbort = () => resolve2();
|
|
8483
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
8484
|
+
})
|
|
8485
|
+
]);
|
|
8486
|
+
} finally {
|
|
8487
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
8488
|
+
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
8489
|
+
}
|
|
8490
|
+
}
|
|
8491
|
+
function singleResponse(message) {
|
|
8492
|
+
const result = new AssistantMessageEventStream();
|
|
8493
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
8494
|
+
result.push({ type: "error", reason: message.stopReason, error: { ...message, content: [] } });
|
|
8495
|
+
} else {
|
|
8496
|
+
result.push({ type: "done", reason: message.stopReason, message });
|
|
8497
|
+
}
|
|
8498
|
+
result.end();
|
|
8499
|
+
return result;
|
|
8500
|
+
}
|
|
8501
|
+
function throwWithDetails(error, details) {
|
|
8502
|
+
const failure = new Error(details.classification?.message ?? (error instanceof Error ? error.message : String(error)));
|
|
8503
|
+
failure.cause = error;
|
|
8504
|
+
failure.subagentFailure = details;
|
|
8505
|
+
throw failure;
|
|
8506
|
+
}
|
|
8507
|
+
function withSubagentResponseRecovery(stream, backoffMs = 2e3, onRetry, onActivity, hooks, recoveryOptions) {
|
|
8508
|
+
const recovery = resolveRecovery(recoveryOptions, backoffMs);
|
|
8509
|
+
const episode = recoveryOptions?.episode ?? hooks?.recoveryEpisode ?? new AgentSessionRetryEpisode(recovery.policy);
|
|
8510
|
+
if (hooks) hooks.recoveryEpisode = episode;
|
|
8273
8511
|
return async (model, context, options) => {
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8512
|
+
let attempts = 0;
|
|
8513
|
+
let retries = 0;
|
|
8514
|
+
if (hooks) hooks.failure = void 0;
|
|
8515
|
+
try {
|
|
8516
|
+
for (; ; ) {
|
|
8517
|
+
if (hooks) {
|
|
8518
|
+
hooks.terminalText = void 0;
|
|
8519
|
+
hooks.terminalStopReason = void 0;
|
|
8520
|
+
}
|
|
8521
|
+
if (options?.signal?.aborted) {
|
|
8522
|
+
episode.resetAttempts();
|
|
8523
|
+
throw new Error("Subagent aborted by parent");
|
|
8524
|
+
}
|
|
8525
|
+
if (hooks?.hardDeadlineAt !== void 0 && Date.now() >= hooks.hardDeadlineAt) throw new Error("Subagent hard deadline reached");
|
|
8526
|
+
let message;
|
|
8527
|
+
const finishUsage = hooks?.usage ? beginSubagentUsage(hooks.usage, model) : void 0;
|
|
8528
|
+
attempts += 1;
|
|
8287
8529
|
try {
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8530
|
+
const response = await stream(model, context, options);
|
|
8531
|
+
let terminal;
|
|
8532
|
+
const iterator = response[Symbol.asyncIterator]();
|
|
8533
|
+
try {
|
|
8534
|
+
for (; ; ) {
|
|
8535
|
+
const next = await iterator.next();
|
|
8536
|
+
if (next.done) break;
|
|
8537
|
+
try {
|
|
8538
|
+
onActivity?.(next.value.type);
|
|
8539
|
+
} catch {
|
|
8540
|
+
}
|
|
8541
|
+
if (next.value.type === "done") {
|
|
8542
|
+
terminal = next.value.message;
|
|
8543
|
+
finishUsage?.(terminal);
|
|
8544
|
+
}
|
|
8545
|
+
if (next.value.type === "error") {
|
|
8546
|
+
terminal = next.value.error;
|
|
8547
|
+
finishUsage?.(terminal);
|
|
8548
|
+
}
|
|
8549
|
+
if (next.value.type === "done" || next.value.type === "error") {
|
|
8550
|
+
if (hooks && terminal) {
|
|
8551
|
+
const reportTerminal = ["stop", "length", "error", "aborted"].includes(terminal.stopReason) && !terminal.content.some((block) => block.type === "toolCall");
|
|
8552
|
+
hooks.terminalStopReason = reportTerminal ? terminal.stopReason : void 0;
|
|
8553
|
+
hooks.terminalText = reportTerminal ? terminal.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").slice(0, 24001) : void 0;
|
|
8554
|
+
}
|
|
8555
|
+
break;
|
|
8556
|
+
}
|
|
8307
8557
|
}
|
|
8558
|
+
} finally {
|
|
8559
|
+
await closeIterator(iterator, hooks, options?.signal);
|
|
8308
8560
|
}
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8561
|
+
if (!terminal) throw new Error("Provider protocol error: response ended without a terminal event");
|
|
8562
|
+
message = terminal;
|
|
8563
|
+
} catch (error) {
|
|
8564
|
+
finishUsage?.();
|
|
8565
|
+
if (options?.signal?.aborted) {
|
|
8566
|
+
episode.resetAttempts();
|
|
8567
|
+
throw error;
|
|
8313
8568
|
}
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8569
|
+
const facts2 = factsOf(error);
|
|
8570
|
+
if (facts2.stopReason === "aborted") {
|
|
8571
|
+
episode.resetAttempts();
|
|
8572
|
+
const classification3 = {
|
|
8573
|
+
kind: "user_abort",
|
|
8574
|
+
retryable: false,
|
|
8575
|
+
compactable: false,
|
|
8576
|
+
userAbort: true,
|
|
8577
|
+
terminal: true,
|
|
8578
|
+
message: facts2.errorMessage ?? "Subagent aborted"
|
|
8579
|
+
};
|
|
8580
|
+
const details = failureDetails(facts2, classification3, { outcome: "terminal", attempts, retries, reason: "cancelled" });
|
|
8581
|
+
if (hooks) hooks.failure = details;
|
|
8582
|
+
throwWithDetails(error, details);
|
|
8323
8583
|
}
|
|
8324
|
-
|
|
8325
|
-
|
|
8584
|
+
const classification2 = classify(facts2, recovery);
|
|
8585
|
+
const failure2 = await resolveFailure({
|
|
8586
|
+
facts: facts2,
|
|
8587
|
+
classification: classification2,
|
|
8588
|
+
attempts,
|
|
8589
|
+
retries,
|
|
8590
|
+
episode,
|
|
8591
|
+
hooks,
|
|
8592
|
+
onRetry,
|
|
8593
|
+
options
|
|
8594
|
+
});
|
|
8595
|
+
if (failure2.retry) {
|
|
8596
|
+
retries += 1;
|
|
8597
|
+
continue;
|
|
8598
|
+
}
|
|
8599
|
+
throwWithDetails(error, failure2.details);
|
|
8326
8600
|
}
|
|
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 {
|
|
8601
|
+
if (options?.signal?.aborted) {
|
|
8602
|
+
episode.resetAttempts();
|
|
8603
|
+
throw new Error("Subagent aborted by parent");
|
|
8334
8604
|
}
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8605
|
+
if (hooks?.hardDeadlineAt !== void 0 && Date.now() >= hooks.hardDeadlineAt) {
|
|
8606
|
+
episode.resetAttempts();
|
|
8607
|
+
throw new Error("Subagent hard deadline reached");
|
|
8608
|
+
}
|
|
8609
|
+
if (message.stopReason !== "error" && message.stopReason !== "aborted") {
|
|
8610
|
+
episode.succeed();
|
|
8611
|
+
if (hooks) hooks.failure = void 0;
|
|
8612
|
+
return singleResponse(message);
|
|
8613
|
+
}
|
|
8614
|
+
if (message.stopReason === "aborted") {
|
|
8615
|
+
episode.resetAttempts();
|
|
8616
|
+
if (hooks) hooks.failure = void 0;
|
|
8617
|
+
return singleResponse(message);
|
|
8618
|
+
}
|
|
8619
|
+
const facts = factsFromMessage(message);
|
|
8620
|
+
const classification = classify(facts, recovery);
|
|
8621
|
+
const failure = await resolveFailure({
|
|
8622
|
+
facts,
|
|
8623
|
+
classification,
|
|
8624
|
+
attempts,
|
|
8625
|
+
retries,
|
|
8626
|
+
episode,
|
|
8627
|
+
hooks,
|
|
8628
|
+
onRetry,
|
|
8629
|
+
options
|
|
8630
|
+
});
|
|
8631
|
+
if (failure.retry) {
|
|
8632
|
+
retries += 1;
|
|
8633
|
+
continue;
|
|
8634
|
+
}
|
|
8635
|
+
const finalMessage = failure.details.classification?.message && failure.details.classification.message !== message.errorMessage ? { ...message, errorMessage: failure.details.classification.message } : message;
|
|
8636
|
+
return singleResponse(finalMessage);
|
|
8343
8637
|
}
|
|
8344
|
-
|
|
8345
|
-
|
|
8638
|
+
} finally {
|
|
8639
|
+
episode.resetAttempts();
|
|
8346
8640
|
}
|
|
8347
8641
|
};
|
|
8348
8642
|
}
|
|
8643
|
+
async function resolveFailure(params) {
|
|
8644
|
+
const { facts, classification, attempts, retries, episode, hooks, onRetry, options } = params;
|
|
8645
|
+
if (options?.signal?.aborted) {
|
|
8646
|
+
episode.resetAttempts();
|
|
8647
|
+
throw new Error("Subagent aborted by parent");
|
|
8648
|
+
}
|
|
8649
|
+
if (classification.userAbort) {
|
|
8650
|
+
episode.resetAttempts();
|
|
8651
|
+
const cancellation = {
|
|
8652
|
+
...classification,
|
|
8653
|
+
kind: "user_abort",
|
|
8654
|
+
retryable: false,
|
|
8655
|
+
compactable: false,
|
|
8656
|
+
userAbort: true,
|
|
8657
|
+
terminal: true
|
|
8658
|
+
};
|
|
8659
|
+
const details2 = failureDetails({ ...facts, stopReason: "aborted" }, cancellation, {
|
|
8660
|
+
outcome: "terminal",
|
|
8661
|
+
attempts,
|
|
8662
|
+
retries,
|
|
8663
|
+
reason: "cancelled"
|
|
8664
|
+
});
|
|
8665
|
+
if (hooks) hooks.failure = details2;
|
|
8666
|
+
return { retry: false, details: details2 };
|
|
8667
|
+
}
|
|
8668
|
+
if (classification.compactable) {
|
|
8669
|
+
const details2 = failureDetails(facts, classification, {
|
|
8670
|
+
outcome: "overflow",
|
|
8671
|
+
attempts,
|
|
8672
|
+
retries,
|
|
8673
|
+
reason: "compaction_unavailable"
|
|
8674
|
+
});
|
|
8675
|
+
if (hooks) hooks.failure = details2;
|
|
8676
|
+
return { retry: false, details: details2 };
|
|
8677
|
+
}
|
|
8678
|
+
let decision = episode.next(classification);
|
|
8679
|
+
if (decision.action === "retry") {
|
|
8680
|
+
const remaining = hooks?.hardDeadlineAt === void 0 ? Infinity : hooks.hardDeadlineAt - Date.now();
|
|
8681
|
+
if (remaining <= 0) {
|
|
8682
|
+
episode.resetAttempts();
|
|
8683
|
+
throw new Error("Subagent hard deadline reached");
|
|
8684
|
+
}
|
|
8685
|
+
if (decision.delayMs > remaining) {
|
|
8686
|
+
const deadlineClassification = {
|
|
8687
|
+
...classification,
|
|
8688
|
+
retryable: false,
|
|
8689
|
+
terminal: true,
|
|
8690
|
+
message: `${classification.message} (recovery delay ${decision.delayMs}ms exceeds the remaining subagent deadline ${Math.max(0, Math.round(remaining))}ms)`
|
|
8691
|
+
};
|
|
8692
|
+
const details2 = failureDetails(facts, deadlineClassification, {
|
|
8693
|
+
outcome: "exhausted",
|
|
8694
|
+
attempts,
|
|
8695
|
+
retries,
|
|
8696
|
+
maxRetries: decision.maxRetries,
|
|
8697
|
+
reason: "deadline"
|
|
8698
|
+
});
|
|
8699
|
+
if (hooks) hooks.failure = details2;
|
|
8700
|
+
return { retry: false, details: details2 };
|
|
8701
|
+
}
|
|
8702
|
+
try {
|
|
8703
|
+
onRetry?.();
|
|
8704
|
+
} catch {
|
|
8705
|
+
}
|
|
8706
|
+
await cancellableBackoff(decision.delayMs, options?.signal);
|
|
8707
|
+
if (options?.signal?.aborted) {
|
|
8708
|
+
episode.resetAttempts();
|
|
8709
|
+
throw new Error("Subagent aborted by parent");
|
|
8710
|
+
}
|
|
8711
|
+
if (hooks?.hardDeadlineAt !== void 0 && Date.now() >= hooks.hardDeadlineAt) {
|
|
8712
|
+
episode.resetAttempts();
|
|
8713
|
+
throw new Error("Subagent hard deadline reached");
|
|
8714
|
+
}
|
|
8715
|
+
return { retry: true };
|
|
8716
|
+
}
|
|
8717
|
+
const finalClassification = decision.action === "exhausted" ? decision.failure : classification;
|
|
8718
|
+
const details = failureDetails(facts, finalClassification, {
|
|
8719
|
+
outcome: decision.action === "exhausted" ? "exhausted" : "terminal",
|
|
8720
|
+
attempts,
|
|
8721
|
+
retries,
|
|
8722
|
+
...decision.action !== "terminal" ? { maxRetries: decision.maxRetries } : {},
|
|
8723
|
+
...decision.action === "exhausted" ? { reason: decision.reason } : {}
|
|
8724
|
+
});
|
|
8725
|
+
if (hooks) hooks.failure = details;
|
|
8726
|
+
return { retry: false, details };
|
|
8727
|
+
}
|
|
8349
8728
|
|
|
8350
8729
|
// ../../packages/shared-headless-capabilities/src/subagent-sessions.ts
|
|
8351
8730
|
var SUBAGENT_SESSION_RECORD_VERSION = 1;
|
|
@@ -8525,14 +8904,14 @@ function lastPipelineCommand(command) {
|
|
|
8525
8904
|
const segments = command.split("|").map((part) => part.trim()).filter(Boolean);
|
|
8526
8905
|
return segments[segments.length - 1] || command;
|
|
8527
8906
|
}
|
|
8528
|
-
function splitShellSegments(command) {
|
|
8907
|
+
function splitShellSegments(command, shell = "bash") {
|
|
8529
8908
|
const segments = [];
|
|
8530
8909
|
let current = "";
|
|
8531
8910
|
let quote;
|
|
8532
8911
|
for (let index = 0; index < command.length; index += 1) {
|
|
8533
8912
|
const char = command[index];
|
|
8534
8913
|
if (quote) {
|
|
8535
|
-
if (quote === '"' && char === "\\" && index + 1 < command.length) {
|
|
8914
|
+
if (quote === '"' && char === (shell === "powershell" ? "`" : "\\") && index + 1 < command.length) {
|
|
8536
8915
|
current += char;
|
|
8537
8916
|
current += command[index + 1];
|
|
8538
8917
|
index += 1;
|
|
@@ -8547,13 +8926,13 @@ function splitShellSegments(command) {
|
|
|
8547
8926
|
current += char;
|
|
8548
8927
|
continue;
|
|
8549
8928
|
}
|
|
8550
|
-
if (char === "\\" && index + 1 < command.length) {
|
|
8929
|
+
if (char === (shell === "powershell" ? "`" : "\\") && index + 1 < command.length) {
|
|
8551
8930
|
current += char;
|
|
8552
8931
|
current += command[index + 1];
|
|
8553
8932
|
index += 1;
|
|
8554
8933
|
continue;
|
|
8555
8934
|
}
|
|
8556
|
-
if (char === "&" || char === "|" || char === ";" || char === "\n") {
|
|
8935
|
+
if (char === "&" || char === "|" || char === ";" || char === "\n" || shell === "powershell" && /[(){}]/.test(char)) {
|
|
8557
8936
|
if ((char === "&" || char === "|") && command[index + 1] === char) {
|
|
8558
8937
|
index += 1;
|
|
8559
8938
|
}
|
|
@@ -8566,7 +8945,7 @@ function splitShellSegments(command) {
|
|
|
8566
8945
|
segments.push(current);
|
|
8567
8946
|
return segments.map((part) => part.trim()).filter(Boolean);
|
|
8568
8947
|
}
|
|
8569
|
-
function splitShellWords(segment) {
|
|
8948
|
+
function splitShellWords(segment, shell = "bash") {
|
|
8570
8949
|
const words = [];
|
|
8571
8950
|
let current = "";
|
|
8572
8951
|
let quote;
|
|
@@ -8575,7 +8954,7 @@ function splitShellWords(segment) {
|
|
|
8575
8954
|
if (quote) {
|
|
8576
8955
|
if (char === quote) {
|
|
8577
8956
|
quote = void 0;
|
|
8578
|
-
} else if (quote === '"' && char === "\\" && index + 1 < segment.length) {
|
|
8957
|
+
} else if (quote === '"' && char === (shell === "powershell" ? "`" : "\\") && index + 1 < segment.length) {
|
|
8579
8958
|
current += segment[index + 1];
|
|
8580
8959
|
index += 1;
|
|
8581
8960
|
} else {
|
|
@@ -8587,7 +8966,7 @@ function splitShellWords(segment) {
|
|
|
8587
8966
|
quote = char;
|
|
8588
8967
|
continue;
|
|
8589
8968
|
}
|
|
8590
|
-
if (char === "\\") {
|
|
8969
|
+
if (char === (shell === "powershell" ? "`" : "\\")) {
|
|
8591
8970
|
if (index + 1 >= segment.length) return void 0;
|
|
8592
8971
|
current += segment[index + 1];
|
|
8593
8972
|
index += 1;
|
|
@@ -8796,7 +9175,7 @@ function classifyShellCommand(command, shell = "bash", options) {
|
|
|
8796
9175
|
const warnings = [];
|
|
8797
9176
|
let commandClass = "unknown_shell";
|
|
8798
9177
|
let destructive = false;
|
|
8799
|
-
const destructivePatterns = shell === "
|
|
9178
|
+
const destructivePatterns = shell === "cmd" ? [...CMD_DESTRUCTIVE_PATTERNS, ...DESTRUCTIVE_PATTERNS] : DESTRUCTIVE_PATTERNS;
|
|
8800
9179
|
const removeWarnings = recursiveRemoveWarnings(command);
|
|
8801
9180
|
if (removeWarnings.length > 0) {
|
|
8802
9181
|
if (shell === "bash" && isStaticallyScopedRecursiveForceRemove(command, options)) {
|
|
@@ -8813,6 +9192,37 @@ function classifyShellCommand(command, shell = "bash", options) {
|
|
|
8813
9192
|
commandClass = "unknown_shell";
|
|
8814
9193
|
destructive = true;
|
|
8815
9194
|
}
|
|
9195
|
+
if (shell === "powershell") {
|
|
9196
|
+
const segments = splitShellSegments(command, shell);
|
|
9197
|
+
const simple = !/[$`#@{}()[\]=<>]/.test(command) && segments.every((segment) => {
|
|
9198
|
+
const words2 = splitShellWords(segment, shell);
|
|
9199
|
+
if (!words2?.length) return false;
|
|
9200
|
+
const head = words2[0] === "." ? words2[1] : words2[0];
|
|
9201
|
+
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);
|
|
9202
|
+
});
|
|
9203
|
+
if (!simple) {
|
|
9204
|
+
for (const entry of POWERSHELL_DESTRUCTIVE_PATTERNS) {
|
|
9205
|
+
if (!entry.pattern.test(command)) continue;
|
|
9206
|
+
warnings.push(entry.warning);
|
|
9207
|
+
commandClass = entry.commandClass;
|
|
9208
|
+
destructive = true;
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
for (const segment of segments) {
|
|
9212
|
+
const words2 = splitShellWords(segment, shell) ?? segment.split(/\s+/);
|
|
9213
|
+
if (words2[0] === ".") words2.shift();
|
|
9214
|
+
if (!words2[0]) continue;
|
|
9215
|
+
words2[0] = executableName(words2[0]);
|
|
9216
|
+
for (const entry of POWERSHELL_DESTRUCTIVE_PATTERNS) {
|
|
9217
|
+
const pattern = new RegExp(`^(?:${entry.pattern.source})`, entry.pattern.flags);
|
|
9218
|
+
if (pattern.test(words2.join(" "))) {
|
|
9219
|
+
warnings.push(entry.warning);
|
|
9220
|
+
commandClass = entry.commandClass;
|
|
9221
|
+
destructive = true;
|
|
9222
|
+
}
|
|
9223
|
+
}
|
|
9224
|
+
}
|
|
9225
|
+
}
|
|
8816
9226
|
for (const entry of destructivePatterns) {
|
|
8817
9227
|
if (entry.pattern.test(command)) {
|
|
8818
9228
|
warnings.push(entry.warning);
|
|
@@ -8828,8 +9238,6 @@ function classifyShellCommand(command, shell = "bash", options) {
|
|
|
8828
9238
|
const primary = words[0] || firstWord(normalized);
|
|
8829
9239
|
if (shell === "powershell" && /\b(invoke-webrequest|iwr|invoke-restmethod|irm)\b/.test(normalized)) {
|
|
8830
9240
|
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
9241
|
} else if (shell === "cmd" && /\b(curl|bitsadmin|certutil)\b/.test(normalized)) {
|
|
8834
9242
|
commandClass = "network";
|
|
8835
9243
|
} else if (shell === "cmd" && /\b(copy|xcopy|robocopy|move|mkdir|md|ren|rename)\b/.test(normalized)) {
|
|
@@ -9131,7 +9539,7 @@ function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingL
|
|
|
9131
9539
|
hooks.observation.lastWorkAt = Date.now();
|
|
9132
9540
|
hooks.observation.lastActivity = "response_event";
|
|
9133
9541
|
}
|
|
9134
|
-
}, hooks);
|
|
9542
|
+
}, hooks, deps.recovery);
|
|
9135
9543
|
if (deps.createAgent) {
|
|
9136
9544
|
const agent2 = deps.createAgent({
|
|
9137
9545
|
streamFn,
|
|
@@ -9160,6 +9568,22 @@ function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingL
|
|
|
9160
9568
|
agentRunHooks.set(agent, hooks);
|
|
9161
9569
|
return agent;
|
|
9162
9570
|
}
|
|
9571
|
+
function inFlightRequestSummary(observation, now) {
|
|
9572
|
+
const transport = observation?.transport;
|
|
9573
|
+
if (!transport || transport.length === 0) return void 0;
|
|
9574
|
+
const lastStartIndex = transport.map((event) => event.phase).lastIndexOf("request_start");
|
|
9575
|
+
if (lastStartIndex < 0) return void 0;
|
|
9576
|
+
const start = transport[lastStartIndex];
|
|
9577
|
+
const after = transport.slice(lastStartIndex + 1);
|
|
9578
|
+
const terminal = after.find((event) => event.phase === "terminal_accepted");
|
|
9579
|
+
if (terminal) {
|
|
9580
|
+
const gapMs = Math.max(0, now - terminal.at);
|
|
9581
|
+
return `last model response completed ${Math.round(gapMs / 100) / 10}s before the deadline; no request in flight at abort`;
|
|
9582
|
+
}
|
|
9583
|
+
const sawFirst = after.some((event) => event.phase === "first_application_message");
|
|
9584
|
+
const ageMs = Math.max(0, now - start.at);
|
|
9585
|
+
return `a model request was still in flight at abort (${Math.round(ageMs / 100) / 10}s old, ${sawFirst ? "receiving stream events" : "no application message yet"}) \u2014 the deadline cut an unfinished response, which usually means the budget is too small for this reasoning level, not a transport stall`;
|
|
9586
|
+
}
|
|
9163
9587
|
function isParentHandoffRequired(task) {
|
|
9164
9588
|
return task.report?.availability !== "available" || task.mode === "edit" || task.status === "timeout" || task.status === "failed" || task.confidenceSignals.some((signal) => signal.type === "tool_error" || signal.type === "no_tool_use" || signal.type === "no_text_report");
|
|
9165
9589
|
}
|
|
@@ -9605,10 +10029,21 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
|
|
|
9605
10029
|
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
10030
|
}
|
|
9607
10031
|
const detail = terminalError ? `: ${terminalError.slice(0, 200)}` : "";
|
|
9608
|
-
const
|
|
10032
|
+
const hooks2 = agentRunHooks.get(subagent);
|
|
10033
|
+
const fallbackFailure = lastAssistant && (lastAssistant.retryable !== void 0 || lastAssistant.replayClass !== void 0 || lastAssistant.replayEligible !== void 0 || lastAssistant.remoteFailure !== void 0) ? {
|
|
10034
|
+
errorMessage: terminalError,
|
|
10035
|
+
retryable: lastAssistant.retryable,
|
|
10036
|
+
replayClass: lastAssistant.replayClass,
|
|
10037
|
+
replayEligible: lastAssistant.replayEligible,
|
|
10038
|
+
remoteFailure: lastAssistant.remoteFailure
|
|
10039
|
+
} : void 0;
|
|
10040
|
+
const subagentFailure = hooks2?.failure ?? fallbackFailure;
|
|
10041
|
+
const overflow = subagentFailure?.recovery?.outcome === "overflow";
|
|
10042
|
+
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
10043
|
const carrier = failure;
|
|
9610
|
-
carrier.retryable =
|
|
9611
|
-
carrier.replayClass =
|
|
10044
|
+
carrier.retryable = subagentFailure?.retryable;
|
|
10045
|
+
carrier.replayClass = subagentFailure?.replayClass;
|
|
10046
|
+
carrier.subagentFailure = subagentFailure;
|
|
9612
10047
|
throw failure;
|
|
9613
10048
|
}
|
|
9614
10049
|
let response;
|
|
@@ -9653,17 +10088,25 @@ ${DELEGATION_REPORT_INSTRUCTION}`;
|
|
|
9653
10088
|
task.status = timedOut ? "timeout" : signal?.aborted ? "cancelled" : "failed";
|
|
9654
10089
|
observeDelegationPhase(task.observation, timedOut ? "deadline" : signal?.aborted ? "cancelled" : "failed");
|
|
9655
10090
|
phaseProgress(task.observation.phase);
|
|
9656
|
-
|
|
10091
|
+
const deadlineDetail = timedOut ? inFlightRequestSummary(task.observation, Date.now()) : void 0;
|
|
10092
|
+
task.error = `${errorMessage.slice(0, 2e3)}${deadlineDetail ? `; ${deadlineDetail}` : ""}`.slice(0, 2e3);
|
|
9657
10093
|
const failureCarrier = error;
|
|
9658
|
-
if (failureCarrier
|
|
9659
|
-
task.failure =
|
|
10094
|
+
if (failureCarrier?.subagentFailure) {
|
|
10095
|
+
task.failure = failureCarrier.subagentFailure;
|
|
10096
|
+
} else if (failureCarrier && (failureCarrier.retryable !== void 0 || failureCarrier.replayClass !== void 0 || failureCarrier.replayEligible !== void 0 || failureCarrier.remoteFailure !== void 0)) {
|
|
10097
|
+
task.failure = {
|
|
10098
|
+
...failureCarrier.retryable !== void 0 ? { retryable: failureCarrier.retryable } : {},
|
|
10099
|
+
...failureCarrier.replayClass !== void 0 ? { replayClass: failureCarrier.replayClass } : {},
|
|
10100
|
+
...failureCarrier.replayEligible !== void 0 ? { replayEligible: failureCarrier.replayEligible } : {},
|
|
10101
|
+
...failureCarrier.remoteFailure !== void 0 ? { remoteFailure: failureCarrier.remoteFailure } : {}
|
|
10102
|
+
};
|
|
9660
10103
|
} else {
|
|
9661
10104
|
task.failure = void 0;
|
|
9662
10105
|
}
|
|
9663
10106
|
task.report ??= parseDelegationReport(void 0, task.id);
|
|
9664
10107
|
task.parentHandoffRequired = isParentHandoffRequired(task);
|
|
9665
10108
|
task.completedAt = Date.now();
|
|
9666
|
-
report({ message: `[${task.name}] Failed: ${errorMessage.slice(0,
|
|
10109
|
+
report({ message: `[${task.name}] Failed: ${(errorMessage + (deadlineDetail ? `; ${deadlineDetail}` : "")).slice(0, 160)}`, increment: 100 });
|
|
9667
10110
|
} finally {
|
|
9668
10111
|
if (heartbeat) {
|
|
9669
10112
|
clearInterval(heartbeat);
|
|
@@ -9741,6 +10184,11 @@ function buildParallelResult(tasks, totalDuration) {
|
|
|
9741
10184
|
`;
|
|
9742
10185
|
if (task.observation) resultText += `Tools: ${task.observation.toolStarts} started, ${task.observation.toolEnds} ended, ${task.observation.toolErrors} errors
|
|
9743
10186
|
`;
|
|
10187
|
+
if (task.failure?.recovery) {
|
|
10188
|
+
const recovery = task.failure.recovery;
|
|
10189
|
+
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}` : ""}
|
|
10190
|
+
`;
|
|
10191
|
+
}
|
|
9744
10192
|
if (task.status === "completed" && task.result) {
|
|
9745
10193
|
resultText += `${task.result}
|
|
9746
10194
|
`;
|
|
@@ -9753,7 +10201,7 @@ function buildParallelResult(tasks, totalDuration) {
|
|
|
9753
10201
|
`;
|
|
9754
10202
|
}
|
|
9755
10203
|
if (task.sessionId) {
|
|
9756
|
-
resultText += task.sessionBusy ? `Session: ${task.sessionId} (busy \u2014 previous run is still settling; recall will fail until it settles` : `Session: ${task.sessionId} (resumable \u2014 pass as "sessionId" in a later spawn to continue this subagent with its accumulated context`;
|
|
10204
|
+
resultText += task.sessionBusy ? `Session: ${task.sessionId} (busy \u2014 previous run is still settling; recall will fail until it settles` : `Session: ${task.sessionId} (resumable \u2014 pass as "sessionId" in a later spawn to continue this subagent with its accumulated context; add resumeMode:"report" to reuse only its last report`;
|
|
9757
10205
|
if (task.sessionRunCount) resultText += `; run ${task.sessionRunCount}`;
|
|
9758
10206
|
if (task.sessionPersisted) resultText += "; persisted to disk, survives restarts";
|
|
9759
10207
|
resultText += ")\n";
|
|
@@ -9929,7 +10377,7 @@ Default mode="read" is for investigation, search, review, or triage and cannot e
|
|
|
9929
10377
|
priority: Type.Optional(Type.Union([Type.Literal("fast"), Type.Literal("thorough")], { description: "fast uses lower-cost defaults; thorough raises model class when needed." })),
|
|
9930
10378
|
model: Type.Optional(Type.String({ description: 'Explicit model override, e.g. "provider/model-id". Only when the user names a specific model; otherwise automatic.' })),
|
|
9931
10379
|
thinkingLevel: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("minimal"), Type.Literal("low"), Type.Literal("medium"), Type.Literal("high"), Type.Literal("xhigh")], { description: "off|minimal|low|medium|high|xhigh. Default medium; override only when the user asks or for cheap lookups (off)." })),
|
|
9932
|
-
timeout: Type.Optional(Type.Number({ description: "Seconds. Default 1200; set higher for long tasks." })),
|
|
10380
|
+
timeout: Type.Optional(Type.Number({ description: "Seconds. Default 1200; set higher for long tasks. Note: high/xhigh reasoning tasks can spend minutes per response on thinking alone \u2014 budget accordingly or the deadline will cut an in-flight response mid-thought." })),
|
|
9933
10381
|
resumeMode: Type.Optional(Type.Union([Type.Literal("full"), Type.Literal("report")], { description: "Default full retains prior transcript. Explicit report rebuilds with current tools/model/credentials and only the latest valid historical report as data plus the new task; fails if unavailable." })),
|
|
9934
10382
|
sessionId: Type.Optional(Type.String({ description: "Continue a previous subagent session by id (from an earlier result's Session line); full mode retains historical input with unknown cost. Optional mode/model/thinkingLevel reconfigure it; one task at a time; recalls never replay on failure and must target an idle session; prefer a fresh task for independent reviews." }))
|
|
9935
10383
|
}
|
|
@@ -9992,40 +10440,8 @@ ${SPAWN_USAGE_HINT}` }],
|
|
|
9992
10440
|
toolContext.log?.(`Spawning ${tasks.length} parallel subagents: ${tasks.map((task) => task.name).join(", ")}`);
|
|
9993
10441
|
toolContext.reportProgress?.({ message: `Launching ${tasks.length} parallel subagents...`, increment: 5 });
|
|
9994
10442
|
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
10443
|
const batch = Promise.resolve().then(() => runWithConcurrencyLimit(
|
|
10028
|
-
tasks.map((task) => () =>
|
|
10444
|
+
tasks.map((task) => () => run(task, toolContext.reportProgress, signal)),
|
|
10029
10445
|
Math.max(1, deps.maxConcurrentSubagents ?? DEFAULT_MAX_CONCURRENT_SUBAGENTS)
|
|
10030
10446
|
));
|
|
10031
10447
|
pending.add(batch);
|
|
@@ -10068,9 +10484,8 @@ ${SPAWN_USAGE_HINT}` }],
|
|
|
10068
10484
|
observation: task.observation,
|
|
10069
10485
|
parentHandoffRequired: task.parentHandoffRequired,
|
|
10070
10486
|
failureReason: task.error,
|
|
10071
|
-
// Structured provider
|
|
10072
|
-
//
|
|
10073
|
-
// provider vetoes (auth/quota/config — not recoverable here).
|
|
10487
|
+
// Structured provider facts plus the actual recovery outcome. Failed-
|
|
10488
|
+
// response tool calls never execute; do not infer their absence.
|
|
10074
10489
|
failure: task.failure,
|
|
10075
10490
|
details: task.details
|
|
10076
10491
|
})),
|
|
@@ -10293,7 +10708,11 @@ function executeManageTodoList(args, currentTodos) {
|
|
|
10293
10708
|
};
|
|
10294
10709
|
}
|
|
10295
10710
|
default:
|
|
10296
|
-
return rejectTodoAction(
|
|
10711
|
+
return rejectTodoAction(
|
|
10712
|
+
"read",
|
|
10713
|
+
`unknown action: ${args.action}. Valid actions: create, read, complete_current, clear.`,
|
|
10714
|
+
safeCurrentTodos
|
|
10715
|
+
);
|
|
10297
10716
|
}
|
|
10298
10717
|
}
|
|
10299
10718
|
|
|
@@ -11142,6 +11561,7 @@ var resolveToCwd = resolveInSandbox;
|
|
|
11142
11561
|
var resolveReadPath = resolveInSandbox;
|
|
11143
11562
|
|
|
11144
11563
|
// ../../packages/shared-headless-capabilities/src/builtin/fs/schema-utils.ts
|
|
11564
|
+
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
11565
|
function formatPrefixes(prefixes) {
|
|
11146
11566
|
if (!prefixes || prefixes.length === 0) {
|
|
11147
11567
|
return "";
|
|
@@ -11151,6 +11571,11 @@ function formatPrefixes(prefixes) {
|
|
|
11151
11571
|
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
11152
11572
|
}).join(", ");
|
|
11153
11573
|
}
|
|
11574
|
+
function arraysEqual(a, b) {
|
|
11575
|
+
if (a === b) return true;
|
|
11576
|
+
if (a.length !== b.length) return false;
|
|
11577
|
+
return a.every((value, index) => value === b[index]);
|
|
11578
|
+
}
|
|
11154
11579
|
function buildRootParameterSchema(sandboxRoot, options) {
|
|
11155
11580
|
const roots = getAvailableRoots(options);
|
|
11156
11581
|
if (roots.length === 0) {
|
|
@@ -11174,8 +11599,11 @@ function buildRootParameterSchema(sandboxRoot, options) {
|
|
|
11174
11599
|
function buildPathResolutionDescription(sandboxRoot, options) {
|
|
11175
11600
|
const roots = getAvailableRoots(options);
|
|
11176
11601
|
const allowlistedDirs = options?.allowlistedDirs ?? [];
|
|
11177
|
-
const
|
|
11178
|
-
const
|
|
11602
|
+
const descriptionOverride = options?.descriptionAllowlistedDirs;
|
|
11603
|
+
const describedAllowlist = descriptionOverride ?? allowlistedDirs;
|
|
11604
|
+
const viewsEqual = arraysEqual(describedAllowlist, allowlistedDirs);
|
|
11605
|
+
const hasGlobalAllowlist = describedAllowlist.some((dir) => dir.trim() === "/") && viewsEqual;
|
|
11606
|
+
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
11607
|
if (roots.length === 0) {
|
|
11180
11608
|
return ["Relative paths resolve against the workspace root.", allowlistSentence].filter(Boolean).join(" ");
|
|
11181
11609
|
}
|
|
@@ -11700,9 +12128,10 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
11700
12128
|
return;
|
|
11701
12129
|
}
|
|
11702
12130
|
if (magicInfo?.kind === "image") {
|
|
12131
|
+
const inlineSupported = INLINE_IMAGE_MIME_TYPES.has(magicInfo.mimeType);
|
|
11703
12132
|
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" } },
|
|
12133
|
+
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}.` }],
|
|
12134
|
+
details: { understanding: { kind: "image", mimeType: magicInfo.mimeType, fallbackReason: inlineSupported ? "image bridge read failed" : "unsupported inline image format" } },
|
|
11706
12135
|
isError: true
|
|
11707
12136
|
});
|
|
11708
12137
|
return;
|
|
@@ -12109,11 +12538,10 @@ function detectTextOnlyBinary(input, filePath, prefixMayBeTruncated) {
|
|
|
12109
12538
|
async function readImageInfo(path3, fs2) {
|
|
12110
12539
|
try {
|
|
12111
12540
|
let base64 = "";
|
|
12112
|
-
const size = (await fs2.stat(path3).catch(() => void 0))?.size ?? 0;
|
|
12113
12541
|
if (typeof fs2.readFile === "function") {
|
|
12114
12542
|
base64 = await fs2.readFile(path3, "base64").catch(() => "");
|
|
12115
12543
|
}
|
|
12116
|
-
if (!base64 &&
|
|
12544
|
+
if (!base64 && typeof fs2.readFileSync === "function") {
|
|
12117
12545
|
base64 = fs2.readFileSync(path3, "base64");
|
|
12118
12546
|
}
|
|
12119
12547
|
if (!base64) {
|
|
@@ -12141,6 +12569,8 @@ async function readImageInfo(path3, fs2) {
|
|
|
12141
12569
|
if (bytes.length >= 12 && asciiAt(bytes, 0, 4) === "RIFF" && asciiAt(bytes, 8, 12 - 8) === "WEBP") {
|
|
12142
12570
|
return { mimeType: "image/webp", base64, ...parseWebpDimensions(bytes) };
|
|
12143
12571
|
}
|
|
12572
|
+
console.error(`[pie] readImageInfo decoded ${bytes.length} bytes for ${path3} but no known image signature matched`);
|
|
12573
|
+
return void 0;
|
|
12144
12574
|
} catch (error) {
|
|
12145
12575
|
console.error(`[pie] readImageInfo failed for ${path3}: ${error instanceof Error ? error.message : String(error)}`);
|
|
12146
12576
|
return void 0;
|
|
@@ -12148,7 +12578,7 @@ async function readImageInfo(path3, fs2) {
|
|
|
12148
12578
|
return void 0;
|
|
12149
12579
|
}
|
|
12150
12580
|
function decodeBase64ToBytes(base64) {
|
|
12151
|
-
if (
|
|
12581
|
+
if (hasUsableNodeBuffer()) {
|
|
12152
12582
|
return Buffer.from(base64, "base64");
|
|
12153
12583
|
}
|
|
12154
12584
|
const clean = base64.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
@@ -12167,6 +12597,17 @@ function decodeBase64ToBytes(base64) {
|
|
|
12167
12597
|
}
|
|
12168
12598
|
return bytes.subarray(0, at);
|
|
12169
12599
|
}
|
|
12600
|
+
function hasUsableNodeBuffer() {
|
|
12601
|
+
if (typeof Buffer === "undefined" || typeof Buffer.from !== "function") {
|
|
12602
|
+
return false;
|
|
12603
|
+
}
|
|
12604
|
+
try {
|
|
12605
|
+
const probe = Buffer.from("aGk=", "base64");
|
|
12606
|
+
return probe.length === 2 && probe[0] === 104;
|
|
12607
|
+
} catch {
|
|
12608
|
+
return false;
|
|
12609
|
+
}
|
|
12610
|
+
}
|
|
12170
12611
|
function readUIntBE(bytes, offset, length) {
|
|
12171
12612
|
let value = 0;
|
|
12172
12613
|
for (let i = 0; i < length; i += 1) {
|