@cydm/pie 2.6.4 → 2.6.6
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/mcp-adapter/index.js +148 -3
- package/dist/builtin/extensions/plan-mode/index.js +3 -3
- package/dist/builtin/extensions/subagent/index.js +4 -4
- package/dist/builtin/extensions/todo/index.js +3 -3
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-bridge.js +3 -2
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-runtime.js +1 -1
- package/dist/builtin/skills/chrome-devtools-axi/{chunk-UPATXALW.js → chunk-U75F4DPF.js} +60 -0
- package/dist/builtin/skills/chrome-devtools-axi/{cli-GB37NZHQ.js → cli-QAILYGLM.js} +97 -36
- package/dist/builtin/skills/skill-creator/SKILL.md +7 -8
- package/dist/chunks/{chunk-OCEYHWVX.js → chunk-G5B6D35K.js} +404 -8
- package/dist/chunks/{chunk-3AOMWS2Z.js → chunk-H6BAM2N4.js} +802 -186
- package/dist/chunks/{chunk-YEUBQBOB.js → chunk-KEARZX6F.js} +2 -2
- package/dist/chunks/{chunk-6QEDYZUR.js → chunk-OWSXALCG.js} +1 -1
- package/dist/chunks/{src-XT5S6AUQ.js → src-ZJXBS2DB.js} +2 -2
- package/dist/chunks/{test-stream-FNWDHRTM.js → test-stream-TI2S25NY.js} +1 -1
- package/dist/cli.js +454 -180
- package/package.json +2 -2
|
@@ -2,8 +2,9 @@ import { createRequire as __createRequire } from "node:module"; const require =
|
|
|
2
2
|
import {
|
|
3
3
|
Agent,
|
|
4
4
|
getToolCallerIdentity
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KEARZX6F.js";
|
|
6
6
|
import {
|
|
7
|
+
AssistantMessageEventStream,
|
|
7
8
|
FileSystemGateway,
|
|
8
9
|
completeSimple,
|
|
9
10
|
detectPlatform,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
getPlatformConfig,
|
|
12
13
|
hashText,
|
|
13
14
|
streamSimple
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-G5B6D35K.js";
|
|
15
16
|
import {
|
|
16
17
|
Type
|
|
17
18
|
} from "./chunk-XXGYUYXI.js";
|
|
@@ -1070,6 +1071,73 @@ function createPlanCapability() {
|
|
|
1070
1071
|
});
|
|
1071
1072
|
}
|
|
1072
1073
|
|
|
1074
|
+
// ../../packages/shared-headless-capabilities/src/subagent-stream.ts
|
|
1075
|
+
function isRetryableSubagentFailure(message) {
|
|
1076
|
+
const remoteClosure = /SSE stream (?:aborted|closed before end|ended before response completed)/i.test(message);
|
|
1077
|
+
const classificationText = message.replace(/econnaborted/ig, "").replace(/SSE stream aborted/ig, "remote stream closed");
|
|
1078
|
+
if (/\b(?:400|401|403|404|422|429)\b|quota|weekly|monthly|limit reached|exhausted|unauthorized|authentication|invalid|protocol|stopReason=length|token limit|abort/i.test(classificationText)) return false;
|
|
1079
|
+
return remoteClosure || /Request timeout/i.test(message) || /econnreset|econnaborted|etimedout|eai_again|econnrefused|socket hang up|socket disconnected|secure TLS connection|fetch failed|network error|connection reset|premature close|\b50[0234]\b|bad gateway|service unavailable/i.test(message);
|
|
1080
|
+
}
|
|
1081
|
+
function cancellableBackoff(ms, signal) {
|
|
1082
|
+
return new Promise((resolve3) => {
|
|
1083
|
+
const finish = () => {
|
|
1084
|
+
clearTimeout(timer);
|
|
1085
|
+
signal?.removeEventListener("abort", finish);
|
|
1086
|
+
resolve3();
|
|
1087
|
+
};
|
|
1088
|
+
const timer = setTimeout(finish, ms);
|
|
1089
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
1090
|
+
if (signal?.aborted) finish();
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
function withSubagentResponseRecovery(stream, backoffMs = 2e3) {
|
|
1094
|
+
return async (model, context, options) => {
|
|
1095
|
+
for (let attempt = 0; ; attempt++) {
|
|
1096
|
+
if (options?.signal?.aborted) throw new Error("Subagent aborted by parent");
|
|
1097
|
+
let message;
|
|
1098
|
+
try {
|
|
1099
|
+
const response = await stream(model, context, options);
|
|
1100
|
+
let terminal;
|
|
1101
|
+
const iterator = response[Symbol.asyncIterator]();
|
|
1102
|
+
try {
|
|
1103
|
+
while (true) {
|
|
1104
|
+
const next = await iterator.next();
|
|
1105
|
+
if (next.done) break;
|
|
1106
|
+
if (next.value.type === "done") terminal = next.value.message;
|
|
1107
|
+
if (next.value.type === "error") terminal = next.value.error;
|
|
1108
|
+
}
|
|
1109
|
+
} finally {
|
|
1110
|
+
try {
|
|
1111
|
+
await iterator.return?.();
|
|
1112
|
+
} catch {
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (!terminal) throw new Error("Provider protocol error: response ended without a terminal event");
|
|
1116
|
+
message = terminal;
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
if (!options?.signal?.aborted && attempt < 2 && isRetryableSubagentFailure(error instanceof Error ? error.message : String(error))) {
|
|
1119
|
+
await cancellableBackoff(backoffMs * (attempt + 1), options?.signal);
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
throw error;
|
|
1123
|
+
}
|
|
1124
|
+
if (options?.signal?.aborted) throw new Error("Subagent aborted by parent");
|
|
1125
|
+
if (message.stopReason === "error" && attempt < 2 && isRetryableSubagentFailure(message.errorMessage ?? "")) {
|
|
1126
|
+
await cancellableBackoff(backoffMs * (attempt + 1), options?.signal);
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
const result = new AssistantMessageEventStream();
|
|
1130
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
1131
|
+
result.push({ type: "error", reason: message.stopReason, error: { ...message, content: [] } });
|
|
1132
|
+
} else {
|
|
1133
|
+
result.push({ type: "done", reason: message.stopReason, message });
|
|
1134
|
+
}
|
|
1135
|
+
result.end();
|
|
1136
|
+
return result;
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1073
1141
|
// ../../packages/shared-headless-capabilities/src/bash-policy.ts
|
|
1074
1142
|
var DESTRUCTIVE_PATTERNS = [
|
|
1075
1143
|
{ pattern: /\bgit\s+reset\s+--hard\b/, warning: "may discard uncommitted changes", commandClass: "git_destructive" },
|
|
@@ -1404,6 +1472,10 @@ function clipHeartbeatLabel(text, maxLength = 60) {
|
|
|
1404
1472
|
function formatSkillsForPrompt(skills) {
|
|
1405
1473
|
return formatSkillSummariesForPrompt(skills);
|
|
1406
1474
|
}
|
|
1475
|
+
var SYNTHETIC_PROVIDER_TEXT_PREFIXES = ["[output truncated:", "[dropped "];
|
|
1476
|
+
function isSyntheticProviderText(text) {
|
|
1477
|
+
return SYNTHETIC_PROVIDER_TEXT_PREFIXES.some((prefix) => text.startsWith(prefix));
|
|
1478
|
+
}
|
|
1407
1479
|
function collectLastAssistantText(messages) {
|
|
1408
1480
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1409
1481
|
const message = messages[i];
|
|
@@ -1411,17 +1483,29 @@ function collectLastAssistantText(messages) {
|
|
|
1411
1483
|
continue;
|
|
1412
1484
|
}
|
|
1413
1485
|
if (Array.isArray(message.content)) {
|
|
1414
|
-
const
|
|
1486
|
+
const realBlocks = message.content.filter((content) => content?.type === "text" && typeof content.text === "string").map((content) => content.text).filter((text) => !isSyntheticProviderText(text));
|
|
1487
|
+
const textContent = realBlocks.join("\n");
|
|
1415
1488
|
if (textContent.trim()) {
|
|
1416
1489
|
return textContent;
|
|
1417
1490
|
}
|
|
1418
1491
|
}
|
|
1419
|
-
if (typeof message.content === "string" && message.content.trim()) {
|
|
1492
|
+
if (typeof message.content === "string" && message.content.trim() && !isSyntheticProviderText(message.content)) {
|
|
1420
1493
|
return message.content;
|
|
1421
1494
|
}
|
|
1422
1495
|
}
|
|
1423
1496
|
return "";
|
|
1424
1497
|
}
|
|
1498
|
+
function visibleTextBlocks(message) {
|
|
1499
|
+
if (!Array.isArray(message.content)) return [];
|
|
1500
|
+
return message.content.filter((content) => content?.type === "text" && typeof content.text === "string").map((content) => content.text).filter((text) => text.trim().length > 0 && !isSyntheticProviderText(text));
|
|
1501
|
+
}
|
|
1502
|
+
function messageHasOwnText(message) {
|
|
1503
|
+
if (!message) return false;
|
|
1504
|
+
if (Array.isArray(message.content)) {
|
|
1505
|
+
return visibleTextBlocks(message).length > 0;
|
|
1506
|
+
}
|
|
1507
|
+
return typeof message.content === "string" && message.content.trim().length > 0 && !isSyntheticProviderText(message.content);
|
|
1508
|
+
}
|
|
1425
1509
|
function inferComplexity(task, priority) {
|
|
1426
1510
|
const normalized = task.toLowerCase();
|
|
1427
1511
|
if (priority === "thorough" || task.length > 600 || /\b(across|architecture|refactor|review|investigate|debug|failing|test|compare|multiple|race|root cause)\b/.test(normalized)) {
|
|
@@ -1540,8 +1624,13 @@ function subagentOutputBudget(model) {
|
|
|
1540
1624
|
return Math.min(model.maxTokens || 8192, 16384);
|
|
1541
1625
|
}
|
|
1542
1626
|
function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingLevel) {
|
|
1627
|
+
const streamFn = withSubagentResponseRecovery((streamModel, context, options) => streamSimple(streamModel, context, {
|
|
1628
|
+
...options,
|
|
1629
|
+
maxTokens: options?.maxTokens ?? subagentOutputBudget(streamModel)
|
|
1630
|
+
}), deps.transientRetryBackoffMs);
|
|
1543
1631
|
if (deps.createAgent) {
|
|
1544
1632
|
return deps.createAgent({
|
|
1633
|
+
streamFn,
|
|
1545
1634
|
systemPrompt,
|
|
1546
1635
|
model,
|
|
1547
1636
|
tools,
|
|
@@ -1560,10 +1649,7 @@ function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingL
|
|
|
1560
1649
|
// Subagent turns are single-purpose report generations: request an
|
|
1561
1650
|
// explicit output budget (see subagentOutputBudget) instead of leaving
|
|
1562
1651
|
// it to endpoint defaults that silently truncate long reports.
|
|
1563
|
-
streamFn
|
|
1564
|
-
...options,
|
|
1565
|
-
maxTokens: options?.maxTokens ?? subagentOutputBudget(streamModel)
|
|
1566
|
-
})
|
|
1652
|
+
streamFn
|
|
1567
1653
|
});
|
|
1568
1654
|
}
|
|
1569
1655
|
function extractEvidenceFromToolArgs(toolName, args) {
|
|
@@ -1595,13 +1681,16 @@ function inferUncertainty(text, signals) {
|
|
|
1595
1681
|
if (signals.some((signal) => signal.type === "tool_error" || signal.type === "model_fallback")) {
|
|
1596
1682
|
return "medium";
|
|
1597
1683
|
}
|
|
1684
|
+
if (signals.some((signal) => signal.type === "no_text_report")) {
|
|
1685
|
+
return "medium";
|
|
1686
|
+
}
|
|
1598
1687
|
if (signals.some((signal) => signal.type === "tool_success" || signal.type === "file_read" || signal.type === "command_run")) {
|
|
1599
1688
|
return "low";
|
|
1600
1689
|
}
|
|
1601
1690
|
return "medium";
|
|
1602
1691
|
}
|
|
1603
1692
|
function isParentHandoffRequired(task) {
|
|
1604
|
-
return task.mode === "edit" || task.needsParentWrite || task.status === "timeout" || task.status === "failed" || task.confidenceSignals.some((signal) => signal.type === "tool_error" || signal.type === "no_tool_use");
|
|
1693
|
+
return task.mode === "edit" || task.needsParentWrite || task.status === "timeout" || task.status === "failed" || task.confidenceSignals.some((signal) => signal.type === "tool_error" || signal.type === "no_tool_use" || signal.type === "no_text_report");
|
|
1605
1694
|
}
|
|
1606
1695
|
async function runSubagentWithProgress(deps, task, reportProgress, signal) {
|
|
1607
1696
|
const startTime = Date.now();
|
|
@@ -1746,12 +1835,19 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1746
1835
|
const lastAssistant = [...messages].reverse().find((m) => m?.role === "assistant");
|
|
1747
1836
|
const terminalStopReason = lastAssistant?.stopReason;
|
|
1748
1837
|
const terminalError = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
|
|
1749
|
-
const collectedResponse = collectLastAssistantText(messages);
|
|
1750
|
-
|
|
1838
|
+
const collectedResponse = collectLastAssistantText(messages) || void 0;
|
|
1839
|
+
const streamInterrupted = terminalStopReason === "error" || terminalStopReason === "aborted" || terminalError || !collectedResponse && terminalStopReason !== "stop" || terminalStopReason === "length" && !messageHasOwnText(lastAssistant);
|
|
1840
|
+
if (streamInterrupted) {
|
|
1841
|
+
if (terminalStopReason === "length") {
|
|
1842
|
+
throw new Error(`Subagent output token limit reached (stopReason=length): the provider cut the final response before the report was produced${collectedResponse ? " (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.`);
|
|
1843
|
+
}
|
|
1751
1844
|
const detail = terminalError ? `: ${terminalError.slice(0, 200)}` : "";
|
|
1752
|
-
throw new Error(`Subagent
|
|
1845
|
+
throw new Error(`Subagent response failed (provider/transport failure before the report was produced, stopReason=${terminalStopReason ?? "none"}${collectedResponse ? ", partial transcript discarded" : ", no text response"})${detail}`);
|
|
1846
|
+
}
|
|
1847
|
+
let response = collectedResponse ?? "[No textual report: the subagent finished normally (stopReason=stop) but produced no text \u2014 every assistant message was tool calls only. Review the task's effects directly, or rerun it with an explicit instruction to summarize findings.]";
|
|
1848
|
+
if (!collectedResponse) {
|
|
1849
|
+
task.confidenceSignals.push({ type: "no_text_report", message: "subagent finished normally without any textual report" });
|
|
1753
1850
|
}
|
|
1754
|
-
let response = collectedResponse;
|
|
1755
1851
|
const outputTruncated = terminalStopReason === "length";
|
|
1756
1852
|
if (outputTruncated) {
|
|
1757
1853
|
task.confidenceSignals.push({ type: "output_truncated", message: "final response hit the provider's output token limit" });
|
|
@@ -1904,25 +2000,6 @@ function normalizeTaskDefs(args) {
|
|
|
1904
2000
|
return normalized;
|
|
1905
2001
|
}
|
|
1906
2002
|
var DEFAULT_MAX_CONCURRENT_SUBAGENTS = 4;
|
|
1907
|
-
var MAX_TRANSIENT_RETRIES = 2;
|
|
1908
|
-
var TRANSIENT_RETRY_BACKOFF_MS = 2e3;
|
|
1909
|
-
var TRANSIENT_ERROR_PATTERN = /econnreset|econnaborted|etimedout|timeout|enotfound|eai_again|econnrefused|socket hang up|sse stream aborted|fetch failed|status 5\d\d|bad gateway|service unavailable|overloaded/i;
|
|
1910
|
-
function isTransientNetworkError(message) {
|
|
1911
|
-
return TRANSIENT_ERROR_PATTERN.test(message);
|
|
1912
|
-
}
|
|
1913
|
-
function sleep(ms, signal) {
|
|
1914
|
-
return new Promise((resolve3) => {
|
|
1915
|
-
const timer = setTimeout(resolve3, ms);
|
|
1916
|
-
signal?.addEventListener(
|
|
1917
|
-
"abort",
|
|
1918
|
-
() => {
|
|
1919
|
-
clearTimeout(timer);
|
|
1920
|
-
resolve3();
|
|
1921
|
-
},
|
|
1922
|
-
{ once: true }
|
|
1923
|
-
);
|
|
1924
|
-
});
|
|
1925
|
-
}
|
|
1926
2003
|
async function runWithConcurrencyLimit(jobs, limit) {
|
|
1927
2004
|
let next = 0;
|
|
1928
2005
|
const workers = Array.from({ length: Math.max(1, Math.min(limit, jobs.length)) }, async () => {
|
|
@@ -2011,24 +2088,23 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
2011
2088
|
toolContext.reportProgress?.({ message: `Launching ${tasks.length} parallel subagents...`, increment: 5 });
|
|
2012
2089
|
const startTime = Date.now();
|
|
2013
2090
|
const runTask = async (task) => {
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
}
|
|
2091
|
+
await runSubagentWithProgress(deps, task, toolContext.reportProgress, signal);
|
|
2092
|
+
if (task.mode !== "read" || task.status !== "failed" || !task.error) return;
|
|
2093
|
+
if (signal?.aborted) return;
|
|
2094
|
+
if (!isRetryableSubagentFailure(task.error)) return;
|
|
2095
|
+
const settleMs = Math.max(0, (deps.transientRetryBackoffMs ?? 2e3) * 10);
|
|
2096
|
+
toolContext.reportProgress?.({ message: `[${task.name}] Provider/transport failure \u2014 replaying the read task once after ${Math.round(settleMs / 1e3)}s`, increment: 0 });
|
|
2097
|
+
await cancellableBackoff(settleMs, signal);
|
|
2098
|
+
if (signal?.aborted) return;
|
|
2099
|
+
task.evidence = [];
|
|
2100
|
+
task.confidenceSignals = [];
|
|
2101
|
+
task.runtimeEvidence = [];
|
|
2102
|
+
task.reportedEvidence = [];
|
|
2103
|
+
task.error = void 0;
|
|
2104
|
+
task.result = void 0;
|
|
2105
|
+
task.details = void 0;
|
|
2106
|
+
task.parentHandoffRequired = false;
|
|
2107
|
+
await runSubagentWithProgress(deps, task, toolContext.reportProgress, signal);
|
|
2032
2108
|
};
|
|
2033
2109
|
await runWithConcurrencyLimit(
|
|
2034
2110
|
tasks.map((task) => () => runTask(task)),
|
|
@@ -3485,6 +3561,7 @@ var FileSessionStore = class {
|
|
|
3485
3561
|
}
|
|
3486
3562
|
delete(sessionId) {
|
|
3487
3563
|
const paths = this.pathsFor(sessionId);
|
|
3564
|
+
if (sessionDirIsSymlink(paths.dir)) return false;
|
|
3488
3565
|
if (!this.gateway.exists(paths.dir)) return false;
|
|
3489
3566
|
this.gateway.deleteDirectory(paths.dir, { recursive: true });
|
|
3490
3567
|
this.dropCaches();
|
|
@@ -4979,6 +5056,163 @@ function createSessionManager(options) {
|
|
|
4979
5056
|
return new SessionManager(options);
|
|
4980
5057
|
}
|
|
4981
5058
|
|
|
5059
|
+
// ../../packages/agent-framework/src/observer.ts
|
|
5060
|
+
function notifyObserver(listener, event) {
|
|
5061
|
+
try {
|
|
5062
|
+
void Promise.resolve(listener(event)).catch(() => {
|
|
5063
|
+
});
|
|
5064
|
+
} catch {
|
|
5065
|
+
}
|
|
5066
|
+
}
|
|
5067
|
+
|
|
5068
|
+
// ../../packages/agent-framework/src/session/controller/end-hook.ts
|
|
5069
|
+
async function runEndHook(hook, context, isCurrent, emit) {
|
|
5070
|
+
if (!hook) return;
|
|
5071
|
+
emit({ type: "agent_end_hook_start", hasQueuedMessages: context.hasQueuedMessages });
|
|
5072
|
+
try {
|
|
5073
|
+
await hook(context);
|
|
5074
|
+
if (isCurrent()) emit({ type: "agent_end_hook_end" });
|
|
5075
|
+
} catch (error) {
|
|
5076
|
+
if (isCurrent()) emit({ type: "agent_end_hook_end", errorMessage: error instanceof Error ? error.message : String(error) });
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
|
|
5080
|
+
// ../../packages/agent-framework/src/session/controller/convergence.ts
|
|
5081
|
+
var SessionConvergence = class {
|
|
5082
|
+
constructor(options) {
|
|
5083
|
+
this.options = options;
|
|
5084
|
+
}
|
|
5085
|
+
options;
|
|
5086
|
+
generation = 0;
|
|
5087
|
+
active = false;
|
|
5088
|
+
disposed = false;
|
|
5089
|
+
operations = /* @__PURE__ */ new Set();
|
|
5090
|
+
outcome;
|
|
5091
|
+
monitoring = /* @__PURE__ */ new Set();
|
|
5092
|
+
invalidated;
|
|
5093
|
+
invalidation = new Promise((resolve3) => {
|
|
5094
|
+
this.invalidated = resolve3;
|
|
5095
|
+
});
|
|
5096
|
+
get identity() {
|
|
5097
|
+
return { sessionId: this.options.sessionId(), generation: this.generation };
|
|
5098
|
+
}
|
|
5099
|
+
begin() {
|
|
5100
|
+
if (this.disposed || this.active) return;
|
|
5101
|
+
this.active = true;
|
|
5102
|
+
this.outcome = void 0;
|
|
5103
|
+
this.generation++;
|
|
5104
|
+
notifyObserver(this.options.emit, { type: "session_work_started", ...this.identity });
|
|
5105
|
+
}
|
|
5106
|
+
invalidate(reason) {
|
|
5107
|
+
if (this.disposed) return;
|
|
5108
|
+
if (reason === "dispose") this.disposed = true;
|
|
5109
|
+
this.invalidated();
|
|
5110
|
+
this.invalidation = new Promise((resolve3) => {
|
|
5111
|
+
this.invalidated = resolve3;
|
|
5112
|
+
});
|
|
5113
|
+
this.generation++;
|
|
5114
|
+
this.active = false;
|
|
5115
|
+
this.operations.clear();
|
|
5116
|
+
notifyObserver(this.options.emit, { type: "session_invalidated", ...this.identity, reason });
|
|
5117
|
+
}
|
|
5118
|
+
run(action) {
|
|
5119
|
+
if (this.disposed) return Promise.reject(new Error("Controller disposed"));
|
|
5120
|
+
const wasActive = this.active;
|
|
5121
|
+
const preBeginGeneration = this.generation;
|
|
5122
|
+
this.begin();
|
|
5123
|
+
if (this.disposed) return Promise.reject(new Error("Controller disposed"));
|
|
5124
|
+
if (!wasActive && (!this.active || this.generation !== preBeginGeneration + 1)) {
|
|
5125
|
+
return Promise.resolve();
|
|
5126
|
+
}
|
|
5127
|
+
const generation = this.generation;
|
|
5128
|
+
let resolve3;
|
|
5129
|
+
let reject;
|
|
5130
|
+
const operation = new Promise((yes, no) => {
|
|
5131
|
+
resolve3 = yes;
|
|
5132
|
+
reject = no;
|
|
5133
|
+
});
|
|
5134
|
+
this.operations.add(operation);
|
|
5135
|
+
try {
|
|
5136
|
+
resolve3(action());
|
|
5137
|
+
} catch (error) {
|
|
5138
|
+
reject(error);
|
|
5139
|
+
}
|
|
5140
|
+
void operation.catch(() => {
|
|
5141
|
+
if (generation === this.generation) this.outcome = "failed";
|
|
5142
|
+
}).finally(() => {
|
|
5143
|
+
this.operations.delete(operation);
|
|
5144
|
+
this.check();
|
|
5145
|
+
});
|
|
5146
|
+
this.check();
|
|
5147
|
+
return operation;
|
|
5148
|
+
}
|
|
5149
|
+
observe(event) {
|
|
5150
|
+
if (event.type === "agent_event" && event.event.type === "agent_start") this.begin();
|
|
5151
|
+
if (event.type === "turn_failed_terminal" || event.type === "turn_policy_error" || event.type === "agent_end_hook_end" && event.errorMessage) this.outcome = "failed";
|
|
5152
|
+
if (event.type === "turn_cancelled") this.outcome = "cancelled";
|
|
5153
|
+
this.check();
|
|
5154
|
+
}
|
|
5155
|
+
async wait() {
|
|
5156
|
+
const generation = this.generation;
|
|
5157
|
+
const invalidation = this.invalidation;
|
|
5158
|
+
while (generation === this.generation && !this.disposed) {
|
|
5159
|
+
await Promise.race([Promise.allSettled([...this.operations]), invalidation]);
|
|
5160
|
+
if (generation !== this.generation || this.disposed) return;
|
|
5161
|
+
await Promise.race([this.options.wait(), invalidation]);
|
|
5162
|
+
if (generation !== this.generation || this.disposed) return;
|
|
5163
|
+
if (!this.operations.size && !this.options.busy()) return;
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
check() {
|
|
5167
|
+
const generation = this.generation;
|
|
5168
|
+
if (!this.active || this.disposed || this.monitoring.has(generation)) return;
|
|
5169
|
+
this.monitoring.add(generation);
|
|
5170
|
+
void this.wait().then(() => {
|
|
5171
|
+
if (generation !== this.generation || !this.active || this.disposed) return;
|
|
5172
|
+
this.active = false;
|
|
5173
|
+
notifyObserver(this.options.emit, {
|
|
5174
|
+
type: "session_quiescent",
|
|
5175
|
+
...this.identity,
|
|
5176
|
+
outcome: this.outcome ?? (this.options.queued() ? "blocked" : "success")
|
|
5177
|
+
});
|
|
5178
|
+
}).catch(() => {
|
|
5179
|
+
if (generation !== this.generation || !this.active || this.disposed) return;
|
|
5180
|
+
this.active = false;
|
|
5181
|
+
notifyObserver(this.options.emit, { type: "session_quiescent", ...this.identity, outcome: "failed" });
|
|
5182
|
+
}).finally(() => this.monitoring.delete(generation));
|
|
5183
|
+
}
|
|
5184
|
+
};
|
|
5185
|
+
|
|
5186
|
+
// ../../packages/agent-framework/src/session/controller/pending-operations.ts
|
|
5187
|
+
var PendingEndOperations = class {
|
|
5188
|
+
operations = /* @__PURE__ */ new Set();
|
|
5189
|
+
get pending() {
|
|
5190
|
+
return this.operations.size > 0;
|
|
5191
|
+
}
|
|
5192
|
+
clear() {
|
|
5193
|
+
this.operations.clear();
|
|
5194
|
+
}
|
|
5195
|
+
async wait() {
|
|
5196
|
+
await Promise.allSettled([...this.operations]);
|
|
5197
|
+
}
|
|
5198
|
+
track(action) {
|
|
5199
|
+
let resolve3;
|
|
5200
|
+
let reject;
|
|
5201
|
+
const operation = new Promise((yes, no) => {
|
|
5202
|
+
resolve3 = yes;
|
|
5203
|
+
reject = no;
|
|
5204
|
+
});
|
|
5205
|
+
this.operations.add(operation);
|
|
5206
|
+
void operation.finally(() => this.operations.delete(operation)).catch(() => {
|
|
5207
|
+
});
|
|
5208
|
+
try {
|
|
5209
|
+
resolve3(action());
|
|
5210
|
+
} catch (error) {
|
|
5211
|
+
reject(error);
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
};
|
|
5215
|
+
|
|
4982
5216
|
// ../../packages/agent-framework/src/session/controller/auto-compact-policy.ts
|
|
4983
5217
|
function defaultSetTimeout(callback, delay) {
|
|
4984
5218
|
return setTimeout(callback, delay);
|
|
@@ -5078,6 +5312,8 @@ var AgentSessionAutoContinueController = class {
|
|
|
5078
5312
|
}
|
|
5079
5313
|
options;
|
|
5080
5314
|
autoContinueTimer;
|
|
5315
|
+
generation = 0;
|
|
5316
|
+
cancelExecutionWait;
|
|
5081
5317
|
pendingAutoContinue;
|
|
5082
5318
|
resolvePendingAutoContinue;
|
|
5083
5319
|
get hasPendingAutoContinue() {
|
|
@@ -5093,21 +5329,30 @@ var AgentSessionAutoContinueController = class {
|
|
|
5093
5329
|
this.ensurePending();
|
|
5094
5330
|
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout2;
|
|
5095
5331
|
const snapshot = messages.slice();
|
|
5332
|
+
const generation = this.generation;
|
|
5096
5333
|
this.autoContinueTimer = setTimer(() => {
|
|
5097
5334
|
this.autoContinueTimer = void 0;
|
|
5098
|
-
void this.maybeScheduleAutoContinue(snapshot).
|
|
5335
|
+
void this.maybeScheduleAutoContinue(snapshot, generation).catch((error) => {
|
|
5336
|
+
if (generation === this.generation && !this.options.getDisposed()) {
|
|
5337
|
+
this.options.emit({ type: "turn_policy_error", message: error instanceof Error ? error.message : String(error) });
|
|
5338
|
+
}
|
|
5339
|
+
}).finally(() => {
|
|
5340
|
+
if (generation === this.generation && this.autoContinueTimer === void 0) this.resolvePending();
|
|
5341
|
+
});
|
|
5099
5342
|
}, 0);
|
|
5100
5343
|
}
|
|
5101
5344
|
cancel() {
|
|
5102
|
-
|
|
5103
|
-
|
|
5345
|
+
this.generation++;
|
|
5346
|
+
if (this.autoContinueTimer !== void 0) {
|
|
5347
|
+
const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout2;
|
|
5348
|
+
clearTimer(this.autoContinueTimer);
|
|
5349
|
+
this.autoContinueTimer = void 0;
|
|
5104
5350
|
}
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
this.autoContinueTimer = void 0;
|
|
5351
|
+
this.cancelExecutionWait?.();
|
|
5352
|
+
this.cancelExecutionWait = void 0;
|
|
5108
5353
|
this.resolvePending();
|
|
5109
5354
|
}
|
|
5110
|
-
async maybeScheduleAutoContinue(messages) {
|
|
5355
|
+
async maybeScheduleAutoContinue(messages, generation) {
|
|
5111
5356
|
if (!this.options.getAutoContinueMessage || this.options.getDisposed() || this.options.getRuntimeGuardTriggered() || this.options.isBlockedByRuntimePolicy() || this.options.hasQueuedMessages() || this.options.isStreaming()) {
|
|
5112
5357
|
if (this.options.hasQueuedMessages()) {
|
|
5113
5358
|
this.options.emit({ type: "auto_continue_skipped", reason: "queued_messages" });
|
|
@@ -5126,6 +5371,7 @@ var AgentSessionAutoContinueController = class {
|
|
|
5126
5371
|
messages,
|
|
5127
5372
|
hasQueuedMessages: this.options.hasQueuedMessages()
|
|
5128
5373
|
});
|
|
5374
|
+
if (generation !== this.generation || this.options.getDisposed() || this.options.getRuntimeGuardTriggered()) return;
|
|
5129
5375
|
const message = result && "role" in result ? result : result?.message;
|
|
5130
5376
|
const reason = result && "role" in result ? void 0 : result?.reason;
|
|
5131
5377
|
if (!message) {
|
|
@@ -5140,9 +5386,11 @@ var AgentSessionAutoContinueController = class {
|
|
|
5140
5386
|
this.options.agent.followUp(message);
|
|
5141
5387
|
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout2;
|
|
5142
5388
|
await new Promise((resolve3) => {
|
|
5389
|
+
this.cancelExecutionWait = resolve3;
|
|
5143
5390
|
this.autoContinueTimer = setTimer(() => {
|
|
5144
5391
|
this.autoContinueTimer = void 0;
|
|
5145
|
-
|
|
5392
|
+
this.cancelExecutionWait = void 0;
|
|
5393
|
+
if (generation !== this.generation || this.options.getDisposed()) {
|
|
5146
5394
|
resolve3();
|
|
5147
5395
|
return;
|
|
5148
5396
|
}
|
|
@@ -5195,7 +5443,7 @@ async function runAgentSessionCompaction(params) {
|
|
|
5195
5443
|
throw new Error("No compaction handler configured");
|
|
5196
5444
|
}
|
|
5197
5445
|
const abortedResult = () => ({ aborted: true });
|
|
5198
|
-
const isAborted = () => params.signal?.aborted === true;
|
|
5446
|
+
const isAborted = () => params.signal?.aborted === true || params.isCurrent?.() === false;
|
|
5199
5447
|
params.emit({ type: "auto_compaction_start", reason: params.reason, willRetry: params.willRetry });
|
|
5200
5448
|
try {
|
|
5201
5449
|
if (isAborted()) {
|
|
@@ -5336,7 +5584,7 @@ async function runPreRequestAutoCompact(params) {
|
|
|
5336
5584
|
try {
|
|
5337
5585
|
await params.runAutoCompact();
|
|
5338
5586
|
} catch {
|
|
5339
|
-
params.setRecoveryState("none");
|
|
5587
|
+
if (params.isCurrent?.() !== false) params.setRecoveryState("none");
|
|
5340
5588
|
}
|
|
5341
5589
|
}
|
|
5342
5590
|
|
|
@@ -5510,6 +5758,7 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5510
5758
|
}
|
|
5511
5759
|
options;
|
|
5512
5760
|
dispatchTimer;
|
|
5761
|
+
generation = 0;
|
|
5513
5762
|
pendingDispatch;
|
|
5514
5763
|
resolvePendingDispatch;
|
|
5515
5764
|
get hasPendingDispatch() {
|
|
@@ -5524,25 +5773,32 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5524
5773
|
}
|
|
5525
5774
|
this.ensurePending();
|
|
5526
5775
|
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout3;
|
|
5776
|
+
const generation = this.generation;
|
|
5527
5777
|
this.dispatchTimer = setTimer(() => {
|
|
5528
5778
|
this.dispatchTimer = void 0;
|
|
5529
|
-
void this.dispatchNext().
|
|
5779
|
+
void this.dispatchNext().catch((error) => {
|
|
5780
|
+
if (generation === this.generation) this.options.emit({ type: "turn_policy_error", message: String(error) });
|
|
5781
|
+
}).finally(() => {
|
|
5782
|
+
if (generation === this.generation) this.resolvePending();
|
|
5783
|
+
});
|
|
5530
5784
|
}, delayMs);
|
|
5531
5785
|
}
|
|
5532
5786
|
cancel() {
|
|
5533
|
-
|
|
5534
|
-
|
|
5787
|
+
this.generation++;
|
|
5788
|
+
if (this.dispatchTimer !== void 0) {
|
|
5789
|
+
const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout3;
|
|
5790
|
+
clearTimer(this.dispatchTimer);
|
|
5791
|
+
this.dispatchTimer = void 0;
|
|
5535
5792
|
}
|
|
5536
|
-
const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout3;
|
|
5537
|
-
clearTimer(this.dispatchTimer);
|
|
5538
|
-
this.dispatchTimer = void 0;
|
|
5539
5793
|
this.resolvePending();
|
|
5540
5794
|
}
|
|
5541
5795
|
async dispatchNext() {
|
|
5796
|
+
const generation = this.generation;
|
|
5542
5797
|
if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
|
|
5543
5798
|
return false;
|
|
5544
5799
|
}
|
|
5545
5800
|
await this.options.agent.waitForIdle();
|
|
5801
|
+
if (generation !== this.generation) return false;
|
|
5546
5802
|
if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
|
|
5547
5803
|
this.schedule(100);
|
|
5548
5804
|
return false;
|
|
@@ -5557,6 +5813,7 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5557
5813
|
cwd: this.options.cwd,
|
|
5558
5814
|
queuedInput: cloneQueuedInput(queuedInput)
|
|
5559
5815
|
});
|
|
5816
|
+
if (generation !== this.generation) return false;
|
|
5560
5817
|
const dispatchDecision = typeof decision === "boolean" ? { allowed: decision } : decision ?? { allowed: true };
|
|
5561
5818
|
if (!dispatchDecision.allowed) {
|
|
5562
5819
|
this.options.emit({
|
|
@@ -5570,6 +5827,7 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5570
5827
|
return false;
|
|
5571
5828
|
}
|
|
5572
5829
|
await this.options.runPreRequestAutoCompact();
|
|
5830
|
+
if (generation !== this.generation) return false;
|
|
5573
5831
|
if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
|
|
5574
5832
|
this.schedule(100);
|
|
5575
5833
|
return false;
|
|
@@ -5593,6 +5851,7 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5593
5851
|
clearEditor: dispatched.clearEditor,
|
|
5594
5852
|
visibility: dispatched.visibility
|
|
5595
5853
|
});
|
|
5854
|
+
if (generation !== this.generation) return false;
|
|
5596
5855
|
if (!prepared.handled) {
|
|
5597
5856
|
if (prepared.systemPrompt !== void 0) {
|
|
5598
5857
|
this.options.agent.setSystemPrompt(prepared.systemPrompt);
|
|
@@ -5701,7 +5960,12 @@ var AgentSessionRetryController = class {
|
|
|
5701
5960
|
removeTrailingAssistantError;
|
|
5702
5961
|
continueTurn;
|
|
5703
5962
|
setRecoveryState;
|
|
5704
|
-
|
|
5963
|
+
/** Attempts consumed per failure KIND: budgets are granted per kind (a
|
|
5964
|
+
* 10-attempt network budget must not be eaten by interleaved provider
|
|
5965
|
+
* errors, and vice versa), so each kind counts its own attempts. */
|
|
5966
|
+
retryAttempts = /* @__PURE__ */ new Map();
|
|
5967
|
+
/** Total attempts across kinds in this recovery episode, for reporting. */
|
|
5968
|
+
totalRetryAttempts = 0;
|
|
5705
5969
|
retryTimer;
|
|
5706
5970
|
pendingRetry;
|
|
5707
5971
|
resolvePendingRetry;
|
|
@@ -5720,36 +5984,41 @@ var AgentSessionRetryController = class {
|
|
|
5720
5984
|
return;
|
|
5721
5985
|
}
|
|
5722
5986
|
if (!failure) {
|
|
5723
|
-
if (this.
|
|
5724
|
-
this.emit({ type: "retry_succeeded", attempts: this.
|
|
5725
|
-
this.emit({ type: "turn_retry_succeeded", attempts: this.
|
|
5726
|
-
this.
|
|
5987
|
+
if (this.totalRetryAttempts > 0) {
|
|
5988
|
+
this.emit({ type: "retry_succeeded", attempts: this.totalRetryAttempts });
|
|
5989
|
+
this.emit({ type: "turn_retry_succeeded", attempts: this.totalRetryAttempts });
|
|
5990
|
+
this.resetAttempts();
|
|
5727
5991
|
this.setRecoveryState("none");
|
|
5728
5992
|
this.resolvePending();
|
|
5729
5993
|
}
|
|
5730
5994
|
return;
|
|
5731
5995
|
}
|
|
5732
5996
|
if (!failure.retryable) {
|
|
5733
|
-
if (this.
|
|
5734
|
-
this.
|
|
5997
|
+
if (this.totalRetryAttempts > 0) {
|
|
5998
|
+
this.resetAttempts();
|
|
5735
5999
|
this.setRecoveryState("none");
|
|
5736
6000
|
this.resolvePending();
|
|
5737
6001
|
}
|
|
5738
6002
|
return;
|
|
5739
6003
|
}
|
|
5740
6004
|
const maxRetries = this.retry.maxRetriesForFailure?.(failure) ?? this.retry.maxRetries ?? 3;
|
|
5741
|
-
this.
|
|
5742
|
-
|
|
6005
|
+
const kindAttempts = (this.retryAttempts.get(failure.kind) ?? 0) + 1;
|
|
6006
|
+
this.retryAttempts.set(failure.kind, kindAttempts);
|
|
6007
|
+
this.totalRetryAttempts += 1;
|
|
6008
|
+
const totalBudget = this.retry.maxTotalRetries;
|
|
6009
|
+
const kindExhausted = kindAttempts > maxRetries;
|
|
6010
|
+
const totalExhausted = totalBudget !== void 0 && this.totalRetryAttempts > totalBudget;
|
|
6011
|
+
if (kindExhausted || totalExhausted) {
|
|
5743
6012
|
const terminalFailure = asTerminalFailure(failure);
|
|
5744
|
-
this.emit({ type: "retry_exhausted", errorMessage: failure.message, maxRetries });
|
|
6013
|
+
this.emit({ type: "retry_exhausted", errorMessage: failure.message, maxRetries: totalExhausted ? totalBudget : maxRetries });
|
|
5745
6014
|
this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
|
|
5746
6015
|
this.emit({ type: "turn_settled" });
|
|
5747
|
-
this.
|
|
6016
|
+
this.resetAttempts();
|
|
5748
6017
|
this.setRecoveryState("terminal");
|
|
5749
6018
|
this.resolvePending();
|
|
5750
6019
|
return;
|
|
5751
6020
|
}
|
|
5752
|
-
const attempt =
|
|
6021
|
+
const attempt = kindAttempts;
|
|
5753
6022
|
const rawDelayMs = (this.retry.baseDelayMs ?? 2e3) * 2 ** (attempt - 1);
|
|
5754
6023
|
const delayMs = this.retry.maxDelayMs !== void 0 ? Math.min(rawDelayMs, this.retry.maxDelayMs) : rawDelayMs;
|
|
5755
6024
|
this.ensurePending();
|
|
@@ -5774,7 +6043,7 @@ var AgentSessionRetryController = class {
|
|
|
5774
6043
|
});
|
|
5775
6044
|
this.emit({ type: "turn_failed_terminal", failure: terminalFailure, message });
|
|
5776
6045
|
this.emit({ type: "turn_settled" });
|
|
5777
|
-
this.
|
|
6046
|
+
this.resetAttempts();
|
|
5778
6047
|
this.setRecoveryState("terminal");
|
|
5779
6048
|
this.resolvePending();
|
|
5780
6049
|
});
|
|
@@ -5803,15 +6072,19 @@ var AgentSessionRetryController = class {
|
|
|
5803
6072
|
this.emit({ type: "retry_exhausted", errorMessage: terminalFailure.message, maxRetries });
|
|
5804
6073
|
this.emit({ type: "turn_failed_terminal", failure: terminalFailure });
|
|
5805
6074
|
this.emit({ type: "turn_settled" });
|
|
5806
|
-
this.
|
|
6075
|
+
this.resetAttempts();
|
|
5807
6076
|
this.setRecoveryState("terminal");
|
|
5808
6077
|
this.resolvePending();
|
|
5809
6078
|
}
|
|
5810
6079
|
cancel() {
|
|
5811
6080
|
this.cancelTimerOnly();
|
|
5812
|
-
this.
|
|
6081
|
+
this.resetAttempts();
|
|
5813
6082
|
this.resolvePending();
|
|
5814
6083
|
}
|
|
6084
|
+
resetAttempts() {
|
|
6085
|
+
this.retryAttempts.clear();
|
|
6086
|
+
this.totalRetryAttempts = 0;
|
|
6087
|
+
}
|
|
5815
6088
|
cancelTimerOnly() {
|
|
5816
6089
|
if (this.retryTimer === void 0) {
|
|
5817
6090
|
return;
|
|
@@ -5993,9 +6266,14 @@ function classifyControllerRuntimeError(params) {
|
|
|
5993
6266
|
return createFailureClassification({ errorMessage, phase: params.phase }, params.retry, params.classifyFailure);
|
|
5994
6267
|
}
|
|
5995
6268
|
async function runAgentSessionRuntimePhase(params) {
|
|
6269
|
+
const startedGeneration = params.getCurrentGeneration?.();
|
|
6270
|
+
const isStale = () => params.getCurrentGeneration !== void 0 && startedGeneration !== void 0 && params.getCurrentGeneration() !== startedGeneration;
|
|
5996
6271
|
try {
|
|
5997
6272
|
return await params.action();
|
|
5998
6273
|
} catch (error) {
|
|
6274
|
+
if (isStale()) {
|
|
6275
|
+
throw error;
|
|
6276
|
+
}
|
|
5999
6277
|
const failure = classifyControllerRuntimeError({
|
|
6000
6278
|
error,
|
|
6001
6279
|
phase: params.phase,
|
|
@@ -6228,6 +6506,7 @@ async function runAgentSessionControllerAutoCompact(params) {
|
|
|
6228
6506
|
reason: params.reason,
|
|
6229
6507
|
willRetry: params.willRetry,
|
|
6230
6508
|
signal: params.signal,
|
|
6509
|
+
isCurrent: params.isCurrent,
|
|
6231
6510
|
compaction: params.compaction,
|
|
6232
6511
|
agent: params.agent,
|
|
6233
6512
|
sessionManager: params.sessionManager,
|
|
@@ -6238,6 +6517,7 @@ async function runAgentSessionControllerAutoCompact(params) {
|
|
|
6238
6517
|
markSyncedToAgentState: params.markSyncedToAgentState,
|
|
6239
6518
|
continueTurn: params.continueTurn
|
|
6240
6519
|
});
|
|
6520
|
+
if (params.isCurrent?.() === false) return result;
|
|
6241
6521
|
if (!params.willRetry || result?.aborted) {
|
|
6242
6522
|
const alreadyCancelled = params.getRecoveryState?.() === "cancelled" || params.getDisposed?.() === true;
|
|
6243
6523
|
params.setRecoveryState(result?.aborted && cancelOnAbort ? "cancelled" : "none");
|
|
@@ -6251,11 +6531,12 @@ async function runAgentSessionControllerAutoCompact(params) {
|
|
|
6251
6531
|
params.emit({ type: "turn_settled" });
|
|
6252
6532
|
}
|
|
6253
6533
|
if (!result?.aborted && runAfterAgentEndHook) {
|
|
6254
|
-
|
|
6534
|
+
await params.handleAgentEndHook(params.agent.state.messages.slice());
|
|
6255
6535
|
}
|
|
6256
6536
|
}
|
|
6257
6537
|
return result;
|
|
6258
6538
|
} catch (error) {
|
|
6539
|
+
if (params.isCurrent?.() === false) return { aborted: true };
|
|
6259
6540
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6260
6541
|
if (!params.willRetry && params.reason !== "overflow") {
|
|
6261
6542
|
params.setRecoveryState("none");
|
|
@@ -6263,7 +6544,7 @@ async function runAgentSessionControllerAutoCompact(params) {
|
|
|
6263
6544
|
params.emit({ type: "turn_settled" });
|
|
6264
6545
|
}
|
|
6265
6546
|
if (runAfterAgentEndHook) {
|
|
6266
|
-
|
|
6547
|
+
await params.handleAgentEndHook(params.agent.state.messages.slice());
|
|
6267
6548
|
}
|
|
6268
6549
|
throw error;
|
|
6269
6550
|
}
|
|
@@ -6281,7 +6562,7 @@ async function runAgentSessionControllerAutoCompact(params) {
|
|
|
6281
6562
|
params.emit({ type: "turn_settled" });
|
|
6282
6563
|
throw error;
|
|
6283
6564
|
} finally {
|
|
6284
|
-
params.clearPendingFailure();
|
|
6565
|
+
if (params.isCurrent?.() !== false) params.clearPendingFailure();
|
|
6285
6566
|
}
|
|
6286
6567
|
}
|
|
6287
6568
|
|
|
@@ -6302,8 +6583,22 @@ var AgentSessionController = class {
|
|
|
6302
6583
|
unsubscribeSemantic;
|
|
6303
6584
|
lastSyncedMessageCount = 0;
|
|
6304
6585
|
disposed = false;
|
|
6586
|
+
transitioning = false;
|
|
6587
|
+
generation = 0;
|
|
6588
|
+
convergence = new SessionConvergence({
|
|
6589
|
+
sessionId: () => this.sessionManager.getActiveSession()?.id,
|
|
6590
|
+
wait: () => this.waitForRuntimePolicies(),
|
|
6591
|
+
busy: () => this.isStreaming || this.hasPendingRuntimePolicy(),
|
|
6592
|
+
queued: () => this.hasQueuedMessages,
|
|
6593
|
+
emit: (event) => this.emit(event)
|
|
6594
|
+
});
|
|
6595
|
+
get workIdentity() {
|
|
6596
|
+
return this.convergence.identity;
|
|
6597
|
+
}
|
|
6598
|
+
pendingEndOperations = new PendingEndOperations();
|
|
6305
6599
|
baseSystemPrompt;
|
|
6306
6600
|
prepareTurn;
|
|
6601
|
+
onExecutionEnd;
|
|
6307
6602
|
afterAgentEnd;
|
|
6308
6603
|
getAutoCompactDecision;
|
|
6309
6604
|
classifyFailure;
|
|
@@ -6318,6 +6613,7 @@ var AgentSessionController = class {
|
|
|
6318
6613
|
this.baseSystemPrompt = options.baseSystemPrompt ?? this.agent.state.systemPrompt;
|
|
6319
6614
|
this.prepareTurn = options.prepareTurn;
|
|
6320
6615
|
this.afterAgentEnd = options.afterAgentEnd;
|
|
6616
|
+
this.onExecutionEnd = options.onExecutionEnd;
|
|
6321
6617
|
this.getAutoCompactDecision = options.getAutoCompactDecision;
|
|
6322
6618
|
this.classifyFailure = options.classifyFailure;
|
|
6323
6619
|
if (options.onEvent) {
|
|
@@ -6395,7 +6691,7 @@ var AgentSessionController = class {
|
|
|
6395
6691
|
return this.agent.state.isStreaming;
|
|
6396
6692
|
}
|
|
6397
6693
|
get hasQueuedMessages() {
|
|
6398
|
-
return this.queue.hasQueuedMessages;
|
|
6694
|
+
return this.queue.hasQueuedMessages || this.agent.hasQueuedMessages();
|
|
6399
6695
|
}
|
|
6400
6696
|
get pendingMessageCount() {
|
|
6401
6697
|
return this.queue.length;
|
|
@@ -6427,18 +6723,30 @@ var AgentSessionController = class {
|
|
|
6427
6723
|
};
|
|
6428
6724
|
}
|
|
6429
6725
|
subscribe(listener) {
|
|
6726
|
+
if (this.disposed) return () => {
|
|
6727
|
+
};
|
|
6430
6728
|
this.listeners.add(listener);
|
|
6431
6729
|
return () => this.listeners.delete(listener);
|
|
6432
6730
|
}
|
|
6433
6731
|
syncFromActiveSession(messages) {
|
|
6434
|
-
|
|
6435
|
-
this.
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6732
|
+
if (this.disposed || this.transitioning) return;
|
|
6733
|
+
this.transitioning = true;
|
|
6734
|
+
try {
|
|
6735
|
+
this.generation++;
|
|
6736
|
+
this.cancelScheduledWork();
|
|
6737
|
+
this.pendingEndOperations.clear();
|
|
6738
|
+
const activeSession = this.sessionManager.getActiveSession();
|
|
6739
|
+
this.agent.sessionId = activeSession?.id;
|
|
6740
|
+
const sessionMessages = messages ?? this.sessionManager.getMessages();
|
|
6741
|
+
const conversationMessages = getConversationMessages(sessionMessages);
|
|
6742
|
+
this.agent.replaceMessages(conversationMessages);
|
|
6743
|
+
this.lastSyncedMessageCount = conversationMessages.length;
|
|
6744
|
+
this.queue.clear();
|
|
6745
|
+
this.recordLifecycle({ type: "session_synced" });
|
|
6746
|
+
this.convergence.invalidate("sync");
|
|
6747
|
+
} finally {
|
|
6748
|
+
this.transitioning = false;
|
|
6749
|
+
}
|
|
6442
6750
|
}
|
|
6443
6751
|
markSyncedToAgentState() {
|
|
6444
6752
|
this.lastSyncedMessageCount = this.agent.state.messages.length;
|
|
@@ -6456,7 +6764,12 @@ var AgentSessionController = class {
|
|
|
6456
6764
|
this.agent.setTools(context.tools);
|
|
6457
6765
|
}
|
|
6458
6766
|
}
|
|
6459
|
-
|
|
6767
|
+
prompt(input, options) {
|
|
6768
|
+
if (this.disposed || this.transitioning) return Promise.reject(new Error("Controller disposed or transitioning"));
|
|
6769
|
+
return this.convergence.run(() => this.promptInternal(input, options));
|
|
6770
|
+
}
|
|
6771
|
+
async promptInternal(input, options) {
|
|
6772
|
+
const generation = this.generation;
|
|
6460
6773
|
const messages = normalizePromptInput(input);
|
|
6461
6774
|
if (this.isRecoveryActive()) {
|
|
6462
6775
|
this.queue.enqueue(messages, options?.streamingBehavior ?? "followUp", {
|
|
@@ -6481,8 +6794,9 @@ var AgentSessionController = class {
|
|
|
6481
6794
|
throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
|
|
6482
6795
|
}
|
|
6483
6796
|
await this.runPreRequestAutoCompactIfNeeded();
|
|
6797
|
+
if (generation !== this.generation || this.disposed) return;
|
|
6484
6798
|
const prepared = await this.runRuntimePhase("prepare", () => this.preparePromptMessages(messages, options));
|
|
6485
|
-
if (prepared.handled) {
|
|
6799
|
+
if (generation !== this.generation || this.disposed || prepared.handled) {
|
|
6486
6800
|
return;
|
|
6487
6801
|
}
|
|
6488
6802
|
this.recordLifecycle({ type: "prompt_started" });
|
|
@@ -6490,45 +6804,66 @@ var AgentSessionController = class {
|
|
|
6490
6804
|
this.agent.setSystemPrompt(prepared.systemPrompt);
|
|
6491
6805
|
}
|
|
6492
6806
|
await this.runRuntimePhase("request", () => this.agent.prompt(prepared.messages));
|
|
6807
|
+
if (generation !== this.generation || this.disposed) return;
|
|
6493
6808
|
await this.runRuntimePhase("post_turn", () => this.syncNewAgentMessages());
|
|
6494
6809
|
}
|
|
6495
|
-
|
|
6810
|
+
continue() {
|
|
6811
|
+
return this.disposed || this.transitioning ? Promise.reject(new Error("Controller disposed or transitioning")) : this.convergence.run(() => this.continueInternal());
|
|
6812
|
+
}
|
|
6813
|
+
async continueInternal() {
|
|
6814
|
+
const generation = this.generation;
|
|
6496
6815
|
if (this.isStreaming) {
|
|
6497
6816
|
throw new Error("Agent is already processing. Wait for completion before continuing.");
|
|
6498
6817
|
}
|
|
6499
6818
|
await this.runPreRequestAutoCompactIfNeeded();
|
|
6819
|
+
if (generation !== this.generation || this.disposed) return;
|
|
6500
6820
|
this.recordLifecycle({ type: "prompt_started" });
|
|
6501
6821
|
await this.runRuntimePhase("request", () => this.agent.continue());
|
|
6822
|
+
if (generation !== this.generation || this.disposed) return;
|
|
6502
6823
|
await this.runRuntimePhase("post_turn", () => this.syncNewAgentMessages());
|
|
6503
6824
|
}
|
|
6504
6825
|
abort() {
|
|
6505
|
-
this.
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
this.
|
|
6513
|
-
|
|
6514
|
-
|
|
6826
|
+
if (this.disposed || this.transitioning) return;
|
|
6827
|
+
this.transitioning = true;
|
|
6828
|
+
try {
|
|
6829
|
+
this.generation++;
|
|
6830
|
+
this.pendingEndOperations.clear();
|
|
6831
|
+
this.recordLifecycle({ type: "abort" });
|
|
6832
|
+
const cancelPendingRecovery = this.isRecoveryActive() && !this.isStreaming;
|
|
6833
|
+
this.cancelScheduledWork();
|
|
6834
|
+
if (cancelPendingRecovery) {
|
|
6835
|
+
this.recoveryState = "cancelled";
|
|
6836
|
+
this.emit({ type: "turn_cancelled", reason: "user_abort" });
|
|
6837
|
+
this.emit({ type: "turn_settled" });
|
|
6838
|
+
}
|
|
6839
|
+
this.agent.abort();
|
|
6840
|
+
this.convergence.invalidate("abort");
|
|
6841
|
+
} finally {
|
|
6842
|
+
this.transitioning = false;
|
|
6515
6843
|
}
|
|
6516
|
-
this.agent.abort();
|
|
6517
6844
|
}
|
|
6518
6845
|
waitForIdle() {
|
|
6519
6846
|
return this.agent.waitForIdle();
|
|
6520
6847
|
}
|
|
6521
|
-
|
|
6522
|
-
|
|
6848
|
+
waitForSettled() {
|
|
6849
|
+
return this.convergence.wait();
|
|
6850
|
+
}
|
|
6851
|
+
async waitForRuntimePolicies() {
|
|
6852
|
+
const generation = this.generation;
|
|
6853
|
+
while (!this.disposed && generation === this.generation) {
|
|
6523
6854
|
await this.agent.waitForIdle();
|
|
6524
|
-
if (!this.hasPendingRuntimePolicy()) {
|
|
6855
|
+
if (this.disposed || generation !== this.generation || !this.hasPendingRuntimePolicy()) {
|
|
6525
6856
|
return;
|
|
6526
6857
|
}
|
|
6527
|
-
await Promise.all([this.retryController.waitForRetry(), this.autoCompact.waitForAutoCompact(), this.autoContinue.waitForAutoContinue(), this.queueDispatcher.waitForDispatch()]);
|
|
6858
|
+
await Promise.all([this.pendingEndOperations.wait(), this.retryController.waitForRetry(), this.autoCompact.waitForAutoCompact(), this.autoContinue.waitForAutoContinue(), this.queueDispatcher.waitForDispatch()]);
|
|
6528
6859
|
}
|
|
6529
6860
|
}
|
|
6530
6861
|
async runAutoCompact(reason, willRetry = false, options) {
|
|
6531
|
-
|
|
6862
|
+
const generation = this.generation;
|
|
6863
|
+
if (this.disposed || this.transitioning) throw new Error("Controller disposed or transitioning");
|
|
6864
|
+
const isCurrent = () => generation === this.generation && !this.disposed;
|
|
6865
|
+
return this.convergence.run(() => runAgentSessionControllerAutoCompact({
|
|
6866
|
+
isCurrent,
|
|
6532
6867
|
reason,
|
|
6533
6868
|
willRetry,
|
|
6534
6869
|
signal: options?.signal,
|
|
@@ -6537,7 +6872,9 @@ var AgentSessionController = class {
|
|
|
6537
6872
|
sessionManager: this.sessionManager,
|
|
6538
6873
|
cwd: this.cwd,
|
|
6539
6874
|
pendingFailure: this.pendingCompactionFailure,
|
|
6540
|
-
emit: (event) =>
|
|
6875
|
+
emit: (event) => {
|
|
6876
|
+
if (isCurrent()) this.emit(event);
|
|
6877
|
+
},
|
|
6541
6878
|
setRecoveryState: (state) => {
|
|
6542
6879
|
this.recoveryState = state;
|
|
6543
6880
|
},
|
|
@@ -6555,19 +6892,19 @@ var AgentSessionController = class {
|
|
|
6555
6892
|
emitSettled: options?.emitSettled,
|
|
6556
6893
|
runAfterAgentEndHook: options?.runAfterAgentEndHook,
|
|
6557
6894
|
cancelOnAbort: options?.cancelOnAbort
|
|
6558
|
-
});
|
|
6895
|
+
}));
|
|
6559
6896
|
}
|
|
6560
6897
|
dispose() {
|
|
6561
6898
|
if (this.disposed) return;
|
|
6562
6899
|
this.disposed = true;
|
|
6563
|
-
this.
|
|
6564
|
-
this.
|
|
6565
|
-
this.
|
|
6566
|
-
this.autoContinue.cancel();
|
|
6900
|
+
this.generation++;
|
|
6901
|
+
this.pendingEndOperations.clear();
|
|
6902
|
+
this.cancelScheduledWork();
|
|
6567
6903
|
this.unsubscribeAgent?.();
|
|
6568
6904
|
this.unsubscribeSemantic?.();
|
|
6569
|
-
this.listeners.clear();
|
|
6570
6905
|
this.queue.clear();
|
|
6906
|
+
this.convergence.invalidate("dispose");
|
|
6907
|
+
this.listeners.clear();
|
|
6571
6908
|
}
|
|
6572
6909
|
drainQueuedInputsForEditor() {
|
|
6573
6910
|
return this.queue.drainForEditor();
|
|
@@ -6582,10 +6919,16 @@ var AgentSessionController = class {
|
|
|
6582
6919
|
this.queue.replaceText(search, replacement);
|
|
6583
6920
|
}
|
|
6584
6921
|
requestQueueDispatch() {
|
|
6585
|
-
if (this.queue.length > 0)
|
|
6922
|
+
if (!this.disposed && !this.transitioning && this.queue.length > 0) {
|
|
6923
|
+
void this.convergence.run(async () => {
|
|
6924
|
+
this.queueDispatcher.schedule();
|
|
6925
|
+
});
|
|
6926
|
+
}
|
|
6586
6927
|
}
|
|
6587
6928
|
async runPreRequestAutoCompactIfNeeded() {
|
|
6929
|
+
const generation = this.generation;
|
|
6588
6930
|
return runPreRequestAutoCompact({
|
|
6931
|
+
isCurrent: () => generation === this.generation && !this.disposed,
|
|
6589
6932
|
disposed: this.disposed,
|
|
6590
6933
|
runtimeGuardTriggered: this.runtimeGuard.triggered,
|
|
6591
6934
|
isStreaming: this.isStreaming,
|
|
@@ -6617,11 +6960,11 @@ var AgentSessionController = class {
|
|
|
6617
6960
|
this.queue.markMessageDelivered(event.message);
|
|
6618
6961
|
}
|
|
6619
6962
|
if (event.type === "turn_end") {
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
});
|
|
6963
|
+
this.pendingEndOperations.track(() => this.syncNewAgentMessages().catch((error) => {
|
|
6964
|
+
this.emit({ type: "turn_policy_error", message: String(error) });
|
|
6965
|
+
}));
|
|
6623
6966
|
this.recordLifecycle({ type: "turn_end_success" });
|
|
6624
|
-
|
|
6967
|
+
this.pendingEndOperations.track(() => this.handleTurnEndRuntimePolicies(event.message, event.toolResults).catch((error) => handleTurnEndPolicyThrow((event2) => this.emit(event2), error, {
|
|
6625
6968
|
recoveryWasActive: this.isRecoveryActive(),
|
|
6626
6969
|
cancelPendingRecovery: () => {
|
|
6627
6970
|
this.retryController.cancel();
|
|
@@ -6630,11 +6973,15 @@ var AgentSessionController = class {
|
|
|
6630
6973
|
markRecoveryTerminal: () => {
|
|
6631
6974
|
this.recoveryState = "terminal";
|
|
6632
6975
|
}
|
|
6633
|
-
}));
|
|
6976
|
+
})));
|
|
6634
6977
|
}
|
|
6635
6978
|
if (event.type === "agent_end") {
|
|
6636
6979
|
this.recordLifecycle({ type: "agent_end" });
|
|
6637
|
-
|
|
6980
|
+
const generation = this.generation;
|
|
6981
|
+
this.pendingEndOperations.track(async () => {
|
|
6982
|
+
await runEndHook(this.onExecutionEnd, { messages: event.messages, agent: this.agent, sessionManager: this.sessionManager, cwd: this.cwd, hasQueuedMessages: this.hasQueuedMessages }, () => generation === this.generation && !this.disposed, (e) => this.emit(e));
|
|
6983
|
+
if (generation === this.generation && !this.disposed) await this.handleAgentEndHook(event.messages);
|
|
6984
|
+
});
|
|
6638
6985
|
this.emit({ type: "streaming_changed", isStreaming: false });
|
|
6639
6986
|
}
|
|
6640
6987
|
}
|
|
@@ -6690,27 +7037,12 @@ var AgentSessionController = class {
|
|
|
6690
7037
|
});
|
|
6691
7038
|
}
|
|
6692
7039
|
async handleAgentEndHook(messages) {
|
|
7040
|
+
const generation = this.generation;
|
|
6693
7041
|
if (this.disposed || this.runtimeGuard.triggered || this.isBlockedByRuntimePolicy()) {
|
|
6694
7042
|
return;
|
|
6695
7043
|
}
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
try {
|
|
6699
|
-
await this.afterAgentEnd({
|
|
6700
|
-
messages,
|
|
6701
|
-
agent: this.agent,
|
|
6702
|
-
sessionManager: this.sessionManager,
|
|
6703
|
-
cwd: this.cwd,
|
|
6704
|
-
hasQueuedMessages: this.hasQueuedMessages
|
|
6705
|
-
});
|
|
6706
|
-
this.emit({ type: "agent_end_hook_end" });
|
|
6707
|
-
} catch (error) {
|
|
6708
|
-
this.emit({
|
|
6709
|
-
type: "agent_end_hook_end",
|
|
6710
|
-
errorMessage: error instanceof Error ? error.message : String(error)
|
|
6711
|
-
});
|
|
6712
|
-
}
|
|
6713
|
-
}
|
|
7044
|
+
await runEndHook(this.afterAgentEnd, { messages, agent: this.agent, sessionManager: this.sessionManager, cwd: this.cwd, hasQueuedMessages: this.hasQueuedMessages }, () => generation === this.generation && !this.disposed, (event) => this.emit(event));
|
|
7045
|
+
if (generation !== this.generation || this.disposed) return;
|
|
6714
7046
|
if (this.queue.length > 0) {
|
|
6715
7047
|
this.queueDispatcher.schedule();
|
|
6716
7048
|
}
|
|
@@ -6731,6 +7063,7 @@ var AgentSessionController = class {
|
|
|
6731
7063
|
action,
|
|
6732
7064
|
retry: this.retryController.policy,
|
|
6733
7065
|
classifyFailure: this.classifyFailure,
|
|
7066
|
+
getCurrentGeneration: () => this.generation,
|
|
6734
7067
|
emit: (event) => this.emit(event),
|
|
6735
7068
|
setRecoveryState: (state) => {
|
|
6736
7069
|
this.recoveryState = state;
|
|
@@ -6743,8 +7076,14 @@ var AgentSessionController = class {
|
|
|
6743
7076
|
isBlockedByRuntimePolicy() {
|
|
6744
7077
|
return this.recoveryState !== "none" || this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact;
|
|
6745
7078
|
}
|
|
7079
|
+
cancelScheduledWork() {
|
|
7080
|
+
this.retryController.cancel();
|
|
7081
|
+
this.autoCompact.cancel();
|
|
7082
|
+
this.queueDispatcher.cancel();
|
|
7083
|
+
this.autoContinue.cancel();
|
|
7084
|
+
}
|
|
6746
7085
|
hasPendingRuntimePolicy() {
|
|
6747
|
-
return this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact || this.autoContinue.hasPendingAutoContinue || this.queueDispatcher.hasPendingDispatch;
|
|
7086
|
+
return this.pendingEndOperations.pending || this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact || this.autoContinue.hasPendingAutoContinue || this.queueDispatcher.hasPendingDispatch;
|
|
6748
7087
|
}
|
|
6749
7088
|
scheduleAutoCompact(decision, failure) {
|
|
6750
7089
|
this.recordLifecycle({ type: "auto_compact_scheduled" });
|
|
@@ -6762,10 +7101,7 @@ var AgentSessionController = class {
|
|
|
6762
7101
|
if (this.runtimeGuard.triggered) {
|
|
6763
7102
|
this.runtimeGuard.markTriggered();
|
|
6764
7103
|
}
|
|
6765
|
-
this.
|
|
6766
|
-
this.autoCompact.cancel();
|
|
6767
|
-
this.queueDispatcher.cancel();
|
|
6768
|
-
this.autoContinue.cancel();
|
|
7104
|
+
this.cancelScheduledWork();
|
|
6769
7105
|
this.recoveryState = "terminal";
|
|
6770
7106
|
this.emit({ type: "runtime_guard_triggered", toolName, repeatCount, reason });
|
|
6771
7107
|
this.emit({
|
|
@@ -6776,13 +7112,13 @@ var AgentSessionController = class {
|
|
|
6776
7112
|
this.agent.abort();
|
|
6777
7113
|
}
|
|
6778
7114
|
emit(event) {
|
|
7115
|
+
if (!event.type.startsWith("session_work_") && event.type !== "session_quiescent" && event.type !== "session_invalidated") this.convergence.observe(event);
|
|
6779
7116
|
for (const listener of this.listeners) {
|
|
6780
|
-
listener
|
|
7117
|
+
notifyObserver(listener, event);
|
|
6781
7118
|
}
|
|
6782
7119
|
}
|
|
6783
7120
|
recordLifecycle(input) {
|
|
6784
7121
|
this.lifecycleState = reduceAgentSessionLifecycle(this.lifecycleState, input);
|
|
6785
|
-
void this.lifecycleState.phase;
|
|
6786
7122
|
}
|
|
6787
7123
|
};
|
|
6788
7124
|
|
|
@@ -6965,6 +7301,109 @@ async function requestInteraction(request) {
|
|
|
6965
7301
|
return await currentInteractionHandler(request);
|
|
6966
7302
|
}
|
|
6967
7303
|
|
|
7304
|
+
// ../../packages/agent-framework/src/attention.ts
|
|
7305
|
+
function same(a, b) {
|
|
7306
|
+
return a?.sessionId === b.sessionId && a?.generation === b.generation;
|
|
7307
|
+
}
|
|
7308
|
+
var AttentionCoordinator = class {
|
|
7309
|
+
constructor(policy = {}) {
|
|
7310
|
+
this.policy = policy;
|
|
7311
|
+
}
|
|
7312
|
+
policy;
|
|
7313
|
+
current;
|
|
7314
|
+
requests = /* @__PURE__ */ new Map();
|
|
7315
|
+
listeners = /* @__PURE__ */ new Set();
|
|
7316
|
+
sequence = 0;
|
|
7317
|
+
userWork = false;
|
|
7318
|
+
consumed = false;
|
|
7319
|
+
disposed = false;
|
|
7320
|
+
get activeRequests() {
|
|
7321
|
+
return [...this.requests.values()].map((r) => ({ ...r }));
|
|
7322
|
+
}
|
|
7323
|
+
subscribe(listener) {
|
|
7324
|
+
if (this.disposed) return () => {
|
|
7325
|
+
};
|
|
7326
|
+
this.listeners.add(listener);
|
|
7327
|
+
return () => this.listeners.delete(listener);
|
|
7328
|
+
}
|
|
7329
|
+
/** Feed controller work events only. Old turn_settled/skipped events are not outcomes. */
|
|
7330
|
+
handle(event) {
|
|
7331
|
+
if (this.disposed || this.current && event.generation < this.current.generation) return;
|
|
7332
|
+
if (event.type === "session_invalidated") {
|
|
7333
|
+
this.current = { sessionId: event.sessionId, generation: event.generation };
|
|
7334
|
+
this.consumed = true;
|
|
7335
|
+
this.clear();
|
|
7336
|
+
return;
|
|
7337
|
+
}
|
|
7338
|
+
if (event.type === "session_work_started") {
|
|
7339
|
+
if (same(this.current, event)) return;
|
|
7340
|
+
this.current = { sessionId: event.sessionId, generation: event.generation };
|
|
7341
|
+
this.consumed = false;
|
|
7342
|
+
this.clear();
|
|
7343
|
+
return;
|
|
7344
|
+
}
|
|
7345
|
+
if (!same(this.current, event) || this.consumed) return;
|
|
7346
|
+
this.consumed = true;
|
|
7347
|
+
const userWork = this.userWork;
|
|
7348
|
+
this.userWork = false;
|
|
7349
|
+
if (event.outcome === "cancelled") this.clear();
|
|
7350
|
+
else if (event.outcome === "failed" || event.outcome === "blocked") this.request(event, event.outcome);
|
|
7351
|
+
else if (this.policy.completion === "accepted-user-work" && userWork) this.request(event, "completed");
|
|
7352
|
+
}
|
|
7353
|
+
/** Call at actual accepted human delivery, including steer/retry; never infer from message role. */
|
|
7354
|
+
acceptUserWork(identity) {
|
|
7355
|
+
if (this.disposed || !same(this.current, identity) || this.consumed) return false;
|
|
7356
|
+
this.userWork = true;
|
|
7357
|
+
return true;
|
|
7358
|
+
}
|
|
7359
|
+
/** Producers retain the returned ID until answered/cancelled. Duplicate IDs do not broadcast twice. */
|
|
7360
|
+
request(identity, reason, id = `attention-${++this.sequence}`) {
|
|
7361
|
+
if (this.disposed || !same(this.current, identity)) return void 0;
|
|
7362
|
+
const existing = this.requests.get(id);
|
|
7363
|
+
if (existing) return { ...existing };
|
|
7364
|
+
const request = { sessionId: identity.sessionId, generation: identity.generation, id, reason };
|
|
7365
|
+
this.requests.set(id, request);
|
|
7366
|
+
this.emit({ type: "attention_requested", ...request });
|
|
7367
|
+
return { ...request };
|
|
7368
|
+
}
|
|
7369
|
+
/** Only the interaction owner resolves; suppressing/defering host presentation is not resolution. */
|
|
7370
|
+
resolve(id, identity, resolution = "answered") {
|
|
7371
|
+
const request = this.requests.get(id);
|
|
7372
|
+
if (!request || !same(request, identity)) return false;
|
|
7373
|
+
this.requests.delete(id);
|
|
7374
|
+
this.emit({ type: "attention_resolved", ...request, resolution });
|
|
7375
|
+
return true;
|
|
7376
|
+
}
|
|
7377
|
+
dispose() {
|
|
7378
|
+
if (this.disposed) return;
|
|
7379
|
+
this.disposed = true;
|
|
7380
|
+
this.current = void 0;
|
|
7381
|
+
this.consumed = true;
|
|
7382
|
+
this.clear();
|
|
7383
|
+
this.listeners.clear();
|
|
7384
|
+
}
|
|
7385
|
+
clear() {
|
|
7386
|
+
const requests = [...this.requests.values()];
|
|
7387
|
+
this.requests.clear();
|
|
7388
|
+
this.userWork = false;
|
|
7389
|
+
for (const request of requests) this.emit({ type: "attention_resolved", ...request, resolution: "invalidated" });
|
|
7390
|
+
}
|
|
7391
|
+
emit(event) {
|
|
7392
|
+
for (const listener of this.listeners) notifyObserver(listener, event);
|
|
7393
|
+
}
|
|
7394
|
+
};
|
|
7395
|
+
|
|
7396
|
+
// ../../packages/agent-framework/src/interaction-attention.ts
|
|
7397
|
+
function interactionAttentionReason(request) {
|
|
7398
|
+
return request.type === "notify" ? void 0 : "input-required";
|
|
7399
|
+
}
|
|
7400
|
+
function interactionAttentionResolution(response) {
|
|
7401
|
+
if (!response || typeof response !== "object") return "cancelled";
|
|
7402
|
+
const result = response;
|
|
7403
|
+
if (result.timedOut || result.skipped || result.unavailable) return "cancelled";
|
|
7404
|
+
return ["confirmed", "selection", "value", "answers"].some((key) => result[key] !== void 0) ? "answered" : "cancelled";
|
|
7405
|
+
}
|
|
7406
|
+
|
|
6968
7407
|
// ../../packages/agent-framework/src/context-files.ts
|
|
6969
7408
|
var AGENTS_CONTEXT_FILE_NAME = "AGENTS.md";
|
|
6970
7409
|
function normalizeContextPath(path4) {
|
|
@@ -8827,14 +9266,23 @@ function createFileObservationState(options = {}) {
|
|
|
8827
9266
|
function coverageSpansFile(coveredRanges, totalLines) {
|
|
8828
9267
|
if (totalLines <= 0) return true;
|
|
8829
9268
|
const merged = mergeLineRanges(coveredRanges);
|
|
8830
|
-
return merged.length
|
|
9269
|
+
return merged.length === 1 && merged[0].startLine <= 1 && merged[0].endLine >= totalLines;
|
|
8831
9270
|
}
|
|
8832
9271
|
function summarize(observed) {
|
|
8833
9272
|
const merged = mergeLineRanges(observed.coveredRanges);
|
|
8834
9273
|
const coveredLines = merged.reduce((total, range2) => total + (range2.endLine - range2.startLine + 1), 0);
|
|
8835
9274
|
let firstUnseenLine;
|
|
8836
9275
|
if (observed.coveredTotalLines > 0 && coveredLines < observed.coveredTotalLines) {
|
|
8837
|
-
firstUnseenLine =
|
|
9276
|
+
firstUnseenLine = 1;
|
|
9277
|
+
if (merged.length > 0 && merged[0].startLine <= 1) {
|
|
9278
|
+
firstUnseenLine = merged[merged.length - 1].endLine + 1;
|
|
9279
|
+
for (let index = 0; index + 1 < merged.length; index++) {
|
|
9280
|
+
if (merged[index + 1].startLine > merged[index].endLine + 1) {
|
|
9281
|
+
firstUnseenLine = merged[index].endLine + 1;
|
|
9282
|
+
break;
|
|
9283
|
+
}
|
|
9284
|
+
}
|
|
9285
|
+
}
|
|
8838
9286
|
}
|
|
8839
9287
|
let lastRange;
|
|
8840
9288
|
for (const range2 of observed.readRanges.values()) {
|
|
@@ -8950,22 +9398,32 @@ function createReadTool(cwd, options) {
|
|
|
8950
9398
|
...rootSchema ? { root: rootSchema } : {},
|
|
8951
9399
|
offset: Type.Optional(Type.Integer({ minimum: 1, maximum: MAX_CROSS_HOST_LINE_NUMBER, description: "Line number to start reading from, 1-indexed. Example: 201." })),
|
|
8952
9400
|
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: MAX_CROSS_HOST_LINE_NUMBER, description: "Maximum number of lines to read. Example: 120." })),
|
|
9401
|
+
tail: Type.Optional(Type.Integer({ minimum: 1, maximum: MAX_CROSS_HOST_LINE_NUMBER, description: "Read the last N lines of the file, without needing to know the total line count. Useful for logs and files that grow. Mutually exclusive with offset/limit. Example: tail=50." })),
|
|
8953
9402
|
question: Type.Optional(Type.String({ description: "Optional focused question for image, video, audio, or document understanding. Use this when asking what a media/document file contains; omit for ordinary text reads." }))
|
|
8954
9403
|
});
|
|
8955
9404
|
const pathResolutionDescription = buildPathResolutionDescription(cwd, options);
|
|
8956
9405
|
return {
|
|
8957
9406
|
name: "read",
|
|
8958
9407
|
label: "read",
|
|
8959
|
-
description: `Read a file's contents. Use this before editing, and use offset/limit for large files. Images are attached for the current model when it supports vision; otherwise a configured file-understanding service analyzes them. Video, audio, and documents require a configured understanding service. Pass question to specify what to focus on; it does not force a separate model request. Example: read_file path='Scripts/PlayerController.cs' or read_file path='Data/config.json'. ${pathResolutionDescription} For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). When you need the full file, continue with a larger offset until complete.`,
|
|
9408
|
+
description: `Read a file's contents. Use this before editing, and use offset/limit for large files, or tail=N to read the last N lines when the line count is unknown. Images are attached for the current model when it supports vision; otherwise a configured file-understanding service analyzes them. Video, audio, and documents require a configured understanding service. Pass question to specify what to focus on; it does not force a separate model request. Example: read_file path='Scripts/PlayerController.cs' or read_file path='Data/config.json'. ${pathResolutionDescription} For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). When you need the full file, continue with a larger offset until complete.`,
|
|
8960
9409
|
parameters: readSchema,
|
|
8961
9410
|
execute: async (_toolCallId, params, signal) => {
|
|
8962
|
-
const { path: path4, root,
|
|
9411
|
+
const { path: path4, root, question } = params;
|
|
9412
|
+
const tail = params.tail;
|
|
9413
|
+
let offset = params.offset;
|
|
9414
|
+
let limit = params.limit;
|
|
8963
9415
|
if (offset !== void 0 && (!Number.isSafeInteger(offset) || offset < 1 || offset > MAX_CROSS_HOST_LINE_NUMBER)) {
|
|
8964
9416
|
return createReadErrorResult(`Invalid arguments: offset must be a positive safe integer no greater than ${MAX_CROSS_HOST_LINE_NUMBER}.`);
|
|
8965
9417
|
}
|
|
8966
9418
|
if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_CROSS_HOST_LINE_NUMBER)) {
|
|
8967
9419
|
return createReadErrorResult(`Invalid arguments: limit must be a positive safe integer no greater than ${MAX_CROSS_HOST_LINE_NUMBER}.`);
|
|
8968
9420
|
}
|
|
9421
|
+
if (tail !== void 0 && (!Number.isSafeInteger(tail) || tail < 1 || tail > MAX_CROSS_HOST_LINE_NUMBER)) {
|
|
9422
|
+
return createReadErrorResult(`Invalid arguments: tail must be a positive safe integer no greater than ${MAX_CROSS_HOST_LINE_NUMBER}.`);
|
|
9423
|
+
}
|
|
9424
|
+
if (tail !== void 0 && (offset !== void 0 || limit !== void 0)) {
|
|
9425
|
+
return createReadErrorResult("Invalid arguments: tail cannot be combined with offset or limit. Use tail=N to read the last N lines, or offset/limit for a window.");
|
|
9426
|
+
}
|
|
8969
9427
|
if (offset !== void 0 && limit !== void 0 && offset - 1 + limit > MAX_CROSS_HOST_LINE_NUMBER) {
|
|
8970
9428
|
return createReadErrorResult(`Invalid arguments: offset and limit exceed the ${MAX_CROSS_HOST_LINE_NUMBER}-line cross-host range.`);
|
|
8971
9429
|
}
|
|
@@ -9129,14 +9587,38 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
9129
9587
|
});
|
|
9130
9588
|
return;
|
|
9131
9589
|
}
|
|
9132
|
-
|
|
9590
|
+
let translatedFromTail = false;
|
|
9591
|
+
let tailWindowIncludesPhantom = false;
|
|
9592
|
+
let tailEffectiveLines;
|
|
9593
|
+
let startLine;
|
|
9594
|
+
let range2;
|
|
9595
|
+
if (tail !== void 0) {
|
|
9596
|
+
const effectiveTail = Math.max(1, Math.min(tail, DEFAULT_MAX_LINES - 1));
|
|
9597
|
+
translatedFromTail = true;
|
|
9598
|
+
tailEffectiveLines = effectiveTail;
|
|
9599
|
+
range2 = await fs2.readTextRange(absolutePath, {
|
|
9600
|
+
tailLines: effectiveTail,
|
|
9601
|
+
maxOutputLines: DEFAULT_MAX_LINES,
|
|
9602
|
+
maxOutputBytes: DEFAULT_MAX_BYTES
|
|
9603
|
+
}, signal);
|
|
9604
|
+
tailWindowIncludesPhantom = range2.endsWithNewline === true;
|
|
9605
|
+
if (range2.selectedLines > 0) {
|
|
9606
|
+
const derivedHead = range2.totalLines - range2.selectedLines + 1;
|
|
9607
|
+
startLine = range2.outputStartLine !== void 0 && !range2.firstLineExceedsLimit ? range2.outputStartLine - 1 : derivedHead - 1;
|
|
9608
|
+
limit = range2.selectedLines;
|
|
9609
|
+
} else {
|
|
9610
|
+
startLine = 0;
|
|
9611
|
+
}
|
|
9612
|
+
} else {
|
|
9613
|
+
startLine = offset === void 0 ? 0 : offset - 1;
|
|
9614
|
+
range2 = await fs2.readTextRange(absolutePath, {
|
|
9615
|
+
offsetLine: startLine,
|
|
9616
|
+
limitLines: limit,
|
|
9617
|
+
maxOutputLines: DEFAULT_MAX_LINES,
|
|
9618
|
+
maxOutputBytes: DEFAULT_MAX_BYTES
|
|
9619
|
+
}, signal);
|
|
9620
|
+
}
|
|
9133
9621
|
const startLineDisplay = startLine + 1;
|
|
9134
|
-
const range2 = await fs2.readTextRange(absolutePath, {
|
|
9135
|
-
offsetLine: startLine,
|
|
9136
|
-
limitLines: limit,
|
|
9137
|
-
maxOutputLines: DEFAULT_MAX_LINES,
|
|
9138
|
-
maxOutputBytes: DEFAULT_MAX_BYTES
|
|
9139
|
-
}, signal);
|
|
9140
9622
|
if (options?.allowBinary === false) {
|
|
9141
9623
|
const selectedLooksBinary = range2.selectedBytesLookBinary ?? (looksLikeBinaryText(range2.content) || range2.content.includes("\uFFFD"));
|
|
9142
9624
|
const selectedBinary = selectedLooksBinary ? { kind: "document", mimeType: "application/octet-stream" } : void 0;
|
|
@@ -9155,7 +9637,7 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
9155
9637
|
contentHash: range2.contentHash
|
|
9156
9638
|
};
|
|
9157
9639
|
const totalFileLines = range2.totalLines;
|
|
9158
|
-
if (startLine >= range2.totalLines) {
|
|
9640
|
+
if (startLine >= range2.totalLines && !translatedFromTail) {
|
|
9159
9641
|
throw new Error(`Offset ${offset} is beyond end of file (${range2.totalLines} lines total)`);
|
|
9160
9642
|
}
|
|
9161
9643
|
let userLimitedLines;
|
|
@@ -9176,7 +9658,7 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
9176
9658
|
maxLines: DEFAULT_MAX_LINES,
|
|
9177
9659
|
maxBytes: DEFAULT_MAX_BYTES
|
|
9178
9660
|
};
|
|
9179
|
-
const readStartsAtBeginning =
|
|
9661
|
+
const readStartsAtBeginning = startLine === 0;
|
|
9180
9662
|
const readReachedEndOfFile = startLine + range2.selectedLines >= range2.totalLines;
|
|
9181
9663
|
const isCompleteTextKnown = readStartsAtBeginning && readReachedEndOfFile && !truncation.truncated && !truncation.firstLineExceedsLimit;
|
|
9182
9664
|
let callerObservation = fileObservationState;
|
|
@@ -9198,13 +9680,14 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
9198
9680
|
resolve3({
|
|
9199
9681
|
content: [{
|
|
9200
9682
|
type: "text",
|
|
9201
|
-
text: `File unchanged since previous read of this range (offset=${startLineDisplay}${limit === void 0 ? ", no limit" : `, limit=${limit}`}). Earlier reads still count toward write_file coverage of this file. Use a different offset/limit if you need another section.`
|
|
9683
|
+
text: translatedFromTail ? `File unchanged since previous tail=${tailEffectiveLines} read (last ${tailWindowIncludesPhantom ? range2.selectedLines - 1 : range2.selectedLines} lines, ${range2.totalLines} lines total). Earlier reads still count toward write_file coverage of this file.` : `File unchanged since previous read of this range (offset=${startLineDisplay}${limit === void 0 ? ", no limit" : `, limit=${limit}`}). Earlier reads still count toward write_file coverage of this file. Use a different offset/limit if you need another section.`
|
|
9202
9684
|
}],
|
|
9203
9685
|
details: {
|
|
9204
9686
|
unchangedSincePreviousRead: true,
|
|
9205
9687
|
path: absolutePath,
|
|
9206
9688
|
offset: startLineDisplay,
|
|
9207
|
-
limit
|
|
9689
|
+
limit,
|
|
9690
|
+
...translatedFromTail ? { totalLines: range2.totalLines } : {}
|
|
9208
9691
|
}
|
|
9209
9692
|
});
|
|
9210
9693
|
return;
|
|
@@ -9223,7 +9706,12 @@ Base directory: ${nodePath.dirname(absolutePath)}
|
|
|
9223
9706
|
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
9224
9707
|
const nextOffset = endLineDisplay + 1;
|
|
9225
9708
|
outputText = truncation.content;
|
|
9226
|
-
if (
|
|
9709
|
+
if (translatedFromTail) {
|
|
9710
|
+
const endsAtLastLine = truncation.content.length > 0 ? ", and the content above ends at the file's last line" : "";
|
|
9711
|
+
outputText += `
|
|
9712
|
+
|
|
9713
|
+
[Tail window hit the output cap: the earliest lines of the requested tail were dropped${endsAtLastLine}. Read earlier lines with offset, or request a smaller tail.]`;
|
|
9714
|
+
} else if (truncation.truncatedBy === "lines") {
|
|
9227
9715
|
outputText += `
|
|
9228
9716
|
|
|
9229
9717
|
[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;
|
|
@@ -9249,18 +9737,38 @@ Base directory: ${nodePath.dirname(absolutePath)}
|
|
|
9249
9737
|
snapshot,
|
|
9250
9738
|
range2.selectedContentHash,
|
|
9251
9739
|
isCompleteTextKnown,
|
|
9252
|
-
{
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9740
|
+
(() => {
|
|
9741
|
+
if (translatedFromTail && truncation.truncated && !truncation.firstLineExceedsLimit) {
|
|
9742
|
+
const outputStart = range2.outputStartLine ?? startLineDisplay + Math.max(0, range2.selectedLines - range2.outputLines);
|
|
9743
|
+
const clampedStart = Math.min(outputStart, range2.totalLines + 1);
|
|
9744
|
+
const clampedEnd = Math.min(range2.totalLines, clampedStart + range2.outputLines - 1);
|
|
9745
|
+
return {
|
|
9746
|
+
startLine: clampedStart,
|
|
9747
|
+
endLine: clampedStart > range2.totalLines ? clampedStart - 1 : clampedEnd,
|
|
9748
|
+
totalLines: range2.totalLines
|
|
9749
|
+
};
|
|
9750
|
+
}
|
|
9751
|
+
return {
|
|
9752
|
+
// firstLineExceedsLimit yields an intentionally empty range
|
|
9753
|
+
// (start - 1): zero lines were actually shown, so it must
|
|
9754
|
+
// contribute nothing to write coverage.
|
|
9755
|
+
startLine: startLineDisplay,
|
|
9756
|
+
endLine: startLineDisplay + (truncation.firstLineExceedsLimit ? 0 : truncation.truncated ? truncation.outputLines : range2.selectedLines) - 1,
|
|
9757
|
+
totalLines: range2.totalLines
|
|
9758
|
+
};
|
|
9759
|
+
})()
|
|
9260
9760
|
);
|
|
9261
9761
|
if (callerObservation !== fileObservationState) {
|
|
9262
9762
|
callerObservation.recordTextRead(absolutePath, rangeKey, snapshot, range2.selectedContentHash, isCompleteTextKnown);
|
|
9263
9763
|
}
|
|
9764
|
+
if (translatedFromTail && tail !== void 0 && tailEffectiveLines !== void 0 && tailEffectiveLines < tail && startLineDisplay > 1) {
|
|
9765
|
+
outputText += `
|
|
9766
|
+
|
|
9767
|
+
[Tail clamped to ${tailEffectiveLines} lines: the per-read line cap is ${DEFAULT_MAX_LINES}, so the ${startLineDisplay - 1} earliest lines of the file are not shown. Use offset reads to walk earlier lines, or a smaller tail.]`;
|
|
9768
|
+
}
|
|
9769
|
+
if (translatedFromTail) {
|
|
9770
|
+
details = { ...details ?? {}, totalLines: range2.totalLines };
|
|
9771
|
+
}
|
|
9264
9772
|
outputText = sourceHeader + outputText;
|
|
9265
9773
|
const textContent = [{ type: "text", text: outputText }];
|
|
9266
9774
|
if (aborted) return;
|
|
@@ -9296,10 +9804,28 @@ function isSpecialDevicePath(path4) {
|
|
|
9296
9804
|
function detectUnderstandingKind(filePath) {
|
|
9297
9805
|
const lower = filePath.toLowerCase();
|
|
9298
9806
|
if (/\.(mp4|mov|m4v|webm|avi|mkv)$/.test(lower)) {
|
|
9299
|
-
|
|
9807
|
+
const videoMime = {
|
|
9808
|
+
mp4: "video/mp4",
|
|
9809
|
+
mov: "video/quicktime",
|
|
9810
|
+
m4v: "video/x-m4v",
|
|
9811
|
+
webm: "video/webm",
|
|
9812
|
+
avi: "video/x-msvideo",
|
|
9813
|
+
mkv: "video/x-matroska"
|
|
9814
|
+
};
|
|
9815
|
+
const ext2 = lower.split(".").pop();
|
|
9816
|
+
return { kind: "video", mimeType: videoMime[ext2] ?? "video/mp4" };
|
|
9300
9817
|
}
|
|
9301
9818
|
if (/\.(mp3|wav|m4a|aac|flac|ogg)$/.test(lower)) {
|
|
9302
|
-
|
|
9819
|
+
const audioMime = {
|
|
9820
|
+
mp3: "audio/mpeg",
|
|
9821
|
+
wav: "audio/wav",
|
|
9822
|
+
m4a: "audio/mp4",
|
|
9823
|
+
aac: "audio/aac",
|
|
9824
|
+
flac: "audio/flac",
|
|
9825
|
+
ogg: "audio/ogg"
|
|
9826
|
+
};
|
|
9827
|
+
const ext2 = lower.split(".").pop();
|
|
9828
|
+
return { kind: "audio", mimeType: audioMime[ext2] ?? "audio/mpeg" };
|
|
9303
9829
|
}
|
|
9304
9830
|
if (/\.(pdf)$/.test(lower)) {
|
|
9305
9831
|
return { kind: "document", mimeType: "application/pdf" };
|
|
@@ -9621,13 +10147,15 @@ function createWriteTool(cwd, options) {
|
|
|
9621
10147
|
const observedSnapshot = fileObservationState.getCompleteTextSnapshot(absolutePath);
|
|
9622
10148
|
if (!observedSnapshot) {
|
|
9623
10149
|
const summary = fileObservationState.describeObservation(absolutePath);
|
|
9624
|
-
const coverageHint = summary && summary.totalLines > 0 ? ` Previous reads cover ${summary.coveredLines}/${summary.totalLines} lines${summary.lastRange ? ` (last read: lines ${summary.lastRange.startLine}-${summary.lastRange.endLine})` : ""}${summary.firstUnseenLine ? `; read the rest with read_file offset=${summary.firstUnseenLine}` : "; read the full file with read_file (no offset/limit)"}.` : " Read the current full file with read_file (no offset/limit) first.";
|
|
10150
|
+
const coverageHint = summary && summary.totalLines > 0 ? ` Previous reads cover ${summary.coveredLines}/${summary.totalLines} lines${summary.lastRange ? ` (last read: lines ${summary.lastRange.startLine}-${summary.lastRange.endLine})` : ""}${summary.firstUnseenLine ? `; read the rest with read_file offset=${summary.firstUnseenLine}, or a tail read spanning the whole file` : "; read the full file with read_file (no offset/limit, or a tail read spanning the whole file)"}.` : " Read the current full file with read_file (no offset/limit, or a tail read spanning the whole file) first.";
|
|
9625
10151
|
throw new Error(
|
|
9626
10152
|
usesExternalFileObservationState ? `write_file blocked: overwriting requires the file to be fully observed by read_file.${coverageHint} Alternatively, use edit_file for targeted changes.` : "Existing-file overwrite requires write_file to share a fileObservationState with read_file; use createSharedFileSystemTools or pass the same fileObservationState to both tools."
|
|
9627
10153
|
);
|
|
9628
10154
|
}
|
|
9629
10155
|
if (!snapshotsHaveSameContent(observedSnapshot, currentSnapshot)) {
|
|
9630
|
-
throw new Error(
|
|
10156
|
+
throw new Error(
|
|
10157
|
+
`File changed since it was read (${observedSnapshot.size} \u2192 ${currentSnapshot.size} bytes; out-of-band edits such as bash sed/redirects cause this). Re-read with the read_file tool \u2014 offset/limit/tail reads accumulate coverage; bash head/cat does not count \u2014 or use edit_file for targeted changes.`
|
|
10158
|
+
);
|
|
9631
10159
|
}
|
|
9632
10160
|
}
|
|
9633
10161
|
await fs2.writeFile(absolutePath, content, "utf-8");
|
|
@@ -9641,7 +10169,7 @@ function createWriteTool(cwd, options) {
|
|
|
9641
10169
|
signal.removeEventListener("abort", onAbort);
|
|
9642
10170
|
}
|
|
9643
10171
|
resolve3({
|
|
9644
|
-
content: [{ type: "text", text: `Successfully wrote ${
|
|
10172
|
+
content: [{ type: "text", text: `Successfully wrote ${writtenStat.size} bytes to ${filePath}` }],
|
|
9645
10173
|
details: void 0
|
|
9646
10174
|
});
|
|
9647
10175
|
} catch (error) {
|
|
@@ -9674,6 +10202,8 @@ function restoreLineEndings(text, ending) {
|
|
|
9674
10202
|
return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
|
|
9675
10203
|
}
|
|
9676
10204
|
var MAX_FUZZY_DIAGNOSTIC_CHARS = 256 * 1024;
|
|
10205
|
+
var MAX_CLOSEST_DIFF_REGION_LINES = 80;
|
|
10206
|
+
var MAX_CLOSEST_DIFF_LINES = 30;
|
|
9677
10207
|
var RELAXED_MATCH_WORK_BUDGET = 1e6;
|
|
9678
10208
|
function normalizeForFuzzyMatch(text) {
|
|
9679
10209
|
return buildFuzzyIndexMap(text).normalized;
|
|
@@ -9904,6 +10434,9 @@ function reindentNewText(newText, qLines, fileLines, window) {
|
|
|
9904
10434
|
nIdx.push(i);
|
|
9905
10435
|
}
|
|
9906
10436
|
if (nIdx.length === 0) return newText;
|
|
10437
|
+
if (qIdx.length === nIdx.length && qIdx.every((q, pos) => qLines[q].trim() === newLines[nIdx[pos]].trim()) && qIdx.some((q, pos) => leadingWhitespace(qLines[q]) !== leadingWhitespace(newLines[nIdx[pos]]))) {
|
|
10438
|
+
return newText;
|
|
10439
|
+
}
|
|
9907
10440
|
const counterpart = /* @__PURE__ */ new Map();
|
|
9908
10441
|
let p = 0;
|
|
9909
10442
|
while (p < qIdx.length && p < nIdx.length && qLines[qIdx[p]].trim() === newLines[nIdx[p]].trim()) {
|
|
@@ -10021,7 +10554,8 @@ function describeClosestRegion(content, oldText) {
|
|
|
10021
10554
|
let matched = 0;
|
|
10022
10555
|
let fi = i;
|
|
10023
10556
|
let firstDiff;
|
|
10024
|
-
|
|
10557
|
+
let qi = firstNonBlank;
|
|
10558
|
+
for (; qi < qLines.length; qi++) {
|
|
10025
10559
|
budget -= 1;
|
|
10026
10560
|
if (budget < 0) {
|
|
10027
10561
|
return `Closest-region diagnostics were skipped because the file and the quoted block are too repetitive to compare within the relaxed-match budget \u2014 quote a larger region with distinguishing context lines and retry.`;
|
|
@@ -10030,24 +10564,102 @@ function describeClosestRegion(content, oldText) {
|
|
|
10030
10564
|
if (!q) continue;
|
|
10031
10565
|
while (fi < fileLines.length && !fileLines[fi].trim()) fi++;
|
|
10032
10566
|
if (fi >= fileLines.length || fileLines[fi].trim() !== q) {
|
|
10033
|
-
firstDiff = { fileNo: fi + 1, fileLine: fileLines[fi] ?? "<end of file>", qLine: qLines[qi] };
|
|
10567
|
+
firstDiff = { fileNo: fi + 1, fileLine: fileLines[fi] ?? "<end of file>", qLine: qLines[qi], qIdx: qi };
|
|
10034
10568
|
break;
|
|
10035
10569
|
}
|
|
10036
10570
|
matched++;
|
|
10037
10571
|
fi++;
|
|
10038
10572
|
}
|
|
10039
|
-
if (!best || matched > best.matched) best = { start: i, matched, firstDiff };
|
|
10573
|
+
if (!best || matched > best.matched) best = { start: i, walkEnd: fi, matched, firstDiff };
|
|
10040
10574
|
}
|
|
10041
10575
|
const total = qLines.filter((line) => line.trim() !== "").length;
|
|
10042
10576
|
if (!best) {
|
|
10043
10577
|
return `No line matching ${JSON.stringify(clip(anchor))} exists in this file \u2014 the text may live in a different file, or the file changed since it was read.`;
|
|
10044
10578
|
}
|
|
10045
|
-
const
|
|
10579
|
+
const regionStart = best.start + 1;
|
|
10580
|
+
let regionEnd = Math.max(regionStart, best.walkEnd + (best.firstDiff ? 1 : 0));
|
|
10581
|
+
const parts = [];
|
|
10582
|
+
let alignment = "unknown";
|
|
10583
|
+
let shiftDelta = 0;
|
|
10046
10584
|
if (best.firstDiff) {
|
|
10047
10585
|
parts.push(`First difference at line ${best.firstDiff.fileNo}: file has ${JSON.stringify(clip(best.firstDiff.fileLine.trim()))}, edit expected ${JSON.stringify(clip(best.firstDiff.qLine.trim()))}.`);
|
|
10586
|
+
const suffixIdx = [];
|
|
10587
|
+
for (let s = best.firstDiff.qIdx + 1; s < qLines.length && suffixIdx.length < 3; s++) {
|
|
10588
|
+
if (qLines[s].trim()) suffixIdx.push(s);
|
|
10589
|
+
}
|
|
10590
|
+
if (suffixIdx.length === 0) {
|
|
10591
|
+
alignment = "aligned";
|
|
10592
|
+
} else {
|
|
10593
|
+
budget -= 20;
|
|
10594
|
+
const nextNonBlank = (from) => {
|
|
10595
|
+
let i = from;
|
|
10596
|
+
while (i < fileLines.length && !fileLines[i].trim()) i++;
|
|
10597
|
+
return i;
|
|
10598
|
+
};
|
|
10599
|
+
const tryAlign = (fileStart) => {
|
|
10600
|
+
let score = 0;
|
|
10601
|
+
let fi = fileStart;
|
|
10602
|
+
for (const s of suffixIdx) {
|
|
10603
|
+
fi = nextNonBlank(fi);
|
|
10604
|
+
if (fi >= fileLines.length || fileLines[fi].trim() !== qLines[s].trim()) break;
|
|
10605
|
+
score++;
|
|
10606
|
+
fi++;
|
|
10607
|
+
}
|
|
10608
|
+
return { score, endFileIdx: fi };
|
|
10609
|
+
};
|
|
10610
|
+
const atSameLength = tryAlign(best.walkEnd + 1);
|
|
10611
|
+
if (atSameLength.score === suffixIdx.length) {
|
|
10612
|
+
alignment = "aligned";
|
|
10613
|
+
regionEnd = Math.max(regionEnd, Math.min(atSameLength.endFileIdx, best.start + MAX_CLOSEST_DIFF_REGION_LINES));
|
|
10614
|
+
} else {
|
|
10615
|
+
const diffQuoteLine = best.firstDiff.qLine.trim();
|
|
10616
|
+
const nextFile = nextNonBlank(best.walkEnd + 1);
|
|
10617
|
+
const quoteSkipsAFileLine = nextFile < fileLines.length && fileLines[nextFile].trim() === diffQuoteLine;
|
|
10618
|
+
const shiftedCandidates = [best.walkEnd - 1, best.walkEnd, best.walkEnd + 2, best.walkEnd + 3];
|
|
10619
|
+
const shiftedResults = shiftedCandidates.map((candidate) => tryAlign(candidate));
|
|
10620
|
+
let bestShiftedIndex = 0;
|
|
10621
|
+
for (let index = 1; index < shiftedResults.length; index++) {
|
|
10622
|
+
if (shiftedResults[index].score > shiftedResults[bestShiftedIndex].score) bestShiftedIndex = index;
|
|
10623
|
+
}
|
|
10624
|
+
const bestShifted = shiftedResults[bestShiftedIndex];
|
|
10625
|
+
if (quoteSkipsAFileLine) {
|
|
10626
|
+
alignment = "shifted";
|
|
10627
|
+
shiftDelta = nextFile - best.walkEnd;
|
|
10628
|
+
const throughSuffix = tryAlign(nextFile + 1);
|
|
10629
|
+
const end = throughSuffix.score > 0 ? throughSuffix.endFileIdx : nextFile + 1;
|
|
10630
|
+
regionEnd = Math.max(regionEnd, Math.min(end, best.start + MAX_CLOSEST_DIFF_REGION_LINES));
|
|
10631
|
+
} else if (bestShifted.score > atSameLength.score && bestShifted.score === suffixIdx.length) {
|
|
10632
|
+
alignment = "shifted";
|
|
10633
|
+
shiftDelta = shiftedCandidates[bestShiftedIndex] - (best.walkEnd + 1);
|
|
10634
|
+
regionEnd = Math.max(regionEnd, Math.min(bestShifted.endFileIdx, best.start + MAX_CLOSEST_DIFF_REGION_LINES));
|
|
10635
|
+
} else if (atSameLength.score > 0) {
|
|
10636
|
+
alignment = "aligned";
|
|
10637
|
+
regionEnd = Math.max(regionEnd, Math.min(atSameLength.endFileIdx, best.start + MAX_CLOSEST_DIFF_REGION_LINES));
|
|
10638
|
+
}
|
|
10639
|
+
}
|
|
10640
|
+
}
|
|
10641
|
+
if (alignment === "shifted") {
|
|
10642
|
+
parts.push("The remainder of the quote re-aligns only at a SHIFTED position \u2014 an extra, missing, or duplicated line changed the line count; fixing the quoted difference line alone cannot repair the quote.");
|
|
10643
|
+
} else if (alignment === "unknown") {
|
|
10644
|
+
parts.push("The remainder of the quote does not re-align within a few lines of the difference \u2014 the quote is likely stale; re-read the region and re-quote it.");
|
|
10645
|
+
}
|
|
10646
|
+
const quoteSpanLines = qLines.length - firstNonBlank + (alignment === "shifted" ? shiftDelta : 0);
|
|
10647
|
+
regionEnd = Math.max(regionEnd, Math.min(best.start + quoteSpanLines, best.start + MAX_CLOSEST_DIFF_REGION_LINES));
|
|
10048
10648
|
} else {
|
|
10049
10649
|
parts.push("All non-empty lines match by trimmed content \u2014 the mismatch is leading whitespace or blank-line placement.");
|
|
10050
10650
|
}
|
|
10651
|
+
parts.unshift(`Closest match: file lines ${regionStart}\u2013${regionEnd} (${best.matched}/${total} lines matched by trimmed content).`);
|
|
10652
|
+
if (best.firstDiff && alignment !== "unknown" && regionEnd - regionStart + 1 <= MAX_CLOSEST_DIFF_REGION_LINES) {
|
|
10653
|
+
const regionText = fileLines.slice(best.start, Math.min(regionEnd, fileLines.length)).join("\n");
|
|
10654
|
+
const { diff } = generateDiffString(regionText, oldText);
|
|
10655
|
+
const body = diff.split("\n").slice(3);
|
|
10656
|
+
if (body.length > 0) {
|
|
10657
|
+
const shown = body.slice(0, MAX_CLOSEST_DIFF_LINES);
|
|
10658
|
+
const suffix = body.length > shown.length ? ["\u2026"] : [];
|
|
10659
|
+
parts.push(`Diff of the region against the quote (- file, + quote):
|
|
10660
|
+
${[...shown, ...suffix].join("\n")}`);
|
|
10661
|
+
}
|
|
10662
|
+
}
|
|
10051
10663
|
return parts.join(" ");
|
|
10052
10664
|
}
|
|
10053
10665
|
function generateDiffString(oldContent, newContent, contextLines = 4) {
|
|
@@ -13659,13 +14271,14 @@ Focus: ${request.question.trim()}` : ""}` },
|
|
|
13659
14271
|
if (request.signal?.aborted) throw new Error("Operation aborted");
|
|
13660
14272
|
const data = await fs2.readFile(request.absolutePath, "base64");
|
|
13661
14273
|
if (request.signal?.aborted) throw new Error("Operation aborted");
|
|
13662
|
-
|
|
14274
|
+
const readBytes = Math.floor(data.length * 3 / 4) - (data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0);
|
|
14275
|
+
if (readBytes > 0 && readBytes <= cap && data) return {
|
|
13663
14276
|
content: [
|
|
13664
14277
|
{ type: "text", text: `Read ${request.kind} file ${request.displayPath} [${request.mimeType}]. Original media attached for the current model.${request.question?.trim() ? `
|
|
13665
14278
|
Focus: ${request.question.trim()}` : ""}` },
|
|
13666
14279
|
{ type: request.kind, data, mimeType: request.mimeType }
|
|
13667
14280
|
],
|
|
13668
|
-
details: { understanding: { purpose, kind: request.kind, mimeType: request.mimeType, agentModel: getModelLabel(agentModel), routeSource: "current_model", inputMode: "nativeAttachment", attachmentBytes:
|
|
14281
|
+
details: { understanding: { purpose, kind: request.kind, mimeType: request.mimeType, agentModel: getModelLabel(agentModel), routeSource: "current_model", inputMode: "nativeAttachment", attachmentBytes: readBytes } },
|
|
13669
14282
|
isError: false
|
|
13670
14283
|
};
|
|
13671
14284
|
}
|
|
@@ -16702,6 +17315,9 @@ export {
|
|
|
16702
17315
|
registerInteractionHandler,
|
|
16703
17316
|
hasInteractionHandler,
|
|
16704
17317
|
requestInteraction,
|
|
17318
|
+
AttentionCoordinator,
|
|
17319
|
+
interactionAttentionReason,
|
|
17320
|
+
interactionAttentionResolution,
|
|
16705
17321
|
AGENTS_CONTEXT_FILE_NAME,
|
|
16706
17322
|
buildProjectContextSection,
|
|
16707
17323
|
attachAgentEventsToSessionTrace,
|