@loops-adk/core 0.1.0 → 0.2.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 +120 -13
- package/assets/logo.png +0 -0
- package/bin/loops.mjs +5 -5
- 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 +177 -3
- package/dist/api.js +26 -10
- package/dist/api.js.map +1 -1
- package/dist/{chunk-XC46B4FD.js → chunk-MA6NDQMO.js} +2 -2
- package/dist/chunk-MA6NDQMO.js.map +1 -0
- package/dist/{chunk-3BPU34DE.js → chunk-WM5QVHM2.js} +789 -46
- package/dist/chunk-WM5QVHM2.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 +249 -11
- package/dist/index.js.map +1 -1
- package/dist/{types-B4wGVpqo.d.ts → types-Cv_3ymr9.d.ts} +118 -37
- package/package.json +10 -1
- package/skills/author-loop/SKILL.md +25 -14
- package/skills/design-agent-team/SKILL.md +108 -0
- package/skills/supervise-loop-run/SKILL.md +64 -0
- package/dist/chunk-3BPU34DE.js.map +0 -1
- package/dist/chunk-XC46B4FD.js.map +0 -1
- package/dist/codex-6I5UZ2HM.js.map +0 -1
|
@@ -1,11 +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
|
-
import { readFileSync, mkdtempSync, existsSync, writeFileSync, appendFileSync, mkdirSync, rmSync } from 'fs';
|
|
5
|
+
import { readFileSync, mkdtempSync, existsSync, writeFileSync, appendFileSync, readdirSync, mkdirSync, rmSync } from 'fs';
|
|
6
6
|
import { execa } from 'execa';
|
|
7
|
-
import { tmpdir } from 'os';
|
|
7
|
+
import { tmpdir, homedir } from 'os';
|
|
8
8
|
import { join, dirname } from 'path';
|
|
9
|
+
import { randomBytes } from 'crypto';
|
|
9
10
|
|
|
10
11
|
// src/core/describe.ts
|
|
11
12
|
var META = /* @__PURE__ */ new WeakMap();
|
|
@@ -34,6 +35,19 @@ function describeConditions(input) {
|
|
|
34
35
|
return [condLabel(input)];
|
|
35
36
|
}
|
|
36
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
|
+
}
|
|
37
51
|
function renderPlan(meta, indent = "") {
|
|
38
52
|
if (!meta) return [`${indent}(a runnable job, shape not introspectable)`];
|
|
39
53
|
const nm = meta.name ? ` "${meta.name}"` : "";
|
|
@@ -68,6 +82,10 @@ function renderPlan(meta, indent = "") {
|
|
|
68
82
|
}
|
|
69
83
|
case "agent":
|
|
70
84
|
out.push(`${indent}agent${nm}${meta.ground ? " (grounded)" : ""}`);
|
|
85
|
+
{
|
|
86
|
+
const contract = renderContract(meta.contract);
|
|
87
|
+
if (contract) out.push(`${indent} contract: ${contract}`);
|
|
88
|
+
}
|
|
71
89
|
break;
|
|
72
90
|
case "fn":
|
|
73
91
|
out.push(`${indent}fn${nm}`);
|
|
@@ -128,12 +146,59 @@ function defineSkill(skill) {
|
|
|
128
146
|
if (!skill.instructions?.trim()) throw new Error(`defineSkill "${skill.name}": empty instructions`);
|
|
129
147
|
return skill;
|
|
130
148
|
}
|
|
149
|
+
function skillRefName(ref) {
|
|
150
|
+
return typeof ref === "string" ? ref : ref.name;
|
|
151
|
+
}
|
|
152
|
+
function validateName(value, label) {
|
|
153
|
+
if (!value?.trim()) throw new Error(`${label}: \`name\` is required`);
|
|
154
|
+
}
|
|
155
|
+
function validateSkillRef(ref, label) {
|
|
156
|
+
if (typeof ref === "string") {
|
|
157
|
+
if (!ref.trim()) throw new Error(`${label}: empty skill name`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
defineSkill(ref);
|
|
161
|
+
}
|
|
131
162
|
function defineAgent(def) {
|
|
132
163
|
if (!def.name) throw new Error("defineAgent: `name` is required");
|
|
133
164
|
if (!def.system?.trim()) throw new Error(`defineAgent "${def.name}": empty system prompt`);
|
|
134
165
|
def.skills?.forEach((s) => defineSkill(s));
|
|
166
|
+
def.requiresSkills?.forEach(
|
|
167
|
+
(s) => validateSkillRef(s, `defineAgent "${def.name}" requiresSkills`)
|
|
168
|
+
);
|
|
169
|
+
def.usesSkills?.forEach(
|
|
170
|
+
(s) => validateSkillRef(s, `defineAgent "${def.name}" usesSkills`)
|
|
171
|
+
);
|
|
172
|
+
def.outputs?.forEach(
|
|
173
|
+
(o) => validateName(o.name, `defineAgent "${def.name}" outputs`)
|
|
174
|
+
);
|
|
175
|
+
def.humanGates?.forEach(
|
|
176
|
+
(g) => validateName(g.name, `defineAgent "${def.name}" humanGates`)
|
|
177
|
+
);
|
|
178
|
+
def.failureModes?.forEach((f) => {
|
|
179
|
+
if (!f.mode?.trim())
|
|
180
|
+
throw new Error(`defineAgent "${def.name}" failureModes: \`mode\` is required`);
|
|
181
|
+
if (!f.recovery?.trim())
|
|
182
|
+
throw new Error(`defineAgent "${def.name}" failureModes "${f.mode}": \`recovery\` is required`);
|
|
183
|
+
});
|
|
135
184
|
return def;
|
|
136
185
|
}
|
|
186
|
+
function agentContract(agent) {
|
|
187
|
+
if (!agent) return void 0;
|
|
188
|
+
const summary = {};
|
|
189
|
+
if (agent.tier) summary.tier = agent.tier;
|
|
190
|
+
if (agent.capabilities?.length) summary.capabilities = [...agent.capabilities];
|
|
191
|
+
if (agent.outputs?.length) summary.outputs = agent.outputs.map((o) => o.name);
|
|
192
|
+
if (agent.requiresSkills?.length)
|
|
193
|
+
summary.requiresSkills = agent.requiresSkills.map(skillRefName);
|
|
194
|
+
if (agent.usesSkills?.length)
|
|
195
|
+
summary.usesSkills = agent.usesSkills.map(skillRefName);
|
|
196
|
+
if (agent.humanGates?.length)
|
|
197
|
+
summary.humanGates = agent.humanGates.map((g) => g.name);
|
|
198
|
+
if (agent.failureModes?.length)
|
|
199
|
+
summary.failureModes = agent.failureModes.map((f) => f.mode);
|
|
200
|
+
return Object.keys(summary).length ? summary : void 0;
|
|
201
|
+
}
|
|
137
202
|
function resolveSystem(agent) {
|
|
138
203
|
if (!agent.skills?.length) return agent.system;
|
|
139
204
|
const methods = agent.skills.map((s) => `### ${s.name}
|
|
@@ -927,6 +992,15 @@ function resetLedger(workspace) {
|
|
|
927
992
|
reset(ledgerPath(workspace));
|
|
928
993
|
}
|
|
929
994
|
|
|
995
|
+
// src/core/text.ts
|
|
996
|
+
function oneLine(text) {
|
|
997
|
+
return text.replace(/\s+/g, " ").trim();
|
|
998
|
+
}
|
|
999
|
+
function truncate(s, max) {
|
|
1000
|
+
return s.length > max ? `${s.slice(0, max).trimEnd()}
|
|
1001
|
+
\u2026` : s;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
930
1004
|
// src/core/consolidate.ts
|
|
931
1005
|
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.";
|
|
932
1006
|
function digest(body, n = 280) {
|
|
@@ -965,11 +1039,6 @@ Output the updated consolidated ledger.`,
|
|
|
965
1039
|
return result.text.trim();
|
|
966
1040
|
}
|
|
967
1041
|
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.";
|
|
968
|
-
function truncate(s, n) {
|
|
969
|
-
const t = s.trim();
|
|
970
|
-
return t.length > n ? `${t.slice(0, n).trimEnd()}
|
|
971
|
-
\u2026` : t;
|
|
972
|
-
}
|
|
973
1042
|
async function compactLedger(ctx, text, opts = {}) {
|
|
974
1043
|
const trimmed = text.trim();
|
|
975
1044
|
if (!trimmed) return "";
|
|
@@ -1038,10 +1107,6 @@ function consolidateJob(config = {}) {
|
|
|
1038
1107
|
}
|
|
1039
1108
|
|
|
1040
1109
|
// src/core/ground.ts
|
|
1041
|
-
function truncate2(s, n) {
|
|
1042
|
-
return s.length > n ? `${s.slice(0, n).trimEnd()}
|
|
1043
|
-
\u2026` : s;
|
|
1044
|
-
}
|
|
1045
1110
|
async function groundingText(workspace, opts = {}) {
|
|
1046
1111
|
const records = await log({
|
|
1047
1112
|
cwd: workspace.dir,
|
|
@@ -1058,7 +1123,7 @@ What prior iterations already did and why \u2014 read it before working so you d
|
|
|
1058
1123
|
const head = `### ${r.sha.slice(0, 7)} ${r.subject}`;
|
|
1059
1124
|
return r.body ? `${head}
|
|
1060
1125
|
|
|
1061
|
-
${
|
|
1126
|
+
${truncate(r.body, bodyChars)}` : head;
|
|
1062
1127
|
});
|
|
1063
1128
|
return `${header}
|
|
1064
1129
|
|
|
@@ -1114,12 +1179,305 @@ Commits a search judged relevant \u2014 read them before working.`;
|
|
|
1114
1179
|
const head = `### ${r.sha.slice(0, 7)} ${r.subject}`;
|
|
1115
1180
|
return r.body ? `${head}
|
|
1116
1181
|
|
|
1117
|
-
${
|
|
1182
|
+
${truncate(r.body, bodyChars)}` : head;
|
|
1118
1183
|
});
|
|
1119
1184
|
return `${header}
|
|
1120
1185
|
|
|
1121
1186
|
${entries.join("\n\n")}`;
|
|
1122
1187
|
}
|
|
1188
|
+
function normalizeFeedbackSeverity(severity) {
|
|
1189
|
+
switch (severity) {
|
|
1190
|
+
case "advisory":
|
|
1191
|
+
return "nice-to-have";
|
|
1192
|
+
case "blocking":
|
|
1193
|
+
case void 0:
|
|
1194
|
+
return "block";
|
|
1195
|
+
default:
|
|
1196
|
+
return severity;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function isRequiredFeedbackSeverity(severity) {
|
|
1200
|
+
const normalized = normalizeFeedbackSeverity(severity);
|
|
1201
|
+
return normalized === "block" || normalized === "should-fix";
|
|
1202
|
+
}
|
|
1203
|
+
function findingLine(finding) {
|
|
1204
|
+
const reviewer = finding.reviewer ? `${finding.reviewer} ` : "";
|
|
1205
|
+
const severity = normalizeFeedbackSeverity(finding.severity);
|
|
1206
|
+
const decision = finding.decision ? ` Decision: ${finding.decision}.` : "";
|
|
1207
|
+
const recommendation = finding.recommendation ? ` Recommendation: ${oneLine(finding.recommendation)}` : "";
|
|
1208
|
+
return `- ${reviewer}[${severity}]: ${oneLine(finding.evidence)}${decision}${recommendation}`;
|
|
1209
|
+
}
|
|
1210
|
+
function defaultReason(findings) {
|
|
1211
|
+
if (!findings?.length) return "Revision requested";
|
|
1212
|
+
if (findings.length === 1) return oneLine(findings[0].evidence);
|
|
1213
|
+
return `${findings.length} findings require another pass`;
|
|
1214
|
+
}
|
|
1215
|
+
function normalizeRevision(input) {
|
|
1216
|
+
const reason = input.reason?.trim() || defaultReason(input.findings);
|
|
1217
|
+
return {
|
|
1218
|
+
reason,
|
|
1219
|
+
target: input.target,
|
|
1220
|
+
findings: input.findings,
|
|
1221
|
+
rerun: input.rerun ?? (input.target ? "target-and-dependents" : void 0),
|
|
1222
|
+
source: input.source,
|
|
1223
|
+
decision: input.decision
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
function revisionRequest(input, over = {}) {
|
|
1227
|
+
const revision = normalizeRevision(input);
|
|
1228
|
+
return {
|
|
1229
|
+
status: over.status ?? "fail",
|
|
1230
|
+
confidence: over.confidence,
|
|
1231
|
+
summary: over.summary ?? revision.reason,
|
|
1232
|
+
data: over.data,
|
|
1233
|
+
error: over.error,
|
|
1234
|
+
revision
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function kickback(to, reason, over = {}) {
|
|
1238
|
+
return revisionRequest({ target: to, reason }, over);
|
|
1239
|
+
}
|
|
1240
|
+
function revisionFromOutcome(outcome) {
|
|
1241
|
+
return outcome.revision;
|
|
1242
|
+
}
|
|
1243
|
+
function feedbackBlock(outcome) {
|
|
1244
|
+
const revision = revisionFromOutcome(outcome);
|
|
1245
|
+
const parts = [
|
|
1246
|
+
"## Feedback to address",
|
|
1247
|
+
"A review or downstream stage requested another pass. Address this before unrelated work."
|
|
1248
|
+
];
|
|
1249
|
+
if (revision?.target) parts.push(`Target: ${revision.target}`);
|
|
1250
|
+
if (revision?.source) parts.push(`Source: ${revision.source}`);
|
|
1251
|
+
if (revision?.decision) parts.push(`Caller decision: ${revision.decision}`);
|
|
1252
|
+
const reason = revision?.reason ?? outcome.summary;
|
|
1253
|
+
if (reason) parts.push(`Reason: ${reason}`);
|
|
1254
|
+
const findings = revision?.findings ?? [];
|
|
1255
|
+
if (findings.length) {
|
|
1256
|
+
parts.push("Findings:");
|
|
1257
|
+
parts.push(findings.map(findingLine).join("\n"));
|
|
1258
|
+
}
|
|
1259
|
+
return parts.join("\n\n");
|
|
1260
|
+
}
|
|
1261
|
+
function graphPositionBlock(graph) {
|
|
1262
|
+
return [
|
|
1263
|
+
"## Graph position",
|
|
1264
|
+
`DAG: ${graph.dag}`,
|
|
1265
|
+
`Current node: ${graph.node}`,
|
|
1266
|
+
`Path: ${graph.path.join(" > ")}`,
|
|
1267
|
+
`Depends on: ${graph.needs.length ? graph.needs.join(", ") : "none"}`,
|
|
1268
|
+
`Direct dependents: ${graph.dependents.length ? graph.dependents.join(", ") : "none"}`
|
|
1269
|
+
].join("\n");
|
|
1270
|
+
}
|
|
1271
|
+
async function runReviewer(reviewer, index, ctx) {
|
|
1272
|
+
const name = reviewer.name ?? `reviewer-${index + 1}`;
|
|
1273
|
+
try {
|
|
1274
|
+
if ("job" in reviewer) {
|
|
1275
|
+
const outcome = await reviewer.job(ctx);
|
|
1276
|
+
return {
|
|
1277
|
+
name,
|
|
1278
|
+
met: outcome.status === "pass",
|
|
1279
|
+
confidence: outcome.confidence,
|
|
1280
|
+
reason: outcome.summary ?? outcome.status
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
const result = await toCondition(reviewer.review)(
|
|
1284
|
+
ctx,
|
|
1285
|
+
ctx.lastOutcome
|
|
1286
|
+
);
|
|
1287
|
+
return {
|
|
1288
|
+
name,
|
|
1289
|
+
met: result.met,
|
|
1290
|
+
confidence: result.confidence,
|
|
1291
|
+
reason: result.reason
|
|
1292
|
+
};
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
if (ctx.signal.aborted) throw e;
|
|
1295
|
+
return {
|
|
1296
|
+
name,
|
|
1297
|
+
met: false,
|
|
1298
|
+
reason: `reviewer errored: ${e instanceof Error ? e.message : String(e)}`
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
function reviewFinding(result) {
|
|
1303
|
+
return {
|
|
1304
|
+
reviewer: result.name,
|
|
1305
|
+
severity: "block",
|
|
1306
|
+
evidence: result.reason
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
function findingSeverityCounts(findings) {
|
|
1310
|
+
const counts = {};
|
|
1311
|
+
for (const finding of findings) {
|
|
1312
|
+
const severity = normalizeFeedbackSeverity(finding.severity);
|
|
1313
|
+
counts[severity] = (counts[severity] ?? 0) + 1;
|
|
1314
|
+
}
|
|
1315
|
+
return counts;
|
|
1316
|
+
}
|
|
1317
|
+
function reviewPanel(config) {
|
|
1318
|
+
const label = config.label ?? "review-panel";
|
|
1319
|
+
if (!config.reviewers.length)
|
|
1320
|
+
throw new LoopError({
|
|
1321
|
+
code: "CONFIG",
|
|
1322
|
+
message: `reviewPanel "${label}": at least one reviewer is required`
|
|
1323
|
+
});
|
|
1324
|
+
const job = async (ctx) => {
|
|
1325
|
+
ctx.emit({ kind: "job:start", ts: Date.now(), path: [...ctx.path], label });
|
|
1326
|
+
const results = await Promise.all(
|
|
1327
|
+
config.reviewers.map((reviewer, i) => runReviewer(reviewer, i, ctx))
|
|
1328
|
+
);
|
|
1329
|
+
const passedCount = results.filter((r) => r.met).length;
|
|
1330
|
+
const required = config.pass === void 0 || config.pass === "all" ? results.length : config.pass;
|
|
1331
|
+
const findings = results.filter((r) => !r.met).map(reviewFinding);
|
|
1332
|
+
const passed = passedCount >= required;
|
|
1333
|
+
const summaryHead = `Review panel: ${passedCount}/${results.length} reviewer(s) cleared`;
|
|
1334
|
+
const summary = findings.length ? `${summaryHead}.
|
|
1335
|
+
${findings.map(findingLine).join("\n")}` : `${summaryHead}.`;
|
|
1336
|
+
const scored = results.map((r) => r.confidence).filter((c) => c != null);
|
|
1337
|
+
const confidence = scored.length ? scored.reduce((sum, c) => sum + c, 0) / scored.length : void 0;
|
|
1338
|
+
const data = {
|
|
1339
|
+
findings,
|
|
1340
|
+
results,
|
|
1341
|
+
passed: passedCount,
|
|
1342
|
+
required,
|
|
1343
|
+
severityCounts: findingSeverityCounts(findings)
|
|
1344
|
+
};
|
|
1345
|
+
const outcome = passed ? { status: "pass", summary, confidence, data } : revisionRequest(
|
|
1346
|
+
{
|
|
1347
|
+
target: config.target,
|
|
1348
|
+
// A clean one-line reason. The findings ride the `findings` array, so
|
|
1349
|
+
// feedbackBlock renders them once (not embedded in the reason too) and
|
|
1350
|
+
// the records/tail `reason` stays a single tidy line. The full
|
|
1351
|
+
// multi-line `summary` is kept on the outcome below for logs/TUI.
|
|
1352
|
+
reason: `${summaryHead}.`,
|
|
1353
|
+
findings,
|
|
1354
|
+
rerun: config.rerun
|
|
1355
|
+
},
|
|
1356
|
+
{ summary, confidence, data }
|
|
1357
|
+
);
|
|
1358
|
+
ctx.emit({ kind: "job:end", ts: Date.now(), path: [...ctx.path], label, outcome });
|
|
1359
|
+
return outcome;
|
|
1360
|
+
};
|
|
1361
|
+
return setMeta(job, { kind: "reviewPanel", name: label });
|
|
1362
|
+
}
|
|
1363
|
+
async function gitOutput(cwd, args, signal) {
|
|
1364
|
+
const out = await execa("git", args, {
|
|
1365
|
+
cwd,
|
|
1366
|
+
reject: false,
|
|
1367
|
+
stripFinalNewline: false,
|
|
1368
|
+
cancelSignal: signal
|
|
1369
|
+
});
|
|
1370
|
+
return out.stdout.trim();
|
|
1371
|
+
}
|
|
1372
|
+
async function resolveFiles(ctx, patterns) {
|
|
1373
|
+
const fromGit = await gitOutput(
|
|
1374
|
+
ctx.workspace.dir,
|
|
1375
|
+
["ls-files", "--", ...patterns],
|
|
1376
|
+
ctx.signal
|
|
1377
|
+
).catch(() => "");
|
|
1378
|
+
const files = fromGit ? fromGit.split("\n").filter(Boolean) : [];
|
|
1379
|
+
if (files.length) return files;
|
|
1380
|
+
return patterns.filter((p) => existsSync(join(ctx.workspace.dir, p)));
|
|
1381
|
+
}
|
|
1382
|
+
function reviewContext(config) {
|
|
1383
|
+
return async (ctx, last) => {
|
|
1384
|
+
const max = config.maxChars ?? 6e3;
|
|
1385
|
+
const buildTests = async () => {
|
|
1386
|
+
if (!config.tests) return [];
|
|
1387
|
+
if (config.tests === true) {
|
|
1388
|
+
const lines = [];
|
|
1389
|
+
if (last?.status) lines.push(`Last outcome status: ${last.status}`);
|
|
1390
|
+
if (last?.summary) lines.push(`Last outcome summary: ${last.summary}`);
|
|
1391
|
+
if (last?.data !== void 0) {
|
|
1392
|
+
let rendered;
|
|
1393
|
+
try {
|
|
1394
|
+
rendered = JSON.stringify(last.data, null, 2);
|
|
1395
|
+
} catch {
|
|
1396
|
+
rendered = String(last.data);
|
|
1397
|
+
}
|
|
1398
|
+
lines.push(`Last outcome data: ${rendered}`);
|
|
1399
|
+
}
|
|
1400
|
+
return lines.length ? [`## Test and outcome context
|
|
1401
|
+
|
|
1402
|
+
${lines.join("\n")}`] : [];
|
|
1403
|
+
}
|
|
1404
|
+
const cwd = config.tests.cwd ?? ctx.workspace.dir;
|
|
1405
|
+
const result = await execa(
|
|
1406
|
+
config.tests.command,
|
|
1407
|
+
config.tests.args ?? [],
|
|
1408
|
+
{ cwd, reject: false, stripFinalNewline: false, cancelSignal: ctx.signal }
|
|
1409
|
+
).catch((e) => {
|
|
1410
|
+
if (ctx.signal.aborted) throw e;
|
|
1411
|
+
return {
|
|
1412
|
+
exitCode: void 0,
|
|
1413
|
+
stdout: "",
|
|
1414
|
+
stderr: e instanceof Error ? e.message : String(e)
|
|
1415
|
+
};
|
|
1416
|
+
});
|
|
1417
|
+
const exit = result.exitCode ?? "(command did not run)";
|
|
1418
|
+
return [
|
|
1419
|
+
`## Test command
|
|
1420
|
+
|
|
1421
|
+
${config.tests.command} ${(config.tests.args ?? []).join(" ")}
|
|
1422
|
+
|
|
1423
|
+
exit: ${exit}
|
|
1424
|
+
|
|
1425
|
+
stdout:
|
|
1426
|
+
${truncate(result.stdout ?? "", max)}
|
|
1427
|
+
|
|
1428
|
+
stderr:
|
|
1429
|
+
${truncate(result.stderr ?? "", max)}`
|
|
1430
|
+
];
|
|
1431
|
+
};
|
|
1432
|
+
const buildDiff = async () => {
|
|
1433
|
+
if (!config.diff) return [];
|
|
1434
|
+
const diff2 = await gitOutput(
|
|
1435
|
+
ctx.workspace.dir,
|
|
1436
|
+
["diff", "HEAD", "--"],
|
|
1437
|
+
ctx.signal
|
|
1438
|
+
).catch(() => "");
|
|
1439
|
+
return diff2 ? [`## Git diff
|
|
1440
|
+
|
|
1441
|
+
${truncate(diff2, max)}`] : [];
|
|
1442
|
+
};
|
|
1443
|
+
const buildFiles = async () => {
|
|
1444
|
+
if (!config.files?.length) return [];
|
|
1445
|
+
const files2 = await resolveFiles(ctx, config.files);
|
|
1446
|
+
const out = [];
|
|
1447
|
+
for (const file of files2) {
|
|
1448
|
+
const path = join(ctx.workspace.dir, file);
|
|
1449
|
+
if (!existsSync(path)) continue;
|
|
1450
|
+
out.push(`## File: ${file}
|
|
1451
|
+
|
|
1452
|
+
${truncate(readFileSync(path, "utf8"), max)}`);
|
|
1453
|
+
}
|
|
1454
|
+
return out;
|
|
1455
|
+
};
|
|
1456
|
+
const buildLedger = async () => {
|
|
1457
|
+
if (!config.ledger) return [];
|
|
1458
|
+
const out = [];
|
|
1459
|
+
const live = [readPrompt(ctx.workspace), readLedger(ctx.workspace)].filter(Boolean).join("\n\n");
|
|
1460
|
+
if (live) out.push(`## Live ledger
|
|
1461
|
+
|
|
1462
|
+
${truncate(live, max)}`);
|
|
1463
|
+
const committed = await groundingText(ctx.workspace, {
|
|
1464
|
+
max: 5,
|
|
1465
|
+
bodyChars: 1200,
|
|
1466
|
+
signal: ctx.signal
|
|
1467
|
+
}).catch(() => "");
|
|
1468
|
+
if (committed) out.push(truncate(committed, max));
|
|
1469
|
+
return out;
|
|
1470
|
+
};
|
|
1471
|
+
const [tests, diff, files, ledger] = await Promise.all([
|
|
1472
|
+
buildTests(),
|
|
1473
|
+
buildDiff(),
|
|
1474
|
+
buildFiles(),
|
|
1475
|
+
buildLedger()
|
|
1476
|
+
]);
|
|
1477
|
+
const sections = [...tests, ...diff, ...files, ...ledger];
|
|
1478
|
+
return sections.join("\n\n---\n\n") || "(no review context)";
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1123
1481
|
|
|
1124
1482
|
// src/core/job.ts
|
|
1125
1483
|
var HANDOFF_MARK = "===HANDOFF===";
|
|
@@ -1197,6 +1555,16 @@ var TERMINAL = (text) => ({
|
|
|
1197
1555
|
summary: text.trim().slice(0, 280),
|
|
1198
1556
|
data: text
|
|
1199
1557
|
});
|
|
1558
|
+
function withOperationalContext(ctx, userPrompt, config) {
|
|
1559
|
+
const parts = [userPrompt];
|
|
1560
|
+
if (config.consumeFeedback && ctx.lastReview) {
|
|
1561
|
+
parts.push(feedbackBlock(ctx.lastReview));
|
|
1562
|
+
}
|
|
1563
|
+
if (config.graphContext && ctx.graph) {
|
|
1564
|
+
parts.push(graphPositionBlock(ctx.graph));
|
|
1565
|
+
}
|
|
1566
|
+
return parts.join("\n\n---\n\n");
|
|
1567
|
+
}
|
|
1200
1568
|
function agentJob(config) {
|
|
1201
1569
|
const job = async (ctx) => {
|
|
1202
1570
|
const path = [...ctx.path];
|
|
@@ -1204,7 +1572,8 @@ function agentJob(config) {
|
|
|
1204
1572
|
ctx.emit({ kind: "job:start", ts: Date.now(), path, label });
|
|
1205
1573
|
const engine = ctx.resolveEngine(config.engine);
|
|
1206
1574
|
const userPrompt = typeof config.prompt === "function" ? await config.prompt(ctx) : config.prompt;
|
|
1207
|
-
const
|
|
1575
|
+
const contextualPrompt = withOperationalContext(ctx, userPrompt, config);
|
|
1576
|
+
const prompt = config.ground ? await withGrounding(ctx, contextualPrompt, config.ground) : contextualPrompt;
|
|
1208
1577
|
const system = config.system !== void 0 ? typeof config.system === "function" ? config.system(ctx) : config.system : config.agent ? resolveSystem(config.agent) : void 0;
|
|
1209
1578
|
let result;
|
|
1210
1579
|
const toolUses = /* @__PURE__ */ new Map();
|
|
@@ -1305,7 +1674,8 @@ function agentJob(config) {
|
|
|
1305
1674
|
return setMeta(job, {
|
|
1306
1675
|
kind: "agent",
|
|
1307
1676
|
name: config.label ?? config.agent?.name ?? "agent",
|
|
1308
|
-
ground: !!config.ground
|
|
1677
|
+
ground: !!config.ground,
|
|
1678
|
+
contract: agentContract(config.agent)
|
|
1309
1679
|
});
|
|
1310
1680
|
}
|
|
1311
1681
|
function composeWay(ctx, last) {
|
|
@@ -1381,14 +1751,6 @@ function commitJob(config) {
|
|
|
1381
1751
|
}
|
|
1382
1752
|
};
|
|
1383
1753
|
}
|
|
1384
|
-
function kickback(to, reason, over) {
|
|
1385
|
-
return {
|
|
1386
|
-
status: "fail",
|
|
1387
|
-
summary: reason,
|
|
1388
|
-
...over,
|
|
1389
|
-
kickback: { to, reason }
|
|
1390
|
-
};
|
|
1391
|
-
}
|
|
1392
1754
|
function fnJob(label, fn) {
|
|
1393
1755
|
const job = async (ctx) => {
|
|
1394
1756
|
const path = [...ctx.path];
|
|
@@ -1438,6 +1800,7 @@ function childContext(parent, over) {
|
|
|
1438
1800
|
log: parent.log,
|
|
1439
1801
|
depth: over.depth,
|
|
1440
1802
|
path: over.path,
|
|
1803
|
+
graph: over.graph ?? parent.graph,
|
|
1441
1804
|
// Inherit the enclosing iteration by default. A `loop` always passes one
|
|
1442
1805
|
// explicitly; a `dag`/`sequence` does not, so without this a node nested in a
|
|
1443
1806
|
// loop would reset to 0 — the "Attempt 0" confound where a retry body could not
|
|
@@ -1745,13 +2108,18 @@ function loop(config) {
|
|
|
1745
2108
|
iteration
|
|
1746
2109
|
});
|
|
1747
2110
|
}
|
|
2111
|
+
const reviewPassed = reviewOutcome.status === "pass";
|
|
2112
|
+
const restartsExhausted = config.maxReviewRestarts != null && consecutiveReviewFails + 1 >= config.maxReviewRestarts;
|
|
2113
|
+
const iterationsRemain = config.max == null || iteration < config.max;
|
|
2114
|
+
const willReenter = !reviewPassed && !restartsExhausted && iterationsRemain;
|
|
1748
2115
|
parent.emit({
|
|
1749
2116
|
kind: "loop:review",
|
|
1750
2117
|
ts: ts(),
|
|
1751
2118
|
path,
|
|
1752
|
-
outcome: reviewOutcome
|
|
2119
|
+
outcome: reviewOutcome,
|
|
2120
|
+
accepted: willReenter
|
|
1753
2121
|
});
|
|
1754
|
-
if (
|
|
2122
|
+
if (reviewPassed) {
|
|
1755
2123
|
await recordMilestone(ctxAt(iteration, last));
|
|
1756
2124
|
return finish(
|
|
1757
2125
|
{
|
|
@@ -1769,7 +2137,7 @@ function loop(config) {
|
|
|
1769
2137
|
`review did not pass (${reviewOutcome.summary ?? reviewOutcome.status}); re-entering ${config.name}`,
|
|
1770
2138
|
"warn"
|
|
1771
2139
|
);
|
|
1772
|
-
if (
|
|
2140
|
+
if (restartsExhausted) {
|
|
1773
2141
|
return finish(
|
|
1774
2142
|
{
|
|
1775
2143
|
status: "exhausted",
|
|
@@ -1865,14 +2233,14 @@ var EngineRegistry = class {
|
|
|
1865
2233
|
this.register(
|
|
1866
2234
|
"agent-sdk",
|
|
1867
2235
|
(o) => lazy(
|
|
1868
|
-
() => import('./agent-sdk-
|
|
2236
|
+
() => import('./agent-sdk-4QJDWM7N.js').then((m) => new m.AgentSdkEngine(o)),
|
|
1869
2237
|
"agent-sdk"
|
|
1870
2238
|
)
|
|
1871
2239
|
);
|
|
1872
2240
|
this.register(
|
|
1873
2241
|
"claude-cli",
|
|
1874
2242
|
(o) => lazy(
|
|
1875
|
-
() => import('./claude-cli-
|
|
2243
|
+
() => import('./claude-cli-75AOQUKG.js').then((m) => new m.ClaudeCliEngine(o)),
|
|
1876
2244
|
"claude-cli"
|
|
1877
2245
|
)
|
|
1878
2246
|
);
|
|
@@ -1885,7 +2253,7 @@ var EngineRegistry = class {
|
|
|
1885
2253
|
);
|
|
1886
2254
|
this.register(
|
|
1887
2255
|
"codex",
|
|
1888
|
-
(o) => lazy(() => import('./codex-
|
|
2256
|
+
(o) => lazy(() => import('./codex-LYZF52WL.js').then((m) => new m.CodexEngine(o)), "codex")
|
|
1889
2257
|
);
|
|
1890
2258
|
}
|
|
1891
2259
|
};
|
|
@@ -1971,25 +2339,383 @@ var Stats = class {
|
|
|
1971
2339
|
return m;
|
|
1972
2340
|
}
|
|
1973
2341
|
};
|
|
2342
|
+
function ensureDir2(path) {
|
|
2343
|
+
const dir = dirname(path);
|
|
2344
|
+
if (dir && dir !== ".") mkdirSync(dir, { recursive: true });
|
|
2345
|
+
}
|
|
2346
|
+
function outcomeSummary(outcome) {
|
|
2347
|
+
return {
|
|
2348
|
+
status: outcome.status,
|
|
2349
|
+
summary: outcome.summary,
|
|
2350
|
+
confidence: outcome.confidence
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
function strongestFinding(findings) {
|
|
2354
|
+
if (!findings?.length) return void 0;
|
|
2355
|
+
const severities = findings.map((f) => normalizeFeedbackSeverity(f.severity));
|
|
2356
|
+
if (severities.includes("block")) return "block";
|
|
2357
|
+
if (severities.includes("should-fix")) return "should-fix";
|
|
2358
|
+
if (severities.includes("nice-to-have")) return "nice-to-have";
|
|
2359
|
+
if (severities.includes("approve")) return "approve";
|
|
2360
|
+
return void 0;
|
|
2361
|
+
}
|
|
2362
|
+
function emittedRevisionRecord(event, outcome) {
|
|
2363
|
+
const revision = revisionFromOutcome(outcome);
|
|
2364
|
+
return revision ? [
|
|
2365
|
+
{
|
|
2366
|
+
kind: "revision-emitted",
|
|
2367
|
+
ts: event.ts,
|
|
2368
|
+
path: event.path,
|
|
2369
|
+
sourceEvent: "job:end",
|
|
2370
|
+
revision
|
|
2371
|
+
}
|
|
2372
|
+
] : [];
|
|
2373
|
+
}
|
|
2374
|
+
function semanticRecordsFromEvent(event) {
|
|
2375
|
+
switch (event.kind) {
|
|
2376
|
+
case "job:start":
|
|
2377
|
+
return [
|
|
2378
|
+
{
|
|
2379
|
+
kind: "dispatch",
|
|
2380
|
+
ts: event.ts,
|
|
2381
|
+
path: event.path,
|
|
2382
|
+
unit: "job",
|
|
2383
|
+
label: event.label
|
|
2384
|
+
}
|
|
2385
|
+
];
|
|
2386
|
+
case "dag:node":
|
|
2387
|
+
if (event.phase === "start")
|
|
2388
|
+
return [
|
|
2389
|
+
{
|
|
2390
|
+
kind: "dispatch",
|
|
2391
|
+
ts: event.ts,
|
|
2392
|
+
path: [...event.path, event.node],
|
|
2393
|
+
unit: "dag-node",
|
|
2394
|
+
node: event.node,
|
|
2395
|
+
attempt: event.attempt
|
|
2396
|
+
}
|
|
2397
|
+
];
|
|
2398
|
+
return event.outcome ? [
|
|
2399
|
+
{
|
|
2400
|
+
kind: "completion",
|
|
2401
|
+
ts: event.ts,
|
|
2402
|
+
path: [...event.path, event.node],
|
|
2403
|
+
unit: "dag-node",
|
|
2404
|
+
label: event.node,
|
|
2405
|
+
outcome: outcomeSummary(event.outcome),
|
|
2406
|
+
attempt: event.attempt
|
|
2407
|
+
}
|
|
2408
|
+
] : [];
|
|
2409
|
+
case "job:end":
|
|
2410
|
+
return [
|
|
2411
|
+
{
|
|
2412
|
+
kind: "completion",
|
|
2413
|
+
ts: event.ts,
|
|
2414
|
+
path: event.path,
|
|
2415
|
+
unit: "job",
|
|
2416
|
+
label: event.label,
|
|
2417
|
+
outcome: outcomeSummary(event.outcome)
|
|
2418
|
+
},
|
|
2419
|
+
...emittedRevisionRecord(event, event.outcome)
|
|
2420
|
+
];
|
|
2421
|
+
case "loop:review": {
|
|
2422
|
+
if (event.outcome.status === "pass") return [];
|
|
2423
|
+
const revision = revisionFromOutcome(event.outcome);
|
|
2424
|
+
const decision = event.accepted === false ? "rejected" : "accepted";
|
|
2425
|
+
const records = [
|
|
2426
|
+
{
|
|
2427
|
+
kind: "surfacing",
|
|
2428
|
+
ts: event.ts,
|
|
2429
|
+
path: event.path,
|
|
2430
|
+
source: "loop-review",
|
|
2431
|
+
decision,
|
|
2432
|
+
severity: strongestFinding(revision?.findings),
|
|
2433
|
+
reason: revision?.reason ?? event.outcome.summary ?? event.outcome.status
|
|
2434
|
+
}
|
|
2435
|
+
];
|
|
2436
|
+
if (revision) {
|
|
2437
|
+
records.push({
|
|
2438
|
+
kind: "revision-routed",
|
|
2439
|
+
ts: event.ts,
|
|
2440
|
+
path: event.path,
|
|
2441
|
+
sourceEvent: "loop:review",
|
|
2442
|
+
decision,
|
|
2443
|
+
revision
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
return records;
|
|
2447
|
+
}
|
|
2448
|
+
case "loop:end":
|
|
2449
|
+
return [
|
|
2450
|
+
{
|
|
2451
|
+
kind: "completion",
|
|
2452
|
+
ts: event.ts,
|
|
2453
|
+
path: event.path,
|
|
2454
|
+
unit: "loop",
|
|
2455
|
+
outcome: outcomeSummary(event.outcome),
|
|
2456
|
+
iterations: event.iterations
|
|
2457
|
+
}
|
|
2458
|
+
];
|
|
2459
|
+
case "dag:kickback": {
|
|
2460
|
+
const at = [...event.path, event.to];
|
|
2461
|
+
const decision = event.accepted ? "accepted" : "rejected";
|
|
2462
|
+
return [
|
|
2463
|
+
{
|
|
2464
|
+
kind: "surfacing",
|
|
2465
|
+
ts: event.ts,
|
|
2466
|
+
path: at,
|
|
2467
|
+
source: "dag-kickback",
|
|
2468
|
+
decision,
|
|
2469
|
+
severity: "block",
|
|
2470
|
+
from: event.from,
|
|
2471
|
+
to: event.to,
|
|
2472
|
+
reason: event.reason,
|
|
2473
|
+
note: event.note
|
|
2474
|
+
},
|
|
2475
|
+
{
|
|
2476
|
+
kind: "revision-routed",
|
|
2477
|
+
ts: event.ts,
|
|
2478
|
+
path: at,
|
|
2479
|
+
sourceEvent: "dag:kickback",
|
|
2480
|
+
decision,
|
|
2481
|
+
revision: {
|
|
2482
|
+
target: event.to,
|
|
2483
|
+
reason: event.reason,
|
|
2484
|
+
source: event.from,
|
|
2485
|
+
rerun: event.accepted ? "target-and-dependents" : void 0
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
];
|
|
2489
|
+
}
|
|
2490
|
+
case "dag:end":
|
|
2491
|
+
return [
|
|
2492
|
+
{
|
|
2493
|
+
kind: "completion",
|
|
2494
|
+
ts: event.ts,
|
|
2495
|
+
path: event.path,
|
|
2496
|
+
unit: "dag",
|
|
2497
|
+
outcome: outcomeSummary(event.outcome)
|
|
2498
|
+
}
|
|
2499
|
+
];
|
|
2500
|
+
default:
|
|
2501
|
+
return [];
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
function makeSemanticRecorder(path) {
|
|
2505
|
+
try {
|
|
2506
|
+
ensureDir2(path);
|
|
2507
|
+
writeFileSync(path, "");
|
|
2508
|
+
} catch {
|
|
2509
|
+
return () => {
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
return (event) => {
|
|
2513
|
+
const records = semanticRecordsFromEvent(event);
|
|
2514
|
+
if (!records.length) return;
|
|
2515
|
+
try {
|
|
2516
|
+
for (const record of records) {
|
|
2517
|
+
appendFileSync(path, `${JSON.stringify(record)}
|
|
2518
|
+
`);
|
|
2519
|
+
}
|
|
2520
|
+
} catch {
|
|
2521
|
+
}
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
1974
2524
|
var NOISE = /* @__PURE__ */ new Set([
|
|
1975
2525
|
"engine:text",
|
|
1976
2526
|
"engine:thinking"
|
|
1977
2527
|
]);
|
|
2528
|
+
function runsHome() {
|
|
2529
|
+
const base = process.env.LOOPS_HOME ?? join(homedir(), ".loops");
|
|
2530
|
+
return join(base, "runs");
|
|
2531
|
+
}
|
|
2532
|
+
function slug(s) {
|
|
2533
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "run";
|
|
2534
|
+
}
|
|
2535
|
+
function newRunId(title) {
|
|
2536
|
+
return `${slug(title)}-${randomBytes(3).toString("hex")}`;
|
|
2537
|
+
}
|
|
2538
|
+
function startSupervisor(input) {
|
|
2539
|
+
const dir = join(runsHome(), input.runId);
|
|
2540
|
+
mkdirSync(dir, { recursive: true });
|
|
2541
|
+
const eventsPath = join(dir, "events.jsonl");
|
|
2542
|
+
const semanticPath = join(dir, "semantic.jsonl");
|
|
2543
|
+
const statusPath = join(dir, "status.json");
|
|
2544
|
+
try {
|
|
2545
|
+
writeFileSync(eventsPath, "");
|
|
2546
|
+
} catch {
|
|
2547
|
+
}
|
|
2548
|
+
const semanticSink = makeSemanticRecorder(semanticPath);
|
|
2549
|
+
const status = {
|
|
2550
|
+
runId: input.runId,
|
|
2551
|
+
pid: process.pid,
|
|
2552
|
+
cwd: input.cwd,
|
|
2553
|
+
title: input.title,
|
|
2554
|
+
startedAt: Date.now(),
|
|
2555
|
+
updatedAt: Date.now(),
|
|
2556
|
+
status: "running",
|
|
2557
|
+
shape: input.shape,
|
|
2558
|
+
live: {
|
|
2559
|
+
path: [],
|
|
2560
|
+
iteration: 0,
|
|
2561
|
+
usage: { inputTokens: 0, outputTokens: 0, calls: 0 }
|
|
2562
|
+
}
|
|
2563
|
+
};
|
|
2564
|
+
const writeStatus = () => {
|
|
2565
|
+
status.updatedAt = Date.now();
|
|
2566
|
+
try {
|
|
2567
|
+
writeFileSync(statusPath, JSON.stringify(status, null, 2));
|
|
2568
|
+
} catch {
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
writeStatus();
|
|
2572
|
+
const sink = (event) => {
|
|
2573
|
+
if (!NOISE.has(event.kind)) {
|
|
2574
|
+
try {
|
|
2575
|
+
appendFileSync(eventsPath, `${JSON.stringify(event)}
|
|
2576
|
+
`);
|
|
2577
|
+
} catch {
|
|
2578
|
+
}
|
|
2579
|
+
semanticSink(event);
|
|
2580
|
+
}
|
|
2581
|
+
switch (event.kind) {
|
|
2582
|
+
case "loop:iteration":
|
|
2583
|
+
status.live.path = event.path;
|
|
2584
|
+
status.live.iteration = event.iteration;
|
|
2585
|
+
break;
|
|
2586
|
+
case "loop:condition":
|
|
2587
|
+
status.live.lastGate = {
|
|
2588
|
+
which: event.which,
|
|
2589
|
+
met: event.result.met,
|
|
2590
|
+
confidence: event.result.confidence,
|
|
2591
|
+
reason: event.result.reason
|
|
2592
|
+
};
|
|
2593
|
+
break;
|
|
2594
|
+
case "dag:node":
|
|
2595
|
+
status.live.path = [...event.path, event.node];
|
|
2596
|
+
break;
|
|
2597
|
+
case "loop:end":
|
|
2598
|
+
case "dag:end":
|
|
2599
|
+
case "job:end":
|
|
2600
|
+
status.live.lastOutcome = {
|
|
2601
|
+
status: event.outcome.status,
|
|
2602
|
+
summary: event.outcome.summary
|
|
2603
|
+
};
|
|
2604
|
+
status.live.path = event.path;
|
|
2605
|
+
break;
|
|
2606
|
+
case "engine:usage":
|
|
2607
|
+
status.live.usage.inputTokens += event.usage.inputTokens;
|
|
2608
|
+
status.live.usage.outputTokens += event.usage.outputTokens;
|
|
2609
|
+
status.live.usage.calls += 1;
|
|
2610
|
+
break;
|
|
2611
|
+
}
|
|
2612
|
+
if (!NOISE.has(event.kind)) writeStatus();
|
|
2613
|
+
};
|
|
2614
|
+
const finish = (outcome) => {
|
|
2615
|
+
status.status = outcome.status;
|
|
2616
|
+
status.endedAt = Date.now();
|
|
2617
|
+
status.live.lastOutcome = {
|
|
2618
|
+
status: outcome.status,
|
|
2619
|
+
summary: outcome.summary
|
|
2620
|
+
};
|
|
2621
|
+
writeStatus();
|
|
2622
|
+
};
|
|
2623
|
+
return { runId: input.runId, dir, sink, finish };
|
|
2624
|
+
}
|
|
2625
|
+
function isAlive(pid) {
|
|
2626
|
+
try {
|
|
2627
|
+
process.kill(pid, 0);
|
|
2628
|
+
return true;
|
|
2629
|
+
} catch (e) {
|
|
2630
|
+
return e.code === "EPERM";
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
function readRunStatus(runId) {
|
|
2634
|
+
try {
|
|
2635
|
+
const raw = readFileSync(join(runsHome(), runId, "status.json"), "utf8");
|
|
2636
|
+
const s = JSON.parse(raw);
|
|
2637
|
+
s.alive = s.status === "running" ? isAlive(s.pid) : false;
|
|
2638
|
+
return s;
|
|
2639
|
+
} catch {
|
|
2640
|
+
return void 0;
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
function listRuns() {
|
|
2644
|
+
const base = runsHome();
|
|
2645
|
+
if (!existsSync(base)) return [];
|
|
2646
|
+
const out = [];
|
|
2647
|
+
for (const id of readdirSync(base)) {
|
|
2648
|
+
const s = readRunStatus(id);
|
|
2649
|
+
if (s) out.push(s);
|
|
2650
|
+
}
|
|
2651
|
+
return out.sort((a, b) => b.startedAt - a.startedAt);
|
|
2652
|
+
}
|
|
2653
|
+
function runEventsPath(runId) {
|
|
2654
|
+
return join(runsHome(), runId, "events.jsonl");
|
|
2655
|
+
}
|
|
2656
|
+
function runSemanticRecordsPath(runId) {
|
|
2657
|
+
return join(runsHome(), runId, "semantic.jsonl");
|
|
2658
|
+
}
|
|
2659
|
+
function formatEvent(event) {
|
|
2660
|
+
const at = event.path.length ? `${event.path.join(" \u203A ")} ` : "";
|
|
2661
|
+
switch (event.kind) {
|
|
2662
|
+
case "loop:start":
|
|
2663
|
+
return `${at}\u25B8 loop${event.max ? ` (max ${event.max})` : ""}`;
|
|
2664
|
+
case "dag:start":
|
|
2665
|
+
return `${at}\u25B8 dag (${event.nodes.length} nodes)`;
|
|
2666
|
+
case "loop:iteration":
|
|
2667
|
+
return `${at}\xB7 iteration ${event.iteration}`;
|
|
2668
|
+
case "loop:condition":
|
|
2669
|
+
return `${at}\xB7 ${event.which} ${event.result.met ? "met" : "not met"}: ${event.result.reason}`;
|
|
2670
|
+
case "loop:review":
|
|
2671
|
+
return `${at}\xB7 review: ${event.outcome.status}`;
|
|
2672
|
+
case "loop:end":
|
|
2673
|
+
return `${at}\u25C2 ${event.outcome.status} (${event.iterations} iter)`;
|
|
2674
|
+
case "dag:node":
|
|
2675
|
+
return `${at}\xB7 node ${event.node}: ${event.phase}${event.outcome ? ` (${event.outcome.status})` : ""}`;
|
|
2676
|
+
case "dag:kickback":
|
|
2677
|
+
return `${at}\u21A9 kickback ${event.accepted ? "accepted" : "rejected"} ${event.from} -> ${event.to}: ${event.reason}${event.note ? ` (${event.note})` : ""}`;
|
|
2678
|
+
case "dag:end":
|
|
2679
|
+
return `${at}\u25C2 dag ${event.outcome.status}`;
|
|
2680
|
+
case "job:start":
|
|
2681
|
+
return `${at}\u2022 ${event.label}`;
|
|
2682
|
+
case "job:end":
|
|
2683
|
+
return `${at}\u2022 ${event.label}: ${event.outcome.status}`;
|
|
2684
|
+
case "engine:tool":
|
|
2685
|
+
return `${at} tool ${event.name} ${event.phase}`;
|
|
2686
|
+
case "engine:usage":
|
|
2687
|
+
return `${at} ${event.model}: ${event.usage.inputTokens}/${event.usage.outputTokens} tok`;
|
|
2688
|
+
case "limit:wait":
|
|
2689
|
+
return `${at}\u23F8 limit ${event.code}: waiting ${Math.round(event.waitMs / 1e3)}s`;
|
|
2690
|
+
case "limit:pause":
|
|
2691
|
+
return `${at}\u23F8 paused (${event.code}): ${event.reason}`;
|
|
2692
|
+
case "log":
|
|
2693
|
+
return `${at}${event.message}`;
|
|
2694
|
+
case "error":
|
|
2695
|
+
return `${at}\u2717 ${event.code}: ${event.message}`;
|
|
2696
|
+
default:
|
|
2697
|
+
return `${at}${event.kind}`;
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
var NOISE2 = /* @__PURE__ */ new Set([
|
|
2701
|
+
"engine:text",
|
|
2702
|
+
"engine:thinking"
|
|
2703
|
+
]);
|
|
1978
2704
|
var CHECKPOINT_AT = /* @__PURE__ */ new Set([
|
|
1979
2705
|
"loop:iteration",
|
|
1980
2706
|
"loop:end",
|
|
1981
2707
|
"dag:end",
|
|
1982
2708
|
"job:end"
|
|
1983
2709
|
]);
|
|
1984
|
-
function
|
|
2710
|
+
function ensureDir3(path) {
|
|
1985
2711
|
const dir = dirname(path);
|
|
1986
2712
|
if (dir && dir !== ".") mkdirSync(dir, { recursive: true });
|
|
1987
2713
|
}
|
|
1988
2714
|
function makeRecorder(path) {
|
|
1989
|
-
|
|
2715
|
+
ensureDir3(path);
|
|
1990
2716
|
writeFileSync(path, "");
|
|
1991
2717
|
return (event) => {
|
|
1992
|
-
if (
|
|
2718
|
+
if (NOISE2.has(event.kind)) return;
|
|
1993
2719
|
try {
|
|
1994
2720
|
appendFileSync(path, `${JSON.stringify(event)}
|
|
1995
2721
|
`);
|
|
@@ -1998,14 +2724,14 @@ function makeRecorder(path) {
|
|
|
1998
2724
|
};
|
|
1999
2725
|
}
|
|
2000
2726
|
function makeCheckpointer(path, state) {
|
|
2001
|
-
|
|
2727
|
+
ensureDir3(path);
|
|
2002
2728
|
return (event) => {
|
|
2003
2729
|
if (!CHECKPOINT_AT.has(event.kind)) return;
|
|
2004
2730
|
flushCheckpoint(path, state);
|
|
2005
2731
|
};
|
|
2006
2732
|
}
|
|
2007
2733
|
function flushCheckpoint(path, state) {
|
|
2008
|
-
|
|
2734
|
+
ensureDir3(path);
|
|
2009
2735
|
try {
|
|
2010
2736
|
writeFileSync(path, JSON.stringify({ ts: Date.now(), state }, null, 2));
|
|
2011
2737
|
} catch {
|
|
@@ -2051,10 +2777,23 @@ async function run(job, options = {}) {
|
|
|
2051
2777
|
});
|
|
2052
2778
|
}
|
|
2053
2779
|
}
|
|
2780
|
+
const dir = options.cwd ?? process.cwd();
|
|
2054
2781
|
const sinks = [];
|
|
2055
2782
|
if (options.recordTo) sinks.push(makeRecorder(options.recordTo));
|
|
2056
2783
|
if (options.checkpoint)
|
|
2057
2784
|
sinks.push(makeCheckpointer(options.checkpoint, initialState));
|
|
2785
|
+
let supervisor;
|
|
2786
|
+
if (options.supervise) {
|
|
2787
|
+
const shape = jobMeta(job);
|
|
2788
|
+
const title = shape?.name ?? "run";
|
|
2789
|
+
supervisor = startSupervisor({
|
|
2790
|
+
runId: newRunId(title),
|
|
2791
|
+
cwd: dir,
|
|
2792
|
+
title,
|
|
2793
|
+
shape
|
|
2794
|
+
});
|
|
2795
|
+
sinks.push(supervisor.sink);
|
|
2796
|
+
}
|
|
2058
2797
|
const emit = (event) => {
|
|
2059
2798
|
stats.record(event);
|
|
2060
2799
|
if (budget && event.kind === "engine:usage")
|
|
@@ -2063,7 +2802,6 @@ async function run(job, options = {}) {
|
|
|
2063
2802
|
for (const sink of sinks) sink(event);
|
|
2064
2803
|
};
|
|
2065
2804
|
const resolveEngine = (ref) => registry.create(ref, defaultEngine);
|
|
2066
|
-
const dir = options.cwd ?? process.cwd();
|
|
2067
2805
|
const workspace = {
|
|
2068
2806
|
dir,
|
|
2069
2807
|
branch: await currentBranch({ cwd: dir, signal: controller.signal })
|
|
@@ -2081,18 +2819,21 @@ async function run(job, options = {}) {
|
|
|
2081
2819
|
message: `environment "${options.environment.name}" failed to start: ${error.message}`,
|
|
2082
2820
|
code: error.code
|
|
2083
2821
|
});
|
|
2822
|
+
const failOutcome = {
|
|
2823
|
+
status: "fail",
|
|
2824
|
+
summary: `environment failed to start: ${error.message}`,
|
|
2825
|
+
error
|
|
2826
|
+
};
|
|
2827
|
+
supervisor?.finish(failOutcome);
|
|
2084
2828
|
return {
|
|
2085
|
-
outcome:
|
|
2086
|
-
status: "fail",
|
|
2087
|
-
summary: `environment failed to start: ${error.message}`,
|
|
2088
|
-
error
|
|
2089
|
-
},
|
|
2829
|
+
outcome: failOutcome,
|
|
2090
2830
|
stats: stats.snapshot(),
|
|
2091
2831
|
budget: budget ? {
|
|
2092
2832
|
limit: budget.limit,
|
|
2093
2833
|
spent: budget.spent(),
|
|
2094
2834
|
remaining: budget.remaining()
|
|
2095
|
-
} : void 0
|
|
2835
|
+
} : void 0,
|
|
2836
|
+
runId: supervisor?.runId
|
|
2096
2837
|
};
|
|
2097
2838
|
}
|
|
2098
2839
|
}
|
|
@@ -2133,6 +2874,7 @@ async function run(job, options = {}) {
|
|
|
2133
2874
|
}
|
|
2134
2875
|
if (outcome.status === "paused" && options.checkpoint)
|
|
2135
2876
|
flushCheckpoint(options.checkpoint, initialState);
|
|
2877
|
+
supervisor?.finish(outcome);
|
|
2136
2878
|
return {
|
|
2137
2879
|
outcome,
|
|
2138
2880
|
stats: stats.snapshot(),
|
|
@@ -2140,7 +2882,8 @@ async function run(job, options = {}) {
|
|
|
2140
2882
|
limit: budget.limit,
|
|
2141
2883
|
spent: budget.spent(),
|
|
2142
2884
|
remaining: budget.remaining()
|
|
2143
|
-
} : void 0
|
|
2885
|
+
} : void 0,
|
|
2886
|
+
runId: supervisor?.runId
|
|
2144
2887
|
};
|
|
2145
2888
|
}
|
|
2146
2889
|
function exitCodeFor(outcome) {
|
|
@@ -2158,6 +2901,6 @@ function exitCodeFor(outcome) {
|
|
|
2158
2901
|
}
|
|
2159
2902
|
}
|
|
2160
2903
|
|
|
2161
|
-
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, fromFile, gateJob, groundingText, hasStagedChanges, headSha, isDirty, isForge, isRepo, jobMeta, kickback, ledgerPath, log, loop, mergeAbort, mergeBranch, mergeNoCommit, minConfidence, never, not, predicate, promptPath, push, quorum, readLedger, readPrompt, removeWorktree, renderPlan, resetLedger, resetPrompt, resolveSystem, retrieveLedger, run, setMeta, stageAll, toCondition };
|
|
2162
|
-
//# sourceMappingURL=chunk-
|
|
2163
|
-
//# sourceMappingURL=chunk-
|
|
2904
|
+
export { Budget, EXIT_PAUSED, EngineRegistry, GhForge, MockForge, 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, resolveSystem, retrieveLedger, reviewContext, reviewPanel, revisionFromOutcome, revisionRequest, run, runEventsPath, runSemanticRecordsPath, runsHome, semanticRecordsFromEvent, setMeta, stageAll, toCondition };
|
|
2905
|
+
//# sourceMappingURL=chunk-WM5QVHM2.js.map
|
|
2906
|
+
//# sourceMappingURL=chunk-WM5QVHM2.js.map
|