zelari-code 1.27.2 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/kraken/executor.js +750 -52
- package/dist/cli/kraken/executor.js.map +1 -1
- package/dist/cli/kraken/graphMemory.js +133 -0
- package/dist/cli/kraken/graphMemory.js.map +1 -0
- package/dist/cli/kraken/graphStatus.js +59 -0
- package/dist/cli/kraken/graphStatus.js.map +1 -1
- package/dist/cli/kraken/planner.js +222 -21
- package/dist/cli/kraken/planner.js.map +1 -1
- package/dist/cli/main.bundled.js +1213 -292
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/runHeadless.js +41 -3
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/slashHandlers/krakenGraph.js +22 -4
- package/dist/cli/slashHandlers/krakenGraph.js.map +1 -1
- package/dist/cli/tools/taskTool.js +48 -3
- package/dist/cli/tools/taskTool.js.map +1 -1
- package/package.json +2 -2
package/dist/cli/main.bundled.js
CHANGED
|
@@ -1829,10 +1829,10 @@ function mergeDefs(...defs) {
|
|
|
1829
1829
|
function cloneDef(schema) {
|
|
1830
1830
|
return mergeDefs(schema._zod.def);
|
|
1831
1831
|
}
|
|
1832
|
-
function getElementAtPath(obj,
|
|
1833
|
-
if (!
|
|
1832
|
+
function getElementAtPath(obj, path44) {
|
|
1833
|
+
if (!path44)
|
|
1834
1834
|
return obj;
|
|
1835
|
-
return
|
|
1835
|
+
return path44.reduce((acc, key) => acc?.[key], obj);
|
|
1836
1836
|
}
|
|
1837
1837
|
function promiseAllObject(promisesObj) {
|
|
1838
1838
|
const keys = Object.keys(promisesObj);
|
|
@@ -2160,11 +2160,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
2160
2160
|
}
|
|
2161
2161
|
return false;
|
|
2162
2162
|
}
|
|
2163
|
-
function prefixIssues(
|
|
2163
|
+
function prefixIssues(path44, issues) {
|
|
2164
2164
|
return issues.map((iss) => {
|
|
2165
2165
|
var _a3;
|
|
2166
2166
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
2167
|
-
iss.path.unshift(
|
|
2167
|
+
iss.path.unshift(path44);
|
|
2168
2168
|
return iss;
|
|
2169
2169
|
});
|
|
2170
2170
|
}
|
|
@@ -2382,16 +2382,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2382
2382
|
}
|
|
2383
2383
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
2384
2384
|
const fieldErrors = { _errors: [] };
|
|
2385
|
-
const processError = (error52,
|
|
2385
|
+
const processError = (error52, path44 = []) => {
|
|
2386
2386
|
for (const issue2 of error52.issues) {
|
|
2387
2387
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2388
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2388
|
+
issue2.errors.map((issues) => processError({ issues }, [...path44, ...issue2.path]));
|
|
2389
2389
|
} else if (issue2.code === "invalid_key") {
|
|
2390
|
-
processError({ issues: issue2.issues }, [...
|
|
2390
|
+
processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
|
|
2391
2391
|
} else if (issue2.code === "invalid_element") {
|
|
2392
|
-
processError({ issues: issue2.issues }, [...
|
|
2392
|
+
processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
|
|
2393
2393
|
} else {
|
|
2394
|
-
const fullpath = [...
|
|
2394
|
+
const fullpath = [...path44, ...issue2.path];
|
|
2395
2395
|
if (fullpath.length === 0) {
|
|
2396
2396
|
fieldErrors._errors.push(mapper(issue2));
|
|
2397
2397
|
} else {
|
|
@@ -2418,17 +2418,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2418
2418
|
}
|
|
2419
2419
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
2420
2420
|
const result = { errors: [] };
|
|
2421
|
-
const processError = (error52,
|
|
2421
|
+
const processError = (error52, path44 = []) => {
|
|
2422
2422
|
var _a3, _b;
|
|
2423
2423
|
for (const issue2 of error52.issues) {
|
|
2424
2424
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2425
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2425
|
+
issue2.errors.map((issues) => processError({ issues }, [...path44, ...issue2.path]));
|
|
2426
2426
|
} else if (issue2.code === "invalid_key") {
|
|
2427
|
-
processError({ issues: issue2.issues }, [...
|
|
2427
|
+
processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
|
|
2428
2428
|
} else if (issue2.code === "invalid_element") {
|
|
2429
|
-
processError({ issues: issue2.issues }, [...
|
|
2429
|
+
processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
|
|
2430
2430
|
} else {
|
|
2431
|
-
const fullpath = [...
|
|
2431
|
+
const fullpath = [...path44, ...issue2.path];
|
|
2432
2432
|
if (fullpath.length === 0) {
|
|
2433
2433
|
result.errors.push(mapper(issue2));
|
|
2434
2434
|
continue;
|
|
@@ -2460,8 +2460,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2460
2460
|
}
|
|
2461
2461
|
function toDotPath(_path) {
|
|
2462
2462
|
const segs = [];
|
|
2463
|
-
const
|
|
2464
|
-
for (const seg of
|
|
2463
|
+
const path44 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
2464
|
+
for (const seg of path44) {
|
|
2465
2465
|
if (typeof seg === "number")
|
|
2466
2466
|
segs.push(`[${seg}]`);
|
|
2467
2467
|
else if (typeof seg === "symbol")
|
|
@@ -15964,13 +15964,13 @@ function resolveRef(ref, ctx) {
|
|
|
15964
15964
|
if (!ref.startsWith("#")) {
|
|
15965
15965
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
15966
15966
|
}
|
|
15967
|
-
const
|
|
15968
|
-
if (
|
|
15967
|
+
const path44 = ref.slice(1).split("/").filter(Boolean);
|
|
15968
|
+
if (path44.length === 0) {
|
|
15969
15969
|
return ctx.rootSchema;
|
|
15970
15970
|
}
|
|
15971
15971
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
15972
|
-
if (
|
|
15973
|
-
const key =
|
|
15972
|
+
if (path44[0] === defsKey) {
|
|
15973
|
+
const key = path44[1];
|
|
15974
15974
|
if (!key || !ctx.defs[key]) {
|
|
15975
15975
|
throw new Error(`Reference not found: ${ref}`);
|
|
15976
15976
|
}
|
|
@@ -18134,11 +18134,11 @@ var init_tools = __esm({
|
|
|
18134
18134
|
if (!ctx.addDocument)
|
|
18135
18135
|
return "Knowledge vault tool not available.";
|
|
18136
18136
|
const title = args["title"] || "New Document";
|
|
18137
|
-
const
|
|
18137
|
+
const path44 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
|
|
18138
18138
|
const content = args["content"] || "";
|
|
18139
18139
|
const tags = args["tags"] || [];
|
|
18140
18140
|
ctx.addDocument({
|
|
18141
|
-
path:
|
|
18141
|
+
path: path44,
|
|
18142
18142
|
title,
|
|
18143
18143
|
content,
|
|
18144
18144
|
format: "markdown",
|
|
@@ -18147,7 +18147,7 @@ var init_tools = __esm({
|
|
|
18147
18147
|
workspaceId: ctx.workspaceId
|
|
18148
18148
|
});
|
|
18149
18149
|
ctx.addActivity("vault", "created document", title);
|
|
18150
|
-
return `Document "${title}" created at "${
|
|
18150
|
+
return `Document "${title}" created at "${path44}".`;
|
|
18151
18151
|
}
|
|
18152
18152
|
}
|
|
18153
18153
|
];
|
|
@@ -21839,11 +21839,11 @@ var init_synthesisAudit = __esm({
|
|
|
21839
21839
|
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
21840
21840
|
import { join as join2 } from "node:path";
|
|
21841
21841
|
function loadNfrSpec(zelariRoot) {
|
|
21842
|
-
const
|
|
21843
|
-
if (!existsSync6(
|
|
21842
|
+
const path44 = join2(zelariRoot, "nfr-spec.json");
|
|
21843
|
+
if (!existsSync6(path44))
|
|
21844
21844
|
return null;
|
|
21845
21845
|
try {
|
|
21846
|
-
const raw = JSON.parse(readFileSync5(
|
|
21846
|
+
const raw = JSON.parse(readFileSync5(path44, "utf8"));
|
|
21847
21847
|
if (raw.version !== 1 || !Array.isArray(raw.targets))
|
|
21848
21848
|
return null;
|
|
21849
21849
|
return raw;
|
|
@@ -24149,9 +24149,9 @@ var init_types3 = __esm({
|
|
|
24149
24149
|
import { readFileSync as readFileSync10 } from "node:fs";
|
|
24150
24150
|
import { join as join8 } from "node:path";
|
|
24151
24151
|
function readLessonsDeduped(zelariRoot) {
|
|
24152
|
-
const
|
|
24152
|
+
const path44 = join8(zelariRoot, LESSONS_FILE);
|
|
24153
24153
|
try {
|
|
24154
|
-
const raw = readFileSync10(
|
|
24154
|
+
const raw = readFileSync10(path44, "utf8");
|
|
24155
24155
|
const byId = /* @__PURE__ */ new Map();
|
|
24156
24156
|
for (const line of raw.split(/\r?\n/)) {
|
|
24157
24157
|
if (!line.trim())
|
|
@@ -24252,8 +24252,8 @@ function keywordsFrom(check2, signature) {
|
|
|
24252
24252
|
return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
|
|
24253
24253
|
}
|
|
24254
24254
|
function writeLesson(zelariRoot, lesson) {
|
|
24255
|
-
const
|
|
24256
|
-
appendFileSync(
|
|
24255
|
+
const path44 = join9(zelariRoot, LESSONS_FILE);
|
|
24256
|
+
appendFileSync(path44, `${JSON.stringify(lesson)}
|
|
24257
24257
|
`, "utf8");
|
|
24258
24258
|
}
|
|
24259
24259
|
function findSimilar(lessons, signature) {
|
|
@@ -24866,9 +24866,9 @@ function findCycle(nodes) {
|
|
|
24866
24866
|
if (color.get(start) !== WHITE)
|
|
24867
24867
|
continue;
|
|
24868
24868
|
const stack = [[start, 0]];
|
|
24869
|
-
const
|
|
24869
|
+
const path44 = [];
|
|
24870
24870
|
color.set(start, GRAY);
|
|
24871
|
-
|
|
24871
|
+
path44.push(start);
|
|
24872
24872
|
while (stack.length > 0) {
|
|
24873
24873
|
const top = stack[stack.length - 1];
|
|
24874
24874
|
const [id, idx] = top;
|
|
@@ -24881,17 +24881,17 @@ function findCycle(nodes) {
|
|
|
24881
24881
|
continue;
|
|
24882
24882
|
const c = color.get(dep);
|
|
24883
24883
|
if (c === GRAY) {
|
|
24884
|
-
const at =
|
|
24885
|
-
return [...
|
|
24884
|
+
const at = path44.indexOf(dep);
|
|
24885
|
+
return [...path44.slice(at), dep];
|
|
24886
24886
|
}
|
|
24887
24887
|
if (c === WHITE) {
|
|
24888
24888
|
color.set(dep, GRAY);
|
|
24889
|
-
|
|
24889
|
+
path44.push(dep);
|
|
24890
24890
|
stack.push([dep, 0]);
|
|
24891
24891
|
}
|
|
24892
24892
|
} else {
|
|
24893
24893
|
color.set(id, BLACK);
|
|
24894
|
-
|
|
24894
|
+
path44.pop();
|
|
24895
24895
|
stack.pop();
|
|
24896
24896
|
}
|
|
24897
24897
|
}
|
|
@@ -25074,14 +25074,6 @@ function canRunParallel(a, b) {
|
|
|
25074
25074
|
}
|
|
25075
25075
|
return false;
|
|
25076
25076
|
}
|
|
25077
|
-
function selectParallelWave(candidates) {
|
|
25078
|
-
const wave = [];
|
|
25079
|
-
for (const node of candidates) {
|
|
25080
|
-
if (wave.every((w) => canRunParallel(w, node)))
|
|
25081
|
-
wave.push(node);
|
|
25082
|
-
}
|
|
25083
|
-
return wave;
|
|
25084
|
-
}
|
|
25085
25077
|
var READ_ONLY_KINDS, WRITER_KINDS;
|
|
25086
25078
|
var init_conflict = __esm({
|
|
25087
25079
|
"packages/core/dist/kraken/conflict.js"() {
|
|
@@ -25091,12 +25083,49 @@ var init_conflict = __esm({
|
|
|
25091
25083
|
}
|
|
25092
25084
|
});
|
|
25093
25085
|
|
|
25086
|
+
// packages/core/dist/kraken/verdict.js
|
|
25087
|
+
function parseVerifyVerdict(text) {
|
|
25088
|
+
const source = typeof text === "string" ? text : "";
|
|
25089
|
+
if (source.trim() === "")
|
|
25090
|
+
return { verdict: "unknown", findings: "" };
|
|
25091
|
+
VERDICT_LINE.lastIndex = 0;
|
|
25092
|
+
let match;
|
|
25093
|
+
let last = null;
|
|
25094
|
+
while ((match = VERDICT_LINE.exec(source)) !== null) {
|
|
25095
|
+
last = match;
|
|
25096
|
+
if (match.index === VERDICT_LINE.lastIndex)
|
|
25097
|
+
VERDICT_LINE.lastIndex += 1;
|
|
25098
|
+
}
|
|
25099
|
+
if (!last) {
|
|
25100
|
+
return { verdict: "unknown", findings: capFindings(source) };
|
|
25101
|
+
}
|
|
25102
|
+
const verdict = last[1].toUpperCase() === "FAIL" ? "fail" : "pass";
|
|
25103
|
+
const findings = capFindings(source.slice(0, last.index));
|
|
25104
|
+
return { verdict, findings };
|
|
25105
|
+
}
|
|
25106
|
+
function capFindings(raw) {
|
|
25107
|
+
const trimmed = raw.trim();
|
|
25108
|
+
if (trimmed.length <= MAX_FINDINGS_CHARS)
|
|
25109
|
+
return trimmed;
|
|
25110
|
+
return `${trimmed.slice(0, MAX_FINDINGS_CHARS)}
|
|
25111
|
+
\u2026 [truncated]`;
|
|
25112
|
+
}
|
|
25113
|
+
var MAX_FINDINGS_CHARS, VERDICT_LINE;
|
|
25114
|
+
var init_verdict = __esm({
|
|
25115
|
+
"packages/core/dist/kraken/verdict.js"() {
|
|
25116
|
+
"use strict";
|
|
25117
|
+
MAX_FINDINGS_CHARS = 2800;
|
|
25118
|
+
VERDICT_LINE = /^[\s>*_-]*VERDICT[\s*_]*:[\s*_]*(PASS|FAIL)\b/gim;
|
|
25119
|
+
}
|
|
25120
|
+
});
|
|
25121
|
+
|
|
25094
25122
|
// packages/core/dist/kraken/index.js
|
|
25095
25123
|
var init_kraken = __esm({
|
|
25096
25124
|
"packages/core/dist/kraken/index.js"() {
|
|
25097
25125
|
"use strict";
|
|
25098
25126
|
init_graph();
|
|
25099
25127
|
init_conflict();
|
|
25128
|
+
init_verdict();
|
|
25100
25129
|
}
|
|
25101
25130
|
});
|
|
25102
25131
|
|
|
@@ -25118,8 +25147,11 @@ var init_dist = __esm({
|
|
|
25118
25147
|
// src/cli/kraken/graphStatus.ts
|
|
25119
25148
|
var graphStatus_exports = {};
|
|
25120
25149
|
__export(graphStatus_exports, {
|
|
25150
|
+
DEFAULT_DIGEST_RESULT_CHARS: () => DEFAULT_DIGEST_RESULT_CHARS,
|
|
25121
25151
|
endKrakenGraphLive: () => endKrakenGraphLive,
|
|
25152
|
+
formatDuration: () => formatDuration2,
|
|
25122
25153
|
formatKrakenGraphAscii: () => formatKrakenGraphAscii,
|
|
25154
|
+
formatKrakenGraphDigest: () => formatKrakenGraphDigest,
|
|
25123
25155
|
formatKrakenGraphSummary: () => formatKrakenGraphSummary,
|
|
25124
25156
|
getKrakenGraphLive: () => getKrakenGraphLive,
|
|
25125
25157
|
resetKrakenGraphLive: () => resetKrakenGraphLive,
|
|
@@ -25147,6 +25179,46 @@ function formatKrakenGraphAscii(graph) {
|
|
|
25147
25179
|
if (lines.length === 0) return summary;
|
|
25148
25180
|
return [summary, ...lines].join("\n");
|
|
25149
25181
|
}
|
|
25182
|
+
function formatDuration2(ms) {
|
|
25183
|
+
if (!Number.isFinite(ms) || ms < 0) return "?";
|
|
25184
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
25185
|
+
const totalSeconds = Math.round(ms / 1e3);
|
|
25186
|
+
if (totalSeconds < 60) return `${totalSeconds}s`;
|
|
25187
|
+
const m = Math.floor(totalSeconds / 60);
|
|
25188
|
+
const s = totalSeconds % 60;
|
|
25189
|
+
return s === 0 ? `${m}m` : `${m}m${s}s`;
|
|
25190
|
+
}
|
|
25191
|
+
function formatKrakenGraphDigest(graph, opts = {}) {
|
|
25192
|
+
const maxChars = opts.maxResultChars ?? DEFAULT_DIGEST_RESULT_CHARS;
|
|
25193
|
+
const durations = opts.durationsMs ?? {};
|
|
25194
|
+
const ordered = topoLevels(graph).flat();
|
|
25195
|
+
for (const id of graph.nodes.keys()) {
|
|
25196
|
+
if (!ordered.includes(id)) ordered.push(id);
|
|
25197
|
+
}
|
|
25198
|
+
const lines = [];
|
|
25199
|
+
for (const id of ordered) {
|
|
25200
|
+
const n = graph.nodes.get(id);
|
|
25201
|
+
if (!n) continue;
|
|
25202
|
+
const took = durations[id] !== void 0 ? `, ${formatDuration2(durations[id])}` : "";
|
|
25203
|
+
const detail = n.status === "error" ? n.error : n.result;
|
|
25204
|
+
const firstLine2 = (detail ?? "").trim().split("\n")[0] ?? "";
|
|
25205
|
+
const body = firstLine2.length > maxChars ? `${firstLine2.slice(0, maxChars)}\u2026` : firstLine2;
|
|
25206
|
+
lines.push(
|
|
25207
|
+
`[${STATUS_ICON[n.status]}] ${n.id} (${n.kind}${took})${body ? ` \u2014 ${body}` : ""}`
|
|
25208
|
+
);
|
|
25209
|
+
}
|
|
25210
|
+
const unresolved = opts.unresolvedFindings ?? [];
|
|
25211
|
+
if (unresolved.length > 0) {
|
|
25212
|
+
lines.push("", "unresolved verify findings:");
|
|
25213
|
+
for (const u of unresolved) {
|
|
25214
|
+
const why = u.reason === "fail" ? "rejected, rework budget spent" : "no parseable verdict";
|
|
25215
|
+
const detail = u.findings.trim().split("\n")[0]?.trim() ?? "";
|
|
25216
|
+
const body = detail.length > maxChars ? `${detail.slice(0, maxChars)}\u2026` : detail;
|
|
25217
|
+
lines.push(` ${u.nodeId} (${why})${body ? ` \u2014 ${body}` : ""}`);
|
|
25218
|
+
}
|
|
25219
|
+
}
|
|
25220
|
+
return lines.join("\n");
|
|
25221
|
+
}
|
|
25150
25222
|
function state() {
|
|
25151
25223
|
return globalThis;
|
|
25152
25224
|
}
|
|
@@ -25195,7 +25267,7 @@ function formatKrakenGraphSummary() {
|
|
|
25195
25267
|
if (live.error) parts.push(`${live.error}\u2717`);
|
|
25196
25268
|
return `graph ${parts.join(" \xB7 ")}`;
|
|
25197
25269
|
}
|
|
25198
|
-
var STATUS_ICON;
|
|
25270
|
+
var STATUS_ICON, DEFAULT_DIGEST_RESULT_CHARS;
|
|
25199
25271
|
var init_graphStatus = __esm({
|
|
25200
25272
|
"src/cli/kraken/graphStatus.ts"() {
|
|
25201
25273
|
"use strict";
|
|
@@ -25207,6 +25279,7 @@ var init_graphStatus = __esm({
|
|
|
25207
25279
|
error: "\u2717",
|
|
25208
25280
|
skipped: "\xBB"
|
|
25209
25281
|
};
|
|
25282
|
+
DEFAULT_DIGEST_RESULT_CHARS = 200;
|
|
25210
25283
|
}
|
|
25211
25284
|
});
|
|
25212
25285
|
|
|
@@ -25700,9 +25773,9 @@ function spillToolOutput(fullText, meta3) {
|
|
|
25700
25773
|
const rnd = randomBytes(3).toString("hex");
|
|
25701
25774
|
const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
|
|
25702
25775
|
const file2 = `${stamp}-${safeTool}-${hash3}-${rnd}.txt`;
|
|
25703
|
-
const
|
|
25704
|
-
writeFileSync10(
|
|
25705
|
-
return
|
|
25776
|
+
const path44 = join11(dir, file2);
|
|
25777
|
+
writeFileSync10(path44, fullText, "utf8");
|
|
25778
|
+
return path44;
|
|
25706
25779
|
} catch {
|
|
25707
25780
|
return null;
|
|
25708
25781
|
}
|
|
@@ -25748,10 +25821,10 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
|
|
|
25748
25821
|
${tail}`;
|
|
25749
25822
|
}
|
|
25750
25823
|
if (doSpill) {
|
|
25751
|
-
const
|
|
25752
|
-
if (
|
|
25824
|
+
const path44 = spillToolOutput(text, { toolName: opts.toolName });
|
|
25825
|
+
if (path44) {
|
|
25753
25826
|
const spillNote = `
|
|
25754
|
-
\u2026 [full output spilled to: ${
|
|
25827
|
+
\u2026 [full output spilled to: ${path44} \u2014 re-read with read_file if you need the complete text] \u2026`;
|
|
25755
25828
|
if (preview.includes("] \u2026\n")) {
|
|
25756
25829
|
preview = preview.replace("] \u2026\n", `] \u2026${spillNote}
|
|
25757
25830
|
`);
|
|
@@ -26581,11 +26654,20 @@ function maxToolCallsForThoroughness(thoroughness, agent) {
|
|
|
26581
26654
|
if (thoroughness === "deep") return 12;
|
|
26582
26655
|
return 6;
|
|
26583
26656
|
}
|
|
26584
|
-
async function runSubAgent(harness) {
|
|
26657
|
+
async function runSubAgent(harness, opts = {}) {
|
|
26658
|
+
const { signal } = opts;
|
|
26585
26659
|
let current = "";
|
|
26586
26660
|
let lastCompleted = "";
|
|
26587
26661
|
let error51;
|
|
26662
|
+
if (signal?.aborted) return { result: "", aborted: true };
|
|
26588
26663
|
for await (const ev of harness.run()) {
|
|
26664
|
+
if (signal?.aborted) {
|
|
26665
|
+
return {
|
|
26666
|
+
result: (lastCompleted || current).trim(),
|
|
26667
|
+
...error51 ? { error: error51 } : {},
|
|
26668
|
+
aborted: true
|
|
26669
|
+
};
|
|
26670
|
+
}
|
|
26589
26671
|
switch (ev.type) {
|
|
26590
26672
|
case "message_start":
|
|
26591
26673
|
current = "";
|
|
@@ -26612,7 +26694,7 @@ async function runTentacle(opts) {
|
|
|
26612
26694
|
const started = Date.now();
|
|
26613
26695
|
const g = globalThis;
|
|
26614
26696
|
let worktree = null;
|
|
26615
|
-
let effectiveCwd = parentCwd;
|
|
26697
|
+
let effectiveCwd = opts.cwdOverride || parentCwd;
|
|
26616
26698
|
const wantWt = agent === "general" && deps.allowWorktree !== false && isKrakenWorktreeEnabled();
|
|
26617
26699
|
if (wantWt) {
|
|
26618
26700
|
try {
|
|
@@ -26715,8 +26797,31 @@ async function runTentacle(opts) {
|
|
|
26715
26797
|
error: `task: failed to start sub-agent \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
26716
26798
|
};
|
|
26717
26799
|
}
|
|
26718
|
-
const { result, error: error51 } = await runSubAgent(harness
|
|
26800
|
+
const { result, error: error51, aborted: aborted2 } = await runSubAgent(harness, {
|
|
26801
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
26802
|
+
});
|
|
26719
26803
|
const durationMs = Date.now() - started;
|
|
26804
|
+
if (aborted2) {
|
|
26805
|
+
if (worktree && !shouldKeepWorktree()) await cleanupKrakenWorktree(worktree);
|
|
26806
|
+
appendKrakenRadio(parentCwd, sessionId, {
|
|
26807
|
+
kind: "error",
|
|
26808
|
+
agent,
|
|
26809
|
+
thoroughness,
|
|
26810
|
+
description: args.description,
|
|
26811
|
+
detail: "cancelled: node timeout",
|
|
26812
|
+
model: sub.model,
|
|
26813
|
+
worktree: worktree?.path ?? null,
|
|
26814
|
+
durationMs,
|
|
26815
|
+
ok: false
|
|
26816
|
+
});
|
|
26817
|
+
krakenTentacleEnd(liveId, {
|
|
26818
|
+
ok: false,
|
|
26819
|
+
model: sub.model,
|
|
26820
|
+
detail: "cancelled",
|
|
26821
|
+
durationMs
|
|
26822
|
+
});
|
|
26823
|
+
return { ok: false, agent, error: "task: sub-agent cancelled (node timeout)", cancelled: true };
|
|
26824
|
+
}
|
|
26720
26825
|
if (!result) {
|
|
26721
26826
|
if (worktree) await cleanupKrakenWorktree(worktree);
|
|
26722
26827
|
appendKrakenRadio(parentCwd, sessionId, {
|
|
@@ -28660,21 +28765,21 @@ function normalizeAuth(auth) {
|
|
|
28660
28765
|
return "agent";
|
|
28661
28766
|
}
|
|
28662
28767
|
function readSecrets() {
|
|
28663
|
-
const
|
|
28664
|
-
if (!existsSync17(
|
|
28768
|
+
const path44 = getSshSecretsPath();
|
|
28769
|
+
if (!existsSync17(path44)) return {};
|
|
28665
28770
|
try {
|
|
28666
|
-
return JSON.parse(readFileSync16(
|
|
28771
|
+
return JSON.parse(readFileSync16(path44, "utf8"));
|
|
28667
28772
|
} catch {
|
|
28668
28773
|
return {};
|
|
28669
28774
|
}
|
|
28670
28775
|
}
|
|
28671
28776
|
function writeSecrets(data) {
|
|
28672
|
-
const
|
|
28673
|
-
mkdirSync9(dirname2(
|
|
28674
|
-
writeFileSync11(
|
|
28777
|
+
const path44 = getSshSecretsPath();
|
|
28778
|
+
mkdirSync9(dirname2(path44), { recursive: true });
|
|
28779
|
+
writeFileSync11(path44, `${JSON.stringify(data, null, 2)}
|
|
28675
28780
|
`, "utf8");
|
|
28676
28781
|
try {
|
|
28677
|
-
chmodSync(
|
|
28782
|
+
chmodSync(path44, 384);
|
|
28678
28783
|
} catch {
|
|
28679
28784
|
}
|
|
28680
28785
|
}
|
|
@@ -28703,10 +28808,10 @@ function deleteSshPassword(id) {
|
|
|
28703
28808
|
writeSecrets({ passwords });
|
|
28704
28809
|
}
|
|
28705
28810
|
function readStore2() {
|
|
28706
|
-
const
|
|
28707
|
-
if (!existsSync17(
|
|
28811
|
+
const path44 = getSshTargetsPath();
|
|
28812
|
+
if (!existsSync17(path44)) return [];
|
|
28708
28813
|
try {
|
|
28709
|
-
const parsed = JSON.parse(readFileSync16(
|
|
28814
|
+
const parsed = JSON.parse(readFileSync16(path44, "utf8"));
|
|
28710
28815
|
const list = Array.isArray(parsed.targets) ? parsed.targets : [];
|
|
28711
28816
|
return list.filter(
|
|
28712
28817
|
(t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
|
|
@@ -28721,11 +28826,11 @@ function readStore2() {
|
|
|
28721
28826
|
}
|
|
28722
28827
|
}
|
|
28723
28828
|
function writeStore2(targets) {
|
|
28724
|
-
const
|
|
28725
|
-
mkdirSync9(dirname2(
|
|
28829
|
+
const path44 = getSshTargetsPath();
|
|
28830
|
+
mkdirSync9(dirname2(path44), { recursive: true });
|
|
28726
28831
|
const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
|
|
28727
28832
|
writeFileSync11(
|
|
28728
|
-
|
|
28833
|
+
path44,
|
|
28729
28834
|
`${JSON.stringify({ targets: clean }, null, 2)}
|
|
28730
28835
|
`,
|
|
28731
28836
|
"utf8"
|
|
@@ -28971,11 +29076,11 @@ function formatSshTargetsForPrompt() {
|
|
|
28971
29076
|
];
|
|
28972
29077
|
for (const t of targets) {
|
|
28973
29078
|
const tags = t.tags?.length ? ` tags=[${t.tags.join(",")}]` : "";
|
|
28974
|
-
const
|
|
29079
|
+
const path44 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
|
|
28975
29080
|
const allow = t.allowedCommands?.length ? ` allowed=${t.allowedCommands.join("|")}` : " allowed=status-only";
|
|
28976
29081
|
const auth = t.auth === "password" ? " auth=password" : t.auth === "keyPath" ? " auth=key" : " auth=agent";
|
|
28977
29082
|
lines.push(
|
|
28978
|
-
`- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${
|
|
29083
|
+
`- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path44}${tags}${allow}`
|
|
28979
29084
|
);
|
|
28980
29085
|
}
|
|
28981
29086
|
return lines.join("\n");
|
|
@@ -31827,28 +31932,28 @@ var init_storage = __esm({
|
|
|
31827
31932
|
VALID_SCALARS = /^(true|false|null|~)$/i;
|
|
31828
31933
|
Storage = class {
|
|
31829
31934
|
/** Read a Markdown file with frontmatter. Throws if not found. */
|
|
31830
|
-
read(
|
|
31831
|
-
if (!existsSync25(
|
|
31832
|
-
throw new Error(`File not found: ${
|
|
31935
|
+
read(path44) {
|
|
31936
|
+
if (!existsSync25(path44)) {
|
|
31937
|
+
throw new Error(`File not found: ${path44}`);
|
|
31833
31938
|
}
|
|
31834
|
-
const md = readFileSync21(
|
|
31939
|
+
const md = readFileSync21(path44, "utf8");
|
|
31835
31940
|
return parseFrontmatter(md);
|
|
31836
31941
|
}
|
|
31837
31942
|
/** Read a Markdown file; returns null if not found. */
|
|
31838
|
-
readIfExists(
|
|
31839
|
-
if (!existsSync25(
|
|
31840
|
-
return this.read(
|
|
31943
|
+
readIfExists(path44) {
|
|
31944
|
+
if (!existsSync25(path44)) return null;
|
|
31945
|
+
return this.read(path44);
|
|
31841
31946
|
}
|
|
31842
31947
|
/**
|
|
31843
31948
|
* Write a Markdown file atomically (tmp + rename). Creates parent dirs.
|
|
31844
31949
|
* The meta object is serialized as YAML frontmatter; body as Markdown.
|
|
31845
31950
|
*/
|
|
31846
|
-
write(
|
|
31847
|
-
mkdirSync11(dirname4(
|
|
31848
|
-
const tmp =
|
|
31951
|
+
write(path44, meta3, body) {
|
|
31952
|
+
mkdirSync11(dirname4(path44), { recursive: true });
|
|
31953
|
+
const tmp = path44 + ".tmp-" + process.pid;
|
|
31849
31954
|
const md = serializeFrontmatter(meta3, body);
|
|
31850
31955
|
writeFileSync13(tmp, md, "utf8");
|
|
31851
|
-
renameSync2(tmp,
|
|
31956
|
+
renameSync2(tmp, path44);
|
|
31852
31957
|
}
|
|
31853
31958
|
/** List all .md files in a directory (non-recursive). */
|
|
31854
31959
|
listMarkdown(dir) {
|
|
@@ -31926,8 +32031,8 @@ function readPlan(ctx) {
|
|
|
31926
32031
|
} catch {
|
|
31927
32032
|
}
|
|
31928
32033
|
}
|
|
31929
|
-
const
|
|
31930
|
-
const doc = ctx.storage.readIfExists(
|
|
32034
|
+
const path44 = workspaceFile(ctx.rootDir, "plan");
|
|
32035
|
+
const doc = ctx.storage.readIfExists(path44);
|
|
31931
32036
|
if (!doc) return { phases: [], tasks: [], milestones: [] };
|
|
31932
32037
|
const meta3 = doc.meta;
|
|
31933
32038
|
return {
|
|
@@ -32092,7 +32197,7 @@ function addMilestoneRecord(ctx, summary, input) {
|
|
|
32092
32197
|
dueDate: input.dueDate,
|
|
32093
32198
|
targetVersion: version2
|
|
32094
32199
|
});
|
|
32095
|
-
const
|
|
32200
|
+
const path44 = join23(ctx.rootDir, "milestones", `${id}.md`);
|
|
32096
32201
|
const meta3 = {
|
|
32097
32202
|
kind: "milestone",
|
|
32098
32203
|
id,
|
|
@@ -32109,7 +32214,7 @@ function addMilestoneRecord(ctx, summary, input) {
|
|
|
32109
32214
|
`Target version: ${version2}`,
|
|
32110
32215
|
""
|
|
32111
32216
|
].join("\n");
|
|
32112
|
-
ctx.storage.write(
|
|
32217
|
+
ctx.storage.write(path44, meta3, body);
|
|
32113
32218
|
return { id, created: true };
|
|
32114
32219
|
}
|
|
32115
32220
|
function readPlanSummary(ctx) {
|
|
@@ -32313,7 +32418,7 @@ function addIdeaStub(ctx) {
|
|
|
32313
32418
|
const tags = args["tags"] ?? [];
|
|
32314
32419
|
const category = args["category"] ?? "General";
|
|
32315
32420
|
const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
|
|
32316
|
-
const
|
|
32421
|
+
const path44 = workspaceArtifact(ctx.rootDir, "decisions", id);
|
|
32317
32422
|
const meta3 = {
|
|
32318
32423
|
kind: "adr",
|
|
32319
32424
|
status: "proposed",
|
|
@@ -32339,7 +32444,7 @@ function addIdeaStub(ctx) {
|
|
|
32339
32444
|
...consequences.map((c) => `- ${c}`),
|
|
32340
32445
|
""
|
|
32341
32446
|
].join("\n");
|
|
32342
|
-
ctx.storage.write(
|
|
32447
|
+
ctx.storage.write(path44, meta3, body);
|
|
32343
32448
|
return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
|
|
32344
32449
|
});
|
|
32345
32450
|
}
|
|
@@ -32421,14 +32526,14 @@ function createDocumentStub(ctx) {
|
|
|
32421
32526
|
ctx.storage.write(risksPath, riskMeta, content);
|
|
32422
32527
|
return `Document "${title}" created at risks.md (workspace root).`;
|
|
32423
32528
|
}
|
|
32424
|
-
const
|
|
32529
|
+
const path44 = workspaceArtifact(ctx.rootDir, "docs", slug);
|
|
32425
32530
|
const meta3 = {
|
|
32426
32531
|
kind: "doc",
|
|
32427
32532
|
id: slug,
|
|
32428
32533
|
date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
32429
32534
|
tags
|
|
32430
32535
|
};
|
|
32431
|
-
ctx.storage.write(
|
|
32536
|
+
ctx.storage.write(path44, meta3, content);
|
|
32432
32537
|
return `Document "${title}" created at docs/${slug}.md.`;
|
|
32433
32538
|
});
|
|
32434
32539
|
}
|
|
@@ -32972,10 +33077,10 @@ function getUserMcpPath() {
|
|
|
32972
33077
|
function getProjectMcpPath(projectRoot) {
|
|
32973
33078
|
return join24(projectRoot, ".zelari", "mcp.json");
|
|
32974
33079
|
}
|
|
32975
|
-
function readFile2(
|
|
32976
|
-
if (!existsSync28(
|
|
33080
|
+
function readFile2(path44) {
|
|
33081
|
+
if (!existsSync28(path44)) return {};
|
|
32977
33082
|
try {
|
|
32978
|
-
const parsed = JSON.parse(readFileSync23(
|
|
33083
|
+
const parsed = JSON.parse(readFileSync23(path44, "utf8"));
|
|
32979
33084
|
const out = {};
|
|
32980
33085
|
for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
|
|
32981
33086
|
if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
|
|
@@ -32991,10 +33096,10 @@ function readFile2(path43) {
|
|
|
32991
33096
|
return {};
|
|
32992
33097
|
}
|
|
32993
33098
|
}
|
|
32994
|
-
function writeFile(
|
|
32995
|
-
mkdirSync13(dirname6(
|
|
33099
|
+
function writeFile(path44, servers) {
|
|
33100
|
+
mkdirSync13(dirname6(path44), { recursive: true });
|
|
32996
33101
|
const body = { mcpServers: servers };
|
|
32997
|
-
writeFileSync15(
|
|
33102
|
+
writeFileSync15(path44, `${JSON.stringify(body, null, 2)}
|
|
32998
33103
|
`, "utf8");
|
|
32999
33104
|
}
|
|
33000
33105
|
function listMcpServers(projectRoot) {
|
|
@@ -33027,9 +33132,9 @@ function upsertMcpServer(opts) {
|
|
|
33027
33132
|
if (!opts.config.command?.trim()) {
|
|
33028
33133
|
return { ok: false, error: "command is required" };
|
|
33029
33134
|
}
|
|
33030
|
-
let
|
|
33135
|
+
let path44;
|
|
33031
33136
|
if (opts.scope === "user") {
|
|
33032
|
-
|
|
33137
|
+
path44 = getUserMcpPath();
|
|
33033
33138
|
} else {
|
|
33034
33139
|
const root = opts.projectRoot?.trim();
|
|
33035
33140
|
if (!root) {
|
|
@@ -33038,30 +33143,30 @@ function upsertMcpServer(opts) {
|
|
|
33038
33143
|
error: "projectRoot required for project scope (Open Folder first)"
|
|
33039
33144
|
};
|
|
33040
33145
|
}
|
|
33041
|
-
|
|
33146
|
+
path44 = getProjectMcpPath(root);
|
|
33042
33147
|
}
|
|
33043
|
-
const current = readFile2(
|
|
33148
|
+
const current = readFile2(path44);
|
|
33044
33149
|
current[name] = {
|
|
33045
33150
|
command: opts.config.command.trim(),
|
|
33046
33151
|
args: opts.config.args,
|
|
33047
33152
|
env: opts.config.env,
|
|
33048
33153
|
enabled: opts.config.enabled !== false
|
|
33049
33154
|
};
|
|
33050
|
-
writeFile(
|
|
33051
|
-
return { ok: true, path:
|
|
33155
|
+
writeFile(path44, current);
|
|
33156
|
+
return { ok: true, path: path44 };
|
|
33052
33157
|
}
|
|
33053
33158
|
function removeMcpServer(opts) {
|
|
33054
|
-
const
|
|
33055
|
-
if (!
|
|
33159
|
+
const path44 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
|
|
33160
|
+
if (!path44) {
|
|
33056
33161
|
return { ok: false, error: "projectRoot required for project scope" };
|
|
33057
33162
|
}
|
|
33058
|
-
const current = readFile2(
|
|
33163
|
+
const current = readFile2(path44);
|
|
33059
33164
|
if (!(opts.name in current)) {
|
|
33060
|
-
return { ok: false, error: `Server "${opts.name}" not found in ${
|
|
33165
|
+
return { ok: false, error: `Server "${opts.name}" not found in ${path44}` };
|
|
33061
33166
|
}
|
|
33062
33167
|
delete current[opts.name];
|
|
33063
|
-
writeFile(
|
|
33064
|
-
return { ok: true, path:
|
|
33168
|
+
writeFile(path44, current);
|
|
33169
|
+
return { ok: true, path: path44 };
|
|
33065
33170
|
}
|
|
33066
33171
|
var init_mcpConfigIo = __esm({
|
|
33067
33172
|
"src/cli/mcp/mcpConfigIo.ts"() {
|
|
@@ -33398,10 +33503,10 @@ import { createHash as createHash5 } from "node:crypto";
|
|
|
33398
33503
|
import { join as join26 } from "node:path";
|
|
33399
33504
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
33400
33505
|
async function readPackageJson2(projectRoot) {
|
|
33401
|
-
const
|
|
33402
|
-
if (!existsSync30(
|
|
33506
|
+
const path44 = join26(projectRoot, "package.json");
|
|
33507
|
+
if (!existsSync30(path44)) return null;
|
|
33403
33508
|
try {
|
|
33404
|
-
return JSON.parse(await readFile3(
|
|
33509
|
+
return JSON.parse(await readFile3(path44, "utf8"));
|
|
33405
33510
|
} catch {
|
|
33406
33511
|
return null;
|
|
33407
33512
|
}
|
|
@@ -33483,9 +33588,9 @@ async function genBuild(ctx) {
|
|
|
33483
33588
|
].join("\n");
|
|
33484
33589
|
}
|
|
33485
33590
|
async function genOpenQuestions(ctx) {
|
|
33486
|
-
const
|
|
33487
|
-
if (!existsSync30(
|
|
33488
|
-
const content = readFileSync25(
|
|
33591
|
+
const path44 = join26(ctx.rootDir, "risks.md");
|
|
33592
|
+
if (!existsSync30(path44)) return "_No open questions._";
|
|
33593
|
+
const content = readFileSync25(path44, "utf8");
|
|
33489
33594
|
const lines = content.split("\n");
|
|
33490
33595
|
const questions = [];
|
|
33491
33596
|
let currentTitle = "";
|
|
@@ -34052,8 +34157,8 @@ async function runPostCouncilHook(ctx, options) {
|
|
|
34052
34157
|
sources: scope.sources
|
|
34053
34158
|
} : void 0
|
|
34054
34159
|
});
|
|
34055
|
-
const
|
|
34056
|
-
completionHook = { ran: true, path:
|
|
34160
|
+
const path44 = writeCouncilCompletion(ctx.rootDir, completion);
|
|
34161
|
+
completionHook = { ran: true, path: path44, completion };
|
|
34057
34162
|
} catch (err) {
|
|
34058
34163
|
completionHook = {
|
|
34059
34164
|
ran: true,
|
|
@@ -35271,9 +35376,17 @@ __export(planner_exports, {
|
|
|
35271
35376
|
KRAKEN_PLANNER_SYSTEM_PROMPT: () => KRAKEN_PLANNER_SYSTEM_PROMPT,
|
|
35272
35377
|
PlannerTransportError: () => PlannerTransportError,
|
|
35273
35378
|
buildGraphFromPlan: () => buildGraphFromPlan,
|
|
35379
|
+
buildPlannerUserPrompt: () => buildPlannerUserPrompt,
|
|
35274
35380
|
extractJsonObject: () => extractJsonObject,
|
|
35275
|
-
planTaskGraph: () => planTaskGraph
|
|
35381
|
+
planTaskGraph: () => planTaskGraph,
|
|
35382
|
+
stripReasoningBlocks: () => stripReasoningBlocks
|
|
35276
35383
|
});
|
|
35384
|
+
function resolvePlannerWorkspaceChars(env = process.env) {
|
|
35385
|
+
const raw = env.ZELARI_KRAKEN_PLANNER_WORKSPACE_CHARS;
|
|
35386
|
+
if (raw === void 0 || raw === "") return DEFAULT_PLANNER_WORKSPACE_CHARS;
|
|
35387
|
+
const n = Number.parseInt(raw, 10);
|
|
35388
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_PLANNER_WORKSPACE_CHARS;
|
|
35389
|
+
}
|
|
35277
35390
|
function resolvePlannerTimeoutMs(env = process.env) {
|
|
35278
35391
|
const raw = env.ZELARI_KRAKEN_PLANNER_TIMEOUT_MS;
|
|
35279
35392
|
if (raw === void 0 || raw === "") return DEFAULT_LLM_TIMEOUT_MS;
|
|
@@ -35286,22 +35399,77 @@ function resolvePlannerMaxTokens(env = process.env) {
|
|
|
35286
35399
|
const n = Number.parseInt(raw, 10);
|
|
35287
35400
|
return Number.isFinite(n) && n > 0 ? n : DEFAULT_LLM_MAX_TOKENS;
|
|
35288
35401
|
}
|
|
35289
|
-
function extractJsonObject(text) {
|
|
35290
|
-
const
|
|
35291
|
-
const
|
|
35292
|
-
|
|
35293
|
-
const
|
|
35294
|
-
|
|
35402
|
+
function extractJsonObject(text, opts = {}) {
|
|
35403
|
+
const stripped = stripReasoningBlocks(text);
|
|
35404
|
+
const usable = findJsonIn(stripped, opts);
|
|
35405
|
+
if (usable) return usable.value;
|
|
35406
|
+
const raw = findJsonIn(text, opts);
|
|
35407
|
+
if (raw) return raw.value;
|
|
35408
|
+
const body = bodyOf(stripped) || bodyOf(text);
|
|
35295
35409
|
try {
|
|
35296
|
-
return JSON.parse(
|
|
35410
|
+
return JSON.parse(body);
|
|
35297
35411
|
} catch (err) {
|
|
35298
35412
|
try {
|
|
35299
|
-
return JSON.parse(repairLooseJson(
|
|
35413
|
+
return JSON.parse(repairLooseJson(body));
|
|
35300
35414
|
} catch {
|
|
35301
35415
|
throw err instanceof Error ? err : new Error(String(err));
|
|
35302
35416
|
}
|
|
35303
35417
|
}
|
|
35304
35418
|
}
|
|
35419
|
+
function bodyOf(text) {
|
|
35420
|
+
const trimmed = text.trim();
|
|
35421
|
+
const fenced = /^```(?:json)?\s*([\s\S]*?)```$/i.exec(trimmed);
|
|
35422
|
+
return fenced ? fenced[1].trim() : trimmed;
|
|
35423
|
+
}
|
|
35424
|
+
function findJsonIn(text, opts) {
|
|
35425
|
+
let firstParsed = null;
|
|
35426
|
+
for (const candidate of balancedJsonObjects(bodyOf(text))) {
|
|
35427
|
+
let value;
|
|
35428
|
+
try {
|
|
35429
|
+
value = JSON.parse(candidate);
|
|
35430
|
+
} catch {
|
|
35431
|
+
try {
|
|
35432
|
+
value = JSON.parse(repairLooseJson(candidate));
|
|
35433
|
+
} catch {
|
|
35434
|
+
continue;
|
|
35435
|
+
}
|
|
35436
|
+
}
|
|
35437
|
+
if (!opts.requireKey) return { value };
|
|
35438
|
+
if (value && typeof value === "object" && opts.requireKey in value) {
|
|
35439
|
+
return { value };
|
|
35440
|
+
}
|
|
35441
|
+
const unwrapped = unwrapKey(value, opts.requireKey);
|
|
35442
|
+
if (unwrapped) return { value: unwrapped };
|
|
35443
|
+
if (!firstParsed) firstParsed = { value };
|
|
35444
|
+
}
|
|
35445
|
+
return firstParsed;
|
|
35446
|
+
}
|
|
35447
|
+
function unwrapKey(value, key) {
|
|
35448
|
+
if (!value || typeof value !== "object") return null;
|
|
35449
|
+
for (const child of Object.values(value)) {
|
|
35450
|
+
if (child && typeof child === "object" && key in child) {
|
|
35451
|
+
return child;
|
|
35452
|
+
}
|
|
35453
|
+
}
|
|
35454
|
+
return null;
|
|
35455
|
+
}
|
|
35456
|
+
function stripReasoningBlocks(text) {
|
|
35457
|
+
let out = text.replace(/<(think|thinking|reasoning)>[\s\S]*?<\/\1>/gi, "");
|
|
35458
|
+
const openIdx = out.search(/<(think|thinking|reasoning)>/i);
|
|
35459
|
+
if (openIdx >= 0) out = out.slice(0, openIdx);
|
|
35460
|
+
return out;
|
|
35461
|
+
}
|
|
35462
|
+
function* balancedJsonObjects(s) {
|
|
35463
|
+
let from = 0;
|
|
35464
|
+
while (from < s.length) {
|
|
35465
|
+
const next = extractBalancedJsonObject2(s.slice(from));
|
|
35466
|
+
if (!next) return;
|
|
35467
|
+
yield next;
|
|
35468
|
+
const idx = s.indexOf(next, from);
|
|
35469
|
+
if (idx < 0) return;
|
|
35470
|
+
from = idx + next.length;
|
|
35471
|
+
}
|
|
35472
|
+
}
|
|
35305
35473
|
function extractBalancedJsonObject2(s) {
|
|
35306
35474
|
const start = s.indexOf("{");
|
|
35307
35475
|
if (start < 0) return null;
|
|
@@ -35527,11 +35695,32 @@ async function createDefaultLlmClient(opts) {
|
|
|
35527
35695
|
}
|
|
35528
35696
|
};
|
|
35529
35697
|
}
|
|
35530
|
-
function
|
|
35531
|
-
return
|
|
35532
|
-
|
|
35533
|
-
|
|
35534
|
-
|
|
35698
|
+
function resolveWorkspaceListing(opts) {
|
|
35699
|
+
if (opts.workspace !== void 0) return opts.workspace || void 0;
|
|
35700
|
+
if (!opts.cwd) return void 0;
|
|
35701
|
+
const maxChars = resolvePlannerWorkspaceChars();
|
|
35702
|
+
if (maxChars <= 0) return void 0;
|
|
35703
|
+
try {
|
|
35704
|
+
return buildWorkspaceSummary(opts.cwd, { maxEntries: 24, maxChars }) || void 0;
|
|
35705
|
+
} catch {
|
|
35706
|
+
return void 0;
|
|
35707
|
+
}
|
|
35708
|
+
}
|
|
35709
|
+
function buildPlannerUserPrompt(prompt, opts = {}) {
|
|
35710
|
+
const parts = [`Goal:
|
|
35711
|
+
${prompt.trim()}`];
|
|
35712
|
+
if (opts.workspace?.trim()) {
|
|
35713
|
+
parts.push(
|
|
35714
|
+
"",
|
|
35715
|
+
"## The project this goal is about (real files on disk)",
|
|
35716
|
+
opts.workspace.trim()
|
|
35717
|
+
);
|
|
35718
|
+
}
|
|
35719
|
+
if (opts.previousAttempt?.trim()) {
|
|
35720
|
+
parts.push("", opts.previousAttempt.trim());
|
|
35721
|
+
}
|
|
35722
|
+
parts.push("", "Return ONLY the JSON object described in the system prompt.");
|
|
35723
|
+
return parts.join("\n");
|
|
35535
35724
|
}
|
|
35536
35725
|
function uniqueId(base, existing) {
|
|
35537
35726
|
if (!existing.has(base)) return base;
|
|
@@ -35540,11 +35729,40 @@ function uniqueId(base, existing) {
|
|
|
35540
35729
|
return `${base}-${i}`;
|
|
35541
35730
|
}
|
|
35542
35731
|
function buildAutoVerifyPrompt(general) {
|
|
35543
|
-
const
|
|
35544
|
-
|
|
35545
|
-
|
|
35546
|
-
|
|
35547
|
-
|
|
35732
|
+
const taskPrompt = general.prompt.length > MAX_VERIFY_TASK_PROMPT_CHARS ? `${general.prompt.slice(0, MAX_VERIFY_TASK_PROMPT_CHARS)}
|
|
35733
|
+
\u2026 [truncated]` : general.prompt;
|
|
35734
|
+
const parts = [
|
|
35735
|
+
`Verify on disk that this work was actually completed correctly: ${general.label}.`,
|
|
35736
|
+
"",
|
|
35737
|
+
"## The task that was carried out",
|
|
35738
|
+
taskPrompt
|
|
35739
|
+
];
|
|
35740
|
+
if (general.scope && general.scope.length > 0) {
|
|
35741
|
+
parts.push("", "## Paths the work was scoped to", ...general.scope.map((s) => `- ${s}`));
|
|
35742
|
+
}
|
|
35743
|
+
if (general.acceptance && general.acceptance.length > 0) {
|
|
35744
|
+
parts.push(
|
|
35745
|
+
"",
|
|
35746
|
+
"## Acceptance criteria to check explicitly",
|
|
35747
|
+
...general.acceptance.map((a) => `- ${a}`)
|
|
35748
|
+
);
|
|
35749
|
+
}
|
|
35750
|
+
parts.push(
|
|
35751
|
+
"",
|
|
35752
|
+
"Read the files involved rather than trusting any summary. Report the commands you ran and every gap you found.",
|
|
35753
|
+
"",
|
|
35754
|
+
"## How to report your verdict",
|
|
35755
|
+
"End your final message with a line of exactly this form, as the LAST line:",
|
|
35756
|
+
"",
|
|
35757
|
+
"VERDICT: PASS",
|
|
35758
|
+
"",
|
|
35759
|
+
"or",
|
|
35760
|
+
"",
|
|
35761
|
+
"VERDICT: FAIL",
|
|
35762
|
+
"",
|
|
35763
|
+
"This line is parsed. FAIL sends the work back to the tentacle that wrote it, together with everything you write above the line \u2014 so state each gap concretely enough to be acted on (file, what is wrong, what it should be). Only report FAIL for a real defect against the task or its acceptance criteria: a rework round is expensive and there is only a small number of them. Stylistic preferences are not a FAIL."
|
|
35764
|
+
);
|
|
35765
|
+
return parts.join("\n");
|
|
35548
35766
|
}
|
|
35549
35767
|
function buildGraphFromPlan(graphId, planned) {
|
|
35550
35768
|
const nodes = planned.map((p3) => ({
|
|
@@ -35597,14 +35815,23 @@ async function planTaskGraph(opts) {
|
|
|
35597
35815
|
const client = opts.llmClient ?? await createDefaultLlmClient({ provider: opts.provider, model: opts.model });
|
|
35598
35816
|
const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
|
|
35599
35817
|
const graphId = opts.graphId ?? `kraken-${Date.now().toString(36)}`;
|
|
35600
|
-
const
|
|
35818
|
+
const workspace = resolveWorkspaceListing(opts);
|
|
35819
|
+
const userBase = buildPlannerUserPrompt(opts.prompt, {
|
|
35820
|
+
...opts.previousAttempt ? { previousAttempt: opts.previousAttempt } : {},
|
|
35821
|
+
...workspace ? { workspace } : {}
|
|
35822
|
+
});
|
|
35601
35823
|
let lastError;
|
|
35602
35824
|
let userMessage = userBase;
|
|
35603
35825
|
for (let attempt = 1; attempt <= MAX_PLAN_ATTEMPTS; attempt++) {
|
|
35604
35826
|
try {
|
|
35605
35827
|
const text = await client.complete({ system: KRAKEN_PLANNER_SYSTEM_PROMPT, user: userMessage });
|
|
35606
|
-
const parsedJson = extractJsonObject(text);
|
|
35828
|
+
const parsedJson = extractJsonObject(text, { requireKey: "nodes" });
|
|
35607
35829
|
const validated = PlannedGraphSchema.parse(parsedJson);
|
|
35830
|
+
if (!validated.nodes.some((n) => n.kind === "general")) {
|
|
35831
|
+
throw new Error(
|
|
35832
|
+
'the plan contains no "general" node, so it cannot change anything \u2014 every node is read-only "explore". Include at least one "general" node that performs the actual work.'
|
|
35833
|
+
);
|
|
35834
|
+
}
|
|
35608
35835
|
const graph = buildGraphFromPlan(graphId, validated.nodes);
|
|
35609
35836
|
const check2 = validateGraph(graph, { maxNodes });
|
|
35610
35837
|
if (!check2.ok) {
|
|
@@ -35623,7 +35850,7 @@ Your previous response was invalid (${lastError}). Return ONLY corrected JSON ma
|
|
|
35623
35850
|
`kraken planner: failed to produce a valid task graph after ${MAX_PLAN_ATTEMPTS} attempts \u2014 ${lastError}`
|
|
35624
35851
|
);
|
|
35625
35852
|
}
|
|
35626
|
-
var MAX_PLAN_ATTEMPTS, DEFAULT_LLM_TIMEOUT_MS, PlannerTransportError, DEFAULT_LLM_MAX_TOKENS, DEFAULT_MAX_RETRIES, KRAKEN_PLANNER_SYSTEM_PROMPT, PlannedNodeSchema, PlannedGraphSchema;
|
|
35853
|
+
var MAX_PLAN_ATTEMPTS, DEFAULT_PLANNER_WORKSPACE_CHARS, DEFAULT_LLM_TIMEOUT_MS, PlannerTransportError, DEFAULT_LLM_MAX_TOKENS, DEFAULT_MAX_RETRIES, KRAKEN_PLANNER_SYSTEM_PROMPT, PlannedNodeSchema, PlannedGraphSchema, MAX_VERIFY_TASK_PROMPT_CHARS;
|
|
35627
35854
|
var init_planner = __esm({
|
|
35628
35855
|
"src/cli/kraken/planner.ts"() {
|
|
35629
35856
|
"use strict";
|
|
@@ -35632,7 +35859,9 @@ var init_planner = __esm({
|
|
|
35632
35859
|
init_providerConfig();
|
|
35633
35860
|
init_keyStore();
|
|
35634
35861
|
init_openai_compatible();
|
|
35862
|
+
init_workspaceSummary();
|
|
35635
35863
|
MAX_PLAN_ATTEMPTS = 2;
|
|
35864
|
+
DEFAULT_PLANNER_WORKSPACE_CHARS = 3e3;
|
|
35636
35865
|
DEFAULT_LLM_TIMEOUT_MS = 3e5;
|
|
35637
35866
|
PlannerTransportError = class extends Error {
|
|
35638
35867
|
constructor(message) {
|
|
@@ -35660,12 +35889,14 @@ var init_planner = __esm({
|
|
|
35660
35889
|
'- kind "general": can edit files for one bounded, self-contained unit of work.',
|
|
35661
35890
|
'- Do NOT emit "verify", "fix", or "merge" nodes \u2014 the executor adds those automatically.',
|
|
35662
35891
|
'- "id" must be short, unique, kebab-case (e.g. "e1", "g-auth", "g-ui").',
|
|
35663
|
-
'- "prompt" must be
|
|
35892
|
+
'- "prompt" must be self-contained: the sub-agent sees ONLY this prompt, not this conversation.',
|
|
35664
35893
|
'- "deps" lists ids of nodes that must finish first (topological order); [] if none.',
|
|
35894
|
+
'- A node DOES receive what its "deps" reported when they finished \u2014 the executor injects their conclusions into its prompt. So write a dependent node as "using the findings above, \u2026" rather than duplicating the research its dependency will do.',
|
|
35665
35895
|
'- When two "general" nodes touch disjoint parts of the codebase, give each a "scope" (path/glob allowlist) so they can run in parallel safely. If scopes might overlap, either omit scope (forces sequential execution) or add a dep between them.',
|
|
35896
|
+
'- The user message includes a listing of the real project on disk. Build every "scope" from paths that appear there (or new paths that clearly belong beside them) \u2014 an invented path makes the parallelism decision meaningless, since scopes are exactly what the executor uses to decide which writers may run at the same time.',
|
|
35666
35897
|
'- Prefer one "explore" node feeding several parallel "general" nodes over one giant node.',
|
|
35667
35898
|
"- Keep the graph small: most goals need 3-8 nodes total.",
|
|
35668
|
-
'- "acceptance" (optional) lists concrete, checkable criteria for a "general" node.'
|
|
35899
|
+
'- "acceptance" (optional) lists concrete, checkable criteria for a "general" node. These are ENFORCED: the executor adds a verify tentacle that checks them on disk and can send the work back for a rework round when they are not met. Write criteria a reader can settle by opening a file or running a command ("exports slugify(input: string): string", "npm test passes"), never subjective ones ("the code is elegant") \u2014 a criterion nobody can check just burns a rework round.'
|
|
35669
35900
|
].join("\n");
|
|
35670
35901
|
PlannedNodeSchema = external_exports.object({
|
|
35671
35902
|
id: external_exports.string().min(1).max(64).regex(/^[a-zA-Z0-9_-]+$/, "id must be alphanumeric/dash/underscore"),
|
|
@@ -35679,6 +35910,118 @@ var init_planner = __esm({
|
|
|
35679
35910
|
PlannedGraphSchema = external_exports.object({
|
|
35680
35911
|
nodes: external_exports.array(PlannedNodeSchema).min(1).max(DEFAULT_MAX_NODES)
|
|
35681
35912
|
});
|
|
35913
|
+
MAX_VERIFY_TASK_PROMPT_CHARS = 1200;
|
|
35914
|
+
}
|
|
35915
|
+
});
|
|
35916
|
+
|
|
35917
|
+
// src/cli/kraken/graphMemory.ts
|
|
35918
|
+
var graphMemory_exports = {};
|
|
35919
|
+
__export(graphMemory_exports, {
|
|
35920
|
+
formatSnapshotForPlanner: () => formatSnapshotForPlanner,
|
|
35921
|
+
loadGraphSnapshot: () => loadGraphSnapshot,
|
|
35922
|
+
saveGraphSnapshot: () => saveGraphSnapshot,
|
|
35923
|
+
toGraphSnapshot: () => toGraphSnapshot
|
|
35924
|
+
});
|
|
35925
|
+
import { promises as fs19 } from "node:fs";
|
|
35926
|
+
import path34 from "node:path";
|
|
35927
|
+
function snapshotPath(cwd) {
|
|
35928
|
+
return path34.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
|
|
35929
|
+
}
|
|
35930
|
+
function toGraphSnapshot(graph, opts) {
|
|
35931
|
+
const unresolved = (opts.unresolvedFindings ?? []).map((u) => ({
|
|
35932
|
+
...u,
|
|
35933
|
+
findings: u.findings.slice(0, MAX_SNAPSHOT_FINDINGS_CHARS)
|
|
35934
|
+
}));
|
|
35935
|
+
return {
|
|
35936
|
+
graphId: graph.id,
|
|
35937
|
+
goal: opts.goal,
|
|
35938
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35939
|
+
converged: opts.converged,
|
|
35940
|
+
...unresolved.length > 0 ? { unresolvedFindings: unresolved } : {},
|
|
35941
|
+
nodes: [...graph.nodes.values()].map((n) => ({
|
|
35942
|
+
id: n.id,
|
|
35943
|
+
kind: n.kind,
|
|
35944
|
+
label: n.label,
|
|
35945
|
+
status: n.status,
|
|
35946
|
+
...n.scope && n.scope.length > 0 ? { scope: n.scope } : {},
|
|
35947
|
+
...n.error ? { error: n.error.slice(0, 300) } : {}
|
|
35948
|
+
}))
|
|
35949
|
+
};
|
|
35950
|
+
}
|
|
35951
|
+
async function saveGraphSnapshot(cwd, snapshot) {
|
|
35952
|
+
try {
|
|
35953
|
+
await fs19.access(cwd);
|
|
35954
|
+
const file2 = snapshotPath(cwd);
|
|
35955
|
+
await fs19.mkdir(path34.dirname(file2), { recursive: true });
|
|
35956
|
+
await fs19.writeFile(file2, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
|
|
35957
|
+
} catch {
|
|
35958
|
+
}
|
|
35959
|
+
}
|
|
35960
|
+
async function loadGraphSnapshot(cwd) {
|
|
35961
|
+
try {
|
|
35962
|
+
const raw = await fs19.readFile(snapshotPath(cwd), "utf8");
|
|
35963
|
+
const parsed = JSON.parse(raw);
|
|
35964
|
+
if (!parsed || !Array.isArray(parsed.nodes)) return null;
|
|
35965
|
+
return parsed;
|
|
35966
|
+
} catch {
|
|
35967
|
+
return null;
|
|
35968
|
+
}
|
|
35969
|
+
}
|
|
35970
|
+
function formatSnapshotForPlanner(snapshot) {
|
|
35971
|
+
if (!snapshot) return "";
|
|
35972
|
+
const done = snapshot.nodes.filter((n) => n.status === "done");
|
|
35973
|
+
const failed = snapshot.nodes.filter((n) => n.status === "error");
|
|
35974
|
+
const skipped = snapshot.nodes.filter((n) => n.status === "skipped");
|
|
35975
|
+
const rejected = snapshot.unresolvedFindings ?? [];
|
|
35976
|
+
if (failed.length === 0 && skipped.length === 0 && rejected.length === 0) return "";
|
|
35977
|
+
const line = (n) => `- ${n.label}${n.scope ? ` [${n.scope.join(", ")}]` : ""}${n.error ? ` \u2014 ${n.error}` : ""}`;
|
|
35978
|
+
const rejectedIds = new Set(rejected.map((r) => r.nodeId));
|
|
35979
|
+
const outcome = [
|
|
35980
|
+
`${done.length} done`,
|
|
35981
|
+
`${failed.length} failed`,
|
|
35982
|
+
`${skipped.length} never ran`,
|
|
35983
|
+
...rejected.length > 0 ? [`${rejected.length} rejected by review`] : []
|
|
35984
|
+
].join(", ");
|
|
35985
|
+
const parts = [
|
|
35986
|
+
"",
|
|
35987
|
+
"## Previous unfinished task graph in this project",
|
|
35988
|
+
`Goal it was working on: "${snapshot.goal}"`,
|
|
35989
|
+
`It did NOT finish cleanly (${outcome}).`,
|
|
35990
|
+
"",
|
|
35991
|
+
"If that goal is unrelated to the one above, ignore this section entirely.",
|
|
35992
|
+
""
|
|
35993
|
+
];
|
|
35994
|
+
const cleanlyDone = done.filter((n) => !rejectedIds.has(n.id));
|
|
35995
|
+
if (cleanlyDone.length > 0) {
|
|
35996
|
+
parts.push("Already completed \u2014 do NOT redo this work:", ...cleanlyDone.map(line), "");
|
|
35997
|
+
}
|
|
35998
|
+
if (rejected.length > 0) {
|
|
35999
|
+
parts.push(
|
|
36000
|
+
"Completed but REJECTED by review \u2014 the code exists, the defects do not fix themselves:",
|
|
36001
|
+
...rejected.map(
|
|
36002
|
+
(r) => `- ${r.label} \u2014 ${r.findings.trim().split("\n")[0] ?? "no detail"}`
|
|
36003
|
+
),
|
|
36004
|
+
""
|
|
36005
|
+
);
|
|
36006
|
+
}
|
|
36007
|
+
if (failed.length > 0) {
|
|
36008
|
+
parts.push("Failed \u2014 needs to be finished or repaired:", ...failed.map(line), "");
|
|
36009
|
+
}
|
|
36010
|
+
if (skipped.length > 0) {
|
|
36011
|
+
parts.push("Never ran (blocked by the failures above):", ...skipped.map(line), "");
|
|
36012
|
+
}
|
|
36013
|
+
parts.push(
|
|
36014
|
+
"Plan only the remaining work. Inspect what is already on disk before rewriting it, and prefer wiring/finishing existing modules over recreating them."
|
|
36015
|
+
);
|
|
36016
|
+
return parts.join("\n");
|
|
36017
|
+
}
|
|
36018
|
+
var SNAPSHOT_DIR, SNAPSHOT_FILE, MAX_SNAPSHOT_FINDINGS_CHARS;
|
|
36019
|
+
var init_graphMemory = __esm({
|
|
36020
|
+
"src/cli/kraken/graphMemory.ts"() {
|
|
36021
|
+
"use strict";
|
|
36022
|
+
SNAPSHOT_DIR = path34.join(".zelari", "kraken");
|
|
36023
|
+
SNAPSHOT_FILE = "last-graph.json";
|
|
36024
|
+
MAX_SNAPSHOT_FINDINGS_CHARS = 400;
|
|
35682
36025
|
}
|
|
35683
36026
|
});
|
|
35684
36027
|
|
|
@@ -35693,30 +36036,60 @@ var init_tentacle = __esm({
|
|
|
35693
36036
|
// src/cli/kraken/executor.ts
|
|
35694
36037
|
var executor_exports = {};
|
|
35695
36038
|
__export(executor_exports, {
|
|
36039
|
+
DEFAULT_CANCEL_GRACE_MS: () => DEFAULT_CANCEL_GRACE_MS,
|
|
35696
36040
|
DEFAULT_FIX_BUDGET: () => DEFAULT_FIX_BUDGET,
|
|
36041
|
+
DEFAULT_GRAPH_TIMEOUT_MS: () => DEFAULT_GRAPH_TIMEOUT_MS,
|
|
35697
36042
|
DEFAULT_MAX_PARALLEL: () => DEFAULT_MAX_PARALLEL,
|
|
36043
|
+
DEFAULT_MAX_REVIEW_ROUNDS: () => DEFAULT_MAX_REVIEW_ROUNDS,
|
|
35698
36044
|
DEFAULT_NODE_TIMEOUT_MS: () => DEFAULT_NODE_TIMEOUT_MS,
|
|
35699
36045
|
DEFAULT_WRITER_NODE_TIMEOUT_MS: () => DEFAULT_WRITER_NODE_TIMEOUT_MS,
|
|
35700
36046
|
KrakenGraphExecutor: () => KrakenGraphExecutor,
|
|
36047
|
+
MAX_UPSTREAM_CHARS_PER_DEP: () => MAX_UPSTREAM_CHARS_PER_DEP,
|
|
36048
|
+
MAX_UPSTREAM_CHARS_TOTAL: () => MAX_UPSTREAM_CHARS_TOTAL,
|
|
36049
|
+
buildUpstreamContext: () => buildUpstreamContext,
|
|
35701
36050
|
isKrakenGraphEnabled: () => isKrakenGraphEnabled,
|
|
35702
36051
|
isWorldModelGateEnabled: () => isWorldModelGateEnabled,
|
|
36052
|
+
resolveCancelGraceMs: () => resolveCancelGraceMs,
|
|
35703
36053
|
resolveFixBudget: () => resolveFixBudget,
|
|
36054
|
+
resolveGraphTimeoutMs: () => resolveGraphTimeoutMs,
|
|
35704
36055
|
resolveMaxParallel: () => resolveMaxParallel,
|
|
35705
|
-
|
|
36056
|
+
resolveMaxReviewRounds: () => resolveMaxReviewRounds,
|
|
36057
|
+
resolveNodeTimeoutMs: () => resolveNodeTimeoutMs,
|
|
36058
|
+
thoroughnessForKind: () => thoroughnessForKind
|
|
35706
36059
|
});
|
|
35707
36060
|
import { existsSync as existsSync34 } from "node:fs";
|
|
35708
|
-
import
|
|
36061
|
+
import path35 from "node:path";
|
|
35709
36062
|
function resolveMaxParallel(env = process.env) {
|
|
35710
36063
|
const raw = env.ZELARI_KRAKEN_MAX_PARALLEL;
|
|
35711
36064
|
if (raw === void 0 || raw === "") return DEFAULT_MAX_PARALLEL;
|
|
35712
36065
|
const n = Number.parseInt(raw, 10);
|
|
35713
36066
|
return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_PARALLEL;
|
|
35714
36067
|
}
|
|
35715
|
-
function resolveFixBudget(env = process.env) {
|
|
36068
|
+
function resolveFixBudget(env = process.env, nodeCount = 0) {
|
|
35716
36069
|
const raw = env.ZELARI_KRAKEN_FIX_BUDGET;
|
|
35717
|
-
if (raw === void 0 || raw === "")
|
|
36070
|
+
if (raw === void 0 || raw === "") {
|
|
36071
|
+
return Math.max(DEFAULT_FIX_BUDGET, Math.ceil(nodeCount / 2));
|
|
36072
|
+
}
|
|
35718
36073
|
const n = Number.parseInt(raw, 10);
|
|
35719
|
-
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_FIX_BUDGET;
|
|
36074
|
+
return Number.isFinite(n) && n >= 0 ? n : Math.max(DEFAULT_FIX_BUDGET, Math.ceil(nodeCount / 2));
|
|
36075
|
+
}
|
|
36076
|
+
function resolveMaxReviewRounds(env = process.env) {
|
|
36077
|
+
const raw = env.ZELARI_KRAKEN_MAX_REVIEW_ROUNDS;
|
|
36078
|
+
if (raw === void 0 || raw === "") return DEFAULT_MAX_REVIEW_ROUNDS;
|
|
36079
|
+
const n = Number.parseInt(raw, 10);
|
|
36080
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_MAX_REVIEW_ROUNDS;
|
|
36081
|
+
}
|
|
36082
|
+
function resolveGraphTimeoutMs(env = process.env) {
|
|
36083
|
+
const raw = env.ZELARI_KRAKEN_GRAPH_TIMEOUT_MS;
|
|
36084
|
+
if (raw === void 0 || raw === "") return DEFAULT_GRAPH_TIMEOUT_MS;
|
|
36085
|
+
const n = Number.parseInt(raw, 10);
|
|
36086
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_GRAPH_TIMEOUT_MS;
|
|
36087
|
+
}
|
|
36088
|
+
function resolveCancelGraceMs(env = process.env) {
|
|
36089
|
+
const raw = env.ZELARI_KRAKEN_CANCEL_GRACE_MS;
|
|
36090
|
+
if (raw === void 0 || raw === "") return DEFAULT_CANCEL_GRACE_MS;
|
|
36091
|
+
const n = Number.parseInt(raw, 10);
|
|
36092
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_CANCEL_GRACE_MS;
|
|
35720
36093
|
}
|
|
35721
36094
|
function resolveNodeTimeoutMs(env = process.env, agent) {
|
|
35722
36095
|
const raw = env.ZELARI_KRAKEN_NODE_TIMEOUT_MS;
|
|
@@ -35743,12 +36116,56 @@ function isWorldModelGateEnabled(cwd, env = process.env, checksExists = defaultC
|
|
|
35743
36116
|
}
|
|
35744
36117
|
function defaultChecksExists(cwd) {
|
|
35745
36118
|
try {
|
|
35746
|
-
return existsSync34(
|
|
36119
|
+
return existsSync34(path35.join(cwd, ".zelari", "world", "checks.json"));
|
|
35747
36120
|
} catch {
|
|
35748
36121
|
return false;
|
|
35749
36122
|
}
|
|
35750
36123
|
}
|
|
35751
|
-
|
|
36124
|
+
function buildUpstreamContext(graph, node) {
|
|
36125
|
+
const parts = [];
|
|
36126
|
+
const omitted = [];
|
|
36127
|
+
let budget = MAX_UPSTREAM_CHARS_TOTAL;
|
|
36128
|
+
for (const depId of node.deps) {
|
|
36129
|
+
const dep = graph.nodes.get(depId);
|
|
36130
|
+
if (!dep || dep.status !== "done") continue;
|
|
36131
|
+
const raw = (dep.result ?? "").trim();
|
|
36132
|
+
if (!raw) continue;
|
|
36133
|
+
const cap2 = Math.min(MAX_UPSTREAM_CHARS_PER_DEP, budget);
|
|
36134
|
+
if (cap2 <= 0) {
|
|
36135
|
+
omitted.push(dep.label);
|
|
36136
|
+
continue;
|
|
36137
|
+
}
|
|
36138
|
+
const body = raw.length > cap2 ? `${raw.slice(0, cap2)}
|
|
36139
|
+
\u2026 [truncated ${raw.length}\u2192${cap2} chars]` : raw;
|
|
36140
|
+
budget -= Math.min(raw.length, cap2);
|
|
36141
|
+
const scope = dep.scope && dep.scope.length > 0 ? `, scope: ${dep.scope.join(", ")}` : "";
|
|
36142
|
+
parts.push(`### ${dep.label} (${dep.kind}${scope})
|
|
36143
|
+
${body}`);
|
|
36144
|
+
}
|
|
36145
|
+
if (parts.length === 0) return "";
|
|
36146
|
+
const lines = [
|
|
36147
|
+
"",
|
|
36148
|
+
"## Context from completed upstream tasks",
|
|
36149
|
+
"Results reported by the tasks this one depends on. Treat them as hypotheses \u2014 prefer the actual files on disk where they conflict.",
|
|
36150
|
+
"",
|
|
36151
|
+
...parts
|
|
36152
|
+
];
|
|
36153
|
+
if (omitted.length > 0) {
|
|
36154
|
+
lines.push("", `(omitted for context budget: ${omitted.join(", ")})`);
|
|
36155
|
+
}
|
|
36156
|
+
return lines.join("\n");
|
|
36157
|
+
}
|
|
36158
|
+
function thoroughnessForKind(kind) {
|
|
36159
|
+
return kind === "general" || kind === "fix" ? "deep" : "medium";
|
|
36160
|
+
}
|
|
36161
|
+
function firstLine(text, maxChars = 160) {
|
|
36162
|
+
const line = text.trim().split("\n").find((l) => l.trim() !== "")?.trim() ?? "";
|
|
36163
|
+
return line.length > maxChars ? `${line.slice(0, maxChars)}\u2026` : line;
|
|
36164
|
+
}
|
|
36165
|
+
function agentForNode(node) {
|
|
36166
|
+
return node.kind === "explore" || node.kind === "verify" ? node.kind : "general";
|
|
36167
|
+
}
|
|
36168
|
+
var DEFAULT_MAX_PARALLEL, DEFAULT_FIX_BUDGET, DEFAULT_MAX_REVIEW_ROUNDS, DEFAULT_GRAPH_TIMEOUT_MS, DEFAULT_NODE_TIMEOUT_MS, DEFAULT_WRITER_NODE_TIMEOUT_MS, DEFAULT_CANCEL_GRACE_MS, MAX_UPSTREAM_CHARS_PER_DEP, MAX_UPSTREAM_CHARS_TOTAL, KrakenGraphExecutor;
|
|
35752
36169
|
var init_executor = __esm({
|
|
35753
36170
|
"src/cli/kraken/executor.ts"() {
|
|
35754
36171
|
"use strict";
|
|
@@ -35758,40 +36175,116 @@ var init_executor = __esm({
|
|
|
35758
36175
|
init_krakenRadio();
|
|
35759
36176
|
init_worldModel();
|
|
35760
36177
|
init_graphStatus();
|
|
36178
|
+
init_graphMemory();
|
|
35761
36179
|
DEFAULT_MAX_PARALLEL = 12;
|
|
35762
36180
|
DEFAULT_FIX_BUDGET = 3;
|
|
36181
|
+
DEFAULT_MAX_REVIEW_ROUNDS = 1;
|
|
36182
|
+
DEFAULT_GRAPH_TIMEOUT_MS = 0;
|
|
35763
36183
|
DEFAULT_NODE_TIMEOUT_MS = 3e5;
|
|
35764
36184
|
DEFAULT_WRITER_NODE_TIMEOUT_MS = 9e5;
|
|
36185
|
+
DEFAULT_CANCEL_GRACE_MS = 3e4;
|
|
36186
|
+
MAX_UPSTREAM_CHARS_PER_DEP = 2800;
|
|
36187
|
+
MAX_UPSTREAM_CHARS_TOTAL = 8e3;
|
|
35765
36188
|
KrakenGraphExecutor = class {
|
|
35766
36189
|
deps;
|
|
35767
36190
|
parentCwd;
|
|
35768
36191
|
sessionId;
|
|
36192
|
+
goal;
|
|
35769
36193
|
maxParallel;
|
|
35770
36194
|
/** Explicit all-kinds override; when undefined the budget is per-kind. */
|
|
35771
36195
|
nodeTimeoutMs;
|
|
36196
|
+
cancelGraceMs;
|
|
36197
|
+
/** Explicit override; when undefined the budget scales with the graph size. */
|
|
36198
|
+
fixBudgetOption;
|
|
35772
36199
|
fixBudgetRemaining;
|
|
36200
|
+
maxReviewRounds;
|
|
36201
|
+
graphTimeoutMs;
|
|
35773
36202
|
worldModelGateOverride;
|
|
35774
36203
|
runTentacleFn;
|
|
35775
36204
|
mergeFn;
|
|
35776
36205
|
backtestFn;
|
|
35777
36206
|
nodeRunState = /* @__PURE__ */ new Map();
|
|
36207
|
+
/** fix node id → id of the failed node it was spawned to repair. */
|
|
36208
|
+
repairs = /* @__PURE__ */ new Map();
|
|
36209
|
+
/**
|
|
36210
|
+
* rework node id → id of the writer whose work it is redoing. A rework is a
|
|
36211
|
+
* `fix` node, but unlike a repair it must NOT create a worktree of its own:
|
|
36212
|
+
* it edits the writer's existing one (see {@link spawnReworkPair}).
|
|
36213
|
+
*/
|
|
36214
|
+
reworks = /* @__PURE__ */ new Map();
|
|
36215
|
+
/** lineage root writer id → rework rounds already spent on that lineage. */
|
|
36216
|
+
reviewRounds = /* @__PURE__ */ new Map();
|
|
36217
|
+
/**
|
|
36218
|
+
* rework node id → the ORIGINAL writer its lineage started from.
|
|
36219
|
+
*
|
|
36220
|
+
* The budget has to be per lineage, not per node: a rework is itself a
|
|
36221
|
+
* writer, so counting rounds against the node being reworked reset the
|
|
36222
|
+
* counter every round and the graph chained rework → verify → rework
|
|
36223
|
+
* forever, terminating only on the scheduler's iteration cap.
|
|
36224
|
+
*/
|
|
36225
|
+
reviewLineage = /* @__PURE__ */ new Map();
|
|
36226
|
+
/** Verify verdicts left unresolved when the run ends. */
|
|
36227
|
+
unresolved = [];
|
|
36228
|
+
/** Live cancellation handles for the tentacles currently running. */
|
|
36229
|
+
nodeControllers = /* @__PURE__ */ new Map();
|
|
36230
|
+
/** Wall-clock duration of each node's last run, by node id. */
|
|
36231
|
+
durationsMs = /* @__PURE__ */ new Map();
|
|
36232
|
+
signal;
|
|
36233
|
+
/** Set once the run has been cancelled: stops admission, retries and fixes. */
|
|
36234
|
+
aborted = false;
|
|
35778
36235
|
fixCounter = 0;
|
|
35779
36236
|
constructor(opts) {
|
|
35780
36237
|
this.deps = opts.taskToolDeps;
|
|
35781
36238
|
this.parentCwd = opts.parentCwd;
|
|
35782
36239
|
this.sessionId = opts.sessionId;
|
|
36240
|
+
this.goal = opts.goal;
|
|
35783
36241
|
this.maxParallel = opts.maxParallel ?? resolveMaxParallel();
|
|
35784
36242
|
this.nodeTimeoutMs = opts.nodeTimeoutMs;
|
|
36243
|
+
this.cancelGraceMs = opts.cancelGraceMs;
|
|
36244
|
+
this.fixBudgetOption = opts.fixBudget;
|
|
35785
36245
|
this.fixBudgetRemaining = opts.fixBudget ?? resolveFixBudget();
|
|
36246
|
+
this.maxReviewRounds = opts.maxReviewRounds ?? resolveMaxReviewRounds();
|
|
36247
|
+
this.graphTimeoutMs = opts.graphTimeoutMs ?? resolveGraphTimeoutMs();
|
|
35786
36248
|
this.worldModelGateOverride = opts.worldModelGate;
|
|
36249
|
+
this.signal = opts.signal;
|
|
35787
36250
|
this.runTentacleFn = opts.runTentacleFn ?? runTentacle;
|
|
35788
36251
|
this.mergeFn = opts.mergeFn ?? mergeKrakenWorktree;
|
|
35789
36252
|
this.backtestFn = opts.backtestFn ?? runBacktest;
|
|
35790
36253
|
}
|
|
35791
36254
|
/** Execute the graph in place (mutates node statuses) until it settles. */
|
|
35792
36255
|
async execute(graph) {
|
|
36256
|
+
if (this.fixBudgetOption === void 0) {
|
|
36257
|
+
this.fixBudgetRemaining = resolveFixBudget(process.env, graph.nodes.size);
|
|
36258
|
+
}
|
|
36259
|
+
const onAbort = () => this.cancelRun();
|
|
36260
|
+
if (this.signal) {
|
|
36261
|
+
if (this.signal.aborted) this.aborted = true;
|
|
36262
|
+
else this.signal.addEventListener("abort", onAbort, { once: true });
|
|
36263
|
+
}
|
|
36264
|
+
let graphTimer;
|
|
36265
|
+
if (this.graphTimeoutMs > 0) {
|
|
36266
|
+
graphTimer = setTimeout(() => {
|
|
36267
|
+
this.radio("graph_failed", {
|
|
36268
|
+
description: "graph executor",
|
|
36269
|
+
detail: `graph exceeded its ${this.graphTimeoutMs}ms wall-clock budget \u2014 cancelling`,
|
|
36270
|
+
ok: false
|
|
36271
|
+
});
|
|
36272
|
+
this.cancelRun();
|
|
36273
|
+
}, this.graphTimeoutMs);
|
|
36274
|
+
graphTimer.unref?.();
|
|
36275
|
+
}
|
|
36276
|
+
try {
|
|
36277
|
+
return await this.schedule(graph);
|
|
36278
|
+
} finally {
|
|
36279
|
+
if (graphTimer) clearTimeout(graphTimer);
|
|
36280
|
+
this.signal?.removeEventListener("abort", onAbort);
|
|
36281
|
+
}
|
|
36282
|
+
}
|
|
36283
|
+
/** The scheduling loop proper. See {@link execute} for the cancellation wrapper. */
|
|
36284
|
+
async schedule(graph) {
|
|
35793
36285
|
const maxIterations = Math.max(64, graph.nodes.size * 8);
|
|
35794
36286
|
let iterations = 0;
|
|
36287
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
35795
36288
|
startKrakenGraphLive(graph);
|
|
35796
36289
|
while (!isSettled(graph)) {
|
|
35797
36290
|
iterations += 1;
|
|
@@ -35803,24 +36296,39 @@ var init_executor = __esm({
|
|
|
35803
36296
|
});
|
|
35804
36297
|
break;
|
|
35805
36298
|
}
|
|
35806
|
-
|
|
35807
|
-
|
|
35808
|
-
|
|
35809
|
-
|
|
36299
|
+
if (this.aborted && inFlight.size === 0) break;
|
|
36300
|
+
const admitted = this.aborted ? [] : this.admit(graph, inFlight);
|
|
36301
|
+
if (admitted.length > 0) {
|
|
36302
|
+
for (const node of admitted) node.status = "running";
|
|
36303
|
+
updateKrakenGraphLive(graph);
|
|
36304
|
+
for (const node of admitted) inFlight.set(node.id, this.runNodeSafely(node, graph));
|
|
36305
|
+
}
|
|
36306
|
+
if (inFlight.size === 0) {
|
|
36307
|
+
if (!this.skipBlockedNodes(graph)) {
|
|
35810
36308
|
break;
|
|
35811
36309
|
}
|
|
35812
36310
|
continue;
|
|
35813
36311
|
}
|
|
35814
|
-
const
|
|
35815
|
-
|
|
35816
|
-
const
|
|
35817
|
-
|
|
35818
|
-
this.applyResult(graph, wave[i], results[i]);
|
|
35819
|
-
}
|
|
36312
|
+
const { id, res } = await Promise.race(inFlight.values());
|
|
36313
|
+
inFlight.delete(id);
|
|
36314
|
+
const settledNode = graph.nodes.get(id);
|
|
36315
|
+
if (settledNode) this.applyResult(graph, settledNode, res);
|
|
35820
36316
|
updateKrakenGraphLive(graph);
|
|
35821
36317
|
}
|
|
36318
|
+
if (inFlight.size > 0) {
|
|
36319
|
+
for (const { id, res } of await Promise.all(inFlight.values())) {
|
|
36320
|
+
const node = graph.nodes.get(id);
|
|
36321
|
+
if (node) this.applyResult(graph, node, res);
|
|
36322
|
+
}
|
|
36323
|
+
inFlight.clear();
|
|
36324
|
+
}
|
|
36325
|
+
if (this.aborted) {
|
|
36326
|
+
for (const n of graph.nodes.values()) {
|
|
36327
|
+
if (n.status === "pending") n.status = "skipped";
|
|
36328
|
+
}
|
|
36329
|
+
}
|
|
35822
36330
|
let backtest;
|
|
35823
|
-
const converged = isConverged(graph);
|
|
36331
|
+
const converged = !this.aborted && isConverged(graph);
|
|
35824
36332
|
if (converged) {
|
|
35825
36333
|
const gateOn = this.worldModelGateOverride ?? isWorldModelGateEnabled(this.parentCwd);
|
|
35826
36334
|
if (gateOn) {
|
|
@@ -35834,47 +36342,140 @@ var init_executor = __esm({
|
|
|
35834
36342
|
} else {
|
|
35835
36343
|
this.radio("graph_failed", {
|
|
35836
36344
|
description: "graph executor",
|
|
35837
|
-
detail: `failed nodes: ${failedNodeIds(graph).join(", ") || "none"}`,
|
|
36345
|
+
detail: this.aborted ? "cancelled by caller" : `failed nodes: ${failedNodeIds(graph).join(", ") || "none"}`,
|
|
35838
36346
|
ok: false
|
|
35839
36347
|
});
|
|
35840
36348
|
}
|
|
35841
36349
|
endKrakenGraphLive(graph, converged);
|
|
36350
|
+
await saveGraphSnapshot(
|
|
36351
|
+
this.parentCwd,
|
|
36352
|
+
toGraphSnapshot(graph, {
|
|
36353
|
+
goal: this.goal ?? graph.id,
|
|
36354
|
+
converged,
|
|
36355
|
+
unresolvedFindings: this.unresolved
|
|
36356
|
+
})
|
|
36357
|
+
);
|
|
35842
36358
|
return {
|
|
35843
36359
|
graph,
|
|
35844
36360
|
converged,
|
|
35845
36361
|
failedNodeIds: failedNodeIds(graph),
|
|
35846
36362
|
counts: countByStatus(graph),
|
|
36363
|
+
durationsMs: Object.fromEntries(this.durationsMs),
|
|
36364
|
+
cancelled: this.aborted,
|
|
36365
|
+
unresolvedFindings: [...this.unresolved],
|
|
35847
36366
|
...backtest ? { backtest } : {}
|
|
35848
36367
|
};
|
|
35849
36368
|
}
|
|
36369
|
+
/**
|
|
36370
|
+
* Stop the run: no further admissions, and every tentacle currently running
|
|
36371
|
+
* is told to unwind. Each node's own timeout/grace machinery then resolves
|
|
36372
|
+
* it, so `execute()` settles instead of leaving orphans behind.
|
|
36373
|
+
*/
|
|
36374
|
+
cancelRun() {
|
|
36375
|
+
if (this.aborted) return;
|
|
36376
|
+
this.aborted = true;
|
|
36377
|
+
this.radio("graph_failed", {
|
|
36378
|
+
description: "graph executor",
|
|
36379
|
+
detail: `cancelling ${this.nodeControllers.size} running tentacle(s)`,
|
|
36380
|
+
ok: false
|
|
36381
|
+
});
|
|
36382
|
+
for (const controller of this.nodeControllers.values()) controller.abort();
|
|
36383
|
+
}
|
|
36384
|
+
/**
|
|
36385
|
+
* Pick the ready nodes that may start right now: parallel-safe against every
|
|
36386
|
+
* node already running AND against each other, within the concurrency cap.
|
|
36387
|
+
*
|
|
36388
|
+
* Unlike a wave-at-a-time scheduler this is called on every completion, so a
|
|
36389
|
+
* node becomes eligible the moment its blocker settles instead of waiting
|
|
36390
|
+
* for the slowest member of some earlier batch.
|
|
36391
|
+
*/
|
|
36392
|
+
admit(graph, inFlight) {
|
|
36393
|
+
const capacity = this.maxParallel - inFlight.size;
|
|
36394
|
+
if (capacity <= 0) return [];
|
|
36395
|
+
const running = [];
|
|
36396
|
+
for (const id of inFlight.keys()) {
|
|
36397
|
+
const n = graph.nodes.get(id);
|
|
36398
|
+
if (n) running.push(n);
|
|
36399
|
+
}
|
|
36400
|
+
const admitted = [];
|
|
36401
|
+
for (const node of getReadyNodes(graph)) {
|
|
36402
|
+
if (admitted.length >= capacity) break;
|
|
36403
|
+
const safe = running.every((r) => canRunParallel(r, node)) && admitted.every((a) => canRunParallel(a, node));
|
|
36404
|
+
if (safe) admitted.push(node);
|
|
36405
|
+
}
|
|
36406
|
+
return admitted;
|
|
36407
|
+
}
|
|
36408
|
+
/**
|
|
36409
|
+
* Run one node, tagging the result with its id and converting an unexpected
|
|
36410
|
+
* throw into a node failure. The scheduler races these promises, so a
|
|
36411
|
+
* rejection would abandon every other in-flight tentacle mid-write; one
|
|
36412
|
+
* failed node that the retry/fix machinery can reason about is strictly
|
|
36413
|
+
* better than an aborted graph.
|
|
36414
|
+
*/
|
|
36415
|
+
runNodeSafely(node, graph) {
|
|
36416
|
+
return this.runNode(node, graph).then(
|
|
36417
|
+
(res) => ({ id: node.id, res }),
|
|
36418
|
+
(err) => ({
|
|
36419
|
+
id: node.id,
|
|
36420
|
+
res: {
|
|
36421
|
+
ok: false,
|
|
36422
|
+
agent: agentForNode(node),
|
|
36423
|
+
error: `tentacle threw: ${err instanceof Error ? err.message : String(err)}`,
|
|
36424
|
+
cancelled: true
|
|
36425
|
+
}
|
|
36426
|
+
})
|
|
36427
|
+
);
|
|
36428
|
+
}
|
|
35850
36429
|
/** Run one node: dispatch to the merge handler for `merge` nodes, else a tentacle. */
|
|
35851
36430
|
async runNode(node, graph) {
|
|
36431
|
+
const startedAt = Date.now();
|
|
36432
|
+
try {
|
|
36433
|
+
return await this.runNodeInner(node, graph);
|
|
36434
|
+
} finally {
|
|
36435
|
+
this.durationsMs.set(node.id, Date.now() - startedAt);
|
|
36436
|
+
this.nodeControllers.delete(node.id);
|
|
36437
|
+
}
|
|
36438
|
+
}
|
|
36439
|
+
async runNodeInner(node, graph) {
|
|
35852
36440
|
this.radio("node_start", { description: node.label, agent: node.kind });
|
|
35853
36441
|
if (node.kind === "merge") {
|
|
35854
36442
|
return this.runMergeNode(node, graph);
|
|
35855
36443
|
}
|
|
35856
|
-
const
|
|
36444
|
+
const isRework = this.reworks.has(node.id);
|
|
36445
|
+
const usesWorktree = (node.kind === "general" || node.kind === "fix") && !isRework;
|
|
35857
36446
|
const agent = node.kind === "fix" ? "general" : node.kind;
|
|
36447
|
+
const controller = new AbortController();
|
|
36448
|
+
this.nodeControllers.set(node.id, controller);
|
|
36449
|
+
if (this.aborted) controller.abort();
|
|
36450
|
+
const upstream = buildUpstreamContext(graph, node);
|
|
36451
|
+
const inheritedCwd = node.kind === "verify" || isRework ? this.inheritedWorktreeCwdFor(node, graph) : void 0;
|
|
35858
36452
|
const res = await this.withNodeTimeout(
|
|
35859
36453
|
this.runTentacleFn({
|
|
35860
|
-
|
|
36454
|
+
// `allowWorktree: false` is what actually stops a rework from opening
|
|
36455
|
+
// its own worktree: creation is driven by the agent kind ('general')
|
|
36456
|
+
// inside runTentacle, not by anything the executor passes per-call.
|
|
36457
|
+
deps: isRework ? { ...this.deps, allowWorktree: false } : this.deps,
|
|
35861
36458
|
args: {
|
|
35862
36459
|
description: node.label,
|
|
35863
|
-
prompt: node.prompt
|
|
36460
|
+
prompt: upstream ? `${node.prompt}
|
|
36461
|
+
${upstream}` : node.prompt,
|
|
35864
36462
|
scope: node.scope,
|
|
35865
36463
|
acceptance: node.acceptance
|
|
35866
36464
|
},
|
|
35867
36465
|
agent,
|
|
35868
|
-
thoroughness:
|
|
36466
|
+
thoroughness: thoroughnessForKind(node.kind),
|
|
35869
36467
|
parentCwd: this.parentCwd,
|
|
36468
|
+
...inheritedCwd ? { cwdOverride: inheritedCwd } : {},
|
|
35870
36469
|
sessionId: this.sessionId,
|
|
35871
36470
|
// Defer merge for writers so the executor controls merge ordering
|
|
35872
|
-
// (Correction 4); explore/verify never create a worktree.
|
|
36471
|
+
// (Correction 4); explore/verify/rework never create a worktree.
|
|
35873
36472
|
deferMerge: usesWorktree,
|
|
35874
36473
|
graphId: graph.id,
|
|
35875
|
-
nodeId: node.id
|
|
36474
|
+
nodeId: node.id,
|
|
36475
|
+
signal: controller.signal
|
|
35876
36476
|
}),
|
|
35877
|
-
agent
|
|
36477
|
+
agent,
|
|
36478
|
+
controller
|
|
35878
36479
|
);
|
|
35879
36480
|
if (res.ok && usesWorktree) {
|
|
35880
36481
|
this.nodeRunState.set(node.id, { worktreeHandle: res.worktreeHandle });
|
|
@@ -35883,41 +36484,116 @@ var init_executor = __esm({
|
|
|
35883
36484
|
}
|
|
35884
36485
|
/**
|
|
35885
36486
|
* Bound a tentacle run to its wall-clock budget (explicit `nodeTimeoutMs`
|
|
35886
|
-
* option, else per-kind — writers get more than readers). On timeout
|
|
35887
|
-
*
|
|
35888
|
-
*
|
|
35889
|
-
*
|
|
35890
|
-
*
|
|
35891
|
-
*
|
|
36487
|
+
* option, else per-kind — writers get more than readers). On timeout the
|
|
36488
|
+
* run is CANCELLED via its AbortSignal, then given `cancelGraceMs` to
|
|
36489
|
+
* unwind. Cancellation lands at the sub-agent's next event boundary (an
|
|
36490
|
+
* async generator only observes `.return()` when it next yields), so a run
|
|
36491
|
+
* blocked on a slow provider call can outlive the grace period — that case
|
|
36492
|
+
* resolves with `cancelled: false` and `applyResult` refuses to re-spawn
|
|
36493
|
+
* the node, since a tentacle that may still be writing must not be joined
|
|
36494
|
+
* by a second one on the same scope.
|
|
36495
|
+
*
|
|
36496
|
+
* Either way `execute()` always settles, so the caller's process can exit
|
|
36497
|
+
* instead of hanging forever on one stuck node.
|
|
35892
36498
|
*/
|
|
35893
|
-
withNodeTimeout(promise2, agent) {
|
|
36499
|
+
async withNodeTimeout(promise2, agent, controller) {
|
|
35894
36500
|
const ms = this.nodeTimeoutMs ?? resolveNodeTimeoutMs(process.env, agent);
|
|
35895
36501
|
if (ms <= 0) return promise2;
|
|
35896
36502
|
let timer;
|
|
36503
|
+
const TIMED_OUT = Symbol("timeout");
|
|
35897
36504
|
const timeout = new Promise((resolve3) => {
|
|
35898
|
-
timer = setTimeout(() =>
|
|
35899
|
-
resolve3({ ok: false, agent, error: `tentacle timed out after ${ms}ms` });
|
|
35900
|
-
}, ms);
|
|
36505
|
+
timer = setTimeout(() => resolve3(TIMED_OUT), ms);
|
|
35901
36506
|
});
|
|
35902
|
-
|
|
36507
|
+
const raced = await Promise.race([promise2, timeout]).finally(() => {
|
|
35903
36508
|
if (timer) clearTimeout(timer);
|
|
35904
36509
|
});
|
|
36510
|
+
if (raced !== TIMED_OUT) return raced;
|
|
36511
|
+
controller?.abort();
|
|
36512
|
+
const graceMs = this.cancelGraceMs ?? resolveCancelGraceMs();
|
|
36513
|
+
let graceTimer;
|
|
36514
|
+
const grace = new Promise((resolve3) => {
|
|
36515
|
+
graceTimer = setTimeout(() => resolve3(TIMED_OUT), graceMs);
|
|
36516
|
+
});
|
|
36517
|
+
const settled = await Promise.race([promise2, grace]).finally(() => {
|
|
36518
|
+
if (graceTimer) clearTimeout(graceTimer);
|
|
36519
|
+
});
|
|
36520
|
+
if (settled !== TIMED_OUT) {
|
|
36521
|
+
if (settled.ok) return settled;
|
|
36522
|
+
return { ...settled, cancelled: true };
|
|
36523
|
+
}
|
|
36524
|
+
return {
|
|
36525
|
+
ok: false,
|
|
36526
|
+
agent,
|
|
36527
|
+
error: `tentacle timed out after ${ms}ms and did not stop within ${graceMs}ms of being cancelled; not retrying to avoid two tentacles writing the same scope`,
|
|
36528
|
+
cancelled: false
|
|
36529
|
+
};
|
|
35905
36530
|
}
|
|
35906
36531
|
/**
|
|
35907
|
-
*
|
|
35908
|
-
*
|
|
35909
|
-
*
|
|
35910
|
-
*
|
|
35911
|
-
*
|
|
35912
|
-
*
|
|
36532
|
+
* The worktree a node should run in, inherited from the writer behind it.
|
|
36533
|
+
*
|
|
36534
|
+
* For a `verify`: verification happens BEFORE the merge node, so when its
|
|
36535
|
+
* writer worked in an isolated worktree the changes are not in the parent
|
|
36536
|
+
* tree yet — a verify tentacle pointed at `parentCwd` was inspecting a tree
|
|
36537
|
+
* that provably did not contain the work it was asked to check, and reported
|
|
36538
|
+
* it missing.
|
|
36539
|
+
*
|
|
36540
|
+
* For a rework: the same tree, for the stronger reason that writing anywhere
|
|
36541
|
+
* else would strand the round on a second branch.
|
|
36542
|
+
*
|
|
36543
|
+
* Returns undefined when there is no single tree: no worktrees (isolation
|
|
36544
|
+
* disabled — the writers edited the parent tree directly), or several
|
|
36545
|
+
* distinct ones, in which case no single cwd is correct and the parent tree
|
|
36546
|
+
* is the honest default.
|
|
36547
|
+
*/
|
|
36548
|
+
inheritedWorktreeCwdFor(node, graph) {
|
|
36549
|
+
const paths = new Set(
|
|
36550
|
+
this.collectWorktreeSources(node, graph).map((s) => s.handle.path)
|
|
36551
|
+
);
|
|
36552
|
+
return paths.size === 1 ? [...paths][0] : void 0;
|
|
36553
|
+
}
|
|
36554
|
+
/**
|
|
36555
|
+
* Resolve the deferred worktrees produced behind a node's dependencies, in
|
|
36556
|
+
* ancestors-first order. Used to decide what a `merge` node must merge, and
|
|
36557
|
+
* which tree a `verify` node should actually inspect.
|
|
36558
|
+
*
|
|
36559
|
+
* A merge node's direct deps are NOT the writers: `buildGraphFromPlan`
|
|
36560
|
+
* injects a `verify` node after every `general` node and points the merge at
|
|
36561
|
+
* those verifies, while worktree handles are recorded against the writer
|
|
36562
|
+
* node ids. Looking only at direct deps therefore found nothing to merge and
|
|
36563
|
+
* silently reported success while every tentacle's work stayed stranded on
|
|
36564
|
+
* its branch. Walk up through non-writer deps until the writers are found.
|
|
36565
|
+
*
|
|
36566
|
+
* Post-order so a writer that depends on another writer merges after it (the
|
|
36567
|
+
* later branch was cut from a HEAD that already contained the earlier work).
|
|
36568
|
+
* `merge` nodes terminate the walk: another merge already owns its subtree.
|
|
36569
|
+
*/
|
|
36570
|
+
collectWorktreeSources(node, graph) {
|
|
36571
|
+
const out = [];
|
|
36572
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36573
|
+
const visit = (id) => {
|
|
36574
|
+
if (seen.has(id)) return;
|
|
36575
|
+
seen.add(id);
|
|
36576
|
+
const n = graph.nodes.get(id);
|
|
36577
|
+
if (!n || n.kind === "merge") return;
|
|
36578
|
+
for (const dep of n.deps) visit(dep);
|
|
36579
|
+
const handle = this.nodeRunState.get(id)?.worktreeHandle;
|
|
36580
|
+
if (handle) out.push({ id, handle });
|
|
36581
|
+
};
|
|
36582
|
+
for (const depId of node.deps) visit(depId);
|
|
36583
|
+
return out;
|
|
36584
|
+
}
|
|
36585
|
+
/**
|
|
36586
|
+
* Sequentially merge every deferred worktree this node covers (in
|
|
36587
|
+
* ancestors-first order) into parent HEAD. Nodes without a recorded worktree
|
|
36588
|
+
* handle (worktree isolation disabled, or a read-only node) are a no-op. On
|
|
36589
|
+
* conflict the branch is kept and the conflict is surfaced in the merge
|
|
36590
|
+
* node's error — remaining sources still attempt to merge (independent
|
|
36591
|
+
* branches shouldn't be blocked by one conflict).
|
|
35913
36592
|
*/
|
|
35914
36593
|
async runMergeNode(node, graph) {
|
|
35915
36594
|
const conflicts = [];
|
|
35916
36595
|
const merged = [];
|
|
35917
|
-
for (const depId of node
|
|
35918
|
-
const state3 = this.nodeRunState.get(depId);
|
|
35919
|
-
const handle = state3?.worktreeHandle;
|
|
35920
|
-
if (!handle) continue;
|
|
36596
|
+
for (const { id: depId, handle } of this.collectWorktreeSources(node, graph)) {
|
|
35921
36597
|
let result;
|
|
35922
36598
|
try {
|
|
35923
36599
|
result = await this.mergeFn(handle, {
|
|
@@ -35935,6 +36611,7 @@ var init_executor = __esm({
|
|
|
35935
36611
|
if (!result.ok) {
|
|
35936
36612
|
conflicts.push(`${depId}: ${result.message}`);
|
|
35937
36613
|
} else {
|
|
36614
|
+
this.nodeRunState.set(depId, { worktreeHandle: null });
|
|
35938
36615
|
merged.push(depId);
|
|
35939
36616
|
}
|
|
35940
36617
|
}
|
|
@@ -35962,9 +36639,31 @@ var init_executor = __esm({
|
|
|
35962
36639
|
node.status = "done";
|
|
35963
36640
|
node.result = res.result;
|
|
35964
36641
|
this.radio("node_end", { description: node.label, agent: node.kind, ok: true });
|
|
36642
|
+
this.reconcileRepairedNode(graph, node);
|
|
36643
|
+
if (node.kind === "verify") this.applyVerifyVerdict(graph, node);
|
|
35965
36644
|
return;
|
|
35966
36645
|
}
|
|
35967
36646
|
node.error = res.error;
|
|
36647
|
+
if (this.aborted) {
|
|
36648
|
+
node.status = "error";
|
|
36649
|
+
this.radio("node_end", {
|
|
36650
|
+
description: node.label,
|
|
36651
|
+
agent: node.kind,
|
|
36652
|
+
detail: res.error,
|
|
36653
|
+
ok: false
|
|
36654
|
+
});
|
|
36655
|
+
return;
|
|
36656
|
+
}
|
|
36657
|
+
if (res.cancelled === false) {
|
|
36658
|
+
node.status = "error";
|
|
36659
|
+
this.radio("node_end", {
|
|
36660
|
+
description: node.label,
|
|
36661
|
+
agent: node.kind,
|
|
36662
|
+
detail: res.error,
|
|
36663
|
+
ok: false
|
|
36664
|
+
});
|
|
36665
|
+
return;
|
|
36666
|
+
}
|
|
35968
36667
|
if (node.retryCount < node.maxRetries) {
|
|
35969
36668
|
node.retryCount += 1;
|
|
35970
36669
|
node.status = "pending";
|
|
@@ -35997,6 +36696,180 @@ var init_executor = __esm({
|
|
|
35997
36696
|
ok: false
|
|
35998
36697
|
});
|
|
35999
36698
|
}
|
|
36699
|
+
/**
|
|
36700
|
+
* A `fix` node just completed the work its failed predecessor could not.
|
|
36701
|
+
* That unit of work IS done — but the predecessor was left terminally
|
|
36702
|
+
* `error`, and since `isConverged` requires every node to be `done`/
|
|
36703
|
+
* `skipped`, a fully repaired graph reported "did not converge" and listed
|
|
36704
|
+
* the repaired node under `failedNodeIds`. The cross-run snapshot then told
|
|
36705
|
+
* the next planner to redo work the fix had already completed.
|
|
36706
|
+
*
|
|
36707
|
+
* Marking it `done` has no scheduling effect (dependents were re-pointed at
|
|
36708
|
+
* the fix when it was spawned) — it is purely how the run is reported. The
|
|
36709
|
+
* original failure stays visible as the separate `fix: …` node and in the
|
|
36710
|
+
* repaired node's result line.
|
|
36711
|
+
*/
|
|
36712
|
+
reconcileRepairedNode(graph, fixNode) {
|
|
36713
|
+
const failedId = this.repairs.get(fixNode.id);
|
|
36714
|
+
if (!failedId) return;
|
|
36715
|
+
const failed = graph.nodes.get(failedId);
|
|
36716
|
+
if (!failed || failed.status !== "error") return;
|
|
36717
|
+
const original = failed.error ? ` (original failure: ${failed.error})` : "";
|
|
36718
|
+
failed.status = "done";
|
|
36719
|
+
failed.result = `repaired by "${fixNode.label}"${original}${fixNode.result ? `: ${fixNode.result}` : ""}`;
|
|
36720
|
+
failed.error = void 0;
|
|
36721
|
+
this.radio("node_end", {
|
|
36722
|
+
description: failed.label,
|
|
36723
|
+
agent: failed.kind,
|
|
36724
|
+
detail: `repaired by ${fixNode.id}`,
|
|
36725
|
+
ok: true
|
|
36726
|
+
});
|
|
36727
|
+
}
|
|
36728
|
+
/**
|
|
36729
|
+
* Act on what a completed `verify` node concluded.
|
|
36730
|
+
*
|
|
36731
|
+
* The verify itself stays `done` either way — it did its job, and doing it
|
|
36732
|
+
* well means being free to say "no". A FAIL instead sends the WRITER back
|
|
36733
|
+
* through a bounded rework round.
|
|
36734
|
+
*
|
|
36735
|
+
* Without this the verdict text was never read: a verify that reported the
|
|
36736
|
+
* work as wrong was recorded exactly like one that reported it correct, the
|
|
36737
|
+
* graph converged over the defect, and the only iteration the engine could
|
|
36738
|
+
* do was on execution failure. An `unknown` verdict (no parseable trailer)
|
|
36739
|
+
* is deliberately non-blocking — a prompt drift must not be able to wedge
|
|
36740
|
+
* every graph — but it is recorded, because a gate that has silently stopped
|
|
36741
|
+
* working is worse than no gate.
|
|
36742
|
+
*/
|
|
36743
|
+
applyVerifyVerdict(graph, verify) {
|
|
36744
|
+
const { verdict, findings } = parseVerifyVerdict(verify.result);
|
|
36745
|
+
if (verdict === "pass") return;
|
|
36746
|
+
const writer = this.writerBehind(verify, graph);
|
|
36747
|
+
if (!writer) return;
|
|
36748
|
+
if (verdict === "unknown") {
|
|
36749
|
+
this.unresolved.push({
|
|
36750
|
+
nodeId: writer.id,
|
|
36751
|
+
label: writer.label,
|
|
36752
|
+
reason: "unknown",
|
|
36753
|
+
findings: findings || "(verify produced no parseable VERDICT line)"
|
|
36754
|
+
});
|
|
36755
|
+
return;
|
|
36756
|
+
}
|
|
36757
|
+
const root = this.reviewLineage.get(writer.id) ?? writer.id;
|
|
36758
|
+
const spent = this.reviewRounds.get(root) ?? 0;
|
|
36759
|
+
if (this.aborted || spent >= this.maxReviewRounds) {
|
|
36760
|
+
this.unresolved.push({
|
|
36761
|
+
nodeId: writer.id,
|
|
36762
|
+
label: writer.label,
|
|
36763
|
+
reason: "fail",
|
|
36764
|
+
findings
|
|
36765
|
+
});
|
|
36766
|
+
writer.result = `${writer.result ?? ""}
|
|
36767
|
+
|
|
36768
|
+
[accepted with unresolved verify findings from ` + `"${verify.label}"]${findings ? `: ${firstLine(findings)}` : ""}`.trim();
|
|
36769
|
+
this.radio("node_end", {
|
|
36770
|
+
description: writer.label,
|
|
36771
|
+
agent: writer.kind,
|
|
36772
|
+
detail: this.aborted ? "verify FAIL left unresolved (run cancelled)" : `verify FAIL left unresolved (rework budget ${this.maxReviewRounds} spent)`,
|
|
36773
|
+
ok: false
|
|
36774
|
+
});
|
|
36775
|
+
return;
|
|
36776
|
+
}
|
|
36777
|
+
this.reviewRounds.set(root, spent + 1);
|
|
36778
|
+
this.spawnReworkPair(graph, writer, verify, findings, root, spent + 1);
|
|
36779
|
+
}
|
|
36780
|
+
/**
|
|
36781
|
+
* The writer whose work a `verify` node judged.
|
|
36782
|
+
*
|
|
36783
|
+
* Walks up through non-writer deps, the same shape `collectWorktreeSources`
|
|
36784
|
+
* relies on: a verify's dep is normally its writer directly, but after a
|
|
36785
|
+
* rework round the chain is writer → verify → rework → verify, and the
|
|
36786
|
+
* rework (a `fix` node) is itself the writer to send back.
|
|
36787
|
+
*/
|
|
36788
|
+
writerBehind(verify, graph) {
|
|
36789
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36790
|
+
const visit = (id) => {
|
|
36791
|
+
if (seen.has(id)) return void 0;
|
|
36792
|
+
seen.add(id);
|
|
36793
|
+
const n = graph.nodes.get(id);
|
|
36794
|
+
if (!n || n.kind === "merge") return void 0;
|
|
36795
|
+
if (n.kind === "general" || n.kind === "fix") return n;
|
|
36796
|
+
for (const dep of n.deps) {
|
|
36797
|
+
const found = visit(dep);
|
|
36798
|
+
if (found) return found;
|
|
36799
|
+
}
|
|
36800
|
+
return void 0;
|
|
36801
|
+
};
|
|
36802
|
+
for (const depId of verify.deps) {
|
|
36803
|
+
const found = visit(depId);
|
|
36804
|
+
if (found) return found;
|
|
36805
|
+
}
|
|
36806
|
+
return void 0;
|
|
36807
|
+
}
|
|
36808
|
+
/**
|
|
36809
|
+
* Send a writer's work back for one more round: a rework node carrying the
|
|
36810
|
+
* verify's findings, plus a fresh verify to judge the result.
|
|
36811
|
+
*
|
|
36812
|
+
* The rework runs INSIDE the writer's worktree instead of creating one of
|
|
36813
|
+
* its own. Two worktrees for one scope means two branches, and the merge
|
|
36814
|
+
* node walks up to the writer — so a rework on its own branch would be
|
|
36815
|
+
* merged never or twice, exactly the stranded-work failure the merge fix
|
|
36816
|
+
* addressed. `allowWorktree: false` on this node's deps suppresses creation,
|
|
36817
|
+
* and `cwdOverride` (resolved via {@link inheritedWorktreeCwdFor}) points it
|
|
36818
|
+
* at the existing tree; the handle stays registered against the writer.
|
|
36819
|
+
*
|
|
36820
|
+
* Acyclicity is preserved by construction: both new nodes point only at
|
|
36821
|
+
* nodes that already exist, and the rewiring moves an existing edge forward
|
|
36822
|
+
* along the chain rather than back into it.
|
|
36823
|
+
*/
|
|
36824
|
+
spawnReworkPair(graph, writer, verify, findings, root, round) {
|
|
36825
|
+
const reworkId = `rework-${root}-${round}`;
|
|
36826
|
+
const reworkNode = {
|
|
36827
|
+
id: reworkId,
|
|
36828
|
+
kind: "fix",
|
|
36829
|
+
label: `rework: ${writer.label}`,
|
|
36830
|
+
prompt: `A reviewer inspected this work on disk and REJECTED it. Address every finding below, then leave the work in a state that satisfies the original task.
|
|
36831
|
+
|
|
36832
|
+
## Original task
|
|
36833
|
+
${writer.prompt}
|
|
36834
|
+
|
|
36835
|
+
## Reviewer findings (these are what must change)
|
|
36836
|
+
${findings || "(the reviewer reported FAIL without detail)"}`,
|
|
36837
|
+
...writer.scope ? { scope: writer.scope } : {},
|
|
36838
|
+
...writer.acceptance ? { acceptance: writer.acceptance } : {},
|
|
36839
|
+
// The verify is already `done`, so the rework is immediately ready.
|
|
36840
|
+
deps: [verify.id],
|
|
36841
|
+
status: "pending",
|
|
36842
|
+
retryCount: 0,
|
|
36843
|
+
maxRetries: 0
|
|
36844
|
+
};
|
|
36845
|
+
graph.nodes.set(reworkId, reworkNode);
|
|
36846
|
+
this.reworks.set(reworkId, writer.id);
|
|
36847
|
+
this.reviewLineage.set(reworkId, root);
|
|
36848
|
+
const reVerifyId = `verify-${reworkId}`;
|
|
36849
|
+
const reVerifyNode = {
|
|
36850
|
+
id: reVerifyId,
|
|
36851
|
+
kind: "verify",
|
|
36852
|
+
label: `verify: ${writer.label} (rework ${round})`,
|
|
36853
|
+
prompt: verify.prompt,
|
|
36854
|
+
deps: [reworkId],
|
|
36855
|
+
status: "pending",
|
|
36856
|
+
retryCount: 0,
|
|
36857
|
+
maxRetries: verify.maxRetries
|
|
36858
|
+
};
|
|
36859
|
+
graph.nodes.set(reVerifyId, reVerifyNode);
|
|
36860
|
+
for (const other of graph.nodes.values()) {
|
|
36861
|
+
if (other.id === reworkId || other.id === reVerifyId) continue;
|
|
36862
|
+
if (other.deps.includes(verify.id)) {
|
|
36863
|
+
other.deps = other.deps.map((d) => d === verify.id ? reVerifyId : d);
|
|
36864
|
+
}
|
|
36865
|
+
}
|
|
36866
|
+
this.radio("node_fix", {
|
|
36867
|
+
description: reworkNode.label,
|
|
36868
|
+
agent: "fix",
|
|
36869
|
+
detail: `verify FAIL on "${writer.label}" \u2014 rework round ${round}/${this.maxReviewRounds}`,
|
|
36870
|
+
ok: false
|
|
36871
|
+
});
|
|
36872
|
+
}
|
|
36000
36873
|
/**
|
|
36001
36874
|
* Create a `fix` node that attempts to redo the failed node's work, wired
|
|
36002
36875
|
* so downstream dependents of the failed node also wait on the fix.
|
|
@@ -36023,6 +36896,7 @@ ${failed.error ?? "unknown error"}`,
|
|
|
36023
36896
|
// no further retries — one fix attempt per failed node in v1
|
|
36024
36897
|
};
|
|
36025
36898
|
graph.nodes.set(fixId, fixNode);
|
|
36899
|
+
this.repairs.set(fixId, failed.id);
|
|
36026
36900
|
for (const other of graph.nodes.values()) {
|
|
36027
36901
|
if (other.id === fixId) continue;
|
|
36028
36902
|
if (other.deps.includes(failed.id)) {
|
|
@@ -36453,10 +37327,10 @@ var init_prereqChecks = __esm({
|
|
|
36453
37327
|
|
|
36454
37328
|
// src/cli/plugins/prefs.ts
|
|
36455
37329
|
import { existsSync as existsSync36, readFileSync as readFileSync29, writeFileSync as writeFileSync18, mkdirSync as mkdirSync15 } from "node:fs";
|
|
36456
|
-
import
|
|
37330
|
+
import path36 from "node:path";
|
|
36457
37331
|
import os9 from "node:os";
|
|
36458
37332
|
function getPluginPrefsPath() {
|
|
36459
|
-
return process.env.ZELARI_PLUGINS_PREFS_FILE ??
|
|
37333
|
+
return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path36.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
|
|
36460
37334
|
}
|
|
36461
37335
|
function getPluginPrefs() {
|
|
36462
37336
|
const file2 = getPluginPrefsPath();
|
|
@@ -36477,7 +37351,7 @@ function getPluginPrefs() {
|
|
|
36477
37351
|
}
|
|
36478
37352
|
function writePluginPrefs(prefs) {
|
|
36479
37353
|
const file2 = getPluginPrefsPath();
|
|
36480
|
-
mkdirSync15(
|
|
37354
|
+
mkdirSync15(path36.dirname(file2), { recursive: true });
|
|
36481
37355
|
writeFileSync18(file2, JSON.stringify(prefs, null, 2), {
|
|
36482
37356
|
encoding: "utf-8",
|
|
36483
37357
|
mode: 384
|
|
@@ -36514,7 +37388,7 @@ __export(registry_exports, {
|
|
|
36514
37388
|
isBinaryOnPath: () => isBinaryOnPath
|
|
36515
37389
|
});
|
|
36516
37390
|
import { existsSync as existsSync37 } from "node:fs";
|
|
36517
|
-
import
|
|
37391
|
+
import path37 from "node:path";
|
|
36518
37392
|
function detectLocalBin(bin) {
|
|
36519
37393
|
return (cwd) => {
|
|
36520
37394
|
try {
|
|
@@ -36532,7 +37406,7 @@ function isBinaryOnPath(bin, opts = {}) {
|
|
|
36532
37406
|
const platform = opts.platform ?? process.platform;
|
|
36533
37407
|
const exists = opts.exists ?? existsSync37;
|
|
36534
37408
|
const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
|
|
36535
|
-
const pathMod = platform === "win32" ?
|
|
37409
|
+
const pathMod = platform === "win32" ? path37.win32 : path37.posix;
|
|
36536
37410
|
const sep2 = platform === "win32" ? ";" : ":";
|
|
36537
37411
|
const dirs = pathEnv.split(sep2).filter((d) => d.length > 0);
|
|
36538
37412
|
const candidates = [bin];
|
|
@@ -36841,10 +37715,10 @@ __export(triggerLock_exports, {
|
|
|
36841
37715
|
lockPath: () => lockPath,
|
|
36842
37716
|
releaseLock: () => releaseLock
|
|
36843
37717
|
});
|
|
36844
|
-
import { promises as
|
|
36845
|
-
import * as
|
|
37718
|
+
import { promises as fs24 } from "node:fs";
|
|
37719
|
+
import * as path42 from "node:path";
|
|
36846
37720
|
function lockPath(projectRoot) {
|
|
36847
|
-
return
|
|
37721
|
+
return path42.join(projectRoot, ".zelari", "trigger.lock");
|
|
36848
37722
|
}
|
|
36849
37723
|
function isPidAlive(pid) {
|
|
36850
37724
|
try {
|
|
@@ -36857,10 +37731,10 @@ function isPidAlive(pid) {
|
|
|
36857
37731
|
}
|
|
36858
37732
|
async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date()) {
|
|
36859
37733
|
const lp = lockPath(projectRoot);
|
|
36860
|
-
const dir =
|
|
36861
|
-
await
|
|
37734
|
+
const dir = path42.dirname(lp);
|
|
37735
|
+
await fs24.mkdir(dir, { recursive: true });
|
|
36862
37736
|
try {
|
|
36863
|
-
const raw = await
|
|
37737
|
+
const raw = await fs24.readFile(lp, "utf8");
|
|
36864
37738
|
const existing = JSON.parse(raw);
|
|
36865
37739
|
if (existing.pid && isPidAlive(existing.pid)) {
|
|
36866
37740
|
return { acquired: false, heldBy: existing.pid, lockPath: lp };
|
|
@@ -36871,13 +37745,13 @@ async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date())
|
|
|
36871
37745
|
pid: process.pid,
|
|
36872
37746
|
acquiredAt: now().toISOString()
|
|
36873
37747
|
};
|
|
36874
|
-
await
|
|
37748
|
+
await fs24.writeFile(lp, JSON.stringify(payload, null, 2) + "\n", "utf8");
|
|
36875
37749
|
return { acquired: true, lockPath: lp };
|
|
36876
37750
|
}
|
|
36877
37751
|
async function releaseLock(projectRoot) {
|
|
36878
37752
|
const lp = lockPath(projectRoot);
|
|
36879
37753
|
try {
|
|
36880
|
-
await
|
|
37754
|
+
await fs24.unlink(lp);
|
|
36881
37755
|
} catch {
|
|
36882
37756
|
}
|
|
36883
37757
|
}
|
|
@@ -37165,12 +38039,12 @@ function ensureHome() {
|
|
|
37165
38039
|
}
|
|
37166
38040
|
}
|
|
37167
38041
|
function loadCompanionConfig() {
|
|
37168
|
-
const
|
|
37169
|
-
if (!existsSync43(
|
|
38042
|
+
const path44 = getCompanionConfigPath();
|
|
38043
|
+
if (!existsSync43(path44)) {
|
|
37170
38044
|
return { projects: [] };
|
|
37171
38045
|
}
|
|
37172
38046
|
try {
|
|
37173
|
-
const raw = JSON.parse(readFileSync34(
|
|
38047
|
+
const raw = JSON.parse(readFileSync34(path44, "utf8"));
|
|
37174
38048
|
const projects = Array.isArray(raw.projects) ? raw.projects.filter(
|
|
37175
38049
|
(p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
|
|
37176
38050
|
).map((p3) => ({
|
|
@@ -37208,16 +38082,16 @@ function loadOrCreateToken(explicit) {
|
|
|
37208
38082
|
return { token: explicit.trim(), created: false };
|
|
37209
38083
|
}
|
|
37210
38084
|
ensureHome();
|
|
37211
|
-
const
|
|
37212
|
-
if (existsSync43(
|
|
37213
|
-
const t = readFileSync34(
|
|
38085
|
+
const path44 = getCompanionTokenPath();
|
|
38086
|
+
if (existsSync43(path44)) {
|
|
38087
|
+
const t = readFileSync34(path44, "utf8").trim();
|
|
37214
38088
|
if (t) return { token: t, created: false };
|
|
37215
38089
|
}
|
|
37216
38090
|
const token = randomBytes3(24).toString("base64url");
|
|
37217
|
-
writeFileSync21(
|
|
38091
|
+
writeFileSync21(path44, token + "\n", "utf8");
|
|
37218
38092
|
try {
|
|
37219
|
-
const
|
|
37220
|
-
|
|
38093
|
+
const fs25 = __require("node:fs");
|
|
38094
|
+
fs25.chmodSync?.(path44, 384);
|
|
37221
38095
|
} catch {
|
|
37222
38096
|
}
|
|
37223
38097
|
return { token, created: true };
|
|
@@ -37242,17 +38116,17 @@ function mergeProjects(cfg, extraPaths) {
|
|
|
37242
38116
|
byId.set(p3.id, p3);
|
|
37243
38117
|
}
|
|
37244
38118
|
for (const raw of extraPaths) {
|
|
37245
|
-
const
|
|
37246
|
-
if (!
|
|
37247
|
-
let id = slugFromPath(
|
|
38119
|
+
const path44 = raw.trim();
|
|
38120
|
+
if (!path44) continue;
|
|
38121
|
+
let id = slugFromPath(path44);
|
|
37248
38122
|
let n = 2;
|
|
37249
|
-
while (byId.has(id) && byId.get(id).path !==
|
|
37250
|
-
id = `${slugFromPath(
|
|
38123
|
+
while (byId.has(id) && byId.get(id).path !== path44) {
|
|
38124
|
+
id = `${slugFromPath(path44)}-${n++}`;
|
|
37251
38125
|
}
|
|
37252
38126
|
byId.set(id, {
|
|
37253
38127
|
id,
|
|
37254
|
-
name: slugFromPath(
|
|
37255
|
-
path:
|
|
38128
|
+
name: slugFromPath(path44),
|
|
38129
|
+
path: path44
|
|
37256
38130
|
});
|
|
37257
38131
|
}
|
|
37258
38132
|
return [...byId.values()];
|
|
@@ -37629,9 +38503,9 @@ async function runCompanionServe(opts = {}) {
|
|
|
37629
38503
|
return;
|
|
37630
38504
|
}
|
|
37631
38505
|
const url2 = parseUrl(req);
|
|
37632
|
-
const
|
|
38506
|
+
const path44 = url2.pathname.replace(/\/+$/, "") || "/";
|
|
37633
38507
|
try {
|
|
37634
|
-
if (req.method === "GET" && (
|
|
38508
|
+
if (req.method === "GET" && (path44 === "/health" || path44 === "/v1/health")) {
|
|
37635
38509
|
sendJson(res, 200, {
|
|
37636
38510
|
ok: true,
|
|
37637
38511
|
service: "zelari-companion",
|
|
@@ -37643,18 +38517,18 @@ async function runCompanionServe(opts = {}) {
|
|
|
37643
38517
|
});
|
|
37644
38518
|
return;
|
|
37645
38519
|
}
|
|
37646
|
-
if (
|
|
38520
|
+
if (path44.startsWith("/v1")) {
|
|
37647
38521
|
if (!tokenMatches(token, getBearer(req))) {
|
|
37648
38522
|
sendJson(res, 401, { ok: false, error: "unauthorized" });
|
|
37649
38523
|
return;
|
|
37650
38524
|
}
|
|
37651
38525
|
}
|
|
37652
|
-
if (req.method === "GET" &&
|
|
38526
|
+
if (req.method === "GET" && path44 === "/v1/config") {
|
|
37653
38527
|
const snap = buildDesktopConfigSnapshot();
|
|
37654
38528
|
sendJson(res, 200, { ok: true, ...snap });
|
|
37655
38529
|
return;
|
|
37656
38530
|
}
|
|
37657
|
-
if (req.method === "GET" &&
|
|
38531
|
+
if (req.method === "GET" && path44 === "/v1/projects") {
|
|
37658
38532
|
sendJson(res, 200, {
|
|
37659
38533
|
ok: true,
|
|
37660
38534
|
projects: projects.map((p3) => ({
|
|
@@ -37665,7 +38539,7 @@ async function runCompanionServe(opts = {}) {
|
|
|
37665
38539
|
});
|
|
37666
38540
|
return;
|
|
37667
38541
|
}
|
|
37668
|
-
if (req.method === "GET" &&
|
|
38542
|
+
if (req.method === "GET" && path44 === "/v1/runs") {
|
|
37669
38543
|
sendJson(res, 200, {
|
|
37670
38544
|
ok: true,
|
|
37671
38545
|
active: runs.getActive(),
|
|
@@ -37683,7 +38557,7 @@ async function runCompanionServe(opts = {}) {
|
|
|
37683
38557
|
});
|
|
37684
38558
|
return;
|
|
37685
38559
|
}
|
|
37686
|
-
if (req.method === "POST" &&
|
|
38560
|
+
if (req.method === "POST" && path44 === "/v1/runs") {
|
|
37687
38561
|
const raw = await readBody(req);
|
|
37688
38562
|
let body = {};
|
|
37689
38563
|
try {
|
|
@@ -37730,7 +38604,7 @@ async function runCompanionServe(opts = {}) {
|
|
|
37730
38604
|
});
|
|
37731
38605
|
return;
|
|
37732
38606
|
}
|
|
37733
|
-
const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(
|
|
38607
|
+
const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path44);
|
|
37734
38608
|
if (req.method === "GET" && eventsMatch) {
|
|
37735
38609
|
const runId = eventsMatch[1];
|
|
37736
38610
|
const run = runs.getRun(runId);
|
|
@@ -37795,7 +38669,7 @@ async function runCompanionServe(opts = {}) {
|
|
|
37795
38669
|
}, 500);
|
|
37796
38670
|
return;
|
|
37797
38671
|
}
|
|
37798
|
-
const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(
|
|
38672
|
+
const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path44);
|
|
37799
38673
|
if (req.method === "POST" && cancelMatch) {
|
|
37800
38674
|
const runId = cancelMatch[1];
|
|
37801
38675
|
const result = runs.cancel(runId);
|
|
@@ -37907,11 +38781,11 @@ import { execSync as execSync2 } from "node:child_process";
|
|
|
37907
38781
|
import { existsSync as existsSync45, readFileSync as readFileSync35, readlinkSync, statSync as statSync7 } from "node:fs";
|
|
37908
38782
|
import { createRequire as createRequire3 } from "node:module";
|
|
37909
38783
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
37910
|
-
import
|
|
38784
|
+
import path43 from "node:path";
|
|
37911
38785
|
function findPackageRoot(start) {
|
|
37912
38786
|
let dir = start;
|
|
37913
38787
|
for (let i = 0; i < 6; i += 1) {
|
|
37914
|
-
const candidate =
|
|
38788
|
+
const candidate = path43.join(dir, "package.json");
|
|
37915
38789
|
if (existsSync45(candidate)) {
|
|
37916
38790
|
try {
|
|
37917
38791
|
const pkg = JSON.parse(readFileSync35(candidate, "utf8"));
|
|
@@ -37919,11 +38793,11 @@ function findPackageRoot(start) {
|
|
|
37919
38793
|
} catch {
|
|
37920
38794
|
}
|
|
37921
38795
|
}
|
|
37922
|
-
const parent =
|
|
38796
|
+
const parent = path43.dirname(dir);
|
|
37923
38797
|
if (parent === dir) break;
|
|
37924
38798
|
dir = parent;
|
|
37925
38799
|
}
|
|
37926
|
-
return
|
|
38800
|
+
return path43.resolve(__dirname3, "..", "..", "..");
|
|
37927
38801
|
}
|
|
37928
38802
|
function tryExec(cmd) {
|
|
37929
38803
|
try {
|
|
@@ -37937,7 +38811,7 @@ function tryExec(cmd) {
|
|
|
37937
38811
|
}
|
|
37938
38812
|
function readPackageJson3() {
|
|
37939
38813
|
try {
|
|
37940
|
-
const pkgPath =
|
|
38814
|
+
const pkgPath = path43.join(packageRoot, "package.json");
|
|
37941
38815
|
return JSON.parse(readFileSync35(pkgPath, "utf8"));
|
|
37942
38816
|
} catch {
|
|
37943
38817
|
return null;
|
|
@@ -37953,7 +38827,7 @@ function checkShim(pkgName) {
|
|
|
37953
38827
|
}
|
|
37954
38828
|
const isWin = process.platform === "win32";
|
|
37955
38829
|
const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
|
|
37956
|
-
const shimPath =
|
|
38830
|
+
const shimPath = path43.join(prefix, shimName);
|
|
37957
38831
|
if (!existsSync45(shimPath)) {
|
|
37958
38832
|
return FAIL(
|
|
37959
38833
|
`shim not found at ${shimPath}
|
|
@@ -37981,8 +38855,8 @@ function checkShim(pkgName) {
|
|
|
37981
38855
|
fix: npm install -g ${pkgName}@latest --force`
|
|
37982
38856
|
);
|
|
37983
38857
|
}
|
|
37984
|
-
const resolved =
|
|
37985
|
-
const expected =
|
|
38858
|
+
const resolved = path43.resolve(path43.dirname(shimPath), target);
|
|
38859
|
+
const expected = path43.join(
|
|
37986
38860
|
prefix,
|
|
37987
38861
|
"node_modules",
|
|
37988
38862
|
pkgName,
|
|
@@ -38021,7 +38895,7 @@ function checkNode(pkg) {
|
|
|
38021
38895
|
return OK(`node ${raw}`);
|
|
38022
38896
|
}
|
|
38023
38897
|
function checkBundle() {
|
|
38024
|
-
const bundle =
|
|
38898
|
+
const bundle = path43.join(packageRoot, "dist", "cli", "main.bundled.js");
|
|
38025
38899
|
if (!existsSync45(bundle)) {
|
|
38026
38900
|
return FAIL(
|
|
38027
38901
|
`dist/cli/main.bundled.js missing at ${bundle}
|
|
@@ -38042,7 +38916,7 @@ function checkRuntimeDeps() {
|
|
|
38042
38916
|
const missing = [];
|
|
38043
38917
|
for (const dep of required2) {
|
|
38044
38918
|
try {
|
|
38045
|
-
const localReq = createRequire3(
|
|
38919
|
+
const localReq = createRequire3(path43.join(packageRoot, "package.json"));
|
|
38046
38920
|
localReq.resolve(dep);
|
|
38047
38921
|
} catch {
|
|
38048
38922
|
missing.push(dep);
|
|
@@ -38218,7 +39092,7 @@ var init_doctor = __esm({
|
|
|
38218
39092
|
"use strict";
|
|
38219
39093
|
init_prereqChecks();
|
|
38220
39094
|
require3 = createRequire3(import.meta.url);
|
|
38221
|
-
__dirname3 =
|
|
39095
|
+
__dirname3 = path43.dirname(fileURLToPath2(import.meta.url));
|
|
38222
39096
|
packageRoot = findPackageRoot(__dirname3);
|
|
38223
39097
|
OK = (message) => ({
|
|
38224
39098
|
ok: true,
|
|
@@ -44633,6 +45507,7 @@ init_krakenRadio();
|
|
|
44633
45507
|
init_auditLogger();
|
|
44634
45508
|
init_toolRegistry();
|
|
44635
45509
|
init_planner();
|
|
45510
|
+
init_graphMemory();
|
|
44636
45511
|
init_executor();
|
|
44637
45512
|
init_graphStatus();
|
|
44638
45513
|
async function handleKrakenGraph(ctx, prompt) {
|
|
@@ -44657,19 +45532,36 @@ async function handleKrakenGraph(ctx, prompt) {
|
|
|
44657
45532
|
})
|
|
44658
45533
|
};
|
|
44659
45534
|
try {
|
|
44660
|
-
const
|
|
45535
|
+
const previous = await loadGraphSnapshot(ctx.cwd);
|
|
45536
|
+
const previousAttempt = formatSnapshotForPlanner(previous);
|
|
45537
|
+
if (previousAttempt) {
|
|
45538
|
+
appendSystem(ctx.setMessages, "[kraken] resuming from the previous unfinished graph");
|
|
45539
|
+
}
|
|
45540
|
+
const graph = await planTaskGraph({
|
|
45541
|
+
prompt,
|
|
45542
|
+
graphId: `kraken-${Date.now().toString(36)}`,
|
|
45543
|
+
cwd: ctx.cwd,
|
|
45544
|
+
...previousAttempt ? { previousAttempt } : {}
|
|
45545
|
+
});
|
|
44661
45546
|
appendSystem(ctx.setMessages, formatKrakenGraphAscii(graph));
|
|
44662
45547
|
const executor = new KrakenGraphExecutor({
|
|
44663
45548
|
taskToolDeps,
|
|
44664
45549
|
parentCwd: ctx.cwd,
|
|
44665
|
-
sessionId: ctx.sessionId
|
|
45550
|
+
sessionId: ctx.sessionId,
|
|
45551
|
+
goal: prompt
|
|
44666
45552
|
});
|
|
44667
45553
|
const summary = await executor.execute(graph);
|
|
45554
|
+
const digest = formatKrakenGraphDigest(summary.graph, {
|
|
45555
|
+
durationsMs: summary.durationsMs,
|
|
45556
|
+
unresolvedFindings: summary.unresolvedFindings
|
|
45557
|
+
});
|
|
44668
45558
|
appendSystem(
|
|
44669
45559
|
ctx.setMessages,
|
|
44670
45560
|
`${formatKrakenGraphAscii(summary.graph)}
|
|
44671
45561
|
|
|
44672
|
-
|
|
45562
|
+
${digest}
|
|
45563
|
+
|
|
45564
|
+
` + (summary.converged ? "[kraken] graph converged." : summary.cancelled ? "[kraken] graph cancelled." : `[kraken] graph did not converge \u2014 failed: ${summary.failedNodeIds.join(", ") || "none"}`)
|
|
44673
45565
|
);
|
|
44674
45566
|
} catch (err) {
|
|
44675
45567
|
appendSystem(
|
|
@@ -44976,17 +45868,17 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
|
|
|
44976
45868
|
}
|
|
44977
45869
|
|
|
44978
45870
|
// src/cli/slashHandlers/promoteMember.ts
|
|
44979
|
-
import { promises as
|
|
44980
|
-
import
|
|
45871
|
+
import { promises as fs20 } from "node:fs";
|
|
45872
|
+
import path38 from "node:path";
|
|
44981
45873
|
import os10 from "node:os";
|
|
44982
45874
|
async function handlePromoteMember(ctx, memberId) {
|
|
44983
45875
|
try {
|
|
44984
45876
|
const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
|
|
44985
45877
|
const { skill, markdown } = promoteMember2(memberId);
|
|
44986
|
-
const skillDir = process.env.ANATHEMA_SKILL_DIR ??
|
|
44987
|
-
await
|
|
44988
|
-
const filePath =
|
|
44989
|
-
await
|
|
45878
|
+
const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path38.join(os10.homedir(), ".tmp", "zelari-code", "skills");
|
|
45879
|
+
await fs20.mkdir(skillDir, { recursive: true });
|
|
45880
|
+
const filePath = path38.join(skillDir, `${skill.id}.md`);
|
|
45881
|
+
await fs20.writeFile(filePath, markdown, "utf8");
|
|
44990
45882
|
appendSystem(
|
|
44991
45883
|
ctx.setMessages,
|
|
44992
45884
|
`[promote-member] ${skill.name} (${memberId}) \u2192 ${filePath}
|
|
@@ -45002,25 +45894,25 @@ async function handlePromoteMember(ctx, memberId) {
|
|
|
45002
45894
|
}
|
|
45003
45895
|
|
|
45004
45896
|
// src/cli/branchManager.ts
|
|
45005
|
-
import { promises as
|
|
45006
|
-
import
|
|
45897
|
+
import { promises as fs21, existsSync as existsSync38, readFileSync as readFileSync30, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16, statSync as statSync4, rmSync as rmSync3 } from "node:fs";
|
|
45898
|
+
import path39 from "node:path";
|
|
45007
45899
|
import os11 from "node:os";
|
|
45008
45900
|
var META_FILENAME = "meta.json";
|
|
45009
45901
|
var SESSIONS_SUBDIR = "sessions";
|
|
45010
45902
|
function getBranchesBaseDir() {
|
|
45011
|
-
return process.env.ANATHEMA_BRANCHES_DIR ??
|
|
45903
|
+
return process.env.ANATHEMA_BRANCHES_DIR ?? path39.join(os11.homedir(), ".tmp", "zelari-code", "branches");
|
|
45012
45904
|
}
|
|
45013
45905
|
function getSessionsBaseDir() {
|
|
45014
|
-
return process.env.ANATHEMA_SESSIONS_DIR ??
|
|
45906
|
+
return process.env.ANATHEMA_SESSIONS_DIR ?? path39.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
|
|
45015
45907
|
}
|
|
45016
45908
|
function branchPathFor(name, baseDir) {
|
|
45017
|
-
return
|
|
45909
|
+
return path39.join(baseDir, name);
|
|
45018
45910
|
}
|
|
45019
45911
|
function metaPathFor(name, baseDir) {
|
|
45020
|
-
return
|
|
45912
|
+
return path39.join(baseDir, name, META_FILENAME);
|
|
45021
45913
|
}
|
|
45022
45914
|
function sessionsPathFor(name, baseDir) {
|
|
45023
|
-
return
|
|
45915
|
+
return path39.join(baseDir, name, SESSIONS_SUBDIR);
|
|
45024
45916
|
}
|
|
45025
45917
|
function readBranchMeta(name, baseDir) {
|
|
45026
45918
|
const metaPath = metaPathFor(name, baseDir);
|
|
@@ -45045,13 +45937,13 @@ function readBranchMeta(name, baseDir) {
|
|
|
45045
45937
|
}
|
|
45046
45938
|
function writeBranchMeta(name, baseDir, meta3) {
|
|
45047
45939
|
const metaPath = metaPathFor(name, baseDir);
|
|
45048
|
-
mkdirSync16(
|
|
45940
|
+
mkdirSync16(path39.dirname(metaPath), { recursive: true });
|
|
45049
45941
|
writeFileSync19(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
|
|
45050
45942
|
}
|
|
45051
45943
|
async function countSessions(name, baseDir) {
|
|
45052
45944
|
const sessionsPath = sessionsPathFor(name, baseDir);
|
|
45053
45945
|
try {
|
|
45054
|
-
const entries = await
|
|
45946
|
+
const entries = await fs21.readdir(sessionsPath);
|
|
45055
45947
|
return entries.filter((e) => e.endsWith(".jsonl")).length;
|
|
45056
45948
|
} catch (err) {
|
|
45057
45949
|
if (err.code === "ENOENT") return 0;
|
|
@@ -45096,15 +45988,15 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
|
|
|
45096
45988
|
if (branchExists(name, baseDir)) {
|
|
45097
45989
|
throw new BranchAlreadyExistsError(name);
|
|
45098
45990
|
}
|
|
45099
|
-
const sourcePath =
|
|
45991
|
+
const sourcePath = path39.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
|
|
45100
45992
|
if (!existsSync38(sourcePath)) {
|
|
45101
45993
|
throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
|
|
45102
45994
|
}
|
|
45103
45995
|
const branchPath = branchPathFor(name, baseDir);
|
|
45104
45996
|
const branchSessionsPath = sessionsPathFor(name, baseDir);
|
|
45105
45997
|
mkdirSync16(branchSessionsPath, { recursive: true });
|
|
45106
|
-
const destPath =
|
|
45107
|
-
await
|
|
45998
|
+
const destPath = path39.join(branchSessionsPath, `${fromSessionId}.jsonl`);
|
|
45999
|
+
await fs21.copyFile(sourcePath, destPath);
|
|
45108
46000
|
const meta3 = {
|
|
45109
46001
|
name,
|
|
45110
46002
|
createdAt: Date.now(),
|
|
@@ -45122,7 +46014,7 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
|
|
|
45122
46014
|
async function listBranches(baseDir = getBranchesBaseDir()) {
|
|
45123
46015
|
let entries;
|
|
45124
46016
|
try {
|
|
45125
|
-
entries = await
|
|
46017
|
+
entries = await fs21.readdir(baseDir);
|
|
45126
46018
|
} catch (err) {
|
|
45127
46019
|
if (err.code === "ENOENT") return [];
|
|
45128
46020
|
throw err;
|
|
@@ -45203,26 +46095,26 @@ async function handleBranchCheckout(ctx, branchName) {
|
|
|
45203
46095
|
}
|
|
45204
46096
|
|
|
45205
46097
|
// src/cli/slashHandlers/workspace.ts
|
|
45206
|
-
import { promises as
|
|
45207
|
-
import
|
|
46098
|
+
import { promises as fs22 } from "node:fs";
|
|
46099
|
+
import path40 from "node:path";
|
|
45208
46100
|
async function handleWorkspaceShow(ctx, what) {
|
|
45209
46101
|
try {
|
|
45210
|
-
const zelari =
|
|
46102
|
+
const zelari = path40.join(process.cwd(), ".zelari");
|
|
45211
46103
|
let content;
|
|
45212
46104
|
switch (what) {
|
|
45213
46105
|
case "plan": {
|
|
45214
|
-
const planPath =
|
|
46106
|
+
const planPath = path40.join(zelari, "plan.md");
|
|
45215
46107
|
try {
|
|
45216
|
-
content = await
|
|
46108
|
+
content = await fs22.readFile(planPath, "utf-8");
|
|
45217
46109
|
} catch {
|
|
45218
46110
|
content = "(no plan.md yet \u2014 run a council session first)";
|
|
45219
46111
|
}
|
|
45220
46112
|
break;
|
|
45221
46113
|
}
|
|
45222
46114
|
case "decisions": {
|
|
45223
|
-
const decisionsDir =
|
|
46115
|
+
const decisionsDir = path40.join(zelari, "decisions");
|
|
45224
46116
|
try {
|
|
45225
|
-
const files = (await
|
|
46117
|
+
const files = (await fs22.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
|
|
45226
46118
|
if (files.length === 0) {
|
|
45227
46119
|
content = "(no ADRs yet \u2014 invoke /council to generate some)";
|
|
45228
46120
|
} else {
|
|
@@ -45230,7 +46122,7 @@ async function handleWorkspaceShow(ctx, what) {
|
|
|
45230
46122
|
`];
|
|
45231
46123
|
const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
|
|
45232
46124
|
for (const f of files) {
|
|
45233
|
-
const raw = await
|
|
46125
|
+
const raw = await fs22.readFile(path40.join(decisionsDir, f), "utf-8");
|
|
45234
46126
|
const { meta: meta3, body } = parseFrontmatter2(raw);
|
|
45235
46127
|
const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
|
|
45236
46128
|
lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
|
|
@@ -45243,27 +46135,27 @@ async function handleWorkspaceShow(ctx, what) {
|
|
|
45243
46135
|
break;
|
|
45244
46136
|
}
|
|
45245
46137
|
case "risks": {
|
|
45246
|
-
const risksPath =
|
|
46138
|
+
const risksPath = path40.join(zelari, "risks.md");
|
|
45247
46139
|
try {
|
|
45248
|
-
content = await
|
|
46140
|
+
content = await fs22.readFile(risksPath, "utf-8");
|
|
45249
46141
|
} catch {
|
|
45250
46142
|
content = "(no risks.md yet)";
|
|
45251
46143
|
}
|
|
45252
46144
|
break;
|
|
45253
46145
|
}
|
|
45254
46146
|
case "agents": {
|
|
45255
|
-
const agentsPath =
|
|
46147
|
+
const agentsPath = path40.join(process.cwd(), "AGENTS.MD");
|
|
45256
46148
|
try {
|
|
45257
|
-
content = await
|
|
46149
|
+
content = await fs22.readFile(agentsPath, "utf-8");
|
|
45258
46150
|
} catch {
|
|
45259
46151
|
content = "(no AGENTS.MD yet at project root \u2014 run `/workspace sync` after a council session)";
|
|
45260
46152
|
}
|
|
45261
46153
|
break;
|
|
45262
46154
|
}
|
|
45263
46155
|
case "docs": {
|
|
45264
|
-
const docsDir =
|
|
46156
|
+
const docsDir = path40.join(zelari, "docs");
|
|
45265
46157
|
try {
|
|
45266
|
-
const files = (await
|
|
46158
|
+
const files = (await fs22.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
|
|
45267
46159
|
content = files.length ? `# Docs (${files.length})
|
|
45268
46160
|
|
|
45269
46161
|
` + files.map((f) => `- ${f}`).join("\n") : "(no docs drafts yet)";
|
|
@@ -45303,8 +46195,8 @@ async function handleWorkspaceReset(ctx, force) {
|
|
|
45303
46195
|
return;
|
|
45304
46196
|
}
|
|
45305
46197
|
try {
|
|
45306
|
-
const target =
|
|
45307
|
-
await
|
|
46198
|
+
const target = path40.join(process.cwd(), ".zelari");
|
|
46199
|
+
await fs22.rm(target, { recursive: true, force: true });
|
|
45308
46200
|
appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
|
|
45309
46201
|
} catch (err) {
|
|
45310
46202
|
appendSystem(ctx.setMessages, `[workspace reset error] ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -45663,16 +46555,16 @@ function handleModelsRefresh(ctx) {
|
|
|
45663
46555
|
}
|
|
45664
46556
|
|
|
45665
46557
|
// src/cli/slashHandlers/skills.ts
|
|
45666
|
-
import
|
|
46558
|
+
import path41 from "node:path";
|
|
45667
46559
|
import os12 from "node:os";
|
|
45668
46560
|
|
|
45669
46561
|
// src/cli/skillHistory.ts
|
|
45670
|
-
import { promises as
|
|
46562
|
+
import { promises as fs23, existsSync as existsSync39, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync4, mkdirSync as mkdirSync17 } from "node:fs";
|
|
45671
46563
|
var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
|
|
45672
46564
|
async function readSkillHistory(file2) {
|
|
45673
46565
|
let raw = "";
|
|
45674
46566
|
try {
|
|
45675
|
-
raw = await
|
|
46567
|
+
raw = await fs23.readFile(file2, "utf-8");
|
|
45676
46568
|
} catch {
|
|
45677
46569
|
return [];
|
|
45678
46570
|
}
|
|
@@ -45789,7 +46681,7 @@ function handleSkillPicker(ctx, skills, openPicker, fallbackMessage) {
|
|
|
45789
46681
|
});
|
|
45790
46682
|
}
|
|
45791
46683
|
async function handleSkillStats(ctx, skillId) {
|
|
45792
|
-
const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ??
|
|
46684
|
+
const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path41.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
|
|
45793
46685
|
try {
|
|
45794
46686
|
const records = await readSkillHistory(historyFile);
|
|
45795
46687
|
const stats = getSkillStats(records, skillId);
|
|
@@ -45805,7 +46697,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
|
|
|
45805
46697
|
appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
|
|
45806
46698
|
return;
|
|
45807
46699
|
}
|
|
45808
|
-
const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ??
|
|
46700
|
+
const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path41.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
|
|
45809
46701
|
try {
|
|
45810
46702
|
const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
|
|
45811
46703
|
appendSystem(ctx.setMessages, formatted);
|
|
@@ -47530,7 +48422,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
47530
48422
|
return 1;
|
|
47531
48423
|
}
|
|
47532
48424
|
const { planTaskGraph: planTaskGraph2 } = await Promise.resolve().then(() => (init_planner(), planner_exports));
|
|
47533
|
-
const {
|
|
48425
|
+
const { loadGraphSnapshot: loadGraphSnapshot2, formatSnapshotForPlanner: formatSnapshotForPlanner2 } = await Promise.resolve().then(() => (init_graphMemory(), graphMemory_exports));
|
|
48426
|
+
const { formatKrakenGraphAscii: formatKrakenGraphAscii2, formatKrakenGraphDigest: formatKrakenGraphDigest2 } = await Promise.resolve().then(() => (init_graphStatus(), graphStatus_exports));
|
|
47534
48427
|
const { AuditLogger: AuditLogger2 } = await Promise.resolve().then(() => (init_auditLogger(), auditLogger_exports));
|
|
47535
48428
|
const { createKrakenSubAgentContextFactory: createKrakenSubAgentContextFactory2 } = await Promise.resolve().then(() => (init_toolRegistry(), toolRegistry_exports));
|
|
47536
48429
|
const cwd = process.cwd();
|
|
@@ -47543,9 +48436,24 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
47543
48436
|
`);
|
|
47544
48437
|
}
|
|
47545
48438
|
};
|
|
48439
|
+
const abort = new AbortController();
|
|
48440
|
+
const onSigint = () => {
|
|
48441
|
+
log("SIGINT \u2014 cancelling the graph; press Ctrl-C again to force quit");
|
|
48442
|
+
abort.abort();
|
|
48443
|
+
};
|
|
48444
|
+
process.once("SIGINT", onSigint);
|
|
47546
48445
|
try {
|
|
47547
48446
|
log(`planning kraken graph: ${prompt}`);
|
|
47548
|
-
const
|
|
48447
|
+
const previous = await loadGraphSnapshot2(cwd);
|
|
48448
|
+
const previousAttempt = formatSnapshotForPlanner2(previous);
|
|
48449
|
+
if (previousAttempt) log("resuming from the previous unfinished graph");
|
|
48450
|
+
const graph = await planTaskGraph2({
|
|
48451
|
+
prompt,
|
|
48452
|
+
provider,
|
|
48453
|
+
model,
|
|
48454
|
+
cwd,
|
|
48455
|
+
...previousAttempt ? { previousAttempt } : {}
|
|
48456
|
+
});
|
|
47549
48457
|
log(formatKrakenGraphAscii2(graph));
|
|
47550
48458
|
const audit = new AuditLogger2();
|
|
47551
48459
|
const executor = new KrakenGraphExecutor2({
|
|
@@ -47564,10 +48472,21 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
47564
48472
|
})
|
|
47565
48473
|
},
|
|
47566
48474
|
parentCwd: cwd,
|
|
47567
|
-
sessionId
|
|
48475
|
+
sessionId,
|
|
48476
|
+
goal: prompt,
|
|
48477
|
+
signal: abort.signal
|
|
47568
48478
|
});
|
|
47569
48479
|
const summary = await executor.execute(graph);
|
|
47570
|
-
|
|
48480
|
+
if (summary.cancelled) log("graph cancelled \u2014 partial results below");
|
|
48481
|
+
const finalAscii = `${formatKrakenGraphAscii2(summary.graph)}
|
|
48482
|
+
|
|
48483
|
+
${formatKrakenGraphDigest2(
|
|
48484
|
+
summary.graph,
|
|
48485
|
+
{
|
|
48486
|
+
durationsMs: summary.durationsMs,
|
|
48487
|
+
unresolvedFindings: summary.unresolvedFindings
|
|
48488
|
+
}
|
|
48489
|
+
)}`;
|
|
47571
48490
|
if (opts.output === "json") {
|
|
47572
48491
|
emitEvent({ type: "message_start" });
|
|
47573
48492
|
emitEvent({ type: "message_delta", delta: finalAscii });
|
|
@@ -47594,6 +48513,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
47594
48513
|
`);
|
|
47595
48514
|
}
|
|
47596
48515
|
return 2;
|
|
48516
|
+
} finally {
|
|
48517
|
+
process.off("SIGINT", onSigint);
|
|
47597
48518
|
}
|
|
47598
48519
|
}
|
|
47599
48520
|
function planModeFromOpts(opts) {
|
|
@@ -48709,7 +49630,7 @@ function upsertSkill(opts) {
|
|
|
48709
49630
|
}
|
|
48710
49631
|
dir = getProjectSkillsDir(root);
|
|
48711
49632
|
}
|
|
48712
|
-
const
|
|
49633
|
+
const path44 = skillFilePath(dir, name);
|
|
48713
49634
|
const content = serializeSkillMd({
|
|
48714
49635
|
name,
|
|
48715
49636
|
description,
|
|
@@ -48718,13 +49639,13 @@ function upsertSkill(opts) {
|
|
|
48718
49639
|
tools: opts.tools,
|
|
48719
49640
|
cost: opts.cost
|
|
48720
49641
|
});
|
|
48721
|
-
const parsed = parseSkillMd(content,
|
|
49642
|
+
const parsed = parseSkillMd(content, path44);
|
|
48722
49643
|
if (!parsed) {
|
|
48723
49644
|
return { ok: false, error: "Generated SKILL.md failed validation" };
|
|
48724
49645
|
}
|
|
48725
|
-
mkdirSync18(dirname9(
|
|
48726
|
-
writeFileSync20(
|
|
48727
|
-
return { ok: true, path:
|
|
49646
|
+
mkdirSync18(dirname9(path44), { recursive: true });
|
|
49647
|
+
writeFileSync20(path44, content, "utf8");
|
|
49648
|
+
return { ok: true, path: path44 };
|
|
48728
49649
|
}
|
|
48729
49650
|
function removeSkill(opts) {
|
|
48730
49651
|
const name = opts.name.trim().toLowerCase();
|
|
@@ -48742,8 +49663,8 @@ function removeSkill(opts) {
|
|
|
48742
49663
|
dir = getProjectSkillsDir(root);
|
|
48743
49664
|
}
|
|
48744
49665
|
const skillDir = join34(dir, name);
|
|
48745
|
-
const
|
|
48746
|
-
if (!existsSync42(
|
|
49666
|
+
const path44 = skillFilePath(dir, name);
|
|
49667
|
+
if (!existsSync42(path44) && !existsSync42(skillDir)) {
|
|
48747
49668
|
return { ok: false, error: `Skill "${name}" not found in ${dir}` };
|
|
48748
49669
|
}
|
|
48749
49670
|
try {
|
|
@@ -48754,7 +49675,7 @@ function removeSkill(opts) {
|
|
|
48754
49675
|
error: err instanceof Error ? err.message : String(err)
|
|
48755
49676
|
};
|
|
48756
49677
|
}
|
|
48757
|
-
return { ok: true, path:
|
|
49678
|
+
return { ok: true, path: path44 };
|
|
48758
49679
|
}
|
|
48759
49680
|
|
|
48760
49681
|
// src/cli/generateSkillFromUrl.ts
|
|
@@ -48844,8 +49765,8 @@ function normalizeDraft(raw, sourceUrl, provider, model) {
|
|
|
48844
49765
|
let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
48845
49766
|
if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
|
|
48846
49767
|
try {
|
|
48847
|
-
const
|
|
48848
|
-
name =
|
|
49768
|
+
const path44 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
49769
|
+
name = path44 && /^[a-z0-9]/.test(path44) ? path44 : "imported-skill";
|
|
48849
49770
|
} catch {
|
|
48850
49771
|
name = "imported-skill";
|
|
48851
49772
|
}
|