@loops-adk/core 0.1.1 → 0.3.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/README.md +132 -15
- package/assets/logo.png +0 -0
- package/dist/{agent-sdk-RF5VJZAT.js → agent-sdk-4QJDWM7N.js} +3 -3
- package/dist/{agent-sdk-RF5VJZAT.js.map → agent-sdk-4QJDWM7N.js.map} +1 -1
- package/dist/api.d.ts +119 -3
- package/dist/api.js +26 -10
- package/dist/api.js.map +1 -1
- package/dist/{chunk-6BDWTFOS.js → chunk-3PMVII43.js} +784 -37
- package/dist/chunk-3PMVII43.js.map +1 -0
- package/dist/{chunk-XC46B4FD.js → chunk-MA6NDQMO.js} +2 -2
- package/dist/chunk-MA6NDQMO.js.map +1 -0
- package/dist/{claude-cli-U7WEVAOL.js → claude-cli-75AOQUKG.js} +3 -3
- package/dist/{claude-cli-U7WEVAOL.js.map → claude-cli-75AOQUKG.js.map} +1 -1
- package/dist/{codex-6I5UZ2HM.js → codex-LYZF52WL.js} +25 -13
- package/dist/codex-LYZF52WL.js.map +1 -0
- package/dist/env/command.d.ts +1 -1
- package/dist/env/docker.d.ts +1 -1
- package/dist/env/sst.d.ts +1 -1
- package/dist/index.js +155 -14
- package/dist/index.js.map +1 -1
- package/dist/{types-B4wGVpqo.d.ts → types-CpB03Jj4.d.ts} +255 -38
- package/package.json +11 -1
- package/skills/author-loop/SKILL.md +14 -5
- package/skills/design-agent-team/SKILL.md +108 -0
- package/skills/supervise-loop-run/SKILL.md +64 -0
- package/dist/chunk-6BDWTFOS.js.map +0 -1
- package/dist/chunk-XC46B4FD.js.map +0 -1
- package/dist/codex-6I5UZ2HM.js.map +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { redactSecrets } from './chunk-JFTXJ7I2.js';
|
|
2
|
-
import { isEngine } from './chunk-
|
|
2
|
+
import { isEngine } from './chunk-MA6NDQMO.js';
|
|
3
3
|
import { isLimitError, waitMsFor } from './chunk-Y2SD7GBL.js';
|
|
4
4
|
import { LoopError } from './chunk-I3STY7U6.js';
|
|
5
5
|
import { readFileSync, mkdtempSync, existsSync, writeFileSync, appendFileSync, readdirSync, mkdirSync, rmSync } from 'fs';
|
|
6
6
|
import { execa } from 'execa';
|
|
7
|
+
import { createHash, randomBytes } from 'crypto';
|
|
7
8
|
import { tmpdir, homedir } from 'os';
|
|
8
9
|
import { join, dirname } from 'path';
|
|
9
|
-
import { randomBytes } from 'crypto';
|
|
10
10
|
|
|
11
11
|
// src/core/describe.ts
|
|
12
12
|
var META = /* @__PURE__ */ new WeakMap();
|
|
@@ -35,13 +35,30 @@ function describeConditions(input) {
|
|
|
35
35
|
return [condLabel(input)];
|
|
36
36
|
}
|
|
37
37
|
var count = (n, w) => `${n} ${w}${n === 1 ? "" : "s"}`;
|
|
38
|
+
function renderContract(value) {
|
|
39
|
+
const c = value;
|
|
40
|
+
if (!c || typeof c !== "object") return void 0;
|
|
41
|
+
const bits = [];
|
|
42
|
+
if (c.tier) bits.push(`tier ${c.tier}`);
|
|
43
|
+
if (c.outputs?.length) bits.push(`outputs ${c.outputs.join(", ")}`);
|
|
44
|
+
if (c.capabilities?.length) bits.push(`capabilities ${c.capabilities.join(", ")}`);
|
|
45
|
+
if (c.requiresSkills?.length) bits.push(`requires ${c.requiresSkills.join(", ")}`);
|
|
46
|
+
if (c.usesSkills?.length) bits.push(`uses ${c.usesSkills.join(", ")}`);
|
|
47
|
+
if (c.humanGates?.length) bits.push(`gates ${c.humanGates.join(", ")}`);
|
|
48
|
+
if (c.failureModes?.length) bits.push(`failure modes ${c.failureModes.join(", ")}`);
|
|
49
|
+
return bits.join("; ");
|
|
50
|
+
}
|
|
38
51
|
function renderPlan(meta, indent = "") {
|
|
39
52
|
if (!meta) return [`${indent}(a runnable job, shape not introspectable)`];
|
|
40
53
|
const nm = meta.name ? ` "${meta.name}"` : "";
|
|
41
54
|
const out = [];
|
|
42
55
|
switch (meta.kind) {
|
|
43
56
|
case "loop": {
|
|
44
|
-
const
|
|
57
|
+
const caps = [];
|
|
58
|
+
if (typeof meta.max === "number") caps.push(`max ${meta.max}`);
|
|
59
|
+
if (typeof meta.noProgress === "number")
|
|
60
|
+
caps.push(`stall after ${meta.noProgress} flat`);
|
|
61
|
+
const max = caps.length ? ` (${caps.join("; ")})` : "";
|
|
45
62
|
out.push(`${indent}loop${nm}${max}`);
|
|
46
63
|
const start = meta.start;
|
|
47
64
|
const gate = meta.gate;
|
|
@@ -69,6 +86,10 @@ function renderPlan(meta, indent = "") {
|
|
|
69
86
|
}
|
|
70
87
|
case "agent":
|
|
71
88
|
out.push(`${indent}agent${nm}${meta.ground ? " (grounded)" : ""}`);
|
|
89
|
+
{
|
|
90
|
+
const contract = renderContract(meta.contract);
|
|
91
|
+
if (contract) out.push(`${indent} contract: ${contract}`);
|
|
92
|
+
}
|
|
72
93
|
break;
|
|
73
94
|
case "fn":
|
|
74
95
|
out.push(`${indent}fn${nm}`);
|
|
@@ -129,12 +150,59 @@ function defineSkill(skill) {
|
|
|
129
150
|
if (!skill.instructions?.trim()) throw new Error(`defineSkill "${skill.name}": empty instructions`);
|
|
130
151
|
return skill;
|
|
131
152
|
}
|
|
153
|
+
function skillRefName(ref) {
|
|
154
|
+
return typeof ref === "string" ? ref : ref.name;
|
|
155
|
+
}
|
|
156
|
+
function validateName(value, label) {
|
|
157
|
+
if (!value?.trim()) throw new Error(`${label}: \`name\` is required`);
|
|
158
|
+
}
|
|
159
|
+
function validateSkillRef(ref, label) {
|
|
160
|
+
if (typeof ref === "string") {
|
|
161
|
+
if (!ref.trim()) throw new Error(`${label}: empty skill name`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
defineSkill(ref);
|
|
165
|
+
}
|
|
132
166
|
function defineAgent(def) {
|
|
133
167
|
if (!def.name) throw new Error("defineAgent: `name` is required");
|
|
134
168
|
if (!def.system?.trim()) throw new Error(`defineAgent "${def.name}": empty system prompt`);
|
|
135
169
|
def.skills?.forEach((s) => defineSkill(s));
|
|
170
|
+
def.requiresSkills?.forEach(
|
|
171
|
+
(s) => validateSkillRef(s, `defineAgent "${def.name}" requiresSkills`)
|
|
172
|
+
);
|
|
173
|
+
def.usesSkills?.forEach(
|
|
174
|
+
(s) => validateSkillRef(s, `defineAgent "${def.name}" usesSkills`)
|
|
175
|
+
);
|
|
176
|
+
def.outputs?.forEach(
|
|
177
|
+
(o) => validateName(o.name, `defineAgent "${def.name}" outputs`)
|
|
178
|
+
);
|
|
179
|
+
def.humanGates?.forEach(
|
|
180
|
+
(g) => validateName(g.name, `defineAgent "${def.name}" humanGates`)
|
|
181
|
+
);
|
|
182
|
+
def.failureModes?.forEach((f) => {
|
|
183
|
+
if (!f.mode?.trim())
|
|
184
|
+
throw new Error(`defineAgent "${def.name}" failureModes: \`mode\` is required`);
|
|
185
|
+
if (!f.recovery?.trim())
|
|
186
|
+
throw new Error(`defineAgent "${def.name}" failureModes "${f.mode}": \`recovery\` is required`);
|
|
187
|
+
});
|
|
136
188
|
return def;
|
|
137
189
|
}
|
|
190
|
+
function agentContract(agent) {
|
|
191
|
+
if (!agent) return void 0;
|
|
192
|
+
const summary = {};
|
|
193
|
+
if (agent.tier) summary.tier = agent.tier;
|
|
194
|
+
if (agent.capabilities?.length) summary.capabilities = [...agent.capabilities];
|
|
195
|
+
if (agent.outputs?.length) summary.outputs = agent.outputs.map((o) => o.name);
|
|
196
|
+
if (agent.requiresSkills?.length)
|
|
197
|
+
summary.requiresSkills = agent.requiresSkills.map(skillRefName);
|
|
198
|
+
if (agent.usesSkills?.length)
|
|
199
|
+
summary.usesSkills = agent.usesSkills.map(skillRefName);
|
|
200
|
+
if (agent.humanGates?.length)
|
|
201
|
+
summary.humanGates = agent.humanGates.map((g) => g.name);
|
|
202
|
+
if (agent.failureModes?.length)
|
|
203
|
+
summary.failureModes = agent.failureModes.map((f) => f.mode);
|
|
204
|
+
return Object.keys(summary).length ? summary : void 0;
|
|
205
|
+
}
|
|
138
206
|
function resolveSystem(agent) {
|
|
139
207
|
if (!agent.skills?.length) return agent.system;
|
|
140
208
|
const methods = agent.skills.map((s) => `### ${s.name}
|
|
@@ -748,6 +816,32 @@ async function isDirty(opts) {
|
|
|
748
816
|
const r = await git(["status", "--porcelain"], opts);
|
|
749
817
|
return r.stdout.trim().length > 0;
|
|
750
818
|
}
|
|
819
|
+
async function workspaceFingerprint(opts) {
|
|
820
|
+
try {
|
|
821
|
+
if (!await isRepo(opts)) return void 0;
|
|
822
|
+
const hash = createHash("sha256");
|
|
823
|
+
const feed = (label, value) => {
|
|
824
|
+
hash.update(label);
|
|
825
|
+
hash.update("");
|
|
826
|
+
hash.update(value);
|
|
827
|
+
hash.update("");
|
|
828
|
+
};
|
|
829
|
+
feed("head", await headSha(opts) ?? "");
|
|
830
|
+
feed("status", (await git(["status", "--porcelain"], opts)).stdout);
|
|
831
|
+
feed("unstaged", (await git(["diff"], opts)).stdout);
|
|
832
|
+
feed("staged", (await git(["diff", "--cached"], opts)).stdout);
|
|
833
|
+
const untracked = (await git(["ls-files", "--others", "--exclude-standard"], opts)).stdout.split("\n").filter(Boolean);
|
|
834
|
+
for (let i = 0; i < untracked.length; i += 500) {
|
|
835
|
+
const chunk = untracked.slice(i, i + 500);
|
|
836
|
+
feed(`untracked-paths:${i}`, chunk.join("\n"));
|
|
837
|
+
const r = await git(["hash-object", "--", ...chunk], opts);
|
|
838
|
+
if (r.exitCode === 0) feed(`untracked-content:${i}`, r.stdout);
|
|
839
|
+
}
|
|
840
|
+
return hash.digest("hex");
|
|
841
|
+
} catch {
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
751
845
|
async function commit(input, opts) {
|
|
752
846
|
if (!input.allowEmpty && !await hasStagedChanges(opts)) return void 0;
|
|
753
847
|
const message = input.body ? `${input.subject}
|
|
@@ -928,6 +1022,15 @@ function resetLedger(workspace) {
|
|
|
928
1022
|
reset(ledgerPath(workspace));
|
|
929
1023
|
}
|
|
930
1024
|
|
|
1025
|
+
// src/core/text.ts
|
|
1026
|
+
function oneLine(text) {
|
|
1027
|
+
return text.replace(/\s+/g, " ").trim();
|
|
1028
|
+
}
|
|
1029
|
+
function truncate(s, max) {
|
|
1030
|
+
return s.length > max ? `${s.slice(0, max).trimEnd()}
|
|
1031
|
+
\u2026` : s;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
931
1034
|
// src/core/consolidate.ts
|
|
932
1035
|
var CONSOLIDATE_SYSTEM = "You maintain a project's CONSOLIDATED LEDGER from its commit history \u2014 the bounded coarse memory a fresh context reads to continue safely. Capture the current state and the open threads, and PRESERVE every binding decision, convention and constraint with its exact values verbatim (downstream work must honour them, so dropping or generalising even one is a failure). Tight markdown; MERGE new commits into the prior ledger, deduplicate, omit only narrative \u2014 never omit a decision.";
|
|
933
1036
|
function digest(body, n = 280) {
|
|
@@ -966,11 +1069,6 @@ Output the updated consolidated ledger.`,
|
|
|
966
1069
|
return result.text.trim();
|
|
967
1070
|
}
|
|
968
1071
|
var COMPACT_SYSTEM = "You write the HANDOFF a future agent reads if it lost ALL memory of this work. Include EVERYTHING it needs to continue safely, as structured markdown: ## Why (the problem and the root cause), ## What (exactly what changed, and where \u2014 names, paths, signatures), ## Alternatives (what was ruled out and why), ## Constraints (the invariants and limits that shaped it), ## Next (what is left or to watch). Preserve every decision and specific value verbatim. Completeness matters more than brevity \u2014 drop only literal repetition and play-by-play narration, never a decision or a detail. Omit a section only if it truly has nothing. No preamble.";
|
|
969
|
-
function truncate(s, n) {
|
|
970
|
-
const t = s.trim();
|
|
971
|
-
return t.length > n ? `${t.slice(0, n).trimEnd()}
|
|
972
|
-
\u2026` : t;
|
|
973
|
-
}
|
|
974
1072
|
async function compactLedger(ctx, text, opts = {}) {
|
|
975
1073
|
const trimmed = text.trim();
|
|
976
1074
|
if (!trimmed) return "";
|
|
@@ -1039,10 +1137,6 @@ function consolidateJob(config = {}) {
|
|
|
1039
1137
|
}
|
|
1040
1138
|
|
|
1041
1139
|
// src/core/ground.ts
|
|
1042
|
-
function truncate2(s, n) {
|
|
1043
|
-
return s.length > n ? `${s.slice(0, n).trimEnd()}
|
|
1044
|
-
\u2026` : s;
|
|
1045
|
-
}
|
|
1046
1140
|
async function groundingText(workspace, opts = {}) {
|
|
1047
1141
|
const records = await log({
|
|
1048
1142
|
cwd: workspace.dir,
|
|
@@ -1059,7 +1153,7 @@ What prior iterations already did and why \u2014 read it before working so you d
|
|
|
1059
1153
|
const head = `### ${r.sha.slice(0, 7)} ${r.subject}`;
|
|
1060
1154
|
return r.body ? `${head}
|
|
1061
1155
|
|
|
1062
|
-
${
|
|
1156
|
+
${truncate(r.body, bodyChars)}` : head;
|
|
1063
1157
|
});
|
|
1064
1158
|
return `${header}
|
|
1065
1159
|
|
|
@@ -1115,12 +1209,305 @@ Commits a search judged relevant \u2014 read them before working.`;
|
|
|
1115
1209
|
const head = `### ${r.sha.slice(0, 7)} ${r.subject}`;
|
|
1116
1210
|
return r.body ? `${head}
|
|
1117
1211
|
|
|
1118
|
-
${
|
|
1212
|
+
${truncate(r.body, bodyChars)}` : head;
|
|
1119
1213
|
});
|
|
1120
1214
|
return `${header}
|
|
1121
1215
|
|
|
1122
1216
|
${entries.join("\n\n")}`;
|
|
1123
1217
|
}
|
|
1218
|
+
function normalizeFeedbackSeverity(severity) {
|
|
1219
|
+
switch (severity) {
|
|
1220
|
+
case "advisory":
|
|
1221
|
+
return "nice-to-have";
|
|
1222
|
+
case "blocking":
|
|
1223
|
+
case void 0:
|
|
1224
|
+
return "block";
|
|
1225
|
+
default:
|
|
1226
|
+
return severity;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
function isRequiredFeedbackSeverity(severity) {
|
|
1230
|
+
const normalized = normalizeFeedbackSeverity(severity);
|
|
1231
|
+
return normalized === "block" || normalized === "should-fix";
|
|
1232
|
+
}
|
|
1233
|
+
function findingLine(finding) {
|
|
1234
|
+
const reviewer = finding.reviewer ? `${finding.reviewer} ` : "";
|
|
1235
|
+
const severity = normalizeFeedbackSeverity(finding.severity);
|
|
1236
|
+
const decision = finding.decision ? ` Decision: ${finding.decision}.` : "";
|
|
1237
|
+
const recommendation = finding.recommendation ? ` Recommendation: ${oneLine(finding.recommendation)}` : "";
|
|
1238
|
+
return `- ${reviewer}[${severity}]: ${oneLine(finding.evidence)}${decision}${recommendation}`;
|
|
1239
|
+
}
|
|
1240
|
+
function defaultReason(findings) {
|
|
1241
|
+
if (!findings?.length) return "Revision requested";
|
|
1242
|
+
if (findings.length === 1) return oneLine(findings[0].evidence);
|
|
1243
|
+
return `${findings.length} findings require another pass`;
|
|
1244
|
+
}
|
|
1245
|
+
function normalizeRevision(input) {
|
|
1246
|
+
const reason = input.reason?.trim() || defaultReason(input.findings);
|
|
1247
|
+
return {
|
|
1248
|
+
reason,
|
|
1249
|
+
target: input.target,
|
|
1250
|
+
findings: input.findings,
|
|
1251
|
+
rerun: input.rerun ?? (input.target ? "target-and-dependents" : void 0),
|
|
1252
|
+
source: input.source,
|
|
1253
|
+
decision: input.decision
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
function revisionRequest(input, over = {}) {
|
|
1257
|
+
const revision = normalizeRevision(input);
|
|
1258
|
+
return {
|
|
1259
|
+
status: over.status ?? "fail",
|
|
1260
|
+
confidence: over.confidence,
|
|
1261
|
+
summary: over.summary ?? revision.reason,
|
|
1262
|
+
data: over.data,
|
|
1263
|
+
error: over.error,
|
|
1264
|
+
revision
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
function kickback(to, reason, over = {}) {
|
|
1268
|
+
return revisionRequest({ target: to, reason }, over);
|
|
1269
|
+
}
|
|
1270
|
+
function revisionFromOutcome(outcome) {
|
|
1271
|
+
return outcome.revision;
|
|
1272
|
+
}
|
|
1273
|
+
function feedbackBlock(outcome) {
|
|
1274
|
+
const revision = revisionFromOutcome(outcome);
|
|
1275
|
+
const parts = [
|
|
1276
|
+
"## Feedback to address",
|
|
1277
|
+
"A review or downstream stage requested another pass. Address this before unrelated work."
|
|
1278
|
+
];
|
|
1279
|
+
if (revision?.target) parts.push(`Target: ${revision.target}`);
|
|
1280
|
+
if (revision?.source) parts.push(`Source: ${revision.source}`);
|
|
1281
|
+
if (revision?.decision) parts.push(`Caller decision: ${revision.decision}`);
|
|
1282
|
+
const reason = revision?.reason ?? outcome.summary;
|
|
1283
|
+
if (reason) parts.push(`Reason: ${reason}`);
|
|
1284
|
+
const findings = revision?.findings ?? [];
|
|
1285
|
+
if (findings.length) {
|
|
1286
|
+
parts.push("Findings:");
|
|
1287
|
+
parts.push(findings.map(findingLine).join("\n"));
|
|
1288
|
+
}
|
|
1289
|
+
return parts.join("\n\n");
|
|
1290
|
+
}
|
|
1291
|
+
function graphPositionBlock(graph) {
|
|
1292
|
+
return [
|
|
1293
|
+
"## Graph position",
|
|
1294
|
+
`DAG: ${graph.dag}`,
|
|
1295
|
+
`Current node: ${graph.node}`,
|
|
1296
|
+
`Path: ${graph.path.join(" > ")}`,
|
|
1297
|
+
`Depends on: ${graph.needs.length ? graph.needs.join(", ") : "none"}`,
|
|
1298
|
+
`Direct dependents: ${graph.dependents.length ? graph.dependents.join(", ") : "none"}`
|
|
1299
|
+
].join("\n");
|
|
1300
|
+
}
|
|
1301
|
+
async function runReviewer(reviewer, index, ctx) {
|
|
1302
|
+
const name = reviewer.name ?? `reviewer-${index + 1}`;
|
|
1303
|
+
try {
|
|
1304
|
+
if ("job" in reviewer) {
|
|
1305
|
+
const outcome = await reviewer.job(ctx);
|
|
1306
|
+
return {
|
|
1307
|
+
name,
|
|
1308
|
+
met: outcome.status === "pass",
|
|
1309
|
+
confidence: outcome.confidence,
|
|
1310
|
+
reason: outcome.summary ?? outcome.status
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
const result = await toCondition(reviewer.review)(
|
|
1314
|
+
ctx,
|
|
1315
|
+
ctx.lastOutcome
|
|
1316
|
+
);
|
|
1317
|
+
return {
|
|
1318
|
+
name,
|
|
1319
|
+
met: result.met,
|
|
1320
|
+
confidence: result.confidence,
|
|
1321
|
+
reason: result.reason
|
|
1322
|
+
};
|
|
1323
|
+
} catch (e) {
|
|
1324
|
+
if (ctx.signal.aborted) throw e;
|
|
1325
|
+
return {
|
|
1326
|
+
name,
|
|
1327
|
+
met: false,
|
|
1328
|
+
reason: `reviewer errored: ${e instanceof Error ? e.message : String(e)}`
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
function reviewFinding(result) {
|
|
1333
|
+
return {
|
|
1334
|
+
reviewer: result.name,
|
|
1335
|
+
severity: "block",
|
|
1336
|
+
evidence: result.reason
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
function findingSeverityCounts(findings) {
|
|
1340
|
+
const counts = {};
|
|
1341
|
+
for (const finding of findings) {
|
|
1342
|
+
const severity = normalizeFeedbackSeverity(finding.severity);
|
|
1343
|
+
counts[severity] = (counts[severity] ?? 0) + 1;
|
|
1344
|
+
}
|
|
1345
|
+
return counts;
|
|
1346
|
+
}
|
|
1347
|
+
function reviewPanel(config) {
|
|
1348
|
+
const label = config.label ?? "review-panel";
|
|
1349
|
+
if (!config.reviewers.length)
|
|
1350
|
+
throw new LoopError({
|
|
1351
|
+
code: "CONFIG",
|
|
1352
|
+
message: `reviewPanel "${label}": at least one reviewer is required`
|
|
1353
|
+
});
|
|
1354
|
+
const job = async (ctx) => {
|
|
1355
|
+
ctx.emit({ kind: "job:start", ts: Date.now(), path: [...ctx.path], label });
|
|
1356
|
+
const results = await Promise.all(
|
|
1357
|
+
config.reviewers.map((reviewer, i) => runReviewer(reviewer, i, ctx))
|
|
1358
|
+
);
|
|
1359
|
+
const passedCount = results.filter((r) => r.met).length;
|
|
1360
|
+
const required = config.pass === void 0 || config.pass === "all" ? results.length : config.pass;
|
|
1361
|
+
const findings = results.filter((r) => !r.met).map(reviewFinding);
|
|
1362
|
+
const passed = passedCount >= required;
|
|
1363
|
+
const summaryHead = `Review panel: ${passedCount}/${results.length} reviewer(s) cleared`;
|
|
1364
|
+
const summary = findings.length ? `${summaryHead}.
|
|
1365
|
+
${findings.map(findingLine).join("\n")}` : `${summaryHead}.`;
|
|
1366
|
+
const scored = results.map((r) => r.confidence).filter((c) => c != null);
|
|
1367
|
+
const confidence = scored.length ? scored.reduce((sum, c) => sum + c, 0) / scored.length : void 0;
|
|
1368
|
+
const data = {
|
|
1369
|
+
findings,
|
|
1370
|
+
results,
|
|
1371
|
+
passed: passedCount,
|
|
1372
|
+
required,
|
|
1373
|
+
severityCounts: findingSeverityCounts(findings)
|
|
1374
|
+
};
|
|
1375
|
+
const outcome = passed ? { status: "pass", summary, confidence, data } : revisionRequest(
|
|
1376
|
+
{
|
|
1377
|
+
target: config.target,
|
|
1378
|
+
// A clean one-line reason. The findings ride the `findings` array, so
|
|
1379
|
+
// feedbackBlock renders them once (not embedded in the reason too) and
|
|
1380
|
+
// the records/tail `reason` stays a single tidy line. The full
|
|
1381
|
+
// multi-line `summary` is kept on the outcome below for logs/TUI.
|
|
1382
|
+
reason: `${summaryHead}.`,
|
|
1383
|
+
findings,
|
|
1384
|
+
rerun: config.rerun
|
|
1385
|
+
},
|
|
1386
|
+
{ summary, confidence, data }
|
|
1387
|
+
);
|
|
1388
|
+
ctx.emit({ kind: "job:end", ts: Date.now(), path: [...ctx.path], label, outcome });
|
|
1389
|
+
return outcome;
|
|
1390
|
+
};
|
|
1391
|
+
return setMeta(job, { kind: "reviewPanel", name: label });
|
|
1392
|
+
}
|
|
1393
|
+
async function gitOutput(cwd, args, signal) {
|
|
1394
|
+
const out = await execa("git", args, {
|
|
1395
|
+
cwd,
|
|
1396
|
+
reject: false,
|
|
1397
|
+
stripFinalNewline: false,
|
|
1398
|
+
cancelSignal: signal
|
|
1399
|
+
});
|
|
1400
|
+
return out.stdout.trim();
|
|
1401
|
+
}
|
|
1402
|
+
async function resolveFiles(ctx, patterns) {
|
|
1403
|
+
const fromGit = await gitOutput(
|
|
1404
|
+
ctx.workspace.dir,
|
|
1405
|
+
["ls-files", "--", ...patterns],
|
|
1406
|
+
ctx.signal
|
|
1407
|
+
).catch(() => "");
|
|
1408
|
+
const files = fromGit ? fromGit.split("\n").filter(Boolean) : [];
|
|
1409
|
+
if (files.length) return files;
|
|
1410
|
+
return patterns.filter((p) => existsSync(join(ctx.workspace.dir, p)));
|
|
1411
|
+
}
|
|
1412
|
+
function reviewContext(config) {
|
|
1413
|
+
return async (ctx, last) => {
|
|
1414
|
+
const max = config.maxChars ?? 6e3;
|
|
1415
|
+
const buildTests = async () => {
|
|
1416
|
+
if (!config.tests) return [];
|
|
1417
|
+
if (config.tests === true) {
|
|
1418
|
+
const lines = [];
|
|
1419
|
+
if (last?.status) lines.push(`Last outcome status: ${last.status}`);
|
|
1420
|
+
if (last?.summary) lines.push(`Last outcome summary: ${last.summary}`);
|
|
1421
|
+
if (last?.data !== void 0) {
|
|
1422
|
+
let rendered;
|
|
1423
|
+
try {
|
|
1424
|
+
rendered = JSON.stringify(last.data, null, 2);
|
|
1425
|
+
} catch {
|
|
1426
|
+
rendered = String(last.data);
|
|
1427
|
+
}
|
|
1428
|
+
lines.push(`Last outcome data: ${rendered}`);
|
|
1429
|
+
}
|
|
1430
|
+
return lines.length ? [`## Test and outcome context
|
|
1431
|
+
|
|
1432
|
+
${lines.join("\n")}`] : [];
|
|
1433
|
+
}
|
|
1434
|
+
const cwd = config.tests.cwd ?? ctx.workspace.dir;
|
|
1435
|
+
const result = await execa(
|
|
1436
|
+
config.tests.command,
|
|
1437
|
+
config.tests.args ?? [],
|
|
1438
|
+
{ cwd, reject: false, stripFinalNewline: false, cancelSignal: ctx.signal }
|
|
1439
|
+
).catch((e) => {
|
|
1440
|
+
if (ctx.signal.aborted) throw e;
|
|
1441
|
+
return {
|
|
1442
|
+
exitCode: void 0,
|
|
1443
|
+
stdout: "",
|
|
1444
|
+
stderr: e instanceof Error ? e.message : String(e)
|
|
1445
|
+
};
|
|
1446
|
+
});
|
|
1447
|
+
const exit = result.exitCode ?? "(command did not run)";
|
|
1448
|
+
return [
|
|
1449
|
+
`## Test command
|
|
1450
|
+
|
|
1451
|
+
${config.tests.command} ${(config.tests.args ?? []).join(" ")}
|
|
1452
|
+
|
|
1453
|
+
exit: ${exit}
|
|
1454
|
+
|
|
1455
|
+
stdout:
|
|
1456
|
+
${truncate(result.stdout ?? "", max)}
|
|
1457
|
+
|
|
1458
|
+
stderr:
|
|
1459
|
+
${truncate(result.stderr ?? "", max)}`
|
|
1460
|
+
];
|
|
1461
|
+
};
|
|
1462
|
+
const buildDiff = async () => {
|
|
1463
|
+
if (!config.diff) return [];
|
|
1464
|
+
const diff2 = await gitOutput(
|
|
1465
|
+
ctx.workspace.dir,
|
|
1466
|
+
["diff", "HEAD", "--"],
|
|
1467
|
+
ctx.signal
|
|
1468
|
+
).catch(() => "");
|
|
1469
|
+
return diff2 ? [`## Git diff
|
|
1470
|
+
|
|
1471
|
+
${truncate(diff2, max)}`] : [];
|
|
1472
|
+
};
|
|
1473
|
+
const buildFiles = async () => {
|
|
1474
|
+
if (!config.files?.length) return [];
|
|
1475
|
+
const files2 = await resolveFiles(ctx, config.files);
|
|
1476
|
+
const out = [];
|
|
1477
|
+
for (const file of files2) {
|
|
1478
|
+
const path = join(ctx.workspace.dir, file);
|
|
1479
|
+
if (!existsSync(path)) continue;
|
|
1480
|
+
out.push(`## File: ${file}
|
|
1481
|
+
|
|
1482
|
+
${truncate(readFileSync(path, "utf8"), max)}`);
|
|
1483
|
+
}
|
|
1484
|
+
return out;
|
|
1485
|
+
};
|
|
1486
|
+
const buildLedger = async () => {
|
|
1487
|
+
if (!config.ledger) return [];
|
|
1488
|
+
const out = [];
|
|
1489
|
+
const live = [readPrompt(ctx.workspace), readLedger(ctx.workspace)].filter(Boolean).join("\n\n");
|
|
1490
|
+
if (live) out.push(`## Live ledger
|
|
1491
|
+
|
|
1492
|
+
${truncate(live, max)}`);
|
|
1493
|
+
const committed = await groundingText(ctx.workspace, {
|
|
1494
|
+
max: 5,
|
|
1495
|
+
bodyChars: 1200,
|
|
1496
|
+
signal: ctx.signal
|
|
1497
|
+
}).catch(() => "");
|
|
1498
|
+
if (committed) out.push(truncate(committed, max));
|
|
1499
|
+
return out;
|
|
1500
|
+
};
|
|
1501
|
+
const [tests, diff, files, ledger] = await Promise.all([
|
|
1502
|
+
buildTests(),
|
|
1503
|
+
buildDiff(),
|
|
1504
|
+
buildFiles(),
|
|
1505
|
+
buildLedger()
|
|
1506
|
+
]);
|
|
1507
|
+
const sections = [...tests, ...diff, ...files, ...ledger];
|
|
1508
|
+
return sections.join("\n\n---\n\n") || "(no review context)";
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1124
1511
|
|
|
1125
1512
|
// src/core/job.ts
|
|
1126
1513
|
var HANDOFF_MARK = "===HANDOFF===";
|
|
@@ -1198,6 +1585,16 @@ var TERMINAL = (text) => ({
|
|
|
1198
1585
|
summary: text.trim().slice(0, 280),
|
|
1199
1586
|
data: text
|
|
1200
1587
|
});
|
|
1588
|
+
function withOperationalContext(ctx, userPrompt, config) {
|
|
1589
|
+
const parts = [userPrompt];
|
|
1590
|
+
if (config.consumeFeedback && ctx.lastReview) {
|
|
1591
|
+
parts.push(feedbackBlock(ctx.lastReview));
|
|
1592
|
+
}
|
|
1593
|
+
if (config.graphContext && ctx.graph) {
|
|
1594
|
+
parts.push(graphPositionBlock(ctx.graph));
|
|
1595
|
+
}
|
|
1596
|
+
return parts.join("\n\n---\n\n");
|
|
1597
|
+
}
|
|
1201
1598
|
function agentJob(config) {
|
|
1202
1599
|
const job = async (ctx) => {
|
|
1203
1600
|
const path = [...ctx.path];
|
|
@@ -1205,7 +1602,8 @@ function agentJob(config) {
|
|
|
1205
1602
|
ctx.emit({ kind: "job:start", ts: Date.now(), path, label });
|
|
1206
1603
|
const engine = ctx.resolveEngine(config.engine);
|
|
1207
1604
|
const userPrompt = typeof config.prompt === "function" ? await config.prompt(ctx) : config.prompt;
|
|
1208
|
-
const
|
|
1605
|
+
const contextualPrompt = withOperationalContext(ctx, userPrompt, config);
|
|
1606
|
+
const prompt = config.ground ? await withGrounding(ctx, contextualPrompt, config.ground) : contextualPrompt;
|
|
1209
1607
|
const system = config.system !== void 0 ? typeof config.system === "function" ? config.system(ctx) : config.system : config.agent ? resolveSystem(config.agent) : void 0;
|
|
1210
1608
|
let result;
|
|
1211
1609
|
const toolUses = /* @__PURE__ */ new Map();
|
|
@@ -1306,7 +1704,8 @@ function agentJob(config) {
|
|
|
1306
1704
|
return setMeta(job, {
|
|
1307
1705
|
kind: "agent",
|
|
1308
1706
|
name: config.label ?? config.agent?.name ?? "agent",
|
|
1309
|
-
ground: !!config.ground
|
|
1707
|
+
ground: !!config.ground,
|
|
1708
|
+
contract: agentContract(config.agent)
|
|
1310
1709
|
});
|
|
1311
1710
|
}
|
|
1312
1711
|
function composeWay(ctx, last) {
|
|
@@ -1382,14 +1781,6 @@ function commitJob(config) {
|
|
|
1382
1781
|
}
|
|
1383
1782
|
};
|
|
1384
1783
|
}
|
|
1385
|
-
function kickback(to, reason, over) {
|
|
1386
|
-
return {
|
|
1387
|
-
status: "fail",
|
|
1388
|
-
summary: reason,
|
|
1389
|
-
...over,
|
|
1390
|
-
kickback: { to, reason }
|
|
1391
|
-
};
|
|
1392
|
-
}
|
|
1393
1784
|
function fnJob(label, fn) {
|
|
1394
1785
|
const job = async (ctx) => {
|
|
1395
1786
|
const path = [...ctx.path];
|
|
@@ -1419,6 +1810,102 @@ function fnJob(label, fn) {
|
|
|
1419
1810
|
return setMeta(job, { kind: "fn", name: label });
|
|
1420
1811
|
}
|
|
1421
1812
|
|
|
1813
|
+
// src/core/progress.ts
|
|
1814
|
+
function resolveNoProgress(input) {
|
|
1815
|
+
if (input == null) return void 0;
|
|
1816
|
+
const cfg = typeof input === "number" ? { window: input } : input;
|
|
1817
|
+
return {
|
|
1818
|
+
...cfg,
|
|
1819
|
+
window: cfg.window ?? 3,
|
|
1820
|
+
minConfidenceDelta: cfg.minConfidenceDelta ?? 0.02
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
var ProgressTracker = class {
|
|
1824
|
+
window;
|
|
1825
|
+
minConfidenceDelta;
|
|
1826
|
+
/** Every state this run has reached, namespaced by channel. */
|
|
1827
|
+
seen = /* @__PURE__ */ new Set();
|
|
1828
|
+
/** Confidence high-water mark — the best score at the last progress point. */
|
|
1829
|
+
best;
|
|
1830
|
+
/** The current run of consecutive no-progress iterations. */
|
|
1831
|
+
stalledRun = [];
|
|
1832
|
+
lastEvidence = [];
|
|
1833
|
+
lastReason = "gate not met";
|
|
1834
|
+
indeterminate = 0;
|
|
1835
|
+
sampled = 0;
|
|
1836
|
+
constructor(cfg) {
|
|
1837
|
+
this.window = cfg.window;
|
|
1838
|
+
this.minConfidenceDelta = cfg.minConfidenceDelta;
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Record one iteration. Returns a `StallReport` when this sample fills the
|
|
1842
|
+
* window, else undefined.
|
|
1843
|
+
*/
|
|
1844
|
+
record(sample) {
|
|
1845
|
+
this.sampled += 1;
|
|
1846
|
+
if (sample.reason) this.lastReason = sample.reason;
|
|
1847
|
+
const flat = [];
|
|
1848
|
+
let progressed = false;
|
|
1849
|
+
let channels = 0;
|
|
1850
|
+
if (sample.fingerprint !== void 0) {
|
|
1851
|
+
channels += 1;
|
|
1852
|
+
const key = `fp:${sample.fingerprint}`;
|
|
1853
|
+
if (this.seen.has(key)) {
|
|
1854
|
+
flat.push("workspace: no state this run has not already visited");
|
|
1855
|
+
} else {
|
|
1856
|
+
this.seen.add(key);
|
|
1857
|
+
progressed = true;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
if (sample.signal !== void 0) {
|
|
1861
|
+
channels += 1;
|
|
1862
|
+
const key = `sig:${sample.signal}`;
|
|
1863
|
+
if (this.seen.has(key)) {
|
|
1864
|
+
flat.push(`signal: "${sample.signal}" already seen this run`);
|
|
1865
|
+
} else {
|
|
1866
|
+
this.seen.add(key);
|
|
1867
|
+
progressed = true;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
if (sample.confidence !== void 0) {
|
|
1871
|
+
channels += 1;
|
|
1872
|
+
if (this.best === void 0 || sample.confidence >= this.best + this.minConfidenceDelta) {
|
|
1873
|
+
this.best = Math.max(this.best ?? -Infinity, sample.confidence);
|
|
1874
|
+
progressed = true;
|
|
1875
|
+
} else {
|
|
1876
|
+
flat.push(
|
|
1877
|
+
`confidence ${sample.confidence.toFixed(2)} did not improve on ${this.best.toFixed(2)} (needs +${this.minConfidenceDelta})`
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
if (channels === 0) {
|
|
1882
|
+
this.indeterminate += 1;
|
|
1883
|
+
return void 0;
|
|
1884
|
+
}
|
|
1885
|
+
if (progressed) {
|
|
1886
|
+
this.stalledRun = [];
|
|
1887
|
+
return void 0;
|
|
1888
|
+
}
|
|
1889
|
+
this.stalledRun.push(sample.iteration);
|
|
1890
|
+
this.lastEvidence = flat;
|
|
1891
|
+
if (this.stalledRun.length < this.window) return void 0;
|
|
1892
|
+
return {
|
|
1893
|
+
window: this.window,
|
|
1894
|
+
iterations: [...this.stalledRun],
|
|
1895
|
+
reason: this.lastReason,
|
|
1896
|
+
evidence: [...this.lastEvidence]
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* True when the detector has seen a full window of samples and none carried
|
|
1901
|
+
* any evidence channel — detection is configured but cannot fire. The loop
|
|
1902
|
+
* uses this to warn once instead of failing silently-inert.
|
|
1903
|
+
*/
|
|
1904
|
+
isInert() {
|
|
1905
|
+
return this.indeterminate >= this.window && this.indeterminate === this.sampled;
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1422
1909
|
// src/core/context.ts
|
|
1423
1910
|
function childContext(parent, over) {
|
|
1424
1911
|
return {
|
|
@@ -1439,6 +1926,7 @@ function childContext(parent, over) {
|
|
|
1439
1926
|
log: parent.log,
|
|
1440
1927
|
depth: over.depth,
|
|
1441
1928
|
path: over.path,
|
|
1929
|
+
graph: over.graph ?? parent.graph,
|
|
1442
1930
|
// Inherit the enclosing iteration by default. A `loop` always passes one
|
|
1443
1931
|
// explicitly; a `dag`/`sequence` does not, so without this a node nested in a
|
|
1444
1932
|
// loop would reset to 0 — the "Attempt 0" confound where a retry body could not
|
|
@@ -1480,6 +1968,7 @@ function loop(config) {
|
|
|
1480
1968
|
const until = config.until ? toCondition(config.until) : void 0;
|
|
1481
1969
|
const stopOn = config.stopOn ? toCondition(config.stopOn) : void 0;
|
|
1482
1970
|
const onError = config.retry?.onError ?? "continue";
|
|
1971
|
+
const noProgress = resolveNoProgress(config.noProgress);
|
|
1483
1972
|
const job = async (parent) => {
|
|
1484
1973
|
const path = [...parent.path, config.name];
|
|
1485
1974
|
const depth = parent.depth + 1;
|
|
@@ -1566,6 +2055,8 @@ function loop(config) {
|
|
|
1566
2055
|
let last;
|
|
1567
2056
|
let consecutiveErrors = 0;
|
|
1568
2057
|
let consecutiveReviewFails = 0;
|
|
2058
|
+
const tracker = noProgress ? new ProgressTracker(noProgress) : void 0;
|
|
2059
|
+
let warnedInert = false;
|
|
1569
2060
|
while (true) {
|
|
1570
2061
|
await yieldToLoop();
|
|
1571
2062
|
if (parent.signal.aborted)
|
|
@@ -1586,6 +2077,7 @@ function loop(config) {
|
|
|
1586
2077
|
}
|
|
1587
2078
|
iteration += 1;
|
|
1588
2079
|
const ctx = ctxAt(iteration, last);
|
|
2080
|
+
let turnReview;
|
|
1589
2081
|
parent.emit({ kind: "loop:iteration", ts: ts(), path, iteration });
|
|
1590
2082
|
let bodyThrew = false;
|
|
1591
2083
|
try {
|
|
@@ -1746,13 +2238,18 @@ function loop(config) {
|
|
|
1746
2238
|
iteration
|
|
1747
2239
|
});
|
|
1748
2240
|
}
|
|
2241
|
+
const reviewPassed = reviewOutcome.status === "pass";
|
|
2242
|
+
const restartsExhausted = config.maxReviewRestarts != null && consecutiveReviewFails + 1 >= config.maxReviewRestarts;
|
|
2243
|
+
const iterationsRemain = config.max == null || iteration < config.max;
|
|
2244
|
+
const willReenter = !reviewPassed && !restartsExhausted && iterationsRemain;
|
|
1749
2245
|
parent.emit({
|
|
1750
2246
|
kind: "loop:review",
|
|
1751
2247
|
ts: ts(),
|
|
1752
2248
|
path,
|
|
1753
|
-
outcome: reviewOutcome
|
|
2249
|
+
outcome: reviewOutcome,
|
|
2250
|
+
accepted: willReenter
|
|
1754
2251
|
});
|
|
1755
|
-
if (
|
|
2252
|
+
if (reviewPassed) {
|
|
1756
2253
|
await recordMilestone(ctxAt(iteration, last));
|
|
1757
2254
|
return finish(
|
|
1758
2255
|
{
|
|
@@ -1766,11 +2263,12 @@ function loop(config) {
|
|
|
1766
2263
|
}
|
|
1767
2264
|
consecutiveReviewFails += 1;
|
|
1768
2265
|
lastReview = reviewOutcome;
|
|
2266
|
+
turnReview = reviewOutcome;
|
|
1769
2267
|
parent.log(
|
|
1770
2268
|
`review did not pass (${reviewOutcome.summary ?? reviewOutcome.status}); re-entering ${config.name}`,
|
|
1771
2269
|
"warn"
|
|
1772
2270
|
);
|
|
1773
|
-
if (
|
|
2271
|
+
if (restartsExhausted) {
|
|
1774
2272
|
return finish(
|
|
1775
2273
|
{
|
|
1776
2274
|
status: "exhausted",
|
|
@@ -1781,6 +2279,62 @@ function loop(config) {
|
|
|
1781
2279
|
);
|
|
1782
2280
|
}
|
|
1783
2281
|
}
|
|
2282
|
+
if (tracker) {
|
|
2283
|
+
let fingerprint;
|
|
2284
|
+
if (noProgress.workspace !== false) {
|
|
2285
|
+
fingerprint = await workspaceFingerprint({
|
|
2286
|
+
cwd: ctx.workspace.dir,
|
|
2287
|
+
signal: parent.signal
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
let signalValue;
|
|
2291
|
+
if (noProgress.signal) {
|
|
2292
|
+
try {
|
|
2293
|
+
const v = await noProgress.signal(ctx, last);
|
|
2294
|
+
signalValue = v == null ? void 0 : String(v);
|
|
2295
|
+
} catch (e) {
|
|
2296
|
+
throw LoopError.from(e, {
|
|
2297
|
+
code: "VALIDATION",
|
|
2298
|
+
phase: "body",
|
|
2299
|
+
path,
|
|
2300
|
+
iteration
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
const report = tracker.record({
|
|
2305
|
+
iteration,
|
|
2306
|
+
fingerprint,
|
|
2307
|
+
signal: signalValue,
|
|
2308
|
+
confidence: turnReview?.confidence ?? conv.confidence ?? last.confidence,
|
|
2309
|
+
reason: turnReview ? turnReview.summary ?? "review rejected" : conv.reason
|
|
2310
|
+
});
|
|
2311
|
+
if (!warnedInert && tracker.isInert()) {
|
|
2312
|
+
warnedInert = true;
|
|
2313
|
+
parent.log(
|
|
2314
|
+
`noProgress is set on ${config.name} but no evidence channel exists (no git workspace, no gate confidence, no custom signal); stall detection is inert`,
|
|
2315
|
+
"warn"
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
if (report) {
|
|
2319
|
+
parent.emit({
|
|
2320
|
+
kind: "loop:stall",
|
|
2321
|
+
ts: ts(),
|
|
2322
|
+
path,
|
|
2323
|
+
iteration,
|
|
2324
|
+
report
|
|
2325
|
+
});
|
|
2326
|
+
return finish(
|
|
2327
|
+
{
|
|
2328
|
+
status: "exhausted",
|
|
2329
|
+
summary: `stalled after ${report.iterations.length} iterations with no observable progress: ${report.reason}`,
|
|
2330
|
+
confidence: last.confidence,
|
|
2331
|
+
data: last.data,
|
|
2332
|
+
stall: report
|
|
2333
|
+
},
|
|
2334
|
+
iteration
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
1784
2338
|
if (config.delayMs) await delay(config.delayMs, parent.signal);
|
|
1785
2339
|
}
|
|
1786
2340
|
} catch (e) {
|
|
@@ -1802,6 +2356,7 @@ function loop(config) {
|
|
|
1802
2356
|
kind: "loop",
|
|
1803
2357
|
name: config.name,
|
|
1804
2358
|
max: config.max,
|
|
2359
|
+
noProgress: noProgress?.window,
|
|
1805
2360
|
start: describeConditions(config.start),
|
|
1806
2361
|
gate: describeConditions(config.until),
|
|
1807
2362
|
stopOn: describeConditions(config.stopOn),
|
|
@@ -1866,14 +2421,14 @@ var EngineRegistry = class {
|
|
|
1866
2421
|
this.register(
|
|
1867
2422
|
"agent-sdk",
|
|
1868
2423
|
(o) => lazy(
|
|
1869
|
-
() => import('./agent-sdk-
|
|
2424
|
+
() => import('./agent-sdk-4QJDWM7N.js').then((m) => new m.AgentSdkEngine(o)),
|
|
1870
2425
|
"agent-sdk"
|
|
1871
2426
|
)
|
|
1872
2427
|
);
|
|
1873
2428
|
this.register(
|
|
1874
2429
|
"claude-cli",
|
|
1875
2430
|
(o) => lazy(
|
|
1876
|
-
() => import('./claude-cli-
|
|
2431
|
+
() => import('./claude-cli-75AOQUKG.js').then((m) => new m.ClaudeCliEngine(o)),
|
|
1877
2432
|
"claude-cli"
|
|
1878
2433
|
)
|
|
1879
2434
|
);
|
|
@@ -1886,7 +2441,7 @@ var EngineRegistry = class {
|
|
|
1886
2441
|
);
|
|
1887
2442
|
this.register(
|
|
1888
2443
|
"codex",
|
|
1889
|
-
(o) => lazy(() => import('./codex-
|
|
2444
|
+
(o) => lazy(() => import('./codex-LYZF52WL.js').then((m) => new m.CodexEngine(o)), "codex")
|
|
1890
2445
|
);
|
|
1891
2446
|
}
|
|
1892
2447
|
};
|
|
@@ -1972,6 +2527,188 @@ var Stats = class {
|
|
|
1972
2527
|
return m;
|
|
1973
2528
|
}
|
|
1974
2529
|
};
|
|
2530
|
+
function ensureDir2(path) {
|
|
2531
|
+
const dir = dirname(path);
|
|
2532
|
+
if (dir && dir !== ".") mkdirSync(dir, { recursive: true });
|
|
2533
|
+
}
|
|
2534
|
+
function outcomeSummary(outcome) {
|
|
2535
|
+
return {
|
|
2536
|
+
status: outcome.status,
|
|
2537
|
+
summary: outcome.summary,
|
|
2538
|
+
confidence: outcome.confidence
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
function strongestFinding(findings) {
|
|
2542
|
+
if (!findings?.length) return void 0;
|
|
2543
|
+
const severities = findings.map((f) => normalizeFeedbackSeverity(f.severity));
|
|
2544
|
+
if (severities.includes("block")) return "block";
|
|
2545
|
+
if (severities.includes("should-fix")) return "should-fix";
|
|
2546
|
+
if (severities.includes("nice-to-have")) return "nice-to-have";
|
|
2547
|
+
if (severities.includes("approve")) return "approve";
|
|
2548
|
+
return void 0;
|
|
2549
|
+
}
|
|
2550
|
+
function emittedRevisionRecord(event, outcome) {
|
|
2551
|
+
const revision = revisionFromOutcome(outcome);
|
|
2552
|
+
return revision ? [
|
|
2553
|
+
{
|
|
2554
|
+
kind: "revision-emitted",
|
|
2555
|
+
ts: event.ts,
|
|
2556
|
+
path: event.path,
|
|
2557
|
+
sourceEvent: "job:end",
|
|
2558
|
+
revision
|
|
2559
|
+
}
|
|
2560
|
+
] : [];
|
|
2561
|
+
}
|
|
2562
|
+
function semanticRecordsFromEvent(event) {
|
|
2563
|
+
switch (event.kind) {
|
|
2564
|
+
case "job:start":
|
|
2565
|
+
return [
|
|
2566
|
+
{
|
|
2567
|
+
kind: "dispatch",
|
|
2568
|
+
ts: event.ts,
|
|
2569
|
+
path: event.path,
|
|
2570
|
+
unit: "job",
|
|
2571
|
+
label: event.label
|
|
2572
|
+
}
|
|
2573
|
+
];
|
|
2574
|
+
case "dag:node":
|
|
2575
|
+
if (event.phase === "start")
|
|
2576
|
+
return [
|
|
2577
|
+
{
|
|
2578
|
+
kind: "dispatch",
|
|
2579
|
+
ts: event.ts,
|
|
2580
|
+
path: [...event.path, event.node],
|
|
2581
|
+
unit: "dag-node",
|
|
2582
|
+
node: event.node,
|
|
2583
|
+
attempt: event.attempt
|
|
2584
|
+
}
|
|
2585
|
+
];
|
|
2586
|
+
return event.outcome ? [
|
|
2587
|
+
{
|
|
2588
|
+
kind: "completion",
|
|
2589
|
+
ts: event.ts,
|
|
2590
|
+
path: [...event.path, event.node],
|
|
2591
|
+
unit: "dag-node",
|
|
2592
|
+
label: event.node,
|
|
2593
|
+
outcome: outcomeSummary(event.outcome),
|
|
2594
|
+
attempt: event.attempt
|
|
2595
|
+
}
|
|
2596
|
+
] : [];
|
|
2597
|
+
case "job:end":
|
|
2598
|
+
return [
|
|
2599
|
+
{
|
|
2600
|
+
kind: "completion",
|
|
2601
|
+
ts: event.ts,
|
|
2602
|
+
path: event.path,
|
|
2603
|
+
unit: "job",
|
|
2604
|
+
label: event.label,
|
|
2605
|
+
outcome: outcomeSummary(event.outcome)
|
|
2606
|
+
},
|
|
2607
|
+
...emittedRevisionRecord(event, event.outcome)
|
|
2608
|
+
];
|
|
2609
|
+
case "loop:review": {
|
|
2610
|
+
if (event.outcome.status === "pass") return [];
|
|
2611
|
+
const revision = revisionFromOutcome(event.outcome);
|
|
2612
|
+
const decision = event.accepted === false ? "rejected" : "accepted";
|
|
2613
|
+
const records = [
|
|
2614
|
+
{
|
|
2615
|
+
kind: "surfacing",
|
|
2616
|
+
ts: event.ts,
|
|
2617
|
+
path: event.path,
|
|
2618
|
+
source: "loop-review",
|
|
2619
|
+
decision,
|
|
2620
|
+
severity: strongestFinding(revision?.findings),
|
|
2621
|
+
reason: revision?.reason ?? event.outcome.summary ?? event.outcome.status
|
|
2622
|
+
}
|
|
2623
|
+
];
|
|
2624
|
+
if (revision) {
|
|
2625
|
+
records.push({
|
|
2626
|
+
kind: "revision-routed",
|
|
2627
|
+
ts: event.ts,
|
|
2628
|
+
path: event.path,
|
|
2629
|
+
sourceEvent: "loop:review",
|
|
2630
|
+
decision,
|
|
2631
|
+
revision
|
|
2632
|
+
});
|
|
2633
|
+
}
|
|
2634
|
+
return records;
|
|
2635
|
+
}
|
|
2636
|
+
case "loop:end":
|
|
2637
|
+
return [
|
|
2638
|
+
{
|
|
2639
|
+
kind: "completion",
|
|
2640
|
+
ts: event.ts,
|
|
2641
|
+
path: event.path,
|
|
2642
|
+
unit: "loop",
|
|
2643
|
+
outcome: outcomeSummary(event.outcome),
|
|
2644
|
+
iterations: event.iterations
|
|
2645
|
+
}
|
|
2646
|
+
];
|
|
2647
|
+
case "dag:kickback": {
|
|
2648
|
+
const at = [...event.path, event.to];
|
|
2649
|
+
const decision = event.accepted ? "accepted" : "rejected";
|
|
2650
|
+
return [
|
|
2651
|
+
{
|
|
2652
|
+
kind: "surfacing",
|
|
2653
|
+
ts: event.ts,
|
|
2654
|
+
path: at,
|
|
2655
|
+
source: "dag-kickback",
|
|
2656
|
+
decision,
|
|
2657
|
+
severity: "block",
|
|
2658
|
+
from: event.from,
|
|
2659
|
+
to: event.to,
|
|
2660
|
+
reason: event.reason,
|
|
2661
|
+
note: event.note
|
|
2662
|
+
},
|
|
2663
|
+
{
|
|
2664
|
+
kind: "revision-routed",
|
|
2665
|
+
ts: event.ts,
|
|
2666
|
+
path: at,
|
|
2667
|
+
sourceEvent: "dag:kickback",
|
|
2668
|
+
decision,
|
|
2669
|
+
revision: {
|
|
2670
|
+
target: event.to,
|
|
2671
|
+
reason: event.reason,
|
|
2672
|
+
source: event.from,
|
|
2673
|
+
rerun: event.accepted ? "target-and-dependents" : void 0
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
];
|
|
2677
|
+
}
|
|
2678
|
+
case "dag:end":
|
|
2679
|
+
return [
|
|
2680
|
+
{
|
|
2681
|
+
kind: "completion",
|
|
2682
|
+
ts: event.ts,
|
|
2683
|
+
path: event.path,
|
|
2684
|
+
unit: "dag",
|
|
2685
|
+
outcome: outcomeSummary(event.outcome)
|
|
2686
|
+
}
|
|
2687
|
+
];
|
|
2688
|
+
default:
|
|
2689
|
+
return [];
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
function makeSemanticRecorder(path) {
|
|
2693
|
+
try {
|
|
2694
|
+
ensureDir2(path);
|
|
2695
|
+
writeFileSync(path, "");
|
|
2696
|
+
} catch {
|
|
2697
|
+
return () => {
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
return (event) => {
|
|
2701
|
+
const records = semanticRecordsFromEvent(event);
|
|
2702
|
+
if (!records.length) return;
|
|
2703
|
+
try {
|
|
2704
|
+
for (const record of records) {
|
|
2705
|
+
appendFileSync(path, `${JSON.stringify(record)}
|
|
2706
|
+
`);
|
|
2707
|
+
}
|
|
2708
|
+
} catch {
|
|
2709
|
+
}
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
1975
2712
|
var NOISE = /* @__PURE__ */ new Set([
|
|
1976
2713
|
"engine:text",
|
|
1977
2714
|
"engine:thinking"
|
|
@@ -1990,11 +2727,13 @@ function startSupervisor(input) {
|
|
|
1990
2727
|
const dir = join(runsHome(), input.runId);
|
|
1991
2728
|
mkdirSync(dir, { recursive: true });
|
|
1992
2729
|
const eventsPath = join(dir, "events.jsonl");
|
|
2730
|
+
const semanticPath = join(dir, "semantic.jsonl");
|
|
1993
2731
|
const statusPath = join(dir, "status.json");
|
|
1994
2732
|
try {
|
|
1995
2733
|
writeFileSync(eventsPath, "");
|
|
1996
2734
|
} catch {
|
|
1997
2735
|
}
|
|
2736
|
+
const semanticSink = makeSemanticRecorder(semanticPath);
|
|
1998
2737
|
const status = {
|
|
1999
2738
|
runId: input.runId,
|
|
2000
2739
|
pid: process.pid,
|
|
@@ -2025,6 +2764,7 @@ function startSupervisor(input) {
|
|
|
2025
2764
|
`);
|
|
2026
2765
|
} catch {
|
|
2027
2766
|
}
|
|
2767
|
+
semanticSink(event);
|
|
2028
2768
|
}
|
|
2029
2769
|
switch (event.kind) {
|
|
2030
2770
|
case "loop:iteration":
|
|
@@ -2101,6 +2841,9 @@ function listRuns() {
|
|
|
2101
2841
|
function runEventsPath(runId) {
|
|
2102
2842
|
return join(runsHome(), runId, "events.jsonl");
|
|
2103
2843
|
}
|
|
2844
|
+
function runSemanticRecordsPath(runId) {
|
|
2845
|
+
return join(runsHome(), runId, "semantic.jsonl");
|
|
2846
|
+
}
|
|
2104
2847
|
function formatEvent(event) {
|
|
2105
2848
|
const at = event.path.length ? `${event.path.join(" \u203A ")} ` : "";
|
|
2106
2849
|
switch (event.kind) {
|
|
@@ -2118,6 +2861,8 @@ function formatEvent(event) {
|
|
|
2118
2861
|
return `${at}\u25C2 ${event.outcome.status} (${event.iterations} iter)`;
|
|
2119
2862
|
case "dag:node":
|
|
2120
2863
|
return `${at}\xB7 node ${event.node}: ${event.phase}${event.outcome ? ` (${event.outcome.status})` : ""}`;
|
|
2864
|
+
case "dag:kickback":
|
|
2865
|
+
return `${at}\u21A9 kickback ${event.accepted ? "accepted" : "rejected"} ${event.from} -> ${event.to}: ${event.reason}${event.note ? ` (${event.note})` : ""}`;
|
|
2121
2866
|
case "dag:end":
|
|
2122
2867
|
return `${at}\u25C2 dag ${event.outcome.status}`;
|
|
2123
2868
|
case "job:start":
|
|
@@ -2128,6 +2873,8 @@ function formatEvent(event) {
|
|
|
2128
2873
|
return `${at} tool ${event.name} ${event.phase}`;
|
|
2129
2874
|
case "engine:usage":
|
|
2130
2875
|
return `${at} ${event.model}: ${event.usage.inputTokens}/${event.usage.outputTokens} tok`;
|
|
2876
|
+
case "loop:stall":
|
|
2877
|
+
return `${at}\u23F9 stalled after ${event.report.iterations.length} no-progress iterations: ${event.report.reason}`;
|
|
2131
2878
|
case "limit:wait":
|
|
2132
2879
|
return `${at}\u23F8 limit ${event.code}: waiting ${Math.round(event.waitMs / 1e3)}s`;
|
|
2133
2880
|
case "limit:pause":
|
|
@@ -2150,12 +2897,12 @@ var CHECKPOINT_AT = /* @__PURE__ */ new Set([
|
|
|
2150
2897
|
"dag:end",
|
|
2151
2898
|
"job:end"
|
|
2152
2899
|
]);
|
|
2153
|
-
function
|
|
2900
|
+
function ensureDir3(path) {
|
|
2154
2901
|
const dir = dirname(path);
|
|
2155
2902
|
if (dir && dir !== ".") mkdirSync(dir, { recursive: true });
|
|
2156
2903
|
}
|
|
2157
2904
|
function makeRecorder(path) {
|
|
2158
|
-
|
|
2905
|
+
ensureDir3(path);
|
|
2159
2906
|
writeFileSync(path, "");
|
|
2160
2907
|
return (event) => {
|
|
2161
2908
|
if (NOISE2.has(event.kind)) return;
|
|
@@ -2167,14 +2914,14 @@ function makeRecorder(path) {
|
|
|
2167
2914
|
};
|
|
2168
2915
|
}
|
|
2169
2916
|
function makeCheckpointer(path, state) {
|
|
2170
|
-
|
|
2917
|
+
ensureDir3(path);
|
|
2171
2918
|
return (event) => {
|
|
2172
2919
|
if (!CHECKPOINT_AT.has(event.kind)) return;
|
|
2173
2920
|
flushCheckpoint(path, state);
|
|
2174
2921
|
};
|
|
2175
2922
|
}
|
|
2176
2923
|
function flushCheckpoint(path, state) {
|
|
2177
|
-
|
|
2924
|
+
ensureDir3(path);
|
|
2178
2925
|
try {
|
|
2179
2926
|
writeFileSync(path, JSON.stringify({ ts: Date.now(), state }, null, 2));
|
|
2180
2927
|
} catch {
|
|
@@ -2344,6 +3091,6 @@ function exitCodeFor(outcome) {
|
|
|
2344
3091
|
}
|
|
2345
3092
|
}
|
|
2346
3093
|
|
|
2347
|
-
export { Budget, EXIT_PAUSED, EngineRegistry, GhForge, MockForge, Stats, addWorktree, agentCheck, agentJob, all, always, any, appendLedger, appendPrompt, bodyPassed, buildChecksArgs, buildCreateArgs, buildEditArgs, buildMergeArgs, buildViewArgs, childContext, commandSucceeds, commit, commitJob, compactLedger, composeCommitBody, conflictedFiles, consolidate, consolidateJob, currentBranch, defineAgent, defineSkill, deleteBranch, describeConditions, ensureIgnored, exitCodeFor, fnJob, forgeChecks, formatEvent, fromFile, gateJob, groundingText, hasStagedChanges, headSha, isDirty, isForge, isRepo, jobMeta, kickback, ledgerPath, listRuns, log, loop, mergeAbort, mergeBranch, mergeNoCommit, minConfidence, never, not, predicate, promptPath, push, quorum, readLedger, readPrompt, readRunStatus, removeWorktree, renderPlan, resetLedger, resetPrompt, resolveSystem, retrieveLedger, run, runEventsPath, runsHome, setMeta, stageAll, toCondition };
|
|
2348
|
-
//# sourceMappingURL=chunk-
|
|
2349
|
-
//# sourceMappingURL=chunk-
|
|
3094
|
+
export { Budget, EXIT_PAUSED, EngineRegistry, GhForge, MockForge, ProgressTracker, Stats, addWorktree, agentCheck, agentContract, agentJob, all, always, any, appendLedger, appendPrompt, bodyPassed, buildChecksArgs, buildCreateArgs, buildEditArgs, buildMergeArgs, buildViewArgs, childContext, commandSucceeds, commit, commitJob, compactLedger, composeCommitBody, conflictedFiles, consolidate, consolidateJob, currentBranch, defineAgent, defineSkill, deleteBranch, describeConditions, ensureIgnored, exitCodeFor, feedbackBlock, fnJob, forgeChecks, formatEvent, fromFile, gateJob, graphPositionBlock, groundingText, hasStagedChanges, headSha, isDirty, isForge, isRepo, isRequiredFeedbackSeverity, jobMeta, kickback, ledgerPath, listRuns, log, loop, mergeAbort, mergeBranch, mergeNoCommit, minConfidence, never, normalizeFeedbackSeverity, not, predicate, promptPath, push, quorum, readLedger, readPrompt, readRunStatus, removeWorktree, renderPlan, resetLedger, resetPrompt, resolveNoProgress, resolveSystem, retrieveLedger, reviewContext, reviewPanel, revisionFromOutcome, revisionRequest, run, runEventsPath, runSemanticRecordsPath, runsHome, semanticRecordsFromEvent, setMeta, stageAll, toCondition, workspaceFingerprint };
|
|
3095
|
+
//# sourceMappingURL=chunk-3PMVII43.js.map
|
|
3096
|
+
//# sourceMappingURL=chunk-3PMVII43.js.map
|