pi-firecode 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/dist/index.js +1222 -1840
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
|
|
7
7
|
// jsonc.ts
|
|
8
|
+
function isRecord(value) {
|
|
9
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
}
|
|
8
11
|
function parseJsonc(text) {
|
|
9
12
|
let output = "";
|
|
10
13
|
let inString = false;
|
|
@@ -60,22 +63,15 @@ function seedConfig() {
|
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
function readConfigFile() {
|
|
63
|
-
let
|
|
66
|
+
let parsed;
|
|
64
67
|
try {
|
|
65
|
-
|
|
68
|
+
parsed = parseJsonc(readFileSync(CONFIG_PATH, "utf8"));
|
|
66
69
|
} catch (error) {
|
|
67
70
|
if (error.code === "ENOENT")
|
|
68
71
|
return { raw: {}, fault: { kind: "missing" } };
|
|
69
72
|
return { raw: {}, fault: { kind: "parse", message: error instanceof Error ? error.message : String(error) } };
|
|
70
73
|
}
|
|
71
|
-
|
|
72
|
-
const parsed = parseJsonc(text);
|
|
73
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
74
|
-
return { raw: parsed };
|
|
75
|
-
return { raw: {}, fault: { kind: "notObject" } };
|
|
76
|
-
} catch (error) {
|
|
77
|
-
return { raw: {}, fault: { kind: "parse", message: error instanceof Error ? error.message : String(error) } };
|
|
78
|
-
}
|
|
74
|
+
return isRecord(parsed) ? { raw: parsed } : { raw: {}, fault: { kind: "notObject" } };
|
|
79
75
|
}
|
|
80
76
|
|
|
81
77
|
// i18n.ts
|
|
@@ -123,7 +119,6 @@ var msg = defineMessages({
|
|
|
123
119
|
modelSegment: (value) => `模型段不是 provider/model:${value}`,
|
|
124
120
|
thinkingLevel: (value) => `思考档无效:${value}`,
|
|
125
121
|
modelAtomFaults: (shape, faults) => `${shape}(${faults.join(";")})`,
|
|
126
|
-
reviewBackground: "review.background 已随审查子进程层删除,请直接移除该键",
|
|
127
122
|
reviewIncomplete: "请显式完整配置 review",
|
|
128
123
|
reviewReviewers: "review.reviewers 必须包含 1–5 个模型原子",
|
|
129
124
|
reviewToolsArray: "review.tools 必须是字符串数组",
|
|
@@ -172,7 +167,6 @@ var msg = defineMessages({
|
|
|
172
167
|
modelSegment: (value) => `model segment is not provider/model: ${value}`,
|
|
173
168
|
thinkingLevel: (value) => `invalid thinking level: ${value}`,
|
|
174
169
|
modelAtomFaults: (shape, faults) => `${shape} (${faults.join("; ")})`,
|
|
175
|
-
reviewBackground: "review.background was removed together with the review subprocess layer; delete this key",
|
|
176
170
|
reviewIncomplete: "Configure review explicitly and completely",
|
|
177
171
|
reviewReviewers: "review.reviewers must contain 1–5 model atoms",
|
|
178
172
|
reviewToolsArray: "review.tools must be an array of strings",
|
|
@@ -202,6 +196,7 @@ var msg = defineMessages({
|
|
|
202
196
|
});
|
|
203
197
|
|
|
204
198
|
// config.ts
|
|
199
|
+
var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
205
200
|
var FEATURES = [
|
|
206
201
|
"header",
|
|
207
202
|
"statusbar",
|
|
@@ -233,11 +228,8 @@ function readFile({ raw, fault }, problems) {
|
|
|
233
228
|
return {};
|
|
234
229
|
}
|
|
235
230
|
}
|
|
236
|
-
function isPlainObject(value) {
|
|
237
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
238
|
-
}
|
|
239
231
|
function asRecord(value) {
|
|
240
|
-
return
|
|
232
|
+
return isRecord(value) ? value : {};
|
|
241
233
|
}
|
|
242
234
|
function rejectUnknownKeys(record, allowed, field, problems) {
|
|
243
235
|
for (const key of Object.keys(record))
|
|
@@ -295,7 +287,7 @@ function loadConfig() {
|
|
|
295
287
|
return cached;
|
|
296
288
|
const blocking = [];
|
|
297
289
|
const raw = readFile(readConfigFile(), blocking);
|
|
298
|
-
const featuresBroken = raw.features !== undefined && !
|
|
290
|
+
const featuresBroken = raw.features !== undefined && !isRecord(raw.features);
|
|
299
291
|
if (featuresBroken)
|
|
300
292
|
blocking.push(msg.config.featuresNotObject);
|
|
301
293
|
const features = featuresBroken ? Object.fromEntries(FEATURES.map((feature) => [feature, false])) : asRecord(raw.features);
|
|
@@ -315,7 +307,7 @@ function loadConfig() {
|
|
|
315
307
|
problems.push(msg.config.language);
|
|
316
308
|
const section = (name, refused, parse, incomplete) => {
|
|
317
309
|
const own = [];
|
|
318
|
-
if (raw[name] !== undefined && !
|
|
310
|
+
if (raw[name] !== undefined && !isRecord(raw[name]))
|
|
319
311
|
own.push(msg.config.mustBeObject(name));
|
|
320
312
|
const config = parse(asRecord(raw[name]), own);
|
|
321
313
|
if (features[name] !== false)
|
|
@@ -341,15 +333,6 @@ function loadConfig() {
|
|
|
341
333
|
};
|
|
342
334
|
return cached;
|
|
343
335
|
}
|
|
344
|
-
var THINKING_LEVELS = new Set([
|
|
345
|
-
"off",
|
|
346
|
-
"minimal",
|
|
347
|
-
"low",
|
|
348
|
-
"medium",
|
|
349
|
-
"high",
|
|
350
|
-
"xhigh",
|
|
351
|
-
"max"
|
|
352
|
-
]);
|
|
353
336
|
var FALLBACK_THINKING = "medium";
|
|
354
337
|
function parseModelAtom(value, field, problems) {
|
|
355
338
|
const shape = msg.config.modelAtomShape(field);
|
|
@@ -361,7 +344,7 @@ function parseModelAtom(value, field, problems) {
|
|
|
361
344
|
const model = slash > 0 ? value.slice(0, slash) : "";
|
|
362
345
|
const thinking = slash > 0 ? value.slice(slash + 1) : value;
|
|
363
346
|
const providerSlash = model.indexOf("/");
|
|
364
|
-
const valid = THINKING_LEVELS.
|
|
347
|
+
const valid = THINKING_LEVELS.includes(thinking);
|
|
365
348
|
const faults = [];
|
|
366
349
|
if (providerSlash <= 0 || providerSlash === model.length - 1)
|
|
367
350
|
faults.push(msg.config.modelSegment(model || value));
|
|
@@ -375,14 +358,14 @@ var PRESET_KEYS = ["model", "tools", "instructions", "key"];
|
|
|
375
358
|
function parsePresets(value, problems) {
|
|
376
359
|
if (value === undefined)
|
|
377
360
|
return {};
|
|
378
|
-
if (!
|
|
361
|
+
if (!isRecord(value)) {
|
|
379
362
|
problems.push(msg.config.mustBeObject("presets"));
|
|
380
363
|
return {};
|
|
381
364
|
}
|
|
382
365
|
return Object.fromEntries(Object.entries(value).map(([name, raw]) => [name, parsePreset(raw, `presets.${name}`, problems)]));
|
|
383
366
|
}
|
|
384
367
|
function parsePreset(value, field, problems) {
|
|
385
|
-
if (!
|
|
368
|
+
if (!isRecord(value)) {
|
|
386
369
|
problems.push(msg.config.mustBeObject(field));
|
|
387
370
|
return {};
|
|
388
371
|
}
|
|
@@ -394,21 +377,10 @@ function parsePreset(value, field, problems) {
|
|
|
394
377
|
...value.key === undefined ? {} : { key: stringValue(value.key, `${field}.key`, problems) }
|
|
395
378
|
};
|
|
396
379
|
}
|
|
397
|
-
var REVIEW_KEYS =
|
|
398
|
-
"advisor",
|
|
399
|
-
"reviewers",
|
|
400
|
-
"maxRounds",
|
|
401
|
-
"advisorAfterFailures",
|
|
402
|
-
"timeoutMinutes",
|
|
403
|
-
"tools"
|
|
404
|
-
]);
|
|
380
|
+
var REVIEW_KEYS = ["advisor", "reviewers", "maxRounds", "advisorAfterFailures", "timeoutMinutes", "tools"];
|
|
405
381
|
var DEFAULT_TOOLS = ["read", "grep", "find", "ls", "bash"];
|
|
406
382
|
function parseReviewConfig(raw, problems) {
|
|
407
|
-
|
|
408
|
-
if (REVIEW_KEYS.has(key))
|
|
409
|
-
continue;
|
|
410
|
-
problems.push(key === "background" ? msg.config.reviewBackground : msg.config.unknownField(`review.${key}`));
|
|
411
|
-
}
|
|
383
|
+
rejectUnknownKeys(raw, REVIEW_KEYS, "review", problems);
|
|
412
384
|
for (const key of REVIEW_KEYS)
|
|
413
385
|
if (key !== "advisor" && key !== "reviewers" && !(key in raw))
|
|
414
386
|
problems.push(msg.config.mustSet(`review.${key}`));
|
|
@@ -457,22 +429,17 @@ function parseMasterConfig(raw, problems) {
|
|
|
457
429
|
problems.push(msg.config.unknownField(`master.${key}`));
|
|
458
430
|
const exclusions = stringArray(raw.workerExcludeExtensions, "master.workerExcludeExtensions", problems);
|
|
459
431
|
const autoActivate = booleanValue(raw.autoActivate, "master.autoActivate", true, problems);
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (!isPlainObject(raw.roles) || Object.keys(raw.roles).length === 0) {
|
|
432
|
+
const table = raw.roles;
|
|
433
|
+
if (table !== undefined && (!isRecord(table) || Object.keys(table).length === 0))
|
|
463
434
|
problems.push(msg.config.masterRoles);
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
const roles = Object.entries(raw.roles).map(([role, value]) => masterRole(value, `master.roles.${role}`, role, problems));
|
|
435
|
+
const roles = isRecord(table) ? Object.entries(table).map(([role, value]) => masterRole(value, `master.roles.${role}`, role, problems)) : [];
|
|
467
436
|
return { roles, workerExcludeExtensions: exclusions, autoActivate };
|
|
468
437
|
}
|
|
469
438
|
function masterRole(value, field, role, problems) {
|
|
470
439
|
const record = asRecord(value);
|
|
471
440
|
rejectUnknownKeys(record, ["model", "use", "fallback"], field, problems);
|
|
472
441
|
const atom = parseModelAtom(record.model, `${field}.model`, problems);
|
|
473
|
-
const use =
|
|
474
|
-
if (!use)
|
|
475
|
-
problems.push(msg.config.mustBeString(`${field}.use`));
|
|
442
|
+
const use = stringValue(record.use, `${field}.use`, problems) ?? "";
|
|
476
443
|
const fallback = masterFallback(record.fallback, `${field}.fallback`, problems);
|
|
477
444
|
return { role, ...atom, use, fallback };
|
|
478
445
|
}
|
|
@@ -606,8 +573,8 @@ function flame(cells, phase) {
|
|
|
606
573
|
return out;
|
|
607
574
|
}
|
|
608
575
|
function settleMark(kind, sinceMs) {
|
|
609
|
-
const
|
|
610
|
-
return
|
|
576
|
+
const done = kind === "done";
|
|
577
|
+
return paint(mix(HEAT_COLORS.orange, done ? HEAT_COLORS.green : HEAT_COLORS.fail, easeOut(sinceMs / SETTLE_MS)), done ? "✓" : "✗");
|
|
611
578
|
}
|
|
612
579
|
var settling = (sinceMs) => sinceMs < SETTLE_MS;
|
|
613
580
|
var SPINNER = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏";
|
|
@@ -732,8 +699,6 @@ var FADE_FROM = [20, 19, 18];
|
|
|
732
699
|
var RULE2 = [60, 57, 53];
|
|
733
700
|
var TINY_SUBTITLE_MIN = 8;
|
|
734
701
|
var TINY_RULE_MIN = 3;
|
|
735
|
-
var clamp2 = (value) => Math.min(1, Math.max(0, value));
|
|
736
|
-
var ease = (k) => 1 - (1 - clamp2(k)) ** 3;
|
|
737
702
|
var FLAME = [
|
|
738
703
|
"......#......",
|
|
739
704
|
"......##.....",
|
|
@@ -785,12 +750,12 @@ function flameHeat(y) {
|
|
|
785
750
|
return v < FLAME_RED_END ? mix(HEAT_COLORS.red, HEAT_COLORS.orange, v / FLAME_RED_END) : mix(HEAT_COLORS.orange, HEAT_COLORS.gold, (v - FLAME_RED_END) / (1 - FLAME_RED_END));
|
|
786
751
|
}
|
|
787
752
|
function logo(t) {
|
|
788
|
-
const flameLine =
|
|
753
|
+
const flameLine = easeOut(t / IGNITE_END) * (FLAME.length + 4);
|
|
789
754
|
const wordWidth = WORD_ROWS[0].length;
|
|
790
755
|
const wordEdge = (t - REVEAL_START) / (SETTLE_END - REVEAL_START) * wordWidth;
|
|
791
756
|
return FLAME.map((row, y) => {
|
|
792
757
|
const burnt = flameLine - (FLAME.length - y);
|
|
793
|
-
const color = mix(HEAT_COLORS.white, flameHeat(y),
|
|
758
|
+
const color = mix(HEAT_COLORS.white, flameHeat(y), clamp(burnt / 4));
|
|
794
759
|
const pixels = [...row].map((dot) => dot === "#" && burnt >= 0 ? color : undefined);
|
|
795
760
|
pixels.push(...Array(LOGO_GAP).fill(undefined));
|
|
796
761
|
const word = WORD_ROWS[y - WORD_TOP] ?? "";
|
|
@@ -812,7 +777,7 @@ function fitSubtitle({ version, cwd }, width) {
|
|
|
812
777
|
return clip(segments.at(-1) ?? cwd, width, "start");
|
|
813
778
|
}
|
|
814
779
|
function subtitleLine(subtitle, width, t) {
|
|
815
|
-
return paint(mix(FADE_FROM, HEAT_COLORS.ash,
|
|
780
|
+
return paint(mix(FADE_FROM, HEAT_COLORS.ash, easeOut((t - 0.4) / 0.6)), fitSubtitle(subtitle, width));
|
|
816
781
|
}
|
|
817
782
|
function large(t, subtitle) {
|
|
818
783
|
const rows = halfBlocks(logo(t));
|
|
@@ -826,10 +791,10 @@ function tiny(width, t, glyph, subtitle) {
|
|
|
826
791
|
const room = width - visibleWidth2(word) - 1 - TINY_RULE_MIN;
|
|
827
792
|
const head = room >= TINY_SUBTITLE_MIN ? `${word}${subtitleLine(subtitle, room, t)} ` : word;
|
|
828
793
|
const length = width - visibleWidth2(head);
|
|
829
|
-
const reveal =
|
|
794
|
+
const reveal = clamp((t - REVEAL_START) / (SETTLE_END - REVEAL_START));
|
|
830
795
|
let rule = "";
|
|
831
796
|
for (let index = 0;index < length; index++)
|
|
832
|
-
rule += index / length <= reveal ? paint(mix(HEAT_COLORS.gold, RULE2,
|
|
797
|
+
rule += index / length <= reveal ? paint(mix(HEAT_COLORS.gold, RULE2, easeOut(index / Math.max(8, length * 0.7))), "─") : " ";
|
|
833
798
|
return clip(head + rule, width, "end", "");
|
|
834
799
|
}
|
|
835
800
|
function center(lines, width) {
|
|
@@ -882,8 +847,6 @@ function registerHeader(pi) {
|
|
|
882
847
|
// provider/claude-sub.ts
|
|
883
848
|
import { execFileSync } from "node:child_process";
|
|
884
849
|
import { createHash } from "node:crypto";
|
|
885
|
-
import { appendFileSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
886
|
-
import { dirname as dirname2, resolve } from "node:path";
|
|
887
850
|
|
|
888
851
|
// provider/messages.ts
|
|
889
852
|
var msg2 = defineMessages({
|
|
@@ -912,13 +875,9 @@ var msg2 = defineMessages({
|
|
|
912
875
|
// provider/claude-sub.ts
|
|
913
876
|
var BILLING_PREFIX = "x-anthropic-billing-header:";
|
|
914
877
|
var FALLBACK_CLAUDE_CODE_VERSION = "2.1.281";
|
|
915
|
-
var DEFAULT_ENTRYPOINT = "cli";
|
|
916
878
|
var BILLING_SALT = "59cf53e54c78";
|
|
917
|
-
function isObject(value) {
|
|
918
|
-
return typeof value === "object" && value !== null;
|
|
919
|
-
}
|
|
920
879
|
function isTextBlock(value) {
|
|
921
|
-
return
|
|
880
|
+
return isRecord(value) && value.type === "text" && typeof value.text === "string";
|
|
922
881
|
}
|
|
923
882
|
function shouldApply(ctx) {
|
|
924
883
|
const model = ctx.model;
|
|
@@ -943,33 +902,16 @@ function detectClaudeCodeVersion() {
|
|
|
943
902
|
var claudeCodeVersion = detectClaudeCodeVersion();
|
|
944
903
|
function firstUserText(messages) {
|
|
945
904
|
const list = Array.isArray(messages) ? messages : [];
|
|
946
|
-
const firstUser = list.find((message) =>
|
|
947
|
-
return
|
|
905
|
+
const firstUser = list.find((message) => isRecord(message) && message.role === "user");
|
|
906
|
+
return isRecord(firstUser) ? textOf(firstUser.content) : "";
|
|
948
907
|
}
|
|
949
908
|
function versionSuffix(messageText) {
|
|
950
|
-
const explicit = process.env.PI_CLAUDE_CODE_VERSION_SUFFIX;
|
|
951
|
-
if (explicit)
|
|
952
|
-
return explicit;
|
|
953
909
|
const sampled = [4, 7, 20].map((index) => messageText[index] ?? "0").join("");
|
|
954
910
|
return createHash("sha256").update(`${BILLING_SALT}${sampled}${claudeCodeVersion}`).digest("hex").slice(0, 3);
|
|
955
911
|
}
|
|
956
912
|
function buildBillingHeader(messages) {
|
|
957
913
|
const version = `${claudeCodeVersion}.${versionSuffix(firstUserText(messages))}`;
|
|
958
|
-
|
|
959
|
-
const workload = process.env.PI_CLAUDE_CODE_WORKLOAD ?? process.env.CLAUDE_CODE_WORKLOAD;
|
|
960
|
-
const workloadPart = workload ? ` cc_workload=${workload};` : "";
|
|
961
|
-
return `${BILLING_PREFIX} cc_version=${version}; cc_entrypoint=${entrypoint}; cch=00000;${workloadPart}`;
|
|
962
|
-
}
|
|
963
|
-
function log(details) {
|
|
964
|
-
const logFile = process.env.PI_CLAUDE_OAUTH_LOG_FILE;
|
|
965
|
-
if (!logFile)
|
|
966
|
-
return;
|
|
967
|
-
const path = resolve(logFile);
|
|
968
|
-
try {
|
|
969
|
-
mkdirSync2(dirname2(path), { recursive: true });
|
|
970
|
-
appendFileSync(path, `${JSON.stringify({ timestamp: new Date().toISOString(), ...details })}
|
|
971
|
-
`, "utf8");
|
|
972
|
-
} catch {}
|
|
914
|
+
return `${BILLING_PREFIX} cc_version=${version}; cc_entrypoint=cli; cch=00000;`;
|
|
973
915
|
}
|
|
974
916
|
function isRevokedTokenFailure(entry) {
|
|
975
917
|
if (entry?.type !== "message" || entry.message.role !== "assistant")
|
|
@@ -997,14 +939,13 @@ function registerClaudeSub(pi) {
|
|
|
997
939
|
});
|
|
998
940
|
pi.on("before_provider_request", (event, ctx) => {
|
|
999
941
|
const payload = event.payload;
|
|
1000
|
-
if (!shouldApply(ctx) || !
|
|
942
|
+
if (!shouldApply(ctx) || !isRecord(payload))
|
|
1001
943
|
return;
|
|
1002
944
|
const { system, messages } = payload;
|
|
1003
945
|
const blocks = Array.isArray(system) ? system : [];
|
|
1004
946
|
if (blocks.some((block) => isTextBlock(block) && block.text.startsWith(BILLING_PREFIX)))
|
|
1005
947
|
return;
|
|
1006
948
|
const header = { type: "text", text: buildBillingHeader(messages) };
|
|
1007
|
-
log({ event: "billing_header_injected", header: header.text });
|
|
1008
949
|
return { ...payload, system: [header, ...blocks] };
|
|
1009
950
|
});
|
|
1010
951
|
pi.on("agent_before_settle", (event, ctx) => {
|
|
@@ -1021,9 +962,6 @@ function registerClaudeSub(pi) {
|
|
|
1021
962
|
// provider/openai-native/src/config.ts
|
|
1022
963
|
import { closeSync, openSync, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1023
964
|
var TEXT_VERBOSITIES = ["low", "medium", "high"];
|
|
1024
|
-
function isRecord(value) {
|
|
1025
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1026
|
-
}
|
|
1027
965
|
function isTextVerbosity(value) {
|
|
1028
966
|
return typeof value === "string" && TEXT_VERBOSITIES.includes(value);
|
|
1029
967
|
}
|
|
@@ -1083,11 +1021,8 @@ function replaceKeyedObject(text, key, value) {
|
|
|
1083
1021
|
}
|
|
1084
1022
|
return;
|
|
1085
1023
|
}
|
|
1086
|
-
function
|
|
1087
|
-
return isRecord(root.openai) ? root.openai :
|
|
1088
|
-
}
|
|
1089
|
-
function isFirecodeConfig(root) {
|
|
1090
|
-
return "openai" in root || "presets" in root || "keys" in root || "features" in root;
|
|
1024
|
+
function openaiSection(root) {
|
|
1025
|
+
return isRecord(root.openai) ? root.openai : {};
|
|
1091
1026
|
}
|
|
1092
1027
|
function parseProviderSettings(provider, value, warnings) {
|
|
1093
1028
|
if (!isRecord(value)) {
|
|
@@ -1181,17 +1116,10 @@ function acquireConfigLock(lockPath) {
|
|
|
1181
1116
|
rmSync(lockPath, { force: true });
|
|
1182
1117
|
return openSync(lockPath, "wx", 384);
|
|
1183
1118
|
}
|
|
1184
|
-
function writeConfig(configPath, settings,
|
|
1119
|
+
function writeConfig(configPath, settings, root) {
|
|
1185
1120
|
const serialized = serializeSettings(settings);
|
|
1186
|
-
|
|
1187
|
-
if (firecodeRoot) {
|
|
1188
|
-
const original = readFileSync3(configPath, "utf8");
|
|
1189
|
-
text = replaceKeyedObject(original, "openai", serialized) ?? `${JSON.stringify({ ...firecodeRoot, openai: serialized }, null, "\t")}
|
|
1190
|
-
`;
|
|
1191
|
-
} else {
|
|
1192
|
-
text = `${JSON.stringify(serialized, null, 2)}
|
|
1121
|
+
const text = replaceKeyedObject(readFileSync3(configPath, "utf8"), "openai", serialized) ?? `${JSON.stringify({ ...root, openai: serialized }, null, "\t")}
|
|
1193
1122
|
`;
|
|
1194
|
-
}
|
|
1195
1123
|
const temporaryPath = `${configPath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
1196
1124
|
try {
|
|
1197
1125
|
writeFileSync2(temporaryPath, text, {
|
|
@@ -1217,11 +1145,11 @@ function withConfigLock(configPath, action) {
|
|
|
1217
1145
|
}
|
|
1218
1146
|
function loadOpenAINativeSettings(configPath) {
|
|
1219
1147
|
try {
|
|
1220
|
-
return parseSettings(
|
|
1148
|
+
return parseSettings(openaiSection(readConfig(configPath)));
|
|
1221
1149
|
} catch (error) {
|
|
1222
1150
|
return {
|
|
1223
1151
|
settings: createDefaultSettings(),
|
|
1224
|
-
warnings: [`config.
|
|
1152
|
+
warnings: [`config.jsonc: ${error instanceof Error ? error.message : String(error)}`]
|
|
1225
1153
|
};
|
|
1226
1154
|
}
|
|
1227
1155
|
}
|
|
@@ -1231,7 +1159,7 @@ function togglePriority(provider, configPath) {
|
|
|
1231
1159
|
}
|
|
1232
1160
|
return withConfigLock(configPath, () => {
|
|
1233
1161
|
const root = readConfig(configPath);
|
|
1234
|
-
const loaded = parseSettings(
|
|
1162
|
+
const loaded = parseSettings(openaiSection(root));
|
|
1235
1163
|
const providerSettings = loaded.settings.providers[provider] ?? {};
|
|
1236
1164
|
const enabled = providerSettings.priority !== true;
|
|
1237
1165
|
const providers = {
|
|
@@ -1251,16 +1179,13 @@ function togglePriority(provider, configPath) {
|
|
|
1251
1179
|
nativeCompaction: loaded.settings.nativeCompaction,
|
|
1252
1180
|
providers
|
|
1253
1181
|
};
|
|
1254
|
-
writeConfig(configPath, settings,
|
|
1182
|
+
writeConfig(configPath, settings, root);
|
|
1255
1183
|
return {
|
|
1256
1184
|
enabled,
|
|
1257
1185
|
loaded: { settings, warnings: loaded.warnings }
|
|
1258
1186
|
};
|
|
1259
1187
|
});
|
|
1260
1188
|
}
|
|
1261
|
-
function providerSettings(settings, provider) {
|
|
1262
|
-
return provider ? settings.providers[provider] : undefined;
|
|
1263
|
-
}
|
|
1264
1189
|
|
|
1265
1190
|
// provider/openai-native/src/native-compaction.ts
|
|
1266
1191
|
import { buildSessionContext } from "@earendil-works/pi-coding-agent";
|
|
@@ -1269,9 +1194,6 @@ import { buildSessionContext } from "@earendil-works/pi-coding-agent";
|
|
|
1269
1194
|
var JSON_CONTENT_TYPE = "application/json";
|
|
1270
1195
|
var COMPACTION_TRIGGER = { type: "compaction_trigger" };
|
|
1271
1196
|
var RETAINED_MESSAGE_TOKEN_BUDGET = 64000;
|
|
1272
|
-
function isRecord2(value) {
|
|
1273
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1274
|
-
}
|
|
1275
1197
|
function isAbortError(error) {
|
|
1276
1198
|
return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.name === "ABORT_ERR");
|
|
1277
1199
|
}
|
|
@@ -1287,34 +1209,34 @@ function normalizeResponseTimestamp(value) {
|
|
|
1287
1209
|
return Number.isNaN(parsed) ? value.trim() : new Date(parsed).toISOString();
|
|
1288
1210
|
}
|
|
1289
1211
|
function errorMessage(value) {
|
|
1290
|
-
if (!
|
|
1212
|
+
if (!isRecord(value)) {
|
|
1291
1213
|
return;
|
|
1292
1214
|
}
|
|
1293
1215
|
if (typeof value.message === "string" && value.message.trim()) {
|
|
1294
1216
|
return value.message.trim();
|
|
1295
1217
|
}
|
|
1296
|
-
if (
|
|
1218
|
+
if (isRecord(value.error)) {
|
|
1297
1219
|
return errorMessage(value.error);
|
|
1298
1220
|
}
|
|
1299
|
-
if (
|
|
1221
|
+
if (isRecord(value.response)) {
|
|
1300
1222
|
return errorMessage(value.response);
|
|
1301
1223
|
}
|
|
1302
1224
|
return;
|
|
1303
1225
|
}
|
|
1304
1226
|
function providerError(value) {
|
|
1305
|
-
if (!
|
|
1227
|
+
if (!isRecord(value)) {
|
|
1306
1228
|
return;
|
|
1307
1229
|
}
|
|
1308
|
-
if (
|
|
1230
|
+
if (isRecord(value.error)) {
|
|
1309
1231
|
return value.error;
|
|
1310
1232
|
}
|
|
1311
1233
|
return providerError(value.response);
|
|
1312
1234
|
}
|
|
1313
1235
|
function parseFailedResponse(value) {
|
|
1314
|
-
if (!
|
|
1236
|
+
if (!isRecord(value)) {
|
|
1315
1237
|
return;
|
|
1316
1238
|
}
|
|
1317
|
-
const response =
|
|
1239
|
+
const response = isRecord(value.response) ? value.response : value;
|
|
1318
1240
|
if (value.type !== "response.failed" && value.type !== "error" && response.status !== "failed") {
|
|
1319
1241
|
return;
|
|
1320
1242
|
}
|
|
@@ -1334,10 +1256,10 @@ function parseErrorDetail(responseText) {
|
|
|
1334
1256
|
}
|
|
1335
1257
|
}
|
|
1336
1258
|
function isOutputArray(value) {
|
|
1337
|
-
return Array.isArray(value) && value.every(
|
|
1259
|
+
return Array.isArray(value) && value.every(isRecord);
|
|
1338
1260
|
}
|
|
1339
1261
|
function parseJsonOutput(value) {
|
|
1340
|
-
if (!
|
|
1262
|
+
if (!isRecord(value) || !isOutputArray(value.output)) {
|
|
1341
1263
|
return;
|
|
1342
1264
|
}
|
|
1343
1265
|
return { output: value.output, createdAt: value.created_at };
|
|
@@ -1358,17 +1280,17 @@ function parseSseOutput(responseText) {
|
|
|
1358
1280
|
} catch {
|
|
1359
1281
|
continue;
|
|
1360
1282
|
}
|
|
1361
|
-
if (!
|
|
1283
|
+
if (!isRecord(event)) {
|
|
1362
1284
|
continue;
|
|
1363
1285
|
}
|
|
1364
|
-
if (event.type === "response.output_item.done" &&
|
|
1286
|
+
if (event.type === "response.output_item.done" && isRecord(event.item)) {
|
|
1365
1287
|
streamedItems.push(event.item);
|
|
1366
1288
|
continue;
|
|
1367
1289
|
}
|
|
1368
1290
|
if (event.type === "response.completed") {
|
|
1369
1291
|
completed = parseJsonOutput(event.response) ?? {
|
|
1370
1292
|
output: [],
|
|
1371
|
-
createdAt:
|
|
1293
|
+
createdAt: isRecord(event.response) ? event.response.created_at : undefined
|
|
1372
1294
|
};
|
|
1373
1295
|
continue;
|
|
1374
1296
|
}
|
|
@@ -1408,7 +1330,7 @@ function parseResponseOutput(responseText) {
|
|
|
1408
1330
|
return sse ? { ok: false, ...sse } : { ok: false, reason: "malformed-response" };
|
|
1409
1331
|
}
|
|
1410
1332
|
function isRetainedInputItem(value) {
|
|
1411
|
-
return
|
|
1333
|
+
return isRecord(value) && (value.role === "user" || value.role === "developer" || value.role === "system");
|
|
1412
1334
|
}
|
|
1413
1335
|
function itemText(item) {
|
|
1414
1336
|
if (typeof item.content === "string") {
|
|
@@ -1417,7 +1339,7 @@ function itemText(item) {
|
|
|
1417
1339
|
if (!Array.isArray(item.content)) {
|
|
1418
1340
|
return "";
|
|
1419
1341
|
}
|
|
1420
|
-
return item.content.filter(
|
|
1342
|
+
return item.content.filter(isRecord).map((part) => typeof part.text === "string" ? part.text : "").join("");
|
|
1421
1343
|
}
|
|
1422
1344
|
function estimateTokens(item) {
|
|
1423
1345
|
return Math.max(1, Math.ceil(itemText(item).length / 4));
|
|
@@ -1447,14 +1369,14 @@ function decodeJwtPayload(token) {
|
|
|
1447
1369
|
}
|
|
1448
1370
|
try {
|
|
1449
1371
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
1450
|
-
return
|
|
1372
|
+
return isRecord(payload) ? payload : undefined;
|
|
1451
1373
|
} catch {
|
|
1452
1374
|
return;
|
|
1453
1375
|
}
|
|
1454
1376
|
}
|
|
1455
1377
|
function codexAccountId(token) {
|
|
1456
1378
|
const authClaims = decodeJwtPayload(token)?.["https://api.openai.com/auth"];
|
|
1457
|
-
if (!
|
|
1379
|
+
if (!isRecord(authClaims) || typeof authClaims.chatgpt_account_id !== "string") {
|
|
1458
1380
|
return;
|
|
1459
1381
|
}
|
|
1460
1382
|
return authClaims.chatgpt_account_id.trim() || undefined;
|
|
@@ -1540,9 +1462,6 @@ async function executeNativeCompaction(args) {
|
|
|
1540
1462
|
// provider/openai-native/src/native-details.ts
|
|
1541
1463
|
var NATIVE_COMPACTION_STRATEGY = "openai-native-compact";
|
|
1542
1464
|
var NATIVE_COMPACTION_SUMMARY = "[OpenAI native compaction checkpoint]";
|
|
1543
|
-
function isRecord3(value) {
|
|
1544
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1545
|
-
}
|
|
1546
1465
|
function isNonEmptyString(value) {
|
|
1547
1466
|
return typeof value === "string" && value.trim().length > 0;
|
|
1548
1467
|
}
|
|
@@ -1553,10 +1472,10 @@ function isStructuredValue(value) {
|
|
|
1553
1472
|
if (Array.isArray(value)) {
|
|
1554
1473
|
return value.every(isStructuredValue);
|
|
1555
1474
|
}
|
|
1556
|
-
return
|
|
1475
|
+
return isRecord(value) && Object.values(value).every(isStructuredValue);
|
|
1557
1476
|
}
|
|
1558
1477
|
function isCompactedWindowItem(value) {
|
|
1559
|
-
return
|
|
1478
|
+
return isRecord(value) && Object.values(value).every(isStructuredValue);
|
|
1560
1479
|
}
|
|
1561
1480
|
function hasNativeCompactionIdentity(value) {
|
|
1562
1481
|
return isNonEmptyString(value.provider) && isNonEmptyString(value.api) && isNonEmptyString(value.model) && isNonEmptyString(value.baseUrl);
|
|
@@ -1568,13 +1487,13 @@ function cloneStructuredValue(value) {
|
|
|
1568
1487
|
if (Array.isArray(value)) {
|
|
1569
1488
|
return value.map(cloneStructuredValue);
|
|
1570
1489
|
}
|
|
1571
|
-
if (
|
|
1490
|
+
if (isRecord(value)) {
|
|
1572
1491
|
return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [key, cloneStructuredValue(nestedValue)]));
|
|
1573
1492
|
}
|
|
1574
1493
|
throw new Error(`Unsupported structured value: ${typeof value}`);
|
|
1575
1494
|
}
|
|
1576
1495
|
function isNativeCompactionDetails(value) {
|
|
1577
|
-
if (!
|
|
1496
|
+
if (!isRecord(value) || value.strategy !== NATIVE_COMPACTION_STRATEGY || !hasNativeCompactionIdentity(value)) {
|
|
1578
1497
|
return false;
|
|
1579
1498
|
}
|
|
1580
1499
|
return Array.isArray(value.compactedWindow) && value.compactedWindow.every(isCompactedWindowItem) && isNonEmptyString(value.createdAt);
|
|
@@ -1930,11 +1849,8 @@ function isToolCallBlock(block) {
|
|
|
1930
1849
|
}
|
|
1931
1850
|
|
|
1932
1851
|
// provider/openai-native/src/native-replay.ts
|
|
1933
|
-
function isRecord4(value) {
|
|
1934
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1935
|
-
}
|
|
1936
1852
|
function isResponsesInputContentItem(value) {
|
|
1937
|
-
if (!
|
|
1853
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1938
1854
|
return false;
|
|
1939
1855
|
}
|
|
1940
1856
|
if (value.type === "input_text") {
|
|
@@ -1943,7 +1859,7 @@ function isResponsesInputContentItem(value) {
|
|
|
1943
1859
|
return value.type === "input_image" && value.detail === "auto" && typeof value.image_url === "string";
|
|
1944
1860
|
}
|
|
1945
1861
|
function isResponsesInputMessageItem(value) {
|
|
1946
|
-
if (!
|
|
1862
|
+
if (!isRecord(value) || value.role !== "user" && value.role !== "developer" && value.role !== "system") {
|
|
1947
1863
|
return false;
|
|
1948
1864
|
}
|
|
1949
1865
|
return typeof value.content === "string" || Array.isArray(value.content) && value.content.every(isResponsesInputContentItem);
|
|
@@ -1981,7 +1897,7 @@ function areEquivalentValues(left, right) {
|
|
|
1981
1897
|
if (Array.isArray(left) || Array.isArray(right)) {
|
|
1982
1898
|
return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((item, index) => areEquivalentValues(item, right[index]));
|
|
1983
1899
|
}
|
|
1984
|
-
if (!
|
|
1900
|
+
if (!isRecord(left) || !isRecord(right)) {
|
|
1985
1901
|
return false;
|
|
1986
1902
|
}
|
|
1987
1903
|
const leftKeys = Object.keys(left).sort();
|
|
@@ -2306,12 +2222,6 @@ var PRIORITY_MODEL_IDS = new Set([
|
|
|
2306
2222
|
"gpt-6.1-sol",
|
|
2307
2223
|
"gpt-6-luna"
|
|
2308
2224
|
]);
|
|
2309
|
-
function isRecord5(value) {
|
|
2310
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2311
|
-
}
|
|
2312
|
-
function isTextVerbosity2(value) {
|
|
2313
|
-
return value === "low" || value === "medium" || value === "high";
|
|
2314
|
-
}
|
|
2315
2225
|
function isOpenAIResponsesModel(model) {
|
|
2316
2226
|
return model !== undefined && OPENAI_RESPONSES_APIS.has(model.api);
|
|
2317
2227
|
}
|
|
@@ -2326,24 +2236,24 @@ function fastModeEnabled(model, settings) {
|
|
|
2326
2236
|
if (!model || !supportsFastMode(model)) {
|
|
2327
2237
|
return false;
|
|
2328
2238
|
}
|
|
2329
|
-
return
|
|
2239
|
+
return settings.providers[model.provider]?.priority === true;
|
|
2330
2240
|
}
|
|
2331
2241
|
function resolveTextVerbosity(model, settings, verbosityOverride) {
|
|
2332
2242
|
if (verbosityOverride !== undefined) {
|
|
2333
|
-
return
|
|
2243
|
+
return isTextVerbosity(verbosityOverride) ? verbosityOverride : undefined;
|
|
2334
2244
|
}
|
|
2335
|
-
return
|
|
2245
|
+
return settings.providers[model.provider]?.textVerbosity;
|
|
2336
2246
|
}
|
|
2337
2247
|
function applyOpenAIOptions(payload, model, settings, verbosityOverride) {
|
|
2338
|
-
const configuredProvider = model ?
|
|
2248
|
+
const configuredProvider = model ? settings.providers[model.provider] : undefined;
|
|
2339
2249
|
let nextPayload = payload;
|
|
2340
2250
|
if (model && isOpenAIResponsesModel(model)) {
|
|
2341
2251
|
const textVerbosity = resolveTextVerbosity(model, settings, verbosityOverride);
|
|
2342
|
-
if (textVerbosity && (!
|
|
2252
|
+
if (textVerbosity && (!isRecord(payload.text) || payload.text.verbosity !== textVerbosity)) {
|
|
2343
2253
|
nextPayload = {
|
|
2344
2254
|
...nextPayload,
|
|
2345
2255
|
text: {
|
|
2346
|
-
...
|
|
2256
|
+
...isRecord(nextPayload.text) ? nextPayload.text : {},
|
|
2347
2257
|
verbosity: textVerbosity
|
|
2348
2258
|
}
|
|
2349
2259
|
};
|
|
@@ -2357,12 +2267,9 @@ function applyOpenAIOptions(payload, model, settings, verbosityOverride) {
|
|
|
2357
2267
|
}
|
|
2358
2268
|
|
|
2359
2269
|
// provider/openai-native/src/request-pipeline.ts
|
|
2360
|
-
function isRecord6(value) {
|
|
2361
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2362
|
-
}
|
|
2363
2270
|
function rewriteOpenAIProviderRequest(payload, ctx, settings, verbosityOverride) {
|
|
2364
2271
|
let nextPayload = settings.nativeCompaction ? replayOpenAINative(payload, ctx) ?? payload : payload;
|
|
2365
|
-
if (
|
|
2272
|
+
if (isRecord(nextPayload)) {
|
|
2366
2273
|
nextPayload = applyOpenAIOptions(nextPayload, ctx.model, settings, verbosityOverride);
|
|
2367
2274
|
}
|
|
2368
2275
|
return nextPayload;
|
|
@@ -2607,20 +2514,21 @@ function describe(preset) {
|
|
|
2607
2514
|
return parts.join(" | ");
|
|
2608
2515
|
}
|
|
2609
2516
|
function registerPresets(pi) {
|
|
2610
|
-
|
|
2517
|
+
const { presets } = loadConfig().config;
|
|
2611
2518
|
let activeName;
|
|
2612
|
-
let activePreset;
|
|
2613
2519
|
let originalState;
|
|
2614
2520
|
let applying = false;
|
|
2615
2521
|
pi.registerFlag("preset", {
|
|
2616
2522
|
description: msg3.presets.flagDescription,
|
|
2617
2523
|
type: "string"
|
|
2618
2524
|
});
|
|
2619
|
-
const updateStatus = (ctx) =>
|
|
2620
|
-
|
|
2525
|
+
const updateStatus = (ctx) => {
|
|
2526
|
+
const active = activePreset();
|
|
2527
|
+
ctx.ui.setStatus("preset", activeName && (active?.tools?.length || active?.instructions) ? ctx.ui.theme.fg("accent", title(activeName)) : undefined);
|
|
2528
|
+
};
|
|
2529
|
+
const activePreset = () => activeName === undefined ? undefined : presets[activeName];
|
|
2621
2530
|
function setActive(name, ctx) {
|
|
2622
2531
|
activeName = name;
|
|
2623
|
-
activePreset = name === undefined ? undefined : presets[name];
|
|
2624
2532
|
if (name === undefined)
|
|
2625
2533
|
originalState = undefined;
|
|
2626
2534
|
pi.appendEntry(STATE_ENTRY, { name: name ?? null });
|
|
@@ -2637,7 +2545,8 @@ function registerPresets(pi) {
|
|
|
2637
2545
|
if (valid.length)
|
|
2638
2546
|
pi.setActiveTools(valid);
|
|
2639
2547
|
}
|
|
2640
|
-
async function applyPreset(name,
|
|
2548
|
+
async function applyPreset(name, ctx) {
|
|
2549
|
+
const preset = presets[name];
|
|
2641
2550
|
const snapshot = activeName === undefined ? { model: ctx.model, thinkingLevel: pi.getThinkingLevel(), tools: pi.getActiveTools() } : originalState;
|
|
2642
2551
|
applying = true;
|
|
2643
2552
|
const failure = await applyModel(preset, ctx).finally(() => {
|
|
@@ -2665,18 +2574,13 @@ function registerPresets(pi) {
|
|
|
2665
2574
|
return;
|
|
2666
2575
|
}
|
|
2667
2576
|
function dropIfDiverged(ctx) {
|
|
2668
|
-
|
|
2577
|
+
const active = activePreset();
|
|
2578
|
+
if (!activeName || !active || holds(active, ctx.model))
|
|
2669
2579
|
return;
|
|
2670
2580
|
const name = activeName;
|
|
2671
2581
|
setActive(undefined, ctx);
|
|
2672
2582
|
ctx.ui.notify(msg3.presets.diverged(name), "info");
|
|
2673
2583
|
}
|
|
2674
|
-
async function activate(name, ctx) {
|
|
2675
|
-
const preset = presets[name];
|
|
2676
|
-
if (!preset)
|
|
2677
|
-
return;
|
|
2678
|
-
await applyPreset(name, preset, ctx);
|
|
2679
|
-
}
|
|
2680
2584
|
async function clearPreset(ctx) {
|
|
2681
2585
|
const original = originalState;
|
|
2682
2586
|
setActive(undefined, ctx);
|
|
@@ -2691,7 +2595,7 @@ function registerPresets(pi) {
|
|
|
2691
2595
|
async function showSelector(ctx) {
|
|
2692
2596
|
const names = Object.keys(presets);
|
|
2693
2597
|
if (names.length === 0) {
|
|
2694
|
-
|
|
2598
|
+
ctx.ui.notify(msg3.presets.noneDefined, "warning");
|
|
2695
2599
|
return;
|
|
2696
2600
|
}
|
|
2697
2601
|
const items = names.map((name) => ({
|
|
@@ -2734,15 +2638,14 @@ function registerPresets(pi) {
|
|
|
2734
2638
|
if (choice === CLEAR_ITEM)
|
|
2735
2639
|
await clearPreset(ctx);
|
|
2736
2640
|
else
|
|
2737
|
-
await
|
|
2641
|
+
await applyPreset(choice, ctx);
|
|
2738
2642
|
}
|
|
2739
|
-
const
|
|
2740
|
-
for (const [name, preset] of Object.entries(configured)) {
|
|
2643
|
+
for (const [name, preset] of Object.entries(presets)) {
|
|
2741
2644
|
if (!preset.key)
|
|
2742
2645
|
continue;
|
|
2743
2646
|
pi.registerShortcut(preset.key, {
|
|
2744
2647
|
description: msg3.presets.shortcutDescription(name),
|
|
2745
|
-
handler: (ctx) =>
|
|
2648
|
+
handler: (ctx) => applyPreset(name, ctx)
|
|
2746
2649
|
});
|
|
2747
2650
|
}
|
|
2748
2651
|
pi.registerCommand("preset", {
|
|
@@ -2762,7 +2665,7 @@ function registerPresets(pi) {
|
|
|
2762
2665
|
ctx.ui.notify(msg3.presets.unknown(name, available), "error");
|
|
2763
2666
|
return;
|
|
2764
2667
|
}
|
|
2765
|
-
await
|
|
2668
|
+
await applyPreset(name, ctx);
|
|
2766
2669
|
}
|
|
2767
2670
|
});
|
|
2768
2671
|
pi.on("model_select", (_event, ctx) => {
|
|
@@ -2771,15 +2674,15 @@ function registerPresets(pi) {
|
|
|
2771
2674
|
});
|
|
2772
2675
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
2773
2676
|
dropIfDiverged(ctx);
|
|
2774
|
-
|
|
2677
|
+
const instructions = activePreset()?.instructions;
|
|
2678
|
+
if (!instructions)
|
|
2775
2679
|
return;
|
|
2776
2680
|
return { systemPrompt: `${event.systemPrompt}
|
|
2777
2681
|
|
|
2778
|
-
${
|
|
2682
|
+
${instructions}` };
|
|
2779
2683
|
});
|
|
2780
2684
|
const reset = () => {
|
|
2781
2685
|
activeName = undefined;
|
|
2782
|
-
activePreset = undefined;
|
|
2783
2686
|
originalState = undefined;
|
|
2784
2687
|
};
|
|
2785
2688
|
pi.on("session_tree", (_event, ctx) => {
|
|
@@ -2789,11 +2692,10 @@ ${activePreset.instructions}` };
|
|
|
2789
2692
|
});
|
|
2790
2693
|
pi.on("session_start", async (_event, ctx) => {
|
|
2791
2694
|
reset();
|
|
2792
|
-
presets = loadConfig().config.presets;
|
|
2793
2695
|
const flag = pi.getFlag("preset");
|
|
2794
2696
|
if (typeof flag === "string" && flag) {
|
|
2795
2697
|
if (presets[flag]) {
|
|
2796
|
-
await applyPreset(flag,
|
|
2698
|
+
await applyPreset(flag, ctx);
|
|
2797
2699
|
} else {
|
|
2798
2700
|
const available = Object.keys(presets).join(", ") || msg3.presets.noneAvailable;
|
|
2799
2701
|
ctx.ui.notify(msg3.presets.unknown(flag, available), "warning");
|
|
@@ -2814,7 +2716,6 @@ ${activePreset.instructions}` };
|
|
|
2814
2716
|
return;
|
|
2815
2717
|
}
|
|
2816
2718
|
activeName = name;
|
|
2817
|
-
activePreset = preset;
|
|
2818
2719
|
originalState = { model: undefined, thinkingLevel: pi.getThinkingLevel(), tools: pi.getActiveTools() };
|
|
2819
2720
|
}
|
|
2820
2721
|
}
|
|
@@ -2982,11 +2883,12 @@ var SOURCE = "firecode";
|
|
|
2982
2883
|
var AGENT = "pi";
|
|
2983
2884
|
var RETRY_TIMEOUT_MS = 1500;
|
|
2984
2885
|
var UNSAFE_ARGUMENT = /['\u0000-\u001f\u007f]/;
|
|
2985
|
-
var seq =
|
|
2986
|
-
var nextSeq = () => seq
|
|
2886
|
+
var seq = 0;
|
|
2887
|
+
var nextSeq = () => seq = Math.max(seq + 1, Date.now() * 1000);
|
|
2987
2888
|
var agentKey = (p) => `${p.working}\x00${p.resume?.join("\x00") ?? ""}`;
|
|
2988
2889
|
var metaKey = (p) => `${p.title}\x00${p.agent}\x00${p.label ?? ""}`;
|
|
2989
|
-
function identityOf(ctx,
|
|
2890
|
+
function identityOf(ctx, pi) {
|
|
2891
|
+
const thinking = ctx.model?.reasoning ? pi.getThinkingLevel() : undefined;
|
|
2990
2892
|
const level = thinking && thinking !== "off" ? `/${thinking}` : "";
|
|
2991
2893
|
return { title: ctx.sessionManager.getSessionName() ?? "", agent: `pi·${formatModelName(ctx.model?.id)}${level}` };
|
|
2992
2894
|
}
|
|
@@ -2999,9 +2901,9 @@ function labelOf(view) {
|
|
|
2999
2901
|
return msg3.herdr.review;
|
|
3000
2902
|
return !view.agentRunning && view.inFlight > 0 ? msg3.herdr.workers : undefined;
|
|
3001
2903
|
}
|
|
3002
|
-
function registerHerdrProjection(pi
|
|
2904
|
+
function registerHerdrProjection(pi) {
|
|
3003
2905
|
const env = herdrPaneEnv();
|
|
3004
|
-
if (!env
|
|
2906
|
+
if (!env)
|
|
3005
2907
|
return () => Promise.resolve();
|
|
3006
2908
|
const paneId = env.paneId;
|
|
3007
2909
|
let enabled = false;
|
|
@@ -3072,14 +2974,14 @@ function registerHerdrProjection(pi, subsession = false) {
|
|
|
3072
2974
|
});
|
|
3073
2975
|
const syncIdentity = (ctx) => {
|
|
3074
2976
|
if (ctx.mode === "tui")
|
|
3075
|
-
update(identityOf(ctx,
|
|
2977
|
+
update(identityOf(ctx, pi));
|
|
3076
2978
|
};
|
|
3077
2979
|
pi.on("session_start", (_event, ctx) => {
|
|
3078
2980
|
if (ctx.mode !== "tui")
|
|
3079
2981
|
return;
|
|
3080
2982
|
carriedRun = !ctx.isIdle();
|
|
3081
2983
|
enabled = true;
|
|
3082
|
-
desired = { ...desired, ...identityOf(ctx,
|
|
2984
|
+
desired = { ...desired, ...identityOf(ctx, pi), resume: resumeOf(ctx) };
|
|
3083
2985
|
project();
|
|
3084
2986
|
});
|
|
3085
2987
|
pi.on("session_info_changed", (_event, ctx) => syncIdentity(ctx));
|
|
@@ -3796,6 +3698,9 @@ class Shell {
|
|
|
3796
3698
|
this.stopClock?.();
|
|
3797
3699
|
this.stopClock = undefined;
|
|
3798
3700
|
this.requestRender = () => {};
|
|
3701
|
+
this.busy = IDLE;
|
|
3702
|
+
this.settled = undefined;
|
|
3703
|
+
this.review = undefined;
|
|
3799
3704
|
}
|
|
3800
3705
|
sync(view) {
|
|
3801
3706
|
this.busy = view;
|
|
@@ -3895,9 +3800,7 @@ class ShellEditor extends CustomEditor {
|
|
|
3895
3800
|
return hiddenLineCount > 0 ? super.renderBottomBorder(width, hiddenLineCount) : bottomBorder(width, this.bottomParts(), this.borderColor);
|
|
3896
3801
|
}
|
|
3897
3802
|
}
|
|
3898
|
-
function registerStatusBar(pi
|
|
3899
|
-
if (subsession)
|
|
3900
|
-
return;
|
|
3803
|
+
function registerStatusBar(pi) {
|
|
3901
3804
|
const shell = new Shell;
|
|
3902
3805
|
const updateTitle = (ctx, incoming) => {
|
|
3903
3806
|
shell.title = displayTitle(ctx, incoming);
|
|
@@ -3948,9 +3851,6 @@ function registerStatusBar(pi, subsession = false) {
|
|
|
3948
3851
|
pi.on("model_select", () => shell.requestRender());
|
|
3949
3852
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
3950
3853
|
shell.dispose();
|
|
3951
|
-
shell.busy = IDLE;
|
|
3952
|
-
shell.settled = undefined;
|
|
3953
|
-
shell.review = undefined;
|
|
3954
3854
|
ctx.ui.setFooter(undefined);
|
|
3955
3855
|
ctx.ui.setEditorComponent(undefined);
|
|
3956
3856
|
});
|
|
@@ -4160,7 +4060,6 @@ function genericArgsParts(args) {
|
|
|
4160
4060
|
}
|
|
4161
4061
|
|
|
4162
4062
|
// tools/actions.ts
|
|
4163
|
-
var LABEL = msg4.labels;
|
|
4164
4063
|
var argPath = (args) => args?.file_path ?? args?.path ?? "";
|
|
4165
4064
|
function rangeSuffix(args) {
|
|
4166
4065
|
if (args.offset === undefined && args.limit === undefined)
|
|
@@ -4193,9 +4092,10 @@ function toolTarget(tool, args, cwd) {
|
|
|
4193
4092
|
return { value: genericArgsParts(args), clip: "end" };
|
|
4194
4093
|
}
|
|
4195
4094
|
}
|
|
4095
|
+
var toolTargetText = (tool, args, cwd) => toolTarget(tool, args, cwd).value.map((part) => part.text).join("").trim();
|
|
4196
4096
|
function toolActionText(tool, args, cwd) {
|
|
4197
|
-
const word =
|
|
4198
|
-
const target =
|
|
4097
|
+
const word = msg4.labels[tool] ?? tool;
|
|
4098
|
+
const target = toolTargetText(tool, args, cwd);
|
|
4199
4099
|
return target ? `${word} ${target}` : word;
|
|
4200
4100
|
}
|
|
4201
4101
|
|
|
@@ -4511,7 +4411,11 @@ function entryShell(marker) {
|
|
|
4511
4411
|
shell.hasContent = () => true;
|
|
4512
4412
|
return shell;
|
|
4513
4413
|
}
|
|
4514
|
-
|
|
4414
|
+
function failureResult(message) {
|
|
4415
|
+
const text = message.stopReason === "aborted" ? "Operation aborted" : message.errorMessage || "Error";
|
|
4416
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
4417
|
+
}
|
|
4418
|
+
var failed = (message) => message.stopReason === "aborted" || message.stopReason === "error";
|
|
4515
4419
|
|
|
4516
4420
|
class ChatMirror {
|
|
4517
4421
|
sources;
|
|
@@ -4537,8 +4441,8 @@ class ChatMirror {
|
|
|
4537
4441
|
if (call.type !== "toolCall")
|
|
4538
4442
|
continue;
|
|
4539
4443
|
const row = this.toolRow(call.name, call.id, call.arguments);
|
|
4540
|
-
if (message
|
|
4541
|
-
row.updateResult(
|
|
4444
|
+
if (failed(message))
|
|
4445
|
+
row.updateResult(failureResult(message));
|
|
4542
4446
|
else
|
|
4543
4447
|
this.pending.set(call.id, row);
|
|
4544
4448
|
}
|
|
@@ -4554,20 +4458,15 @@ class ChatMirror {
|
|
|
4554
4458
|
handle(event) {
|
|
4555
4459
|
switch (event.type) {
|
|
4556
4460
|
case "message_start":
|
|
4557
|
-
if (event.message.role === "assistant")
|
|
4558
|
-
this.
|
|
4559
|
-
|
|
4560
|
-
} else
|
|
4461
|
+
if (event.message.role === "assistant")
|
|
4462
|
+
this.stream(event.message);
|
|
4463
|
+
else
|
|
4561
4464
|
this.add(event.message);
|
|
4562
4465
|
return true;
|
|
4563
4466
|
case "message_update":
|
|
4564
4467
|
if (event.message.role !== "assistant")
|
|
4565
4468
|
return false;
|
|
4566
|
-
|
|
4567
|
-
this.streaming = this.assistant(event.message, true);
|
|
4568
|
-
this.chat.addChild(this.streaming);
|
|
4569
|
-
}
|
|
4570
|
-
this.streaming.updateContent(event.message, true);
|
|
4469
|
+
(this.streaming ?? this.stream(event.message)).updateContent(event.message, true);
|
|
4571
4470
|
for (const call of event.message.content) {
|
|
4572
4471
|
if (call.type !== "toolCall")
|
|
4573
4472
|
continue;
|
|
@@ -4582,10 +4481,9 @@ class ChatMirror {
|
|
|
4582
4481
|
if (!this.streaming || event.message.role !== "assistant")
|
|
4583
4482
|
return false;
|
|
4584
4483
|
this.streaming.updateContent(event.message, false);
|
|
4585
|
-
if (event.message
|
|
4586
|
-
const text = event.message.stopReason === "aborted" ? ABORTED_TEXT : event.message.errorMessage || "Error";
|
|
4484
|
+
if (failed(event.message)) {
|
|
4587
4485
|
for (const row of this.pending.values())
|
|
4588
|
-
row.updateResult(
|
|
4486
|
+
row.updateResult(failureResult(event.message));
|
|
4589
4487
|
this.pending.clear();
|
|
4590
4488
|
} else
|
|
4591
4489
|
for (const row of this.pending.values())
|
|
@@ -4651,6 +4549,11 @@ class ChatMirror {
|
|
|
4651
4549
|
this.chat.addChild(new Spacer(1));
|
|
4652
4550
|
this.chat.addChild(new UserMessageComponent(text, getMarkdownTheme2()));
|
|
4653
4551
|
}
|
|
4552
|
+
stream(message) {
|
|
4553
|
+
this.streaming = this.assistant(message, true);
|
|
4554
|
+
this.chat.addChild(this.streaming);
|
|
4555
|
+
return this.streaming;
|
|
4556
|
+
}
|
|
4654
4557
|
assistant(message, streaming = false) {
|
|
4655
4558
|
const component = new AssistantMessageComponent(message, true, getMarkdownTheme2());
|
|
4656
4559
|
if (streaming)
|
|
@@ -4758,9 +4661,10 @@ var enTerms = {
|
|
|
4758
4661
|
};
|
|
4759
4662
|
var ALL_TERMS = [zhTerms, enTerms];
|
|
4760
4663
|
var escapeRegExp = (literal) => literal.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
4761
|
-
function termPattern(pick
|
|
4762
|
-
return `(?:${
|
|
4664
|
+
function termPattern(pick) {
|
|
4665
|
+
return `(?:${ALL_TERMS.map(pick).map(escapeRegExp).join("|")})`;
|
|
4763
4666
|
}
|
|
4667
|
+
var SUGGESTIONS_HEADING = new RegExp(`^##\\s+${termPattern((terms) => terms.suggestions)}\\s*$`, "iu");
|
|
4764
4668
|
var REDUNDANT_VERDICT_LINES = new Set([
|
|
4765
4669
|
"PASS",
|
|
4766
4670
|
"FAIL",
|
|
@@ -4808,9 +4712,7 @@ var msg6 = defineMessages({
|
|
|
4808
4712
|
passed: zhTerms.passed,
|
|
4809
4713
|
failed: zhTerms.failed,
|
|
4810
4714
|
round: (round, title) => `第 ${round} 轮${title}`,
|
|
4811
|
-
advisorNote: "顾问建议",
|
|
4812
4715
|
stoppedByAdvisor: "审查已由顾问终止",
|
|
4813
|
-
cancelled: "审查已取消",
|
|
4814
4716
|
incomplete: "审查未完成",
|
|
4815
4717
|
timeoutBlocker: `${zhTerms.blocker}:审查超时`,
|
|
4816
4718
|
timeoutReason: `${zhTerms.reason}:超过总体时限`,
|
|
@@ -4819,8 +4721,6 @@ var msg6 = defineMessages({
|
|
|
4819
4721
|
advisorGuidance: (decision) => `顾问指引 · ${decision}`,
|
|
4820
4722
|
modelLine: (model) => `**${zhTerms.model} · ${model}**`,
|
|
4821
4723
|
elapsed: (elapsed, total) => total === undefined ? `${zhTerms.elapsed}:${elapsed}` : `${zhTerms.elapsed}:${elapsed} / ${zhTerms.total} ${total}`,
|
|
4822
|
-
stoppedBy: { user: "已按你的操作停止", shutdown: "会话关闭时停止", other: "已停止" },
|
|
4823
|
-
stopReason: { advisor: "顾问建议停止", maxRounds: "已达到最大审查轮数" },
|
|
4824
4724
|
decision: { continue: "继续修复", narrow: "收窄范围", stop: "停止修复" }
|
|
4825
4725
|
},
|
|
4826
4726
|
summary: {
|
|
@@ -4850,11 +4750,11 @@ var msg6 = defineMessages({
|
|
|
4850
4750
|
advisorScope: "顾问收窄后的范围(以此为准):",
|
|
4851
4751
|
advisorAdvice: "顾问建议:",
|
|
4852
4752
|
materialTruncated: (omitted) => `[材料截断:省略 ${omitted} 字]`,
|
|
4853
|
-
materialLabel: { passed: "末轮审查结论:",
|
|
4753
|
+
materialLabel: { passed: "末轮审查结论:", max_rounds: "末轮未通过的发现:", advisor_stop: "顾问裁决:" },
|
|
4854
4754
|
summaryInstruction: {
|
|
4855
4755
|
passed: (rounds) => `对抗审查已通过(共 ${rounds} 轮)。请给用户一个简洁的人话收尾总结:1) 各轮审查发现了什么、你修了什么;2) 最终靠什么通过(关键修复与验证证据);3) 审查中提到但未阻塞通过的建议——逐条列出,并给出你建议处理还是不处理及理由。只做总结:不要修改代码、不要运行工具,直接以最终回复结束本回合。`,
|
|
4856
|
-
|
|
4857
|
-
|
|
4756
|
+
max_rounds: (rounds) => `对抗审查在 ${rounds} 轮内未能通过,已按上限终止。请如实向用户总结(人话):1) 各轮分别卡在什么发现上、你做了哪些修复尝试;2) 你认为无法收敛的根因;3) 当前代码的真实状态与剩余风险;4) 建议用户下一步怎么办。只做总结:不要再继续修改代码或运行工具,直接以最终回复结束本回合。`,
|
|
4757
|
+
advisor_stop: (rounds) => `对抗审查被顾问裁定终止(第 ${rounds} 轮)。请结合下方顾问裁决向用户总结:1) 审查循环走到了哪一步、修了什么;2) 顾问为什么叫停;3) 当前状态与你建议的下一步。只做总结:不要再修改代码或运行工具,直接以最终回复结束本回合。`
|
|
4858
4758
|
}
|
|
4859
4759
|
},
|
|
4860
4760
|
reviewer: {
|
|
@@ -4929,9 +4829,7 @@ Stopped by user`
|
|
|
4929
4829
|
passed: enTerms.passed,
|
|
4930
4830
|
failed: enTerms.failed,
|
|
4931
4831
|
round: (round, title) => `Round ${round} ${title}`,
|
|
4932
|
-
advisorNote: "Advisor note",
|
|
4933
4832
|
stoppedByAdvisor: "Review stopped by advisor",
|
|
4934
|
-
cancelled: "Review cancelled",
|
|
4935
4833
|
incomplete: "Review incomplete",
|
|
4936
4834
|
timeoutBlocker: `${enTerms.blocker}: review timed out`,
|
|
4937
4835
|
timeoutReason: `${enTerms.reason}: overall time limit exceeded`,
|
|
@@ -4940,8 +4838,6 @@ Stopped by user`
|
|
|
4940
4838
|
advisorGuidance: (decision) => `Advisor guidance · ${decision}`,
|
|
4941
4839
|
modelLine: (model) => `**${enTerms.model} · ${model}**`,
|
|
4942
4840
|
elapsed: (elapsed, total) => total === undefined ? `${enTerms.elapsed}: ${elapsed}` : `${enTerms.elapsed}: ${elapsed} / ${enTerms.total} ${total}`,
|
|
4943
|
-
stoppedBy: { user: "Stopped by user", shutdown: "Stopped when the session closed", other: "Stopped" },
|
|
4944
|
-
stopReason: { advisor: "Advisor recommends stopping", maxRounds: "Maximum review rounds reached" },
|
|
4945
4841
|
decision: { continue: "Continue fixing", narrow: "Narrow scope", stop: "Stop fixing" }
|
|
4946
4842
|
},
|
|
4947
4843
|
summary: {
|
|
@@ -4971,11 +4867,11 @@ Stopped by user`
|
|
|
4971
4867
|
advisorScope: "Advisor scope (authoritative):",
|
|
4972
4868
|
advisorAdvice: "Advisor note:",
|
|
4973
4869
|
materialTruncated: (omitted) => `[material truncated: ${omitted} characters omitted]`,
|
|
4974
|
-
materialLabel: { passed: "Final review verdict:",
|
|
4870
|
+
materialLabel: { passed: "Final review verdict:", max_rounds: "Final round findings:", advisor_stop: "Advisor ruling:" },
|
|
4975
4871
|
summaryInstruction: {
|
|
4976
4872
|
passed: (rounds) => `The adversarial review passed after ${rounds} round(s). Give the user a concise plain-language wrap-up: 1) what the review rounds found and what you fixed; 2) how it finally passed (key fixes and verification evidence); 3) non-blocking suggestions raised during review — list each and say whether you recommend addressing it and why. Summary only: do not change code or run tools; end this turn with the final reply.`,
|
|
4977
|
-
|
|
4978
|
-
|
|
4873
|
+
max_rounds: (rounds) => `The adversarial review did not pass within ${rounds} round(s) and stopped at the limit. Summarize honestly for the user in plain language: 1) what each round got stuck on and what fixes you attempted; 2) your root-cause read on why it would not converge; 3) the real current state of the code and remaining risks; 4) what you recommend the user do next. Summary only: do not keep changing code or run tools; end this turn with the final reply.`,
|
|
4874
|
+
advisor_stop: (rounds) => `The adversarial review was stopped by the advisor (round ${rounds}). Using the advisor ruling below, summarize for the user: 1) how far the review loop got and what was fixed; 2) why the advisor called it off; 3) the current state and your recommended next step. Summary only: do not change code or run tools; end this turn with the final reply.`
|
|
4979
4875
|
}
|
|
4980
4876
|
},
|
|
4981
4877
|
reviewer: {
|
|
@@ -5173,7 +5069,6 @@ class Line {
|
|
|
5173
5069
|
}
|
|
5174
5070
|
|
|
5175
5071
|
// tools/group-view.ts
|
|
5176
|
-
var rowData = toolFacts;
|
|
5177
5072
|
function actionLine(component) {
|
|
5178
5073
|
return component && "actionWord" in component && typeof component.actionWord === "string" ? component : undefined;
|
|
5179
5074
|
}
|
|
@@ -5253,8 +5148,8 @@ function scan(segment, activity, env) {
|
|
|
5253
5148
|
const age = settled ? env.clock.arrivalAge(item) : Infinity;
|
|
5254
5149
|
if (settled && age < ARRIVAL_FLASH_MS)
|
|
5255
5150
|
arrival = { text: settled.title, failed: settled.alarm, age };
|
|
5256
|
-
if (item instanceof ToolExecutionComponent2 &&
|
|
5257
|
-
running.push(
|
|
5151
|
+
if (item instanceof ToolExecutionComponent2 && toolFacts(item).isPartial)
|
|
5152
|
+
running.push(toolFacts(item));
|
|
5258
5153
|
}
|
|
5259
5154
|
const record = combineRounds(rounds);
|
|
5260
5155
|
return { notice, arrival, round: record?.round, earlier: record?.earlier, failures: failed.size, action: actionOf(running.at(-1), activity, env) };
|
|
@@ -5262,7 +5157,7 @@ function scan(segment, activity, env) {
|
|
|
5262
5157
|
function actionOf(tool, activity, env) {
|
|
5263
5158
|
if (tool) {
|
|
5264
5159
|
const line = actionLine(tool.callRendererComponent);
|
|
5265
|
-
return line ? { word: line.actionWord, target: line.actionTarget } : { word: tool.toolDefinition?.label ?? tool.toolName, target:
|
|
5160
|
+
return line ? { word: line.actionWord, target: line.actionTarget } : { word: tool.toolDefinition?.label ?? tool.toolName, target: toolTargetText(tool.toolName, tool.args, tool.cwd) };
|
|
5266
5161
|
}
|
|
5267
5162
|
if (activity)
|
|
5268
5163
|
return { word: msg4.activity[activity] };
|
|
@@ -5319,7 +5214,7 @@ class ToolItem {
|
|
|
5319
5214
|
}
|
|
5320
5215
|
invalidate() {}
|
|
5321
5216
|
render(width) {
|
|
5322
|
-
const data =
|
|
5217
|
+
const data = toolFacts(this.row);
|
|
5323
5218
|
if (!data.expanded) {
|
|
5324
5219
|
const renderer = actionLine(data.callRendererComponent) ?? compactLine(data, this.ui.theme);
|
|
5325
5220
|
return renderer.render(width);
|
|
@@ -5329,9 +5224,9 @@ class ToolItem {
|
|
|
5329
5224
|
return lines.slice(this.leadingRows);
|
|
5330
5225
|
}
|
|
5331
5226
|
handleMouse(event) {
|
|
5332
|
-
if (
|
|
5227
|
+
if (toolFacts(this.row).expanded)
|
|
5333
5228
|
return this.row.handleMouse({ ...event, y: event.y + this.leadingRows, height: event.height + this.leadingRows });
|
|
5334
|
-
if (event.type !== "click" || event.button !== "left" || !
|
|
5229
|
+
if (event.type !== "click" || event.button !== "left" || !toolFacts(this.row).result)
|
|
5335
5230
|
return;
|
|
5336
5231
|
this.toggle(this.row);
|
|
5337
5232
|
return { handled: true };
|
|
@@ -5517,7 +5412,7 @@ function processList(segment, env) {
|
|
|
5517
5412
|
return list;
|
|
5518
5413
|
}
|
|
5519
5414
|
function toggleToolDetails(row, setExpanded) {
|
|
5520
|
-
setExpanded.call(row, !
|
|
5415
|
+
setExpanded.call(row, !toolFacts(row).expanded);
|
|
5521
5416
|
}
|
|
5522
5417
|
|
|
5523
5418
|
// tools/grouping.ts
|
|
@@ -5672,21 +5567,14 @@ function attach(tui, ui, options) {
|
|
|
5672
5567
|
}
|
|
5673
5568
|
|
|
5674
5569
|
// tools/index.ts
|
|
5675
|
-
var cache = new Map;
|
|
5676
5570
|
function createTools(cwd) {
|
|
5677
|
-
return {
|
|
5678
|
-
read: createReadTool(cwd),
|
|
5679
|
-
bash: createBashTool(cwd),
|
|
5680
|
-
edit: createEditTool(cwd),
|
|
5681
|
-
write: createWriteTool(cwd)
|
|
5682
|
-
};
|
|
5571
|
+
return { read: createReadTool(cwd), bash: createBashTool(cwd), edit: createEditTool(cwd), write: createWriteTool(cwd) };
|
|
5683
5572
|
}
|
|
5573
|
+
var cache = new Map;
|
|
5684
5574
|
function tools(cwd) {
|
|
5685
5575
|
let value = cache.get(cwd);
|
|
5686
|
-
if (!value)
|
|
5687
|
-
value = createTools(cwd);
|
|
5688
|
-
cache.set(cwd, value);
|
|
5689
|
-
}
|
|
5576
|
+
if (!value)
|
|
5577
|
+
cache.set(cwd, value = createTools(cwd));
|
|
5690
5578
|
return value;
|
|
5691
5579
|
}
|
|
5692
5580
|
function lineCount(text) {
|
|
@@ -5697,8 +5585,13 @@ function lineCount(text) {
|
|
|
5697
5585
|
return text.endsWith(`
|
|
5698
5586
|
`) ? lines - 1 : lines;
|
|
5699
5587
|
}
|
|
5700
|
-
function
|
|
5701
|
-
return
|
|
5588
|
+
function shell(name, meta) {
|
|
5589
|
+
return {
|
|
5590
|
+
label: msg4.labels[name],
|
|
5591
|
+
renderShell: "self",
|
|
5592
|
+
execute: (id, params, signal, update, ctx) => executeTimed(id, () => tools(ctx.cwd)[name].execute(id, params, signal, update, ctx)),
|
|
5593
|
+
renderCall: (args, theme, ctx) => new ToolLine({ label: msg4.labels[name], ...toolTarget(name, args, ctx.cwd), meta: meta?.(args), theme, ctx })
|
|
5594
|
+
};
|
|
5702
5595
|
}
|
|
5703
5596
|
var EDIT_RESULT = makeResultRenderer(false);
|
|
5704
5597
|
var definitions;
|
|
@@ -5708,43 +5601,11 @@ function toolDefinitions() {
|
|
|
5708
5601
|
function buildDefinitions() {
|
|
5709
5602
|
const initial = tools(process.cwd());
|
|
5710
5603
|
return {
|
|
5711
|
-
read: defineTool({
|
|
5712
|
-
|
|
5713
|
-
label: LABEL.read,
|
|
5714
|
-
renderShell: "self",
|
|
5715
|
-
execute: (id, params, signal, update, ctx) => executeTimed(id, () => invoke(tools(ctx.cwd).read.execute, [id, params, signal, update, ctx])),
|
|
5716
|
-
renderCall: (args, theme, ctx) => new ToolLine({
|
|
5717
|
-
label: LABEL.read,
|
|
5718
|
-
...toolTarget("read", args, ctx.cwd),
|
|
5719
|
-
theme,
|
|
5720
|
-
ctx
|
|
5721
|
-
}),
|
|
5722
|
-
renderResult: makeResultRenderer(true)
|
|
5723
|
-
}),
|
|
5724
|
-
bash: defineTool({
|
|
5725
|
-
...initial.bash,
|
|
5726
|
-
label: LABEL.bash,
|
|
5727
|
-
renderShell: "self",
|
|
5728
|
-
execute: (id, params, signal, update, ctx) => executeTimed(id, () => invoke(tools(ctx.cwd).bash.execute, [id, params, signal, update, ctx])),
|
|
5729
|
-
renderCall: (args, theme, ctx) => new ToolLine({
|
|
5730
|
-
label: LABEL.bash,
|
|
5731
|
-
...toolTarget("bash", args, ctx.cwd),
|
|
5732
|
-
theme,
|
|
5733
|
-
ctx
|
|
5734
|
-
}),
|
|
5735
|
-
renderResult: makeResultRenderer(true)
|
|
5736
|
-
}),
|
|
5604
|
+
read: defineTool({ ...initial.read, ...shell("read"), renderResult: makeResultRenderer(true) }),
|
|
5605
|
+
bash: defineTool({ ...initial.bash, ...shell("bash"), renderResult: makeResultRenderer(true) }),
|
|
5737
5606
|
edit: defineTool({
|
|
5738
5607
|
...initial.edit,
|
|
5739
|
-
|
|
5740
|
-
renderShell: "self",
|
|
5741
|
-
execute: (id, params, signal, update, ctx) => executeTimed(id, () => invoke(tools(ctx.cwd).edit.execute, [id, params, signal, update, ctx])),
|
|
5742
|
-
renderCall: (args, theme, ctx) => new ToolLine({
|
|
5743
|
-
label: LABEL.edit,
|
|
5744
|
-
...toolTarget("edit", args, ctx.cwd),
|
|
5745
|
-
theme,
|
|
5746
|
-
ctx
|
|
5747
|
-
}),
|
|
5608
|
+
...shell("edit"),
|
|
5748
5609
|
renderResult(result, options, theme, ctx) {
|
|
5749
5610
|
const details = result.details;
|
|
5750
5611
|
const diff = !ctx.isError && typeof details?.diff === "string" ? details.diff : undefined;
|
|
@@ -5755,16 +5616,7 @@ function buildDefinitions() {
|
|
|
5755
5616
|
}),
|
|
5756
5617
|
write: defineTool({
|
|
5757
5618
|
...initial.write,
|
|
5758
|
-
|
|
5759
|
-
renderShell: "self",
|
|
5760
|
-
execute: (id, params, signal, update, ctx) => executeTimed(id, () => invoke(tools(ctx.cwd).write.execute, [id, params, signal, update, ctx])),
|
|
5761
|
-
renderCall: (args, theme, ctx) => new ToolLine({
|
|
5762
|
-
label: LABEL.write,
|
|
5763
|
-
...toolTarget("write", args, ctx.cwd),
|
|
5764
|
-
meta: [{ text: ` +${lineCount(args.content ?? "")}`, color: "toolDiffAdded" }],
|
|
5765
|
-
theme,
|
|
5766
|
-
ctx
|
|
5767
|
-
}),
|
|
5619
|
+
...shell("write", (args) => [{ text: " +" + lineCount(args.content ?? ""), color: "toolDiffAdded" }]),
|
|
5768
5620
|
renderResult: makeResultRenderer(false)
|
|
5769
5621
|
})
|
|
5770
5622
|
};
|
|
@@ -5805,7 +5657,7 @@ import { randomUUID } from "node:crypto";
|
|
|
5805
5657
|
|
|
5806
5658
|
// master/spawn.ts
|
|
5807
5659
|
import { existsSync as existsSync2 } from "node:fs";
|
|
5808
|
-
import { basename, dirname as
|
|
5660
|
+
import { basename, dirname as dirname2 } from "node:path";
|
|
5809
5661
|
import {
|
|
5810
5662
|
createAgentSession,
|
|
5811
5663
|
createCodemodeExtension,
|
|
@@ -5830,8 +5682,7 @@ var msg7 = defineMessages({
|
|
|
5830
5682
|
thinking: "可选思考档覆盖;省略时使用角色原子档或当前档。",
|
|
5831
5683
|
cwd: "Worker 工作目录的绝对路径;start 默认当前目录,send 给空闲 Worker 换检出时带上(同一会话重开)。",
|
|
5832
5684
|
review: "按审查纪律为 start/send 记录义务;true 不自动开审。",
|
|
5833
|
-
onlyInMaster: (tool) => `${tool} 只在 Master
|
|
5834
|
-
unknownAction: (action) => `未知 subagents action:${action}`
|
|
5685
|
+
onlyInMaster: (tool) => `${tool} 只在 Master 中可用`
|
|
5835
5686
|
},
|
|
5836
5687
|
command: {
|
|
5837
5688
|
description: "翻转当前会话的指挥官模式;status 查看状态",
|
|
@@ -5931,7 +5782,6 @@ var msg7 = defineMessages({
|
|
|
5931
5782
|
},
|
|
5932
5783
|
guard: (path) => `子代理只能修改当前 checkout 或系统临时目录:${path}`,
|
|
5933
5784
|
state: {
|
|
5934
|
-
legacy: (version, current) => `Master Worker Pool 状态是旧版 v${version}(当前 v${current}),不再读取;重新启动指挥官模式会从空池重建,旧运行时进程不会纳入新池,需要手动清理`,
|
|
5935
5785
|
invalidJson: (path) => `Master Worker Pool 状态不是合法 JSON:${path}`,
|
|
5936
5786
|
invalidShape: (path) => `Master Worker Pool 状态结构无效:${path}`,
|
|
5937
5787
|
missing: (name) => `子代理不存在:${name}`,
|
|
@@ -5995,8 +5845,7 @@ var msg7 = defineMessages({
|
|
|
5995
5845
|
thinking: "Optional thinking-level override; defaults to the role atom's level or the current level.",
|
|
5996
5846
|
cwd: "Absolute path of the Worker's working directory; start defaults to the current directory, and send passes it to move an idle Worker to another checkout (the same session is reopened).",
|
|
5997
5847
|
review: "Record a review obligation for start/send per the review discipline; true does not start a review automatically.",
|
|
5998
|
-
onlyInMaster: (tool) => `${tool} is only available in Master mode
|
|
5999
|
-
unknownAction: (action) => `Unknown subagents action: ${action}`
|
|
5848
|
+
onlyInMaster: (tool) => `${tool} is only available in Master mode`
|
|
6000
5849
|
},
|
|
6001
5850
|
command: {
|
|
6002
5851
|
description: "Toggle Master mode for the current session; status shows the state",
|
|
@@ -6096,7 +5945,6 @@ var msg7 = defineMessages({
|
|
|
6096
5945
|
},
|
|
6097
5946
|
guard: (path) => `Workers may only modify the current checkout or the system temp directory: ${path}`,
|
|
6098
5947
|
state: {
|
|
6099
|
-
legacy: (version, current) => `The Master Worker Pool state is legacy v${version} (current v${current}) and is no longer read; restarting Master mode rebuilds from an empty pool, old runtime processes are not adopted into the new pool and need manual cleanup`,
|
|
6100
5948
|
invalidJson: (path) => `The Master Worker Pool state is not valid JSON: ${path}`,
|
|
6101
5949
|
invalidShape: (path) => `The Master Worker Pool state has an invalid structure: ${path}`,
|
|
6102
5950
|
missing: (name) => `Worker does not exist: ${name}`,
|
|
@@ -6237,7 +6085,6 @@ class InProcessSessionPool {
|
|
|
6237
6085
|
this.held.set(key, held);
|
|
6238
6086
|
return {
|
|
6239
6087
|
session: created,
|
|
6240
|
-
sessionPath,
|
|
6241
6088
|
prompt: (text) => created.prompt(text),
|
|
6242
6089
|
dispose: () => this.release(held)
|
|
6243
6090
|
};
|
|
@@ -6304,7 +6151,7 @@ class InProcessSessionPool {
|
|
|
6304
6151
|
}
|
|
6305
6152
|
}
|
|
6306
6153
|
function preallocateWorkerSession(mainSessionPath, cwd) {
|
|
6307
|
-
const sessionPath = SessionManager.create(cwd, `${
|
|
6154
|
+
const sessionPath = SessionManager.create(cwd, `${dirname2(mainSessionPath)}/subagents`).getSessionFile();
|
|
6308
6155
|
if (!sessionPath)
|
|
6309
6156
|
throw new Error(msg7.spawn.noSessionPath);
|
|
6310
6157
|
return sessionPath;
|
|
@@ -6312,7 +6159,7 @@ function preallocateWorkerSession(mainSessionPath, cwd) {
|
|
|
6312
6159
|
function makeSessionManager(persistence, cwd) {
|
|
6313
6160
|
if (persistence.type === "memory")
|
|
6314
6161
|
return SessionManager.inMemory(cwd);
|
|
6315
|
-
return SessionManager.open(persistence.sessionPath,
|
|
6162
|
+
return SessionManager.open(persistence.sessionPath, dirname2(persistence.sessionPath), cwd);
|
|
6316
6163
|
}
|
|
6317
6164
|
function matchesExtension(path, exclusions) {
|
|
6318
6165
|
return exclusions.some((excluded) => excluded === path || excluded === basename(path));
|
|
@@ -6321,257 +6168,603 @@ function matchesExtension(path, exclusions) {
|
|
|
6321
6168
|
// review/card.ts
|
|
6322
6169
|
import { getMarkdownTheme as getMarkdownTheme4 } from "@earendil-works/pi-coding-agent";
|
|
6323
6170
|
import { Box, Markdown as Markdown3, Spacer as Spacer4, Text as Text5 } from "@earendil-works/pi-tui";
|
|
6324
|
-
var CARD_TYPE = "firecode-review-card";
|
|
6325
|
-
var VERSION2 = 1;
|
|
6326
|
-
var CARD_KINDS = new Set([
|
|
6327
|
-
"queued",
|
|
6328
|
-
"start",
|
|
6329
|
-
"pass",
|
|
6330
|
-
"fail",
|
|
6331
|
-
"stop",
|
|
6332
|
-
"cancel",
|
|
6333
|
-
"timeout",
|
|
6334
|
-
"error",
|
|
6335
|
-
"advisor"
|
|
6336
|
-
]);
|
|
6337
|
-
var CARD_TONES = new Set(["success", "warning", "error", "neutral", "accent"]);
|
|
6338
|
-
var MARKS = {
|
|
6339
|
-
start: { glyph: "⠿", color: "review" },
|
|
6340
|
-
advisor: { glyph: "⠿", color: "review" },
|
|
6341
|
-
pass: { glyph: "✓", color: "success" },
|
|
6342
|
-
fail: { glyph: "✗", color: "error" },
|
|
6343
|
-
stop: { glyph: "✗", color: "error" },
|
|
6344
|
-
timeout: { glyph: "◌", color: "error" },
|
|
6345
|
-
error: { glyph: "◌", color: "error" },
|
|
6346
|
-
cancel: { glyph: "‖", color: "muted" }
|
|
6347
|
-
};
|
|
6348
|
-
function paintMark(details, theme) {
|
|
6349
|
-
const mark = MARKS[details.kind];
|
|
6350
|
-
if (!mark)
|
|
6351
|
-
return details.icon;
|
|
6352
|
-
return mark.color === "review" ? paint(HEAT_COLORS.gold, details.icon) : theme.fg(mark.color, details.icon);
|
|
6353
|
-
}
|
|
6354
|
-
function isValidCardDetails(value) {
|
|
6355
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
6356
|
-
return false;
|
|
6357
|
-
const record = value;
|
|
6358
|
-
if (Object.keys(record).length !== 6)
|
|
6359
|
-
return false;
|
|
6360
|
-
if (record.version !== VERSION2)
|
|
6361
|
-
return false;
|
|
6362
|
-
if (typeof record.kind !== "string" || !CARD_KINDS.has(record.kind))
|
|
6363
|
-
return false;
|
|
6364
|
-
if (typeof record.title !== "string")
|
|
6365
|
-
return false;
|
|
6366
|
-
if (!Array.isArray(record.lines) || !record.lines.every((line) => typeof line === "string"))
|
|
6367
|
-
return false;
|
|
6368
|
-
if (typeof record.tone !== "string" || !CARD_TONES.has(record.tone))
|
|
6369
|
-
return false;
|
|
6370
|
-
return typeof record.icon === "string";
|
|
6371
|
-
}
|
|
6372
|
-
function registerCardRenderer(pi) {
|
|
6373
|
-
pi.registerMessageRenderer(CARD_TYPE, (message, _options, theme) => new ReviewCard(message.details, message.content, theme));
|
|
6374
|
-
}
|
|
6375
6171
|
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
return this.fallback.render(Math.max(1, width));
|
|
6395
|
-
} catch {
|
|
6396
|
-
return [];
|
|
6397
|
-
}
|
|
6398
|
-
}
|
|
6399
|
-
}
|
|
6400
|
-
invalidate() {
|
|
6401
|
-
this.card?.invalidate?.();
|
|
6402
|
-
this.fallback.invalidate?.();
|
|
6403
|
-
}
|
|
6172
|
+
// review/state.ts
|
|
6173
|
+
var ISSUE_LINE = new RegExp(`^[-*+]\\s*(?:\\*\\*)?${termPattern((terms) => terms.field.issue)}(?:\\*\\*)?\\s*[::]\\s*(.+)$`, "iu");
|
|
6174
|
+
function initialState(runId) {
|
|
6175
|
+
return {
|
|
6176
|
+
runId,
|
|
6177
|
+
phase: "idle",
|
|
6178
|
+
round: 0,
|
|
6179
|
+
focus: "",
|
|
6180
|
+
history: [],
|
|
6181
|
+
active: null,
|
|
6182
|
+
pending: null,
|
|
6183
|
+
repair: null,
|
|
6184
|
+
summary: null,
|
|
6185
|
+
consecutiveFailures: 0,
|
|
6186
|
+
startedAt: 0,
|
|
6187
|
+
roundStartedAt: 0,
|
|
6188
|
+
updatedAt: 0
|
|
6189
|
+
};
|
|
6404
6190
|
}
|
|
6405
|
-
function
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6191
|
+
function reduce(state, event, limits, now) {
|
|
6192
|
+
switch (event.type) {
|
|
6193
|
+
case "START":
|
|
6194
|
+
return onStart(state, event, now);
|
|
6195
|
+
case "RECOVER":
|
|
6196
|
+
return onRecover(state);
|
|
6197
|
+
case "REVIEWER_SETTLED":
|
|
6198
|
+
return onReviewerSettled(state, event, limits, now);
|
|
6199
|
+
case "ADVISOR_SETTLED":
|
|
6200
|
+
return onAdvisorSettled(state, event, limits, now);
|
|
6201
|
+
case "ADVISOR_SKIPPED":
|
|
6202
|
+
return onAdvisorSkipped(state, now);
|
|
6203
|
+
case "INFRASTRUCTURE_ERROR":
|
|
6204
|
+
return onInfrastructureError(state, event.details, now);
|
|
6205
|
+
case "ADVANCE":
|
|
6206
|
+
return onAdvance(state, limits, now);
|
|
6207
|
+
case "FEEDBACK_DISPATCHED":
|
|
6208
|
+
return updateRepairStatus(state, "pending", "awaiting_start", now);
|
|
6209
|
+
case "REPAIR_STARTED":
|
|
6210
|
+
return updateRepairStatus(state, "awaiting_start", "running", now);
|
|
6211
|
+
case "REPAIR_COMPLETED":
|
|
6212
|
+
return updateRepairStatus(state, "running", "completed", now);
|
|
6213
|
+
case "SUMMARY_DISPATCHED":
|
|
6214
|
+
return updateSummaryStatus(state, "pending", "awaiting_start", now);
|
|
6215
|
+
case "SUMMARY_STARTED":
|
|
6216
|
+
return updateSummaryStatus(state, "awaiting_start", "running", now);
|
|
6217
|
+
case "SUMMARY_SETTLED":
|
|
6218
|
+
return onSummarySettled(state, now);
|
|
6219
|
+
case "CANCEL":
|
|
6220
|
+
return onInterrupted(state, now, "cancelled", event.reason, event.reason === "user" ? [{ kind: "notify_cancelled" }] : []);
|
|
6221
|
+
case "TIMEOUT":
|
|
6222
|
+
return onInterrupted(state, now, "timed_out", "timeout", [{ kind: "send_card", card: { kind: "timeout" } }]);
|
|
6223
|
+
}
|
|
6412
6224
|
}
|
|
6413
|
-
function
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
return "customMessageBg";
|
|
6225
|
+
function onStart(state, event, now) {
|
|
6226
|
+
return {
|
|
6227
|
+
state: { ...initialState(state.runId), phase: "queued", focus: event.focus.trim(), startedAt: now, updatedAt: now },
|
|
6228
|
+
effects: [{ kind: "advance" }]
|
|
6229
|
+
};
|
|
6419
6230
|
}
|
|
6420
|
-
function
|
|
6421
|
-
if (
|
|
6422
|
-
return
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
return
|
|
6426
|
-
`);
|
|
6231
|
+
function onRecover(state) {
|
|
6232
|
+
if (state.phase === "idle" || state.phase === "settled")
|
|
6233
|
+
return { state, effects: [] };
|
|
6234
|
+
const repair = state.phase === "awaiting_fix" && state.repair && state.repair.status !== "completed" ? { ...state.repair, status: "pending" } : state.repair;
|
|
6235
|
+
const summary = state.phase === "summarizing" && state.summary ? { ...state.summary, status: "pending" } : state.summary;
|
|
6236
|
+
return { state: { ...state, repair, summary }, effects: [] };
|
|
6427
6237
|
}
|
|
6428
|
-
function
|
|
6429
|
-
|
|
6238
|
+
function updateRepairStatus(state, expected, status, now) {
|
|
6239
|
+
if (state.phase !== "awaiting_fix" || state.repair?.status !== expected)
|
|
6240
|
+
return { state, effects: [] };
|
|
6241
|
+
return {
|
|
6242
|
+
state: { ...state, repair: { ...state.repair, status }, updatedAt: now },
|
|
6243
|
+
effects: []
|
|
6244
|
+
};
|
|
6430
6245
|
}
|
|
6431
|
-
function
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
return failed(card);
|
|
6439
|
-
case "stop":
|
|
6440
|
-
return stopped(card);
|
|
6441
|
-
case "cancel":
|
|
6442
|
-
return cancelled(card);
|
|
6443
|
-
case "timeout":
|
|
6444
|
-
return timedOut(card);
|
|
6445
|
-
case "error":
|
|
6446
|
-
return errored(card);
|
|
6447
|
-
case "advisor":
|
|
6448
|
-
return advisorCard(card);
|
|
6449
|
-
}
|
|
6246
|
+
function updateSummaryStatus(state, expected, status, now) {
|
|
6247
|
+
if (state.phase !== "summarizing" || state.summary?.status !== expected)
|
|
6248
|
+
return { state, effects: [] };
|
|
6249
|
+
return {
|
|
6250
|
+
state: { ...state, summary: { ...state.summary, status }, updatedAt: now },
|
|
6251
|
+
effects: []
|
|
6252
|
+
};
|
|
6450
6253
|
}
|
|
6451
|
-
function
|
|
6452
|
-
|
|
6254
|
+
function onSummarySettled(state, now) {
|
|
6255
|
+
if (state.phase !== "summarizing")
|
|
6256
|
+
return { state, effects: [] };
|
|
6257
|
+
return {
|
|
6258
|
+
state: { ...state, phase: "settled", summary: null, updatedAt: now },
|
|
6259
|
+
effects: []
|
|
6260
|
+
};
|
|
6453
6261
|
}
|
|
6454
|
-
function
|
|
6455
|
-
return
|
|
6262
|
+
function summarizing(kind) {
|
|
6263
|
+
return { phase: "summarizing", summary: { kind, status: "pending" } };
|
|
6456
6264
|
}
|
|
6457
|
-
function
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6265
|
+
function onAdvance(state, limits, now) {
|
|
6266
|
+
if (state.phase === "queued")
|
|
6267
|
+
return {
|
|
6268
|
+
state: beginRound(state, 1, limits, now),
|
|
6269
|
+
effects: [
|
|
6270
|
+
{ kind: "send_card", card: { kind: "start", models: limits.reviewers.map((item) => item.model) } },
|
|
6271
|
+
{ kind: "advance" }
|
|
6272
|
+
]
|
|
6273
|
+
};
|
|
6274
|
+
if (state.phase !== "awaiting_fix" || state.repair?.status !== "completed")
|
|
6275
|
+
return { state, effects: [] };
|
|
6276
|
+
return {
|
|
6277
|
+
state: beginRound(state, state.round + 1, limits, now),
|
|
6278
|
+
effects: [{ kind: "advance" }]
|
|
6279
|
+
};
|
|
6463
6280
|
}
|
|
6464
|
-
function
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6281
|
+
function onReviewerSettled(state, event, limits, now) {
|
|
6282
|
+
if (state.phase !== "reviewing" || !state.active)
|
|
6283
|
+
return { state, effects: [] };
|
|
6284
|
+
const active = state.active;
|
|
6285
|
+
const reviewers = active.reviewers.map((item) => item.index === event.index ? { ...item, status: event.result.status, result: event.result } : item);
|
|
6286
|
+
const settledCount = active.settledCount + 1;
|
|
6287
|
+
const nextActive = { ...active, reviewers, settledCount };
|
|
6288
|
+
if (settledCount < reviewers.length)
|
|
6289
|
+
return { state: { ...state, active: nextActive, updatedAt: now }, effects: [] };
|
|
6290
|
+
return settleRound(state, nextActive, resultsOf(reviewers), limits, now);
|
|
6471
6291
|
}
|
|
6472
|
-
function
|
|
6473
|
-
const
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6292
|
+
function settleRound(state, active, settled, limits, now) {
|
|
6293
|
+
const { result, details, feedbackDetails, summary } = aggregate(settled);
|
|
6294
|
+
const base = { ...state, active: null, updatedAt: now };
|
|
6295
|
+
const round = closeRound(state, now, { round: active.round, result, details, reviewers: settled });
|
|
6296
|
+
const history = [...state.history, round];
|
|
6297
|
+
if (result === "passed")
|
|
6298
|
+
return {
|
|
6299
|
+
state: { ...base, ...summarizing("passed"), history },
|
|
6300
|
+
effects: [
|
|
6301
|
+
{
|
|
6302
|
+
kind: "send_card",
|
|
6303
|
+
card: {
|
|
6304
|
+
kind: "pass",
|
|
6305
|
+
round: active.round,
|
|
6306
|
+
summary: appendClosedFindings(summary, state.history, active.round),
|
|
6307
|
+
elapsedMs: round.elapsedMs,
|
|
6308
|
+
totalElapsedMs: Math.max(0, now - state.startedAt)
|
|
6309
|
+
}
|
|
6310
|
+
},
|
|
6311
|
+
{ kind: "advance" }
|
|
6312
|
+
]
|
|
6313
|
+
};
|
|
6314
|
+
if (result === "error")
|
|
6315
|
+
return {
|
|
6316
|
+
state: { ...base, phase: "settled", history },
|
|
6317
|
+
effects: [{ kind: "send_card", card: { kind: "error", message: details, elapsedMs: round.elapsedMs } }]
|
|
6318
|
+
};
|
|
6319
|
+
const consecutiveFailures = state.consecutiveFailures + 1;
|
|
6320
|
+
if (active.round >= limits.maxRounds)
|
|
6321
|
+
return {
|
|
6322
|
+
state: { ...base, ...summarizing("max_rounds"), consecutiveFailures, history },
|
|
6323
|
+
effects: [
|
|
6324
|
+
{ kind: "send_card", card: { kind: "stop", reason: "max_rounds", round: active.round, details, elapsedMs: round.elapsedMs } },
|
|
6325
|
+
{ kind: "advance" }
|
|
6326
|
+
]
|
|
6327
|
+
};
|
|
6328
|
+
const failCard = {
|
|
6329
|
+
kind: "send_card",
|
|
6330
|
+
card: { kind: "fail", round: active.round, details, elapsedMs: round.elapsedMs }
|
|
6331
|
+
};
|
|
6332
|
+
if (consecutiveFailures >= limits.advisorAfterFailures)
|
|
6333
|
+
return {
|
|
6334
|
+
state: {
|
|
6335
|
+
...base,
|
|
6336
|
+
phase: "needs_fix",
|
|
6337
|
+
pending: { round: active.round, reviewers: settled, details: feedbackDetails },
|
|
6338
|
+
consecutiveFailures
|
|
6339
|
+
},
|
|
6340
|
+
effects: [failCard, { kind: "advance" }]
|
|
6341
|
+
};
|
|
6342
|
+
return {
|
|
6343
|
+
state: {
|
|
6344
|
+
...base,
|
|
6345
|
+
phase: "awaiting_fix",
|
|
6346
|
+
pending: null,
|
|
6347
|
+
repair: { details: feedbackDetails, advisor: null, status: "pending" },
|
|
6348
|
+
consecutiveFailures,
|
|
6349
|
+
history
|
|
6350
|
+
},
|
|
6351
|
+
effects: [failCard, { kind: "advance" }]
|
|
6352
|
+
};
|
|
6353
|
+
}
|
|
6354
|
+
function onAdvisorSettled(state, event, limits, now) {
|
|
6355
|
+
if (state.phase !== "needs_fix" || !state.pending)
|
|
6356
|
+
return { state, effects: [] };
|
|
6357
|
+
const pending = state.pending;
|
|
6358
|
+
const advisor = event.result;
|
|
6359
|
+
const consultMs = Math.max(0, now - state.updatedAt);
|
|
6360
|
+
if (advisor.verdict === "stop") {
|
|
6361
|
+
const round = closeRound(state, now, { round: pending.round, result: "stopped", details: advisor.advice, reviewers: pending.reviewers, advisor });
|
|
6362
|
+
return {
|
|
6363
|
+
state: { ...state, ...summarizing("advisor_stop"), pending: null, history: [...state.history, round], updatedAt: now },
|
|
6364
|
+
effects: [
|
|
6365
|
+
{
|
|
6366
|
+
kind: "send_card",
|
|
6367
|
+
card: { kind: "stop", reason: "advisor", round: pending.round, details: advisor.advice, advisor, advisorModel: limits.advisorModel, elapsedMs: consultMs }
|
|
6368
|
+
},
|
|
6369
|
+
{ kind: "advance" }
|
|
6370
|
+
]
|
|
6371
|
+
};
|
|
6478
6372
|
}
|
|
6479
|
-
const
|
|
6480
|
-
|
|
6481
|
-
|
|
6373
|
+
const round = closeRound(state, now, { round: pending.round, result: "failed", details: pending.details, reviewers: pending.reviewers, advisor });
|
|
6374
|
+
return {
|
|
6375
|
+
state: {
|
|
6376
|
+
...state,
|
|
6377
|
+
phase: "awaiting_fix",
|
|
6378
|
+
pending: null,
|
|
6379
|
+
repair: { details: pending.details, advisor, status: "pending" },
|
|
6380
|
+
history: [...state.history, round],
|
|
6381
|
+
updatedAt: now
|
|
6382
|
+
},
|
|
6383
|
+
effects: [
|
|
6384
|
+
{ kind: "send_card", card: { kind: "advisor", advisor, advisorModel: limits.advisorModel, elapsedMs: consultMs } },
|
|
6385
|
+
{ kind: "advance" }
|
|
6386
|
+
]
|
|
6387
|
+
};
|
|
6482
6388
|
}
|
|
6483
|
-
function
|
|
6484
|
-
|
|
6389
|
+
function onAdvisorSkipped(state, now) {
|
|
6390
|
+
if (state.phase !== "needs_fix" || !state.pending)
|
|
6391
|
+
return { state, effects: [] };
|
|
6392
|
+
const pending = state.pending;
|
|
6393
|
+
const round = closeRound(state, now, { round: pending.round, result: "failed", details: pending.details, reviewers: pending.reviewers });
|
|
6394
|
+
return {
|
|
6395
|
+
state: {
|
|
6396
|
+
...state,
|
|
6397
|
+
phase: "awaiting_fix",
|
|
6398
|
+
pending: null,
|
|
6399
|
+
repair: { details: pending.details, advisor: null, status: "pending" },
|
|
6400
|
+
history: [...state.history, round],
|
|
6401
|
+
updatedAt: now
|
|
6402
|
+
},
|
|
6403
|
+
effects: [{ kind: "advance" }]
|
|
6404
|
+
};
|
|
6485
6405
|
}
|
|
6486
|
-
function
|
|
6487
|
-
|
|
6406
|
+
function onInterrupted(state, now, result, reason, effects) {
|
|
6407
|
+
if (state.phase === "idle" || state.phase === "settled")
|
|
6408
|
+
return { state, effects: [] };
|
|
6409
|
+
if (state.phase === "summarizing")
|
|
6410
|
+
return onSummarySettled(state, now);
|
|
6411
|
+
const round = interruptedRound(state, now, result, reason);
|
|
6412
|
+
return {
|
|
6413
|
+
state: {
|
|
6414
|
+
...state,
|
|
6415
|
+
phase: "settled",
|
|
6416
|
+
active: null,
|
|
6417
|
+
pending: null,
|
|
6418
|
+
repair: null,
|
|
6419
|
+
history: round ? [...state.history, round] : state.history,
|
|
6420
|
+
updatedAt: now
|
|
6421
|
+
},
|
|
6422
|
+
effects
|
|
6423
|
+
};
|
|
6488
6424
|
}
|
|
6489
|
-
function
|
|
6490
|
-
if (
|
|
6491
|
-
return
|
|
6492
|
-
if (
|
|
6493
|
-
return
|
|
6494
|
-
|
|
6425
|
+
function onInfrastructureError(state, details, now) {
|
|
6426
|
+
if (state.phase === "idle" || state.phase === "settled")
|
|
6427
|
+
return { state, effects: [] };
|
|
6428
|
+
if (state.phase === "summarizing")
|
|
6429
|
+
return onSummarySettled(state, now);
|
|
6430
|
+
const reviewers = state.active ? resultsOf(state.active.reviewers) : state.pending?.reviewers ?? [];
|
|
6431
|
+
const round = state.round > 0 ? closeRound(state, now, { round: state.round, result: "error", details, reviewers }) : undefined;
|
|
6432
|
+
return {
|
|
6433
|
+
state: {
|
|
6434
|
+
...state,
|
|
6435
|
+
phase: "settled",
|
|
6436
|
+
active: null,
|
|
6437
|
+
pending: null,
|
|
6438
|
+
repair: null,
|
|
6439
|
+
history: round ? [...state.history, round] : state.history,
|
|
6440
|
+
updatedAt: now
|
|
6441
|
+
},
|
|
6442
|
+
effects: [{ kind: "send_card", card: { kind: "error", message: details } }]
|
|
6443
|
+
};
|
|
6495
6444
|
}
|
|
6496
|
-
function
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6445
|
+
function beginRound(state, round, limits, now) {
|
|
6446
|
+
const reviewers = limits.reviewers.map((item, index) => ({
|
|
6447
|
+
index,
|
|
6448
|
+
model: item.model,
|
|
6449
|
+
thinking: item.thinking,
|
|
6450
|
+
status: "running",
|
|
6451
|
+
result: null
|
|
6452
|
+
}));
|
|
6453
|
+
return {
|
|
6454
|
+
...state,
|
|
6455
|
+
phase: "reviewing",
|
|
6456
|
+
round,
|
|
6457
|
+
active: { round, reviewers, settledCount: 0 },
|
|
6458
|
+
pending: null,
|
|
6459
|
+
repair: null,
|
|
6460
|
+
summary: null,
|
|
6461
|
+
roundStartedAt: now,
|
|
6462
|
+
updatedAt: now
|
|
6463
|
+
};
|
|
6502
6464
|
}
|
|
6503
|
-
function
|
|
6504
|
-
|
|
6505
|
-
msg6.card.errorBlocker,
|
|
6506
|
-
msg6.card.errorReason(card.message),
|
|
6507
|
-
...card.elapsedMs === undefined ? [] : ["", elapsedLine(card.elapsedMs, card.totalElapsedMs, false)]
|
|
6508
|
-
];
|
|
6509
|
-
return spec("error", msg6.card.incomplete, lines, "warning");
|
|
6465
|
+
function resultsOf(reviewers) {
|
|
6466
|
+
return reviewers.flatMap((item) => item.result ? [item.result] : []);
|
|
6510
6467
|
}
|
|
6511
|
-
function
|
|
6512
|
-
const
|
|
6513
|
-
const
|
|
6514
|
-
|
|
6515
|
-
|
|
6468
|
+
function aggregate(reviewers) {
|
|
6469
|
+
const verdicts = reviewers.filter((item) => item.status === "passed" || item.status === "failed");
|
|
6470
|
+
const details = aggregateDetails(reviewers);
|
|
6471
|
+
if (verdicts.length === 0)
|
|
6472
|
+
return { result: "error", details, feedbackDetails: "", summary: "" };
|
|
6473
|
+
const failed = verdicts.filter((item) => item.status === "failed");
|
|
6474
|
+
if (failed.length > 0)
|
|
6475
|
+
return { result: "failed", details, feedbackDetails: aggregateDetails(failed), summary: "" };
|
|
6476
|
+
const absent = reviewers.filter((item) => item.status === "error");
|
|
6477
|
+
return { result: "passed", details, feedbackDetails: "", summary: aggregatePassSummary(verdicts, absent) };
|
|
6516
6478
|
}
|
|
6517
|
-
function
|
|
6518
|
-
return
|
|
6479
|
+
function aggregateDetails(reviewers) {
|
|
6480
|
+
return reviewers.map((item) => `${modelLabel(item)}
|
|
6481
|
+
${item.details.trim()}`).join(`
|
|
6482
|
+
|
|
6483
|
+
`);
|
|
6519
6484
|
}
|
|
6520
|
-
function
|
|
6521
|
-
|
|
6485
|
+
function aggregatePassSummary(passed, absent) {
|
|
6486
|
+
const fallback = msg6.summary.passedFallback;
|
|
6487
|
+
const lines = passed.length === 1 ? [passBody(passed[0]?.summary ?? "") || fallback] : passed.map((item) => msg6.summary.modelBullet(shortModel(item.model), passBody(item.summary) || fallback));
|
|
6488
|
+
if (absent.length > 0)
|
|
6489
|
+
lines.push(msg6.summary.absent(absent.map((item) => ({ model: shortModel(item.model), reason: firstLine(item.details) }))));
|
|
6490
|
+
const suggestions = [...new Set(passed.flatMap((item) => splitSuggestions(item.details)))];
|
|
6491
|
+
if (suggestions.length === 0)
|
|
6492
|
+
return lines.join(`
|
|
6493
|
+
`);
|
|
6494
|
+
return [
|
|
6495
|
+
...lines,
|
|
6496
|
+
"",
|
|
6497
|
+
`## ${msg6.terms.suggestions}`,
|
|
6498
|
+
...suggestions.map((item) => `- ${item}`)
|
|
6499
|
+
].join(`
|
|
6500
|
+
`);
|
|
6522
6501
|
}
|
|
6523
|
-
function
|
|
6524
|
-
|
|
6525
|
-
for (const line of advice.split(/\r?\n/u)) {
|
|
6526
|
-
if (/^\*\*[^*]+\*\*\s*[::]/u.test(line.trim()) && output.length > 0 && output.at(-1) !== "")
|
|
6527
|
-
output.push("");
|
|
6528
|
-
output.push(line);
|
|
6529
|
-
}
|
|
6530
|
-
return output;
|
|
6502
|
+
function firstLine(text) {
|
|
6503
|
+
return text.trim().split(/\r?\n/u, 1)[0]?.split(/[::]/u, 1)[0]?.trim() ?? "";
|
|
6531
6504
|
}
|
|
6532
|
-
function
|
|
6533
|
-
|
|
6505
|
+
function splitSuggestions(details) {
|
|
6506
|
+
const lines = details.split(/\r?\n/u);
|
|
6507
|
+
const index = lines.findIndex((line) => SUGGESTIONS_HEADING.test(line.trim()));
|
|
6508
|
+
if (index < 0)
|
|
6509
|
+
return [];
|
|
6510
|
+
return lines.slice(index + 1).map((line) => line.replace(/^[-*]\s*/u, "").trim()).filter(Boolean);
|
|
6534
6511
|
}
|
|
6535
|
-
function
|
|
6536
|
-
|
|
6537
|
-
return lines;
|
|
6538
|
-
return [...lines, ...lines.length > 0 ? ["", "---", ""] : [], ...footer];
|
|
6512
|
+
function passBody(summary) {
|
|
6513
|
+
return summary.replace(/^(?:PASS\s*)/iu, "").trim();
|
|
6539
6514
|
}
|
|
6540
|
-
function
|
|
6541
|
-
return
|
|
6515
|
+
function shortModel(model) {
|
|
6516
|
+
return model.split("/").at(-1) || model;
|
|
6542
6517
|
}
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
const
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
if (
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6518
|
+
function appendClosedFindings(summary, history, beforeRound) {
|
|
6519
|
+
const seen = new Set;
|
|
6520
|
+
const findings = [];
|
|
6521
|
+
for (const round of history) {
|
|
6522
|
+
if (round.round >= beforeRound || round.result !== "failed")
|
|
6523
|
+
continue;
|
|
6524
|
+
for (const line of round.details.split(/\r?\n/u)) {
|
|
6525
|
+
const match = ISSUE_LINE.exec(line.trim());
|
|
6526
|
+
const issue = match?.[1]?.replace(/`([^`]+)`/gu, "$1").trim();
|
|
6527
|
+
if (!issue)
|
|
6528
|
+
continue;
|
|
6529
|
+
const key = issue.replace(/\s+/gu, "");
|
|
6530
|
+
if (seen.has(key))
|
|
6531
|
+
continue;
|
|
6532
|
+
seen.add(key);
|
|
6533
|
+
findings.push({ round: round.round, issue });
|
|
6534
|
+
}
|
|
6535
|
+
}
|
|
6536
|
+
if (findings.length === 0)
|
|
6537
|
+
return summary;
|
|
6538
|
+
const shown = findings.slice(0, 6);
|
|
6539
|
+
const recap = [msg6.summary.closedHeading, ...shown.map((finding) => msg6.summary.closedItem(finding.issue, finding.round))];
|
|
6540
|
+
if (findings.length > shown.length)
|
|
6541
|
+
recap.push(msg6.summary.closedMore(findings.length - shown.length));
|
|
6542
|
+
return `${summary}
|
|
6543
|
+
|
|
6544
|
+
${recap.join(`
|
|
6545
|
+
`)}`;
|
|
6546
|
+
}
|
|
6547
|
+
function modelLabel(item) {
|
|
6548
|
+
return msg6.summary.modelSection(item.index + 1, shortModel(item.model));
|
|
6549
|
+
}
|
|
6550
|
+
function interruptedRound(state, now, result, reason) {
|
|
6551
|
+
if (state.phase === "reviewing" && state.active)
|
|
6552
|
+
return closeRound(state, now, { round: state.active.round, result, details: "", reviewers: resultsOf(state.active.reviewers), reason });
|
|
6553
|
+
if (state.phase === "needs_fix" && state.pending)
|
|
6554
|
+
return closeRound(state, now, { round: state.pending.round, result, details: "", reviewers: state.pending.reviewers, reason });
|
|
6555
|
+
return;
|
|
6556
|
+
}
|
|
6557
|
+
function closeRound(state, now, record) {
|
|
6558
|
+
return { ...record, elapsedMs: state.roundStartedAt ? Math.max(0, now - state.roundStartedAt) : 0 };
|
|
6559
|
+
}
|
|
6560
|
+
|
|
6561
|
+
// review/card.ts
|
|
6562
|
+
var CARD_TYPE = "firecode-review-card";
|
|
6563
|
+
var VERSION2 = 1;
|
|
6564
|
+
var MARKS = {
|
|
6565
|
+
start: { glyph: "⠿", color: "review" },
|
|
6566
|
+
advisor: { glyph: "⠿", color: "review" },
|
|
6567
|
+
pass: { glyph: "✓", color: "success" },
|
|
6568
|
+
fail: { glyph: "✗", color: "error" },
|
|
6569
|
+
stop: { glyph: "✗", color: "error" },
|
|
6570
|
+
timeout: { glyph: "◌", color: "error" },
|
|
6571
|
+
error: { glyph: "◌", color: "error" }
|
|
6572
|
+
};
|
|
6573
|
+
var CARD_KINDS = new Set(Object.keys(MARKS));
|
|
6574
|
+
var TONES = ["success", "warning", "neutral"];
|
|
6575
|
+
function paintMark(details, theme) {
|
|
6576
|
+
const { color } = MARKS[details.kind];
|
|
6577
|
+
return color === "review" ? paint(HEAT_COLORS.gold, details.icon) : theme.fg(color, details.icon);
|
|
6578
|
+
}
|
|
6579
|
+
function isValidCardDetails(value) {
|
|
6580
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
6581
|
+
return false;
|
|
6582
|
+
const record = value;
|
|
6583
|
+
if (Object.keys(record).length !== 6)
|
|
6584
|
+
return false;
|
|
6585
|
+
if (record.version !== VERSION2)
|
|
6586
|
+
return false;
|
|
6587
|
+
if (typeof record.kind !== "string" || !CARD_KINDS.has(record.kind))
|
|
6588
|
+
return false;
|
|
6589
|
+
if (typeof record.title !== "string")
|
|
6590
|
+
return false;
|
|
6591
|
+
if (!Array.isArray(record.lines) || !record.lines.every((line) => typeof line === "string"))
|
|
6592
|
+
return false;
|
|
6593
|
+
if (!TONES.includes(record.tone))
|
|
6594
|
+
return false;
|
|
6595
|
+
return typeof record.icon === "string";
|
|
6596
|
+
}
|
|
6597
|
+
function registerCardRenderer(pi) {
|
|
6598
|
+
pi.registerMessageRenderer(CARD_TYPE, (message, _options, theme) => new ReviewCard(message.details, message.content, theme));
|
|
6599
|
+
}
|
|
6600
|
+
|
|
6601
|
+
class ReviewCard {
|
|
6602
|
+
card;
|
|
6603
|
+
fallback;
|
|
6604
|
+
constructor(details, content, theme) {
|
|
6605
|
+
this.fallback = new Text5(plainContent(content), 0, 0);
|
|
6606
|
+
let card;
|
|
6607
|
+
try {
|
|
6608
|
+
card = isValidCardDetails(details) ? nativeCard(details, theme) : undefined;
|
|
6609
|
+
} catch {
|
|
6610
|
+
card = undefined;
|
|
6611
|
+
}
|
|
6612
|
+
this.card = card;
|
|
6613
|
+
}
|
|
6614
|
+
render(width) {
|
|
6615
|
+
try {
|
|
6616
|
+
return (this.card ?? this.fallback).render(Math.max(1, width));
|
|
6617
|
+
} catch {
|
|
6618
|
+
try {
|
|
6619
|
+
return this.fallback.render(Math.max(1, width));
|
|
6620
|
+
} catch {
|
|
6621
|
+
return [];
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
invalidate() {
|
|
6626
|
+
this.card?.invalidate?.();
|
|
6627
|
+
this.fallback.invalidate?.();
|
|
6628
|
+
}
|
|
6629
|
+
}
|
|
6630
|
+
function nativeCard(details, theme) {
|
|
6631
|
+
const box = new Box(1, 0, (text) => theme.bg(backgroundFor(details.tone), text));
|
|
6632
|
+
box.addChild(new Text5(`${paintMark(details, theme)} ${details.title}`, 0, 0));
|
|
6633
|
+
box.addChild(new Spacer4(1));
|
|
6634
|
+
box.addChild(new Markdown3(details.lines.join(`
|
|
6635
|
+
`), 0, 0, getMarkdownTheme4()));
|
|
6636
|
+
return box;
|
|
6637
|
+
}
|
|
6638
|
+
function backgroundFor(tone) {
|
|
6639
|
+
if (tone === "success")
|
|
6640
|
+
return "toolSuccessBg";
|
|
6641
|
+
return tone === "warning" ? "toolErrorBg" : "customMessageBg";
|
|
6642
|
+
}
|
|
6643
|
+
function plainContent(content) {
|
|
6644
|
+
if (typeof content === "string")
|
|
6645
|
+
return content;
|
|
6646
|
+
if (!Array.isArray(content))
|
|
6647
|
+
return "";
|
|
6648
|
+
return content.map(plainPart).filter(Boolean).join(`
|
|
6649
|
+
`);
|
|
6650
|
+
}
|
|
6651
|
+
function plainPart(part) {
|
|
6652
|
+
return typeof part === "object" && part !== null && "text" in part ? String(part.text) : "";
|
|
6653
|
+
}
|
|
6654
|
+
function buildCard(card) {
|
|
6655
|
+
switch (card.kind) {
|
|
6656
|
+
case "start":
|
|
6657
|
+
return started(card);
|
|
6658
|
+
case "pass":
|
|
6659
|
+
return passed(card);
|
|
6660
|
+
case "fail":
|
|
6661
|
+
return failed2(card);
|
|
6662
|
+
case "stop":
|
|
6663
|
+
return stopped(card);
|
|
6664
|
+
case "timeout":
|
|
6665
|
+
return timedOut();
|
|
6666
|
+
case "error":
|
|
6667
|
+
return errored(card);
|
|
6668
|
+
case "advisor":
|
|
6669
|
+
return advisorCard(card);
|
|
6670
|
+
}
|
|
6671
|
+
}
|
|
6672
|
+
function started(card) {
|
|
6673
|
+
return spec("start", msg6.card.started, [msg6.card.startedModels(card.models.map(shortModel))], "neutral");
|
|
6674
|
+
}
|
|
6675
|
+
function passed(card) {
|
|
6676
|
+
const title = qualityTitle(card.round, msg6.card.passed);
|
|
6677
|
+
const lines = withFooter(formatReviewResultLines(card.summary), [
|
|
6678
|
+
elapsedLine(card.elapsedMs, card.round > 1 ? card.totalElapsedMs : undefined)
|
|
6679
|
+
]);
|
|
6680
|
+
return spec("pass", title, lines, "success");
|
|
6681
|
+
}
|
|
6682
|
+
function failed2(card) {
|
|
6683
|
+
const title = qualityTitle(card.round, msg6.card.failed);
|
|
6684
|
+
return spec("fail", title, withFooter(formatReviewResultLines(card.details), [elapsedLine(card.elapsedMs)]), "warning");
|
|
6685
|
+
}
|
|
6686
|
+
function stopped(card) {
|
|
6687
|
+
const footer = card.elapsedMs === undefined ? [] : [elapsedLine(card.elapsedMs)];
|
|
6688
|
+
if (card.reason === "advisor") {
|
|
6689
|
+
const title = qualityTitle(card.round, msg6.card.stoppedByAdvisor);
|
|
6690
|
+
const body = [advisorModelLine(card.advisorModel), "", ...adviceLines(card.advisor.advice)];
|
|
6691
|
+
return spec("stop", title, withFooter(body, footer), "warning");
|
|
6692
|
+
}
|
|
6693
|
+
const title = qualityTitle(card.round, msg6.card.failed);
|
|
6694
|
+
const body = formatReviewResultLines(card.details);
|
|
6695
|
+
return spec("stop", title, withFooter(body, footer), "warning");
|
|
6696
|
+
}
|
|
6697
|
+
function timedOut() {
|
|
6698
|
+
return spec("timeout", msg6.card.incomplete, [msg6.card.timeoutBlocker, msg6.card.timeoutReason], "warning");
|
|
6699
|
+
}
|
|
6700
|
+
function errored(card) {
|
|
6701
|
+
const lines = [
|
|
6702
|
+
msg6.card.errorBlocker,
|
|
6703
|
+
msg6.card.errorReason(card.message),
|
|
6704
|
+
...card.elapsedMs === undefined ? [] : ["", elapsedLine(card.elapsedMs)]
|
|
6705
|
+
];
|
|
6706
|
+
return spec("error", msg6.card.incomplete, lines, "warning");
|
|
6707
|
+
}
|
|
6708
|
+
function advisorCard(card) {
|
|
6709
|
+
const title = msg6.card.advisorGuidance(msg6.card.decision[card.advisor.verdict]);
|
|
6710
|
+
const body = [advisorModelLine(card.advisorModel), "", ...adviceLines(card.advisor.advice)];
|
|
6711
|
+
return spec("advisor", title, withFooter(body, [elapsedLine(card.elapsedMs)]), "neutral");
|
|
6712
|
+
}
|
|
6713
|
+
function advisorModelLine(model) {
|
|
6714
|
+
return msg6.card.modelLine(shortModel(model));
|
|
6715
|
+
}
|
|
6716
|
+
function adviceLines(advice) {
|
|
6717
|
+
const output = [];
|
|
6718
|
+
for (const line of advice.split(/\r?\n/u)) {
|
|
6719
|
+
if (/^\*\*[^*]+\*\*\s*[::]/u.test(line.trim()) && output.length > 0 && output.at(-1) !== "")
|
|
6720
|
+
output.push("");
|
|
6721
|
+
output.push(line);
|
|
6722
|
+
}
|
|
6723
|
+
return output;
|
|
6724
|
+
}
|
|
6725
|
+
function qualityTitle(round, title) {
|
|
6726
|
+
return round <= 1 ? title : msg6.card.round(round, title);
|
|
6727
|
+
}
|
|
6728
|
+
function withFooter(lines, footer) {
|
|
6729
|
+
if (footer.length === 0)
|
|
6730
|
+
return lines;
|
|
6731
|
+
return [...lines, ...lines.length > 0 ? ["", "---", ""] : [], ...footer];
|
|
6732
|
+
}
|
|
6733
|
+
function elapsedLine(ms, totalMs) {
|
|
6734
|
+
return msg6.card.elapsed(formatDuration(ms), totalMs === undefined ? undefined : formatDuration(totalMs));
|
|
6735
|
+
}
|
|
6736
|
+
var MODEL_SECTION = new RegExp(String.raw`^${termPattern((terms) => terms.model)}\s+\d+\s+·\s+`, "iu");
|
|
6737
|
+
var EVIDENCE_LABEL = new RegExp(String.raw`^[-*+]?\s*(?:\*\*)?${termPattern((terms) => terms.field.evidence)}(?:\*\*)?\s*[::]`, "u");
|
|
6738
|
+
function formatReviewResultLines(review) {
|
|
6739
|
+
const lines = review.split(/\r?\n/u);
|
|
6740
|
+
const sections = [];
|
|
6741
|
+
const preface = [];
|
|
6742
|
+
let current;
|
|
6743
|
+
for (const line of lines) {
|
|
6744
|
+
if (MODEL_SECTION.test(line.trim())) {
|
|
6745
|
+
if (current)
|
|
6746
|
+
sections.push(current);
|
|
6747
|
+
current = { title: line.trim(), body: [] };
|
|
6748
|
+
} else if (current)
|
|
6749
|
+
current.body.push(line);
|
|
6750
|
+
else
|
|
6751
|
+
preface.push(line);
|
|
6752
|
+
}
|
|
6753
|
+
if (current)
|
|
6754
|
+
sections.push(current);
|
|
6755
|
+
if (sections.length === 0)
|
|
6756
|
+
return normalizedReviewLines(review);
|
|
6757
|
+
return [
|
|
6758
|
+
...normalizedReviewLines(preface.join(`
|
|
6759
|
+
`)),
|
|
6760
|
+
...preface.join("").trim() ? [""] : [],
|
|
6761
|
+
...sections.flatMap((section, index) => [
|
|
6762
|
+
...index > 0 ? ["", "---", ""] : [],
|
|
6763
|
+
`**${section.title}**`,
|
|
6764
|
+
"",
|
|
6765
|
+
...normalizedReviewLines(section.body.join(`
|
|
6766
|
+
`))
|
|
6767
|
+
])
|
|
6575
6768
|
];
|
|
6576
6769
|
}
|
|
6577
6770
|
function normalizedReviewLines(review) {
|
|
@@ -6639,13 +6832,7 @@ var ROUND_RESULTS = new Set([
|
|
|
6639
6832
|
]);
|
|
6640
6833
|
var REVIEWER_STATUSES = new Set(["running", "passed", "failed", "error"]);
|
|
6641
6834
|
var ADVISOR_VERDICTS = new Set(["continue", "stop", "narrow"]);
|
|
6642
|
-
var STOP_REASONS = new Set([
|
|
6643
|
-
"advisor",
|
|
6644
|
-
"max_rounds",
|
|
6645
|
-
"user",
|
|
6646
|
-
"shutdown",
|
|
6647
|
-
"timeout"
|
|
6648
|
-
]);
|
|
6835
|
+
var STOP_REASONS = new Set(["user", "shutdown", "timeout"]);
|
|
6649
6836
|
var REPAIR_STATUSES = new Set(["pending", "awaiting_start", "running", "completed"]);
|
|
6650
6837
|
var SUMMARY_KINDS = new Set(["passed", "max_rounds", "advisor_stop"]);
|
|
6651
6838
|
var SUMMARY_STATUSES = new Set(["pending", "awaiting_start", "running"]);
|
|
@@ -6716,9 +6903,6 @@ var CHECKPOINT_KEYS = keysOf({
|
|
|
6716
6903
|
roundStartedAt: true,
|
|
6717
6904
|
updatedAt: true
|
|
6718
6905
|
});
|
|
6719
|
-
function isRecord7(value) {
|
|
6720
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6721
|
-
}
|
|
6722
6906
|
function isString(value) {
|
|
6723
6907
|
return typeof value === "string";
|
|
6724
6908
|
}
|
|
@@ -6732,21 +6916,21 @@ function hasOnlyKeys(record, keys) {
|
|
|
6732
6916
|
return Object.keys(record).every((key) => keys.includes(key));
|
|
6733
6917
|
}
|
|
6734
6918
|
function isValidReviewerResult(value) {
|
|
6735
|
-
if (!
|
|
6919
|
+
if (!isRecord(value))
|
|
6736
6920
|
return false;
|
|
6737
6921
|
if (!isNonNegativeInt(value.index) || !hasOnlyKeys(value, REVIEWER_RESULT_KEYS))
|
|
6738
6922
|
return false;
|
|
6739
6923
|
return isString(value.model) && isString(value.thinking) && oneOf(value.status, REVIEWER_STATUSES) && value.status !== "running" && isString(value.summary) && isString(value.details);
|
|
6740
6924
|
}
|
|
6741
6925
|
function isValidActiveReviewer(value) {
|
|
6742
|
-
if (!
|
|
6926
|
+
if (!isRecord(value) || !isNonNegativeInt(value.index))
|
|
6743
6927
|
return false;
|
|
6744
6928
|
if (!hasOnlyKeys(value, ACTIVE_REVIEWER_KEYS))
|
|
6745
6929
|
return false;
|
|
6746
6930
|
return isString(value.model) && isString(value.thinking) && oneOf(value.status, REVIEWER_STATUSES) && (value.result === null || isValidReviewerResult(value.result));
|
|
6747
6931
|
}
|
|
6748
6932
|
function isValidActiveCheck(value) {
|
|
6749
|
-
if (!
|
|
6933
|
+
if (!isRecord(value))
|
|
6750
6934
|
return false;
|
|
6751
6935
|
if (!hasOnlyKeys(value, ACTIVE_CHECK_KEYS))
|
|
6752
6936
|
return false;
|
|
@@ -6757,19 +6941,19 @@ function isValidActiveCheck(value) {
|
|
|
6757
6941
|
return round >= 1 && Array.isArray(value.reviewers) && value.reviewers.every(isValidActiveReviewer) && value.reviewers.length > 0 && settledCount <= value.reviewers.length;
|
|
6758
6942
|
}
|
|
6759
6943
|
function isValidAdvisor(value) {
|
|
6760
|
-
if (!
|
|
6944
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ADVISOR_KEYS))
|
|
6761
6945
|
return false;
|
|
6762
6946
|
return oneOf(value.verdict, ADVISOR_VERDICTS) && isString(value.advice);
|
|
6763
6947
|
}
|
|
6764
6948
|
function isValidRound(value) {
|
|
6765
|
-
if (!
|
|
6949
|
+
if (!isRecord(value))
|
|
6766
6950
|
return false;
|
|
6767
6951
|
if (!hasOnlyKeys(value, ROUND_KEYS))
|
|
6768
6952
|
return false;
|
|
6769
6953
|
return isNonNegativeInt(value.round) && oneOf(value.result, ROUND_RESULTS) && isString(value.details) && Array.isArray(value.reviewers) && value.reviewers.every(isValidReviewerResult) && (value.advisor === undefined || isValidAdvisor(value.advisor)) && (value.reason === undefined || oneOf(value.reason, STOP_REASONS)) && isNonNegativeInt(value.elapsedMs);
|
|
6770
6954
|
}
|
|
6771
6955
|
function isValidPendingRound(value) {
|
|
6772
|
-
if (!
|
|
6956
|
+
if (!isRecord(value))
|
|
6773
6957
|
return false;
|
|
6774
6958
|
if (!hasOnlyKeys(value, PENDING_ROUND_KEYS))
|
|
6775
6959
|
return false;
|
|
@@ -6777,13 +6961,13 @@ function isValidPendingRound(value) {
|
|
|
6777
6961
|
return isNonNegativeInt(round) && round >= 1 && Array.isArray(value.reviewers) && value.reviewers.every(isValidReviewerResult) && isString(value.details);
|
|
6778
6962
|
}
|
|
6779
6963
|
function isValidRepair(value) {
|
|
6780
|
-
return
|
|
6964
|
+
return isRecord(value) && hasOnlyKeys(value, REPAIR_KEYS) && isString(value.details) && (value.advisor === null || isValidAdvisor(value.advisor)) && oneOf(value.status, REPAIR_STATUSES);
|
|
6781
6965
|
}
|
|
6782
6966
|
function isValidSummary(value) {
|
|
6783
|
-
return
|
|
6967
|
+
return isRecord(value) && hasOnlyKeys(value, SUMMARY_KEYS) && oneOf(value.kind, SUMMARY_KINDS) && oneOf(value.status, SUMMARY_STATUSES);
|
|
6784
6968
|
}
|
|
6785
6969
|
function isValidCheckpoint(value) {
|
|
6786
|
-
if (!
|
|
6970
|
+
if (!isRecord(value))
|
|
6787
6971
|
return false;
|
|
6788
6972
|
if (!hasOnlyKeys(value, CHECKPOINT_KEYS))
|
|
6789
6973
|
return false;
|
|
@@ -6810,29 +6994,23 @@ function toCheckpoint(state) {
|
|
|
6810
6994
|
};
|
|
6811
6995
|
}
|
|
6812
6996
|
function readCheckpoint(ctx) {
|
|
6813
|
-
|
|
6814
|
-
|
|
6997
|
+
return latestData(ctx);
|
|
6998
|
+
}
|
|
6999
|
+
function isCheckpointEntry(value) {
|
|
7000
|
+
return isRecord(value) && value.type === "custom" && value.customType === CHECKPOINT_TYPE && "data" in value;
|
|
6815
7001
|
}
|
|
6816
|
-
function
|
|
7002
|
+
function latestData(ctx) {
|
|
6817
7003
|
const entries = ctx.sessionManager.getBranch();
|
|
6818
7004
|
for (let index = entries.length - 1;index >= 0; index -= 1) {
|
|
6819
7005
|
const entry = entries[index];
|
|
6820
|
-
if (
|
|
6821
|
-
|
|
6822
|
-
if (entry.customType !== CHECKPOINT_TYPE)
|
|
6823
|
-
continue;
|
|
6824
|
-
if (!isValidCheckpoint(entry.data))
|
|
6825
|
-
return;
|
|
6826
|
-
return entry.data;
|
|
7006
|
+
if (isCheckpointEntry(entry))
|
|
7007
|
+
return isValidCheckpoint(entry.data) ? entry.data : undefined;
|
|
6827
7008
|
}
|
|
6828
7009
|
return;
|
|
6829
7010
|
}
|
|
6830
7011
|
function readStamp(ctx) {
|
|
6831
|
-
const
|
|
6832
|
-
|
|
6833
|
-
return null;
|
|
6834
|
-
const data = entry;
|
|
6835
|
-
return { runId: data.runId, seq: data.seq };
|
|
7012
|
+
const data = latestData(ctx);
|
|
7013
|
+
return data ? { runId: data.runId, seq: data.seq } : null;
|
|
6836
7014
|
}
|
|
6837
7015
|
function appendCheckpoint(pi, state, seq) {
|
|
6838
7016
|
const data = { ...toCheckpoint(state), seq };
|
|
@@ -6841,12 +7019,12 @@ function appendCheckpoint(pi, state, seq) {
|
|
|
6841
7019
|
pi.appendEntry(CHECKPOINT_TYPE, data);
|
|
6842
7020
|
return { runId: state.runId, seq };
|
|
6843
7021
|
}
|
|
6844
|
-
function beginCheckpoint(pi,
|
|
7022
|
+
function beginCheckpoint(pi, state) {
|
|
6845
7023
|
return appendCheckpoint(pi, state, 1);
|
|
6846
7024
|
}
|
|
6847
7025
|
function writeCheckpoint(pi, ctx, state, expected) {
|
|
6848
7026
|
const current = readStamp(ctx);
|
|
6849
|
-
if (
|
|
7027
|
+
if (current?.runId !== expected.runId || current.seq !== expected.seq)
|
|
6850
7028
|
throw new CheckpointConflictError;
|
|
6851
7029
|
return appendCheckpoint(pi, state, expected.seq + 1);
|
|
6852
7030
|
}
|
|
@@ -6888,7 +7066,7 @@ ${omitted > 0 ? `${msg6.evidence.gap(omitted)}
|
|
|
6888
7066
|
function collectFailedCalls(entries) {
|
|
6889
7067
|
const failed = new Set;
|
|
6890
7068
|
for (const entry of entries) {
|
|
6891
|
-
if (!
|
|
7069
|
+
if (!isRecord(entry) || entry.type !== "message")
|
|
6892
7070
|
continue;
|
|
6893
7071
|
const message = asRecord2(entry.message);
|
|
6894
7072
|
if (message?.role !== "toolResult" || message.isError !== true)
|
|
@@ -6899,7 +7077,7 @@ function collectFailedCalls(entries) {
|
|
|
6899
7077
|
return failed;
|
|
6900
7078
|
}
|
|
6901
7079
|
function renderEntry(entry, render) {
|
|
6902
|
-
if (!
|
|
7080
|
+
if (!isRecord(entry))
|
|
6903
7081
|
return [];
|
|
6904
7082
|
switch (entry.type) {
|
|
6905
7083
|
case "message": {
|
|
@@ -6926,11 +7104,13 @@ ${assistantBody(message.content, render)}` }];
|
|
|
6926
7104
|
${clip2(textOf(entry.content), render)}` }];
|
|
6927
7105
|
}
|
|
6928
7106
|
case "compaction":
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
7107
|
+
case "branch_summary": {
|
|
7108
|
+
if (typeof entry.summary !== "string" || !entry.summary)
|
|
7109
|
+
return [];
|
|
7110
|
+
const label = entry.type === "compaction" ? msg6.evidence.compaction : msg6.evidence.branchSummary;
|
|
7111
|
+
return [{ text: `## ${label}
|
|
7112
|
+
${clip2(entry.summary, render)}` }];
|
|
7113
|
+
}
|
|
6934
7114
|
default:
|
|
6935
7115
|
return [];
|
|
6936
7116
|
}
|
|
@@ -6985,11 +7165,8 @@ function isCjk(char) {
|
|
|
6985
7165
|
const code = char.codePointAt(0) ?? 0;
|
|
6986
7166
|
return code >= 19968 && code <= 40959 || code >= 13312 && code <= 19903 || code >= 63744 && code <= 64255;
|
|
6987
7167
|
}
|
|
6988
|
-
function isRecord8(value) {
|
|
6989
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6990
|
-
}
|
|
6991
7168
|
function asRecord2(value) {
|
|
6992
|
-
return
|
|
7169
|
+
return isRecord(value) ? value : undefined;
|
|
6993
7170
|
}
|
|
6994
7171
|
|
|
6995
7172
|
// review/ui.ts
|
|
@@ -6997,7 +7174,6 @@ import { basename as basename2 } from "node:path";
|
|
|
6997
7174
|
import {
|
|
6998
7175
|
CustomEditor as CustomEditor2
|
|
6999
7176
|
} from "@earendil-works/pi-coding-agent";
|
|
7000
|
-
var reviewTitleActive = false;
|
|
7001
7177
|
function lockEditor(ctx, cancel) {
|
|
7002
7178
|
if (ctx.hasUI === false || typeof ctx.ui.setEditorComponent !== "function")
|
|
7003
7179
|
return () => {};
|
|
@@ -7032,26 +7208,47 @@ class ReviewEditor extends CustomEditor2 {
|
|
|
7032
7208
|
return [lines[0], `\x1B[2m${hint}\x1B[22m`, lines[lines.length - 1]];
|
|
7033
7209
|
}
|
|
7034
7210
|
}
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
}
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7211
|
+
|
|
7212
|
+
class ReviewUi {
|
|
7213
|
+
unlockEditor;
|
|
7214
|
+
titleShown = false;
|
|
7215
|
+
show(ctx, round, canCancel, cancel) {
|
|
7216
|
+
this.showTitle(ctx, round);
|
|
7217
|
+
if (!canCancel)
|
|
7218
|
+
return this.releaseEditor();
|
|
7219
|
+
this.unlockEditor ??= lockEditor(ctx, cancel);
|
|
7220
|
+
}
|
|
7221
|
+
clear(ctx) {
|
|
7222
|
+
if (this.titleShown && canSetTitle(ctx))
|
|
7223
|
+
ctx.ui.setTitle(restoredTitle(ctx));
|
|
7224
|
+
this.titleShown = false;
|
|
7225
|
+
this.releaseEditor();
|
|
7226
|
+
}
|
|
7227
|
+
showTitle(ctx, round) {
|
|
7228
|
+
if (!canSetTitle(ctx))
|
|
7229
|
+
return;
|
|
7230
|
+
const { name, dir } = identity(ctx);
|
|
7231
|
+
const who = name || dir;
|
|
7232
|
+
this.titleShown = true;
|
|
7233
|
+
ctx.ui.setTitle(`${msg6.ui.reviewing}${round > 0 ? ` R${round}` : ""}${who ? ` · ${who}` : ""}`);
|
|
7234
|
+
}
|
|
7235
|
+
releaseEditor() {
|
|
7236
|
+
this.unlockEditor?.();
|
|
7237
|
+
this.unlockEditor = undefined;
|
|
7238
|
+
}
|
|
7239
|
+
}
|
|
7240
|
+
function canSetTitle(ctx) {
|
|
7241
|
+
return ctx.hasUI && typeof ctx.ui.setTitle === "function";
|
|
7242
|
+
}
|
|
7243
|
+
function identity(ctx) {
|
|
7049
7244
|
const manager = ctx.sessionManager;
|
|
7050
|
-
const
|
|
7051
|
-
const
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7245
|
+
const name = manager.getSessionName?.();
|
|
7246
|
+
const cwd = manager.getCwd?.();
|
|
7247
|
+
return { name: typeof name === "string" && name ? name : undefined, dir: typeof cwd === "string" ? basename2(cwd) : "" };
|
|
7248
|
+
}
|
|
7249
|
+
function restoredTitle(ctx) {
|
|
7250
|
+
const { name, dir } = identity(ctx);
|
|
7251
|
+
return name ? `π - ${name} - ${dir}` : `π - ${dir}`;
|
|
7055
7252
|
}
|
|
7056
7253
|
|
|
7057
7254
|
// review/prompt.ts
|
|
@@ -7120,16 +7317,14 @@ function buildFixFeedback(input) {
|
|
|
7120
7317
|
`));
|
|
7121
7318
|
}
|
|
7122
7319
|
var SUMMARY_MATERIAL_LIMIT = 4000;
|
|
7123
|
-
var SUMMARY_KEYS2 = { passed: "passed", max_rounds: "maxRounds", advisor_stop: "advisorStop" };
|
|
7124
7320
|
function buildSummaryPrompt(input) {
|
|
7125
7321
|
const omitted = input.material.length - SUMMARY_MATERIAL_LIMIT;
|
|
7126
7322
|
const material = omitted > 0 ? `${input.material.slice(0, SUMMARY_MATERIAL_LIMIT)}
|
|
7127
7323
|
${msg6.prompt.materialTruncated(omitted)}` : input.material;
|
|
7128
|
-
const
|
|
7129
|
-
const body = msg6.prompt.summaryInstruction[key](input.rounds);
|
|
7324
|
+
const body = msg6.prompt.summaryInstruction[input.kind](input.rounds);
|
|
7130
7325
|
const content = material.trim() ? `${body}
|
|
7131
7326
|
|
|
7132
|
-
${msg6.prompt.materialLabel[
|
|
7327
|
+
${msg6.prompt.materialLabel[input.kind]}
|
|
7133
7328
|
${material}` : body;
|
|
7134
7329
|
return wrapEnvelope("firecode_review", content);
|
|
7135
7330
|
}
|
|
@@ -7138,28 +7333,16 @@ ${material}` : body;
|
|
|
7138
7333
|
async function runAdvisor(options) {
|
|
7139
7334
|
const result = await options.runSession({
|
|
7140
7335
|
role: "advisor",
|
|
7141
|
-
|
|
7142
|
-
thinking: options.config.thinking,
|
|
7143
|
-
tools: options.config.tools,
|
|
7336
|
+
config: options.config,
|
|
7144
7337
|
prompt: options.prompt,
|
|
7145
7338
|
cwd: options.cwd,
|
|
7146
|
-
timeoutMs: options.config.timeoutMs,
|
|
7147
7339
|
signal: options.signal
|
|
7148
7340
|
});
|
|
7149
7341
|
if (result.kind !== "output")
|
|
7150
|
-
throw new Error(
|
|
7342
|
+
throw new Error(msg6.advisor.unavailable(result.kind === "error" ? result.message : result.kind));
|
|
7151
7343
|
return parseAdvisorOutput(result.text);
|
|
7152
7344
|
}
|
|
7153
7345
|
var VERDICTS = new Set(["continue", "stop", "narrow"]);
|
|
7154
|
-
function advisorProcessError(result) {
|
|
7155
|
-
if (result.kind === "aborted")
|
|
7156
|
-
return msg6.advisor.unavailable("aborted");
|
|
7157
|
-
if (result.kind === "timeout")
|
|
7158
|
-
return msg6.advisor.unavailable("timeout");
|
|
7159
|
-
if (result.kind === "empty")
|
|
7160
|
-
return msg6.advisor.unavailable("empty output");
|
|
7161
|
-
return msg6.advisor.unavailable(result.message);
|
|
7162
|
-
}
|
|
7163
7346
|
var VERDICT_SCAN_LINES = 8;
|
|
7164
7347
|
function parseAdvisorOutput(text) {
|
|
7165
7348
|
const lines = unwrapCodeFence(text.trim().split(/\r?\n/));
|
|
@@ -7205,12 +7388,9 @@ function normalizeVerdict(line) {
|
|
|
7205
7388
|
async function runReviewer(options) {
|
|
7206
7389
|
const result = await options.runSession({
|
|
7207
7390
|
role: "reviewer",
|
|
7208
|
-
|
|
7209
|
-
thinking: options.config.thinking,
|
|
7210
|
-
tools: options.config.tools,
|
|
7391
|
+
config: options.config,
|
|
7211
7392
|
prompt: options.prompt,
|
|
7212
7393
|
cwd: options.cwd,
|
|
7213
|
-
timeoutMs: options.config.timeoutMs,
|
|
7214
7394
|
signal: options.signal
|
|
7215
7395
|
});
|
|
7216
7396
|
const parsed = result.kind === "output" ? parseReviewOutput(result.text) : processFailure(result);
|
|
@@ -7272,22 +7452,13 @@ var FILE_SEGMENT = new RegExp(`${termPattern((terms) => terms.anchor.files)}\\s*
|
|
|
7272
7452
|
var COMMAND_SEGMENT = new RegExp(`${termPattern((terms) => terms.anchor.commands)}\\s*=\\s*([^;;]*)`, "iu");
|
|
7273
7453
|
var FILE_ANCHOR = /[\w@./-]*\w\.[a-zA-Z]\w{0,5}\b/u;
|
|
7274
7454
|
var FINDING_HEADING = new RegExp(`^#{1,6}\\s*${termPattern((terms) => terms.finding)}`, "u");
|
|
7275
|
-
var SUGGESTIONS_HEADING = new RegExp(`^##\\s+${termPattern((terms) => terms.suggestions)}\\s*$`, "iu");
|
|
7276
7455
|
var SUGGESTIONS_HEADING_SPLIT = new RegExp(SUGGESTIONS_HEADING.source, "imu");
|
|
7277
7456
|
var bulletField = (label, value = "") => new RegExp(`^[-*+]\\s*(?:\\*\\*)?${label}(?:\\*\\*)?\\s*[::]\\s*${value}`, "iu");
|
|
7278
7457
|
var FINDING_ISSUE = bulletField(termPattern((terms) => terms.field.issue), "(.*)$");
|
|
7279
|
-
var FINDING_FIELDS = [
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
key: "contract",
|
|
7284
|
-
label: termPattern((terms) => terms.field.contract, "违反的约定与期望", "违反的契约或期望行为", "违反的契约", "Violated agreement", "Contract or expected behavior violated", "Contract violated")
|
|
7285
|
-
},
|
|
7286
|
-
{
|
|
7287
|
-
key: "commands",
|
|
7288
|
-
label: termPattern((terms) => terms.field.commands, "需要运行的验证命令", "Verification commands", "Verification command to run", "Verification commands to run")
|
|
7289
|
-
}
|
|
7290
|
-
];
|
|
7458
|
+
var FINDING_FIELDS = ["issue", "evidence", "contract", "commands"].map((key) => ({
|
|
7459
|
+
key,
|
|
7460
|
+
label: termPattern((terms) => terms.field[key])
|
|
7461
|
+
}));
|
|
7291
7462
|
var SEVERITY_LABEL = termPattern((terms) => terms.field.severity);
|
|
7292
7463
|
var SEVERITY_LINE = bulletField(SEVERITY_LABEL, `(?:${termPattern((terms) => terms.high)}|${termPattern((terms) => terms.medium)})\\s*$`);
|
|
7293
7464
|
var FIELD_START = new RegExp(`^[-*+]\\s*(?:\\*\\*)?(?:${[SEVERITY_LABEL, ...FINDING_FIELDS.map((field) => field.label)].join("|")})`, "iu");
|
|
@@ -7378,28 +7549,28 @@ function clean(text) {
|
|
|
7378
7549
|
|
|
7379
7550
|
// review/session.ts
|
|
7380
7551
|
function createReviewSessionRunner(pool) {
|
|
7381
|
-
return (
|
|
7552
|
+
return (request) => runReviewSession(pool, request);
|
|
7382
7553
|
}
|
|
7383
|
-
async function runReviewSession(
|
|
7384
|
-
if (
|
|
7554
|
+
async function runReviewSession(pool, { role, config, prompt, cwd, signal }) {
|
|
7555
|
+
if (signal?.aborted)
|
|
7385
7556
|
return { kind: "aborted" };
|
|
7386
7557
|
let spawned;
|
|
7387
7558
|
try {
|
|
7388
|
-
spawned = await
|
|
7389
|
-
cwd
|
|
7390
|
-
role
|
|
7391
|
-
model: await
|
|
7392
|
-
thinking:
|
|
7393
|
-
tools: [...new Set(
|
|
7394
|
-
systemPrompt: { mode: "replace", text: clean2(
|
|
7559
|
+
spawned = await pool.spawn({
|
|
7560
|
+
cwd,
|
|
7561
|
+
role,
|
|
7562
|
+
model: await pool.resolveModel(config.model),
|
|
7563
|
+
thinking: config.thinking,
|
|
7564
|
+
tools: [...new Set(config.tools)].filter((tool) => tool !== "write" && tool !== "edit"),
|
|
7565
|
+
systemPrompt: { mode: "replace", text: clean2(prompt.system) },
|
|
7395
7566
|
contextFiles: false,
|
|
7396
7567
|
persistence: { type: "memory" },
|
|
7397
7568
|
isolated: true
|
|
7398
7569
|
});
|
|
7399
7570
|
} catch (error) {
|
|
7400
|
-
return
|
|
7571
|
+
return signal?.aborted ? { kind: "aborted" } : { kind: "error", message: errorText(error) };
|
|
7401
7572
|
}
|
|
7402
|
-
if (
|
|
7573
|
+
if (signal?.aborted) {
|
|
7403
7574
|
await spawned.dispose();
|
|
7404
7575
|
return { kind: "aborted" };
|
|
7405
7576
|
}
|
|
@@ -7421,13 +7592,13 @@ async function runReviewSession(options) {
|
|
|
7421
7592
|
interrupted = "aborted";
|
|
7422
7593
|
wake();
|
|
7423
7594
|
};
|
|
7424
|
-
|
|
7595
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7425
7596
|
const timeout = setTimeout(() => {
|
|
7426
7597
|
interrupted = "timeout";
|
|
7427
7598
|
wake();
|
|
7428
|
-
},
|
|
7599
|
+
}, config.timeoutMs);
|
|
7429
7600
|
try {
|
|
7430
|
-
const run = spawned.prompt(clean2(
|
|
7601
|
+
const run = spawned.prompt(clean2(prompt.user)).catch((error) => {
|
|
7431
7602
|
finalError = errorText(error);
|
|
7432
7603
|
});
|
|
7433
7604
|
await Promise.race([run, interruption]);
|
|
@@ -7438,7 +7609,7 @@ async function runReviewSession(options) {
|
|
|
7438
7609
|
return finalText?.trim() ? { kind: "output", text: finalText } : { kind: "empty" };
|
|
7439
7610
|
} finally {
|
|
7440
7611
|
clearTimeout(timeout);
|
|
7441
|
-
|
|
7612
|
+
signal?.removeEventListener("abort", onAbort);
|
|
7442
7613
|
unsubscribe();
|
|
7443
7614
|
await spawned.dispose();
|
|
7444
7615
|
}
|
|
@@ -7448,572 +7619,25 @@ function assistantMessage(event) {
|
|
|
7448
7619
|
return event.message.role === "assistant" ? event.message : undefined;
|
|
7449
7620
|
if (event.type !== "agent_end")
|
|
7450
7621
|
return;
|
|
7451
|
-
return [...event.messages].reverse().find((message) => message.role === "assistant");
|
|
7452
|
-
}
|
|
7453
|
-
function clean2(text) {
|
|
7454
|
-
return text.replaceAll("\x00", "");
|
|
7455
|
-
}
|
|
7456
|
-
function errorText(error) {
|
|
7457
|
-
return error instanceof Error ? error.message : String(error);
|
|
7458
|
-
}
|
|
7459
|
-
|
|
7460
|
-
// review/state.ts
|
|
7461
|
-
var SUGGESTIONS_HEADING2 = new RegExp(`^##\\s*${termPattern((terms) => terms.suggestions)}`, "iu");
|
|
7462
|
-
var ISSUE_LINE = new RegExp(`^[-*+]\\s*(?:\\*\\*)?${termPattern((terms) => terms.field.issue)}(?:\\*\\*)?\\s*[::]\\s*(.+)$`, "iu");
|
|
7463
|
-
function initialState(runId) {
|
|
7464
|
-
return {
|
|
7465
|
-
runId,
|
|
7466
|
-
phase: "idle",
|
|
7467
|
-
round: 0,
|
|
7468
|
-
focus: "",
|
|
7469
|
-
history: [],
|
|
7470
|
-
active: null,
|
|
7471
|
-
pending: null,
|
|
7472
|
-
repair: null,
|
|
7473
|
-
summary: null,
|
|
7474
|
-
consecutiveFailures: 0,
|
|
7475
|
-
startedAt: 0,
|
|
7476
|
-
roundStartedAt: 0,
|
|
7477
|
-
updatedAt: 0
|
|
7478
|
-
};
|
|
7479
|
-
}
|
|
7480
|
-
function reduce(state, event, limits, now) {
|
|
7481
|
-
switch (event.type) {
|
|
7482
|
-
case "START":
|
|
7483
|
-
return onStart(state, event, limits, now);
|
|
7484
|
-
case "RECOVER":
|
|
7485
|
-
return onRecover(state);
|
|
7486
|
-
case "REVIEWER_SETTLED":
|
|
7487
|
-
return onReviewerSettled(state, event, limits, now);
|
|
7488
|
-
case "ADVISOR_SETTLED":
|
|
7489
|
-
return onAdvisorSettled(state, event, limits, now);
|
|
7490
|
-
case "ADVISOR_SKIPPED":
|
|
7491
|
-
return onAdvisorSkipped(state, now);
|
|
7492
|
-
case "INFRASTRUCTURE_ERROR":
|
|
7493
|
-
return onInfrastructureError(state, event.details, now);
|
|
7494
|
-
case "ADVANCE":
|
|
7495
|
-
return onAdvance(state, limits, now);
|
|
7496
|
-
case "FEEDBACK_DISPATCHED":
|
|
7497
|
-
return updateRepairStatus(state, "pending", "awaiting_start", now);
|
|
7498
|
-
case "REPAIR_STARTED":
|
|
7499
|
-
return updateRepairStatus(state, "awaiting_start", "running", now);
|
|
7500
|
-
case "REPAIR_COMPLETED":
|
|
7501
|
-
return updateRepairStatus(state, "running", "completed", now);
|
|
7502
|
-
case "SUMMARY_DISPATCHED":
|
|
7503
|
-
return updateSummaryStatus(state, "pending", "awaiting_start", now);
|
|
7504
|
-
case "SUMMARY_STARTED":
|
|
7505
|
-
return updateSummaryStatus(state, "awaiting_start", "running", now);
|
|
7506
|
-
case "SUMMARY_SETTLED":
|
|
7507
|
-
return onSummarySettled(state, now);
|
|
7508
|
-
case "CANCEL":
|
|
7509
|
-
return onCancel(state, event.reason, now);
|
|
7510
|
-
case "TIMEOUT":
|
|
7511
|
-
return onTimeout(state, now);
|
|
7512
|
-
}
|
|
7513
|
-
}
|
|
7514
|
-
function onStart(state, event, limits, now) {
|
|
7515
|
-
if (state.phase === "reviewing" || state.phase === "needs_fix" || state.phase === "awaiting_fix")
|
|
7516
|
-
return { state, effects: [] };
|
|
7517
|
-
const focus = event.focus.trim();
|
|
7518
|
-
if (event.busy)
|
|
7519
|
-
return {
|
|
7520
|
-
state: {
|
|
7521
|
-
...initialState(state.runId),
|
|
7522
|
-
phase: "queued",
|
|
7523
|
-
focus,
|
|
7524
|
-
startedAt: now,
|
|
7525
|
-
updatedAt: now
|
|
7526
|
-
},
|
|
7527
|
-
effects: [{ kind: "advance" }]
|
|
7528
|
-
};
|
|
7529
|
-
return {
|
|
7530
|
-
state: beginRound({ ...initialState(state.runId), startedAt: now }, focus, 1, limits, now),
|
|
7531
|
-
effects: [
|
|
7532
|
-
{
|
|
7533
|
-
kind: "send_card",
|
|
7534
|
-
card: {
|
|
7535
|
-
kind: "start",
|
|
7536
|
-
round: 1,
|
|
7537
|
-
focus,
|
|
7538
|
-
models: limits.reviewers.map((item) => item.model)
|
|
7539
|
-
}
|
|
7540
|
-
},
|
|
7541
|
-
{ kind: "advance" }
|
|
7542
|
-
]
|
|
7543
|
-
};
|
|
7544
|
-
}
|
|
7545
|
-
function onRecover(state) {
|
|
7546
|
-
if (state.phase === "idle" || state.phase === "settled")
|
|
7547
|
-
return { state, effects: [] };
|
|
7548
|
-
const repair = state.phase === "awaiting_fix" && state.repair && state.repair.status !== "completed" ? { ...state.repair, status: "pending" } : state.repair;
|
|
7549
|
-
const summary = state.phase === "summarizing" && state.summary ? { ...state.summary, status: "pending" } : state.summary;
|
|
7550
|
-
return { state: { ...state, repair, summary }, effects: [] };
|
|
7551
|
-
}
|
|
7552
|
-
function updateRepairStatus(state, expected, status, now) {
|
|
7553
|
-
if (state.phase !== "awaiting_fix" || state.repair?.status !== expected)
|
|
7554
|
-
return { state, effects: [] };
|
|
7555
|
-
return {
|
|
7556
|
-
state: { ...state, repair: { ...state.repair, status }, updatedAt: now },
|
|
7557
|
-
effects: []
|
|
7558
|
-
};
|
|
7559
|
-
}
|
|
7560
|
-
function updateSummaryStatus(state, expected, status, now) {
|
|
7561
|
-
if (state.phase !== "summarizing" || state.summary?.status !== expected)
|
|
7562
|
-
return { state, effects: [] };
|
|
7563
|
-
return {
|
|
7564
|
-
state: { ...state, summary: { ...state.summary, status }, updatedAt: now },
|
|
7565
|
-
effects: []
|
|
7566
|
-
};
|
|
7567
|
-
}
|
|
7568
|
-
function onSummarySettled(state, now) {
|
|
7569
|
-
if (state.phase !== "summarizing")
|
|
7570
|
-
return { state, effects: [] };
|
|
7571
|
-
return {
|
|
7572
|
-
state: { ...state, phase: "settled", summary: null, updatedAt: now },
|
|
7573
|
-
effects: []
|
|
7574
|
-
};
|
|
7575
|
-
}
|
|
7576
|
-
function summarizing(kind) {
|
|
7577
|
-
return { phase: "summarizing", summary: { kind, status: "pending" } };
|
|
7578
|
-
}
|
|
7579
|
-
function onAdvance(state, limits, now) {
|
|
7580
|
-
if (state.phase === "queued")
|
|
7581
|
-
return {
|
|
7582
|
-
state: beginRound(state, state.focus, 1, limits, now),
|
|
7583
|
-
effects: [
|
|
7584
|
-
{
|
|
7585
|
-
kind: "send_card",
|
|
7586
|
-
card: {
|
|
7587
|
-
kind: "start",
|
|
7588
|
-
round: 1,
|
|
7589
|
-
focus: state.focus,
|
|
7590
|
-
models: limits.reviewers.map((item) => item.model)
|
|
7591
|
-
}
|
|
7592
|
-
},
|
|
7593
|
-
{ kind: "advance" }
|
|
7594
|
-
]
|
|
7595
|
-
};
|
|
7596
|
-
if (state.phase !== "awaiting_fix" || state.repair?.status !== "completed")
|
|
7597
|
-
return { state, effects: [] };
|
|
7598
|
-
if (state.round >= limits.maxRounds)
|
|
7599
|
-
return {
|
|
7600
|
-
state: { ...state, ...summarizing("max_rounds"), repair: null, updatedAt: now },
|
|
7601
|
-
effects: [
|
|
7602
|
-
{
|
|
7603
|
-
kind: "send_card",
|
|
7604
|
-
card: { kind: "stop", reason: "max_rounds", round: state.round, details: "" }
|
|
7605
|
-
},
|
|
7606
|
-
{ kind: "advance" }
|
|
7607
|
-
]
|
|
7608
|
-
};
|
|
7609
|
-
return {
|
|
7610
|
-
state: beginRound(state, state.focus, state.round + 1, limits, now),
|
|
7611
|
-
effects: [{ kind: "advance" }]
|
|
7612
|
-
};
|
|
7613
|
-
}
|
|
7614
|
-
function onReviewerSettled(state, event, limits, now) {
|
|
7615
|
-
if (state.phase !== "reviewing" || !state.active)
|
|
7616
|
-
return { state, effects: [] };
|
|
7617
|
-
const active = state.active;
|
|
7618
|
-
const settled = [];
|
|
7619
|
-
const reviewers = active.reviewers.map((item) => {
|
|
7620
|
-
if (item.index !== event.index)
|
|
7621
|
-
return item;
|
|
7622
|
-
settled.push(event.result);
|
|
7623
|
-
return { ...item, status: event.result.status, result: event.result };
|
|
7624
|
-
});
|
|
7625
|
-
const settledCount = active.settledCount + 1;
|
|
7626
|
-
const nextActive = { ...active, reviewers, settledCount };
|
|
7627
|
-
if (settledCount < reviewers.length)
|
|
7628
|
-
return {
|
|
7629
|
-
state: { ...state, active: nextActive, updatedAt: now },
|
|
7630
|
-
effects: []
|
|
7631
|
-
};
|
|
7632
|
-
const allSettled = nextActive.reviewers.flatMap((item) => item.result ? [item.result] : []);
|
|
7633
|
-
return settleRound(state, nextActive, allSettled, limits, now);
|
|
7634
|
-
}
|
|
7635
|
-
function settleRound(state, active, settled, limits, now) {
|
|
7636
|
-
const { result, displayDetails, feedbackDetails, archiveDetails, summary } = aggregate(settled);
|
|
7637
|
-
const base = { ...state, active: null, updatedAt: now };
|
|
7638
|
-
const totalElapsedMs = Math.max(0, now - state.startedAt);
|
|
7639
|
-
const passSummary = result === "passed" ? appendClosedFindings(summary, state.history, active.round) : summary;
|
|
7640
|
-
if (result === "passed" || result === "error") {
|
|
7641
|
-
const round = roundRecord(active.round, result, archiveDetails, settled, undefined, state.roundStartedAt, now);
|
|
7642
|
-
const history = [...state.history, round];
|
|
7643
|
-
if (result === "passed")
|
|
7644
|
-
return {
|
|
7645
|
-
state: { ...base, ...summarizing("passed"), history },
|
|
7646
|
-
effects: [
|
|
7647
|
-
{
|
|
7648
|
-
kind: "send_card",
|
|
7649
|
-
card: {
|
|
7650
|
-
kind: "pass",
|
|
7651
|
-
round: active.round,
|
|
7652
|
-
summary: passSummary,
|
|
7653
|
-
details: archiveDetails,
|
|
7654
|
-
elapsedMs: round.elapsedMs,
|
|
7655
|
-
totalElapsedMs
|
|
7656
|
-
}
|
|
7657
|
-
},
|
|
7658
|
-
{ kind: "advance" }
|
|
7659
|
-
]
|
|
7660
|
-
};
|
|
7661
|
-
return {
|
|
7662
|
-
state: { ...base, phase: "settled", history },
|
|
7663
|
-
effects: [
|
|
7664
|
-
{
|
|
7665
|
-
kind: "send_card",
|
|
7666
|
-
card: { kind: "error", message: displayDetails, elapsedMs: round.elapsedMs, totalElapsedMs }
|
|
7667
|
-
}
|
|
7668
|
-
]
|
|
7669
|
-
};
|
|
7670
|
-
}
|
|
7671
|
-
const consecutiveFailures = state.consecutiveFailures + 1;
|
|
7672
|
-
if (active.round >= limits.maxRounds) {
|
|
7673
|
-
const round = roundRecord(active.round, "failed", archiveDetails, settled, undefined, state.roundStartedAt, now);
|
|
7674
|
-
return {
|
|
7675
|
-
state: {
|
|
7676
|
-
...base,
|
|
7677
|
-
...summarizing("max_rounds"),
|
|
7678
|
-
consecutiveFailures,
|
|
7679
|
-
history: [...state.history, round]
|
|
7680
|
-
},
|
|
7681
|
-
effects: [
|
|
7682
|
-
{
|
|
7683
|
-
kind: "send_card",
|
|
7684
|
-
card: {
|
|
7685
|
-
kind: "stop",
|
|
7686
|
-
reason: "max_rounds",
|
|
7687
|
-
round: active.round,
|
|
7688
|
-
details: displayDetails,
|
|
7689
|
-
elapsedMs: round.elapsedMs
|
|
7690
|
-
}
|
|
7691
|
-
},
|
|
7692
|
-
{ kind: "advance" }
|
|
7693
|
-
]
|
|
7694
|
-
};
|
|
7695
|
-
}
|
|
7696
|
-
const failCard = {
|
|
7697
|
-
kind: "send_card",
|
|
7698
|
-
card: {
|
|
7699
|
-
kind: "fail",
|
|
7700
|
-
round: active.round,
|
|
7701
|
-
details: displayDetails,
|
|
7702
|
-
advisor: null,
|
|
7703
|
-
elapsedMs: Math.max(0, now - state.roundStartedAt),
|
|
7704
|
-
totalElapsedMs
|
|
7705
|
-
}
|
|
7706
|
-
};
|
|
7707
|
-
const pending = { round: active.round, reviewers: settled, details: feedbackDetails };
|
|
7708
|
-
if (consecutiveFailures >= limits.advisorAfterFailures)
|
|
7709
|
-
return {
|
|
7710
|
-
state: { ...base, phase: "needs_fix", pending, consecutiveFailures },
|
|
7711
|
-
effects: [failCard, { kind: "advance" }]
|
|
7712
|
-
};
|
|
7713
|
-
return {
|
|
7714
|
-
state: {
|
|
7715
|
-
...base,
|
|
7716
|
-
phase: "awaiting_fix",
|
|
7717
|
-
pending: null,
|
|
7718
|
-
repair: { details: feedbackDetails, advisor: null, status: "pending" },
|
|
7719
|
-
consecutiveFailures,
|
|
7720
|
-
history: [
|
|
7721
|
-
...state.history,
|
|
7722
|
-
roundRecord(active.round, "failed", archiveDetails, settled, undefined, state.roundStartedAt, now)
|
|
7723
|
-
]
|
|
7724
|
-
},
|
|
7725
|
-
effects: [failCard, { kind: "advance" }]
|
|
7726
|
-
};
|
|
7727
|
-
}
|
|
7728
|
-
function onAdvisorSettled(state, event, limits, now) {
|
|
7729
|
-
if (state.phase !== "needs_fix" || !state.pending)
|
|
7730
|
-
return { state, effects: [] };
|
|
7731
|
-
const pending = state.pending;
|
|
7732
|
-
const advisor = event.result;
|
|
7733
|
-
if (advisor.verdict === "stop") {
|
|
7734
|
-
const round = roundRecord(pending.round, "stopped", advisor.advice, pending.reviewers, advisor, state.roundStartedAt, now);
|
|
7735
|
-
return {
|
|
7736
|
-
state: { ...state, ...summarizing("advisor_stop"), pending: null, history: [...state.history, round], updatedAt: now },
|
|
7737
|
-
effects: [
|
|
7738
|
-
{
|
|
7739
|
-
kind: "send_card",
|
|
7740
|
-
card: {
|
|
7741
|
-
kind: "stop",
|
|
7742
|
-
reason: "advisor",
|
|
7743
|
-
round: pending.round,
|
|
7744
|
-
details: advisor.advice,
|
|
7745
|
-
advisor,
|
|
7746
|
-
advisorModel: limits.advisorModel,
|
|
7747
|
-
elapsedMs: Math.max(0, now - state.updatedAt),
|
|
7748
|
-
totalElapsedMs: Math.max(0, now - state.startedAt)
|
|
7749
|
-
}
|
|
7750
|
-
},
|
|
7751
|
-
{ kind: "advance" }
|
|
7752
|
-
]
|
|
7753
|
-
};
|
|
7754
|
-
}
|
|
7755
|
-
const round = roundRecord(pending.round, "failed", pending.details, pending.reviewers, advisor, state.roundStartedAt, now);
|
|
7756
|
-
return {
|
|
7757
|
-
state: {
|
|
7758
|
-
...state,
|
|
7759
|
-
phase: "awaiting_fix",
|
|
7760
|
-
pending: null,
|
|
7761
|
-
repair: { details: pending.details, advisor, status: "pending" },
|
|
7762
|
-
history: [...state.history, round],
|
|
7763
|
-
updatedAt: now
|
|
7764
|
-
},
|
|
7765
|
-
effects: [
|
|
7766
|
-
{
|
|
7767
|
-
kind: "send_card",
|
|
7768
|
-
card: {
|
|
7769
|
-
kind: "advisor",
|
|
7770
|
-
advisor,
|
|
7771
|
-
advisorModel: limits.advisorModel,
|
|
7772
|
-
elapsedMs: Math.max(0, now - state.updatedAt)
|
|
7773
|
-
}
|
|
7774
|
-
},
|
|
7775
|
-
{ kind: "advance" }
|
|
7776
|
-
]
|
|
7777
|
-
};
|
|
7778
|
-
}
|
|
7779
|
-
function onAdvisorSkipped(state, now) {
|
|
7780
|
-
if (state.phase !== "needs_fix" || !state.pending)
|
|
7781
|
-
return { state, effects: [] };
|
|
7782
|
-
const pending = state.pending;
|
|
7783
|
-
const round = roundRecord(pending.round, "failed", pending.details, pending.reviewers, undefined, state.roundStartedAt, now);
|
|
7784
|
-
return {
|
|
7785
|
-
state: {
|
|
7786
|
-
...state,
|
|
7787
|
-
phase: "awaiting_fix",
|
|
7788
|
-
pending: null,
|
|
7789
|
-
repair: { details: pending.details, advisor: null, status: "pending" },
|
|
7790
|
-
history: [...state.history, round],
|
|
7791
|
-
updatedAt: now
|
|
7792
|
-
},
|
|
7793
|
-
effects: [{ kind: "advance" }]
|
|
7794
|
-
};
|
|
7795
|
-
}
|
|
7796
|
-
function onCancel(state, reason, now) {
|
|
7797
|
-
if (state.phase === "idle" || state.phase === "settled")
|
|
7798
|
-
return { state, effects: [] };
|
|
7799
|
-
if (state.phase === "summarizing")
|
|
7800
|
-
return onSummarySettled(state, now);
|
|
7801
|
-
if (state.phase === "queued")
|
|
7802
|
-
return {
|
|
7803
|
-
state: { ...state, phase: "settled", updatedAt: now },
|
|
7804
|
-
effects: [{ kind: "send_card", card: { kind: "cancel", round: 0, reason } }]
|
|
7805
|
-
};
|
|
7806
|
-
const round = resolveRoundRecord(state, "cancelled", reason, now);
|
|
7807
|
-
return {
|
|
7808
|
-
state: {
|
|
7809
|
-
...state,
|
|
7810
|
-
phase: "settled",
|
|
7811
|
-
active: null,
|
|
7812
|
-
pending: null,
|
|
7813
|
-
repair: null,
|
|
7814
|
-
history: round ? [...state.history, round] : state.history,
|
|
7815
|
-
updatedAt: now
|
|
7816
|
-
},
|
|
7817
|
-
effects: [{ kind: "send_card", card: { kind: "cancel", round: state.round, reason } }]
|
|
7818
|
-
};
|
|
7819
|
-
}
|
|
7820
|
-
function onInfrastructureError(state, details, now) {
|
|
7821
|
-
if (state.phase === "idle" || state.phase === "settled")
|
|
7822
|
-
return { state, effects: [] };
|
|
7823
|
-
if (state.phase === "summarizing")
|
|
7824
|
-
return onSummarySettled(state, now);
|
|
7825
|
-
const message = details.trim() || "review infrastructure unavailable";
|
|
7826
|
-
const reviewers = state.active?.reviewers.flatMap((item) => item.result ? [item.result] : []) ?? state.pending?.reviewers ?? [];
|
|
7827
|
-
const round = state.round > 0 ? roundRecord(state.round, "error", message, reviewers, undefined, state.roundStartedAt, now) : undefined;
|
|
7828
|
-
return {
|
|
7829
|
-
state: {
|
|
7830
|
-
...state,
|
|
7831
|
-
phase: "settled",
|
|
7832
|
-
active: null,
|
|
7833
|
-
pending: null,
|
|
7834
|
-
repair: null,
|
|
7835
|
-
history: round ? [...state.history, round] : state.history,
|
|
7836
|
-
updatedAt: now
|
|
7837
|
-
},
|
|
7838
|
-
effects: [{ kind: "send_card", card: { kind: "error", message } }]
|
|
7839
|
-
};
|
|
7840
|
-
}
|
|
7841
|
-
function onTimeout(state, now) {
|
|
7842
|
-
if (state.phase === "idle" || state.phase === "settled")
|
|
7843
|
-
return { state, effects: [] };
|
|
7844
|
-
if (state.phase === "summarizing")
|
|
7845
|
-
return onSummarySettled(state, now);
|
|
7846
|
-
if (state.phase === "queued")
|
|
7847
|
-
return {
|
|
7848
|
-
state: { ...state, phase: "settled", updatedAt: now },
|
|
7849
|
-
effects: [{ kind: "send_card", card: { kind: "timeout", round: 0, reason: "timeout" } }]
|
|
7850
|
-
};
|
|
7851
|
-
const round = resolveRoundRecord(state, "timed_out", "timeout", now);
|
|
7852
|
-
return {
|
|
7853
|
-
state: {
|
|
7854
|
-
...state,
|
|
7855
|
-
phase: "settled",
|
|
7856
|
-
active: null,
|
|
7857
|
-
pending: null,
|
|
7858
|
-
repair: null,
|
|
7859
|
-
history: round ? [...state.history, round] : state.history,
|
|
7860
|
-
updatedAt: now
|
|
7861
|
-
},
|
|
7862
|
-
effects: [
|
|
7863
|
-
{ kind: "send_card", card: { kind: "timeout", round: state.round, reason: "timeout" } }
|
|
7864
|
-
]
|
|
7865
|
-
};
|
|
7866
|
-
}
|
|
7867
|
-
function beginRound(state, focus, round, limits, now) {
|
|
7868
|
-
const reviewers = limits.reviewers.map((item, index) => ({
|
|
7869
|
-
index,
|
|
7870
|
-
model: item.model,
|
|
7871
|
-
thinking: item.thinking,
|
|
7872
|
-
status: "running",
|
|
7873
|
-
result: null
|
|
7874
|
-
}));
|
|
7875
|
-
return {
|
|
7876
|
-
...state,
|
|
7877
|
-
phase: "reviewing",
|
|
7878
|
-
round,
|
|
7879
|
-
focus,
|
|
7880
|
-
active: { round, reviewers, settledCount: 0 },
|
|
7881
|
-
pending: null,
|
|
7882
|
-
repair: null,
|
|
7883
|
-
summary: null,
|
|
7884
|
-
roundStartedAt: now,
|
|
7885
|
-
updatedAt: now
|
|
7886
|
-
};
|
|
7887
|
-
}
|
|
7888
|
-
function aggregate(reviewers) {
|
|
7889
|
-
const verdicts = reviewers.filter((item) => item.status === "passed" || item.status === "failed");
|
|
7890
|
-
const absent = reviewers.filter((item) => item.status === "error");
|
|
7891
|
-
const archiveDetails = aggregateDetails(reviewers);
|
|
7892
|
-
if (verdicts.length === 0)
|
|
7893
|
-
return { result: "error", displayDetails: archiveDetails, feedbackDetails: "", archiveDetails, summary: "" };
|
|
7894
|
-
const failed = verdicts.filter((item) => item.status === "failed");
|
|
7895
|
-
if (failed.length > 0)
|
|
7896
|
-
return {
|
|
7897
|
-
result: "failed",
|
|
7898
|
-
displayDetails: archiveDetails,
|
|
7899
|
-
feedbackDetails: aggregateDetails(failed),
|
|
7900
|
-
archiveDetails,
|
|
7901
|
-
summary: ""
|
|
7902
|
-
};
|
|
7903
|
-
return {
|
|
7904
|
-
result: "passed",
|
|
7905
|
-
displayDetails: archiveDetails,
|
|
7906
|
-
feedbackDetails: "",
|
|
7907
|
-
archiveDetails,
|
|
7908
|
-
summary: aggregatePassSummary(verdicts, absent)
|
|
7909
|
-
};
|
|
7910
|
-
}
|
|
7911
|
-
function aggregateDetails(reviewers) {
|
|
7912
|
-
return reviewers.map((item) => `${modelLabel(item)}
|
|
7913
|
-
${item.details.trim()}`).join(`
|
|
7914
|
-
|
|
7915
|
-
`);
|
|
7916
|
-
}
|
|
7917
|
-
function aggregatePassSummary(passed, absent) {
|
|
7918
|
-
const fallback = msg6.summary.passedFallback;
|
|
7919
|
-
const lines = passed.length === 1 ? [passBody(passed[0]?.summary ?? "") || fallback] : passed.map((item) => msg6.summary.modelBullet(shortModel2(item.model), passBody(item.summary) || fallback));
|
|
7920
|
-
if (absent.length > 0)
|
|
7921
|
-
lines.push(msg6.summary.absent(absent.map((item) => ({ model: shortModel2(item.model), reason: firstLine(item.details) }))));
|
|
7922
|
-
const suggestions = [...new Set(passed.flatMap((item) => splitSuggestions(item.details).suggestions))];
|
|
7923
|
-
if (suggestions.length === 0)
|
|
7924
|
-
return lines.join(`
|
|
7925
|
-
`);
|
|
7926
|
-
return [
|
|
7927
|
-
...lines,
|
|
7928
|
-
"",
|
|
7929
|
-
`## ${msg6.terms.suggestions}`,
|
|
7930
|
-
...suggestions.map((item) => `- ${item}`)
|
|
7931
|
-
].join(`
|
|
7932
|
-
`);
|
|
7933
|
-
}
|
|
7934
|
-
function firstLine(text) {
|
|
7935
|
-
return text.trim().split(/\r?\n/u, 1)[0]?.split(/[::]/u, 1)[0]?.trim() ?? "";
|
|
7936
|
-
}
|
|
7937
|
-
function splitSuggestions(summary) {
|
|
7938
|
-
const lines = summary.split(/\r?\n/u);
|
|
7939
|
-
const index = lines.findIndex((line) => SUGGESTIONS_HEADING2.test(line.trim()));
|
|
7940
|
-
if (index < 0)
|
|
7941
|
-
return { body: summary, suggestions: [] };
|
|
7942
|
-
return {
|
|
7943
|
-
body: lines.slice(0, index).join(`
|
|
7944
|
-
`),
|
|
7945
|
-
suggestions: lines.slice(index + 1).map((line) => line.replace(/^[-*]\s*/u, "").trim()).filter(Boolean)
|
|
7946
|
-
};
|
|
7947
|
-
}
|
|
7948
|
-
function passBody(summary) {
|
|
7949
|
-
return summary.replace(/^(?:PASS\s*)/iu, "").trim();
|
|
7950
|
-
}
|
|
7951
|
-
function shortModel2(model) {
|
|
7952
|
-
return model.split("/").at(-1) || model;
|
|
7953
|
-
}
|
|
7954
|
-
function appendClosedFindings(summary, history, beforeRound) {
|
|
7955
|
-
const seen = new Set;
|
|
7956
|
-
const findings = [];
|
|
7957
|
-
for (const round of history) {
|
|
7958
|
-
if (round.round >= beforeRound || round.result !== "failed")
|
|
7959
|
-
continue;
|
|
7960
|
-
for (const line of round.details.split(/\r?\n/u)) {
|
|
7961
|
-
const match = ISSUE_LINE.exec(line.trim());
|
|
7962
|
-
const issue = match?.[1]?.replace(/`([^`]+)`/gu, "$1").trim();
|
|
7963
|
-
if (!issue)
|
|
7964
|
-
continue;
|
|
7965
|
-
const key = issue.replace(/\s+/gu, "");
|
|
7966
|
-
if (seen.has(key))
|
|
7967
|
-
continue;
|
|
7968
|
-
seen.add(key);
|
|
7969
|
-
findings.push({ round: round.round, issue });
|
|
7970
|
-
}
|
|
7971
|
-
}
|
|
7972
|
-
if (findings.length === 0)
|
|
7973
|
-
return summary;
|
|
7974
|
-
const shown = findings.slice(0, 6);
|
|
7975
|
-
const recap = [msg6.summary.closedHeading, ...shown.map((finding) => msg6.summary.closedItem(finding.issue, finding.round))];
|
|
7976
|
-
if (findings.length > shown.length)
|
|
7977
|
-
recap.push(msg6.summary.closedMore(findings.length - shown.length));
|
|
7978
|
-
return `${summary}
|
|
7979
|
-
|
|
7980
|
-
${recap.join(`
|
|
7981
|
-
`)}`;
|
|
7982
|
-
}
|
|
7983
|
-
function modelLabel(item) {
|
|
7984
|
-
return msg6.summary.modelSection(item.index + 1, shortModel2(item.model));
|
|
7622
|
+
return [...event.messages].reverse().find((message) => message.role === "assistant");
|
|
7985
7623
|
}
|
|
7986
|
-
function
|
|
7987
|
-
|
|
7988
|
-
const settled = state.active.reviewers.flatMap((item) => item.result ? [item.result] : []);
|
|
7989
|
-
return roundRecord(state.active.round, result, "", settled, undefined, state.roundStartedAt, now, reason);
|
|
7990
|
-
}
|
|
7991
|
-
if (state.phase === "needs_fix" && state.pending)
|
|
7992
|
-
return roundRecord(state.pending.round, result, "", state.pending.reviewers, undefined, state.roundStartedAt, now, reason);
|
|
7993
|
-
return;
|
|
7624
|
+
function clean2(text) {
|
|
7625
|
+
return text.replaceAll("\x00", "");
|
|
7994
7626
|
}
|
|
7995
|
-
function
|
|
7996
|
-
return
|
|
7997
|
-
round,
|
|
7998
|
-
result,
|
|
7999
|
-
details,
|
|
8000
|
-
reviewers,
|
|
8001
|
-
...advisor ? { advisor } : {},
|
|
8002
|
-
...reason ? { reason } : {},
|
|
8003
|
-
elapsedMs: roundStartedAt ? Math.max(0, now - roundStartedAt) : 0
|
|
8004
|
-
};
|
|
7627
|
+
function errorText(error) {
|
|
7628
|
+
return error instanceof Error ? error.message : String(error);
|
|
8005
7629
|
}
|
|
8006
7630
|
|
|
8007
7631
|
// review/index.ts
|
|
8008
7632
|
var FEEDBACK_TYPE = "firecode-review-feedback";
|
|
8009
7633
|
var SUMMARY_REQUEST_TYPE = "firecode-review-summary";
|
|
8010
|
-
var
|
|
7634
|
+
var FOLLOW_UP_START_TIMEOUT_MS = 2000;
|
|
8011
7635
|
var PROMPTS = new URL("./prompts/", new URL("./review/index.js", import.meta.url).href);
|
|
8012
7636
|
function overallTimeoutMs(config) {
|
|
8013
7637
|
return Math.max(30 * 60000, config.maxRounds * config.timeoutMinutes * 2 * 60000);
|
|
8014
7638
|
}
|
|
8015
7639
|
function isActive(state) {
|
|
8016
|
-
return state.phase
|
|
7640
|
+
return state.phase !== "idle" && state.phase !== "settled";
|
|
8017
7641
|
}
|
|
8018
7642
|
function registerReview(pi, enabled = true, configBroken = false, dependencies = {}) {
|
|
8019
7643
|
registerCardRenderer(pi);
|
|
@@ -8048,23 +7672,23 @@ function registerReview(pi, enabled = true, configBroken = false, dependencies =
|
|
|
8048
7672
|
pi.on("session_shutdown", (event, ctx) => handleShutdown(rt, event.reason, ctx));
|
|
8049
7673
|
return handle;
|
|
8050
7674
|
}
|
|
7675
|
+
function sealCheckpoint(pi, state) {
|
|
7676
|
+
beginCheckpoint(pi, {
|
|
7677
|
+
...state,
|
|
7678
|
+
phase: "settled",
|
|
7679
|
+
active: null,
|
|
7680
|
+
pending: null,
|
|
7681
|
+
repair: null,
|
|
7682
|
+
summary: null,
|
|
7683
|
+
updatedAt: Date.now()
|
|
7684
|
+
});
|
|
7685
|
+
}
|
|
8051
7686
|
function settleDisabledCheckpoint(pi, ctx) {
|
|
8052
7687
|
const checkpoint = readCheckpoint(ctx);
|
|
8053
7688
|
if (!checkpoint || !isActive(checkpoint))
|
|
8054
7689
|
return;
|
|
8055
|
-
settleUnavailableCheckpoint(pi, ctx, checkpoint);
|
|
8056
|
-
}
|
|
8057
|
-
function settleUnavailableCheckpoint(pi, ctx, checkpoint) {
|
|
8058
7690
|
try {
|
|
8059
|
-
|
|
8060
|
-
...checkpoint,
|
|
8061
|
-
phase: "settled",
|
|
8062
|
-
active: null,
|
|
8063
|
-
pending: null,
|
|
8064
|
-
repair: null,
|
|
8065
|
-
summary: null,
|
|
8066
|
-
updatedAt: Date.now()
|
|
8067
|
-
});
|
|
7691
|
+
sealCheckpoint(pi, checkpoint);
|
|
8068
7692
|
} catch (error) {
|
|
8069
7693
|
if (ctx.hasUI)
|
|
8070
7694
|
ctx.ui.notify(msg6.notify.cannotSealOld(errorText2(error)), "error");
|
|
@@ -8081,23 +7705,22 @@ function dispatch(rt, event) {
|
|
|
8081
7705
|
if (!persist(rt, state))
|
|
8082
7706
|
return;
|
|
8083
7707
|
if (!isActive(state))
|
|
8084
|
-
clearWatchdog(
|
|
7708
|
+
clearWatchdog(active);
|
|
8085
7709
|
syncOccupancy(rt, active);
|
|
8086
|
-
syncUi(rt);
|
|
7710
|
+
syncUi(rt, active);
|
|
8087
7711
|
}
|
|
8088
7712
|
await runEffects(rt, effects);
|
|
8089
7713
|
});
|
|
8090
|
-
rt.queue = run.catch((error) =>
|
|
8091
|
-
notifyEffectFailure(rt, error);
|
|
8092
|
-
});
|
|
7714
|
+
rt.queue = run.catch((error) => notifyEffectFailure(rt, error));
|
|
8093
7715
|
return rt.queue;
|
|
8094
7716
|
}
|
|
7717
|
+
function notify(active, message, level) {
|
|
7718
|
+
if (active.ctx.hasUI)
|
|
7719
|
+
active.ctx.ui.notify(message, level);
|
|
7720
|
+
}
|
|
8095
7721
|
function notifyEffectFailure(rt, error) {
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
return;
|
|
8099
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
8100
|
-
active.ctx.ui.notify(msg6.notify.stepFailed(message), "warning");
|
|
7722
|
+
if (rt.controller)
|
|
7723
|
+
notify(rt.controller, msg6.notify.stepFailed(errorText2(error)), "warning");
|
|
8101
7724
|
}
|
|
8102
7725
|
function loadReviewConfig() {
|
|
8103
7726
|
return loadConfig().review;
|
|
@@ -8113,31 +7736,30 @@ function limitsOf(config) {
|
|
|
8113
7736
|
}))
|
|
8114
7737
|
};
|
|
8115
7738
|
}
|
|
7739
|
+
function newController(ctx, config, state, stamp) {
|
|
7740
|
+
return {
|
|
7741
|
+
ctx,
|
|
7742
|
+
config,
|
|
7743
|
+
state,
|
|
7744
|
+
signal: new AbortController,
|
|
7745
|
+
watchdog: undefined,
|
|
7746
|
+
persistedStamp: stamp,
|
|
7747
|
+
pendingCards: [],
|
|
7748
|
+
ui: new ReviewUi
|
|
7749
|
+
};
|
|
7750
|
+
}
|
|
8116
7751
|
async function handleCommand(rt, args, ctx) {
|
|
8117
7752
|
const loaded = loadReviewConfig();
|
|
8118
7753
|
if ("error" in loaded)
|
|
8119
7754
|
return refuse(rt, ctx, loaded.error, "error");
|
|
8120
|
-
const config = loaded.config;
|
|
8121
7755
|
if (rt.controller && isActive(rt.controller.state))
|
|
8122
7756
|
return refuse(rt, ctx, msg6.command.alreadyRunning, "info");
|
|
8123
|
-
const
|
|
8124
|
-
if ("
|
|
8125
|
-
return refuse(rt, ctx, command.
|
|
8126
|
-
rt.controller =
|
|
8127
|
-
ctx,
|
|
8128
|
-
config,
|
|
8129
|
-
state: initialState(randomUUID()),
|
|
8130
|
-
signal: new AbortController,
|
|
8131
|
-
watchdog: undefined,
|
|
8132
|
-
persistedStamp: null,
|
|
8133
|
-
pendingCards: []
|
|
8134
|
-
};
|
|
7757
|
+
const input = args.trim();
|
|
7758
|
+
if (input.startsWith("--"))
|
|
7759
|
+
return refuse(rt, ctx, msg6.command.invalidArgs, "error");
|
|
7760
|
+
rt.controller = newController(ctx, loaded.config, initialState(randomUUID()), null);
|
|
8135
7761
|
armWatchdog(rt);
|
|
8136
|
-
dispatch(rt, {
|
|
8137
|
-
type: "START",
|
|
8138
|
-
focus: command.focus,
|
|
8139
|
-
busy: true
|
|
8140
|
-
});
|
|
7762
|
+
dispatch(rt, { type: "START", focus: input });
|
|
8141
7763
|
}
|
|
8142
7764
|
function refuse(rt, ctx, message, level) {
|
|
8143
7765
|
recordRefusal(rt.pi, message);
|
|
@@ -8149,35 +7771,27 @@ function handleSessionStart(rt, ctx) {
|
|
|
8149
7771
|
if (!checkpoint || !isActive(checkpoint))
|
|
8150
7772
|
return;
|
|
8151
7773
|
const loaded = loadReviewConfig();
|
|
8152
|
-
if ("error" in loaded)
|
|
7774
|
+
if ("error" in loaded)
|
|
8153
7775
|
return;
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
rt.controller = {
|
|
8157
|
-
ctx,
|
|
8158
|
-
config,
|
|
8159
|
-
state: checkpoint,
|
|
8160
|
-
signal: new AbortController,
|
|
8161
|
-
watchdog: undefined,
|
|
8162
|
-
persistedStamp: readStamp(ctx),
|
|
8163
|
-
pendingCards: []
|
|
8164
|
-
};
|
|
7776
|
+
const active = newController(ctx, loaded.config, checkpoint, readStamp(ctx));
|
|
7777
|
+
rt.controller = active;
|
|
8165
7778
|
armWatchdog(rt);
|
|
8166
|
-
syncOccupancy(rt,
|
|
8167
|
-
syncUi(rt);
|
|
7779
|
+
syncOccupancy(rt, active);
|
|
7780
|
+
syncUi(rt, active);
|
|
8168
7781
|
return dispatch(rt, { type: "RECOVER" });
|
|
8169
7782
|
}
|
|
8170
7783
|
async function handleAgentStart(rt) {
|
|
8171
7784
|
const active = rt.controller;
|
|
8172
7785
|
if (!active)
|
|
8173
7786
|
return;
|
|
8174
|
-
|
|
8175
|
-
|
|
7787
|
+
const { state } = active;
|
|
7788
|
+
if (state.phase === "awaiting_fix" && state.repair?.status === "awaiting_start") {
|
|
7789
|
+
clearStartTimer(active);
|
|
8176
7790
|
await dispatch(rt, { type: "REPAIR_STARTED" });
|
|
8177
7791
|
return;
|
|
8178
7792
|
}
|
|
8179
|
-
if (
|
|
8180
|
-
|
|
7793
|
+
if (state.phase === "summarizing" && state.summary?.status === "awaiting_start") {
|
|
7794
|
+
clearStartTimer(active);
|
|
8181
7795
|
await dispatch(rt, { type: "SUMMARY_STARTED" });
|
|
8182
7796
|
return;
|
|
8183
7797
|
}
|
|
@@ -8198,8 +7812,8 @@ function handleAgentEnd(rt, event) {
|
|
|
8198
7812
|
return dispatch(rt, { type: "SUMMARY_SETTLED" });
|
|
8199
7813
|
if (active.state.phase !== "awaiting_fix" || active.state.repair?.status !== "running")
|
|
8200
7814
|
return;
|
|
8201
|
-
const assistant = [...event.messages].reverse().find((message) =>
|
|
8202
|
-
if (
|
|
7815
|
+
const assistant = [...event.messages].reverse().find((message) => message.role === "assistant");
|
|
7816
|
+
if (assistant && assistant.stopReason !== "error" && assistant.stopReason !== "aborted")
|
|
8203
7817
|
return dispatch(rt, { type: "REPAIR_COMPLETED" });
|
|
8204
7818
|
active.signal.abort();
|
|
8205
7819
|
return dispatch(rt, { type: "CANCEL", reason: "user" });
|
|
@@ -8208,8 +7822,7 @@ function requestAdvance(rt, ctx) {
|
|
|
8208
7822
|
const active = rt.controller;
|
|
8209
7823
|
if (!active)
|
|
8210
7824
|
return;
|
|
8211
|
-
|
|
8212
|
-
advanceWhenIdle(rt, ctx, runId).catch((error) => {
|
|
7825
|
+
advanceWhenIdle(rt, ctx, active.state.runId).catch((error) => {
|
|
8213
7826
|
notifyEffectFailure(rt, error);
|
|
8214
7827
|
if (rt.controller !== active)
|
|
8215
7828
|
return;
|
|
@@ -8224,51 +7837,41 @@ async function advanceWhenIdle(rt, ctx, runId) {
|
|
|
8224
7837
|
active.ctx = ctx;
|
|
8225
7838
|
flushPendingCards(rt);
|
|
8226
7839
|
const { state } = active;
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
7840
|
+
switch (state.phase) {
|
|
7841
|
+
case "queued":
|
|
7842
|
+
return dispatch(rt, { type: "ADVANCE" });
|
|
7843
|
+
case "reviewing":
|
|
7844
|
+
return startAction(rt, active, `review:${state.round}`, "reviewer", (signal) => startReviewers(rt, active, signal));
|
|
7845
|
+
case "needs_fix":
|
|
7846
|
+
return startAction(rt, active, `advisor:${state.round}`, "advisor", (signal) => consultAdvisor(rt, active, signal));
|
|
7847
|
+
case "summarizing":
|
|
7848
|
+
if (state.summary?.status !== "pending")
|
|
7849
|
+
return;
|
|
7850
|
+
await dispatch(rt, { type: "SUMMARY_DISPATCHED" });
|
|
7851
|
+
if (rt.controller === active && active.state.phase === "summarizing" && active.state.summary?.status === "awaiting_start")
|
|
7852
|
+
deliverSummary(rt, active, active.state);
|
|
7853
|
+
return;
|
|
7854
|
+
case "awaiting_fix":
|
|
7855
|
+
if (state.repair?.status === "completed")
|
|
7856
|
+
return dispatch(rt, { type: "ADVANCE" });
|
|
7857
|
+
if (state.repair?.status !== "pending")
|
|
7858
|
+
return;
|
|
7859
|
+
await dispatch(rt, { type: "FEEDBACK_DISPATCHED" });
|
|
7860
|
+
if (rt.controller === active && active.state.repair?.status === "awaiting_start")
|
|
7861
|
+
deliverFeedback(rt, active, active.state.repair);
|
|
8241
7862
|
return;
|
|
8242
|
-
await dispatch(rt, { type: "SUMMARY_DISPATCHED" });
|
|
8243
|
-
const current = rt.controller?.state;
|
|
8244
|
-
if (rt.controller === active && current?.phase === "summarizing" && current.summary?.status === "awaiting_start")
|
|
8245
|
-
deliverSummaryNow(rt, current);
|
|
8246
|
-
return;
|
|
8247
|
-
}
|
|
8248
|
-
if (state.phase !== "awaiting_fix" || !state.repair)
|
|
8249
|
-
return;
|
|
8250
|
-
if (state.repair.status === "pending") {
|
|
8251
|
-
await dispatch(rt, { type: "FEEDBACK_DISPATCHED" });
|
|
8252
|
-
const repair = rt.controller?.state.repair;
|
|
8253
|
-
if (rt.controller === active && repair?.status === "awaiting_start")
|
|
8254
|
-
deliverFeedbackNow(rt, repair.details, repair.advisor);
|
|
8255
|
-
return;
|
|
8256
7863
|
}
|
|
8257
|
-
if (state.repair.status === "completed")
|
|
8258
|
-
await dispatch(rt, { type: "ADVANCE" });
|
|
8259
7864
|
}
|
|
8260
7865
|
function startAction(rt, active, key, kind, run) {
|
|
8261
7866
|
if (active.runningAction === key)
|
|
8262
7867
|
return;
|
|
8263
7868
|
active.runningAction = key;
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
7869
|
+
const controller = new AbortController;
|
|
7870
|
+
active.actionController = controller;
|
|
7871
|
+
const action = run(controller.signal).catch(async (error) => {
|
|
7872
|
+
if (rt.controller !== active || controller.signal.aborted)
|
|
8267
7873
|
return;
|
|
8268
|
-
await dispatch(rt, {
|
|
8269
|
-
type: "INFRASTRUCTURE_ERROR",
|
|
8270
|
-
details: sessionErrorText(kind, error)
|
|
8271
|
-
});
|
|
7874
|
+
await dispatch(rt, { type: "INFRASTRUCTURE_ERROR", details: sessionErrorText(kind, error) });
|
|
8272
7875
|
}).finally(() => {
|
|
8273
7876
|
if (active.actionPromise !== action)
|
|
8274
7877
|
return;
|
|
@@ -8285,15 +7888,14 @@ async function handleShutdown(rt, reason, ctx) {
|
|
|
8285
7888
|
setOccupancy(rt, active, false);
|
|
8286
7889
|
active.signal.abort();
|
|
8287
7890
|
active.actionController?.abort();
|
|
8288
|
-
clearWatchdog(
|
|
8289
|
-
|
|
7891
|
+
clearWatchdog(active);
|
|
7892
|
+
clearStartTimer(active);
|
|
8290
7893
|
await active.actionPromise;
|
|
8291
|
-
|
|
8292
|
-
active.ctx = ctx;
|
|
7894
|
+
active.ctx = ctx;
|
|
8293
7895
|
if (reason === "quit")
|
|
8294
7896
|
await dispatch(rt, { type: "CANCEL", reason: "shutdown" });
|
|
8295
7897
|
else {
|
|
8296
|
-
|
|
7898
|
+
active.ui.clear(active.ctx);
|
|
8297
7899
|
await rt.queue;
|
|
8298
7900
|
}
|
|
8299
7901
|
if (rt.controller === active)
|
|
@@ -8303,7 +7905,7 @@ function armWatchdog(rt) {
|
|
|
8303
7905
|
const active = rt.controller;
|
|
8304
7906
|
if (!active)
|
|
8305
7907
|
return;
|
|
8306
|
-
clearWatchdog(
|
|
7908
|
+
clearWatchdog(active);
|
|
8307
7909
|
const elapsed = active.state.startedAt ? Math.max(0, Date.now() - active.state.startedAt) : 0;
|
|
8308
7910
|
const remaining = Math.max(0, overallTimeoutMs(active.config) - elapsed);
|
|
8309
7911
|
if (remaining === 0) {
|
|
@@ -8320,14 +7922,14 @@ function armWatchdog(rt) {
|
|
|
8320
7922
|
}, remaining);
|
|
8321
7923
|
active.watchdog.unref?.();
|
|
8322
7924
|
}
|
|
8323
|
-
function clearWatchdog(
|
|
8324
|
-
if (
|
|
8325
|
-
clearTimeout(
|
|
7925
|
+
function clearWatchdog(active) {
|
|
7926
|
+
if (active.watchdog)
|
|
7927
|
+
clearTimeout(active.watchdog);
|
|
8326
7928
|
}
|
|
8327
|
-
function
|
|
8328
|
-
if (active.
|
|
8329
|
-
clearTimeout(active.
|
|
8330
|
-
active.
|
|
7929
|
+
function clearStartTimer(active) {
|
|
7930
|
+
if (active.startTimer)
|
|
7931
|
+
clearTimeout(active.startTimer);
|
|
7932
|
+
active.startTimer = undefined;
|
|
8331
7933
|
}
|
|
8332
7934
|
function persist(rt, state) {
|
|
8333
7935
|
const active = rt.controller;
|
|
@@ -8337,45 +7939,37 @@ function persist(rt, state) {
|
|
|
8337
7939
|
if (persisted === undefined)
|
|
8338
7940
|
return false;
|
|
8339
7941
|
try {
|
|
8340
|
-
active.persistedStamp = persisted === null ? beginCheckpoint(rt.pi,
|
|
7942
|
+
active.persistedStamp = persisted === null ? beginCheckpoint(rt.pi, state) : writeCheckpoint(rt.pi, active.ctx, state, persisted);
|
|
8341
7943
|
return true;
|
|
8342
7944
|
} catch (error) {
|
|
8343
|
-
|
|
8344
|
-
setOccupancy(rt, active, false);
|
|
8345
|
-
active.persistedStamp = undefined;
|
|
8346
|
-
active.signal.abort();
|
|
8347
|
-
active.actionController?.abort();
|
|
8348
|
-
if (active.ctx.hasUI)
|
|
8349
|
-
active.ctx.ui.notify(message, level);
|
|
8350
|
-
};
|
|
8351
|
-
if (error instanceof CheckpointConflictError) {
|
|
8352
|
-
halt(msg6.notify.checkpointConflict, "warning");
|
|
8353
|
-
dispatch(rt, { type: "CANCEL", reason: "shutdown" });
|
|
8354
|
-
return false;
|
|
8355
|
-
}
|
|
8356
|
-
halt(msg6.notify.checkpointWriteFailed(errorText2(error)), "error");
|
|
8357
|
-
clearUi(active);
|
|
8358
|
-
let sealed = true;
|
|
8359
|
-
try {
|
|
8360
|
-
beginCheckpoint(rt.pi, active.ctx, {
|
|
8361
|
-
...state,
|
|
8362
|
-
phase: "settled",
|
|
8363
|
-
active: null,
|
|
8364
|
-
pending: null,
|
|
8365
|
-
repair: null,
|
|
8366
|
-
summary: null
|
|
8367
|
-
});
|
|
8368
|
-
} catch {
|
|
8369
|
-
sealed = false;
|
|
8370
|
-
}
|
|
8371
|
-
clearWatchdog(rt);
|
|
8372
|
-
clearFeedbackStartTimer(active);
|
|
8373
|
-
rt.controller = undefined;
|
|
8374
|
-
if (!sealed && active.ctx.hasUI)
|
|
8375
|
-
active.ctx.ui.notify(msg6.notify.cannotSeal, "warning");
|
|
7945
|
+
haltOnPersistFailure(rt, active, state, error);
|
|
8376
7946
|
return false;
|
|
8377
7947
|
}
|
|
8378
7948
|
}
|
|
7949
|
+
function haltOnPersistFailure(rt, active, state, error) {
|
|
7950
|
+
setOccupancy(rt, active, false);
|
|
7951
|
+
active.persistedStamp = undefined;
|
|
7952
|
+
active.signal.abort();
|
|
7953
|
+
active.actionController?.abort();
|
|
7954
|
+
if (error instanceof CheckpointConflictError) {
|
|
7955
|
+
notify(active, msg6.notify.checkpointConflict, "warning");
|
|
7956
|
+
dispatch(rt, { type: "CANCEL", reason: "shutdown" });
|
|
7957
|
+
return;
|
|
7958
|
+
}
|
|
7959
|
+
notify(active, msg6.notify.checkpointWriteFailed(errorText2(error)), "error");
|
|
7960
|
+
active.ui.clear(active.ctx);
|
|
7961
|
+
let sealed = true;
|
|
7962
|
+
try {
|
|
7963
|
+
sealCheckpoint(rt.pi, state);
|
|
7964
|
+
} catch {
|
|
7965
|
+
sealed = false;
|
|
7966
|
+
}
|
|
7967
|
+
clearWatchdog(active);
|
|
7968
|
+
clearStartTimer(active);
|
|
7969
|
+
rt.controller = undefined;
|
|
7970
|
+
if (!sealed)
|
|
7971
|
+
notify(active, msg6.notify.cannotSeal, "warning");
|
|
7972
|
+
}
|
|
8379
7973
|
function errorText2(error) {
|
|
8380
7974
|
return error instanceof Error ? error.message : String(error);
|
|
8381
7975
|
}
|
|
@@ -8391,29 +7985,16 @@ function setOccupancy(rt, active, held) {
|
|
|
8391
7985
|
rt.pi.events.emit(OCCUPANCY_CHANNEL, payload);
|
|
8392
7986
|
} catch (error) {
|
|
8393
7987
|
try {
|
|
8394
|
-
|
|
8395
|
-
active.ctx.ui.notify(msg6.notify.occupancyFailed(errorText2(error)), "warning");
|
|
7988
|
+
notify(active, msg6.notify.occupancyFailed(errorText2(error)), "warning");
|
|
8396
7989
|
} catch {}
|
|
8397
7990
|
}
|
|
8398
7991
|
}
|
|
8399
|
-
function syncUi(rt) {
|
|
8400
|
-
const active = rt.controller;
|
|
8401
|
-
if (!active)
|
|
8402
|
-
return;
|
|
7992
|
+
function syncUi(rt, active) {
|
|
8403
7993
|
if (!isActive(active.state) || !active.ctx.hasUI)
|
|
8404
|
-
return
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
active.unlockEditor ??= lockEditor(active.ctx, () => cancelByUser(rt));
|
|
8409
|
-
}
|
|
8410
|
-
function clearUi(active) {
|
|
8411
|
-
hideReviewTitle(active.ctx);
|
|
8412
|
-
releaseEditor(active);
|
|
8413
|
-
}
|
|
8414
|
-
function releaseEditor(active) {
|
|
8415
|
-
active.unlockEditor?.();
|
|
8416
|
-
active.unlockEditor = undefined;
|
|
7994
|
+
return active.ui.clear(active.ctx);
|
|
7995
|
+
const { phase, round } = active.state;
|
|
7996
|
+
const canCancel = phase === "queued" || phase === "reviewing" || phase === "needs_fix";
|
|
7997
|
+
active.ui.show(active.ctx, round, canCancel, () => cancelByUser(rt));
|
|
8417
7998
|
}
|
|
8418
7999
|
var STAGE = {
|
|
8419
8000
|
queued: "queued",
|
|
@@ -8424,17 +8005,13 @@ var STAGE = {
|
|
|
8424
8005
|
};
|
|
8425
8006
|
function reviewProgress(rt) {
|
|
8426
8007
|
const state = rt.controller?.state;
|
|
8427
|
-
const stage = state &&
|
|
8008
|
+
const stage = state && STAGE[state.phase];
|
|
8428
8009
|
if (!state || !stage)
|
|
8429
8010
|
return;
|
|
8430
8011
|
const reviewers = stage === "reviewing" ? state.active?.reviewers ?? [] : [];
|
|
8431
8012
|
const count = (status) => reviewers.filter((reviewer) => reviewer.status === status).length;
|
|
8432
8013
|
return { stage, round: state.round, passed: count("passed"), blocked: count("failed"), total: reviewers.length };
|
|
8433
8014
|
}
|
|
8434
|
-
function canCancelWithKey(rt) {
|
|
8435
|
-
const phase = rt.controller?.state.phase;
|
|
8436
|
-
return phase === "queued" || phase === "reviewing" || phase === "needs_fix";
|
|
8437
|
-
}
|
|
8438
8015
|
function cancelByUser(rt) {
|
|
8439
8016
|
const active = rt.controller;
|
|
8440
8017
|
if (!active)
|
|
@@ -8453,35 +8030,31 @@ async function runEffects(rt, effects) {
|
|
|
8453
8030
|
const active = rt.controller;
|
|
8454
8031
|
if (!active)
|
|
8455
8032
|
return;
|
|
8456
|
-
if (effect.kind === "advance")
|
|
8033
|
+
if (effect.kind === "advance")
|
|
8457
8034
|
requestAdvance(rt, active.ctx);
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8035
|
+
else if (effect.kind === "notify_cancelled")
|
|
8036
|
+
notify(active, msg6.notify.cancelled, "info");
|
|
8037
|
+
else
|
|
8038
|
+
try {
|
|
8039
|
+
sendCard(rt, active, effect.card);
|
|
8040
|
+
} catch (error) {
|
|
8041
|
+
notifyEffectFailure(rt, error);
|
|
8042
|
+
}
|
|
8465
8043
|
}
|
|
8466
8044
|
}
|
|
8467
|
-
function
|
|
8045
|
+
function modelConfig(atom, config) {
|
|
8468
8046
|
return {
|
|
8469
|
-
model:
|
|
8470
|
-
thinking:
|
|
8047
|
+
model: atom.model,
|
|
8048
|
+
thinking: atom.thinking,
|
|
8471
8049
|
tools: config.tools,
|
|
8472
8050
|
timeoutMs: config.timeoutMinutes * 60000
|
|
8473
8051
|
};
|
|
8474
8052
|
}
|
|
8475
|
-
async function startReviewers(rt) {
|
|
8476
|
-
const active = rt.controller;
|
|
8477
|
-
if (!active)
|
|
8478
|
-
return;
|
|
8053
|
+
async function startReviewers(rt, active, signal) {
|
|
8479
8054
|
const { state, config } = active;
|
|
8480
8055
|
if (!state.active)
|
|
8481
8056
|
return;
|
|
8482
|
-
const
|
|
8483
|
-
const actionSignal = active.actionController?.signal ?? active.signal.signal;
|
|
8484
|
-
const evidence = buildEvidence(sessionEntries(rt), { sessionFile: active.ctx.sessionManager.getSessionFile() });
|
|
8057
|
+
const evidence = buildEvidence(sessionEntries(active), { sessionFile: active.ctx.sessionManager.getSessionFile() });
|
|
8485
8058
|
const prompt = buildReviewPrompt(readPrompt(PROMPTS, "review"), {
|
|
8486
8059
|
scope: msg6.command.scope,
|
|
8487
8060
|
focus: state.focus,
|
|
@@ -8489,20 +8062,20 @@ async function startReviewers(rt) {
|
|
|
8489
8062
|
history: state.history,
|
|
8490
8063
|
round: state.round
|
|
8491
8064
|
});
|
|
8492
|
-
const tasks =
|
|
8065
|
+
const tasks = state.active.reviewers.filter((reviewer) => reviewer.status === "running").map(async (reviewer) => {
|
|
8493
8066
|
try {
|
|
8494
8067
|
const result = await runReviewer({
|
|
8495
8068
|
index: reviewer.index,
|
|
8496
|
-
config:
|
|
8069
|
+
config: modelConfig(reviewer, config),
|
|
8497
8070
|
prompt,
|
|
8498
8071
|
cwd: active.ctx.cwd,
|
|
8499
|
-
signal
|
|
8072
|
+
signal,
|
|
8500
8073
|
runSession: rt.runSession
|
|
8501
8074
|
});
|
|
8502
|
-
if (!
|
|
8075
|
+
if (!signal.aborted)
|
|
8503
8076
|
await dispatch(rt, { type: "REVIEWER_SETTLED", index: result.index, result });
|
|
8504
8077
|
} catch (error) {
|
|
8505
|
-
if (
|
|
8078
|
+
if (signal.aborted)
|
|
8506
8079
|
return;
|
|
8507
8080
|
await dispatch(rt, {
|
|
8508
8081
|
type: "REVIEWER_SETTLED",
|
|
@@ -8520,128 +8093,103 @@ async function startReviewers(rt) {
|
|
|
8520
8093
|
});
|
|
8521
8094
|
await Promise.all(tasks);
|
|
8522
8095
|
}
|
|
8523
|
-
async function consultAdvisor(rt) {
|
|
8524
|
-
const active = rt.controller;
|
|
8525
|
-
if (!active)
|
|
8526
|
-
return;
|
|
8096
|
+
async function consultAdvisor(rt, active, signal) {
|
|
8527
8097
|
const { state, config } = active;
|
|
8528
8098
|
if (!state.pending)
|
|
8529
8099
|
return;
|
|
8530
|
-
const pending = state.pending;
|
|
8531
|
-
const actionSignal = active.actionController?.signal ?? active.signal.signal;
|
|
8532
8100
|
const prompt = buildAdvisorPrompt(readPrompt(PROMPTS, "advisor"), {
|
|
8533
8101
|
focus: state.focus,
|
|
8534
|
-
details: pending.details,
|
|
8102
|
+
details: state.pending.details,
|
|
8535
8103
|
history: state.history,
|
|
8536
|
-
round: pending.round
|
|
8104
|
+
round: state.pending.round
|
|
8537
8105
|
});
|
|
8538
8106
|
try {
|
|
8539
8107
|
const result = await runAdvisor({
|
|
8540
|
-
config:
|
|
8108
|
+
config: modelConfig(config.advisor, config),
|
|
8541
8109
|
prompt,
|
|
8542
8110
|
cwd: active.ctx.cwd,
|
|
8543
|
-
signal
|
|
8111
|
+
signal,
|
|
8544
8112
|
runSession: rt.runSession
|
|
8545
8113
|
});
|
|
8546
|
-
if (!
|
|
8114
|
+
if (!signal.aborted)
|
|
8547
8115
|
await dispatch(rt, { type: "ADVISOR_SETTLED", result });
|
|
8548
8116
|
} catch (error) {
|
|
8549
|
-
if (
|
|
8117
|
+
if (signal.aborted)
|
|
8550
8118
|
return;
|
|
8551
|
-
await dispatch(rt, {
|
|
8552
|
-
type: "INFRASTRUCTURE_ERROR",
|
|
8553
|
-
details: sessionErrorText("advisor", error)
|
|
8554
|
-
});
|
|
8119
|
+
await dispatch(rt, { type: "INFRASTRUCTURE_ERROR", details: sessionErrorText("advisor", error) });
|
|
8555
8120
|
}
|
|
8556
8121
|
}
|
|
8557
8122
|
function sessionErrorText(kind, error) {
|
|
8558
8123
|
const reason = errorText2(error);
|
|
8559
8124
|
return kind === "reviewer" ? msg6.failure.reviewerSession(reason) : msg6.failure.advisorSession(reason);
|
|
8560
8125
|
}
|
|
8561
|
-
function
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
const feedback = buildFixFeedback({
|
|
8566
|
-
details,
|
|
8567
|
-
advisor
|
|
8568
|
-
});
|
|
8569
|
-
clearFeedbackStartTimer(active);
|
|
8570
|
-
active.feedbackStartTimer = setTimeout(() => {
|
|
8571
|
-
if (rt.controller !== active || active.state.phase !== "awaiting_fix" || active.state.repair?.status !== "awaiting_start")
|
|
8126
|
+
function sendFollowUp(rt, active, customType, content, handlers) {
|
|
8127
|
+
clearStartTimer(active);
|
|
8128
|
+
active.startTimer = setTimeout(() => {
|
|
8129
|
+
if (rt.controller !== active || !handlers.stillAwaiting(active.state))
|
|
8572
8130
|
return;
|
|
8573
|
-
active.
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
dispatch(rt, { type: "CANCEL", reason: "user" });
|
|
8578
|
-
}, FEEDBACK_START_TIMEOUT_MS);
|
|
8579
|
-
active.feedbackStartTimer.unref?.();
|
|
8131
|
+
active.startTimer = undefined;
|
|
8132
|
+
handlers.onNotStarted();
|
|
8133
|
+
}, FOLLOW_UP_START_TIMEOUT_MS);
|
|
8134
|
+
active.startTimer.unref?.();
|
|
8580
8135
|
try {
|
|
8581
|
-
rt.pi.sendMessage({ customType
|
|
8136
|
+
rt.pi.sendMessage({ customType, content, display: false }, { deliverAs: "followUp", triggerTurn: true });
|
|
8582
8137
|
} catch (error) {
|
|
8583
|
-
|
|
8584
|
-
|
|
8138
|
+
clearStartTimer(active);
|
|
8139
|
+
handlers.onSendError(error);
|
|
8585
8140
|
}
|
|
8586
8141
|
}
|
|
8587
|
-
function
|
|
8588
|
-
|
|
8589
|
-
|
|
8142
|
+
function deliverFeedback(rt, active, repair) {
|
|
8143
|
+
sendFollowUp(rt, active, FEEDBACK_TYPE, buildFixFeedback(repair), {
|
|
8144
|
+
stillAwaiting: (state) => state.phase === "awaiting_fix" && state.repair?.status === "awaiting_start",
|
|
8145
|
+
onNotStarted: () => {
|
|
8146
|
+
notify(active, msg6.notify.feedbackNotStarted, "error");
|
|
8147
|
+
active.signal.abort();
|
|
8148
|
+
dispatch(rt, { type: "CANCEL", reason: "user" });
|
|
8149
|
+
},
|
|
8150
|
+
onSendError: (error) => {
|
|
8151
|
+
throw error;
|
|
8152
|
+
}
|
|
8153
|
+
});
|
|
8154
|
+
}
|
|
8155
|
+
function deliverSummary(rt, active, state) {
|
|
8156
|
+
if (!state.summary)
|
|
8590
8157
|
return;
|
|
8591
8158
|
const last = state.history.at(-1);
|
|
8592
8159
|
const material = state.summary.kind === "advisor_stop" ? last?.advisor?.advice ?? last?.details ?? "" : last?.details ?? "";
|
|
8593
|
-
const prompt = buildSummaryPrompt({
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8160
|
+
const prompt = buildSummaryPrompt({ kind: state.summary.kind, rounds: state.history.length, material });
|
|
8161
|
+
sendFollowUp(rt, active, SUMMARY_REQUEST_TYPE, prompt, {
|
|
8162
|
+
stillAwaiting: (current) => current.phase === "summarizing" && current.summary?.status === "awaiting_start",
|
|
8163
|
+
onNotStarted: () => {
|
|
8164
|
+
notify(active, msg6.notify.summaryNotStarted, "warning");
|
|
8165
|
+
dispatch(rt, { type: "SUMMARY_SETTLED" });
|
|
8166
|
+
},
|
|
8167
|
+
onSendError: (error) => {
|
|
8168
|
+
notifyEffectFailure(rt, error);
|
|
8169
|
+
dispatch(rt, { type: "SUMMARY_SETTLED" });
|
|
8170
|
+
}
|
|
8597
8171
|
});
|
|
8598
|
-
clearFeedbackStartTimer(active);
|
|
8599
|
-
active.feedbackStartTimer = setTimeout(() => {
|
|
8600
|
-
if (rt.controller !== active || active.state.phase !== "summarizing" || active.state.summary?.status !== "awaiting_start")
|
|
8601
|
-
return;
|
|
8602
|
-
active.feedbackStartTimer = undefined;
|
|
8603
|
-
if (active.ctx.hasUI)
|
|
8604
|
-
active.ctx.ui.notify(msg6.notify.summaryNotStarted, "warning");
|
|
8605
|
-
dispatch(rt, { type: "SUMMARY_SETTLED" });
|
|
8606
|
-
}, FEEDBACK_START_TIMEOUT_MS);
|
|
8607
|
-
active.feedbackStartTimer.unref?.();
|
|
8608
|
-
try {
|
|
8609
|
-
rt.pi.sendMessage({ customType: SUMMARY_REQUEST_TYPE, content: prompt, display: false }, { deliverAs: "followUp", triggerTurn: true });
|
|
8610
|
-
} catch (error) {
|
|
8611
|
-
clearFeedbackStartTimer(active);
|
|
8612
|
-
notifyEffectFailure(rt, error);
|
|
8613
|
-
dispatch(rt, { type: "SUMMARY_SETTLED" });
|
|
8614
|
-
}
|
|
8615
8172
|
}
|
|
8616
|
-
function sendCard(rt, card) {
|
|
8617
|
-
const active = rt.controller;
|
|
8618
|
-
if (!active)
|
|
8619
|
-
return;
|
|
8620
|
-
if (card.kind === "cancel") {
|
|
8621
|
-
if (card.reason === "user" && active.ctx.hasUI)
|
|
8622
|
-
active.ctx.ui.notify(msg6.notify.cancelled, "info");
|
|
8623
|
-
return;
|
|
8624
|
-
}
|
|
8173
|
+
function sendCard(rt, active, card) {
|
|
8625
8174
|
if (!active.ctx.isIdle()) {
|
|
8626
8175
|
active.pendingCards.push(card);
|
|
8627
8176
|
return;
|
|
8628
8177
|
}
|
|
8629
|
-
sendCardNow(rt,
|
|
8178
|
+
sendCardNow(rt, card);
|
|
8630
8179
|
}
|
|
8631
8180
|
function flushPendingCards(rt) {
|
|
8632
8181
|
const active = rt.controller;
|
|
8633
8182
|
if (!active || !active.ctx.isIdle() || active.pendingCards.length === 0)
|
|
8634
8183
|
return;
|
|
8635
|
-
const
|
|
8636
|
-
for (const card of cards) {
|
|
8184
|
+
for (const card of active.pendingCards.splice(0)) {
|
|
8637
8185
|
try {
|
|
8638
|
-
sendCardNow(rt,
|
|
8186
|
+
sendCardNow(rt, card);
|
|
8639
8187
|
} catch (error) {
|
|
8640
8188
|
notifyEffectFailure(rt, error);
|
|
8641
8189
|
}
|
|
8642
8190
|
}
|
|
8643
8191
|
}
|
|
8644
|
-
function sendCardNow(rt,
|
|
8192
|
+
function sendCardNow(rt, card) {
|
|
8645
8193
|
const built = buildCard(card);
|
|
8646
8194
|
rt.pi.sendMessage({
|
|
8647
8195
|
customType: CARD_TYPE,
|
|
@@ -8650,22 +8198,13 @@ function sendCardNow(rt, active, card) {
|
|
|
8650
8198
|
details: built.details
|
|
8651
8199
|
});
|
|
8652
8200
|
}
|
|
8653
|
-
function
|
|
8654
|
-
const
|
|
8655
|
-
return
|
|
8656
|
-
}
|
|
8657
|
-
function sessionEntries(rt) {
|
|
8658
|
-
const manager = rt.controller?.ctx.sessionManager;
|
|
8659
|
-
const entries = manager?.getBranch?.() ?? [];
|
|
8660
|
-
return entries.filter((entry) => !isRecord9(entry) || entry.type !== "custom_message" || entry.customType !== CARD_TYPE && entry.customType !== FEEDBACK_TYPE && entry.customType !== SUMMARY_REQUEST_TYPE);
|
|
8661
|
-
}
|
|
8662
|
-
function isRecord9(value) {
|
|
8663
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8201
|
+
function sessionEntries(active) {
|
|
8202
|
+
const own = new Set([CARD_TYPE, FEEDBACK_TYPE, SUMMARY_REQUEST_TYPE]);
|
|
8203
|
+
return active.ctx.sessionManager.getBranch().filter((entry) => entry.type !== "custom_message" || !own.has(entry.customType));
|
|
8664
8204
|
}
|
|
8665
8205
|
|
|
8666
8206
|
// master/index.ts
|
|
8667
8207
|
import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
8668
|
-
import { getAgentDir as getAgentDir5 } from "@earendil-works/pi-coding-agent";
|
|
8669
8208
|
|
|
8670
8209
|
// master/actions.ts
|
|
8671
8210
|
import { existsSync as existsSync3 } from "node:fs";
|
|
@@ -8681,7 +8220,7 @@ function readReviewOutcome(sessionPath) {
|
|
|
8681
8220
|
} catch (error) {
|
|
8682
8221
|
if (isMissingFile(error))
|
|
8683
8222
|
return { status: "none" };
|
|
8684
|
-
return { status: "error", message: msg6.failure.cannotReadSession(
|
|
8223
|
+
return { status: "error", message: msg6.failure.cannotReadSession(error instanceof Error ? error.message : String(error)) };
|
|
8685
8224
|
}
|
|
8686
8225
|
let latest;
|
|
8687
8226
|
let damage;
|
|
@@ -8734,14 +8273,8 @@ function outcomeOf2(latest) {
|
|
|
8734
8273
|
}
|
|
8735
8274
|
return { status: "failed", runId: latest.runId, rounds, reason: last?.details?.trim() || result || "unknown" };
|
|
8736
8275
|
}
|
|
8737
|
-
function isCheckpointEntry(value) {
|
|
8738
|
-
return typeof value === "object" && value !== null && value.type === "custom" && value.customType === CHECKPOINT_TYPE && "data" in value;
|
|
8739
|
-
}
|
|
8740
8276
|
function isMissingFile(error) {
|
|
8741
|
-
return
|
|
8742
|
-
}
|
|
8743
|
-
function errorMessage2(error) {
|
|
8744
|
-
return error instanceof Error ? error.message : String(error);
|
|
8277
|
+
return error?.code === "ENOENT";
|
|
8745
8278
|
}
|
|
8746
8279
|
|
|
8747
8280
|
// master/list-view.ts
|
|
@@ -8790,42 +8323,40 @@ function currentWorkerAction(worker, live) {
|
|
|
8790
8323
|
const current = [...live?.currentTools.values() ?? []].at(-1);
|
|
8791
8324
|
return current ? { kind: "tool", ...current } : undefined;
|
|
8792
8325
|
}
|
|
8326
|
+
function actionParts(action) {
|
|
8327
|
+
if (action?.kind === "tool")
|
|
8328
|
+
return [{ text: ` · ${action.tool} · ${msg7.list.running(formatDuration(Math.max(0, Date.now() - action.startedAt)))}`, color: "accent" }];
|
|
8329
|
+
if (action?.kind === "idle")
|
|
8330
|
+
return [{
|
|
8331
|
+
text: action.since ? ` · ${msg7.list.settledAgo(formatDuration(Date.now() - action.since))}` : ` · ${msg7.list.settled}`,
|
|
8332
|
+
color: "muted"
|
|
8333
|
+
}];
|
|
8334
|
+
if (action?.kind === "review")
|
|
8335
|
+
return [{ text: ` · ${msg7.list.reviewRound(action.round, action.settled, action.total)}`, color: "accent" }];
|
|
8336
|
+
return [];
|
|
8337
|
+
}
|
|
8793
8338
|
function expandedWorkerList(workers, theme, context) {
|
|
8794
8339
|
return {
|
|
8795
8340
|
invalidate() {},
|
|
8796
8341
|
render(width) {
|
|
8797
|
-
return ["", ...workers.flatMap((
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
color: "
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
label: String(worker.name),
|
|
8809
|
-
value: [
|
|
8810
|
-
{ text: roleStatusText(worker), color: "accent" },
|
|
8811
|
-
...actionParts,
|
|
8812
|
-
{ text: ` · ${String(worker.model).split("/").pop()}/${String(worker.thinking)}`, color: "muted" }
|
|
8813
|
-
],
|
|
8814
|
-
clip: "end",
|
|
8815
|
-
theme,
|
|
8816
|
-
ctx: { ...context, state: {}, expanded: false }
|
|
8817
|
-
}).render(width);
|
|
8818
|
-
})];
|
|
8342
|
+
return ["", ...workers.flatMap((worker) => new ToolLine({
|
|
8343
|
+
label: worker.name,
|
|
8344
|
+
value: [
|
|
8345
|
+
{ text: roleStatusText(worker), color: "accent" },
|
|
8346
|
+
...actionParts(worker.currentAction),
|
|
8347
|
+
{ text: ` · ${worker.model.split("/").pop()}/${worker.thinking}`, color: "muted" }
|
|
8348
|
+
],
|
|
8349
|
+
clip: "end",
|
|
8350
|
+
theme,
|
|
8351
|
+
ctx: { ...context, state: {}, expanded: false }
|
|
8352
|
+
}).render(width))];
|
|
8819
8353
|
}
|
|
8820
8354
|
};
|
|
8821
8355
|
}
|
|
8822
8356
|
function listMeta(workers) {
|
|
8823
8357
|
if (!workers.length)
|
|
8824
8358
|
return [{ text: ` — ${msg7.list.poolEmpty}`, color: "muted" }];
|
|
8825
|
-
const summary = workers.map((
|
|
8826
|
-
const worker = value;
|
|
8827
|
-
return `${String(worker.name)} ${roleStatusText(worker)}`;
|
|
8828
|
-
}).join(" · ");
|
|
8359
|
+
const summary = workers.map((worker) => `${worker.name} ${roleStatusText(worker)}`).join(" · ");
|
|
8829
8360
|
return [{ text: ` — ${msg7.list.pool(workers.length, summary)}`, color: "muted" }];
|
|
8830
8361
|
}
|
|
8831
8362
|
function statusText(workers) {
|
|
@@ -8833,8 +8364,7 @@ function statusText(workers) {
|
|
|
8833
8364
|
`) : msg7.list.noWorkers;
|
|
8834
8365
|
}
|
|
8835
8366
|
function roleStatusText(worker) {
|
|
8836
|
-
|
|
8837
|
-
return worker.role ? `${String(worker.role)}·${status}` : status;
|
|
8367
|
+
return `${worker.role}·${STATUS_WORD[worker.status]}`;
|
|
8838
8368
|
}
|
|
8839
8369
|
function optionalText(value) {
|
|
8840
8370
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
@@ -8923,7 +8453,7 @@ async function spawnWorker(active, worker, resume) {
|
|
|
8923
8453
|
thinking: worker.thinking,
|
|
8924
8454
|
tools: active.setup.pi.getActiveTools().includes(CODEMODE_TOOL) ? [...WORKER_TOOLS, CODEMODE_TOOL] : WORKER_TOOLS,
|
|
8925
8455
|
excludeExtensions: exclusions,
|
|
8926
|
-
systemPrompt: { mode: "append", text: assembleWorkerPrompt(active.setup.workerPrompt
|
|
8456
|
+
systemPrompt: { mode: "append", text: assembleWorkerPrompt(active.setup.workerPrompt, worker.name) },
|
|
8927
8457
|
contextFiles: true,
|
|
8928
8458
|
persistence: { type: "file", sessionPath: worker.sessionPath, ...resume ? { resume: true } : {} }
|
|
8929
8459
|
});
|
|
@@ -8989,7 +8519,7 @@ function settleInterrupted(active, identity) {
|
|
|
8989
8519
|
if (!current)
|
|
8990
8520
|
return;
|
|
8991
8521
|
const interrupted = { ...current, status: "idle", interruptedAt: Date.now() };
|
|
8992
|
-
active.store.
|
|
8522
|
+
active.store.upsert(interrupted);
|
|
8993
8523
|
active.markIdle(interrupted, { kind: "interrupted" }, interrupted.interruptedAt);
|
|
8994
8524
|
active.outbox.enqueue(masterEvent.interrupted(identity.name, current.reviewNeeded === true, takeViewPrompts(active, identity)), identity.name);
|
|
8995
8525
|
}
|
|
@@ -9006,7 +8536,7 @@ function armInterruptReminder(active, worker) {
|
|
|
9006
8536
|
const current = active.find(worker);
|
|
9007
8537
|
if (!current?.interruptedAt || current.interruptedAt !== worker.interruptedAt)
|
|
9008
8538
|
return;
|
|
9009
|
-
active.store.
|
|
8539
|
+
active.store.upsert({ ...current, disposition: "reminded" });
|
|
9010
8540
|
active.outbox.enqueue(masterEvent.resumeReminder(worker.name), worker.name);
|
|
9011
8541
|
}, delay);
|
|
9012
8542
|
live.interruptTimer.unref?.();
|
|
@@ -9044,7 +8574,7 @@ function settleWorker(active, identity, terminal, error) {
|
|
|
9044
8574
|
return;
|
|
9045
8575
|
const failure = error instanceof Error ? error.message : error === undefined ? terminalFailure(terminal) : String(error);
|
|
9046
8576
|
const idle = { ...current, status: "idle" };
|
|
9047
|
-
active.store.
|
|
8577
|
+
active.store.upsert(idle);
|
|
9048
8578
|
active.markIdle(idle, failure ? { kind: "failed", note: firstSentence(failure) } : { kind: "done", note: firstSentence(terminal.text) });
|
|
9049
8579
|
const obligation = current.reviewNeeded === true;
|
|
9050
8580
|
const event = failure ? masterEvent.failed(identity.name, failure, obligation, takeViewPrompts(active, identity)) : masterEvent.returned(identity.name, terminal.text, obligation, takeViewPrompts(active, identity));
|
|
@@ -9062,15 +8592,15 @@ function monitorAndSettleReview(active, target, session, previousRunId) {
|
|
|
9062
8592
|
return;
|
|
9063
8593
|
const { reviewNeeded: _needed, ...fulfilled } = current;
|
|
9064
8594
|
const idle = { ...outcome.status === "passed" || outcome.status === "stopped" ? fulfilled : current, status: "idle" };
|
|
9065
|
-
active.store.
|
|
8595
|
+
active.store.upsert(idle);
|
|
9066
8596
|
active.markIdle(idle, reviewOutcomeRow(outcome));
|
|
9067
|
-
active.outbox.enqueue(masterEvent.review(target.name, outcome,
|
|
8597
|
+
active.outbox.enqueue(masterEvent.review(target.name, outcome, textOf(lastAssistant(session.messages)?.content)), target.name);
|
|
9068
8598
|
}, (error) => {
|
|
9069
8599
|
const current = reviewing();
|
|
9070
8600
|
if (!current)
|
|
9071
8601
|
return;
|
|
9072
8602
|
const idle = { ...current, status: "idle" };
|
|
9073
|
-
active.store.
|
|
8603
|
+
active.store.upsert(idle);
|
|
9074
8604
|
active.markIdle(idle, { kind: "failed", note: msg7.run.reviewIncomplete });
|
|
9075
8605
|
active.outbox.enqueue(masterEvent.reviewIncomplete(target.name, String(error)), target.name);
|
|
9076
8606
|
});
|
|
@@ -9127,7 +8657,7 @@ function reviewOutcomeRow(outcome) {
|
|
|
9127
8657
|
return { kind: "failed", note: outcome.status === "stopped" ? msg7.run.reviewStopped : msg7.run.reviewIncomplete };
|
|
9128
8658
|
}
|
|
9129
8659
|
function captureWorkerTerminal(messages) {
|
|
9130
|
-
const message = messages
|
|
8660
|
+
const message = lastAssistant(messages);
|
|
9131
8661
|
if (!message)
|
|
9132
8662
|
return;
|
|
9133
8663
|
return {
|
|
@@ -9154,8 +8684,8 @@ function faultSummary(terminal) {
|
|
|
9154
8684
|
const [first = message] = message.split(/(?<=[.。!!??])\s|\n/u);
|
|
9155
8685
|
return clip(first.trim(), FAULT_SUMMARY_WIDTH);
|
|
9156
8686
|
}
|
|
9157
|
-
function
|
|
9158
|
-
return
|
|
8687
|
+
function lastAssistant(messages) {
|
|
8688
|
+
return messages.findLast((candidate) => candidate.role === "assistant");
|
|
9159
8689
|
}
|
|
9160
8690
|
function nextFallback(role, worker) {
|
|
9161
8691
|
const chain = [role, ...role.fallback];
|
|
@@ -9175,174 +8705,106 @@ function fallbackResumePrompt(from, to, reason) {
|
|
|
9175
8705
|
}
|
|
9176
8706
|
|
|
9177
8707
|
// master/state.ts
|
|
9178
|
-
import { mkdirSync as
|
|
9179
|
-
import { dirname as
|
|
8708
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8709
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
9180
8710
|
var STATE_VERSION = 9;
|
|
8711
|
+
var WORKER_NAME = /^[a-z][a-z0-9_-]{0,31}$/u;
|
|
9181
8712
|
var THINKING_LEVELS2 = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
9182
|
-
function initialMasterState() {
|
|
9183
|
-
return { version: STATE_VERSION, workers: [] };
|
|
9184
|
-
}
|
|
9185
|
-
function reduceMaster(state, event) {
|
|
9186
|
-
switch (event.type) {
|
|
9187
|
-
case "UPSERT_WORKER":
|
|
9188
|
-
return { ...state, workers: upsertWorker(state.workers, event.worker) };
|
|
9189
|
-
case "REMOVE_WORKER": {
|
|
9190
|
-
const workers = state.workers.filter((worker) => worker.name !== event.name);
|
|
9191
|
-
return workers.length === state.workers.length ? state : { ...state, workers };
|
|
9192
|
-
}
|
|
9193
|
-
case "CLEAR":
|
|
9194
|
-
return initialMasterState();
|
|
9195
|
-
}
|
|
9196
|
-
}
|
|
9197
|
-
function recoverMasterState(state, interruptedAt = Date.now()) {
|
|
9198
|
-
let changed = false;
|
|
9199
|
-
const workers = state.workers.map((worker) => {
|
|
9200
|
-
if (worker.status === "idle")
|
|
9201
|
-
return worker;
|
|
9202
|
-
changed = true;
|
|
9203
|
-
return { ...worker, status: "idle", interruptedAt };
|
|
9204
|
-
});
|
|
9205
|
-
return changed ? { ...state, workers } : state;
|
|
9206
|
-
}
|
|
9207
|
-
function restoreMasterState(data) {
|
|
9208
|
-
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
9209
|
-
return;
|
|
9210
|
-
const record = data;
|
|
9211
|
-
if (record.version !== STATE_VERSION || !Array.isArray(record.workers) || !record.workers.every(isWorker))
|
|
9212
|
-
return;
|
|
9213
|
-
const workers = record.workers;
|
|
9214
|
-
if (new Set(workers.map((worker) => worker.name)).size !== workers.length)
|
|
9215
|
-
return;
|
|
9216
|
-
if (new Set(workers.map((worker) => worker.sessionPath)).size !== workers.length)
|
|
9217
|
-
return;
|
|
9218
|
-
return { version: STATE_VERSION, workers };
|
|
9219
|
-
}
|
|
9220
|
-
|
|
9221
|
-
class LegacyMasterStateError extends Error {
|
|
9222
|
-
version;
|
|
9223
|
-
constructor(version) {
|
|
9224
|
-
super(msg7.state.legacy(version, STATE_VERSION));
|
|
9225
|
-
this.version = version;
|
|
9226
|
-
}
|
|
9227
|
-
}
|
|
9228
8713
|
function masterStatePath(agentDir, sessionId) {
|
|
9229
8714
|
const safeId = sessionId.replace(/[^a-zA-Z0-9_-]/gu, "-");
|
|
9230
8715
|
return join3(agentDir, "tmp", `firecode-master-${safeId}.json`);
|
|
9231
8716
|
}
|
|
9232
|
-
function loadMasterState(path) {
|
|
9233
|
-
let raw;
|
|
9234
|
-
try {
|
|
9235
|
-
raw = readFileSync6(path, "utf8");
|
|
9236
|
-
} catch (error) {
|
|
9237
|
-
if (error.code === "ENOENT")
|
|
9238
|
-
return;
|
|
9239
|
-
throw error;
|
|
9240
|
-
}
|
|
9241
|
-
let data;
|
|
9242
|
-
try {
|
|
9243
|
-
data = JSON.parse(raw);
|
|
9244
|
-
} catch {
|
|
9245
|
-
throw new Error(msg7.state.invalidJson(path));
|
|
9246
|
-
}
|
|
9247
|
-
const version = data?.version;
|
|
9248
|
-
if (typeof version === "number" && version !== STATE_VERSION && version !== 8)
|
|
9249
|
-
throw new LegacyMasterStateError(version);
|
|
9250
|
-
const state = restoreMasterState(version === 8 ? migrateFromV8(data) : data);
|
|
9251
|
-
if (!state)
|
|
9252
|
-
throw new Error(msg7.state.invalidShape(path));
|
|
9253
|
-
return state;
|
|
9254
|
-
}
|
|
9255
8717
|
|
|
9256
8718
|
class MasterStore {
|
|
9257
|
-
stateValue;
|
|
9258
8719
|
path;
|
|
9259
8720
|
onChange;
|
|
8721
|
+
list;
|
|
9260
8722
|
discardedLegacyVersion;
|
|
9261
|
-
constructor(path,
|
|
8723
|
+
constructor(path, onChange) {
|
|
9262
8724
|
this.path = path;
|
|
9263
8725
|
this.onChange = onChange;
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
this.
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
if (
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
8726
|
+
const loaded = load(path);
|
|
8727
|
+
this.discardedLegacyVersion = loaded.legacyVersion;
|
|
8728
|
+
const now = Date.now();
|
|
8729
|
+
this.list = loaded.workers.map((worker) => worker.status === "idle" ? worker : { ...worker, status: "idle", interruptedAt: now });
|
|
8730
|
+
if (this.list.some((worker, index) => worker !== loaded.workers[index]))
|
|
8731
|
+
this.persist();
|
|
8732
|
+
}
|
|
8733
|
+
get workers() {
|
|
8734
|
+
return this.list;
|
|
8735
|
+
}
|
|
8736
|
+
find(name) {
|
|
8737
|
+
return this.list.find((worker) => worker.name === name);
|
|
8738
|
+
}
|
|
8739
|
+
require(name) {
|
|
8740
|
+
const worker = this.find(name);
|
|
8741
|
+
if (!worker)
|
|
8742
|
+
throw new Error(msg7.state.missing(name));
|
|
8743
|
+
return worker;
|
|
8744
|
+
}
|
|
8745
|
+
upsert(worker) {
|
|
8746
|
+
const owner = this.list.find((candidate) => candidate.sessionPath === worker.sessionPath);
|
|
8747
|
+
if (owner && owner.name !== worker.name)
|
|
8748
|
+
throw new Error(msg7.state.pathTaken(worker.sessionPath));
|
|
8749
|
+
const existing = this.find(worker.name);
|
|
8750
|
+
if (existing && existing.sessionPath !== worker.sessionPath)
|
|
8751
|
+
throw new Error(msg7.state.pathChange(worker.name));
|
|
8752
|
+
this.list = existing ? this.list.map((candidate) => candidate === existing ? worker : candidate) : [...this.list, worker];
|
|
8753
|
+
this.commit();
|
|
8754
|
+
}
|
|
8755
|
+
remove(name) {
|
|
8756
|
+
if (!this.find(name))
|
|
8757
|
+
return false;
|
|
8758
|
+
this.list = this.list.filter((worker) => worker.name !== name);
|
|
8759
|
+
this.commit();
|
|
8760
|
+
return true;
|
|
8761
|
+
}
|
|
8762
|
+
commit() {
|
|
8763
|
+
this.persist();
|
|
9284
8764
|
this.onChange?.();
|
|
9285
|
-
return next;
|
|
9286
8765
|
}
|
|
9287
|
-
|
|
8766
|
+
persist() {
|
|
8767
|
+
mkdirSync2(dirname3(this.path), { recursive: true, mode: 448 });
|
|
8768
|
+
const temporary = `${this.path}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
9288
8769
|
try {
|
|
9289
|
-
|
|
8770
|
+
writeFileSync3(temporary, `${JSON.stringify({ version: STATE_VERSION, workers: this.list })}
|
|
8771
|
+
`, { encoding: "utf8", mode: 384 });
|
|
8772
|
+
renameSync2(temporary, this.path);
|
|
9290
8773
|
} catch (error) {
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
rmSync2(this.path, { force: true });
|
|
9294
|
-
return { state: initialMasterState(), discardedLegacyVersion: error.version };
|
|
8774
|
+
rmSync2(temporary, { force: true });
|
|
8775
|
+
throw error;
|
|
9295
8776
|
}
|
|
9296
8777
|
}
|
|
9297
8778
|
}
|
|
9298
|
-
function
|
|
9299
|
-
|
|
9300
|
-
return data;
|
|
9301
|
-
const workers = data.workers;
|
|
9302
|
-
const created = (index) => typeof workers[index]?.createdAt === "number" ? workers[index].createdAt : -Infinity;
|
|
9303
|
-
const order = workers.map((_, index) => index).sort((a, b) => created(a) - created(b) || a - b);
|
|
9304
|
-
return {
|
|
9305
|
-
version: STATE_VERSION,
|
|
9306
|
-
workers: workers.map((worker, index) => {
|
|
9307
|
-
const { createdAt: _createdAt, ...rest } = worker ?? {};
|
|
9308
|
-
return { ...rest, launch: order.indexOf(index) + 1 };
|
|
9309
|
-
})
|
|
9310
|
-
};
|
|
9311
|
-
}
|
|
9312
|
-
function requireWorker(state, name) {
|
|
9313
|
-
const worker = state.workers.find((candidate) => candidate.name === name);
|
|
9314
|
-
if (!worker)
|
|
9315
|
-
throw new Error(msg7.state.missing(name));
|
|
9316
|
-
return worker;
|
|
9317
|
-
}
|
|
9318
|
-
function writeState(path, state) {
|
|
9319
|
-
mkdirSync3(dirname4(path), { recursive: true, mode: 448 });
|
|
9320
|
-
const temporary = `${path}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
8779
|
+
function load(path) {
|
|
8780
|
+
let raw;
|
|
9321
8781
|
try {
|
|
9322
|
-
|
|
9323
|
-
`, { encoding: "utf8", mode: 384 });
|
|
9324
|
-
renameSync2(temporary, path);
|
|
8782
|
+
raw = readFileSync6(path, "utf8");
|
|
9325
8783
|
} catch (error) {
|
|
9326
|
-
|
|
8784
|
+
if (error.code === "ENOENT")
|
|
8785
|
+
return { workers: [] };
|
|
9327
8786
|
throw error;
|
|
9328
8787
|
}
|
|
9329
|
-
|
|
9330
|
-
|
|
9331
|
-
|
|
9332
|
-
|
|
9333
|
-
|
|
9334
|
-
|
|
9335
|
-
if (
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
|
|
8788
|
+
let data;
|
|
8789
|
+
try {
|
|
8790
|
+
data = JSON.parse(raw);
|
|
8791
|
+
} catch {
|
|
8792
|
+
throw new Error(msg7.state.invalidJson(path));
|
|
8793
|
+
}
|
|
8794
|
+
if (typeof data?.version === "number" && data.version !== STATE_VERSION) {
|
|
8795
|
+
rmSync2(path, { force: true });
|
|
8796
|
+
return { workers: [], legacyVersion: data.version };
|
|
8797
|
+
}
|
|
8798
|
+
const workers = data?.workers;
|
|
8799
|
+
if (data?.version !== STATE_VERSION || !Array.isArray(workers) || !workers.every(isWorker) || new Set(workers.map((worker) => worker.name)).size !== workers.length || new Set(workers.map((worker) => worker.sessionPath)).size !== workers.length)
|
|
8800
|
+
throw new Error(msg7.state.invalidShape(path));
|
|
8801
|
+
return { workers };
|
|
9340
8802
|
}
|
|
9341
8803
|
function isWorker(value) {
|
|
9342
8804
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
9343
8805
|
return false;
|
|
9344
8806
|
const record = value;
|
|
9345
|
-
if (typeof record.name !== "string" ||
|
|
8807
|
+
if (typeof record.name !== "string" || !WORKER_NAME.test(record.name) || typeof record.role !== "string" || !record.role || typeof record.model !== "string" || !record.model || typeof record.thinking !== "string" || !THINKING_LEVELS2.includes(record.thinking) || record.status !== "working" && record.status !== "idle" && record.status !== "reviewing" || typeof record.sessionPath !== "string" || !record.sessionPath)
|
|
9346
8808
|
return false;
|
|
9347
8809
|
if (record.cwd !== undefined && (typeof record.cwd !== "string" || !record.cwd))
|
|
9348
8810
|
return false;
|
|
@@ -9352,12 +8814,7 @@ function isWorker(value) {
|
|
|
9352
8814
|
return false;
|
|
9353
8815
|
if (typeof record.launch !== "number" || !Number.isInteger(record.launch) || record.launch <= 0)
|
|
9354
8816
|
return false;
|
|
9355
|
-
|
|
9356
|
-
return false;
|
|
9357
|
-
return true;
|
|
9358
|
-
}
|
|
9359
|
-
function isStatus(value) {
|
|
9360
|
-
return value === "working" || value === "idle" || value === "reviewing";
|
|
8817
|
+
return record.disposition === undefined || record.disposition === "pending" || record.disposition === "reminded";
|
|
9361
8818
|
}
|
|
9362
8819
|
|
|
9363
8820
|
// master/actions.ts
|
|
@@ -9365,24 +8822,24 @@ var ACTIONS = ["start", "send", "interrupt", "review", "tail", "ack", "kill"];
|
|
|
9365
8822
|
var MAX_IN_FLIGHT = 15;
|
|
9366
8823
|
var ACTION_HANDLERS = { start, send, interrupt, review, tail: tail2, ack, kill };
|
|
9367
8824
|
async function kill(active, params) {
|
|
9368
|
-
const target =
|
|
8825
|
+
const target = targetOf(active, params);
|
|
9369
8826
|
active.remove(target.name);
|
|
9370
8827
|
await active.setup.pool.dispose(target.sessionPath);
|
|
9371
8828
|
return toolResult({ killed: true });
|
|
9372
8829
|
}
|
|
9373
8830
|
async function tail2(active, params) {
|
|
9374
|
-
const target =
|
|
8831
|
+
const target = targetOf(active, params);
|
|
9375
8832
|
return { content: [{ type: "text", text: await readWorkerTrace(target) }], details: undefined };
|
|
9376
8833
|
}
|
|
9377
8834
|
async function ack(active, params) {
|
|
9378
|
-
const target =
|
|
8835
|
+
const target = targetOf(active, params);
|
|
9379
8836
|
if (target.reviewNeeded)
|
|
9380
8837
|
throw new Error(msg7.action.ackObligation(target.name));
|
|
9381
8838
|
if (target.status !== "idle")
|
|
9382
8839
|
throw new Error(msg7.action.ackStatus(target.name, target.status));
|
|
9383
8840
|
if (target.disposition) {
|
|
9384
8841
|
const { disposition: _disposition, ...rest } = target;
|
|
9385
|
-
active.store.
|
|
8842
|
+
active.store.upsert(rest);
|
|
9386
8843
|
}
|
|
9387
8844
|
const live = active.live.get(target.name);
|
|
9388
8845
|
if (live?.outcome && live.outcome.kind !== "done")
|
|
@@ -9393,7 +8850,7 @@ async function ack(active, params) {
|
|
|
9393
8850
|
async function review(active, params) {
|
|
9394
8851
|
if (active.setup.reviewGate)
|
|
9395
8852
|
throw new Error(active.setup.reviewGate);
|
|
9396
|
-
const target =
|
|
8853
|
+
const target = targetOf(active, params);
|
|
9397
8854
|
const live = active.liveOf(target.name);
|
|
9398
8855
|
if (target.status !== "idle" || live.transitioning)
|
|
9399
8856
|
throw new Error(msg7.action.reviewBusy(target.name));
|
|
@@ -9413,7 +8870,7 @@ async function review(active, params) {
|
|
|
9413
8870
|
}
|
|
9414
8871
|
}
|
|
9415
8872
|
async function interrupt(active, params) {
|
|
9416
|
-
const target =
|
|
8873
|
+
const target = targetOf(active, params);
|
|
9417
8874
|
if (target.status !== "working")
|
|
9418
8875
|
throw new Error(msg7.action.interruptStatus(target.name, target.status));
|
|
9419
8876
|
const session = active.setup.pool.getSession(target.sessionPath);
|
|
@@ -9438,7 +8895,7 @@ async function send(active, params) {
|
|
|
9438
8895
|
const { reviewGate, pool, roster } = active.setup;
|
|
9439
8896
|
if (params.review === true && reviewGate)
|
|
9440
8897
|
throw new Error(reviewGate);
|
|
9441
|
-
const target =
|
|
8898
|
+
const target = targetOf(active, params);
|
|
9442
8899
|
const live = active.liveOf(target.name);
|
|
9443
8900
|
if (live.transitioning)
|
|
9444
8901
|
throw new Error(msg7.action.switching(target.name));
|
|
@@ -9527,9 +8984,9 @@ async function start(active, params, ctx) {
|
|
|
9527
8984
|
const name = params.worker.trim();
|
|
9528
8985
|
validateWorkerName(name);
|
|
9529
8986
|
const starting = [...active.live].flatMap(([candidate, live]) => live.starting ? [candidate] : []);
|
|
9530
|
-
if (active.store.
|
|
8987
|
+
if (active.store.find(name) || starting.includes(name))
|
|
9531
8988
|
throw new Error(msg7.action.exists(name));
|
|
9532
|
-
const inFlight = active.store.
|
|
8989
|
+
const inFlight = active.store.workers.filter((worker) => worker.status === "working" || worker.status === "reviewing");
|
|
9533
8990
|
if (inFlight.length + starting.length >= MAX_IN_FLIGHT)
|
|
9534
8991
|
throw new Error(msg7.action.limit(MAX_IN_FLIGHT, [...inFlight.map((worker) => worker.name), ...starting]));
|
|
9535
8992
|
const prompt = requiredString(params.prompt, "prompt");
|
|
@@ -9557,7 +9014,7 @@ async function start(active, params, ctx) {
|
|
|
9557
9014
|
launch,
|
|
9558
9015
|
...params.review === true ? { reviewNeeded: true } : {}
|
|
9559
9016
|
};
|
|
9560
|
-
active.store.
|
|
9017
|
+
active.store.upsert(worker);
|
|
9561
9018
|
live.starting = undefined;
|
|
9562
9019
|
active.beginRun(name);
|
|
9563
9020
|
const session = await spawnWorker(active, worker, false);
|
|
@@ -9596,6 +9053,9 @@ ${lines.join(`
|
|
|
9596
9053
|
function toolResult(value) {
|
|
9597
9054
|
return { content: [{ type: "text", text: JSON.stringify(value) }], details: value };
|
|
9598
9055
|
}
|
|
9056
|
+
function targetOf(active, params) {
|
|
9057
|
+
return active.store.require(requiredString(params.worker, "worker"));
|
|
9058
|
+
}
|
|
9599
9059
|
function requiredString(value, field) {
|
|
9600
9060
|
if (typeof value !== "string" || !value.trim())
|
|
9601
9061
|
throw new Error(msg7.action.empty(field));
|
|
@@ -9613,7 +9073,7 @@ function resolveRole(roles, role) {
|
|
|
9613
9073
|
return roles.find((candidate) => candidate.role === role);
|
|
9614
9074
|
}
|
|
9615
9075
|
function validateWorkerName(name) {
|
|
9616
|
-
if (
|
|
9076
|
+
if (!WORKER_NAME.test(name))
|
|
9617
9077
|
throw new Error(msg7.action.badName);
|
|
9618
9078
|
}
|
|
9619
9079
|
function validateDelegationText(prompt) {
|
|
@@ -9640,40 +9100,12 @@ async function resolveSendCwd(worker, requested) {
|
|
|
9640
9100
|
|
|
9641
9101
|
// master/event-card.ts
|
|
9642
9102
|
import { getMarkdownTheme as getMarkdownTheme5 } from "@earendil-works/pi-coding-agent";
|
|
9643
|
-
import { Box as Box2, Markdown as Markdown4
|
|
9103
|
+
import { Box as Box2, Markdown as Markdown4 } from "@earendil-works/pi-tui";
|
|
9644
9104
|
function registerMasterEventRenderer(pi) {
|
|
9645
|
-
pi.registerMessageRenderer(MASTER_EVENT_TYPE, (message, options, theme) =>
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
card;
|
|
9650
|
-
fallback;
|
|
9651
|
-
constructor(content, expanded, theme) {
|
|
9652
|
-
const text = textOf(content);
|
|
9653
|
-
this.fallback = new Text6(text, 0, 0);
|
|
9654
|
-
let card;
|
|
9655
|
-
try {
|
|
9656
|
-
card = !expanded ? compactCard(text, theme) : fullCard(text, theme);
|
|
9657
|
-
} catch {
|
|
9658
|
-
card = undefined;
|
|
9659
|
-
}
|
|
9660
|
-
this.card = card ?? this.fallback;
|
|
9661
|
-
}
|
|
9662
|
-
render(width) {
|
|
9663
|
-
try {
|
|
9664
|
-
return this.card.render(Math.max(1, width));
|
|
9665
|
-
} catch {
|
|
9666
|
-
try {
|
|
9667
|
-
return this.fallback.render(Math.max(1, width));
|
|
9668
|
-
} catch {
|
|
9669
|
-
return [];
|
|
9670
|
-
}
|
|
9671
|
-
}
|
|
9672
|
-
}
|
|
9673
|
-
invalidate() {
|
|
9674
|
-
this.card.invalidate?.();
|
|
9675
|
-
this.fallback.invalidate?.();
|
|
9676
|
-
}
|
|
9105
|
+
pi.registerMessageRenderer(MASTER_EVENT_TYPE, (message, options, theme) => {
|
|
9106
|
+
const text = textOf(message.content);
|
|
9107
|
+
return options.expanded ? fullCard(text, theme) : compactCard(text, theme);
|
|
9108
|
+
});
|
|
9677
9109
|
}
|
|
9678
9110
|
function compactCard(text, theme) {
|
|
9679
9111
|
const entries = machineEntries(text);
|
|
@@ -9708,7 +9140,7 @@ function card(theme, children) {
|
|
|
9708
9140
|
// master/guard.ts
|
|
9709
9141
|
import { realpath as realpath2 } from "node:fs/promises";
|
|
9710
9142
|
import { tmpdir } from "node:os";
|
|
9711
|
-
import { basename as basename3, dirname as
|
|
9143
|
+
import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute2, relative, resolve, sep } from "node:path";
|
|
9712
9144
|
import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
|
|
9713
9145
|
function registerWorkerGuard(pi) {
|
|
9714
9146
|
pi.on("tool_call", async (event, ctx) => {
|
|
@@ -9721,7 +9153,7 @@ function registerWorkerGuard(pi) {
|
|
|
9721
9153
|
}
|
|
9722
9154
|
var TEMP_ROOTS = [tmpdir(), "/tmp"];
|
|
9723
9155
|
async function outsideCheckoutReason(path, cwd) {
|
|
9724
|
-
const target = await canonicalWritePath(
|
|
9156
|
+
const target = await canonicalWritePath(resolve(cwd, path));
|
|
9725
9157
|
const roots = await Promise.all([cwd, ...TEMP_ROOTS].map(canonicalWritePath));
|
|
9726
9158
|
return roots.some((root) => within(root, target)) ? undefined : msg7.guard(path);
|
|
9727
9159
|
}
|
|
@@ -9734,9 +9166,9 @@ async function canonicalWritePath(path) {
|
|
|
9734
9166
|
const missing = [];
|
|
9735
9167
|
while (true) {
|
|
9736
9168
|
try {
|
|
9737
|
-
return
|
|
9169
|
+
return resolve(await realpath2(ancestor), ...missing.reverse());
|
|
9738
9170
|
} catch {
|
|
9739
|
-
const parent =
|
|
9171
|
+
const parent = dirname4(ancestor);
|
|
9740
9172
|
if (parent === ancestor)
|
|
9741
9173
|
return path;
|
|
9742
9174
|
missing.push(basename3(ancestor));
|
|
@@ -10038,7 +9470,7 @@ class Outbox {
|
|
|
10038
9470
|
if (this.active.closed)
|
|
10039
9471
|
return;
|
|
10040
9472
|
const names = new Set;
|
|
10041
|
-
for (const worker of this.active.store.
|
|
9473
|
+
for (const worker of this.active.store.workers)
|
|
10042
9474
|
if ((worker.status === "working" || worker.status === "reviewing") && this.active.live.get(worker.name)?.origin !== "view")
|
|
10043
9475
|
names.add(worker.name);
|
|
10044
9476
|
for (const event of [...this.queued, ...this.delivering])
|
|
@@ -10105,9 +9537,9 @@ class Outbox {
|
|
|
10105
9537
|
for (const event of batch) {
|
|
10106
9538
|
if (!event.worker)
|
|
10107
9539
|
continue;
|
|
10108
|
-
const worker = active.store.
|
|
9540
|
+
const worker = active.store.find(event.worker);
|
|
10109
9541
|
if (worker?.status === "idle" && worker.disposition !== "reminded")
|
|
10110
|
-
active.store.
|
|
9542
|
+
active.store.upsert({ ...worker, disposition: "pending" });
|
|
10111
9543
|
}
|
|
10112
9544
|
this.scheduleInFlight();
|
|
10113
9545
|
}, (error) => {
|
|
@@ -10191,7 +9623,7 @@ function runtimeSource(active) {
|
|
|
10191
9623
|
runtimeDrafts.set(active, drafts = new Map);
|
|
10192
9624
|
return {
|
|
10193
9625
|
facts: () => active.activityFacts(),
|
|
10194
|
-
worker: (name) => active.store.
|
|
9626
|
+
worker: (name) => active.store.find(name),
|
|
10195
9627
|
session: (worker) => active.setup.pool.getSession(worker.sessionPath),
|
|
10196
9628
|
onSession: (listener) => active.onWorkerSession(listener),
|
|
10197
9629
|
onWorkerRemoved: (listener) => active.onWorkerRemoved(listener),
|
|
@@ -10622,6 +10054,7 @@ class WorkerView {
|
|
|
10622
10054
|
// master/runtime.ts
|
|
10623
10055
|
var LIST_WIDGET_KEY = "firecode-master-list";
|
|
10624
10056
|
var MASTER_IDENTITY = paint(HEAT_COLORS.orange, msg7.command.identity);
|
|
10057
|
+
var newLive = () => ({ currentTools: new Map, viewPrompts: [], origin: "master" });
|
|
10625
10058
|
|
|
10626
10059
|
class MasterRuntime {
|
|
10627
10060
|
setup;
|
|
@@ -10635,12 +10068,12 @@ class MasterRuntime {
|
|
|
10635
10068
|
stopReleaseWatch;
|
|
10636
10069
|
sessionListeners = new Set;
|
|
10637
10070
|
removedListeners = new Set;
|
|
10638
|
-
constructor(setup, ctx
|
|
10071
|
+
constructor(setup, ctx) {
|
|
10639
10072
|
this.setup = setup;
|
|
10640
10073
|
this.ctx = ctx;
|
|
10641
10074
|
this.outbox = new Outbox(this);
|
|
10642
|
-
this.store = new MasterStore(masterStatePath(getAgentDir4(), ctx.sessionManager.getSessionId()),
|
|
10643
|
-
this.launchSeq = Math.max(0, ...this.store.
|
|
10075
|
+
this.store = new MasterStore(masterStatePath(getAgentDir4(), ctx.sessionManager.getSessionId()), () => this.render());
|
|
10076
|
+
this.launchSeq = Math.max(0, ...this.store.workers.map((worker) => worker.launch));
|
|
10644
10077
|
this.stopReleaseWatch = setup.pool.onRelease((sessionPath) => {
|
|
10645
10078
|
for (const live of this.live.values())
|
|
10646
10079
|
if (live.observed?.sessionPath === sessionPath)
|
|
@@ -10671,11 +10104,11 @@ class MasterRuntime {
|
|
|
10671
10104
|
liveOf(name) {
|
|
10672
10105
|
let live = this.live.get(name);
|
|
10673
10106
|
if (!live)
|
|
10674
|
-
this.live.set(name, live =
|
|
10107
|
+
this.live.set(name, live = newLive());
|
|
10675
10108
|
return live;
|
|
10676
10109
|
}
|
|
10677
10110
|
reserve(name) {
|
|
10678
|
-
const live = {
|
|
10111
|
+
const live = { ...newLive(), starting: true };
|
|
10679
10112
|
this.live.set(name, live);
|
|
10680
10113
|
return { live, launch: ++this.launchSeq };
|
|
10681
10114
|
}
|
|
@@ -10687,27 +10120,26 @@ class MasterRuntime {
|
|
|
10687
10120
|
this.unobserve(live);
|
|
10688
10121
|
this.live.delete(name);
|
|
10689
10122
|
}
|
|
10690
|
-
|
|
10691
|
-
if (this.store.dispatch({ type: "REMOVE_WORKER", name }) === before)
|
|
10123
|
+
if (!this.store.remove(name))
|
|
10692
10124
|
return;
|
|
10693
10125
|
for (const notify of this.removedListeners)
|
|
10694
10126
|
notify(name);
|
|
10695
10127
|
}
|
|
10696
10128
|
current(identity) {
|
|
10697
10129
|
this.assertOpen();
|
|
10698
|
-
const current = this.
|
|
10699
|
-
if (current
|
|
10130
|
+
const current = this.find(identity);
|
|
10131
|
+
if (current)
|
|
10700
10132
|
return current;
|
|
10701
10133
|
this.setup.pool.dispose(identity.sessionPath);
|
|
10702
10134
|
throw new Error(msg7.runtime.killed(identity.name));
|
|
10703
10135
|
}
|
|
10704
10136
|
commit(identity, update) {
|
|
10705
10137
|
const next = update(this.current(identity));
|
|
10706
|
-
this.store.
|
|
10138
|
+
this.store.upsert(next);
|
|
10707
10139
|
return next;
|
|
10708
10140
|
}
|
|
10709
10141
|
find(identity) {
|
|
10710
|
-
const current = this.store.
|
|
10142
|
+
const current = this.store.find(identity.name);
|
|
10711
10143
|
return current?.sessionPath === identity.sessionPath ? current : undefined;
|
|
10712
10144
|
}
|
|
10713
10145
|
markIdle(worker, outcome, at = Date.now()) {
|
|
@@ -10768,7 +10200,7 @@ class MasterRuntime {
|
|
|
10768
10200
|
live.observed = undefined;
|
|
10769
10201
|
}
|
|
10770
10202
|
activityFacts() {
|
|
10771
|
-
const workers = this.store.
|
|
10203
|
+
const workers = this.store.workers;
|
|
10772
10204
|
const byPath = (pick) => new Map(workers.flatMap((worker) => {
|
|
10773
10205
|
const live = this.live.get(worker.name);
|
|
10774
10206
|
const value = live && pick(live);
|
|
@@ -10794,16 +10226,12 @@ var WAKE_QUIET_MS = 1500;
|
|
|
10794
10226
|
function registerMaster(pi, dependencies = {}, worker = false) {
|
|
10795
10227
|
if (worker)
|
|
10796
10228
|
return registerWorkerGuard(pi);
|
|
10797
|
-
const
|
|
10229
|
+
const all = loadConfig();
|
|
10230
|
+
const loaded = all.master;
|
|
10798
10231
|
const prompts = loadMasterPrompts();
|
|
10799
|
-
const startupError = "error" in loaded ? loaded.error :
|
|
10232
|
+
const startupError = "error" in loaded ? loaded.error : prompts.error;
|
|
10800
10233
|
const roster = "error" in loaded ? [] : loaded.config.roles;
|
|
10801
10234
|
const autoActivate = "error" in loaded ? false : loaded.config.autoActivate;
|
|
10802
|
-
const requirePrompts = () => {
|
|
10803
|
-
if ("error" in prompts)
|
|
10804
|
-
throw new Error(prompts.error);
|
|
10805
|
-
return prompts;
|
|
10806
|
-
};
|
|
10807
10235
|
let publishedInFlight = 0;
|
|
10808
10236
|
const publishInFlight = (count, teardown = false) => {
|
|
10809
10237
|
if (count === publishedInFlight)
|
|
@@ -10820,8 +10248,8 @@ function registerMaster(pi, dependencies = {}, worker = false) {
|
|
|
10820
10248
|
pool: dependencies.pool ?? new InProcessSessionPool,
|
|
10821
10249
|
roster,
|
|
10822
10250
|
exclusions: "error" in loaded ? [] : loaded.config.workerExcludeExtensions,
|
|
10823
|
-
workerPrompt:
|
|
10824
|
-
reviewGate:
|
|
10251
|
+
workerPrompt: prompts.worker,
|
|
10252
|
+
reviewGate: all.config.features.review === false ? msg7.command.reviewOff : ("error" in all.review) ? all.review.error : undefined,
|
|
10825
10253
|
interruptResumeMs: dependencies.interruptResumeMs ?? INTERRUPT_RESUME_MS,
|
|
10826
10254
|
wakeQuietMs: dependencies.wakeQuietMs ?? WAKE_QUIET_MS,
|
|
10827
10255
|
publishInFlight,
|
|
@@ -10833,19 +10261,18 @@ function registerMaster(pi, dependencies = {}, worker = false) {
|
|
|
10833
10261
|
const tools = pi.getActiveTools().filter((name) => !MASTER_TOOLS.includes(name));
|
|
10834
10262
|
pi.setActiveTools(active ? [...tools, ...MASTER_TOOLS] : tools);
|
|
10835
10263
|
};
|
|
10836
|
-
const activate = (ctx
|
|
10264
|
+
const activate = (ctx) => {
|
|
10837
10265
|
if (startupError)
|
|
10838
10266
|
throw new Error(startupError);
|
|
10839
|
-
|
|
10840
|
-
runtime.ctx = ctx;
|
|
10841
|
-
return runtime;
|
|
10842
|
-
}
|
|
10843
|
-
const active = runtime = new MasterRuntime(setup, ctx, restored);
|
|
10267
|
+
const active = runtime = new MasterRuntime(setup, ctx);
|
|
10844
10268
|
setTools(true);
|
|
10845
10269
|
if (active.store.discardedLegacyVersion !== undefined)
|
|
10846
10270
|
ctx.ui.notify(msg7.command.legacyPool(active.store.discardedLegacyVersion), "warning");
|
|
10847
10271
|
active.render();
|
|
10848
|
-
|
|
10272
|
+
for (const worker of active.store.workers)
|
|
10273
|
+
if (worker.interruptedAt)
|
|
10274
|
+
armInterruptReminder(active, worker);
|
|
10275
|
+
active.outbox.replayUnacked(ctx);
|
|
10849
10276
|
};
|
|
10850
10277
|
const deactivate = async () => {
|
|
10851
10278
|
const active = runtime;
|
|
@@ -10855,34 +10282,12 @@ function registerMaster(pi, dependencies = {}, worker = false) {
|
|
|
10855
10282
|
await setup.pool.disposeAll();
|
|
10856
10283
|
setTools(false);
|
|
10857
10284
|
};
|
|
10858
|
-
const activateSession = (ctx) => {
|
|
10859
|
-
if (runtime)
|
|
10860
|
-
return activate(ctx);
|
|
10861
|
-
let restored;
|
|
10862
|
-
try {
|
|
10863
|
-
restored = loadMasterState(masterStatePath(getAgentDir5(), ctx.sessionManager.getSessionId()));
|
|
10864
|
-
} catch {
|
|
10865
|
-
return activate(ctx);
|
|
10866
|
-
}
|
|
10867
|
-
const active = activate(ctx, restored);
|
|
10868
|
-
if (restored) {
|
|
10869
|
-
const recovered = recoverMasterState(restored);
|
|
10870
|
-
for (const worker of recovered.workers) {
|
|
10871
|
-
if (worker !== restored.workers.find((candidate) => candidate.name === worker.name))
|
|
10872
|
-
active.store.dispatch({ type: "UPSERT_WORKER", worker });
|
|
10873
|
-
if (worker.interruptedAt)
|
|
10874
|
-
armInterruptReminder(active, worker);
|
|
10875
|
-
}
|
|
10876
|
-
}
|
|
10877
|
-
active.outbox.replayUnacked(ctx);
|
|
10878
|
-
return active;
|
|
10879
|
-
};
|
|
10880
10285
|
pi.registerCommand("fire-master", {
|
|
10881
10286
|
description: msg7.command.description,
|
|
10882
10287
|
handler: async (args, ctx) => {
|
|
10883
10288
|
const input = args.trim();
|
|
10884
10289
|
if (input === "status") {
|
|
10885
|
-
ctx.ui.notify(runtime ? statusText(runtime.store.
|
|
10290
|
+
ctx.ui.notify(runtime ? statusText(runtime.store.workers) : msg7.command.notStarted, "info");
|
|
10886
10291
|
return;
|
|
10887
10292
|
}
|
|
10888
10293
|
if (input) {
|
|
@@ -10895,7 +10300,7 @@ function registerMaster(pi, dependencies = {}, worker = false) {
|
|
|
10895
10300
|
return;
|
|
10896
10301
|
}
|
|
10897
10302
|
try {
|
|
10898
|
-
|
|
10303
|
+
activate(ctx);
|
|
10899
10304
|
ctx.ui.notify(msg7.command.on, "info");
|
|
10900
10305
|
} catch (error) {
|
|
10901
10306
|
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
@@ -10912,7 +10317,7 @@ function registerMaster(pi, dependencies = {}, worker = false) {
|
|
|
10912
10317
|
return {
|
|
10913
10318
|
systemPrompt: `${event.systemPrompt}
|
|
10914
10319
|
|
|
10915
|
-
${assembleMasterPrompt(
|
|
10320
|
+
${assembleMasterPrompt(prompts.master, rosterText(roster))}`
|
|
10916
10321
|
};
|
|
10917
10322
|
});
|
|
10918
10323
|
pi.registerTool({
|
|
@@ -10922,10 +10327,10 @@ ${assembleMasterPrompt(requirePrompts().master, rosterText(roster))}`
|
|
|
10922
10327
|
renderShell: "self",
|
|
10923
10328
|
renderCall: (_args, theme, ctx) => new ToolLine({ label: msg7.tool.label, value: subagentsCallParts({ action: "list" }), clip: "end", theme, ctx }),
|
|
10924
10329
|
renderResult: (result, options, theme, context) => {
|
|
10925
|
-
const
|
|
10926
|
-
context.state.meta = !context.isError &&
|
|
10927
|
-
if (options.expanded &&
|
|
10928
|
-
return expandedWorkerList(
|
|
10330
|
+
const workers = result.details?.workers;
|
|
10331
|
+
context.state.meta = !context.isError && workers ? listMeta(workers) : undefined;
|
|
10332
|
+
if (options.expanded && workers)
|
|
10333
|
+
return expandedWorkerList(workers, theme, context);
|
|
10929
10334
|
return renderSubagentsResult(result, options, theme, context);
|
|
10930
10335
|
},
|
|
10931
10336
|
parameters: Type.Object({}),
|
|
@@ -10933,7 +10338,7 @@ ${assembleMasterPrompt(requirePrompts().master, rosterText(roster))}`
|
|
|
10933
10338
|
const active = runtime;
|
|
10934
10339
|
if (!active)
|
|
10935
10340
|
throw new Error(msg7.tool.onlyInMaster(MASTER_LIST_TOOL));
|
|
10936
|
-
const workers = active.store.
|
|
10341
|
+
const workers = active.store.workers.map(compactWorker);
|
|
10937
10342
|
return {
|
|
10938
10343
|
content: [{ type: "text", text: JSON.stringify({ workers }) }],
|
|
10939
10344
|
details: {
|
|
@@ -10962,10 +10367,7 @@ ${assembleMasterPrompt(requirePrompts().master, rosterText(roster))}`
|
|
|
10962
10367
|
const active = runtime;
|
|
10963
10368
|
if (!active)
|
|
10964
10369
|
throw new Error(msg7.tool.onlyInMaster(MASTER_TOOL));
|
|
10965
|
-
|
|
10966
|
-
if (!handler)
|
|
10967
|
-
throw new Error(msg7.tool.unknownAction(String(params.action)));
|
|
10968
|
-
return handler(active, params, ctx);
|
|
10370
|
+
return ACTION_HANDLERS[params.action](active, params, ctx);
|
|
10969
10371
|
}
|
|
10970
10372
|
});
|
|
10971
10373
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -10973,24 +10375,18 @@ ${assembleMasterPrompt(requirePrompts().master, rosterText(roster))}`
|
|
|
10973
10375
|
if (!autoActivate)
|
|
10974
10376
|
return;
|
|
10975
10377
|
try {
|
|
10976
|
-
|
|
10378
|
+
activate(ctx);
|
|
10977
10379
|
} catch (error) {
|
|
10978
10380
|
ctx.ui.notify(msg7.command.restoreFailed(error instanceof Error ? error.message : String(error)), "error");
|
|
10979
10381
|
}
|
|
10980
10382
|
});
|
|
10981
10383
|
pi.on("session_shutdown", () => deactivate());
|
|
10982
10384
|
}
|
|
10983
|
-
function reviewGateError() {
|
|
10984
|
-
const loaded = loadConfig();
|
|
10985
|
-
if (loaded.config.features.review === false)
|
|
10986
|
-
return msg7.command.reviewOff;
|
|
10987
|
-
return "error" in loaded.review ? loaded.review.error : undefined;
|
|
10988
|
-
}
|
|
10989
10385
|
function loadMasterPrompts() {
|
|
10990
10386
|
try {
|
|
10991
10387
|
return { master: readMasterPrompt("master"), worker: readMasterPrompt("worker") };
|
|
10992
10388
|
} catch (error) {
|
|
10993
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
10389
|
+
return { master: "", worker: "", error: error instanceof Error ? error.message : String(error) };
|
|
10994
10390
|
}
|
|
10995
10391
|
}
|
|
10996
10392
|
function rosterText(models) {
|
|
@@ -11001,7 +10397,7 @@ function rosterText(models) {
|
|
|
11001
10397
|
}
|
|
11002
10398
|
|
|
11003
10399
|
// watcher/card.ts
|
|
11004
|
-
import { Text as
|
|
10400
|
+
import { Text as Text6 } from "@earendil-works/pi-tui";
|
|
11005
10401
|
|
|
11006
10402
|
// watcher/messages.ts
|
|
11007
10403
|
var msg8 = defineMessages({
|
|
@@ -11070,17 +10466,14 @@ var msg8 = defineMessages({
|
|
|
11070
10466
|
// watcher/card.ts
|
|
11071
10467
|
var WATCHER_MESSAGE_TYPE = "firecode-watcher-note";
|
|
11072
10468
|
function adviceMessage(card) {
|
|
11073
|
-
return wrapEnvelope("firecode_watcher", `${
|
|
10469
|
+
return wrapEnvelope("firecode_watcher", `${msg8.card.headline(card.turnIndex)}
|
|
11074
10470
|
${card.note}
|
|
11075
10471
|
${msg8.card.weighNotice}`);
|
|
11076
10472
|
}
|
|
11077
|
-
function adviceHeadline(card) {
|
|
11078
|
-
return msg8.card.headline(card.turnIndex);
|
|
11079
|
-
}
|
|
11080
10473
|
function registerWatcherCardRenderer(pi) {
|
|
11081
10474
|
pi.registerMessageRenderer(WATCHER_MESSAGE_TYPE, (message, options, theme) => {
|
|
11082
10475
|
const advice = parseAdvice(message.content);
|
|
11083
|
-
return advice ? new AdviceLine(advice, options.expanded, theme) : new
|
|
10476
|
+
return advice ? new AdviceLine(advice, options.expanded, theme) : new Text6(textOf(message.content), 0, 0);
|
|
11084
10477
|
});
|
|
11085
10478
|
}
|
|
11086
10479
|
function parseAdvice(content) {
|
|
@@ -11097,32 +10490,24 @@ class AdviceLine {
|
|
|
11097
10490
|
card;
|
|
11098
10491
|
expanded;
|
|
11099
10492
|
theme;
|
|
11100
|
-
fallback;
|
|
11101
10493
|
constructor(card, expanded, theme) {
|
|
11102
10494
|
this.card = card;
|
|
11103
10495
|
this.expanded = expanded;
|
|
11104
10496
|
this.theme = theme;
|
|
11105
|
-
this.fallback = new Text7(`${card.headline} ${card.note}`, 0, 0);
|
|
11106
10497
|
}
|
|
10498
|
+
invalidate() {}
|
|
11107
10499
|
render(width) {
|
|
11108
10500
|
const columns = Math.max(1, width);
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
const body = new Text7(this.theme.fg("dim", ` ${this.card.note}
|
|
10501
|
+
const bgFn = (text) => this.theme.bg("toolPendingBg", text);
|
|
10502
|
+
if (this.expanded) {
|
|
10503
|
+
const headline = this.theme.fg("warning", clip(oneLine(this.card.headline), columns));
|
|
10504
|
+
const body = new Text6(this.theme.fg("dim", ` ${this.card.note}
|
|
11114
10505
|
${msg8.card.expandedFooter}`), 0, 0);
|
|
11115
|
-
|
|
11116
|
-
}
|
|
11117
|
-
const firstLine = oneLine(this.card.note.split(/\r?\n/u, 1)[0] ?? "");
|
|
11118
|
-
const line = clip(`${this.theme.fg("dim", RAIL)}${this.theme.fg("warning", msg8.card.label)}${this.theme.fg("dim", ` — ${firstLine}`)}`, columns);
|
|
11119
|
-
return [paintBgLine(line, columns, bgFn)];
|
|
11120
|
-
} catch {
|
|
11121
|
-
return this.fallback.render(columns);
|
|
10506
|
+
return [paintBgLine(headline, columns, bgFn), ...body.render(columns)];
|
|
11122
10507
|
}
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
|
|
10508
|
+
const firstLine = oneLine(this.card.note.split(/\r?\n/u, 1)[0] ?? "");
|
|
10509
|
+
const line = clip(`${this.theme.fg("dim", RAIL)}${this.theme.fg("warning", msg8.card.label)}${this.theme.fg("dim", ` — ${firstLine}`)}`, columns);
|
|
10510
|
+
return [paintBgLine(line, columns, bgFn)];
|
|
11126
10511
|
}
|
|
11127
10512
|
}
|
|
11128
10513
|
|
|
@@ -11138,8 +10523,8 @@ async function createObserver(options) {
|
|
|
11138
10523
|
model: options.model,
|
|
11139
10524
|
thinking: options.thinking,
|
|
11140
10525
|
tools: OBSERVER_TOOLS,
|
|
11141
|
-
customTools: [adviseTool(() => advice, (
|
|
11142
|
-
advice =
|
|
10526
|
+
customTools: [adviseTool(() => advice, (note) => {
|
|
10527
|
+
advice = note;
|
|
11143
10528
|
})],
|
|
11144
10529
|
systemPrompt: { mode: "replace", text: readPrompt(PROMPTS2, "watch") },
|
|
11145
10530
|
contextFiles: true,
|
|
@@ -11169,7 +10554,7 @@ function adviseTool(current, capture) {
|
|
|
11169
10554
|
const note = typeof params.note === "string" ? params.note.trim() : "";
|
|
11170
10555
|
if (!note)
|
|
11171
10556
|
throw new Error(msg8.advise.emptyNote);
|
|
11172
|
-
capture(
|
|
10557
|
+
capture(note);
|
|
11173
10558
|
return { content: [{ type: "text", text: msg8.advise.recorded }], details: undefined };
|
|
11174
10559
|
}
|
|
11175
10560
|
};
|
|
@@ -11205,18 +10590,15 @@ function toolCallLine2(part, context) {
|
|
|
11205
10590
|
function argumentText(key, value, context) {
|
|
11206
10591
|
const text = typeof value === "string" ? value : JSON.stringify(value) ?? "";
|
|
11207
10592
|
if (context === "full")
|
|
11208
|
-
return
|
|
11209
|
-
return BODY_KEYS.has(key) ? msg8.transcript.characters(text.length) :
|
|
10593
|
+
return oneLine(text);
|
|
10594
|
+
return BODY_KEYS.has(key) ? msg8.transcript.characters(text.length) : oneLine(text);
|
|
11210
10595
|
}
|
|
11211
10596
|
var BODY_KEYS = new Set(["content", "oldText", "newText", "old_text", "new_text", "edits"]);
|
|
11212
10597
|
function blocks(content) {
|
|
11213
10598
|
return Array.isArray(content) ? content.filter((part) => !!part && typeof part === "object") : [];
|
|
11214
10599
|
}
|
|
11215
|
-
function oneLine2(text) {
|
|
11216
|
-
return text.replace(/\s+/gu, " ").trim();
|
|
11217
|
-
}
|
|
11218
10600
|
function clipText(text, budget) {
|
|
11219
|
-
const single =
|
|
10601
|
+
const single = oneLine(text);
|
|
11220
10602
|
return single.length > budget ? `${single.slice(0, budget)}…` : single;
|
|
11221
10603
|
}
|
|
11222
10604
|
|
|
@@ -11265,8 +10647,8 @@ function registerWatcher(pi, dependencies = {}, subsession = false) {
|
|
|
11265
10647
|
ctx.ui.setStatus("watcher", ctx.ui.theme.fg("dim", msg8.statusLabel));
|
|
11266
10648
|
return owner;
|
|
11267
10649
|
};
|
|
11268
|
-
const speak = async (owner,
|
|
11269
|
-
const card = { note
|
|
10650
|
+
const speak = async (owner, note, turnIndex) => {
|
|
10651
|
+
const card = { note, turnIndex };
|
|
11270
10652
|
try {
|
|
11271
10653
|
await deliver(pi, owner.ctx, { customType: WATCHER_MESSAGE_TYPE, content: adviceMessage(card) });
|
|
11272
10654
|
} catch (error) {
|
|
@@ -11387,9 +10769,10 @@ var REGISTRARS = {
|
|
|
11387
10769
|
presets: registerPresets,
|
|
11388
10770
|
stats: registerStats,
|
|
11389
10771
|
claudeSub: registerClaudeSub,
|
|
11390
|
-
openaiNative: registerOpenAINative
|
|
10772
|
+
openaiNative: registerOpenAINative,
|
|
10773
|
+
statusbar: registerStatusBar
|
|
11391
10774
|
};
|
|
11392
|
-
var MAIN_ONLY = new Set(["header", "tools", "presets", "stats"]);
|
|
10775
|
+
var MAIN_ONLY = new Set(["header", "tools", "presets", "stats", "statusbar"]);
|
|
11393
10776
|
function registerFirecode(pi, role = "main") {
|
|
11394
10777
|
let seeding = seedForMainSession(role);
|
|
11395
10778
|
const { config, problems, featuresBroken } = loadConfig();
|
|
@@ -11402,13 +10785,12 @@ function registerFirecode(pi, role = "main") {
|
|
|
11402
10785
|
continue;
|
|
11403
10786
|
register(pi);
|
|
11404
10787
|
}
|
|
11405
|
-
if (config.features.statusbar !== false)
|
|
11406
|
-
registerStatusBar(pi, subsession);
|
|
11407
10788
|
if (config.features.watcher !== false)
|
|
11408
10789
|
registerWatcher(pi, {}, subsession);
|
|
11409
10790
|
if (config.features.master !== false)
|
|
11410
10791
|
registerMaster(pi, {}, subsession);
|
|
11411
|
-
|
|
10792
|
+
if (!subsession)
|
|
10793
|
+
registerHerdrProjection(pi);
|
|
11412
10794
|
registerReview(pi, reviewEnabled, featuresBroken);
|
|
11413
10795
|
if (problems.length === 0 && !seeding)
|
|
11414
10796
|
return;
|