@ordewell/core 0.5.4 → 0.5.5
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/{ITerminalRunner-Bd-vAJnw.d.ts → ITerminalRunner-BV9Rd2o9.d.ts} +3 -1
- package/dist/{ITerminalRunner-ByeoLF57.d.mts → ITerminalRunner-C77ZNZS9.d.mts} +3 -1
- package/dist/{ModeResolver-DVJ7HV3k.d.mts → ModeResolver-D-SUFRNF.d.mts} +1 -1
- package/dist/{ModeResolver-Dkig8ghQ.d.ts → ModeResolver-D3XO0fT9.d.ts} +1 -1
- package/dist/{Task-BxQkPlXO.d.mts → Task-Vl5Zq_D-.d.mts} +168 -7
- package/dist/{Task-BxQkPlXO.d.ts → Task-Vl5Zq_D-.d.ts} +168 -7
- package/dist/{chunk-JVMDEHRQ.mjs → chunk-HD2FWPRV.mjs} +72 -37
- package/dist/chunk-HD2FWPRV.mjs.map +1 -0
- package/dist/{chunk-GWPIYDQW.mjs → chunk-KLN7ELXO.mjs} +897 -18
- package/dist/chunk-KLN7ELXO.mjs.map +1 -0
- package/dist/{chunk-XWOUIA6A.mjs → chunk-UUBGVCGJ.mjs} +2 -2
- package/dist/index.d.mts +320 -60
- package/dist/index.d.ts +320 -60
- package/dist/index.js +2497 -672
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1605 -659
- package/dist/index.mjs.map +1 -1
- package/dist/order-labels.d.mts +1 -1
- package/dist/order-labels.d.ts +1 -1
- package/dist/{parsing-CF_grC29.d.ts → parsing-BTP4bwkk.d.ts} +11 -3
- package/dist/{parsing-DRp4dPC0.d.mts → parsing-CDtRSxBY.d.mts} +11 -3
- package/dist/parsing.d.mts +3 -3
- package/dist/parsing.d.ts +3 -3
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +2 -2
- package/dist/plan-utils-BFaPo-IT.d.ts +708 -0
- package/dist/plan-utils-pE4TBwxl.d.mts +708 -0
- package/dist/plan-utils.d.mts +3 -3
- package/dist/plan-utils.d.ts +3 -3
- package/dist/plan-utils.js +668 -7
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +38 -4
- package/dist/testing.d.mts +5 -3
- package/dist/testing.d.ts +5 -3
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +3 -0
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-GWPIYDQW.mjs.map +0 -1
- package/dist/chunk-JVMDEHRQ.mjs.map +0 -1
- package/dist/plan-utils-CkNbqAmS.d.ts +0 -329
- package/dist/plan-utils-CtB3_Ovf.d.mts +0 -329
- /package/dist/{chunk-XWOUIA6A.mjs.map → chunk-UUBGVCGJ.mjs.map} +0 -0
|
@@ -10,7 +10,54 @@ import {
|
|
|
10
10
|
renumberTasks,
|
|
11
11
|
stripTrailingCommas,
|
|
12
12
|
updateTaskInPlan
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-HD2FWPRV.mjs";
|
|
14
|
+
|
|
15
|
+
// src/models/Usage.ts
|
|
16
|
+
function partedPromptUsage(parts) {
|
|
17
|
+
const reported = [parts.uncached, parts.cacheRead, parts.cacheWrite].filter((n) => n !== void 0);
|
|
18
|
+
return {
|
|
19
|
+
...reported.length > 0 ? { inputTokens: reported.reduce((a, b) => a + b, 0) } : {},
|
|
20
|
+
...parts.cacheRead !== void 0 ? { cachedInputTokens: parts.cacheRead } : {}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
var TOKEN_FIELDS = ["inputTokens", "outputTokens", "cachedInputTokens"];
|
|
24
|
+
function addUsage(totals, record) {
|
|
25
|
+
const next = { ...totals };
|
|
26
|
+
for (const field of TOKEN_FIELDS) {
|
|
27
|
+
const reported = record[field];
|
|
28
|
+
if (reported !== void 0) next[field] = (totals[field] ?? 0) + reported;
|
|
29
|
+
}
|
|
30
|
+
if (record.reportedCost) {
|
|
31
|
+
const { amount, currency } = record.reportedCost;
|
|
32
|
+
next.reportedCost = { ...totals.reportedCost, [currency]: (totals.reportedCost?.[currency] ?? 0) + amount };
|
|
33
|
+
}
|
|
34
|
+
return next;
|
|
35
|
+
}
|
|
36
|
+
function addPlannerUsage(usage, record) {
|
|
37
|
+
const next = { ...usage, totals: addUsage(usage.totals, record) };
|
|
38
|
+
if (record.subagentId) {
|
|
39
|
+
next.bySubagent = { ...usage.bySubagent, [record.subagentId]: addUsage(usage.bySubagent?.[record.subagentId] ?? {}, record) };
|
|
40
|
+
return next;
|
|
41
|
+
}
|
|
42
|
+
if (record.inputTokens !== void 0) next.lastPromptTokens = record.inputTokens;
|
|
43
|
+
if (record.contextWindow !== void 0 && record.contextWindow > 0) next.contextWindow = record.contextWindow;
|
|
44
|
+
return next;
|
|
45
|
+
}
|
|
46
|
+
function isMeasured(totals) {
|
|
47
|
+
return totals.inputTokens !== void 0 || totals.outputTokens !== void 0 || totals.cachedInputTokens !== void 0 || Object.keys(totals.reportedCost ?? {}).length > 0;
|
|
48
|
+
}
|
|
49
|
+
function usageLine(usage) {
|
|
50
|
+
const contextFill = plannerContextFill(usage);
|
|
51
|
+
return {
|
|
52
|
+
totals: usage.totals,
|
|
53
|
+
...usage.bySubagent ? { bySubagent: usage.bySubagent } : {},
|
|
54
|
+
...contextFill ? { contextFill } : {}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function plannerContextFill(usage) {
|
|
58
|
+
if (usage.lastPromptTokens === void 0 || usage.contextWindow === void 0 || usage.contextWindow <= 0) return void 0;
|
|
59
|
+
return { usedTokens: usage.lastPromptTokens, windowTokens: usage.contextWindow };
|
|
60
|
+
}
|
|
14
61
|
|
|
15
62
|
// src/services/ModelAllowlistResolver.ts
|
|
16
63
|
function effectiveAllowlist(allowlist, runner, modelsByRunner) {
|
|
@@ -215,37 +262,37 @@ function taskOpsProtocol(executionNote = "") {
|
|
|
215
262
|
'"rearm" puts a failed OR completed task back to pending \u2014 verdict and output summary are cleared, any dependents it had blocked are released, and it may carry field changes (e.g. a corrected "prompt") applied in the same op. A running task cannot be re-armed. Never rearm a task just to relabel it \u2014 only when it should actually run again.'
|
|
216
263
|
];
|
|
217
264
|
}
|
|
218
|
-
function textHasTaskOps(
|
|
219
|
-
return
|
|
265
|
+
function textHasTaskOps(text2) {
|
|
266
|
+
return text2.includes(`"${TASK_OPS_ENVELOPE_KEY}"`);
|
|
220
267
|
}
|
|
221
|
-
function parseTaskOpsJson(
|
|
222
|
-
const { matches, fallback } = extractObjectsWithKey(
|
|
268
|
+
function parseTaskOpsJson(text2) {
|
|
269
|
+
const { matches, fallback } = extractObjectsWithKey(text2, TASK_OPS_ENVELOPE_KEY);
|
|
223
270
|
const candidates = matches.length > 0 ? [...matches].reverse() : [fallback.json];
|
|
224
271
|
let lastError = null;
|
|
225
272
|
for (const json of candidates) {
|
|
226
273
|
try {
|
|
227
|
-
return parseTaskOpsObject(json,
|
|
274
|
+
return parseTaskOpsObject(json, text2);
|
|
228
275
|
} catch (err) {
|
|
229
276
|
if (!(err instanceof PlanParseError)) throw err;
|
|
230
277
|
if (!lastError) lastError = err;
|
|
231
278
|
}
|
|
232
279
|
}
|
|
233
|
-
throw lastError ?? new PlanParseError("No JSON object found in task-ops reply",
|
|
280
|
+
throw lastError ?? new PlanParseError("No JSON object found in task-ops reply", text2);
|
|
234
281
|
}
|
|
235
|
-
function parseTaskOpsObject(json,
|
|
282
|
+
function parseTaskOpsObject(json, text2) {
|
|
236
283
|
let parsed;
|
|
237
284
|
try {
|
|
238
285
|
parsed = JSON.parse(escapeControlCharsInStrings(stripTrailingCommas(json)));
|
|
239
286
|
} catch (err) {
|
|
240
|
-
throw new PlanParseError(`Task-ops JSON is invalid: ${err instanceof Error ? err.message : String(err)}`,
|
|
287
|
+
throw new PlanParseError(`Task-ops JSON is invalid: ${err instanceof Error ? err.message : String(err)}`, text2);
|
|
241
288
|
}
|
|
242
289
|
const ops = parsed.taskOps;
|
|
243
290
|
if (!Array.isArray(ops) || ops.length === 0) {
|
|
244
|
-
throw new PlanParseError('"taskOps" must be a non-empty array',
|
|
291
|
+
throw new PlanParseError('"taskOps" must be a non-empty array', text2);
|
|
245
292
|
}
|
|
246
293
|
for (const op of ops) {
|
|
247
294
|
if (typeof op !== "object" || op === null || typeof op.op !== "string") {
|
|
248
|
-
throw new PlanParseError('Every task op needs an "op" field (update | add | remove | reorder | merge | split)',
|
|
295
|
+
throw new PlanParseError('Every task op needs an "op" field (update | add | remove | reorder | merge | split)', text2);
|
|
249
296
|
}
|
|
250
297
|
}
|
|
251
298
|
return ops;
|
|
@@ -274,6 +321,27 @@ function resolveRef(ref, originalScope, liveScope, handleOwner, handleId, opInde
|
|
|
274
321
|
if (byTitle) return { id: byTitle.id };
|
|
275
322
|
return { error: `task "${s}" not found` };
|
|
276
323
|
}
|
|
324
|
+
function taskOpRefs(op) {
|
|
325
|
+
switch (op.op) {
|
|
326
|
+
case "update":
|
|
327
|
+
case "remove":
|
|
328
|
+
case "split":
|
|
329
|
+
case "rearm":
|
|
330
|
+
return [op.taskId];
|
|
331
|
+
case "merge":
|
|
332
|
+
case "reorder":
|
|
333
|
+
return op.taskIds;
|
|
334
|
+
case "add":
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function refMatchesTask(ref, task) {
|
|
339
|
+
if (typeof ref !== "string" && typeof ref !== "number") return false;
|
|
340
|
+
const s = String(ref).trim();
|
|
341
|
+
if (task.id === s) return true;
|
|
342
|
+
const orderStr = s.startsWith("#") ? s.slice(1) : s;
|
|
343
|
+
return /^\d+$/.test(orderStr) && task.order === Number(orderStr) || task.title === s;
|
|
344
|
+
}
|
|
277
345
|
function refCollidesWithExisting(name, originalFlatAll) {
|
|
278
346
|
if (originalFlatAll.some((t) => t.id === name)) return true;
|
|
279
347
|
const orderStr = name.startsWith("#") ? name.slice(1) : name;
|
|
@@ -796,9 +864,9 @@ function applyTaskOps(currentTasks, ops, runners, catalog) {
|
|
|
796
864
|
}
|
|
797
865
|
|
|
798
866
|
// src/services/researchStepSummary.ts
|
|
799
|
-
function ellipsize(
|
|
800
|
-
const
|
|
801
|
-
return
|
|
867
|
+
function ellipsize(text2, max) {
|
|
868
|
+
const oneLine2 = text2.replace(/\s+/g, " ").trim();
|
|
869
|
+
return oneLine2.length > max ? `${oneLine2.slice(0, max)}\u2026` : oneLine2;
|
|
802
870
|
}
|
|
803
871
|
function summarizeToolCall(tool, argsJson, toolLabel) {
|
|
804
872
|
const display = toolLabel?.trim() || tool;
|
|
@@ -815,8 +883,8 @@ function summarizeToolCall(tool, argsJson, toolLabel) {
|
|
|
815
883
|
try {
|
|
816
884
|
const args = JSON.parse(argsJson);
|
|
817
885
|
const prompt = typeof args.prompt === "string" ? args.prompt : "";
|
|
818
|
-
const
|
|
819
|
-
return `spawn: "${
|
|
886
|
+
const oneLine2 = prompt.replace(/\s+/g, " ").trim();
|
|
887
|
+
return `spawn: "${oneLine2.slice(0, 60)}${oneLine2.length > 60 ? "\u2026" : ""}"`;
|
|
820
888
|
} catch {
|
|
821
889
|
return "spawn_research_agent";
|
|
822
890
|
}
|
|
@@ -918,7 +986,796 @@ function truncateCheckpointSummary(summary) {
|
|
|
918
986
|
return firstLine.slice(0, CHECKPOINT_TRUNCATE_LENGTH - 1) + "\u2026";
|
|
919
987
|
}
|
|
920
988
|
|
|
989
|
+
// src/services/terminalRender.ts
|
|
990
|
+
var ANSI_OR_CTRL_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[()][AB012]|\x1b[=>]|[\x00-\x08\x0b-\x1f\x7f]/g;
|
|
991
|
+
function flattenTerminalOutput(raw) {
|
|
992
|
+
return raw.replace(ANSI_OR_CTRL_RE, "").replace(/[─-▟]/g, "").replace(/\s+/g, "");
|
|
993
|
+
}
|
|
994
|
+
function renderTerminalOutput(raw) {
|
|
995
|
+
const rows = /* @__PURE__ */ new Map();
|
|
996
|
+
let row = 1;
|
|
997
|
+
let col = 1;
|
|
998
|
+
let savedRow = 1;
|
|
999
|
+
let savedCol = 1;
|
|
1000
|
+
const cells = (r) => {
|
|
1001
|
+
let line = rows.get(r);
|
|
1002
|
+
if (!line) {
|
|
1003
|
+
line = [];
|
|
1004
|
+
rows.set(r, line);
|
|
1005
|
+
}
|
|
1006
|
+
return line;
|
|
1007
|
+
};
|
|
1008
|
+
const firstParam = (params, fallback = 1) => params[0] || fallback;
|
|
1009
|
+
const eraseLine = (mode) => {
|
|
1010
|
+
const line = cells(row);
|
|
1011
|
+
if (mode === 2) {
|
|
1012
|
+
rows.set(row, []);
|
|
1013
|
+
} else if (mode === 1) {
|
|
1014
|
+
for (let c = 0; c < col; c++) line[c] = " ";
|
|
1015
|
+
} else {
|
|
1016
|
+
line.length = Math.max(0, col - 1);
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
for (let i = 0; i < raw.length; ) {
|
|
1020
|
+
const ch = raw[i];
|
|
1021
|
+
if (ch === "\x1B") {
|
|
1022
|
+
const kind = raw[i + 1];
|
|
1023
|
+
if (kind === "[") {
|
|
1024
|
+
let end = i + 2;
|
|
1025
|
+
while (end < raw.length) {
|
|
1026
|
+
const code = raw.charCodeAt(end);
|
|
1027
|
+
if (code >= 64 && code <= 126) break;
|
|
1028
|
+
end++;
|
|
1029
|
+
}
|
|
1030
|
+
if (end >= raw.length) break;
|
|
1031
|
+
const final = raw[end];
|
|
1032
|
+
const body = raw.slice(i + 2, end).replace(/^[?<>=!]+/, "");
|
|
1033
|
+
const params = body.split(";").map((p) => Number.parseInt(p, 10) || 0);
|
|
1034
|
+
switch (final) {
|
|
1035
|
+
case "H":
|
|
1036
|
+
case "f":
|
|
1037
|
+
row = params[0] || 1;
|
|
1038
|
+
col = params[1] || 1;
|
|
1039
|
+
break;
|
|
1040
|
+
case "G":
|
|
1041
|
+
col = firstParam(params);
|
|
1042
|
+
break;
|
|
1043
|
+
case "d":
|
|
1044
|
+
row = firstParam(params);
|
|
1045
|
+
break;
|
|
1046
|
+
case "A":
|
|
1047
|
+
row = Math.max(1, row - firstParam(params));
|
|
1048
|
+
break;
|
|
1049
|
+
case "B":
|
|
1050
|
+
row += firstParam(params);
|
|
1051
|
+
break;
|
|
1052
|
+
case "C":
|
|
1053
|
+
col += firstParam(params);
|
|
1054
|
+
break;
|
|
1055
|
+
case "D":
|
|
1056
|
+
col = Math.max(1, col - firstParam(params));
|
|
1057
|
+
break;
|
|
1058
|
+
case "E":
|
|
1059
|
+
row += firstParam(params);
|
|
1060
|
+
col = 1;
|
|
1061
|
+
break;
|
|
1062
|
+
case "F":
|
|
1063
|
+
row = Math.max(1, row - firstParam(params));
|
|
1064
|
+
col = 1;
|
|
1065
|
+
break;
|
|
1066
|
+
case "s":
|
|
1067
|
+
savedRow = row;
|
|
1068
|
+
savedCol = col;
|
|
1069
|
+
break;
|
|
1070
|
+
case "u":
|
|
1071
|
+
row = savedRow;
|
|
1072
|
+
col = savedCol;
|
|
1073
|
+
break;
|
|
1074
|
+
case "K":
|
|
1075
|
+
eraseLine(params[0] || 0);
|
|
1076
|
+
break;
|
|
1077
|
+
case "J":
|
|
1078
|
+
if ((params[0] || 0) === 2 || (params[0] || 0) === 3) rows.clear();
|
|
1079
|
+
break;
|
|
1080
|
+
case "X": {
|
|
1081
|
+
const line = cells(row);
|
|
1082
|
+
for (let c = 0; c < firstParam(params); c++) line[col - 1 + c] = " ";
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
case "P": {
|
|
1086
|
+
cells(row).splice(col - 1, firstParam(params));
|
|
1087
|
+
break;
|
|
1088
|
+
}
|
|
1089
|
+
case "@": {
|
|
1090
|
+
cells(row).splice(col - 1, 0, ...Array(firstParam(params)).fill(" "));
|
|
1091
|
+
break;
|
|
1092
|
+
}
|
|
1093
|
+
default:
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
i = end + 1;
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
if (kind === "]" || kind === "P" || kind === "^" || kind === "_") {
|
|
1100
|
+
let end = i + 2;
|
|
1101
|
+
while (end < raw.length && raw[end] !== "\x07" && !(raw[end] === "\x1B" && raw[end + 1] === "\\")) end++;
|
|
1102
|
+
if (end >= raw.length) break;
|
|
1103
|
+
i = raw[end] === "\x07" ? end + 1 : end + 2;
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
if (kind === "7") {
|
|
1107
|
+
savedRow = row;
|
|
1108
|
+
savedCol = col;
|
|
1109
|
+
} else if (kind === "8") {
|
|
1110
|
+
row = savedRow;
|
|
1111
|
+
col = savedCol;
|
|
1112
|
+
} else if (kind === "c") {
|
|
1113
|
+
rows.clear();
|
|
1114
|
+
row = 1;
|
|
1115
|
+
col = 1;
|
|
1116
|
+
}
|
|
1117
|
+
i += kind === "(" || kind === ")" ? 3 : 2;
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
if (ch === "\r") {
|
|
1121
|
+
col = 1;
|
|
1122
|
+
i++;
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
if (ch === "\n") {
|
|
1126
|
+
row++;
|
|
1127
|
+
i++;
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (ch === "\b") {
|
|
1131
|
+
col = Math.max(1, col - 1);
|
|
1132
|
+
i++;
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
if (ch === " ") {
|
|
1136
|
+
col += 8 - (col - 1) % 8;
|
|
1137
|
+
i++;
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
if (ch < " " || ch === "\x7F") {
|
|
1141
|
+
i++;
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
cells(row)[col - 1] = ch;
|
|
1145
|
+
col++;
|
|
1146
|
+
i++;
|
|
1147
|
+
}
|
|
1148
|
+
const populated = [...rows.keys()].sort((a, b) => a - b);
|
|
1149
|
+
return populated.map((r) => cells(r).join("")).join("\n");
|
|
1150
|
+
}
|
|
1151
|
+
function renderCleanCapture(raw, doneToken) {
|
|
1152
|
+
const rendered = renderTerminalOutput(raw).replace(/[\s─-▟]+$/gm, "");
|
|
1153
|
+
if (!doneToken) return rendered.trim();
|
|
1154
|
+
const lines = rendered.split("\n");
|
|
1155
|
+
const flat = (s) => flattenTerminalOutput(s);
|
|
1156
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1157
|
+
const own = flat(lines[i]).includes(doneToken);
|
|
1158
|
+
if (own) return lines.slice(0, i).join("\n").trim();
|
|
1159
|
+
const window = flat(lines[i]) + (i + 1 < lines.length ? flat(lines[i + 1]) : "");
|
|
1160
|
+
if (window.includes(doneToken)) return lines.slice(0, i + 1).join("\n").trim();
|
|
1161
|
+
}
|
|
1162
|
+
return rendered.trim();
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// src/conversation/format.ts
|
|
1166
|
+
var DISPLAY_NAMES = {
|
|
1167
|
+
read_file: "Read",
|
|
1168
|
+
read_files: "Read",
|
|
1169
|
+
list_dir: "List",
|
|
1170
|
+
glob: "Glob",
|
|
1171
|
+
grep: "Grep",
|
|
1172
|
+
find_symbol: "Symbol",
|
|
1173
|
+
bash: "Bash",
|
|
1174
|
+
fetch: "Fetch",
|
|
1175
|
+
web_search: "WebSearch",
|
|
1176
|
+
spawn_research_agent: "Agent"
|
|
1177
|
+
};
|
|
1178
|
+
var HINT_FIELDS = ["file_path", "filePath", "path", "url", "pattern", "query", "command", "description", "prompt"];
|
|
1179
|
+
function parseArgs(args) {
|
|
1180
|
+
try {
|
|
1181
|
+
const parsed = JSON.parse(args);
|
|
1182
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
1183
|
+
} catch {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
function oneLine(text2) {
|
|
1188
|
+
return text2.replace(/\s+/g, " ").trim();
|
|
1189
|
+
}
|
|
1190
|
+
function firstLineOf(command) {
|
|
1191
|
+
const lines = command.trim().split(/\r?\n/);
|
|
1192
|
+
const more = lines.length - 1;
|
|
1193
|
+
return more > 0 ? `${lines[0]} \u2026 +${more} line${more === 1 ? "" : "s"}` : lines[0];
|
|
1194
|
+
}
|
|
1195
|
+
function text(value) {
|
|
1196
|
+
return typeof value === "string" ? value : "";
|
|
1197
|
+
}
|
|
1198
|
+
function searchArg(pattern, path, include = "") {
|
|
1199
|
+
return `${pattern}${path ? ` in ${path}` : ""}${include ? ` (${include})` : ""}`;
|
|
1200
|
+
}
|
|
1201
|
+
function hintArg(args) {
|
|
1202
|
+
for (const field of HINT_FIELDS) {
|
|
1203
|
+
const value = text(args[field]);
|
|
1204
|
+
if (value) return field === "command" ? firstLineOf(value) : oneLine(value);
|
|
1205
|
+
}
|
|
1206
|
+
const firstText = Object.values(args).find((v) => typeof v === "string" && v.trim() !== "");
|
|
1207
|
+
return firstText ? oneLine(firstText) : "";
|
|
1208
|
+
}
|
|
1209
|
+
function keyArgOf(tool, args) {
|
|
1210
|
+
switch (tool) {
|
|
1211
|
+
case "bash":
|
|
1212
|
+
return firstLineOf(text(args.command));
|
|
1213
|
+
case "read_file":
|
|
1214
|
+
case "list_dir":
|
|
1215
|
+
return text(args.path);
|
|
1216
|
+
case "read_files":
|
|
1217
|
+
return Array.isArray(args.paths) ? args.paths.map(String).join(", ") : "";
|
|
1218
|
+
case "glob":
|
|
1219
|
+
return searchArg(text(args.pattern), text(args.path));
|
|
1220
|
+
case "grep":
|
|
1221
|
+
return searchArg(text(args.pattern), text(args.path), text(args.include));
|
|
1222
|
+
case "find_symbol":
|
|
1223
|
+
return `${text(args.symbol)}${text(args.language) ? ` [${text(args.language)}]` : ""}`;
|
|
1224
|
+
case "fetch":
|
|
1225
|
+
return text(args.url);
|
|
1226
|
+
case "web_search":
|
|
1227
|
+
return oneLine(text(args.query));
|
|
1228
|
+
case "spawn_research_agent":
|
|
1229
|
+
return oneLine(text(args.prompt));
|
|
1230
|
+
default:
|
|
1231
|
+
return hintArg(args);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
function toolHeadline(tool, args, toolLabel) {
|
|
1235
|
+
const name = toolLabel?.trim() || DISPLAY_NAMES[tool] || tool;
|
|
1236
|
+
const parsed = parseArgs(args);
|
|
1237
|
+
return { name, keyArg: parsed ? keyArgOf(tool, parsed) : oneLine(args) };
|
|
1238
|
+
}
|
|
1239
|
+
function outputLines(output) {
|
|
1240
|
+
const lines = output.replace(/\r\n?/g, "\n").replace(ANSI_OR_CTRL_RE, "").split("\n");
|
|
1241
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") lines.pop();
|
|
1242
|
+
return lines;
|
|
1243
|
+
}
|
|
1244
|
+
function outputPreview(output, maxLines) {
|
|
1245
|
+
const lines = outputLines(output);
|
|
1246
|
+
const shown = lines.slice(0, Math.max(0, maxLines));
|
|
1247
|
+
return { lines: shown, hiddenLineCount: lines.length - shown.length };
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// src/conversation/records.ts
|
|
1251
|
+
function settledMessage(id, role, text2, turnId) {
|
|
1252
|
+
return { type: "message", id, role, text: text2, streaming: false, ...turnId ? { turnId } : {} };
|
|
1253
|
+
}
|
|
1254
|
+
function subagentBlock(id, { toolCallId, model, usage, turnId, ...fields }) {
|
|
1255
|
+
return {
|
|
1256
|
+
type: "subagent",
|
|
1257
|
+
id,
|
|
1258
|
+
...fields,
|
|
1259
|
+
...toolCallId ? { toolCallId } : {},
|
|
1260
|
+
...model ? { model } : {},
|
|
1261
|
+
...usage ? { usage } : {},
|
|
1262
|
+
...turnId ? { turnId } : {}
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
function usageBlock(id, { totals, bySubagent, contextFill }) {
|
|
1266
|
+
return { type: "usage", id, totals, ...bySubagent ? { bySubagent } : {}, ...contextFill ? { contextFill } : {} };
|
|
1267
|
+
}
|
|
1268
|
+
var STATUS_OF_OUTCOME = {
|
|
1269
|
+
success: "ok",
|
|
1270
|
+
failure: "error",
|
|
1271
|
+
refused: "denied",
|
|
1272
|
+
denied: "denied",
|
|
1273
|
+
not_executed: "interrupted"
|
|
1274
|
+
};
|
|
1275
|
+
function settledTool(call, step) {
|
|
1276
|
+
return { ...call, status: STATUS_OF_OUTCOME[step.outcome], outcome: step.outcome, output: step.result, outputLineCount: outputLines(step.result).length };
|
|
1277
|
+
}
|
|
1278
|
+
function pendingTool(id, { tool, toolLabel, args, toolCallId, turnId }) {
|
|
1279
|
+
return {
|
|
1280
|
+
type: "tool",
|
|
1281
|
+
id,
|
|
1282
|
+
tool,
|
|
1283
|
+
headline: toolHeadline(tool, args, toolLabel),
|
|
1284
|
+
args,
|
|
1285
|
+
status: "pending",
|
|
1286
|
+
output: "",
|
|
1287
|
+
outputLineCount: 0,
|
|
1288
|
+
...toolCallId ? { toolCallId } : {},
|
|
1289
|
+
...toolLabel ? { toolLabel } : {},
|
|
1290
|
+
...turnId ? { turnId } : {}
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
function toolFromStep(id, step, turnId) {
|
|
1294
|
+
return settledTool(pendingTool(id, { ...step, turnId }), step);
|
|
1295
|
+
}
|
|
1296
|
+
function planMarker(content) {
|
|
1297
|
+
const count = /(\d+) tasks?\b/.exec(content);
|
|
1298
|
+
return { status: content.startsWith("Plan updated") ? "updated" : "generated", ...count ? { taskCount: Number(count[1]) } : {} };
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// src/conversation/reduce.ts
|
|
1302
|
+
var EMPTY_CONVERSATION = { blocks: [], nextId: 1 };
|
|
1303
|
+
function findLastIndex(items, match) {
|
|
1304
|
+
for (let i = items.length - 1; i >= 0; i--) if (match(items[i])) return i;
|
|
1305
|
+
return -1;
|
|
1306
|
+
}
|
|
1307
|
+
function replaceAt(items, index, item) {
|
|
1308
|
+
const next = items.slice();
|
|
1309
|
+
next[index] = item;
|
|
1310
|
+
return next;
|
|
1311
|
+
}
|
|
1312
|
+
function insertTop(blocks, block) {
|
|
1313
|
+
const last = blocks[blocks.length - 1];
|
|
1314
|
+
return last?.type === "usage" ? [...blocks.slice(0, -1), block, last] : [...blocks, block];
|
|
1315
|
+
}
|
|
1316
|
+
function append(view, make) {
|
|
1317
|
+
return { ...view, blocks: insertTop(view.blocks, make(`b${view.nextId}`)), nextId: view.nextId + 1 };
|
|
1318
|
+
}
|
|
1319
|
+
function subagentAt(view, lane) {
|
|
1320
|
+
const block = view.blocks[lane];
|
|
1321
|
+
if (block.type !== "subagent") throw new Error(`block ${lane} is not a subagent`);
|
|
1322
|
+
return block;
|
|
1323
|
+
}
|
|
1324
|
+
function laneBlocks(view, lane) {
|
|
1325
|
+
return lane === null ? view.blocks : subagentAt(view, lane).children;
|
|
1326
|
+
}
|
|
1327
|
+
function setChildren(view, lane, children) {
|
|
1328
|
+
return { ...view, blocks: replaceAt(view.blocks, lane, { ...subagentAt(view, lane), children }) };
|
|
1329
|
+
}
|
|
1330
|
+
function laneReplace(view, lane, index, block) {
|
|
1331
|
+
if (lane === null) return { ...view, blocks: replaceAt(view.blocks, index, block) };
|
|
1332
|
+
return setChildren(view, lane, replaceAt(subagentAt(view, lane).children, index, block));
|
|
1333
|
+
}
|
|
1334
|
+
function laneAppend(view, lane, make) {
|
|
1335
|
+
if (lane === null) return append(view, make);
|
|
1336
|
+
const children = [...subagentAt(view, lane).children, make(`b${view.nextId}`)];
|
|
1337
|
+
return { ...setChildren(view, lane, children), nextId: view.nextId + 1 };
|
|
1338
|
+
}
|
|
1339
|
+
function isStreaming(block) {
|
|
1340
|
+
return (block.type === "message" || block.type === "thinking") && block.streaming;
|
|
1341
|
+
}
|
|
1342
|
+
function sealed(block) {
|
|
1343
|
+
return isStreaming(block) ? { ...block, streaming: false } : block;
|
|
1344
|
+
}
|
|
1345
|
+
function sealLane(view, lane, keep) {
|
|
1346
|
+
const blocks = laneBlocks(view, lane);
|
|
1347
|
+
if (!blocks.some((b, i) => i !== keep && isStreaming(b))) return view;
|
|
1348
|
+
if (lane === null) return { ...view, blocks: view.blocks.map((b, i) => i === keep ? b : sealed(b)) };
|
|
1349
|
+
return setChildren(view, lane, subagentAt(view, lane).children.map((b, i) => i === keep ? b : sealed(b)));
|
|
1350
|
+
}
|
|
1351
|
+
function appendOutput(view, lane, make) {
|
|
1352
|
+
const next = laneAppend(view, lane, make);
|
|
1353
|
+
return sealLane(next, lane, findLastIndex(laneBlocks(next, lane), (b) => b.type !== "usage"));
|
|
1354
|
+
}
|
|
1355
|
+
function isUnsettledSegment(block, turnId) {
|
|
1356
|
+
return block.type === "message" && block.turnId === turnId && block.segmentId !== void 0;
|
|
1357
|
+
}
|
|
1358
|
+
function isBuildingPlan(block, turnId) {
|
|
1359
|
+
return block.type === "plan" && block.status === "building" && block.turnId === turnId;
|
|
1360
|
+
}
|
|
1361
|
+
function finalSegmentIndex(blocks, turnId, pastPlan = false) {
|
|
1362
|
+
for (let i = blocks.length - 1; i >= 0; i--) {
|
|
1363
|
+
const block = blocks[i];
|
|
1364
|
+
if (block.type === "usage" || block.type === "thinking" || block.type === "approval" || pastPlan && block.type === "plan") continue;
|
|
1365
|
+
if (block.turnId !== turnId) continue;
|
|
1366
|
+
return isUnsettledSegment(block, turnId) ? i : -1;
|
|
1367
|
+
}
|
|
1368
|
+
return -1;
|
|
1369
|
+
}
|
|
1370
|
+
function openTurn(view, turnId, prompt) {
|
|
1371
|
+
const i = findLastIndex(view.blocks, (b) => b.type === "message" && b.role === "user");
|
|
1372
|
+
const sent = view.blocks[i];
|
|
1373
|
+
if (sent?.type === "message" && sent.turnId === void 0 && sent.text === prompt) {
|
|
1374
|
+
return { ...view, blocks: replaceAt(view.blocks, i, { ...sent, turnId }) };
|
|
1375
|
+
}
|
|
1376
|
+
return append(view, (id) => settledMessage(id, "user", prompt, turnId));
|
|
1377
|
+
}
|
|
1378
|
+
function streamText(view, { turnId, segmentId, text: text2 }) {
|
|
1379
|
+
const i = findLastIndex(view.blocks, (b) => isUnsettledSegment(b, turnId) && b.segmentId === segmentId);
|
|
1380
|
+
const segment = view.blocks[i];
|
|
1381
|
+
if (segment?.type === "message") {
|
|
1382
|
+
return sealLane(laneReplace(view, null, i, { ...segment, text: segment.text + text2, streaming: true }), null, i);
|
|
1383
|
+
}
|
|
1384
|
+
return appendOutput(view, null, (id) => ({ type: "message", id, role: "planner", text: text2, streaming: true, turnId, segmentId }));
|
|
1385
|
+
}
|
|
1386
|
+
function dropBuildingPlan(view, turnId, segmentId) {
|
|
1387
|
+
const blocks = view.blocks.filter((b) => !(isBuildingPlan(b, turnId) && (segmentId === void 0 || b.segmentId === segmentId)));
|
|
1388
|
+
return blocks.length === view.blocks.length ? view : { ...view, blocks };
|
|
1389
|
+
}
|
|
1390
|
+
function settleReply(view, { content, turnId }) {
|
|
1391
|
+
if (turnId !== void 0) {
|
|
1392
|
+
const i = finalSegmentIndex(view.blocks, turnId);
|
|
1393
|
+
if (i >= 0) {
|
|
1394
|
+
return dropBuildingPlan({ ...view, blocks: replaceAt(view.blocks, i, settledMessage(view.blocks[i].id, "planner", content, turnId)) }, turnId);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
return append(dropBuildingPlan(view, turnId), (id) => settledMessage(id, "planner", content, turnId));
|
|
1398
|
+
}
|
|
1399
|
+
function retractText(view, { turnId, segmentId }) {
|
|
1400
|
+
const blocks = view.blocks.filter((b) => !(isUnsettledSegment(b, turnId) && (segmentId === void 0 || b.segmentId === segmentId)));
|
|
1401
|
+
return dropBuildingPlan(blocks.length === view.blocks.length ? view : { ...view, blocks }, turnId, segmentId);
|
|
1402
|
+
}
|
|
1403
|
+
function subagentLane(view, subagentId, turnId) {
|
|
1404
|
+
const existing = findLastIndex(view.blocks, (b) => b.type === "subagent" && b.subagentId === subagentId);
|
|
1405
|
+
if (existing >= 0) return [view, existing];
|
|
1406
|
+
const spawn = findLastIndex(view.blocks, (b) => b.type === "tool" && (b.spawns === subagentId || b.toolCallId === subagentId));
|
|
1407
|
+
const call = view.blocks[spawn];
|
|
1408
|
+
if (call?.type === "tool") {
|
|
1409
|
+
const block = subagentBlock(call.id, {
|
|
1410
|
+
subagentId,
|
|
1411
|
+
brief: call.headline.keyArg,
|
|
1412
|
+
status: "running",
|
|
1413
|
+
children: [],
|
|
1414
|
+
digest: "",
|
|
1415
|
+
toolCallId: call.toolCallId,
|
|
1416
|
+
turnId: call.turnId
|
|
1417
|
+
});
|
|
1418
|
+
return [{ ...view, blocks: replaceAt(view.blocks, spawn, block) }, spawn];
|
|
1419
|
+
}
|
|
1420
|
+
const next = append(view, (id) => subagentBlock(id, { subagentId, brief: "", status: "running", children: [], digest: "", turnId }));
|
|
1421
|
+
return [next, findLastIndex(next.blocks, (b) => b.type === "subagent" && b.subagentId === subagentId)];
|
|
1422
|
+
}
|
|
1423
|
+
function updateSubagent(view, subagentId, turnId, update) {
|
|
1424
|
+
const [next, lane] = subagentLane(view, subagentId, turnId);
|
|
1425
|
+
return { ...next, blocks: replaceAt(next.blocks, lane, update(subagentAt(next, lane))) };
|
|
1426
|
+
}
|
|
1427
|
+
function startSubagent(view, { subagentId, brief, model, turnId }) {
|
|
1428
|
+
return updateSubagent(view, subagentId, turnId, (block) => ({ ...block, ...brief ? { brief } : {}, ...model ? { model } : {} }));
|
|
1429
|
+
}
|
|
1430
|
+
function finishSubagent(view, { subagentId, outcome, digest, usage, turnId }) {
|
|
1431
|
+
return updateSubagent(view, subagentId, turnId, (block) => ({
|
|
1432
|
+
...block,
|
|
1433
|
+
status: outcome,
|
|
1434
|
+
digest,
|
|
1435
|
+
children: block.children.map(sealed),
|
|
1436
|
+
...usage ? { usage } : {}
|
|
1437
|
+
}));
|
|
1438
|
+
}
|
|
1439
|
+
function think(view, text2, turnId, segmentId, subagentId) {
|
|
1440
|
+
const [placed, lane] = subagentId ? subagentLane(view, subagentId, turnId) : [view, null];
|
|
1441
|
+
const blocks = laneBlocks(placed, lane);
|
|
1442
|
+
const lastIndex = findLastIndex(blocks, (b) => b.type !== "usage");
|
|
1443
|
+
const last = blocks[lastIndex];
|
|
1444
|
+
if (last?.type === "thinking" && last.streaming && last.segmentId === segmentId) {
|
|
1445
|
+
return laneReplace(placed, lane, lastIndex, { ...last, text: last.text + text2 });
|
|
1446
|
+
}
|
|
1447
|
+
return appendOutput(placed, lane, (id) => ({
|
|
1448
|
+
type: "thinking",
|
|
1449
|
+
id,
|
|
1450
|
+
text: text2,
|
|
1451
|
+
streaming: true,
|
|
1452
|
+
...turnId ? { turnId } : {},
|
|
1453
|
+
...segmentId ? { segmentId } : {},
|
|
1454
|
+
...subagentId ? { subagentId } : {}
|
|
1455
|
+
}));
|
|
1456
|
+
}
|
|
1457
|
+
function announceTool(view, call) {
|
|
1458
|
+
const spawns = call.tool === "spawn_research_agent" ? call.subagentId : void 0;
|
|
1459
|
+
const [placed, lane] = call.subagentId && !spawns ? subagentLane(view, call.subagentId, call.turnId) : [view, null];
|
|
1460
|
+
return appendOutput(placed, lane, (id) => ({ ...pendingTool(id, call), ...spawns ? { spawns } : {} }));
|
|
1461
|
+
}
|
|
1462
|
+
function isPendingCallOf(block, step) {
|
|
1463
|
+
if (block.type !== "tool" || block.status !== "pending") return false;
|
|
1464
|
+
return step.toolCallId ? block.toolCallId === step.toolCallId : block.toolCallId === void 0 && block.tool === step.tool;
|
|
1465
|
+
}
|
|
1466
|
+
function settleTool(view, { step, turnId }) {
|
|
1467
|
+
const [placed, lane] = step.subagentId ? subagentLane(view, step.subagentId, turnId) : [view, null];
|
|
1468
|
+
const blocks = laneBlocks(placed, lane);
|
|
1469
|
+
const i = findLastIndex(blocks, (b) => isPendingCallOf(b, step));
|
|
1470
|
+
const pending = blocks[i];
|
|
1471
|
+
if (pending?.type === "tool") return laneReplace(placed, lane, i, settledTool(pending, step));
|
|
1472
|
+
if (lane === null && step.toolCallId && blocks.some((b) => b.type === "subagent" && b.toolCallId === step.toolCallId)) return view;
|
|
1473
|
+
return laneAppend(placed, lane, (id) => toolFromStep(id, step, turnId));
|
|
1474
|
+
}
|
|
1475
|
+
function streamPlan(view, { token, turnId, segmentId }) {
|
|
1476
|
+
const i = findLastIndex(view.blocks, (b) => isBuildingPlan(b, turnId));
|
|
1477
|
+
const building = view.blocks[i];
|
|
1478
|
+
if (building?.type === "plan" && building.segmentId === segmentId) {
|
|
1479
|
+
return { ...view, blocks: replaceAt(view.blocks, i, { ...building, text: building.text + token }) };
|
|
1480
|
+
}
|
|
1481
|
+
return append(dropBuildingPlan(view, turnId), (id) => ({
|
|
1482
|
+
type: "plan",
|
|
1483
|
+
id,
|
|
1484
|
+
status: "building",
|
|
1485
|
+
text: token,
|
|
1486
|
+
...turnId ? { turnId } : {},
|
|
1487
|
+
...segmentId ? { segmentId } : {}
|
|
1488
|
+
}));
|
|
1489
|
+
}
|
|
1490
|
+
function markPlan(view, content, turnId) {
|
|
1491
|
+
const marker = planMarker(content);
|
|
1492
|
+
const i = findLastIndex(view.blocks, (b) => isBuildingPlan(b, turnId));
|
|
1493
|
+
const building = view.blocks[i];
|
|
1494
|
+
if (building?.type === "plan") {
|
|
1495
|
+
const settled = { type: "plan", id: building.id, ...marker, text: "", ...building.turnId ? { turnId: building.turnId } : {} };
|
|
1496
|
+
return { ...view, blocks: replaceAt(view.blocks, i, settled) };
|
|
1497
|
+
}
|
|
1498
|
+
return append(view, (id) => ({ type: "plan", id, text: "", ...marker }));
|
|
1499
|
+
}
|
|
1500
|
+
function requestApproval(view, { id: approvalId, kind, subject, scope, detail, turnId }) {
|
|
1501
|
+
return append(view, (id) => ({
|
|
1502
|
+
type: "approval",
|
|
1503
|
+
id,
|
|
1504
|
+
approvalId,
|
|
1505
|
+
kind,
|
|
1506
|
+
subject,
|
|
1507
|
+
scope,
|
|
1508
|
+
status: "pending",
|
|
1509
|
+
...detail ? { detail } : {},
|
|
1510
|
+
...turnId ? { turnId } : {}
|
|
1511
|
+
}));
|
|
1512
|
+
}
|
|
1513
|
+
function settleApproval(view, { id, granted }) {
|
|
1514
|
+
const i = findLastIndex(view.blocks, (b) => b.type === "approval" && b.approvalId === id);
|
|
1515
|
+
const request = view.blocks[i];
|
|
1516
|
+
if (request?.type !== "approval") return view;
|
|
1517
|
+
return { ...view, blocks: replaceAt(view.blocks, i, { ...request, status: granted ? "granted" : "denied", decidedBy: "asked" }) };
|
|
1518
|
+
}
|
|
1519
|
+
function decideApproval(view, { kind, subject, scope, detail, granted, source }) {
|
|
1520
|
+
return append(view, (id) => ({
|
|
1521
|
+
type: "approval",
|
|
1522
|
+
id,
|
|
1523
|
+
kind,
|
|
1524
|
+
subject,
|
|
1525
|
+
scope,
|
|
1526
|
+
status: granted ? "granted" : "denied",
|
|
1527
|
+
decidedBy: source,
|
|
1528
|
+
...detail ? { detail } : {}
|
|
1529
|
+
}));
|
|
1530
|
+
}
|
|
1531
|
+
function reportUsage(view, message) {
|
|
1532
|
+
if (!isMeasured(message.totals)) return view;
|
|
1533
|
+
const last = view.blocks[view.blocks.length - 1];
|
|
1534
|
+
const line = usageBlock(last?.type === "usage" ? last.id : `b${view.nextId}`, message);
|
|
1535
|
+
if (last?.type === "usage") return { ...view, blocks: replaceAt(view.blocks, view.blocks.length - 1, line) };
|
|
1536
|
+
return { ...view, blocks: [...view.blocks, line], nextId: view.nextId + 1 };
|
|
1537
|
+
}
|
|
1538
|
+
function closeBlock(block, interrupted) {
|
|
1539
|
+
return interrupted && block.type === "tool" && block.status === "pending" ? { ...block, status: "interrupted" } : sealed(block);
|
|
1540
|
+
}
|
|
1541
|
+
function closeSubagent(block, cut) {
|
|
1542
|
+
const children = block.children.map((c) => closeBlock(c, cut !== null));
|
|
1543
|
+
const status = cut && block.status === "running" ? cut : block.status;
|
|
1544
|
+
return status === block.status && children.every((c, i) => c === block.children[i]) ? block : { ...block, status, children };
|
|
1545
|
+
}
|
|
1546
|
+
function endTurn(view, { turnId, outcome }) {
|
|
1547
|
+
let next = dropBuildingPlan(view, turnId);
|
|
1548
|
+
const planText = outcome === "plan" ? finalSegmentIndex(next.blocks, turnId, true) : -1;
|
|
1549
|
+
if (planText >= 0) next = { ...next, blocks: next.blocks.filter((_, i) => i !== planText) };
|
|
1550
|
+
const cut = outcome === "stopped" ? "stopped" : outcome === "error" ? "failed" : null;
|
|
1551
|
+
const blocks = next.blocks.map((b) => b.type === "subagent" ? closeSubagent(b, cut) : closeBlock(b, cut !== null));
|
|
1552
|
+
return blocks.every((b, i) => b === next.blocks[i]) ? next : { ...next, blocks };
|
|
1553
|
+
}
|
|
1554
|
+
function markCompaction(view, summary) {
|
|
1555
|
+
const i = findLastIndex(view.blocks, (b) => b.type === "message" && b.role === "planner" && b.turnId === void 0 && b.text === summary);
|
|
1556
|
+
const announced = view.blocks[i];
|
|
1557
|
+
if (announced?.type === "message") return { ...view, blocks: replaceAt(view.blocks, i, { ...announced, role: "system" }) };
|
|
1558
|
+
return append(view, (id) => settledMessage(id, "system", summary));
|
|
1559
|
+
}
|
|
1560
|
+
function syncTranscript(view, { plan, turnId }) {
|
|
1561
|
+
const history = plan.conversationHistory ?? [];
|
|
1562
|
+
const committed = findLastIndex(history, (e) => e.kind === "plan_generated");
|
|
1563
|
+
let next = view;
|
|
1564
|
+
let latest = view.transcriptAt;
|
|
1565
|
+
for (const [i, entry] of history.entries()) {
|
|
1566
|
+
const isNew = view.transcriptAt === void 0 || entry.timestamp > view.transcriptAt;
|
|
1567
|
+
if (isNew && entry.kind === "plan_generated") next = markPlan(next, entry.content, i === committed ? turnId : void 0);
|
|
1568
|
+
if (isNew && entry.kind === "system") next = append(next, (id) => settledMessage(id, "system", entry.content));
|
|
1569
|
+
if (isNew && entry.kind === "compaction") next = markCompaction(next, entry.content);
|
|
1570
|
+
if (latest === void 0 || entry.timestamp > latest) latest = entry.timestamp;
|
|
1571
|
+
}
|
|
1572
|
+
return next === view && latest === view.transcriptAt ? view : { ...next, transcriptAt: latest };
|
|
1573
|
+
}
|
|
1574
|
+
function reduceConversation(view, input) {
|
|
1575
|
+
switch (input.type) {
|
|
1576
|
+
case "local_entry":
|
|
1577
|
+
return append(view, (id) => settledMessage(id, input.role, input.text));
|
|
1578
|
+
case "planner_turn_started":
|
|
1579
|
+
return input.prompt === void 0 ? view : openTurn(view, input.turnId, input.prompt);
|
|
1580
|
+
case "planner_text_delta":
|
|
1581
|
+
return streamText(view, input);
|
|
1582
|
+
case "planner_message":
|
|
1583
|
+
return settleReply(view, input);
|
|
1584
|
+
case "planner_text_retracted":
|
|
1585
|
+
return retractText(view, input);
|
|
1586
|
+
case "planner_thinking_delta":
|
|
1587
|
+
return think(view, input.text, input.turnId, input.segmentId, input.subagentId);
|
|
1588
|
+
case "research_step":
|
|
1589
|
+
return announceTool(view, input);
|
|
1590
|
+
case "research_step_done":
|
|
1591
|
+
return settleTool(view, input);
|
|
1592
|
+
case "subagent_started":
|
|
1593
|
+
return startSubagent(view, input);
|
|
1594
|
+
case "subagent_finished":
|
|
1595
|
+
return finishSubagent(view, input);
|
|
1596
|
+
case "plan_token":
|
|
1597
|
+
return streamPlan(view, input);
|
|
1598
|
+
case "plan_generated":
|
|
1599
|
+
return syncTranscript(view, input);
|
|
1600
|
+
case "planner_turn_ended":
|
|
1601
|
+
return endTurn(view, input);
|
|
1602
|
+
case "approval_request":
|
|
1603
|
+
return requestApproval(view, input);
|
|
1604
|
+
case "approval_settled":
|
|
1605
|
+
return settleApproval(view, input);
|
|
1606
|
+
case "approval_decided":
|
|
1607
|
+
return decideApproval(view, input);
|
|
1608
|
+
case "planner_usage":
|
|
1609
|
+
return reportUsage(view, input);
|
|
1610
|
+
default:
|
|
1611
|
+
return view;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// src/conversation/transcript.ts
|
|
1616
|
+
var USER = 0;
|
|
1617
|
+
var RESEARCH = 1;
|
|
1618
|
+
var SETTLED = 2;
|
|
1619
|
+
function fromEntry(entry) {
|
|
1620
|
+
if (entry.kind === "plan_generated") return (id) => ({ type: "plan", id: id(), text: "", ...planMarker(entry.content) });
|
|
1621
|
+
const role = entry.kind === "system" || entry.kind === "compaction" ? "system" : entry.role === "user" ? "user" : "planner";
|
|
1622
|
+
return (id) => settledMessage(id(), role, entry.content);
|
|
1623
|
+
}
|
|
1624
|
+
function spawnBrief(step) {
|
|
1625
|
+
try {
|
|
1626
|
+
const args = JSON.parse(step.args);
|
|
1627
|
+
const prompt = args && typeof args === "object" ? args.prompt : void 0;
|
|
1628
|
+
return typeof prompt === "string" ? prompt.trim() : void 0;
|
|
1629
|
+
} catch {
|
|
1630
|
+
return void 0;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
function spawnedBy(step, unclaimed) {
|
|
1634
|
+
const byId = unclaimed.find((e) => e.subagentId === step.toolCallId);
|
|
1635
|
+
if (byId) return byId;
|
|
1636
|
+
if (step.tool !== "spawn_research_agent") return void 0;
|
|
1637
|
+
const brief = spawnBrief(step);
|
|
1638
|
+
return brief === void 0 ? void 0 : unclaimed.find((e) => e.brief === brief);
|
|
1639
|
+
}
|
|
1640
|
+
function research(log) {
|
|
1641
|
+
const subagents = log.filter((e) => "type" in e && e.type === "subagent");
|
|
1642
|
+
const ownSubagent = new Set(subagents.map((e) => e.subagentId));
|
|
1643
|
+
const unclaimed = [...subagents];
|
|
1644
|
+
const spawnCall = /* @__PURE__ */ new Map();
|
|
1645
|
+
const placed = [];
|
|
1646
|
+
for (const entry of log) {
|
|
1647
|
+
if ("type" in entry) {
|
|
1648
|
+
if (entry.type === "system") placed.push({ at: entry.timestamp, rank: RESEARCH, build: (id) => settledMessage(id(), "system", entry.content) });
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
if (entry.subagentId && ownSubagent.has(entry.subagentId)) continue;
|
|
1652
|
+
const spawned = spawnedBy(entry, unclaimed);
|
|
1653
|
+
if (spawned) {
|
|
1654
|
+
unclaimed.splice(unclaimed.indexOf(spawned), 1);
|
|
1655
|
+
spawnCall.set(spawned.subagentId, entry.toolCallId);
|
|
1656
|
+
continue;
|
|
1657
|
+
}
|
|
1658
|
+
placed.push({ at: entry.timestamp, rank: RESEARCH, build: (id) => toolFromStep(id(), entry) });
|
|
1659
|
+
}
|
|
1660
|
+
for (const entry of subagents) {
|
|
1661
|
+
const toolCallId = spawnCall.get(entry.subagentId);
|
|
1662
|
+
const steps = log.filter((e) => !("type" in e) && e.subagentId === entry.subagentId);
|
|
1663
|
+
placed.push({
|
|
1664
|
+
at: entry.timestamp,
|
|
1665
|
+
rank: RESEARCH,
|
|
1666
|
+
build: (id) => subagentBlock(id(), {
|
|
1667
|
+
subagentId: entry.subagentId,
|
|
1668
|
+
brief: entry.brief,
|
|
1669
|
+
status: entry.outcome,
|
|
1670
|
+
digest: entry.digest,
|
|
1671
|
+
children: steps.map((step) => toolFromStep(id(), step)),
|
|
1672
|
+
toolCallId,
|
|
1673
|
+
model: entry.model,
|
|
1674
|
+
usage: entry.usage
|
|
1675
|
+
})
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
return placed;
|
|
1679
|
+
}
|
|
1680
|
+
function fromTranscript(conversationHistory, researchLog, plannerUsage) {
|
|
1681
|
+
const history = conversationHistory ?? [];
|
|
1682
|
+
const compacted = history[0]?.kind === "compaction" ? history[0] : void 0;
|
|
1683
|
+
const kept = compacted ? history.slice(1) : history;
|
|
1684
|
+
const cutoff = compacted ? kept[0]?.timestamp ?? compacted.timestamp : "";
|
|
1685
|
+
const placed = [
|
|
1686
|
+
...kept.map((entry) => ({ at: entry.timestamp, rank: entry.role === "user" && !entry.kind ? USER : SETTLED, build: fromEntry(entry) })),
|
|
1687
|
+
...research(researchLog ?? []).filter((item) => item.at >= cutoff)
|
|
1688
|
+
];
|
|
1689
|
+
placed.sort((a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : a.rank - b.rank);
|
|
1690
|
+
if (compacted) placed.unshift({ at: "", rank: SETTLED, build: fromEntry(compacted) });
|
|
1691
|
+
let nextId = 1;
|
|
1692
|
+
const id = () => `b${nextId++}`;
|
|
1693
|
+
const blocks = placed.map((item) => item.build(id));
|
|
1694
|
+
if (plannerUsage && isMeasured(plannerUsage.totals)) blocks.push(usageBlock(id(), usageLine(plannerUsage)));
|
|
1695
|
+
const transcriptAt = history.reduce((latest, e) => latest === void 0 || e.timestamp > latest ? e.timestamp : latest, void 0);
|
|
1696
|
+
return { blocks, nextId, ...transcriptAt ? { transcriptAt } : {} };
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// src/conversation/promptHold.ts
|
|
1700
|
+
var EMPTY_HOLD = [];
|
|
1701
|
+
function holdPrompt(hold, text2) {
|
|
1702
|
+
return [...hold, text2];
|
|
1703
|
+
}
|
|
1704
|
+
function drainNext(hold) {
|
|
1705
|
+
const [text2, ...rest] = hold;
|
|
1706
|
+
return text2 === void 0 ? void 0 : { text: text2, rest };
|
|
1707
|
+
}
|
|
1708
|
+
function unsendLatest(hold) {
|
|
1709
|
+
if (hold.length === 0) return void 0;
|
|
1710
|
+
return { text: hold[hold.length - 1], rest: hold.slice(0, -1) };
|
|
1711
|
+
}
|
|
1712
|
+
function unsendAll(hold) {
|
|
1713
|
+
if (hold.length === 0) return void 0;
|
|
1714
|
+
return { text: hold.join("\n"), rest: EMPTY_HOLD };
|
|
1715
|
+
}
|
|
1716
|
+
function aheadOfDraft(text2, draft) {
|
|
1717
|
+
return draft ? `${text2}
|
|
1718
|
+
${draft}` : text2;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// src/conversation/turnGate.ts
|
|
1722
|
+
var NO_TURN = { open: null, stopped: null };
|
|
1723
|
+
var TURN_STREAM = /* @__PURE__ */ new Set([
|
|
1724
|
+
"planner_text_delta",
|
|
1725
|
+
"planner_thinking_delta",
|
|
1726
|
+
"planner_text_retracted",
|
|
1727
|
+
"planner_message",
|
|
1728
|
+
"plan_token",
|
|
1729
|
+
"research_step",
|
|
1730
|
+
"research_step_done",
|
|
1731
|
+
"subagent_started",
|
|
1732
|
+
"subagent_finished",
|
|
1733
|
+
"planner_turn_ended"
|
|
1734
|
+
]);
|
|
1735
|
+
function cutOff(gate, input) {
|
|
1736
|
+
if (gate.stopped === null || !TURN_STREAM.has(input.type)) return false;
|
|
1737
|
+
const turnId = "turnId" in input ? input.turnId : void 0;
|
|
1738
|
+
return turnId === void 0 || turnId === gate.stopped;
|
|
1739
|
+
}
|
|
1740
|
+
function withGate(live, gate) {
|
|
1741
|
+
return gate.open === live.gate.open && gate.stopped === live.gate.stopped ? live : { ...live, gate };
|
|
1742
|
+
}
|
|
1743
|
+
function followTurn(view, gate, input) {
|
|
1744
|
+
const live = { view, gate };
|
|
1745
|
+
if (input.type === "planner_turn_started") {
|
|
1746
|
+
return withGate({ view: reduceConversation(view, input), gate }, { open: input.turnId, stopped: null });
|
|
1747
|
+
}
|
|
1748
|
+
if (cutOff(gate, input)) {
|
|
1749
|
+
return input.type === "planner_turn_ended" ? withGate(live, { ...gate, stopped: null }) : live;
|
|
1750
|
+
}
|
|
1751
|
+
const next = { view: reduceConversation(view, input), gate };
|
|
1752
|
+
return input.type === "planner_turn_ended" && input.turnId === gate.open ? withGate(next, { ...gate, open: null }) : next;
|
|
1753
|
+
}
|
|
1754
|
+
function stopTurn(view, gate) {
|
|
1755
|
+
if (gate.open === null) return { view, gate };
|
|
1756
|
+
return {
|
|
1757
|
+
view: reduceConversation(view, { type: "planner_turn_ended", turnId: gate.open, outcome: "stopped" }),
|
|
1758
|
+
gate: { open: null, stopped: gate.open }
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// src/conversation/detail.ts
|
|
1763
|
+
function hasHiddenDetail(blocks) {
|
|
1764
|
+
return blocks.some((b) => b.type === "thinking" || b.type === "tool" || b.type === "subagent");
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// src/conversation/notices.ts
|
|
1768
|
+
function taskStartedNotice(title, runner) {
|
|
1769
|
+
return runner ? `Started "${title}" \xB7 ${runner}` : `Started "${title}"`;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
921
1772
|
export {
|
|
1773
|
+
partedPromptUsage,
|
|
1774
|
+
addUsage,
|
|
1775
|
+
addPlannerUsage,
|
|
1776
|
+
isMeasured,
|
|
1777
|
+
usageLine,
|
|
1778
|
+
plannerContextFill,
|
|
922
1779
|
effectiveAllowlist,
|
|
923
1780
|
filterModelsForPrompt,
|
|
924
1781
|
clampThinkingEffort,
|
|
@@ -927,6 +1784,8 @@ export {
|
|
|
927
1784
|
taskOpsProtocol,
|
|
928
1785
|
textHasTaskOps,
|
|
929
1786
|
parseTaskOpsJson,
|
|
1787
|
+
taskOpRefs,
|
|
1788
|
+
refMatchesTask,
|
|
930
1789
|
canMergeTasks,
|
|
931
1790
|
dependentsOf,
|
|
932
1791
|
dependencyCandidates,
|
|
@@ -935,11 +1794,31 @@ export {
|
|
|
935
1794
|
applyTaskOps,
|
|
936
1795
|
summarizeToolCall,
|
|
937
1796
|
classifyOutcome,
|
|
1797
|
+
flattenTerminalOutput,
|
|
1798
|
+
renderTerminalOutput,
|
|
1799
|
+
renderCleanCapture,
|
|
938
1800
|
serializeTask,
|
|
939
1801
|
serializeTaskStatus,
|
|
940
1802
|
serializePlan,
|
|
941
1803
|
executionSummary,
|
|
942
1804
|
CHECKPOINT_TRUNCATE_LENGTH,
|
|
943
|
-
truncateCheckpointSummary
|
|
1805
|
+
truncateCheckpointSummary,
|
|
1806
|
+
toolHeadline,
|
|
1807
|
+
outputLines,
|
|
1808
|
+
outputPreview,
|
|
1809
|
+
EMPTY_CONVERSATION,
|
|
1810
|
+
reduceConversation,
|
|
1811
|
+
fromTranscript,
|
|
1812
|
+
EMPTY_HOLD,
|
|
1813
|
+
holdPrompt,
|
|
1814
|
+
drainNext,
|
|
1815
|
+
unsendLatest,
|
|
1816
|
+
unsendAll,
|
|
1817
|
+
aheadOfDraft,
|
|
1818
|
+
NO_TURN,
|
|
1819
|
+
followTurn,
|
|
1820
|
+
stopTurn,
|
|
1821
|
+
hasHiddenDetail,
|
|
1822
|
+
taskStartedNotice
|
|
944
1823
|
};
|
|
945
|
-
//# sourceMappingURL=chunk-
|
|
1824
|
+
//# sourceMappingURL=chunk-KLN7ELXO.mjs.map
|