@cydm/pie 2.6.5 → 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 +61 -6
- 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-5Z4COIKQ.js → cli-QAILYGLM.js} +54 -34
- package/dist/builtin/skills/skill-creator/SKILL.md +7 -8
- package/dist/chunks/{chunk-5OXH6NUH.js → chunk-G5B6D35K.js} +298 -6
- package/dist/chunks/{chunk-QKSK5K5N.js → chunk-H6BAM2N4.js} +739 -164
- package/dist/chunks/{chunk-AETQBDEG.js → chunk-KEARZX6F.js} +1 -1
- package/dist/chunks/{chunk-TFRQE7UQ.js → chunk-OWSXALCG.js} +1 -1
- package/dist/chunks/{src-Z4KWMX6E.js → src-ZJXBS2DB.js} +2 -2
- package/dist/chunks/{test-stream-3FG7FHHU.js → test-stream-TI2S25NY.js} +1 -1
- package/dist/cli.js +204 -101
- 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) {
|
|
@@ -1750,10 +1836,13 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1750
1836
|
const terminalStopReason = lastAssistant?.stopReason;
|
|
1751
1837
|
const terminalError = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
|
|
1752
1838
|
const collectedResponse = collectLastAssistantText(messages) || void 0;
|
|
1753
|
-
const streamInterrupted = terminalStopReason === "error" || terminalStopReason === "aborted" || terminalError || !collectedResponse && terminalStopReason !== "stop";
|
|
1839
|
+
const streamInterrupted = terminalStopReason === "error" || terminalStopReason === "aborted" || terminalError || !collectedResponse && terminalStopReason !== "stop" || terminalStopReason === "length" && !messageHasOwnText(lastAssistant);
|
|
1754
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
|
+
}
|
|
1755
1844
|
const detail = terminalError ? `: ${terminalError.slice(0, 200)}` : "";
|
|
1756
|
-
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}`);
|
|
1757
1846
|
}
|
|
1758
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.]";
|
|
1759
1848
|
if (!collectedResponse) {
|
|
@@ -1911,25 +2000,6 @@ function normalizeTaskDefs(args) {
|
|
|
1911
2000
|
return normalized;
|
|
1912
2001
|
}
|
|
1913
2002
|
var DEFAULT_MAX_CONCURRENT_SUBAGENTS = 4;
|
|
1914
|
-
var MAX_TRANSIENT_RETRIES = 2;
|
|
1915
|
-
var TRANSIENT_RETRY_BACKOFF_MS = 2e3;
|
|
1916
|
-
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;
|
|
1917
|
-
function isTransientNetworkError(message) {
|
|
1918
|
-
return TRANSIENT_ERROR_PATTERN.test(message);
|
|
1919
|
-
}
|
|
1920
|
-
function sleep(ms, signal) {
|
|
1921
|
-
return new Promise((resolve3) => {
|
|
1922
|
-
const timer = setTimeout(resolve3, ms);
|
|
1923
|
-
signal?.addEventListener(
|
|
1924
|
-
"abort",
|
|
1925
|
-
() => {
|
|
1926
|
-
clearTimeout(timer);
|
|
1927
|
-
resolve3();
|
|
1928
|
-
},
|
|
1929
|
-
{ once: true }
|
|
1930
|
-
);
|
|
1931
|
-
});
|
|
1932
|
-
}
|
|
1933
2003
|
async function runWithConcurrencyLimit(jobs, limit) {
|
|
1934
2004
|
let next = 0;
|
|
1935
2005
|
const workers = Array.from({ length: Math.max(1, Math.min(limit, jobs.length)) }, async () => {
|
|
@@ -2018,24 +2088,23 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
2018
2088
|
toolContext.reportProgress?.({ message: `Launching ${tasks.length} parallel subagents...`, increment: 5 });
|
|
2019
2089
|
const startTime = Date.now();
|
|
2020
2090
|
const runTask = async (task) => {
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
}
|
|
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);
|
|
2039
2108
|
};
|
|
2040
2109
|
await runWithConcurrencyLimit(
|
|
2041
2110
|
tasks.map((task) => () => runTask(task)),
|
|
@@ -4987,6 +5056,163 @@ function createSessionManager(options) {
|
|
|
4987
5056
|
return new SessionManager(options);
|
|
4988
5057
|
}
|
|
4989
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
|
+
|
|
4990
5216
|
// ../../packages/agent-framework/src/session/controller/auto-compact-policy.ts
|
|
4991
5217
|
function defaultSetTimeout(callback, delay) {
|
|
4992
5218
|
return setTimeout(callback, delay);
|
|
@@ -5086,6 +5312,8 @@ var AgentSessionAutoContinueController = class {
|
|
|
5086
5312
|
}
|
|
5087
5313
|
options;
|
|
5088
5314
|
autoContinueTimer;
|
|
5315
|
+
generation = 0;
|
|
5316
|
+
cancelExecutionWait;
|
|
5089
5317
|
pendingAutoContinue;
|
|
5090
5318
|
resolvePendingAutoContinue;
|
|
5091
5319
|
get hasPendingAutoContinue() {
|
|
@@ -5101,21 +5329,30 @@ var AgentSessionAutoContinueController = class {
|
|
|
5101
5329
|
this.ensurePending();
|
|
5102
5330
|
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout2;
|
|
5103
5331
|
const snapshot = messages.slice();
|
|
5332
|
+
const generation = this.generation;
|
|
5104
5333
|
this.autoContinueTimer = setTimer(() => {
|
|
5105
5334
|
this.autoContinueTimer = void 0;
|
|
5106
|
-
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
|
+
});
|
|
5107
5342
|
}, 0);
|
|
5108
5343
|
}
|
|
5109
5344
|
cancel() {
|
|
5110
|
-
|
|
5111
|
-
|
|
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;
|
|
5112
5350
|
}
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
this.autoContinueTimer = void 0;
|
|
5351
|
+
this.cancelExecutionWait?.();
|
|
5352
|
+
this.cancelExecutionWait = void 0;
|
|
5116
5353
|
this.resolvePending();
|
|
5117
5354
|
}
|
|
5118
|
-
async maybeScheduleAutoContinue(messages) {
|
|
5355
|
+
async maybeScheduleAutoContinue(messages, generation) {
|
|
5119
5356
|
if (!this.options.getAutoContinueMessage || this.options.getDisposed() || this.options.getRuntimeGuardTriggered() || this.options.isBlockedByRuntimePolicy() || this.options.hasQueuedMessages() || this.options.isStreaming()) {
|
|
5120
5357
|
if (this.options.hasQueuedMessages()) {
|
|
5121
5358
|
this.options.emit({ type: "auto_continue_skipped", reason: "queued_messages" });
|
|
@@ -5134,6 +5371,7 @@ var AgentSessionAutoContinueController = class {
|
|
|
5134
5371
|
messages,
|
|
5135
5372
|
hasQueuedMessages: this.options.hasQueuedMessages()
|
|
5136
5373
|
});
|
|
5374
|
+
if (generation !== this.generation || this.options.getDisposed() || this.options.getRuntimeGuardTriggered()) return;
|
|
5137
5375
|
const message = result && "role" in result ? result : result?.message;
|
|
5138
5376
|
const reason = result && "role" in result ? void 0 : result?.reason;
|
|
5139
5377
|
if (!message) {
|
|
@@ -5148,9 +5386,11 @@ var AgentSessionAutoContinueController = class {
|
|
|
5148
5386
|
this.options.agent.followUp(message);
|
|
5149
5387
|
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout2;
|
|
5150
5388
|
await new Promise((resolve3) => {
|
|
5389
|
+
this.cancelExecutionWait = resolve3;
|
|
5151
5390
|
this.autoContinueTimer = setTimer(() => {
|
|
5152
5391
|
this.autoContinueTimer = void 0;
|
|
5153
|
-
|
|
5392
|
+
this.cancelExecutionWait = void 0;
|
|
5393
|
+
if (generation !== this.generation || this.options.getDisposed()) {
|
|
5154
5394
|
resolve3();
|
|
5155
5395
|
return;
|
|
5156
5396
|
}
|
|
@@ -5203,7 +5443,7 @@ async function runAgentSessionCompaction(params) {
|
|
|
5203
5443
|
throw new Error("No compaction handler configured");
|
|
5204
5444
|
}
|
|
5205
5445
|
const abortedResult = () => ({ aborted: true });
|
|
5206
|
-
const isAborted = () => params.signal?.aborted === true;
|
|
5446
|
+
const isAborted = () => params.signal?.aborted === true || params.isCurrent?.() === false;
|
|
5207
5447
|
params.emit({ type: "auto_compaction_start", reason: params.reason, willRetry: params.willRetry });
|
|
5208
5448
|
try {
|
|
5209
5449
|
if (isAborted()) {
|
|
@@ -5344,7 +5584,7 @@ async function runPreRequestAutoCompact(params) {
|
|
|
5344
5584
|
try {
|
|
5345
5585
|
await params.runAutoCompact();
|
|
5346
5586
|
} catch {
|
|
5347
|
-
params.setRecoveryState("none");
|
|
5587
|
+
if (params.isCurrent?.() !== false) params.setRecoveryState("none");
|
|
5348
5588
|
}
|
|
5349
5589
|
}
|
|
5350
5590
|
|
|
@@ -5518,6 +5758,7 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5518
5758
|
}
|
|
5519
5759
|
options;
|
|
5520
5760
|
dispatchTimer;
|
|
5761
|
+
generation = 0;
|
|
5521
5762
|
pendingDispatch;
|
|
5522
5763
|
resolvePendingDispatch;
|
|
5523
5764
|
get hasPendingDispatch() {
|
|
@@ -5532,25 +5773,32 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5532
5773
|
}
|
|
5533
5774
|
this.ensurePending();
|
|
5534
5775
|
const setTimer = this.options.retry?.setTimeout ?? defaultSetTimeout3;
|
|
5776
|
+
const generation = this.generation;
|
|
5535
5777
|
this.dispatchTimer = setTimer(() => {
|
|
5536
5778
|
this.dispatchTimer = void 0;
|
|
5537
|
-
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
|
+
});
|
|
5538
5784
|
}, delayMs);
|
|
5539
5785
|
}
|
|
5540
5786
|
cancel() {
|
|
5541
|
-
|
|
5542
|
-
|
|
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;
|
|
5543
5792
|
}
|
|
5544
|
-
const clearTimer = this.options.retry?.clearTimeout ?? defaultClearTimeout3;
|
|
5545
|
-
clearTimer(this.dispatchTimer);
|
|
5546
|
-
this.dispatchTimer = void 0;
|
|
5547
5793
|
this.resolvePending();
|
|
5548
5794
|
}
|
|
5549
5795
|
async dispatchNext() {
|
|
5796
|
+
const generation = this.generation;
|
|
5550
5797
|
if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
|
|
5551
5798
|
return false;
|
|
5552
5799
|
}
|
|
5553
5800
|
await this.options.agent.waitForIdle();
|
|
5801
|
+
if (generation !== this.generation) return false;
|
|
5554
5802
|
if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
|
|
5555
5803
|
this.schedule(100);
|
|
5556
5804
|
return false;
|
|
@@ -5565,6 +5813,7 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5565
5813
|
cwd: this.options.cwd,
|
|
5566
5814
|
queuedInput: cloneQueuedInput(queuedInput)
|
|
5567
5815
|
});
|
|
5816
|
+
if (generation !== this.generation) return false;
|
|
5568
5817
|
const dispatchDecision = typeof decision === "boolean" ? { allowed: decision } : decision ?? { allowed: true };
|
|
5569
5818
|
if (!dispatchDecision.allowed) {
|
|
5570
5819
|
this.options.emit({
|
|
@@ -5578,6 +5827,7 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5578
5827
|
return false;
|
|
5579
5828
|
}
|
|
5580
5829
|
await this.options.runPreRequestAutoCompact();
|
|
5830
|
+
if (generation !== this.generation) return false;
|
|
5581
5831
|
if (this.options.getDisposed() || this.options.isStreaming() || this.options.isBlockedByRuntimePolicy()) {
|
|
5582
5832
|
this.schedule(100);
|
|
5583
5833
|
return false;
|
|
@@ -5601,6 +5851,7 @@ var AgentSessionQueueDispatcher = class {
|
|
|
5601
5851
|
clearEditor: dispatched.clearEditor,
|
|
5602
5852
|
visibility: dispatched.visibility
|
|
5603
5853
|
});
|
|
5854
|
+
if (generation !== this.generation) return false;
|
|
5604
5855
|
if (!prepared.handled) {
|
|
5605
5856
|
if (prepared.systemPrompt !== void 0) {
|
|
5606
5857
|
this.options.agent.setSystemPrompt(prepared.systemPrompt);
|
|
@@ -6015,9 +6266,14 @@ function classifyControllerRuntimeError(params) {
|
|
|
6015
6266
|
return createFailureClassification({ errorMessage, phase: params.phase }, params.retry, params.classifyFailure);
|
|
6016
6267
|
}
|
|
6017
6268
|
async function runAgentSessionRuntimePhase(params) {
|
|
6269
|
+
const startedGeneration = params.getCurrentGeneration?.();
|
|
6270
|
+
const isStale = () => params.getCurrentGeneration !== void 0 && startedGeneration !== void 0 && params.getCurrentGeneration() !== startedGeneration;
|
|
6018
6271
|
try {
|
|
6019
6272
|
return await params.action();
|
|
6020
6273
|
} catch (error) {
|
|
6274
|
+
if (isStale()) {
|
|
6275
|
+
throw error;
|
|
6276
|
+
}
|
|
6021
6277
|
const failure = classifyControllerRuntimeError({
|
|
6022
6278
|
error,
|
|
6023
6279
|
phase: params.phase,
|
|
@@ -6250,6 +6506,7 @@ async function runAgentSessionControllerAutoCompact(params) {
|
|
|
6250
6506
|
reason: params.reason,
|
|
6251
6507
|
willRetry: params.willRetry,
|
|
6252
6508
|
signal: params.signal,
|
|
6509
|
+
isCurrent: params.isCurrent,
|
|
6253
6510
|
compaction: params.compaction,
|
|
6254
6511
|
agent: params.agent,
|
|
6255
6512
|
sessionManager: params.sessionManager,
|
|
@@ -6260,6 +6517,7 @@ async function runAgentSessionControllerAutoCompact(params) {
|
|
|
6260
6517
|
markSyncedToAgentState: params.markSyncedToAgentState,
|
|
6261
6518
|
continueTurn: params.continueTurn
|
|
6262
6519
|
});
|
|
6520
|
+
if (params.isCurrent?.() === false) return result;
|
|
6263
6521
|
if (!params.willRetry || result?.aborted) {
|
|
6264
6522
|
const alreadyCancelled = params.getRecoveryState?.() === "cancelled" || params.getDisposed?.() === true;
|
|
6265
6523
|
params.setRecoveryState(result?.aborted && cancelOnAbort ? "cancelled" : "none");
|
|
@@ -6273,11 +6531,12 @@ async function runAgentSessionControllerAutoCompact(params) {
|
|
|
6273
6531
|
params.emit({ type: "turn_settled" });
|
|
6274
6532
|
}
|
|
6275
6533
|
if (!result?.aborted && runAfterAgentEndHook) {
|
|
6276
|
-
|
|
6534
|
+
await params.handleAgentEndHook(params.agent.state.messages.slice());
|
|
6277
6535
|
}
|
|
6278
6536
|
}
|
|
6279
6537
|
return result;
|
|
6280
6538
|
} catch (error) {
|
|
6539
|
+
if (params.isCurrent?.() === false) return { aborted: true };
|
|
6281
6540
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6282
6541
|
if (!params.willRetry && params.reason !== "overflow") {
|
|
6283
6542
|
params.setRecoveryState("none");
|
|
@@ -6285,7 +6544,7 @@ async function runAgentSessionControllerAutoCompact(params) {
|
|
|
6285
6544
|
params.emit({ type: "turn_settled" });
|
|
6286
6545
|
}
|
|
6287
6546
|
if (runAfterAgentEndHook) {
|
|
6288
|
-
|
|
6547
|
+
await params.handleAgentEndHook(params.agent.state.messages.slice());
|
|
6289
6548
|
}
|
|
6290
6549
|
throw error;
|
|
6291
6550
|
}
|
|
@@ -6303,7 +6562,7 @@ async function runAgentSessionControllerAutoCompact(params) {
|
|
|
6303
6562
|
params.emit({ type: "turn_settled" });
|
|
6304
6563
|
throw error;
|
|
6305
6564
|
} finally {
|
|
6306
|
-
params.clearPendingFailure();
|
|
6565
|
+
if (params.isCurrent?.() !== false) params.clearPendingFailure();
|
|
6307
6566
|
}
|
|
6308
6567
|
}
|
|
6309
6568
|
|
|
@@ -6324,8 +6583,22 @@ var AgentSessionController = class {
|
|
|
6324
6583
|
unsubscribeSemantic;
|
|
6325
6584
|
lastSyncedMessageCount = 0;
|
|
6326
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();
|
|
6327
6599
|
baseSystemPrompt;
|
|
6328
6600
|
prepareTurn;
|
|
6601
|
+
onExecutionEnd;
|
|
6329
6602
|
afterAgentEnd;
|
|
6330
6603
|
getAutoCompactDecision;
|
|
6331
6604
|
classifyFailure;
|
|
@@ -6340,6 +6613,7 @@ var AgentSessionController = class {
|
|
|
6340
6613
|
this.baseSystemPrompt = options.baseSystemPrompt ?? this.agent.state.systemPrompt;
|
|
6341
6614
|
this.prepareTurn = options.prepareTurn;
|
|
6342
6615
|
this.afterAgentEnd = options.afterAgentEnd;
|
|
6616
|
+
this.onExecutionEnd = options.onExecutionEnd;
|
|
6343
6617
|
this.getAutoCompactDecision = options.getAutoCompactDecision;
|
|
6344
6618
|
this.classifyFailure = options.classifyFailure;
|
|
6345
6619
|
if (options.onEvent) {
|
|
@@ -6417,7 +6691,7 @@ var AgentSessionController = class {
|
|
|
6417
6691
|
return this.agent.state.isStreaming;
|
|
6418
6692
|
}
|
|
6419
6693
|
get hasQueuedMessages() {
|
|
6420
|
-
return this.queue.hasQueuedMessages;
|
|
6694
|
+
return this.queue.hasQueuedMessages || this.agent.hasQueuedMessages();
|
|
6421
6695
|
}
|
|
6422
6696
|
get pendingMessageCount() {
|
|
6423
6697
|
return this.queue.length;
|
|
@@ -6449,18 +6723,30 @@ var AgentSessionController = class {
|
|
|
6449
6723
|
};
|
|
6450
6724
|
}
|
|
6451
6725
|
subscribe(listener) {
|
|
6726
|
+
if (this.disposed) return () => {
|
|
6727
|
+
};
|
|
6452
6728
|
this.listeners.add(listener);
|
|
6453
6729
|
return () => this.listeners.delete(listener);
|
|
6454
6730
|
}
|
|
6455
6731
|
syncFromActiveSession(messages) {
|
|
6456
|
-
|
|
6457
|
-
this.
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
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
|
+
}
|
|
6464
6750
|
}
|
|
6465
6751
|
markSyncedToAgentState() {
|
|
6466
6752
|
this.lastSyncedMessageCount = this.agent.state.messages.length;
|
|
@@ -6478,7 +6764,12 @@ var AgentSessionController = class {
|
|
|
6478
6764
|
this.agent.setTools(context.tools);
|
|
6479
6765
|
}
|
|
6480
6766
|
}
|
|
6481
|
-
|
|
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;
|
|
6482
6773
|
const messages = normalizePromptInput(input);
|
|
6483
6774
|
if (this.isRecoveryActive()) {
|
|
6484
6775
|
this.queue.enqueue(messages, options?.streamingBehavior ?? "followUp", {
|
|
@@ -6503,8 +6794,9 @@ var AgentSessionController = class {
|
|
|
6503
6794
|
throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
|
|
6504
6795
|
}
|
|
6505
6796
|
await this.runPreRequestAutoCompactIfNeeded();
|
|
6797
|
+
if (generation !== this.generation || this.disposed) return;
|
|
6506
6798
|
const prepared = await this.runRuntimePhase("prepare", () => this.preparePromptMessages(messages, options));
|
|
6507
|
-
if (prepared.handled) {
|
|
6799
|
+
if (generation !== this.generation || this.disposed || prepared.handled) {
|
|
6508
6800
|
return;
|
|
6509
6801
|
}
|
|
6510
6802
|
this.recordLifecycle({ type: "prompt_started" });
|
|
@@ -6512,45 +6804,66 @@ var AgentSessionController = class {
|
|
|
6512
6804
|
this.agent.setSystemPrompt(prepared.systemPrompt);
|
|
6513
6805
|
}
|
|
6514
6806
|
await this.runRuntimePhase("request", () => this.agent.prompt(prepared.messages));
|
|
6807
|
+
if (generation !== this.generation || this.disposed) return;
|
|
6515
6808
|
await this.runRuntimePhase("post_turn", () => this.syncNewAgentMessages());
|
|
6516
6809
|
}
|
|
6517
|
-
|
|
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;
|
|
6518
6815
|
if (this.isStreaming) {
|
|
6519
6816
|
throw new Error("Agent is already processing. Wait for completion before continuing.");
|
|
6520
6817
|
}
|
|
6521
6818
|
await this.runPreRequestAutoCompactIfNeeded();
|
|
6819
|
+
if (generation !== this.generation || this.disposed) return;
|
|
6522
6820
|
this.recordLifecycle({ type: "prompt_started" });
|
|
6523
6821
|
await this.runRuntimePhase("request", () => this.agent.continue());
|
|
6822
|
+
if (generation !== this.generation || this.disposed) return;
|
|
6524
6823
|
await this.runRuntimePhase("post_turn", () => this.syncNewAgentMessages());
|
|
6525
6824
|
}
|
|
6526
6825
|
abort() {
|
|
6527
|
-
this.
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
this.
|
|
6535
|
-
|
|
6536
|
-
|
|
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;
|
|
6537
6843
|
}
|
|
6538
|
-
this.agent.abort();
|
|
6539
6844
|
}
|
|
6540
6845
|
waitForIdle() {
|
|
6541
6846
|
return this.agent.waitForIdle();
|
|
6542
6847
|
}
|
|
6543
|
-
|
|
6544
|
-
|
|
6848
|
+
waitForSettled() {
|
|
6849
|
+
return this.convergence.wait();
|
|
6850
|
+
}
|
|
6851
|
+
async waitForRuntimePolicies() {
|
|
6852
|
+
const generation = this.generation;
|
|
6853
|
+
while (!this.disposed && generation === this.generation) {
|
|
6545
6854
|
await this.agent.waitForIdle();
|
|
6546
|
-
if (!this.hasPendingRuntimePolicy()) {
|
|
6855
|
+
if (this.disposed || generation !== this.generation || !this.hasPendingRuntimePolicy()) {
|
|
6547
6856
|
return;
|
|
6548
6857
|
}
|
|
6549
|
-
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()]);
|
|
6550
6859
|
}
|
|
6551
6860
|
}
|
|
6552
6861
|
async runAutoCompact(reason, willRetry = false, options) {
|
|
6553
|
-
|
|
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,
|
|
6554
6867
|
reason,
|
|
6555
6868
|
willRetry,
|
|
6556
6869
|
signal: options?.signal,
|
|
@@ -6559,7 +6872,9 @@ var AgentSessionController = class {
|
|
|
6559
6872
|
sessionManager: this.sessionManager,
|
|
6560
6873
|
cwd: this.cwd,
|
|
6561
6874
|
pendingFailure: this.pendingCompactionFailure,
|
|
6562
|
-
emit: (event) =>
|
|
6875
|
+
emit: (event) => {
|
|
6876
|
+
if (isCurrent()) this.emit(event);
|
|
6877
|
+
},
|
|
6563
6878
|
setRecoveryState: (state) => {
|
|
6564
6879
|
this.recoveryState = state;
|
|
6565
6880
|
},
|
|
@@ -6577,19 +6892,19 @@ var AgentSessionController = class {
|
|
|
6577
6892
|
emitSettled: options?.emitSettled,
|
|
6578
6893
|
runAfterAgentEndHook: options?.runAfterAgentEndHook,
|
|
6579
6894
|
cancelOnAbort: options?.cancelOnAbort
|
|
6580
|
-
});
|
|
6895
|
+
}));
|
|
6581
6896
|
}
|
|
6582
6897
|
dispose() {
|
|
6583
6898
|
if (this.disposed) return;
|
|
6584
6899
|
this.disposed = true;
|
|
6585
|
-
this.
|
|
6586
|
-
this.
|
|
6587
|
-
this.
|
|
6588
|
-
this.autoContinue.cancel();
|
|
6900
|
+
this.generation++;
|
|
6901
|
+
this.pendingEndOperations.clear();
|
|
6902
|
+
this.cancelScheduledWork();
|
|
6589
6903
|
this.unsubscribeAgent?.();
|
|
6590
6904
|
this.unsubscribeSemantic?.();
|
|
6591
|
-
this.listeners.clear();
|
|
6592
6905
|
this.queue.clear();
|
|
6906
|
+
this.convergence.invalidate("dispose");
|
|
6907
|
+
this.listeners.clear();
|
|
6593
6908
|
}
|
|
6594
6909
|
drainQueuedInputsForEditor() {
|
|
6595
6910
|
return this.queue.drainForEditor();
|
|
@@ -6604,10 +6919,16 @@ var AgentSessionController = class {
|
|
|
6604
6919
|
this.queue.replaceText(search, replacement);
|
|
6605
6920
|
}
|
|
6606
6921
|
requestQueueDispatch() {
|
|
6607
|
-
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
|
+
}
|
|
6608
6927
|
}
|
|
6609
6928
|
async runPreRequestAutoCompactIfNeeded() {
|
|
6929
|
+
const generation = this.generation;
|
|
6610
6930
|
return runPreRequestAutoCompact({
|
|
6931
|
+
isCurrent: () => generation === this.generation && !this.disposed,
|
|
6611
6932
|
disposed: this.disposed,
|
|
6612
6933
|
runtimeGuardTriggered: this.runtimeGuard.triggered,
|
|
6613
6934
|
isStreaming: this.isStreaming,
|
|
@@ -6639,11 +6960,11 @@ var AgentSessionController = class {
|
|
|
6639
6960
|
this.queue.markMessageDelivered(event.message);
|
|
6640
6961
|
}
|
|
6641
6962
|
if (event.type === "turn_end") {
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
});
|
|
6963
|
+
this.pendingEndOperations.track(() => this.syncNewAgentMessages().catch((error) => {
|
|
6964
|
+
this.emit({ type: "turn_policy_error", message: String(error) });
|
|
6965
|
+
}));
|
|
6645
6966
|
this.recordLifecycle({ type: "turn_end_success" });
|
|
6646
|
-
|
|
6967
|
+
this.pendingEndOperations.track(() => this.handleTurnEndRuntimePolicies(event.message, event.toolResults).catch((error) => handleTurnEndPolicyThrow((event2) => this.emit(event2), error, {
|
|
6647
6968
|
recoveryWasActive: this.isRecoveryActive(),
|
|
6648
6969
|
cancelPendingRecovery: () => {
|
|
6649
6970
|
this.retryController.cancel();
|
|
@@ -6652,11 +6973,15 @@ var AgentSessionController = class {
|
|
|
6652
6973
|
markRecoveryTerminal: () => {
|
|
6653
6974
|
this.recoveryState = "terminal";
|
|
6654
6975
|
}
|
|
6655
|
-
}));
|
|
6976
|
+
})));
|
|
6656
6977
|
}
|
|
6657
6978
|
if (event.type === "agent_end") {
|
|
6658
6979
|
this.recordLifecycle({ type: "agent_end" });
|
|
6659
|
-
|
|
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
|
+
});
|
|
6660
6985
|
this.emit({ type: "streaming_changed", isStreaming: false });
|
|
6661
6986
|
}
|
|
6662
6987
|
}
|
|
@@ -6712,27 +7037,12 @@ var AgentSessionController = class {
|
|
|
6712
7037
|
});
|
|
6713
7038
|
}
|
|
6714
7039
|
async handleAgentEndHook(messages) {
|
|
7040
|
+
const generation = this.generation;
|
|
6715
7041
|
if (this.disposed || this.runtimeGuard.triggered || this.isBlockedByRuntimePolicy()) {
|
|
6716
7042
|
return;
|
|
6717
7043
|
}
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
try {
|
|
6721
|
-
await this.afterAgentEnd({
|
|
6722
|
-
messages,
|
|
6723
|
-
agent: this.agent,
|
|
6724
|
-
sessionManager: this.sessionManager,
|
|
6725
|
-
cwd: this.cwd,
|
|
6726
|
-
hasQueuedMessages: this.hasQueuedMessages
|
|
6727
|
-
});
|
|
6728
|
-
this.emit({ type: "agent_end_hook_end" });
|
|
6729
|
-
} catch (error) {
|
|
6730
|
-
this.emit({
|
|
6731
|
-
type: "agent_end_hook_end",
|
|
6732
|
-
errorMessage: error instanceof Error ? error.message : String(error)
|
|
6733
|
-
});
|
|
6734
|
-
}
|
|
6735
|
-
}
|
|
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;
|
|
6736
7046
|
if (this.queue.length > 0) {
|
|
6737
7047
|
this.queueDispatcher.schedule();
|
|
6738
7048
|
}
|
|
@@ -6753,6 +7063,7 @@ var AgentSessionController = class {
|
|
|
6753
7063
|
action,
|
|
6754
7064
|
retry: this.retryController.policy,
|
|
6755
7065
|
classifyFailure: this.classifyFailure,
|
|
7066
|
+
getCurrentGeneration: () => this.generation,
|
|
6756
7067
|
emit: (event) => this.emit(event),
|
|
6757
7068
|
setRecoveryState: (state) => {
|
|
6758
7069
|
this.recoveryState = state;
|
|
@@ -6765,8 +7076,14 @@ var AgentSessionController = class {
|
|
|
6765
7076
|
isBlockedByRuntimePolicy() {
|
|
6766
7077
|
return this.recoveryState !== "none" || this.retryController.hasPendingRetry || this.autoCompact.hasPendingAutoCompact;
|
|
6767
7078
|
}
|
|
7079
|
+
cancelScheduledWork() {
|
|
7080
|
+
this.retryController.cancel();
|
|
7081
|
+
this.autoCompact.cancel();
|
|
7082
|
+
this.queueDispatcher.cancel();
|
|
7083
|
+
this.autoContinue.cancel();
|
|
7084
|
+
}
|
|
6768
7085
|
hasPendingRuntimePolicy() {
|
|
6769
|
-
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;
|
|
6770
7087
|
}
|
|
6771
7088
|
scheduleAutoCompact(decision, failure) {
|
|
6772
7089
|
this.recordLifecycle({ type: "auto_compact_scheduled" });
|
|
@@ -6784,10 +7101,7 @@ var AgentSessionController = class {
|
|
|
6784
7101
|
if (this.runtimeGuard.triggered) {
|
|
6785
7102
|
this.runtimeGuard.markTriggered();
|
|
6786
7103
|
}
|
|
6787
|
-
this.
|
|
6788
|
-
this.autoCompact.cancel();
|
|
6789
|
-
this.queueDispatcher.cancel();
|
|
6790
|
-
this.autoContinue.cancel();
|
|
7104
|
+
this.cancelScheduledWork();
|
|
6791
7105
|
this.recoveryState = "terminal";
|
|
6792
7106
|
this.emit({ type: "runtime_guard_triggered", toolName, repeatCount, reason });
|
|
6793
7107
|
this.emit({
|
|
@@ -6798,13 +7112,13 @@ var AgentSessionController = class {
|
|
|
6798
7112
|
this.agent.abort();
|
|
6799
7113
|
}
|
|
6800
7114
|
emit(event) {
|
|
7115
|
+
if (!event.type.startsWith("session_work_") && event.type !== "session_quiescent" && event.type !== "session_invalidated") this.convergence.observe(event);
|
|
6801
7116
|
for (const listener of this.listeners) {
|
|
6802
|
-
listener
|
|
7117
|
+
notifyObserver(listener, event);
|
|
6803
7118
|
}
|
|
6804
7119
|
}
|
|
6805
7120
|
recordLifecycle(input) {
|
|
6806
7121
|
this.lifecycleState = reduceAgentSessionLifecycle(this.lifecycleState, input);
|
|
6807
|
-
void this.lifecycleState.phase;
|
|
6808
7122
|
}
|
|
6809
7123
|
};
|
|
6810
7124
|
|
|
@@ -6987,6 +7301,109 @@ async function requestInteraction(request) {
|
|
|
6987
7301
|
return await currentInteractionHandler(request);
|
|
6988
7302
|
}
|
|
6989
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
|
+
|
|
6990
7407
|
// ../../packages/agent-framework/src/context-files.ts
|
|
6991
7408
|
var AGENTS_CONTEXT_FILE_NAME = "AGENTS.md";
|
|
6992
7409
|
function normalizeContextPath(path4) {
|
|
@@ -8849,14 +9266,23 @@ function createFileObservationState(options = {}) {
|
|
|
8849
9266
|
function coverageSpansFile(coveredRanges, totalLines) {
|
|
8850
9267
|
if (totalLines <= 0) return true;
|
|
8851
9268
|
const merged = mergeLineRanges(coveredRanges);
|
|
8852
|
-
return merged.length
|
|
9269
|
+
return merged.length === 1 && merged[0].startLine <= 1 && merged[0].endLine >= totalLines;
|
|
8853
9270
|
}
|
|
8854
9271
|
function summarize(observed) {
|
|
8855
9272
|
const merged = mergeLineRanges(observed.coveredRanges);
|
|
8856
9273
|
const coveredLines = merged.reduce((total, range2) => total + (range2.endLine - range2.startLine + 1), 0);
|
|
8857
9274
|
let firstUnseenLine;
|
|
8858
9275
|
if (observed.coveredTotalLines > 0 && coveredLines < observed.coveredTotalLines) {
|
|
8859
|
-
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
|
+
}
|
|
8860
9286
|
}
|
|
8861
9287
|
let lastRange;
|
|
8862
9288
|
for (const range2 of observed.readRanges.values()) {
|
|
@@ -8972,22 +9398,32 @@ function createReadTool(cwd, options) {
|
|
|
8972
9398
|
...rootSchema ? { root: rootSchema } : {},
|
|
8973
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." })),
|
|
8974
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." })),
|
|
8975
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." }))
|
|
8976
9403
|
});
|
|
8977
9404
|
const pathResolutionDescription = buildPathResolutionDescription(cwd, options);
|
|
8978
9405
|
return {
|
|
8979
9406
|
name: "read",
|
|
8980
9407
|
label: "read",
|
|
8981
|
-
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.`,
|
|
8982
9409
|
parameters: readSchema,
|
|
8983
9410
|
execute: async (_toolCallId, params, signal) => {
|
|
8984
|
-
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;
|
|
8985
9415
|
if (offset !== void 0 && (!Number.isSafeInteger(offset) || offset < 1 || offset > MAX_CROSS_HOST_LINE_NUMBER)) {
|
|
8986
9416
|
return createReadErrorResult(`Invalid arguments: offset must be a positive safe integer no greater than ${MAX_CROSS_HOST_LINE_NUMBER}.`);
|
|
8987
9417
|
}
|
|
8988
9418
|
if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_CROSS_HOST_LINE_NUMBER)) {
|
|
8989
9419
|
return createReadErrorResult(`Invalid arguments: limit must be a positive safe integer no greater than ${MAX_CROSS_HOST_LINE_NUMBER}.`);
|
|
8990
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
|
+
}
|
|
8991
9427
|
if (offset !== void 0 && limit !== void 0 && offset - 1 + limit > MAX_CROSS_HOST_LINE_NUMBER) {
|
|
8992
9428
|
return createReadErrorResult(`Invalid arguments: offset and limit exceed the ${MAX_CROSS_HOST_LINE_NUMBER}-line cross-host range.`);
|
|
8993
9429
|
}
|
|
@@ -9151,14 +9587,38 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
9151
9587
|
});
|
|
9152
9588
|
return;
|
|
9153
9589
|
}
|
|
9154
|
-
|
|
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
|
+
}
|
|
9155
9621
|
const startLineDisplay = startLine + 1;
|
|
9156
|
-
const range2 = await fs2.readTextRange(absolutePath, {
|
|
9157
|
-
offsetLine: startLine,
|
|
9158
|
-
limitLines: limit,
|
|
9159
|
-
maxOutputLines: DEFAULT_MAX_LINES,
|
|
9160
|
-
maxOutputBytes: DEFAULT_MAX_BYTES
|
|
9161
|
-
}, signal);
|
|
9162
9622
|
if (options?.allowBinary === false) {
|
|
9163
9623
|
const selectedLooksBinary = range2.selectedBytesLookBinary ?? (looksLikeBinaryText(range2.content) || range2.content.includes("\uFFFD"));
|
|
9164
9624
|
const selectedBinary = selectedLooksBinary ? { kind: "document", mimeType: "application/octet-stream" } : void 0;
|
|
@@ -9177,7 +9637,7 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
9177
9637
|
contentHash: range2.contentHash
|
|
9178
9638
|
};
|
|
9179
9639
|
const totalFileLines = range2.totalLines;
|
|
9180
|
-
if (startLine >= range2.totalLines) {
|
|
9640
|
+
if (startLine >= range2.totalLines && !translatedFromTail) {
|
|
9181
9641
|
throw new Error(`Offset ${offset} is beyond end of file (${range2.totalLines} lines total)`);
|
|
9182
9642
|
}
|
|
9183
9643
|
let userLimitedLines;
|
|
@@ -9198,7 +9658,7 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
9198
9658
|
maxLines: DEFAULT_MAX_LINES,
|
|
9199
9659
|
maxBytes: DEFAULT_MAX_BYTES
|
|
9200
9660
|
};
|
|
9201
|
-
const readStartsAtBeginning =
|
|
9661
|
+
const readStartsAtBeginning = startLine === 0;
|
|
9202
9662
|
const readReachedEndOfFile = startLine + range2.selectedLines >= range2.totalLines;
|
|
9203
9663
|
const isCompleteTextKnown = readStartsAtBeginning && readReachedEndOfFile && !truncation.truncated && !truncation.firstLineExceedsLimit;
|
|
9204
9664
|
let callerObservation = fileObservationState;
|
|
@@ -9220,13 +9680,14 @@ Focus: ${question.trim()}` : ""}` },
|
|
|
9220
9680
|
resolve3({
|
|
9221
9681
|
content: [{
|
|
9222
9682
|
type: "text",
|
|
9223
|
-
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.`
|
|
9224
9684
|
}],
|
|
9225
9685
|
details: {
|
|
9226
9686
|
unchangedSincePreviousRead: true,
|
|
9227
9687
|
path: absolutePath,
|
|
9228
9688
|
offset: startLineDisplay,
|
|
9229
|
-
limit
|
|
9689
|
+
limit,
|
|
9690
|
+
...translatedFromTail ? { totalLines: range2.totalLines } : {}
|
|
9230
9691
|
}
|
|
9231
9692
|
});
|
|
9232
9693
|
return;
|
|
@@ -9245,7 +9706,12 @@ Base directory: ${nodePath.dirname(absolutePath)}
|
|
|
9245
9706
|
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
9246
9707
|
const nextOffset = endLineDisplay + 1;
|
|
9247
9708
|
outputText = truncation.content;
|
|
9248
|
-
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") {
|
|
9249
9715
|
outputText += `
|
|
9250
9716
|
|
|
9251
9717
|
[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;
|
|
@@ -9271,18 +9737,38 @@ Base directory: ${nodePath.dirname(absolutePath)}
|
|
|
9271
9737
|
snapshot,
|
|
9272
9738
|
range2.selectedContentHash,
|
|
9273
9739
|
isCompleteTextKnown,
|
|
9274
|
-
{
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
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
|
+
})()
|
|
9282
9760
|
);
|
|
9283
9761
|
if (callerObservation !== fileObservationState) {
|
|
9284
9762
|
callerObservation.recordTextRead(absolutePath, rangeKey, snapshot, range2.selectedContentHash, isCompleteTextKnown);
|
|
9285
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
|
+
}
|
|
9286
9772
|
outputText = sourceHeader + outputText;
|
|
9287
9773
|
const textContent = [{ type: "text", text: outputText }];
|
|
9288
9774
|
if (aborted) return;
|
|
@@ -9661,13 +10147,15 @@ function createWriteTool(cwd, options) {
|
|
|
9661
10147
|
const observedSnapshot = fileObservationState.getCompleteTextSnapshot(absolutePath);
|
|
9662
10148
|
if (!observedSnapshot) {
|
|
9663
10149
|
const summary = fileObservationState.describeObservation(absolutePath);
|
|
9664
|
-
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.";
|
|
9665
10151
|
throw new Error(
|
|
9666
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."
|
|
9667
10153
|
);
|
|
9668
10154
|
}
|
|
9669
10155
|
if (!snapshotsHaveSameContent(observedSnapshot, currentSnapshot)) {
|
|
9670
|
-
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
|
+
);
|
|
9671
10159
|
}
|
|
9672
10160
|
}
|
|
9673
10161
|
await fs2.writeFile(absolutePath, content, "utf-8");
|
|
@@ -9681,7 +10169,7 @@ function createWriteTool(cwd, options) {
|
|
|
9681
10169
|
signal.removeEventListener("abort", onAbort);
|
|
9682
10170
|
}
|
|
9683
10171
|
resolve3({
|
|
9684
|
-
content: [{ type: "text", text: `Successfully wrote ${
|
|
10172
|
+
content: [{ type: "text", text: `Successfully wrote ${writtenStat.size} bytes to ${filePath}` }],
|
|
9685
10173
|
details: void 0
|
|
9686
10174
|
});
|
|
9687
10175
|
} catch (error) {
|
|
@@ -9714,6 +10202,8 @@ function restoreLineEndings(text, ending) {
|
|
|
9714
10202
|
return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
|
|
9715
10203
|
}
|
|
9716
10204
|
var MAX_FUZZY_DIAGNOSTIC_CHARS = 256 * 1024;
|
|
10205
|
+
var MAX_CLOSEST_DIFF_REGION_LINES = 80;
|
|
10206
|
+
var MAX_CLOSEST_DIFF_LINES = 30;
|
|
9717
10207
|
var RELAXED_MATCH_WORK_BUDGET = 1e6;
|
|
9718
10208
|
function normalizeForFuzzyMatch(text) {
|
|
9719
10209
|
return buildFuzzyIndexMap(text).normalized;
|
|
@@ -9944,6 +10434,9 @@ function reindentNewText(newText, qLines, fileLines, window) {
|
|
|
9944
10434
|
nIdx.push(i);
|
|
9945
10435
|
}
|
|
9946
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
|
+
}
|
|
9947
10440
|
const counterpart = /* @__PURE__ */ new Map();
|
|
9948
10441
|
let p = 0;
|
|
9949
10442
|
while (p < qIdx.length && p < nIdx.length && qLines[qIdx[p]].trim() === newLines[nIdx[p]].trim()) {
|
|
@@ -10061,7 +10554,8 @@ function describeClosestRegion(content, oldText) {
|
|
|
10061
10554
|
let matched = 0;
|
|
10062
10555
|
let fi = i;
|
|
10063
10556
|
let firstDiff;
|
|
10064
|
-
|
|
10557
|
+
let qi = firstNonBlank;
|
|
10558
|
+
for (; qi < qLines.length; qi++) {
|
|
10065
10559
|
budget -= 1;
|
|
10066
10560
|
if (budget < 0) {
|
|
10067
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.`;
|
|
@@ -10070,24 +10564,102 @@ function describeClosestRegion(content, oldText) {
|
|
|
10070
10564
|
if (!q) continue;
|
|
10071
10565
|
while (fi < fileLines.length && !fileLines[fi].trim()) fi++;
|
|
10072
10566
|
if (fi >= fileLines.length || fileLines[fi].trim() !== q) {
|
|
10073
|
-
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 };
|
|
10074
10568
|
break;
|
|
10075
10569
|
}
|
|
10076
10570
|
matched++;
|
|
10077
10571
|
fi++;
|
|
10078
10572
|
}
|
|
10079
|
-
if (!best || matched > best.matched) best = { start: i, matched, firstDiff };
|
|
10573
|
+
if (!best || matched > best.matched) best = { start: i, walkEnd: fi, matched, firstDiff };
|
|
10080
10574
|
}
|
|
10081
10575
|
const total = qLines.filter((line) => line.trim() !== "").length;
|
|
10082
10576
|
if (!best) {
|
|
10083
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.`;
|
|
10084
10578
|
}
|
|
10085
|
-
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;
|
|
10086
10584
|
if (best.firstDiff) {
|
|
10087
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));
|
|
10088
10648
|
} else {
|
|
10089
10649
|
parts.push("All non-empty lines match by trimmed content \u2014 the mismatch is leading whitespace or blank-line placement.");
|
|
10090
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
|
+
}
|
|
10091
10663
|
return parts.join(" ");
|
|
10092
10664
|
}
|
|
10093
10665
|
function generateDiffString(oldContent, newContent, contextLines = 4) {
|
|
@@ -16743,6 +17315,9 @@ export {
|
|
|
16743
17315
|
registerInteractionHandler,
|
|
16744
17316
|
hasInteractionHandler,
|
|
16745
17317
|
requestInteraction,
|
|
17318
|
+
AttentionCoordinator,
|
|
17319
|
+
interactionAttentionReason,
|
|
17320
|
+
interactionAttentionResolution,
|
|
16746
17321
|
AGENTS_CONTEXT_FILE_NAME,
|
|
16747
17322
|
buildProjectContextSection,
|
|
16748
17323
|
attachAgentEventsToSessionTrace,
|