@chit-run/cli 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chit.js +2057 -1374
- package/package.json +1 -1
package/dist/chit.js
CHANGED
|
@@ -970,6 +970,7 @@ function validateLoopRecord(raw) {
|
|
|
970
970
|
scope: str2(o, "scope", ctx),
|
|
971
971
|
task: str2(o, "task", ctx),
|
|
972
972
|
repo: str2(o, "repo", ctx),
|
|
973
|
+
repoKey: str2(o, "repoKey", ctx),
|
|
973
974
|
startedAt: str2(o, "startedAt", ctx),
|
|
974
975
|
maxIterations: int2(o, "maxIterations", ctx, 1)
|
|
975
976
|
};
|
|
@@ -988,8 +989,11 @@ function validateLoopRecord(raw) {
|
|
|
988
989
|
checkDurationMs: int2(o, "checkDurationMs", ctx, 0),
|
|
989
990
|
at: str2(o, "at", ctx)
|
|
990
991
|
};
|
|
991
|
-
if (o.
|
|
992
|
-
rec.
|
|
992
|
+
if (o.workspaceWarnings !== undefined) {
|
|
993
|
+
rec.workspaceWarnings = stringArray2(o, "workspaceWarnings", ctx);
|
|
994
|
+
}
|
|
995
|
+
if (o.auditRef !== undefined)
|
|
996
|
+
rec.auditRef = str2(o, "auditRef", ctx);
|
|
993
997
|
const usage = optUsage2(o, ctx);
|
|
994
998
|
if (usage !== undefined)
|
|
995
999
|
rec.usage = usage;
|
|
@@ -4675,7 +4679,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
4675
4679
|
const schOrFunc = root.refs[ref];
|
|
4676
4680
|
if (schOrFunc)
|
|
4677
4681
|
return schOrFunc;
|
|
4678
|
-
let _sch =
|
|
4682
|
+
let _sch = resolve4.call(this, root, ref);
|
|
4679
4683
|
if (_sch === undefined) {
|
|
4680
4684
|
const schema = (_a3 = root.localRefs) === null || _a3 === undefined ? undefined : _a3[ref];
|
|
4681
4685
|
const { schemaId } = this.opts;
|
|
@@ -4702,7 +4706,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
4702
4706
|
function sameSchemaEnv(s1, s2) {
|
|
4703
4707
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
4704
4708
|
}
|
|
4705
|
-
function
|
|
4709
|
+
function resolve4(root, ref) {
|
|
4706
4710
|
let sch;
|
|
4707
4711
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
4708
4712
|
ref = sch;
|
|
@@ -5288,7 +5292,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5288
5292
|
}
|
|
5289
5293
|
return uri;
|
|
5290
5294
|
}
|
|
5291
|
-
function
|
|
5295
|
+
function resolve4(baseURI, relativeURI, options) {
|
|
5292
5296
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
5293
5297
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
5294
5298
|
schemelessOptions.skipEscape = true;
|
|
@@ -5547,7 +5551,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5547
5551
|
var fastUri = {
|
|
5548
5552
|
SCHEMES,
|
|
5549
5553
|
normalize,
|
|
5550
|
-
resolve:
|
|
5554
|
+
resolve: resolve4,
|
|
5551
5555
|
resolveComponent,
|
|
5552
5556
|
equal,
|
|
5553
5557
|
serialize,
|
|
@@ -9963,12 +9967,12 @@ var init_dist = __esm(() => {
|
|
|
9963
9967
|
});
|
|
9964
9968
|
|
|
9965
9969
|
// ../studio/src/server/audit.ts
|
|
9966
|
-
import { existsSync as
|
|
9967
|
-
import { homedir as
|
|
9968
|
-
import { join as
|
|
9970
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
|
|
9971
|
+
import { homedir as homedir7 } from "os";
|
|
9972
|
+
import { join as join10 } from "path";
|
|
9969
9973
|
function defaultAuditDir2() {
|
|
9970
|
-
const xdg = process.env.XDG_STATE_HOME ||
|
|
9971
|
-
return
|
|
9974
|
+
const xdg = process.env.XDG_STATE_HOME || join10(homedir7(), ".local", "state");
|
|
9975
|
+
return join10(xdg, "chit", "audit");
|
|
9972
9976
|
}
|
|
9973
9977
|
function blobRefs(e) {
|
|
9974
9978
|
switch (e.type) {
|
|
@@ -9987,13 +9991,13 @@ function blobRefs(e) {
|
|
|
9987
9991
|
function readAuditRun(auditDir, runId, includeBlobs) {
|
|
9988
9992
|
if (!SAFE_RUN_ID2.test(runId))
|
|
9989
9993
|
return { kind: "invalid-id" };
|
|
9990
|
-
const runDir =
|
|
9991
|
-
const eventsPath =
|
|
9992
|
-
if (!
|
|
9994
|
+
const runDir = join10(auditDir, "runs", runId);
|
|
9995
|
+
const eventsPath = join10(runDir, "events.jsonl");
|
|
9996
|
+
if (!existsSync9(eventsPath))
|
|
9993
9997
|
return { kind: "not-found" };
|
|
9994
9998
|
let events2;
|
|
9995
9999
|
try {
|
|
9996
|
-
events2 = parseAuditLog(
|
|
10000
|
+
events2 = parseAuditLog(readFileSync12(eventsPath, "utf-8"));
|
|
9997
10001
|
} catch (e) {
|
|
9998
10002
|
if (e instanceof AuditEventError)
|
|
9999
10003
|
return { kind: "invalid-log", message: e.message };
|
|
@@ -10001,15 +10005,15 @@ function readAuditRun(auditDir, runId, includeBlobs) {
|
|
|
10001
10005
|
}
|
|
10002
10006
|
if (!includeBlobs)
|
|
10003
10007
|
return { kind: "ok", events: events2 };
|
|
10004
|
-
const blobsDir =
|
|
10008
|
+
const blobsDir = join10(runDir, "blobs");
|
|
10005
10009
|
const blobs = {};
|
|
10006
10010
|
for (const e of events2) {
|
|
10007
10011
|
for (const ref of blobRefs(e)) {
|
|
10008
10012
|
if (!SHA256_HEX2.test(ref) || ref in blobs)
|
|
10009
10013
|
continue;
|
|
10010
|
-
const blobPath =
|
|
10011
|
-
if (
|
|
10012
|
-
blobs[ref] =
|
|
10014
|
+
const blobPath = join10(blobsDir, ref);
|
|
10015
|
+
if (existsSync9(blobPath))
|
|
10016
|
+
blobs[ref] = readFileSync12(blobPath, "utf-8");
|
|
10013
10017
|
}
|
|
10014
10018
|
}
|
|
10015
10019
|
return { kind: "ok", events: events2, blobs };
|
|
@@ -10067,12 +10071,12 @@ var init_auth = __esm(() => {
|
|
|
10067
10071
|
});
|
|
10068
10072
|
|
|
10069
10073
|
// ../studio/src/server/paths.ts
|
|
10070
|
-
import { existsSync as
|
|
10071
|
-
import { isAbsolute as
|
|
10074
|
+
import { existsSync as existsSync10, statSync as statSync3 } from "fs";
|
|
10075
|
+
import { isAbsolute as isAbsolute4, resolve as resolve6 } from "path";
|
|
10072
10076
|
function resolveExplicitPath(userPath, cwd) {
|
|
10073
|
-
const candidate =
|
|
10074
|
-
const canonical =
|
|
10075
|
-
if (!
|
|
10077
|
+
const candidate = isAbsolute4(userPath) ? userPath : resolve6(cwd, userPath);
|
|
10078
|
+
const canonical = resolve6(candidate);
|
|
10079
|
+
if (!existsSync10(canonical)) {
|
|
10076
10080
|
throw new PathError("not-found", `path "${userPath}" does not exist`);
|
|
10077
10081
|
}
|
|
10078
10082
|
if (!statSync3(canonical).isFile()) {
|
|
@@ -10093,11 +10097,11 @@ var init_paths = __esm(() => {
|
|
|
10093
10097
|
});
|
|
10094
10098
|
|
|
10095
10099
|
// ../studio/src/server/discovery.ts
|
|
10096
|
-
import { readdirSync as
|
|
10097
|
-
import { basename, join as
|
|
10100
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync13 } from "fs";
|
|
10101
|
+
import { basename, join as join11, relative } from "path";
|
|
10098
10102
|
function safeParseChit(absolutePath) {
|
|
10099
10103
|
try {
|
|
10100
|
-
const raw2 = JSON.parse(
|
|
10104
|
+
const raw2 = JSON.parse(readFileSync13(absolutePath, "utf-8"));
|
|
10101
10105
|
parseManifest(raw2);
|
|
10102
10106
|
return true;
|
|
10103
10107
|
} catch {
|
|
@@ -10117,14 +10121,14 @@ function discover(opts) {
|
|
|
10117
10121
|
relPath: relPathFromCwd(absolutePath, opts.cwd)
|
|
10118
10122
|
};
|
|
10119
10123
|
}
|
|
10120
|
-
const entries =
|
|
10124
|
+
const entries = readdirSync4(opts.cwd, { withFileTypes: true });
|
|
10121
10125
|
const candidates = [];
|
|
10122
10126
|
for (const entry of entries) {
|
|
10123
10127
|
if (!entry.isFile())
|
|
10124
10128
|
continue;
|
|
10125
10129
|
if (!entry.name.endsWith(".json"))
|
|
10126
10130
|
continue;
|
|
10127
|
-
const absolutePath =
|
|
10131
|
+
const absolutePath = join11(opts.cwd, entry.name);
|
|
10128
10132
|
if (!safeParseChit(absolutePath))
|
|
10129
10133
|
continue;
|
|
10130
10134
|
candidates.push({
|
|
@@ -10149,14 +10153,14 @@ var init_discovery = __esm(() => {
|
|
|
10149
10153
|
});
|
|
10150
10154
|
|
|
10151
10155
|
// ../studio/src/server/docs.ts
|
|
10152
|
-
import { createHash as
|
|
10153
|
-
import { readFileSync as
|
|
10156
|
+
import { createHash as createHash6 } from "crypto";
|
|
10157
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
10154
10158
|
import { basename as basename2, relative as relative2 } from "path";
|
|
10155
10159
|
function canonicalize(draft) {
|
|
10156
10160
|
return JSON.stringify(draft, null, "\t");
|
|
10157
10161
|
}
|
|
10158
10162
|
function hashRaw(raw2) {
|
|
10159
|
-
return
|
|
10163
|
+
return createHash6("sha256").update(raw2, "utf-8").digest("hex");
|
|
10160
10164
|
}
|
|
10161
10165
|
|
|
10162
10166
|
class DocStore {
|
|
@@ -10183,7 +10187,7 @@ class DocStore {
|
|
|
10183
10187
|
if (!entry)
|
|
10184
10188
|
return null;
|
|
10185
10189
|
try {
|
|
10186
|
-
return hashRaw(
|
|
10190
|
+
return hashRaw(readFileSync14(entry.absolutePath, "utf-8"));
|
|
10187
10191
|
} catch {
|
|
10188
10192
|
return null;
|
|
10189
10193
|
}
|
|
@@ -10225,7 +10229,7 @@ class DocStore {
|
|
|
10225
10229
|
return null;
|
|
10226
10230
|
let raw2;
|
|
10227
10231
|
try {
|
|
10228
|
-
raw2 =
|
|
10232
|
+
raw2 = readFileSync14(entry.absolutePath, "utf-8");
|
|
10229
10233
|
} catch (e) {
|
|
10230
10234
|
const errorDoc = {
|
|
10231
10235
|
id: docId,
|
|
@@ -10281,7 +10285,7 @@ class DocStore {
|
|
|
10281
10285
|
return { kind: "not-found" };
|
|
10282
10286
|
let currentRaw;
|
|
10283
10287
|
try {
|
|
10284
|
-
currentRaw =
|
|
10288
|
+
currentRaw = readFileSync14(entry.absolutePath, "utf-8");
|
|
10285
10289
|
} catch {
|
|
10286
10290
|
return { kind: "not-found" };
|
|
10287
10291
|
}
|
|
@@ -10293,7 +10297,7 @@ class DocStore {
|
|
|
10293
10297
|
const manifest = parseManifest(draft);
|
|
10294
10298
|
const graphModel = buildGraphModel(manifest, this.registry, surface);
|
|
10295
10299
|
const canonicalRaw = canonicalize(draft);
|
|
10296
|
-
|
|
10300
|
+
writeFileSync7(entry.absolutePath, canonicalRaw, "utf-8");
|
|
10297
10301
|
const newHash = hashRaw(canonicalRaw);
|
|
10298
10302
|
return {
|
|
10299
10303
|
kind: "saved",
|
|
@@ -10362,11 +10366,8 @@ var init_docs = __esm(() => {
|
|
|
10362
10366
|
});
|
|
10363
10367
|
|
|
10364
10368
|
// ../studio/src/server/loops.ts
|
|
10365
|
-
import { existsSync as
|
|
10366
|
-
import { join as
|
|
10367
|
-
function loopsDir2(cwd) {
|
|
10368
|
-
return join10(cwd, ".chit", "loops");
|
|
10369
|
-
}
|
|
10369
|
+
import { existsSync as existsSync11, readdirSync as readdirSync5, readFileSync as readFileSync15 } from "fs";
|
|
10370
|
+
import { join as join12 } from "path";
|
|
10370
10371
|
function summarize(loopId, records) {
|
|
10371
10372
|
const header = records[0];
|
|
10372
10373
|
if (header?.type !== "loop")
|
|
@@ -10383,40 +10384,46 @@ function summarize(loopId, records) {
|
|
|
10383
10384
|
startedAt: header.startedAt
|
|
10384
10385
|
};
|
|
10385
10386
|
}
|
|
10386
|
-
function
|
|
10387
|
-
const
|
|
10388
|
-
if (!
|
|
10387
|
+
function readLoopFrom(dir, loopId) {
|
|
10388
|
+
const path = join12(dir, `${loopId}.jsonl`);
|
|
10389
|
+
if (!existsSync11(path))
|
|
10390
|
+
return { kind: "not-found" };
|
|
10391
|
+
try {
|
|
10392
|
+
const records = validateLoopLog(parseLoopLog(readFileSync15(path, "utf-8")));
|
|
10393
|
+
const header = records[0];
|
|
10394
|
+
if (header?.type !== "loop" || header.loopId !== loopId) {
|
|
10395
|
+
return { kind: "invalid-log", message: "header loopId does not match the file name" };
|
|
10396
|
+
}
|
|
10397
|
+
return { kind: "ok", records };
|
|
10398
|
+
} catch (e) {
|
|
10399
|
+
if (e instanceof LoopLogError)
|
|
10400
|
+
return { kind: "invalid-log", message: e.message };
|
|
10401
|
+
throw e;
|
|
10402
|
+
}
|
|
10403
|
+
}
|
|
10404
|
+
function listLoops(loopsDir) {
|
|
10405
|
+
if (!loopsDir || !existsSync11(loopsDir))
|
|
10389
10406
|
return [];
|
|
10390
10407
|
const summaries = [];
|
|
10391
|
-
for (const name of
|
|
10408
|
+
for (const name of readdirSync5(loopsDir)) {
|
|
10392
10409
|
if (!name.endsWith(".jsonl"))
|
|
10393
10410
|
continue;
|
|
10394
10411
|
const loopId = name.slice(0, -".jsonl".length);
|
|
10395
|
-
|
|
10412
|
+
if (!SAFE_LOOP_ID2.test(loopId))
|
|
10413
|
+
continue;
|
|
10414
|
+
const result = readLoopFrom(loopsDir, loopId);
|
|
10396
10415
|
if (result.kind === "ok")
|
|
10397
10416
|
summaries.push(summarize(loopId, result.records));
|
|
10398
10417
|
}
|
|
10399
10418
|
summaries.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
10400
10419
|
return summaries;
|
|
10401
10420
|
}
|
|
10402
|
-
function readLoop2(
|
|
10421
|
+
function readLoop2(loopsDir, loopId) {
|
|
10403
10422
|
if (!SAFE_LOOP_ID2.test(loopId))
|
|
10404
10423
|
return { kind: "invalid-id" };
|
|
10405
|
-
|
|
10406
|
-
if (!existsSync10(path))
|
|
10424
|
+
if (!loopsDir)
|
|
10407
10425
|
return { kind: "not-found" };
|
|
10408
|
-
|
|
10409
|
-
const records = validateLoopLog(parseLoopLog(readFileSync12(path, "utf-8")));
|
|
10410
|
-
const header = records[0];
|
|
10411
|
-
if (header?.type !== "loop" || header.loopId !== loopId) {
|
|
10412
|
-
return { kind: "invalid-log", message: "header loopId does not match the file name" };
|
|
10413
|
-
}
|
|
10414
|
-
return { kind: "ok", records };
|
|
10415
|
-
} catch (e) {
|
|
10416
|
-
if (e instanceof LoopLogError)
|
|
10417
|
-
return { kind: "invalid-log", message: e.message };
|
|
10418
|
-
throw e;
|
|
10419
|
-
}
|
|
10426
|
+
return readLoopFrom(loopsDir, loopId);
|
|
10420
10427
|
}
|
|
10421
10428
|
var SAFE_LOOP_ID2;
|
|
10422
10429
|
var init_loops = __esm(() => {
|
|
@@ -10455,8 +10462,8 @@ __export(exports_server, {
|
|
|
10455
10462
|
buildApp: () => buildApp,
|
|
10456
10463
|
PathError: () => PathError
|
|
10457
10464
|
});
|
|
10458
|
-
import { existsSync as
|
|
10459
|
-
import { join as
|
|
10465
|
+
import { existsSync as existsSync12 } from "fs";
|
|
10466
|
+
import { join as join13 } from "path";
|
|
10460
10467
|
async function startStudio(opts) {
|
|
10461
10468
|
const hostname3 = opts.hostname ?? "127.0.0.1";
|
|
10462
10469
|
const requestedPort = opts.port ?? 0;
|
|
@@ -10474,7 +10481,8 @@ async function startStudio(opts) {
|
|
|
10474
10481
|
store,
|
|
10475
10482
|
allowedHosts,
|
|
10476
10483
|
clientDistDir,
|
|
10477
|
-
lifecycle: opts.lifecycle
|
|
10484
|
+
lifecycle: opts.lifecycle,
|
|
10485
|
+
loopsDir: opts.loopsDir
|
|
10478
10486
|
});
|
|
10479
10487
|
const server2 = Bun.serve({
|
|
10480
10488
|
port: requestedPort,
|
|
@@ -10508,8 +10516,8 @@ function buildApp(opts) {
|
|
|
10508
10516
|
const asset = c.req.param("asset");
|
|
10509
10517
|
if (!CLIENT_ASSETS.has(asset))
|
|
10510
10518
|
return c.text("not found", 404);
|
|
10511
|
-
const path =
|
|
10512
|
-
if (!
|
|
10519
|
+
const path = join13(opts.clientDistDir, asset);
|
|
10520
|
+
if (!existsSync12(path)) {
|
|
10513
10521
|
return c.text(`client bundle missing at ${path}. Run: bun run studio:build`, 503);
|
|
10514
10522
|
}
|
|
10515
10523
|
return new Response(Bun.file(path));
|
|
@@ -10589,10 +10597,10 @@ function buildApp(opts) {
|
|
|
10589
10597
|
return c.json(result);
|
|
10590
10598
|
});
|
|
10591
10599
|
app.get("/api/loops", (c) => {
|
|
10592
|
-
return c.json(listLoops(opts.
|
|
10600
|
+
return c.json(listLoops(opts.loopsDir));
|
|
10593
10601
|
});
|
|
10594
10602
|
app.get("/api/loops/:loopId", (c) => {
|
|
10595
|
-
const result = readLoop2(opts.
|
|
10603
|
+
const result = readLoop2(opts.loopsDir, c.req.param("loopId"));
|
|
10596
10604
|
if (result.kind === "not-found")
|
|
10597
10605
|
return c.text("not found", 404);
|
|
10598
10606
|
if (result.kind === "invalid-id")
|
|
@@ -10684,15 +10692,15 @@ var init_server = __esm(() => {
|
|
|
10684
10692
|
init_loops();
|
|
10685
10693
|
init_token();
|
|
10686
10694
|
init_paths();
|
|
10687
|
-
CLIENT_DIST =
|
|
10695
|
+
CLIENT_DIST = join13(import.meta.dir, "..", "..", "dist", "client");
|
|
10688
10696
|
CLIENT_ASSETS = new Set(["index.js", "index.css"]);
|
|
10689
10697
|
});
|
|
10690
10698
|
|
|
10691
10699
|
// src/cli/run.ts
|
|
10692
10700
|
init_src();
|
|
10693
|
-
import { readFileSync as
|
|
10694
|
-
import { homedir as
|
|
10695
|
-
import { basename as basename3, dirname as dirname2, join as
|
|
10701
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
10702
|
+
import { homedir as homedir8 } from "os";
|
|
10703
|
+
import { basename as basename3, dirname as dirname2, join as join14 } from "path";
|
|
10696
10704
|
|
|
10697
10705
|
// src/adapters/sanitize.ts
|
|
10698
10706
|
var SENSITIVE_KEY = /key|token|secret|password|auth/i;
|
|
@@ -11596,8 +11604,317 @@ function wrapAdaptersWithAudit(adapters, recorder) {
|
|
|
11596
11604
|
return out;
|
|
11597
11605
|
}
|
|
11598
11606
|
|
|
11607
|
+
// src/jobs/store.ts
|
|
11608
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
11609
|
+
import {
|
|
11610
|
+
existsSync as existsSync3,
|
|
11611
|
+
mkdirSync as mkdirSync2,
|
|
11612
|
+
readdirSync as readdirSync2,
|
|
11613
|
+
readFileSync as readFileSync4,
|
|
11614
|
+
renameSync as renameSync2,
|
|
11615
|
+
rmSync as rmSync3,
|
|
11616
|
+
writeFileSync as writeFileSync2
|
|
11617
|
+
} from "fs";
|
|
11618
|
+
import { homedir as homedir3 } from "os";
|
|
11619
|
+
import { join as join3 } from "path";
|
|
11620
|
+
|
|
11621
|
+
// src/jobs/lock.ts
|
|
11622
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
11623
|
+
import { closeSync, openSync, readFileSync as readFileSync3, rmSync as rmSync2, writeSync } from "fs";
|
|
11624
|
+
|
|
11625
|
+
class LockError extends Error {
|
|
11626
|
+
}
|
|
11627
|
+
function sleepSync(ms) {
|
|
11628
|
+
if (ms <= 0)
|
|
11629
|
+
return;
|
|
11630
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
11631
|
+
}
|
|
11632
|
+
function acquireLock(lockPath, opts = {}) {
|
|
11633
|
+
const retryMs = opts.retryMs ?? 50;
|
|
11634
|
+
const maxAttempts = opts.maxAttempts ?? 200;
|
|
11635
|
+
const token = randomUUID2();
|
|
11636
|
+
for (let attempt = 0;attempt < maxAttempts; attempt++) {
|
|
11637
|
+
try {
|
|
11638
|
+
const fd = openSync(lockPath, "wx");
|
|
11639
|
+
try {
|
|
11640
|
+
writeSync(fd, token);
|
|
11641
|
+
} finally {
|
|
11642
|
+
closeSync(fd);
|
|
11643
|
+
}
|
|
11644
|
+
return { path: lockPath, token };
|
|
11645
|
+
} catch (err) {
|
|
11646
|
+
if (err.code !== "EEXIST")
|
|
11647
|
+
throw err;
|
|
11648
|
+
sleepSync(retryMs);
|
|
11649
|
+
}
|
|
11650
|
+
}
|
|
11651
|
+
throw new LockError(`could not acquire lock ${lockPath} after ${maxAttempts} attempts. ` + "If no chit process holds it, a previous run may have crashed while holding it; " + `remove the lock file to continue: rm ${JSON.stringify(lockPath)}`);
|
|
11652
|
+
}
|
|
11653
|
+
function releaseLock(lock) {
|
|
11654
|
+
try {
|
|
11655
|
+
if (readFileSync3(lock.path, "utf-8") === lock.token)
|
|
11656
|
+
rmSync2(lock.path, { force: true });
|
|
11657
|
+
} catch {}
|
|
11658
|
+
}
|
|
11659
|
+
function withFileLock(lockPath, fn, opts) {
|
|
11660
|
+
const lock = acquireLock(lockPath, opts);
|
|
11661
|
+
try {
|
|
11662
|
+
return fn();
|
|
11663
|
+
} finally {
|
|
11664
|
+
releaseLock(lock);
|
|
11665
|
+
}
|
|
11666
|
+
}
|
|
11667
|
+
|
|
11668
|
+
// src/jobs/store.ts
|
|
11669
|
+
class JobStoreError extends Error {
|
|
11670
|
+
}
|
|
11671
|
+
var SAFE_JOB_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
11672
|
+
function defaultJobsDir() {
|
|
11673
|
+
const xdg = process.env.XDG_STATE_HOME || join3(homedir3(), ".local", "state");
|
|
11674
|
+
return join3(xdg, "chit", "jobs");
|
|
11675
|
+
}
|
|
11676
|
+
|
|
11677
|
+
class JobStore {
|
|
11678
|
+
baseDir;
|
|
11679
|
+
constructor(baseDir = defaultJobsDir()) {
|
|
11680
|
+
this.baseDir = baseDir;
|
|
11681
|
+
}
|
|
11682
|
+
path(jobId) {
|
|
11683
|
+
if (!SAFE_JOB_ID.test(jobId))
|
|
11684
|
+
throw new JobStoreError(`invalid job id ${JSON.stringify(jobId)}`);
|
|
11685
|
+
return join3(this.baseDir, `${jobId}.json`);
|
|
11686
|
+
}
|
|
11687
|
+
lockPath(jobId) {
|
|
11688
|
+
return `${this.path(jobId)}.lock`;
|
|
11689
|
+
}
|
|
11690
|
+
loopLockPath(loopId) {
|
|
11691
|
+
if (!SAFE_JOB_ID.test(loopId))
|
|
11692
|
+
throw new JobStoreError(`invalid loop id ${JSON.stringify(loopId)}`);
|
|
11693
|
+
mkdirSync2(join3(this.baseDir, "locks"), { recursive: true });
|
|
11694
|
+
return join3(this.baseDir, "locks", `${loopId}.lock`);
|
|
11695
|
+
}
|
|
11696
|
+
create(record) {
|
|
11697
|
+
mkdirSync2(this.baseDir, { recursive: true });
|
|
11698
|
+
const path = this.path(record.jobId);
|
|
11699
|
+
withFileLock(this.lockPath(record.jobId), () => {
|
|
11700
|
+
if (existsSync3(path))
|
|
11701
|
+
throw new JobStoreError(`job ${JSON.stringify(record.jobId)} already exists`);
|
|
11702
|
+
writeAtomic(path, record);
|
|
11703
|
+
});
|
|
11704
|
+
}
|
|
11705
|
+
get(jobId) {
|
|
11706
|
+
const path = this.path(jobId);
|
|
11707
|
+
if (!existsSync3(path))
|
|
11708
|
+
return;
|
|
11709
|
+
try {
|
|
11710
|
+
return JSON.parse(readFileSync4(path, "utf-8"));
|
|
11711
|
+
} catch {
|
|
11712
|
+
return;
|
|
11713
|
+
}
|
|
11714
|
+
}
|
|
11715
|
+
update(jobId, mutate) {
|
|
11716
|
+
const path = this.path(jobId);
|
|
11717
|
+
return withFileLock(this.lockPath(jobId), () => {
|
|
11718
|
+
if (!existsSync3(path))
|
|
11719
|
+
throw new JobStoreError(`no job ${JSON.stringify(jobId)}`);
|
|
11720
|
+
const current = JSON.parse(readFileSync4(path, "utf-8"));
|
|
11721
|
+
const next = mutate(current);
|
|
11722
|
+
writeAtomic(path, next);
|
|
11723
|
+
return next;
|
|
11724
|
+
});
|
|
11725
|
+
}
|
|
11726
|
+
list() {
|
|
11727
|
+
if (!existsSync3(this.baseDir))
|
|
11728
|
+
return [];
|
|
11729
|
+
const jobs = [];
|
|
11730
|
+
for (const name of readdirSync2(this.baseDir)) {
|
|
11731
|
+
if (!name.endsWith(".json"))
|
|
11732
|
+
continue;
|
|
11733
|
+
try {
|
|
11734
|
+
jobs.push(JSON.parse(readFileSync4(join3(this.baseDir, name), "utf-8")));
|
|
11735
|
+
} catch {}
|
|
11736
|
+
}
|
|
11737
|
+
jobs.sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? ""));
|
|
11738
|
+
return jobs;
|
|
11739
|
+
}
|
|
11740
|
+
}
|
|
11741
|
+
function writeAtomic(path, record) {
|
|
11742
|
+
const tmp = `${path}.${randomUUID3()}.tmp`;
|
|
11743
|
+
writeFileSync2(tmp, JSON.stringify(record, null, 2));
|
|
11744
|
+
try {
|
|
11745
|
+
renameSync2(tmp, path);
|
|
11746
|
+
} catch (err) {
|
|
11747
|
+
rmSync3(tmp, { force: true });
|
|
11748
|
+
throw err;
|
|
11749
|
+
}
|
|
11750
|
+
}
|
|
11751
|
+
|
|
11752
|
+
// src/jobs/worker.ts
|
|
11753
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
11754
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
|
|
11755
|
+
|
|
11756
|
+
// src/cli/converge.ts
|
|
11757
|
+
init_src();
|
|
11758
|
+
import { execFileSync } from "child_process";
|
|
11759
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
11760
|
+
import { resolve as resolve2 } from "path";
|
|
11761
|
+
|
|
11762
|
+
// src/loops/log-store.ts
|
|
11763
|
+
init_src();
|
|
11764
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
11765
|
+
import { join as join5 } from "path";
|
|
11766
|
+
|
|
11767
|
+
// src/loops/location.ts
|
|
11768
|
+
import { spawnSync } from "child_process";
|
|
11769
|
+
import { createHash as createHash2 } from "crypto";
|
|
11770
|
+
import { realpathSync } from "fs";
|
|
11771
|
+
import { homedir as homedir4 } from "os";
|
|
11772
|
+
import { join as join4 } from "path";
|
|
11773
|
+
function gitTopLevel(cwd) {
|
|
11774
|
+
try {
|
|
11775
|
+
const out = spawnSync("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
|
|
11776
|
+
encoding: "utf-8",
|
|
11777
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
11778
|
+
});
|
|
11779
|
+
if (out.status !== 0)
|
|
11780
|
+
return;
|
|
11781
|
+
const top = out.stdout.trim();
|
|
11782
|
+
return top || undefined;
|
|
11783
|
+
} catch {
|
|
11784
|
+
return;
|
|
11785
|
+
}
|
|
11786
|
+
}
|
|
11787
|
+
function repoRoot(cwd) {
|
|
11788
|
+
const base = gitTopLevel(cwd) ?? cwd;
|
|
11789
|
+
try {
|
|
11790
|
+
return realpathSync(base);
|
|
11791
|
+
} catch {
|
|
11792
|
+
return base;
|
|
11793
|
+
}
|
|
11794
|
+
}
|
|
11795
|
+
function repoKey(cwd) {
|
|
11796
|
+
return createHash2("sha256").update(repoRoot(cwd)).digest("hex").slice(0, 16);
|
|
11797
|
+
}
|
|
11798
|
+
function loopStateDir() {
|
|
11799
|
+
const xdg = process.env.XDG_STATE_HOME || join4(homedir4(), ".local", "state");
|
|
11800
|
+
return join4(xdg, "chit", "loops");
|
|
11801
|
+
}
|
|
11802
|
+
function loopLogDir(cwd) {
|
|
11803
|
+
return join4(loopStateDir(), repoKey(cwd));
|
|
11804
|
+
}
|
|
11805
|
+
|
|
11806
|
+
// src/loops/log-store.ts
|
|
11807
|
+
class LoopStoreError extends Error {
|
|
11808
|
+
}
|
|
11809
|
+
var SAFE_LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
11810
|
+
var realClock2 = () => Date.now();
|
|
11811
|
+
function iso(ms) {
|
|
11812
|
+
return new Date(ms).toISOString();
|
|
11813
|
+
}
|
|
11814
|
+
function safeId(loopId) {
|
|
11815
|
+
if (!SAFE_LOOP_ID.test(loopId)) {
|
|
11816
|
+
throw new LoopStoreError(`invalid loop id ${JSON.stringify(loopId)}`);
|
|
11817
|
+
}
|
|
11818
|
+
return loopId;
|
|
11819
|
+
}
|
|
11820
|
+
function loopPath(cwd, loopId) {
|
|
11821
|
+
return join5(loopLogDir(cwd), `${safeId(loopId)}.jsonl`);
|
|
11822
|
+
}
|
|
11823
|
+
function readRecords(path, loopId) {
|
|
11824
|
+
if (!existsSync4(path)) {
|
|
11825
|
+
throw new LoopStoreError(`no loop log for ${JSON.stringify(loopId)} at ${path}`);
|
|
11826
|
+
}
|
|
11827
|
+
const records = validateLoopLog(parseLoopLog(readFileSync5(path, "utf-8")));
|
|
11828
|
+
const header = records[0];
|
|
11829
|
+
if (header.loopId !== loopId) {
|
|
11830
|
+
throw new LoopStoreError(`loop log at ${path} declares loopId ${JSON.stringify(header.loopId)}, expected ${JSON.stringify(loopId)}`);
|
|
11831
|
+
}
|
|
11832
|
+
return records;
|
|
11833
|
+
}
|
|
11834
|
+
function startLoop(cwd, opts) {
|
|
11835
|
+
const loopId = opts.loopId ?? crypto.randomUUID();
|
|
11836
|
+
const path = loopPath(cwd, loopId);
|
|
11837
|
+
if (existsSync4(path) && !opts.force) {
|
|
11838
|
+
throw new LoopStoreError(`loop log already exists at ${path} (pass force to overwrite)`);
|
|
11839
|
+
}
|
|
11840
|
+
mkdirSync3(loopLogDir(cwd), { recursive: true });
|
|
11841
|
+
const header = {
|
|
11842
|
+
type: "loop",
|
|
11843
|
+
schema: 1,
|
|
11844
|
+
loopId,
|
|
11845
|
+
scope: opts.scope,
|
|
11846
|
+
task: opts.task,
|
|
11847
|
+
repo: repoRoot(cwd),
|
|
11848
|
+
repoKey: repoKey(cwd),
|
|
11849
|
+
startedAt: iso((opts.clock ?? realClock2)()),
|
|
11850
|
+
maxIterations: opts.maxIterations
|
|
11851
|
+
};
|
|
11852
|
+
writeFileSync3(path, `${serializeLoopRecord(header)}
|
|
11853
|
+
`);
|
|
11854
|
+
return { loopId, path };
|
|
11855
|
+
}
|
|
11856
|
+
function appendIteration(cwd, loopId, opts) {
|
|
11857
|
+
const path = loopPath(cwd, loopId);
|
|
11858
|
+
const records = readRecords(path, loopId);
|
|
11859
|
+
if (records.some((r) => r.type === "stop")) {
|
|
11860
|
+
throw new LoopStoreError(`loop ${JSON.stringify(loopId)} is already stopped; cannot append`);
|
|
11861
|
+
}
|
|
11862
|
+
const header = records[0];
|
|
11863
|
+
const n = records.filter((r) => r.type === "iteration").length + 1;
|
|
11864
|
+
if (n > header.maxIterations) {
|
|
11865
|
+
throw new LoopStoreError(`loop ${JSON.stringify(loopId)} is at its iteration budget (maxIterations=${header.maxIterations}); cannot append iteration ${n}`);
|
|
11866
|
+
}
|
|
11867
|
+
const rec = {
|
|
11868
|
+
type: "iteration",
|
|
11869
|
+
n,
|
|
11870
|
+
implementSummary: opts.implementSummary,
|
|
11871
|
+
changedFiles: opts.changedFiles,
|
|
11872
|
+
checksRun: opts.checksRun,
|
|
11873
|
+
verdict: opts.verdict,
|
|
11874
|
+
findingCount: opts.findingCount,
|
|
11875
|
+
decision: opts.decision,
|
|
11876
|
+
checkDurationMs: opts.checkDurationMs,
|
|
11877
|
+
at: iso((opts.clock ?? realClock2)())
|
|
11878
|
+
};
|
|
11879
|
+
if (opts.workspaceWarnings !== undefined && opts.workspaceWarnings.length > 0) {
|
|
11880
|
+
rec.workspaceWarnings = opts.workspaceWarnings;
|
|
11881
|
+
}
|
|
11882
|
+
if (opts.auditRef !== undefined)
|
|
11883
|
+
rec.auditRef = opts.auditRef;
|
|
11884
|
+
if (opts.usage !== undefined)
|
|
11885
|
+
rec.usage = opts.usage;
|
|
11886
|
+
appendFileSync2(path, `${serializeLoopRecord(rec)}
|
|
11887
|
+
`);
|
|
11888
|
+
return { n, path };
|
|
11889
|
+
}
|
|
11890
|
+
function stopLoop(cwd, loopId, opts) {
|
|
11891
|
+
const path = loopPath(cwd, loopId);
|
|
11892
|
+
const records = readRecords(path, loopId);
|
|
11893
|
+
if (records.some((r) => r.type === "stop")) {
|
|
11894
|
+
throw new LoopStoreError(`loop ${JSON.stringify(loopId)} is already stopped`);
|
|
11895
|
+
}
|
|
11896
|
+
const header = records[0];
|
|
11897
|
+
const iterations = records.filter((r) => r.type === "iteration").length;
|
|
11898
|
+
const nowMs = (opts.clock ?? realClock2)();
|
|
11899
|
+
const totalElapsedMs = Math.max(0, nowMs - Date.parse(header.startedAt));
|
|
11900
|
+
const rec = {
|
|
11901
|
+
type: "stop",
|
|
11902
|
+
status: opts.status,
|
|
11903
|
+
reason: opts.reason,
|
|
11904
|
+
iterations,
|
|
11905
|
+
totalElapsedMs,
|
|
11906
|
+
endedAt: iso(nowMs)
|
|
11907
|
+
};
|
|
11908
|
+
appendFileSync2(path, `${serializeLoopRecord(rec)}
|
|
11909
|
+
`);
|
|
11910
|
+
return { iterations, totalElapsedMs, path };
|
|
11911
|
+
}
|
|
11912
|
+
function readLoop(cwd, loopId) {
|
|
11913
|
+
return readRecords(loopPath(cwd, loopId), loopId);
|
|
11914
|
+
}
|
|
11915
|
+
|
|
11599
11916
|
// src/runtime/render.ts
|
|
11600
|
-
import { existsSync as
|
|
11917
|
+
import { existsSync as existsSync5 } from "fs";
|
|
11601
11918
|
import { isAbsolute, resolve } from "path";
|
|
11602
11919
|
|
|
11603
11920
|
class RuntimeError extends Error {
|
|
@@ -11645,7 +11962,7 @@ function renderFilePaths(paths, invocationCwd, inputName) {
|
|
|
11645
11962
|
const resolved = [];
|
|
11646
11963
|
for (const p of paths) {
|
|
11647
11964
|
const abs = isAbsolute(p) ? p : resolve(invocationCwd, p);
|
|
11648
|
-
if (!
|
|
11965
|
+
if (!existsSync5(abs)) {
|
|
11649
11966
|
throw new RuntimeError(`input "${inputName}" references missing file: ${p}`);
|
|
11650
11967
|
}
|
|
11651
11968
|
resolved.push(abs);
|
|
@@ -11818,7 +12135,7 @@ async function executeManifest(manifest, options) {
|
|
|
11818
12135
|
}
|
|
11819
12136
|
|
|
11820
12137
|
// src/sessions/fingerprint.ts
|
|
11821
|
-
import { createHash as
|
|
12138
|
+
import { createHash as createHash3 } from "crypto";
|
|
11822
12139
|
function computeFingerprint(input) {
|
|
11823
12140
|
const { agent, participant } = input;
|
|
11824
12141
|
const baseUrl = agent.env?.ANTHROPIC_BASE_URL ?? agent.env?.OPENAI_BASE_URL ?? agent.env?.OLLAMA_HOST ?? "";
|
|
@@ -11834,7 +12151,7 @@ function computeFingerprint(input) {
|
|
|
11834
12151
|
session: participant.session,
|
|
11835
12152
|
permissions: participant.permissions
|
|
11836
12153
|
});
|
|
11837
|
-
return
|
|
12154
|
+
return createHash3("sha256").update(material).digest("hex").slice(0, 16);
|
|
11838
12155
|
}
|
|
11839
12156
|
|
|
11840
12157
|
// src/sessions/coordinator.ts
|
|
@@ -11886,20 +12203,20 @@ function buildSessionAdapter(inner, manifestId, scope, trackedFingerprints, stor
|
|
|
11886
12203
|
}
|
|
11887
12204
|
|
|
11888
12205
|
// src/sessions/store.ts
|
|
11889
|
-
import { createHash as
|
|
12206
|
+
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
11890
12207
|
import {
|
|
11891
|
-
closeSync,
|
|
11892
|
-
existsSync as
|
|
11893
|
-
mkdirSync as
|
|
11894
|
-
openSync,
|
|
11895
|
-
readFileSync as
|
|
11896
|
-
renameSync as
|
|
11897
|
-
rmSync as
|
|
11898
|
-
writeFileSync as
|
|
11899
|
-
writeSync
|
|
12208
|
+
closeSync as closeSync2,
|
|
12209
|
+
existsSync as existsSync6,
|
|
12210
|
+
mkdirSync as mkdirSync4,
|
|
12211
|
+
openSync as openSync2,
|
|
12212
|
+
readFileSync as readFileSync6,
|
|
12213
|
+
renameSync as renameSync3,
|
|
12214
|
+
rmSync as rmSync4,
|
|
12215
|
+
writeFileSync as writeFileSync4,
|
|
12216
|
+
writeSync as writeSync2
|
|
11900
12217
|
} from "fs";
|
|
11901
|
-
import { homedir as
|
|
11902
|
-
import { dirname, join as
|
|
12218
|
+
import { homedir as homedir5 } from "os";
|
|
12219
|
+
import { dirname, join as join6 } from "path";
|
|
11903
12220
|
function isObject4(v) {
|
|
11904
12221
|
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
11905
12222
|
}
|
|
@@ -11910,14 +12227,14 @@ function entryKey(participantId, fingerprint) {
|
|
|
11910
12227
|
return `${participantId}--${fingerprint}`;
|
|
11911
12228
|
}
|
|
11912
12229
|
var HASH_SEP = String.fromCharCode(0);
|
|
11913
|
-
function
|
|
12230
|
+
function sleepSync2(ms) {
|
|
11914
12231
|
if (ms <= 0)
|
|
11915
12232
|
return;
|
|
11916
12233
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
11917
12234
|
}
|
|
11918
12235
|
function defaultSessionDir() {
|
|
11919
|
-
const xdg = process.env.XDG_STATE_HOME ||
|
|
11920
|
-
return
|
|
12236
|
+
const xdg = process.env.XDG_STATE_HOME || join6(homedir5(), ".local", "state");
|
|
12237
|
+
return join6(xdg, "chit", "sessions");
|
|
11921
12238
|
}
|
|
11922
12239
|
|
|
11923
12240
|
class FileSessionStore {
|
|
@@ -11931,11 +12248,11 @@ class FileSessionStore {
|
|
|
11931
12248
|
}
|
|
11932
12249
|
load(key) {
|
|
11933
12250
|
const path = this.filePath(key);
|
|
11934
|
-
if (!
|
|
12251
|
+
if (!existsSync6(path))
|
|
11935
12252
|
return;
|
|
11936
12253
|
let raw;
|
|
11937
12254
|
try {
|
|
11938
|
-
raw = JSON.parse(
|
|
12255
|
+
raw = JSON.parse(readFileSync6(path, "utf-8"));
|
|
11939
12256
|
} catch {
|
|
11940
12257
|
return;
|
|
11941
12258
|
}
|
|
@@ -11945,13 +12262,13 @@ class FileSessionStore {
|
|
|
11945
12262
|
}
|
|
11946
12263
|
save(key, payload) {
|
|
11947
12264
|
const path = this.filePath(key);
|
|
11948
|
-
|
|
12265
|
+
mkdirSync4(dirname(path), { recursive: true });
|
|
11949
12266
|
const lock = this.acquireLock(path);
|
|
11950
12267
|
try {
|
|
11951
12268
|
let data = {};
|
|
11952
|
-
if (
|
|
12269
|
+
if (existsSync6(path)) {
|
|
11953
12270
|
try {
|
|
11954
|
-
const raw = JSON.parse(
|
|
12271
|
+
const raw = JSON.parse(readFileSync6(path, "utf-8"));
|
|
11955
12272
|
if (isObject4(raw))
|
|
11956
12273
|
data = raw;
|
|
11957
12274
|
} catch {
|
|
@@ -11959,13 +12276,13 @@ class FileSessionStore {
|
|
|
11959
12276
|
}
|
|
11960
12277
|
}
|
|
11961
12278
|
data[entryKey(key.participantId, key.fingerprint)] = payload;
|
|
11962
|
-
const tmpPath = `${path}.${
|
|
11963
|
-
|
|
12279
|
+
const tmpPath = `${path}.${randomUUID4()}.tmp`;
|
|
12280
|
+
writeFileSync4(tmpPath, JSON.stringify(data, null, 2));
|
|
11964
12281
|
try {
|
|
11965
12282
|
this.assertOwned(lock);
|
|
11966
|
-
|
|
12283
|
+
renameSync3(tmpPath, path);
|
|
11967
12284
|
} catch (err) {
|
|
11968
|
-
|
|
12285
|
+
rmSync4(tmpPath, { force: true });
|
|
11969
12286
|
throw err;
|
|
11970
12287
|
}
|
|
11971
12288
|
} finally {
|
|
@@ -11974,20 +12291,20 @@ class FileSessionStore {
|
|
|
11974
12291
|
}
|
|
11975
12292
|
acquireLock(path) {
|
|
11976
12293
|
const lockPath = `${path}.lock`;
|
|
11977
|
-
const token =
|
|
12294
|
+
const token = randomUUID4();
|
|
11978
12295
|
for (let attempt = 0;attempt < this.lockMaxAttempts; attempt++) {
|
|
11979
12296
|
try {
|
|
11980
|
-
const fd =
|
|
12297
|
+
const fd = openSync2(lockPath, "wx");
|
|
11981
12298
|
try {
|
|
11982
|
-
|
|
12299
|
+
writeSync2(fd, token);
|
|
11983
12300
|
} finally {
|
|
11984
|
-
|
|
12301
|
+
closeSync2(fd);
|
|
11985
12302
|
}
|
|
11986
12303
|
return { path: lockPath, token };
|
|
11987
12304
|
} catch (err) {
|
|
11988
12305
|
if (err.code !== "EEXIST")
|
|
11989
12306
|
throw err;
|
|
11990
|
-
|
|
12307
|
+
sleepSync2(this.lockRetryMs);
|
|
11991
12308
|
}
|
|
11992
12309
|
}
|
|
11993
12310
|
throw new Error(`session store: could not acquire lock ${lockPath} after ${this.lockMaxAttempts} attempts. ` + "If no chit process is using this scope, a previous run may have crashed while holding it; " + `remove the lock file to continue: rm ${JSON.stringify(lockPath)}`);
|
|
@@ -11995,7 +12312,7 @@ class FileSessionStore {
|
|
|
11995
12312
|
assertOwned(lock) {
|
|
11996
12313
|
let current;
|
|
11997
12314
|
try {
|
|
11998
|
-
current =
|
|
12315
|
+
current = readFileSync6(lock.path, "utf-8");
|
|
11999
12316
|
} catch {
|
|
12000
12317
|
current = undefined;
|
|
12001
12318
|
}
|
|
@@ -12005,265 +12322,999 @@ class FileSessionStore {
|
|
|
12005
12322
|
}
|
|
12006
12323
|
releaseLock(lock) {
|
|
12007
12324
|
try {
|
|
12008
|
-
if (
|
|
12009
|
-
|
|
12325
|
+
if (readFileSync6(lock.path, "utf-8") === lock.token) {
|
|
12326
|
+
rmSync4(lock.path, { force: true });
|
|
12010
12327
|
}
|
|
12011
12328
|
} catch {}
|
|
12012
12329
|
}
|
|
12013
12330
|
filePath(key) {
|
|
12014
12331
|
const readable = `${safeSegment(key.scope)}--${safeSegment(key.manifestId)}`;
|
|
12015
|
-
const hash =
|
|
12016
|
-
return
|
|
12332
|
+
const hash = createHash4("sha256").update(`${key.scope}${HASH_SEP}${key.manifestId}`).digest("hex").slice(0, 12);
|
|
12333
|
+
return join6(this.baseDir, `${readable}--${hash}.json`);
|
|
12017
12334
|
}
|
|
12018
12335
|
}
|
|
12019
12336
|
|
|
12020
|
-
// src/
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
}
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
} catch (e) {
|
|
12052
|
-
throw new SurfaceInstallError(`invalid manifest at ${opts.manifestPath}: ${e.message}`);
|
|
12053
|
-
}
|
|
12054
|
-
const missingCaps = findMissingCapabilities(manifest, CLAUDE_SKILL_CAPABILITIES);
|
|
12055
|
-
if (missingCaps.length > 0) {
|
|
12056
|
-
throw new SurfaceInstallError(`claude-skill surface does not provide capabilities required by "${manifest.id}": ${missingCaps.join(", ")}`);
|
|
12057
|
-
}
|
|
12058
|
-
const inputNames = Object.keys(manifest.inputs);
|
|
12059
|
-
const primaryInput = inputNames[0];
|
|
12060
|
-
if (!primaryInput) {
|
|
12061
|
-
throw new SurfaceInstallError(`manifest "${manifest.id}" has no inputs; nothing to wire from $ARGUMENTS`);
|
|
12062
|
-
}
|
|
12063
|
-
const primaryInputSchema = manifest.inputs[primaryInput];
|
|
12064
|
-
if (primaryInputSchema?.type !== "string") {
|
|
12065
|
-
throw new SurfaceInstallError(`manifest "${manifest.id}": claude-skill surface only supports a string-typed primary input ` + `(got "${primaryInput}": ${primaryInputSchema?.type ?? "missing"})`);
|
|
12066
|
-
}
|
|
12067
|
-
if (inputNames.length > 1) {
|
|
12068
|
-
throw new SurfaceInstallError(`manifest "${manifest.id}" declares multiple inputs (${inputNames.join(", ")}); ` + `claude-skill surface in PR6 supports exactly one string input`);
|
|
12069
|
-
}
|
|
12070
|
-
const registry2 = opts.registry ?? loadRegistry();
|
|
12071
|
-
const unknownAgents = findUnknownAgents(manifest, registry2);
|
|
12072
|
-
if (unknownAgents.length > 0) {
|
|
12073
|
-
const lines = unknownAgents.map((u) => ` - participant "${u.participantId}" references unknown agent "${u.agentId}"`).join(`
|
|
12074
|
-
`);
|
|
12075
|
-
throw new SurfaceInstallError(`manifest "${manifest.id}" references agents that are not in the registry:
|
|
12076
|
-
${lines}`);
|
|
12077
|
-
}
|
|
12078
|
-
const gaps = findEnforcementGaps(manifest, registry2);
|
|
12079
|
-
if (gaps.length > 0 && !allowUnenforced) {
|
|
12080
|
-
throw new SurfaceInstallError(`cannot enforce required permissions for "${manifest.id}":
|
|
12081
|
-
${formatEnforcementGaps(gaps)}
|
|
12337
|
+
// src/cli/default-converge-manifest.ts
|
|
12338
|
+
var DEFAULT_CONVERGE_MANIFEST = {
|
|
12339
|
+
schema: 1,
|
|
12340
|
+
id: "converge",
|
|
12341
|
+
description: "Autonomous convergence: a write-capable Claude implements a slice, then a read-only Codex reviews the diff and returns proceed/revise/block. Drive it in a loop with the `chit converge` CLI driver, or stepwise from the MCP - one chit_run_start per iteration, same scope so both agents keep their thread, feeding the prior review back in via inputs.prior_review. The human sequences and checkpoints (inspect the diff each round, stop if it goes sideways); chit runs the agents. Run against an isolated worktree, not the main checkout.",
|
|
12342
|
+
inputs: {
|
|
12343
|
+
task: { type: "string" },
|
|
12344
|
+
prior_review: { type: "string", optional: true }
|
|
12345
|
+
},
|
|
12346
|
+
requires: {
|
|
12347
|
+
can_show_markdown: true
|
|
12348
|
+
},
|
|
12349
|
+
participants: {
|
|
12350
|
+
implementer: {
|
|
12351
|
+
agent: "claude",
|
|
12352
|
+
role: "You implement one small, focused slice of a software task in the repository at your cwd. Make the ACTUAL code edits with your tools - do not just describe them. Stay scoped to the task; do not refactor unrelated code. If a prior review is provided, address its concrete findings. Run the project's checks if quick. Then summarize precisely: which files you changed, what each change does and why, what you deliberately did NOT do, and which checks you ran with their results.",
|
|
12353
|
+
session: "per_scope",
|
|
12354
|
+
permissions: { filesystem: "write" }
|
|
12355
|
+
},
|
|
12356
|
+
reviewer: {
|
|
12357
|
+
agent: "codex",
|
|
12358
|
+
role: "You are a skeptical implementation reviewer for a convergence loop. Claude just edited the repository at your cwd. Inspect the current git diff and the changed files, and verify the work against the task. Base your verdict on the TASK changes. Untracked generated build artifacts (e.g. __pycache__, *.pyc) are workspace hygiene, not task changes: note them at most as a minor aside and do NOT revise solely because of them. chit keeps its own control-plane state outside the repo, so it never appears in the diff. Run non-mutating checks if useful. Do not edit. Do not agree for the sake of agreeing. Use prior context from this scope. Cite file:line and command results.",
|
|
12359
|
+
session: "per_scope",
|
|
12360
|
+
permissions: { filesystem: "read_only" }
|
|
12361
|
+
}
|
|
12362
|
+
},
|
|
12363
|
+
steps: {
|
|
12364
|
+
implement: {
|
|
12365
|
+
call: "implementer",
|
|
12366
|
+
prompt: `Task:
|
|
12367
|
+
{{ inputs.task }}
|
|
12082
12368
|
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
const installName = opts.overrideName ?? manifest.id;
|
|
12086
|
-
const skillDir = join4(outputDir, installName);
|
|
12087
|
-
if (existsSync5(skillDir)) {
|
|
12088
|
-
if (!opts.force) {
|
|
12089
|
-
throw new SurfaceInstallError(`skill directory already exists: ${skillDir}
|
|
12369
|
+
Prior review to address (empty on the first iteration):
|
|
12370
|
+
{{ inputs.prior_review }}
|
|
12090
12371
|
|
|
12091
|
-
|
|
12092
|
-
}
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
const manifestPath = join4(skillDir, "manifest.json");
|
|
12098
|
-
const markerPath = join4(skillDir, INSTALL_MARKER_FILENAME);
|
|
12099
|
-
const manifestJson = `${JSON.stringify(rawJson, null, 2)}
|
|
12100
|
-
`;
|
|
12101
|
-
writeFileSync3(manifestPath, manifestJson);
|
|
12102
|
-
writeFileSync3(skillMdPath, buildSkillMd({
|
|
12103
|
-
manifest,
|
|
12104
|
-
runtimePath,
|
|
12105
|
-
primaryInputName: primaryInput,
|
|
12106
|
-
allowUnenforced: gaps.length > 0,
|
|
12107
|
-
trace: opts.trace === true,
|
|
12108
|
-
heredocDelimiter: generateHeredocDelimiter(),
|
|
12109
|
-
installName
|
|
12110
|
-
}));
|
|
12111
|
-
const marker = {
|
|
12112
|
-
schema: 1,
|
|
12113
|
-
surface: "claude-skill",
|
|
12114
|
-
installName,
|
|
12115
|
-
manifestId: manifest.id,
|
|
12116
|
-
runtimePath,
|
|
12117
|
-
installedAt: new Date().toISOString(),
|
|
12118
|
-
manifestHash: createHash4("sha256").update(manifestJson).digest("hex")
|
|
12119
|
-
};
|
|
12120
|
-
writeFileSync3(markerPath, `${JSON.stringify(marker, null, 2)}
|
|
12121
|
-
`);
|
|
12122
|
-
return { skillDir, skillMdPath, manifestPath, markerPath, enforcementGaps: gaps };
|
|
12123
|
-
}
|
|
12124
|
-
function generateHeredocDelimiter() {
|
|
12125
|
-
return `CHIT_INPUT_${randomBytes(8).toString("hex").toUpperCase()}_EOF`;
|
|
12126
|
-
}
|
|
12127
|
-
function buildSkillMd(opts) {
|
|
12128
|
-
const {
|
|
12129
|
-
manifest,
|
|
12130
|
-
runtimePath,
|
|
12131
|
-
primaryInputName,
|
|
12132
|
-
allowUnenforced,
|
|
12133
|
-
trace,
|
|
12134
|
-
heredocDelimiter,
|
|
12135
|
-
installName
|
|
12136
|
-
} = opts;
|
|
12137
|
-
const allowFlag = allowUnenforced ? `\\
|
|
12138
|
-
--allow-unenforced-permissions ` : "";
|
|
12139
|
-
const traceFlag = trace ? `\\
|
|
12140
|
-
--trace ` : "";
|
|
12141
|
-
return `---
|
|
12142
|
-
name: ${installName}
|
|
12143
|
-
description: ${escapeFrontmatter(manifest.description)}
|
|
12144
|
-
argument-hint: <${primaryInputName}>
|
|
12145
|
-
disable-model-invocation: true
|
|
12146
|
-
---
|
|
12372
|
+
Implement this slice now by editing files in the repo at your cwd. Keep it small and focused. Then summarize what you changed (files + what/why), what you did not do, and any checks you ran.`
|
|
12373
|
+
},
|
|
12374
|
+
review: {
|
|
12375
|
+
call: "reviewer",
|
|
12376
|
+
prompt: `Task under review:
|
|
12377
|
+
{{ inputs.task }}
|
|
12147
12378
|
|
|
12148
|
-
|
|
12379
|
+
Claude's summary of what it just implemented:
|
|
12380
|
+
{{ steps.implement.output }}
|
|
12149
12381
|
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
# the real session id baked in (always non-empty); when running outside,
|
|
12156
|
-
# bash evaluates the env var (typically empty, so we fail fast).
|
|
12157
|
-
if [ -z "\${CLAUDE_SESSION_ID}" ]; then
|
|
12158
|
-
echo "chit: CLAUDE_SESSION_ID is required; this skill must run inside Claude Code" >&2
|
|
12159
|
-
exit 2
|
|
12160
|
-
fi
|
|
12161
|
-
WORKTREE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
12162
|
-
SCOPE_HASH=$(printf '%s' "$WORKTREE" | (shasum -a 256 2>/dev/null || sha256sum) | cut -c1-12)
|
|
12163
|
-
SCOPE="\${CLAUDE_SESSION_ID}-\${SCOPE_HASH}"
|
|
12382
|
+
Inspect the current git diff and the changed files at your cwd. Verify the change against the task and run non-mutating checks if useful. Return prose with:
|
|
12383
|
+
1. Verdict: proceed / revise / block.
|
|
12384
|
+
2. Findings ordered by severity, with file:line.
|
|
12385
|
+
3. What Claude should fix next if the verdict is revise.
|
|
12386
|
+
4. Remaining risk if proceeding.
|
|
12164
12387
|
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
--invocation-cwd "$WORKTREE" ${allowFlag}${traceFlag}\\
|
|
12169
|
-
--input-stdin ${primaryInputName} <<'${heredocDelimiter}'
|
|
12170
|
-
$ARGUMENTS
|
|
12171
|
-
${heredocDelimiter}
|
|
12172
|
-
} 2>&1
|
|
12388
|
+
Then, as the LAST thing in your reply, emit a single machine-readable fenced JSON block that the driver parses (the prose above is for humans). Use exactly these keys:
|
|
12389
|
+
\`\`\`json
|
|
12390
|
+
{"verdict": "proceed | revise | block", "findingCount": 0, "checksRun": "the non-mutating checks you ran, or 'none'", "risk": "remaining risk if proceeding"}
|
|
12173
12391
|
\`\`\`
|
|
12392
|
+
findingCount is the integer number of findings; checksRun is a short human string.`
|
|
12393
|
+
},
|
|
12394
|
+
out: {
|
|
12395
|
+
format: `## Converge iteration
|
|
12174
12396
|
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
}
|
|
12178
|
-
function escapeFrontmatter(s) {
|
|
12179
|
-
return s.replace(/\r?\n/g, " ").replace(/"/g, "'");
|
|
12180
|
-
}
|
|
12397
|
+
### Implementer (Claude)
|
|
12398
|
+
{{ steps.implement.output }}
|
|
12181
12399
|
|
|
12182
|
-
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
|
|
12186
|
-
|
|
12187
|
-
|
|
12188
|
-
return join5(homedir4(), ".claude", "skills");
|
|
12189
|
-
}
|
|
12400
|
+
### Reviewer (Codex)
|
|
12401
|
+
{{ steps.review.output }}`
|
|
12402
|
+
}
|
|
12403
|
+
},
|
|
12404
|
+
output: "out"
|
|
12405
|
+
};
|
|
12190
12406
|
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
this.name = "LifecycleError";
|
|
12195
|
-
}
|
|
12407
|
+
// src/cli/workspace.ts
|
|
12408
|
+
function isChitOwned(path) {
|
|
12409
|
+
return path === ".chit" || path.startsWith(".chit/");
|
|
12196
12410
|
}
|
|
12197
|
-
function
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
const out = [];
|
|
12201
|
-
const entries = readdirSync2(parentDir);
|
|
12202
|
-
for (const name of entries) {
|
|
12203
|
-
const skillDir = join5(parentDir, name);
|
|
12204
|
-
let stat;
|
|
12205
|
-
try {
|
|
12206
|
-
stat = statSync2(skillDir);
|
|
12207
|
-
} catch {
|
|
12208
|
-
continue;
|
|
12209
|
-
}
|
|
12210
|
-
if (!stat.isDirectory())
|
|
12211
|
-
continue;
|
|
12212
|
-
const markerPath = join5(skillDir, INSTALL_MARKER_FILENAME);
|
|
12213
|
-
if (!existsSync6(markerPath))
|
|
12214
|
-
continue;
|
|
12215
|
-
let raw;
|
|
12216
|
-
try {
|
|
12217
|
-
raw = JSON.parse(readFileSync5(markerPath, "utf-8"));
|
|
12218
|
-
} catch {
|
|
12219
|
-
continue;
|
|
12220
|
-
}
|
|
12221
|
-
try {
|
|
12222
|
-
const marker = parseInstallMarker(raw, markerPath);
|
|
12223
|
-
out.push({ skillDir, markerPath, marker });
|
|
12224
|
-
} catch {}
|
|
12225
|
-
}
|
|
12226
|
-
return out.sort((a, b) => a.marker.installName.localeCompare(b.marker.installName));
|
|
12411
|
+
function isGeneratedArtifact(path) {
|
|
12412
|
+
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
12413
|
+
return path === "__pycache__" || path.startsWith("__pycache__/") || path.includes("/__pycache__/") || path.endsWith(".pyc") || path.endsWith(".pyo") || base === ".DS_Store";
|
|
12227
12414
|
}
|
|
12228
|
-
function
|
|
12229
|
-
|
|
12230
|
-
|
|
12231
|
-
|
|
12232
|
-
|
|
12233
|
-
if (!existsSync6(skillDir)) {
|
|
12234
|
-
throw new LifecycleError(`no install at ${skillDir}`);
|
|
12235
|
-
}
|
|
12236
|
-
if (!statSync2(skillDir).isDirectory()) {
|
|
12237
|
-
throw new LifecycleError(`${skillDir} is not a directory`);
|
|
12415
|
+
function classifyWorkspace(snap) {
|
|
12416
|
+
const changed = new Set;
|
|
12417
|
+
for (const f of snap.tracked) {
|
|
12418
|
+
if (!isChitOwned(f))
|
|
12419
|
+
changed.add(f);
|
|
12238
12420
|
}
|
|
12239
|
-
const
|
|
12240
|
-
|
|
12241
|
-
|
|
12421
|
+
const workspaceWarnings = [];
|
|
12422
|
+
for (const f of snap.untracked) {
|
|
12423
|
+
if (isChitOwned(f))
|
|
12424
|
+
continue;
|
|
12425
|
+
if (isGeneratedArtifact(f))
|
|
12426
|
+
workspaceWarnings.push(`untracked generated artifact: ${f}`);
|
|
12427
|
+
else
|
|
12428
|
+
changed.add(f);
|
|
12242
12429
|
}
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
12430
|
+
return { changedFiles: [...changed], workspaceWarnings };
|
|
12431
|
+
}
|
|
12432
|
+
|
|
12433
|
+
// src/cli/converge.ts
|
|
12434
|
+
var defaultIO = {
|
|
12435
|
+
out: (s) => process.stdout.write(s),
|
|
12436
|
+
err: (s) => process.stderr.write(s)
|
|
12437
|
+
};
|
|
12438
|
+
var JSON_BLOCK_RE = /```json\s*([\s\S]*?)```/gi;
|
|
12439
|
+
var VERDICTS3 = new Set(["proceed", "revise", "block"]);
|
|
12440
|
+
var IMPLEMENT_STEP_ID = "implement";
|
|
12441
|
+
var REVIEW_STEP_ID = "review";
|
|
12442
|
+
var IMPLEMENT_SUMMARY_CAP = 2000;
|
|
12443
|
+
var CHECKS_RUN_FALLBACK = "unreported";
|
|
12444
|
+
function extractReviewJson(reviewText) {
|
|
12445
|
+
let last;
|
|
12446
|
+
for (const m of reviewText.matchAll(JSON_BLOCK_RE)) {
|
|
12447
|
+
if (m[1] !== undefined)
|
|
12448
|
+
last = m[1];
|
|
12248
12449
|
}
|
|
12249
|
-
|
|
12450
|
+
if (last === undefined)
|
|
12451
|
+
return null;
|
|
12250
12452
|
try {
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
throw new LifecycleError(`refusing to uninstall ${skillDir}: ${e.message}`);
|
|
12453
|
+
const parsed = JSON.parse(last);
|
|
12454
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
12455
|
+
return parsed;
|
|
12255
12456
|
}
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
const record = { skillDir, markerPath, marker };
|
|
12259
|
-
rmSync4(skillDir, { recursive: true, force: true });
|
|
12260
|
-
return record;
|
|
12457
|
+
} catch {}
|
|
12458
|
+
return null;
|
|
12261
12459
|
}
|
|
12262
|
-
|
|
12263
|
-
|
|
12460
|
+
function parseReview(reviewText) {
|
|
12461
|
+
const block = extractReviewJson(reviewText);
|
|
12462
|
+
const rawVerdict = typeof block?.verdict === "string" ? block.verdict.toLowerCase() : undefined;
|
|
12463
|
+
if (block && rawVerdict && VERDICTS3.has(rawVerdict)) {
|
|
12464
|
+
const fc = block.findingCount;
|
|
12465
|
+
const cr = block.checksRun;
|
|
12466
|
+
return {
|
|
12467
|
+
verdict: rawVerdict,
|
|
12468
|
+
findingCount: typeof fc === "number" && Number.isInteger(fc) && fc >= 0 ? fc : 0,
|
|
12469
|
+
checksRun: typeof cr === "string" && cr.trim() !== "" ? cr.trim() : CHECKS_RUN_FALLBACK
|
|
12470
|
+
};
|
|
12471
|
+
}
|
|
12472
|
+
return { verdict: "block", findingCount: 0, checksRun: CHECKS_RUN_FALLBACK };
|
|
12473
|
+
}
|
|
12474
|
+
function reviewDurationMs(trace) {
|
|
12475
|
+
for (const e of trace) {
|
|
12476
|
+
if (e.type === "step.completed" && e.stepId === REVIEW_STEP_ID)
|
|
12477
|
+
return e.durationMs;
|
|
12478
|
+
}
|
|
12479
|
+
return 0;
|
|
12480
|
+
}
|
|
12481
|
+
var USAGE_KEYS = [
|
|
12482
|
+
"inputTokens",
|
|
12483
|
+
"outputTokens",
|
|
12484
|
+
"totalTokens",
|
|
12485
|
+
"cachedInputTokens",
|
|
12486
|
+
"reasoningTokens",
|
|
12487
|
+
"estimatedCostUsd"
|
|
12488
|
+
];
|
|
12489
|
+
function sumTraceUsage(trace) {
|
|
12490
|
+
const usage = {};
|
|
12491
|
+
let any = false;
|
|
12492
|
+
for (const e of trace) {
|
|
12493
|
+
if (e.type !== "step.completed" || !e.usage)
|
|
12494
|
+
continue;
|
|
12495
|
+
for (const k of USAGE_KEYS) {
|
|
12496
|
+
const v = e.usage[k];
|
|
12497
|
+
if (typeof v === "number") {
|
|
12498
|
+
usage[k] = (usage[k] ?? 0) + v;
|
|
12499
|
+
any = true;
|
|
12500
|
+
}
|
|
12501
|
+
}
|
|
12502
|
+
}
|
|
12503
|
+
return any ? usage : undefined;
|
|
12504
|
+
}
|
|
12505
|
+
function capSummary(text) {
|
|
12506
|
+
if (text === "")
|
|
12507
|
+
return "(no summary)";
|
|
12508
|
+
if (text.length <= IMPLEMENT_SUMMARY_CAP)
|
|
12509
|
+
return text;
|
|
12510
|
+
return `${text.slice(0, IMPLEMENT_SUMMARY_CAP)}\u2026 (truncated, ${text.length} chars)`;
|
|
12511
|
+
}
|
|
12512
|
+
function gitLines(cwd, args) {
|
|
12513
|
+
try {
|
|
12514
|
+
const out = execFileSync("git", args, {
|
|
12515
|
+
cwd,
|
|
12516
|
+
encoding: "utf-8",
|
|
12517
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
12518
|
+
});
|
|
12519
|
+
return out.split(`
|
|
12520
|
+
`).map((s) => s.trim()).filter(Boolean);
|
|
12521
|
+
} catch {
|
|
12522
|
+
return [];
|
|
12523
|
+
}
|
|
12524
|
+
}
|
|
12525
|
+
function gitWorkspace(cwd) {
|
|
12526
|
+
return classifyWorkspace({
|
|
12527
|
+
tracked: [
|
|
12528
|
+
...gitLines(cwd, ["diff", "--name-only"]),
|
|
12529
|
+
...gitLines(cwd, ["diff", "--cached", "--name-only"])
|
|
12530
|
+
],
|
|
12531
|
+
untracked: gitLines(cwd, ["ls-files", "--others", "--exclude-standard"])
|
|
12532
|
+
});
|
|
12533
|
+
}
|
|
12534
|
+
|
|
12535
|
+
class ConvergeExecuteError extends Error {
|
|
12536
|
+
executeError;
|
|
12537
|
+
constructor(executeError) {
|
|
12538
|
+
super(executeError instanceof Error ? executeError.message : String(executeError));
|
|
12539
|
+
this.executeError = executeError;
|
|
12540
|
+
}
|
|
12541
|
+
}
|
|
12542
|
+
async function runConvergeIteration(ctx) {
|
|
12543
|
+
let result;
|
|
12544
|
+
try {
|
|
12545
|
+
result = await ctx.execute({ task: ctx.task, prior_review: ctx.prior_review }, {
|
|
12546
|
+
loopId: ctx.loopId,
|
|
12547
|
+
iteration: ctx.iteration,
|
|
12548
|
+
...ctx.signal && { signal: ctx.signal },
|
|
12549
|
+
...ctx.onTrace && { onTrace: ctx.onTrace }
|
|
12550
|
+
});
|
|
12551
|
+
} catch (e) {
|
|
12552
|
+
throw new ConvergeExecuteError(e);
|
|
12553
|
+
}
|
|
12554
|
+
if (!result.ok) {
|
|
12555
|
+
return {
|
|
12556
|
+
ok: false,
|
|
12557
|
+
failure: `manifest run failed at step "${result.failedStep}": ${result.error}`
|
|
12558
|
+
};
|
|
12559
|
+
}
|
|
12560
|
+
const reviewText = result.outputs.review ?? "";
|
|
12561
|
+
const review = parseReview(reviewText);
|
|
12562
|
+
const usage = sumTraceUsage(result.trace);
|
|
12563
|
+
const { changedFiles, workspaceWarnings } = gitWorkspace(ctx.cwd);
|
|
12564
|
+
appendIteration(ctx.cwd, ctx.loopId, {
|
|
12565
|
+
implementSummary: capSummary(result.outputs.implement ?? ""),
|
|
12566
|
+
changedFiles,
|
|
12567
|
+
workspaceWarnings,
|
|
12568
|
+
checksRun: review.checksRun,
|
|
12569
|
+
verdict: review.verdict,
|
|
12570
|
+
findingCount: review.findingCount,
|
|
12571
|
+
decision: review.verdict,
|
|
12572
|
+
checkDurationMs: reviewDurationMs(result.trace),
|
|
12573
|
+
...usage && { usage },
|
|
12574
|
+
...result.auditRunId && { auditRef: result.auditRunId }
|
|
12575
|
+
});
|
|
12576
|
+
const stopStatus = review.verdict === "proceed" ? "converged" : review.verdict === "block" ? "blocked" : undefined;
|
|
12577
|
+
return {
|
|
12578
|
+
ok: true,
|
|
12579
|
+
verdict: review.verdict,
|
|
12580
|
+
findingCount: review.findingCount,
|
|
12581
|
+
checksRun: review.checksRun,
|
|
12582
|
+
decision: review.verdict,
|
|
12583
|
+
changedFiles,
|
|
12584
|
+
workspaceWarnings,
|
|
12585
|
+
...usage && { usage },
|
|
12586
|
+
...result.auditRunId && { auditRunId: result.auditRunId },
|
|
12587
|
+
reviewText,
|
|
12588
|
+
...stopStatus && { stopStatus }
|
|
12589
|
+
};
|
|
12590
|
+
}
|
|
12591
|
+
async function convergeLoop(opts) {
|
|
12592
|
+
const { loopId } = startLoop(opts.cwd, {
|
|
12593
|
+
scope: opts.scope,
|
|
12594
|
+
task: opts.task,
|
|
12595
|
+
maxIterations: opts.maxIterations,
|
|
12596
|
+
loopId: opts.loopId,
|
|
12597
|
+
force: opts.force
|
|
12598
|
+
});
|
|
12599
|
+
let priorReview = "";
|
|
12600
|
+
let iterations = 0;
|
|
12601
|
+
let status;
|
|
12602
|
+
for (let i = 1;i <= opts.maxIterations; i++) {
|
|
12603
|
+
let iter;
|
|
12604
|
+
try {
|
|
12605
|
+
iter = await runConvergeIteration({
|
|
12606
|
+
cwd: opts.cwd,
|
|
12607
|
+
loopId,
|
|
12608
|
+
iteration: i,
|
|
12609
|
+
task: opts.task,
|
|
12610
|
+
prior_review: priorReview,
|
|
12611
|
+
execute: opts.execute
|
|
12612
|
+
});
|
|
12613
|
+
} catch (e) {
|
|
12614
|
+
if (e instanceof ConvergeExecuteError) {
|
|
12615
|
+
stopLoop(opts.cwd, loopId, {
|
|
12616
|
+
status: "blocked",
|
|
12617
|
+
reason: `manifest run threw: ${e.message}`
|
|
12618
|
+
});
|
|
12619
|
+
throw e.executeError;
|
|
12620
|
+
}
|
|
12621
|
+
throw e;
|
|
12622
|
+
}
|
|
12623
|
+
if (!iter.ok) {
|
|
12624
|
+
status = "blocked";
|
|
12625
|
+
stopLoop(opts.cwd, loopId, { status, reason: iter.failure });
|
|
12626
|
+
return { loopId, iterations, status, failure: iter.failure };
|
|
12627
|
+
}
|
|
12628
|
+
iterations++;
|
|
12629
|
+
if (iter.stopStatus !== undefined) {
|
|
12630
|
+
status = iter.stopStatus;
|
|
12631
|
+
break;
|
|
12632
|
+
}
|
|
12633
|
+
priorReview = iter.reviewText;
|
|
12634
|
+
}
|
|
12635
|
+
if (status === undefined)
|
|
12636
|
+
status = "max-iterations";
|
|
12637
|
+
const reason = status === "converged" ? "reviewer returned proceed" : status === "blocked" ? "reviewer returned block" : `reached max iterations (${opts.maxIterations}) without converging`;
|
|
12638
|
+
stopLoop(opts.cwd, loopId, { status, reason });
|
|
12639
|
+
return { loopId, iterations, status };
|
|
12640
|
+
}
|
|
12641
|
+
function buildExecute(manifest, registry2, scope, cwd) {
|
|
12642
|
+
const baseAdapters = {};
|
|
12643
|
+
for (const p of Object.values(manifest.participants)) {
|
|
12644
|
+
if (!(p.agent in baseAdapters)) {
|
|
12645
|
+
const agent = registry2.agents[p.agent];
|
|
12646
|
+
if (!agent)
|
|
12647
|
+
continue;
|
|
12648
|
+
baseAdapters[p.agent] = buildAdapter(agent);
|
|
12649
|
+
}
|
|
12650
|
+
}
|
|
12651
|
+
return makeAuditedExecute(manifest, baseAdapters, registry2, scope, cwd, new FileSessionStore(defaultSessionDir()), new AuditStore);
|
|
12652
|
+
}
|
|
12653
|
+
function prepareConvergeExecute(raw, registry2, scope, cwd, allowUnenforced) {
|
|
12654
|
+
let manifest;
|
|
12655
|
+
try {
|
|
12656
|
+
manifest = parseManifest(raw);
|
|
12657
|
+
} catch (e) {
|
|
12658
|
+
return { ok: false, error: e.message };
|
|
12659
|
+
}
|
|
12660
|
+
const shapeError = validateConvergeManifest(manifest);
|
|
12661
|
+
if (shapeError)
|
|
12662
|
+
return { ok: false, error: shapeError };
|
|
12663
|
+
const unknown = findUnknownAgents(manifest, registry2);
|
|
12664
|
+
if (unknown.length > 0) {
|
|
12665
|
+
return {
|
|
12666
|
+
ok: false,
|
|
12667
|
+
error: `unknown agent(s): ${unknown.map((u) => `${u.agentId} (participant "${u.participantId}")`).join(", ")}`
|
|
12668
|
+
};
|
|
12669
|
+
}
|
|
12670
|
+
const gaps = findEnforcementGaps(manifest, registry2);
|
|
12671
|
+
if (gaps.length > 0 && !allowUnenforced) {
|
|
12672
|
+
return {
|
|
12673
|
+
ok: false,
|
|
12674
|
+
error: `cannot enforce required permissions:
|
|
12675
|
+
${formatEnforcementGaps(gaps)}
|
|
12676
|
+
Pass allow_unenforced_permissions=true to run anyway.`
|
|
12677
|
+
};
|
|
12678
|
+
}
|
|
12679
|
+
const warnings = gaps.map((g) => `unenforced permission: participant "${g.participantId}" requires ${g.permission}`);
|
|
12680
|
+
return { ok: true, execute: buildExecute(manifest, registry2, scope, cwd), warnings };
|
|
12681
|
+
}
|
|
12682
|
+
function makeAuditedExecute(manifest, baseAdapters, registry2, scope, cwd, sessionStore, auditStore) {
|
|
12683
|
+
return async (inputs, ctx) => {
|
|
12684
|
+
const runId = crypto.randomUUID();
|
|
12685
|
+
const recorder = new AuditRecorder(auditStore, runId, {
|
|
12686
|
+
manifestId: manifest.id,
|
|
12687
|
+
cwd,
|
|
12688
|
+
surface: "converge",
|
|
12689
|
+
scope,
|
|
12690
|
+
...ctx?.loopId !== undefined && { loopId: ctx.loopId },
|
|
12691
|
+
...ctx?.iteration !== undefined && { iteration: ctx.iteration },
|
|
12692
|
+
participants: resolveParticipantSnapshots(manifest, registry2)
|
|
12693
|
+
});
|
|
12694
|
+
recorder.runStarted();
|
|
12695
|
+
const adapters = wrapAdaptersWithSessions(wrapAdaptersWithAudit(baseAdapters, recorder), manifest, registry2, scope, sessionStore);
|
|
12696
|
+
const startedAt = Date.now();
|
|
12697
|
+
try {
|
|
12698
|
+
const result = await executeManifest(manifest, {
|
|
12699
|
+
inputs,
|
|
12700
|
+
adapters,
|
|
12701
|
+
invocationCwd: cwd,
|
|
12702
|
+
onTrace: (e) => {
|
|
12703
|
+
recorder.fromTrace(e);
|
|
12704
|
+
ctx?.onTrace?.(e);
|
|
12705
|
+
},
|
|
12706
|
+
...ctx?.signal && { signal: ctx.signal }
|
|
12707
|
+
});
|
|
12708
|
+
recorder.runCompleted(result.ok ? "ok" : "failed", Date.now() - startedAt);
|
|
12709
|
+
recorder.prune();
|
|
12710
|
+
return recorder.lastError === undefined ? { ...result, auditRunId: runId } : result;
|
|
12711
|
+
} catch (e) {
|
|
12712
|
+
recorder.runCompleted("failed", Date.now() - startedAt);
|
|
12713
|
+
recorder.prune();
|
|
12714
|
+
throw e;
|
|
12715
|
+
}
|
|
12716
|
+
};
|
|
12717
|
+
}
|
|
12718
|
+
function validateConvergeManifest(manifest) {
|
|
12719
|
+
for (const id of [IMPLEMENT_STEP_ID, REVIEW_STEP_ID]) {
|
|
12720
|
+
const step = manifest.steps[id];
|
|
12721
|
+
if (!step) {
|
|
12722
|
+
return `manifest "${manifest.id}" is not converge-shaped: missing call step "${id}" (converge needs call steps named "implement" and "review")`;
|
|
12723
|
+
}
|
|
12724
|
+
if (step.kind !== "call") {
|
|
12725
|
+
return `manifest "${manifest.id}" is not converge-shaped: step "${id}" must be a call step, not ${step.kind}`;
|
|
12726
|
+
}
|
|
12727
|
+
}
|
|
12728
|
+
return null;
|
|
12729
|
+
}
|
|
12730
|
+
var CONVERGE_HELP = `chit converge --task <text> --scope <id> [options]
|
|
12731
|
+
|
|
12732
|
+
--task <text> Required. The slice to converge on.
|
|
12733
|
+
--scope <id> Required. Session scope; both agents keep their thread.
|
|
12734
|
+
--cwd <dir> Repo to run in. Default: current directory.
|
|
12735
|
+
--manifest <path> Convergence manifest. Default: the built-in converge manifest.
|
|
12736
|
+
--max-iterations <n> Iteration budget. Default: 3.
|
|
12737
|
+
--loop-id <id> Reuse/seed a loop id. Default: generated.
|
|
12738
|
+
--allow-unenforced-permissions
|
|
12739
|
+
Run even when the manifest declares permissions its
|
|
12740
|
+
adapter cannot enforce (emits a warning each run).
|
|
12741
|
+
Default off: such a manifest is refused before running.
|
|
12742
|
+
|
|
12743
|
+
Runs the implement/check loop to convergence and records it under chit's
|
|
12744
|
+
state dir (keyed by repo, not in the worktree). Stops at the reviewer's verdict: proceed ->
|
|
12745
|
+
converged, block -> blocked, else revise and retry up to the budget. An
|
|
12746
|
+
unparseable verdict is treated as block (never an implicit proceed).
|
|
12747
|
+
`;
|
|
12748
|
+
|
|
12749
|
+
class UsageError extends Error {
|
|
12750
|
+
}
|
|
12751
|
+
function parseConvergeArgs(argv) {
|
|
12752
|
+
let task;
|
|
12753
|
+
let scope;
|
|
12754
|
+
let cwd;
|
|
12755
|
+
let manifestPath;
|
|
12756
|
+
let maxIterations = 3;
|
|
12757
|
+
let loopId;
|
|
12758
|
+
let allowUnenforcedPermissions = false;
|
|
12759
|
+
for (let i = 0;i < argv.length; i++) {
|
|
12760
|
+
const a = argv[i];
|
|
12761
|
+
const need = (key) => {
|
|
12762
|
+
const v = argv[++i];
|
|
12763
|
+
if (v === undefined)
|
|
12764
|
+
throw new UsageError(`${key} requires a value`);
|
|
12765
|
+
return v;
|
|
12766
|
+
};
|
|
12767
|
+
if (a === "--task")
|
|
12768
|
+
task = need("--task");
|
|
12769
|
+
else if (a === "--scope")
|
|
12770
|
+
scope = need("--scope");
|
|
12771
|
+
else if (a === "--cwd")
|
|
12772
|
+
cwd = need("--cwd");
|
|
12773
|
+
else if (a === "--manifest")
|
|
12774
|
+
manifestPath = need("--manifest");
|
|
12775
|
+
else if (a === "--loop-id")
|
|
12776
|
+
loopId = need("--loop-id");
|
|
12777
|
+
else if (a === "--allow-unenforced-permissions")
|
|
12778
|
+
allowUnenforcedPermissions = true;
|
|
12779
|
+
else if (a === "--max-iterations") {
|
|
12780
|
+
const raw = need("--max-iterations");
|
|
12781
|
+
const n = Number(raw);
|
|
12782
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
12783
|
+
throw new UsageError(`--max-iterations must be a positive integer (got ${JSON.stringify(raw)})`);
|
|
12784
|
+
}
|
|
12785
|
+
maxIterations = n;
|
|
12786
|
+
} else {
|
|
12787
|
+
throw new UsageError(`unknown flag ${JSON.stringify(a)}`);
|
|
12788
|
+
}
|
|
12789
|
+
}
|
|
12790
|
+
if (task === undefined)
|
|
12791
|
+
throw new UsageError("--task is required");
|
|
12792
|
+
if (scope === undefined)
|
|
12793
|
+
throw new UsageError("--scope is required");
|
|
12794
|
+
return {
|
|
12795
|
+
task,
|
|
12796
|
+
scope,
|
|
12797
|
+
cwd: resolve2(cwd ?? process.cwd()),
|
|
12798
|
+
manifestPath,
|
|
12799
|
+
maxIterations,
|
|
12800
|
+
loopId,
|
|
12801
|
+
allowUnenforcedPermissions
|
|
12802
|
+
};
|
|
12803
|
+
}
|
|
12804
|
+
async function runConverge(argv, io = defaultIO) {
|
|
12805
|
+
if (argv[0] === "-h" || argv[0] === "--help") {
|
|
12806
|
+
io.out(CONVERGE_HELP);
|
|
12807
|
+
return 0;
|
|
12808
|
+
}
|
|
12809
|
+
let parsed;
|
|
12810
|
+
try {
|
|
12811
|
+
parsed = parseConvergeArgs(argv);
|
|
12812
|
+
} catch (e) {
|
|
12813
|
+
if (e instanceof UsageError) {
|
|
12814
|
+
io.err(`chit converge: ${e.message}
|
|
12815
|
+
|
|
12816
|
+
${CONVERGE_HELP}`);
|
|
12817
|
+
return 2;
|
|
12818
|
+
}
|
|
12819
|
+
throw e;
|
|
12820
|
+
}
|
|
12821
|
+
let manifest;
|
|
12822
|
+
try {
|
|
12823
|
+
const raw = parsed.manifestPath !== undefined ? JSON.parse(readFileSync7(parsed.manifestPath, "utf-8")) : DEFAULT_CONVERGE_MANIFEST;
|
|
12824
|
+
manifest = parseManifest(raw);
|
|
12825
|
+
} catch (e) {
|
|
12826
|
+
io.err(`chit converge: failed to load manifest ${parsed.manifestPath ?? "(built-in default)"}: ${e.message}
|
|
12827
|
+
`);
|
|
12828
|
+
return 2;
|
|
12829
|
+
}
|
|
12830
|
+
const shapeError = validateConvergeManifest(manifest);
|
|
12831
|
+
if (shapeError) {
|
|
12832
|
+
io.err(`chit converge: ${shapeError}
|
|
12833
|
+
`);
|
|
12834
|
+
return 1;
|
|
12835
|
+
}
|
|
12836
|
+
let registry2;
|
|
12837
|
+
try {
|
|
12838
|
+
registry2 = loadRegistry();
|
|
12839
|
+
} catch (e) {
|
|
12840
|
+
io.err(`chit converge: ${e.message}
|
|
12841
|
+
`);
|
|
12842
|
+
return 1;
|
|
12843
|
+
}
|
|
12844
|
+
const unknown = findUnknownAgents(manifest, registry2);
|
|
12845
|
+
if (unknown.length > 0) {
|
|
12846
|
+
for (const u of unknown) {
|
|
12847
|
+
io.err(`chit converge: unknown agent "${u.agentId}" in registry (participant "${u.participantId}")
|
|
12848
|
+
`);
|
|
12849
|
+
}
|
|
12850
|
+
return 2;
|
|
12851
|
+
}
|
|
12852
|
+
const gaps = findEnforcementGaps(manifest, registry2);
|
|
12853
|
+
if (gaps.length > 0 && !parsed.allowUnenforcedPermissions) {
|
|
12854
|
+
io.err(`chit converge: cannot enforce required permissions for "${manifest.id}":
|
|
12855
|
+
`);
|
|
12856
|
+
io.err(`${formatEnforcementGaps(gaps)}
|
|
12857
|
+
`);
|
|
12858
|
+
io.err(`
|
|
12859
|
+
Pass --allow-unenforced-permissions to run anyway (emits a warning each run).
|
|
12860
|
+
`);
|
|
12861
|
+
return 1;
|
|
12862
|
+
}
|
|
12863
|
+
for (const g of gaps) {
|
|
12864
|
+
io.err(`chit converge: WARNING -- unenforced permission: participant "${g.participantId}" requires ${g.permission}
|
|
12865
|
+
`);
|
|
12866
|
+
}
|
|
12867
|
+
let result;
|
|
12868
|
+
try {
|
|
12869
|
+
result = await convergeLoop({
|
|
12870
|
+
cwd: parsed.cwd,
|
|
12871
|
+
scope: parsed.scope,
|
|
12872
|
+
task: parsed.task,
|
|
12873
|
+
maxIterations: parsed.maxIterations,
|
|
12874
|
+
loopId: parsed.loopId,
|
|
12875
|
+
execute: buildExecute(manifest, registry2, parsed.scope, parsed.cwd)
|
|
12876
|
+
});
|
|
12877
|
+
} catch (e) {
|
|
12878
|
+
io.err(`chit converge: ${e.message}
|
|
12879
|
+
`);
|
|
12880
|
+
return 1;
|
|
12881
|
+
}
|
|
12882
|
+
if (result.failure !== undefined) {
|
|
12883
|
+
io.err(`chit converge: ${result.failure}
|
|
12884
|
+
`);
|
|
12885
|
+
return 1;
|
|
12886
|
+
}
|
|
12887
|
+
io.out(`chit converge: ${result.loopId}
|
|
12888
|
+
`);
|
|
12889
|
+
io.out(` iterations: ${result.iterations}
|
|
12890
|
+
`);
|
|
12891
|
+
io.out(` status: ${result.status}
|
|
12892
|
+
`);
|
|
12893
|
+
return 0;
|
|
12894
|
+
}
|
|
12895
|
+
|
|
12896
|
+
// src/jobs/worker.ts
|
|
12897
|
+
function iso2(ms) {
|
|
12898
|
+
return new Date(ms).toISOString();
|
|
12899
|
+
}
|
|
12900
|
+
function defaultResolveExecute(job) {
|
|
12901
|
+
let raw;
|
|
12902
|
+
if (job.manifestPath) {
|
|
12903
|
+
const path = isAbsolute2(job.manifestPath) ? job.manifestPath : resolve3(job.cwd, job.manifestPath);
|
|
12904
|
+
try {
|
|
12905
|
+
raw = JSON.parse(readFileSync8(path, "utf-8"));
|
|
12906
|
+
} catch (e) {
|
|
12907
|
+
return { ok: false, error: `could not read manifest at ${path}: ${e.message}` };
|
|
12908
|
+
}
|
|
12909
|
+
} else {
|
|
12910
|
+
raw = DEFAULT_CONVERGE_MANIFEST;
|
|
12911
|
+
}
|
|
12912
|
+
const prep = prepareConvergeExecute(raw, loadRegistry(), job.scope, job.cwd, job.allowUnenforced);
|
|
12913
|
+
return prep.ok ? { ok: true, execute: prep.execute } : { ok: false, error: prep.error };
|
|
12914
|
+
}
|
|
12915
|
+
async function runJobWorker(jobId, deps) {
|
|
12916
|
+
const store = deps.jobStore;
|
|
12917
|
+
const now = deps.now ?? Date.now;
|
|
12918
|
+
const heartbeatMs = deps.heartbeatMs ?? 1e4;
|
|
12919
|
+
const resolveExecute = deps.resolveExecute ?? defaultResolveExecute;
|
|
12920
|
+
const job = store.get(jobId);
|
|
12921
|
+
if (job?.state !== "queued")
|
|
12922
|
+
return;
|
|
12923
|
+
const workerToken = crypto.randomUUID();
|
|
12924
|
+
const setPhase = (phase) => {
|
|
12925
|
+
try {
|
|
12926
|
+
store.update(jobId, (c) => ({ ...c, phase, lastHeartbeatAt: iso2(now()) }));
|
|
12927
|
+
} catch {}
|
|
12928
|
+
};
|
|
12929
|
+
const controller = new AbortController;
|
|
12930
|
+
const onSignal = () => {
|
|
12931
|
+
setPhase("cancelling");
|
|
12932
|
+
controller.abort();
|
|
12933
|
+
};
|
|
12934
|
+
if (deps.installSignalHandlers !== false) {
|
|
12935
|
+
process.once("SIGTERM", onSignal);
|
|
12936
|
+
process.once("SIGINT", onSignal);
|
|
12937
|
+
}
|
|
12938
|
+
let loopLock;
|
|
12939
|
+
let heartbeat;
|
|
12940
|
+
try {
|
|
12941
|
+
store.update(jobId, (c) => ({
|
|
12942
|
+
...c,
|
|
12943
|
+
state: "running",
|
|
12944
|
+
startedAt: iso2(now()),
|
|
12945
|
+
pid: process.pid,
|
|
12946
|
+
pgid: process.pid,
|
|
12947
|
+
workerToken,
|
|
12948
|
+
lastHeartbeatAt: iso2(now()),
|
|
12949
|
+
phase: "starting"
|
|
12950
|
+
}));
|
|
12951
|
+
const resolved = resolveExecute(job);
|
|
12952
|
+
if (!resolved.ok) {
|
|
12953
|
+
stopLoopSafely(job, "blocked", `could not prepare converge execute: ${resolved.error}`);
|
|
12954
|
+
finish(store, jobId, now, "failed", { failure: resolved.error });
|
|
12955
|
+
return;
|
|
12956
|
+
}
|
|
12957
|
+
try {
|
|
12958
|
+
loopLock = acquireLock(store.loopLockPath(job.loopId), deps.loopLockOpts);
|
|
12959
|
+
} catch (e) {
|
|
12960
|
+
if (e instanceof LockError) {
|
|
12961
|
+
finish(store, jobId, now, "failed", {
|
|
12962
|
+
failure: `loop "${job.loopId}" is locked by another advancer; not started`
|
|
12963
|
+
});
|
|
12964
|
+
return;
|
|
12965
|
+
}
|
|
12966
|
+
throw e;
|
|
12967
|
+
}
|
|
12968
|
+
heartbeat = setInterval(() => {
|
|
12969
|
+
try {
|
|
12970
|
+
store.update(jobId, (c) => ({ ...c, lastHeartbeatAt: iso2(now()) }));
|
|
12971
|
+
} catch {}
|
|
12972
|
+
}, heartbeatMs);
|
|
12973
|
+
let priorReview = "";
|
|
12974
|
+
for (let i = 1;i <= job.maxIterations; i++) {
|
|
12975
|
+
if (store.get(jobId)?.cancelRequestedAt || controller.signal.aborted) {
|
|
12976
|
+
stopLoopSafely(job, "cancelled", "cancelled via chit_job_cancel");
|
|
12977
|
+
finish(store, jobId, now, "cancelled", { stopStatus: "cancelled" });
|
|
12978
|
+
return;
|
|
12979
|
+
}
|
|
12980
|
+
store.update(jobId, (c) => ({
|
|
12981
|
+
...c,
|
|
12982
|
+
iteration: i,
|
|
12983
|
+
phase: "implementing",
|
|
12984
|
+
lastHeartbeatAt: iso2(now())
|
|
12985
|
+
}));
|
|
12986
|
+
let iter;
|
|
12987
|
+
try {
|
|
12988
|
+
iter = await runConvergeIteration({
|
|
12989
|
+
cwd: job.cwd,
|
|
12990
|
+
loopId: job.loopId,
|
|
12991
|
+
iteration: i,
|
|
12992
|
+
task: job.task,
|
|
12993
|
+
prior_review: priorReview,
|
|
12994
|
+
execute: resolved.execute,
|
|
12995
|
+
signal: controller.signal,
|
|
12996
|
+
onTrace: (e) => {
|
|
12997
|
+
if (e.type === "step.started" && e.stepId === "implement")
|
|
12998
|
+
setPhase("implementing");
|
|
12999
|
+
else if (e.type === "step.started" && e.stepId === "review")
|
|
13000
|
+
setPhase("reviewing");
|
|
13001
|
+
}
|
|
13002
|
+
});
|
|
13003
|
+
} catch (e) {
|
|
13004
|
+
if (e instanceof ConvergeExecuteError) {
|
|
13005
|
+
stopLoopSafely(job, "blocked", `manifest run threw: ${e.message}`);
|
|
13006
|
+
finish(store, jobId, now, "failed", { failure: e.message });
|
|
13007
|
+
} else {
|
|
13008
|
+
finish(store, jobId, now, "failed", { failure: e.message });
|
|
13009
|
+
}
|
|
13010
|
+
return;
|
|
13011
|
+
}
|
|
13012
|
+
if (!iter.ok) {
|
|
13013
|
+
if (controller.signal.aborted) {
|
|
13014
|
+
stopLoopSafely(job, "cancelled", "cancelled mid-iteration (signal)");
|
|
13015
|
+
finish(store, jobId, now, "cancelled", { stopStatus: "cancelled" });
|
|
13016
|
+
} else {
|
|
13017
|
+
stopLoopSafely(job, "blocked", iter.failure);
|
|
13018
|
+
finish(store, jobId, now, "failed", { failure: iter.failure });
|
|
13019
|
+
}
|
|
13020
|
+
return;
|
|
13021
|
+
}
|
|
13022
|
+
store.update(jobId, (c) => ({
|
|
13023
|
+
...c,
|
|
13024
|
+
phase: "recording",
|
|
13025
|
+
iterationsCompleted: i,
|
|
13026
|
+
lastVerdict: iter.verdict,
|
|
13027
|
+
auditRefs: iter.auditRunId ? [...c.auditRefs, iter.auditRunId] : c.auditRefs,
|
|
13028
|
+
lastHeartbeatAt: iso2(now())
|
|
13029
|
+
}));
|
|
13030
|
+
if (iter.stopStatus) {
|
|
13031
|
+
const reason = iter.stopStatus === "converged" ? "reviewer returned proceed" : "reviewer returned block";
|
|
13032
|
+
stopLoopSafely(job, iter.stopStatus, reason);
|
|
13033
|
+
finish(store, jobId, now, "completed", { stopStatus: iter.stopStatus });
|
|
13034
|
+
return;
|
|
13035
|
+
}
|
|
13036
|
+
priorReview = iter.reviewText;
|
|
13037
|
+
if (i >= job.maxIterations) {
|
|
13038
|
+
stopLoopSafely(job, "max-iterations", `reached max iterations (${job.maxIterations}) without converging`);
|
|
13039
|
+
finish(store, jobId, now, "completed", { stopStatus: "max-iterations" });
|
|
13040
|
+
return;
|
|
13041
|
+
}
|
|
13042
|
+
}
|
|
13043
|
+
} finally {
|
|
13044
|
+
if (heartbeat)
|
|
13045
|
+
clearInterval(heartbeat);
|
|
13046
|
+
if (deps.installSignalHandlers !== false) {
|
|
13047
|
+
process.removeListener("SIGTERM", onSignal);
|
|
13048
|
+
process.removeListener("SIGINT", onSignal);
|
|
13049
|
+
}
|
|
13050
|
+
if (loopLock)
|
|
13051
|
+
releaseLock(loopLock);
|
|
13052
|
+
}
|
|
13053
|
+
}
|
|
13054
|
+
function stopLoopSafely(job, status, reason) {
|
|
13055
|
+
try {
|
|
13056
|
+
stopLoop(job.cwd, job.loopId, { status, reason });
|
|
13057
|
+
} catch {}
|
|
13058
|
+
}
|
|
13059
|
+
function finish(store, jobId, now, state, extra) {
|
|
13060
|
+
store.update(jobId, (c) => ({
|
|
13061
|
+
...c,
|
|
13062
|
+
state,
|
|
13063
|
+
endedAt: iso2(now()),
|
|
13064
|
+
phase: undefined,
|
|
13065
|
+
lastHeartbeatAt: iso2(now()),
|
|
13066
|
+
...extra.stopStatus !== undefined && { stopStatus: extra.stopStatus },
|
|
13067
|
+
...extra.failure !== undefined && { failure: extra.failure }
|
|
13068
|
+
}));
|
|
13069
|
+
}
|
|
13070
|
+
|
|
13071
|
+
// src/surfaces/claude-skill.ts
|
|
12264
13072
|
init_src();
|
|
12265
|
-
import {
|
|
12266
|
-
import {
|
|
13073
|
+
import { createHash as createHash5, randomBytes } from "crypto";
|
|
13074
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
13075
|
+
import { join as join7, resolve as resolvePath } from "path";
|
|
13076
|
+
class SurfaceInstallError extends Error {
|
|
13077
|
+
constructor(message) {
|
|
13078
|
+
super(message);
|
|
13079
|
+
this.name = "SurfaceInstallError";
|
|
13080
|
+
}
|
|
13081
|
+
}
|
|
13082
|
+
var CLAUDE_SKILL_CAPABILITIES = new Set([
|
|
13083
|
+
"can_show_markdown",
|
|
13084
|
+
"can_provide_stable_scope"
|
|
13085
|
+
]);
|
|
13086
|
+
function installClaudeSkill(opts) {
|
|
13087
|
+
if (opts.overrideName !== undefined && !VALID_INSTALL_NAME_RE.test(opts.overrideName)) {
|
|
13088
|
+
throw new SurfaceInstallError(`overrideName "${opts.overrideName}" is invalid: must be kebab-case (lowercase letters, digits, hyphens; must start with a letter). Path-traversal sequences like ".." or "/" are rejected.`);
|
|
13089
|
+
}
|
|
13090
|
+
const outputDir = resolvePath(opts.outputDir);
|
|
13091
|
+
const runtimePath = resolvePath(opts.runtimePath);
|
|
13092
|
+
const allowUnenforced = opts.allowUnenforcedPermissions === true;
|
|
13093
|
+
let rawJson;
|
|
13094
|
+
try {
|
|
13095
|
+
rawJson = JSON.parse(readFileSync9(opts.manifestPath, "utf-8"));
|
|
13096
|
+
} catch (e) {
|
|
13097
|
+
throw new SurfaceInstallError(`failed to read manifest ${opts.manifestPath}: ${e.message}`);
|
|
13098
|
+
}
|
|
13099
|
+
let manifest;
|
|
13100
|
+
try {
|
|
13101
|
+
manifest = parseManifest(rawJson);
|
|
13102
|
+
} catch (e) {
|
|
13103
|
+
throw new SurfaceInstallError(`invalid manifest at ${opts.manifestPath}: ${e.message}`);
|
|
13104
|
+
}
|
|
13105
|
+
const missingCaps = findMissingCapabilities(manifest, CLAUDE_SKILL_CAPABILITIES);
|
|
13106
|
+
if (missingCaps.length > 0) {
|
|
13107
|
+
throw new SurfaceInstallError(`claude-skill surface does not provide capabilities required by "${manifest.id}": ${missingCaps.join(", ")}`);
|
|
13108
|
+
}
|
|
13109
|
+
const inputNames = Object.keys(manifest.inputs);
|
|
13110
|
+
const primaryInput = inputNames[0];
|
|
13111
|
+
if (!primaryInput) {
|
|
13112
|
+
throw new SurfaceInstallError(`manifest "${manifest.id}" has no inputs; nothing to wire from $ARGUMENTS`);
|
|
13113
|
+
}
|
|
13114
|
+
const primaryInputSchema = manifest.inputs[primaryInput];
|
|
13115
|
+
if (primaryInputSchema?.type !== "string") {
|
|
13116
|
+
throw new SurfaceInstallError(`manifest "${manifest.id}": claude-skill surface only supports a string-typed primary input ` + `(got "${primaryInput}": ${primaryInputSchema?.type ?? "missing"})`);
|
|
13117
|
+
}
|
|
13118
|
+
if (inputNames.length > 1) {
|
|
13119
|
+
throw new SurfaceInstallError(`manifest "${manifest.id}" declares multiple inputs (${inputNames.join(", ")}); ` + `claude-skill surface in PR6 supports exactly one string input`);
|
|
13120
|
+
}
|
|
13121
|
+
const registry2 = opts.registry ?? loadRegistry();
|
|
13122
|
+
const unknownAgents = findUnknownAgents(manifest, registry2);
|
|
13123
|
+
if (unknownAgents.length > 0) {
|
|
13124
|
+
const lines = unknownAgents.map((u) => ` - participant "${u.participantId}" references unknown agent "${u.agentId}"`).join(`
|
|
13125
|
+
`);
|
|
13126
|
+
throw new SurfaceInstallError(`manifest "${manifest.id}" references agents that are not in the registry:
|
|
13127
|
+
${lines}`);
|
|
13128
|
+
}
|
|
13129
|
+
const gaps = findEnforcementGaps(manifest, registry2);
|
|
13130
|
+
if (gaps.length > 0 && !allowUnenforced) {
|
|
13131
|
+
throw new SurfaceInstallError(`cannot enforce required permissions for "${manifest.id}":
|
|
13132
|
+
${formatEnforcementGaps(gaps)}
|
|
13133
|
+
|
|
13134
|
+
Pass allowUnenforcedPermissions=true to install anyway; the generated skill will warn on every run.`);
|
|
13135
|
+
}
|
|
13136
|
+
const installName = opts.overrideName ?? manifest.id;
|
|
13137
|
+
const skillDir = join7(outputDir, installName);
|
|
13138
|
+
if (existsSync7(skillDir)) {
|
|
13139
|
+
if (!opts.force) {
|
|
13140
|
+
throw new SurfaceInstallError(`skill directory already exists: ${skillDir}
|
|
13141
|
+
|
|
13142
|
+
Pass force=true to remove and replace it, or use overrideName="<id>" to install with a different name (avoids overwriting an unrelated skill that happens to share this id).`);
|
|
13143
|
+
}
|
|
13144
|
+
rmSync5(skillDir, { recursive: true, force: true });
|
|
13145
|
+
}
|
|
13146
|
+
mkdirSync5(skillDir, { recursive: true });
|
|
13147
|
+
const skillMdPath = join7(skillDir, "SKILL.md");
|
|
13148
|
+
const manifestPath = join7(skillDir, "manifest.json");
|
|
13149
|
+
const markerPath = join7(skillDir, INSTALL_MARKER_FILENAME);
|
|
13150
|
+
const manifestJson = `${JSON.stringify(rawJson, null, 2)}
|
|
13151
|
+
`;
|
|
13152
|
+
writeFileSync5(manifestPath, manifestJson);
|
|
13153
|
+
writeFileSync5(skillMdPath, buildSkillMd({
|
|
13154
|
+
manifest,
|
|
13155
|
+
runtimePath,
|
|
13156
|
+
primaryInputName: primaryInput,
|
|
13157
|
+
allowUnenforced: gaps.length > 0,
|
|
13158
|
+
trace: opts.trace === true,
|
|
13159
|
+
heredocDelimiter: generateHeredocDelimiter(),
|
|
13160
|
+
installName
|
|
13161
|
+
}));
|
|
13162
|
+
const marker = {
|
|
13163
|
+
schema: 1,
|
|
13164
|
+
surface: "claude-skill",
|
|
13165
|
+
installName,
|
|
13166
|
+
manifestId: manifest.id,
|
|
13167
|
+
runtimePath,
|
|
13168
|
+
installedAt: new Date().toISOString(),
|
|
13169
|
+
manifestHash: createHash5("sha256").update(manifestJson).digest("hex")
|
|
13170
|
+
};
|
|
13171
|
+
writeFileSync5(markerPath, `${JSON.stringify(marker, null, 2)}
|
|
13172
|
+
`);
|
|
13173
|
+
return { skillDir, skillMdPath, manifestPath, markerPath, enforcementGaps: gaps };
|
|
13174
|
+
}
|
|
13175
|
+
function generateHeredocDelimiter() {
|
|
13176
|
+
return `CHIT_INPUT_${randomBytes(8).toString("hex").toUpperCase()}_EOF`;
|
|
13177
|
+
}
|
|
13178
|
+
function buildSkillMd(opts) {
|
|
13179
|
+
const {
|
|
13180
|
+
manifest,
|
|
13181
|
+
runtimePath,
|
|
13182
|
+
primaryInputName,
|
|
13183
|
+
allowUnenforced,
|
|
13184
|
+
trace,
|
|
13185
|
+
heredocDelimiter,
|
|
13186
|
+
installName
|
|
13187
|
+
} = opts;
|
|
13188
|
+
const allowFlag = allowUnenforced ? `\\
|
|
13189
|
+
--allow-unenforced-permissions ` : "";
|
|
13190
|
+
const traceFlag = trace ? `\\
|
|
13191
|
+
--trace ` : "";
|
|
13192
|
+
return `---
|
|
13193
|
+
name: ${installName}
|
|
13194
|
+
description: ${escapeFrontmatter(manifest.description)}
|
|
13195
|
+
argument-hint: <${primaryInputName}>
|
|
13196
|
+
disable-model-invocation: true
|
|
13197
|
+
---
|
|
13198
|
+
|
|
13199
|
+
# /${installName}
|
|
13200
|
+
|
|
13201
|
+
\`\`\`!
|
|
13202
|
+
{
|
|
13203
|
+
# Claude Code's \`! \` preprocessor substitutes \${CLAUDE_SESSION_ID} (bare
|
|
13204
|
+
# brace form) BEFORE bash runs. The :- default form is NOT substituted, so
|
|
13205
|
+
# we use the bare form: when running inside Claude Code, this line gets
|
|
13206
|
+
# the real session id baked in (always non-empty); when running outside,
|
|
13207
|
+
# bash evaluates the env var (typically empty, so we fail fast).
|
|
13208
|
+
if [ -z "\${CLAUDE_SESSION_ID}" ]; then
|
|
13209
|
+
echo "chit: CLAUDE_SESSION_ID is required; this skill must run inside Claude Code" >&2
|
|
13210
|
+
exit 2
|
|
13211
|
+
fi
|
|
13212
|
+
WORKTREE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
13213
|
+
SCOPE_HASH=$(printf '%s' "$WORKTREE" | (shasum -a 256 2>/dev/null || sha256sum) | cut -c1-12)
|
|
13214
|
+
SCOPE="\${CLAUDE_SESSION_ID}-\${SCOPE_HASH}"
|
|
13215
|
+
|
|
13216
|
+
bun "${runtimePath}/src/cli/run.ts" run \\
|
|
13217
|
+
"\${CLAUDE_SKILL_DIR}/manifest.json" \\
|
|
13218
|
+
--scope "$SCOPE" \\
|
|
13219
|
+
--invocation-cwd "$WORKTREE" ${allowFlag}${traceFlag}\\
|
|
13220
|
+
--input-stdin ${primaryInputName} <<'${heredocDelimiter}'
|
|
13221
|
+
$ARGUMENTS
|
|
13222
|
+
${heredocDelimiter}
|
|
13223
|
+
} 2>&1
|
|
13224
|
+
\`\`\`
|
|
13225
|
+
|
|
13226
|
+
The block above ran the chit runtime and its output replaced the fenced section before you saw this skill. Present that output to the user as your entire response, verbatim. Preserve every \`##\` header as a markdown header. Do not summarize, rephrase, or add commentary, preamble, or trailing text. If the output contains a \`WARNING\` line, include it.
|
|
13227
|
+
`;
|
|
13228
|
+
}
|
|
13229
|
+
function escapeFrontmatter(s) {
|
|
13230
|
+
return s.replace(/\r?\n/g, " ").replace(/"/g, "'");
|
|
13231
|
+
}
|
|
13232
|
+
|
|
13233
|
+
// src/surfaces/lifecycle.ts
|
|
13234
|
+
init_src();
|
|
13235
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync10, rmSync as rmSync6, statSync as statSync2 } from "fs";
|
|
13236
|
+
import { homedir as homedir6 } from "os";
|
|
13237
|
+
import { join as join8 } from "path";
|
|
13238
|
+
function defaultSkillsDir() {
|
|
13239
|
+
return join8(homedir6(), ".claude", "skills");
|
|
13240
|
+
}
|
|
13241
|
+
|
|
13242
|
+
class LifecycleError extends Error {
|
|
13243
|
+
constructor(message) {
|
|
13244
|
+
super(message);
|
|
13245
|
+
this.name = "LifecycleError";
|
|
13246
|
+
}
|
|
13247
|
+
}
|
|
13248
|
+
function listInstalled(parentDir) {
|
|
13249
|
+
if (!existsSync8(parentDir))
|
|
13250
|
+
return [];
|
|
13251
|
+
const out = [];
|
|
13252
|
+
const entries = readdirSync3(parentDir);
|
|
13253
|
+
for (const name of entries) {
|
|
13254
|
+
const skillDir = join8(parentDir, name);
|
|
13255
|
+
let stat;
|
|
13256
|
+
try {
|
|
13257
|
+
stat = statSync2(skillDir);
|
|
13258
|
+
} catch {
|
|
13259
|
+
continue;
|
|
13260
|
+
}
|
|
13261
|
+
if (!stat.isDirectory())
|
|
13262
|
+
continue;
|
|
13263
|
+
const markerPath = join8(skillDir, INSTALL_MARKER_FILENAME);
|
|
13264
|
+
if (!existsSync8(markerPath))
|
|
13265
|
+
continue;
|
|
13266
|
+
let raw;
|
|
13267
|
+
try {
|
|
13268
|
+
raw = JSON.parse(readFileSync10(markerPath, "utf-8"));
|
|
13269
|
+
} catch {
|
|
13270
|
+
continue;
|
|
13271
|
+
}
|
|
13272
|
+
try {
|
|
13273
|
+
const marker = parseInstallMarker(raw, markerPath);
|
|
13274
|
+
out.push({ skillDir, markerPath, marker });
|
|
13275
|
+
} catch {}
|
|
13276
|
+
}
|
|
13277
|
+
return out.sort((a, b) => a.marker.installName.localeCompare(b.marker.installName));
|
|
13278
|
+
}
|
|
13279
|
+
function uninstall(parentDir, name) {
|
|
13280
|
+
if (!VALID_INSTALL_NAME_RE.test(name)) {
|
|
13281
|
+
throw new LifecycleError(`install name "${name}" is invalid: must be kebab-case (lowercase letters, digits, hyphens; must start with a letter). Path-traversal sequences like ".." or "/" are rejected.`);
|
|
13282
|
+
}
|
|
13283
|
+
const skillDir = join8(parentDir, name);
|
|
13284
|
+
if (!existsSync8(skillDir)) {
|
|
13285
|
+
throw new LifecycleError(`no install at ${skillDir}`);
|
|
13286
|
+
}
|
|
13287
|
+
if (!statSync2(skillDir).isDirectory()) {
|
|
13288
|
+
throw new LifecycleError(`${skillDir} is not a directory`);
|
|
13289
|
+
}
|
|
13290
|
+
const markerPath = join8(skillDir, INSTALL_MARKER_FILENAME);
|
|
13291
|
+
if (!existsSync8(markerPath)) {
|
|
13292
|
+
throw new LifecycleError(`refusing to uninstall ${skillDir}: no install marker (${INSTALL_MARKER_FILENAME}) present. ` + `This directory was not created by chit (or was created by a pre-marker version). ` + `If you're sure this is yours, remove it manually with rm -rf.`);
|
|
13293
|
+
}
|
|
13294
|
+
let raw;
|
|
13295
|
+
try {
|
|
13296
|
+
raw = JSON.parse(readFileSync10(markerPath, "utf-8"));
|
|
13297
|
+
} catch (e) {
|
|
13298
|
+
throw new LifecycleError(`refusing to uninstall ${skillDir}: ${INSTALL_MARKER_FILENAME} is not valid JSON (${e.message})`);
|
|
13299
|
+
}
|
|
13300
|
+
let marker;
|
|
13301
|
+
try {
|
|
13302
|
+
marker = parseInstallMarker(raw, markerPath);
|
|
13303
|
+
} catch (e) {
|
|
13304
|
+
if (e instanceof MarkerError) {
|
|
13305
|
+
throw new LifecycleError(`refusing to uninstall ${skillDir}: ${e.message}`);
|
|
13306
|
+
}
|
|
13307
|
+
throw e;
|
|
13308
|
+
}
|
|
13309
|
+
const record = { skillDir, markerPath, marker };
|
|
13310
|
+
rmSync6(skillDir, { recursive: true, force: true });
|
|
13311
|
+
return record;
|
|
13312
|
+
}
|
|
13313
|
+
|
|
13314
|
+
// src/surfaces/mcp/server.ts
|
|
13315
|
+
import { spawn } from "child_process";
|
|
13316
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
13317
|
+
import { isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
|
|
12267
13318
|
|
|
12268
13319
|
// ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v3/helpers/util.js
|
|
12269
13320
|
var util;
|
|
@@ -33131,7 +34182,7 @@ class Protocol {
|
|
|
33131
34182
|
return;
|
|
33132
34183
|
}
|
|
33133
34184
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
|
|
33134
|
-
await new Promise((
|
|
34185
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
33135
34186
|
options?.signal?.throwIfAborted();
|
|
33136
34187
|
}
|
|
33137
34188
|
} catch (error51) {
|
|
@@ -33143,7 +34194,7 @@ class Protocol {
|
|
|
33143
34194
|
}
|
|
33144
34195
|
request(request, resultSchema, options) {
|
|
33145
34196
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
33146
|
-
return new Promise((
|
|
34197
|
+
return new Promise((resolve4, reject) => {
|
|
33147
34198
|
const earlyReject = (error51) => {
|
|
33148
34199
|
reject(error51);
|
|
33149
34200
|
};
|
|
@@ -33221,7 +34272,7 @@ class Protocol {
|
|
|
33221
34272
|
if (!parseResult.success) {
|
|
33222
34273
|
reject(parseResult.error);
|
|
33223
34274
|
} else {
|
|
33224
|
-
|
|
34275
|
+
resolve4(parseResult.data);
|
|
33225
34276
|
}
|
|
33226
34277
|
} catch (error51) {
|
|
33227
34278
|
reject(error51);
|
|
@@ -33412,12 +34463,12 @@ class Protocol {
|
|
|
33412
34463
|
interval = task.pollInterval;
|
|
33413
34464
|
}
|
|
33414
34465
|
} catch {}
|
|
33415
|
-
return new Promise((
|
|
34466
|
+
return new Promise((resolve4, reject) => {
|
|
33416
34467
|
if (signal.aborted) {
|
|
33417
34468
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
33418
34469
|
return;
|
|
33419
34470
|
}
|
|
33420
|
-
const timeoutId = setTimeout(
|
|
34471
|
+
const timeoutId = setTimeout(resolve4, interval);
|
|
33421
34472
|
signal.addEventListener("abort", () => {
|
|
33422
34473
|
clearTimeout(timeoutId);
|
|
33423
34474
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -34270,7 +35321,7 @@ class McpServer {
|
|
|
34270
35321
|
let task = createTaskResult.task;
|
|
34271
35322
|
const pollInterval = task.pollInterval ?? 5000;
|
|
34272
35323
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
34273
|
-
await new Promise((
|
|
35324
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
34274
35325
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
34275
35326
|
if (!updatedTask) {
|
|
34276
35327
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -34647,1022 +35698,433 @@ class McpServer {
|
|
|
34647
35698
|
this.sendToolListChanged();
|
|
34648
35699
|
return registeredTool;
|
|
34649
35700
|
}
|
|
34650
|
-
tool(name, ...rest) {
|
|
34651
|
-
if (this._registeredTools[name]) {
|
|
34652
|
-
throw new Error(`Tool ${name} is already registered`);
|
|
34653
|
-
}
|
|
34654
|
-
let description;
|
|
34655
|
-
let inputSchema;
|
|
34656
|
-
let outputSchema;
|
|
34657
|
-
let annotations;
|
|
34658
|
-
if (typeof rest[0] === "string") {
|
|
34659
|
-
description = rest.shift();
|
|
34660
|
-
}
|
|
34661
|
-
if (rest.length > 1) {
|
|
34662
|
-
const firstArg = rest[0];
|
|
34663
|
-
if (isZodRawShapeCompat(firstArg)) {
|
|
34664
|
-
inputSchema = rest.shift();
|
|
34665
|
-
if (rest.length > 1 && typeof rest[0] === "object" && rest[0] !== null && !isZodRawShapeCompat(rest[0])) {
|
|
34666
|
-
annotations = rest.shift();
|
|
34667
|
-
}
|
|
34668
|
-
} else if (typeof firstArg === "object" && firstArg !== null) {
|
|
34669
|
-
if (Object.values(firstArg).some((v) => typeof v === "object" && v !== null)) {
|
|
34670
|
-
throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
|
|
34671
|
-
}
|
|
34672
|
-
annotations = rest.shift();
|
|
34673
|
-
}
|
|
34674
|
-
}
|
|
34675
|
-
const callback = rest[0];
|
|
34676
|
-
return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, undefined, callback);
|
|
34677
|
-
}
|
|
34678
|
-
registerTool(name, config2, cb) {
|
|
34679
|
-
if (this._registeredTools[name]) {
|
|
34680
|
-
throw new Error(`Tool ${name} is already registered`);
|
|
34681
|
-
}
|
|
34682
|
-
const { title, description, inputSchema, outputSchema, annotations, _meta } = config2;
|
|
34683
|
-
return this._createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, _meta, cb);
|
|
34684
|
-
}
|
|
34685
|
-
prompt(name, ...rest) {
|
|
34686
|
-
if (this._registeredPrompts[name]) {
|
|
34687
|
-
throw new Error(`Prompt ${name} is already registered`);
|
|
34688
|
-
}
|
|
34689
|
-
let description;
|
|
34690
|
-
if (typeof rest[0] === "string") {
|
|
34691
|
-
description = rest.shift();
|
|
34692
|
-
}
|
|
34693
|
-
let argsSchema;
|
|
34694
|
-
if (rest.length > 1) {
|
|
34695
|
-
argsSchema = rest.shift();
|
|
34696
|
-
}
|
|
34697
|
-
const cb = rest[0];
|
|
34698
|
-
const registeredPrompt = this._createRegisteredPrompt(name, undefined, description, argsSchema, cb);
|
|
34699
|
-
this.setPromptRequestHandlers();
|
|
34700
|
-
this.sendPromptListChanged();
|
|
34701
|
-
return registeredPrompt;
|
|
34702
|
-
}
|
|
34703
|
-
registerPrompt(name, config2, cb) {
|
|
34704
|
-
if (this._registeredPrompts[name]) {
|
|
34705
|
-
throw new Error(`Prompt ${name} is already registered`);
|
|
34706
|
-
}
|
|
34707
|
-
const { title, description, argsSchema } = config2;
|
|
34708
|
-
const registeredPrompt = this._createRegisteredPrompt(name, title, description, argsSchema, cb);
|
|
34709
|
-
this.setPromptRequestHandlers();
|
|
34710
|
-
this.sendPromptListChanged();
|
|
34711
|
-
return registeredPrompt;
|
|
34712
|
-
}
|
|
34713
|
-
isConnected() {
|
|
34714
|
-
return this.server.transport !== undefined;
|
|
34715
|
-
}
|
|
34716
|
-
async sendLoggingMessage(params, sessionId) {
|
|
34717
|
-
return this.server.sendLoggingMessage(params, sessionId);
|
|
34718
|
-
}
|
|
34719
|
-
sendResourceListChanged() {
|
|
34720
|
-
if (this.isConnected()) {
|
|
34721
|
-
this.server.sendResourceListChanged();
|
|
34722
|
-
}
|
|
34723
|
-
}
|
|
34724
|
-
sendToolListChanged() {
|
|
34725
|
-
if (this.isConnected()) {
|
|
34726
|
-
this.server.sendToolListChanged();
|
|
34727
|
-
}
|
|
34728
|
-
}
|
|
34729
|
-
sendPromptListChanged() {
|
|
34730
|
-
if (this.isConnected()) {
|
|
34731
|
-
this.server.sendPromptListChanged();
|
|
34732
|
-
}
|
|
34733
|
-
}
|
|
34734
|
-
}
|
|
34735
|
-
var EMPTY_OBJECT_JSON_SCHEMA = {
|
|
34736
|
-
type: "object",
|
|
34737
|
-
properties: {}
|
|
34738
|
-
};
|
|
34739
|
-
function isZodTypeLike(value) {
|
|
34740
|
-
return value !== null && typeof value === "object" && "parse" in value && typeof value.parse === "function" && "safeParse" in value && typeof value.safeParse === "function";
|
|
34741
|
-
}
|
|
34742
|
-
function isZodSchemaInstance(obj3) {
|
|
34743
|
-
return "_def" in obj3 || "_zod" in obj3 || isZodTypeLike(obj3);
|
|
34744
|
-
}
|
|
34745
|
-
function isZodRawShapeCompat(obj3) {
|
|
34746
|
-
if (typeof obj3 !== "object" || obj3 === null) {
|
|
34747
|
-
return false;
|
|
34748
|
-
}
|
|
34749
|
-
if (isZodSchemaInstance(obj3)) {
|
|
34750
|
-
return false;
|
|
34751
|
-
}
|
|
34752
|
-
if (Object.keys(obj3).length === 0) {
|
|
34753
|
-
return true;
|
|
34754
|
-
}
|
|
34755
|
-
return Object.values(obj3).some(isZodTypeLike);
|
|
34756
|
-
}
|
|
34757
|
-
function getZodSchemaObject(schema) {
|
|
34758
|
-
if (!schema) {
|
|
34759
|
-
return;
|
|
34760
|
-
}
|
|
34761
|
-
if (isZodRawShapeCompat(schema)) {
|
|
34762
|
-
return objectFromShape(schema);
|
|
34763
|
-
}
|
|
34764
|
-
if (!isZodSchemaInstance(schema)) {
|
|
34765
|
-
throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
|
|
34766
|
-
}
|
|
34767
|
-
return schema;
|
|
34768
|
-
}
|
|
34769
|
-
function promptArgumentsFromSchema(schema) {
|
|
34770
|
-
const shape = getObjectShape(schema);
|
|
34771
|
-
if (!shape)
|
|
34772
|
-
return [];
|
|
34773
|
-
return Object.entries(shape).map(([name, field]) => {
|
|
34774
|
-
const description = getSchemaDescription(field);
|
|
34775
|
-
const isOptional = isSchemaOptional(field);
|
|
34776
|
-
return {
|
|
34777
|
-
name,
|
|
34778
|
-
description,
|
|
34779
|
-
required: !isOptional
|
|
34780
|
-
};
|
|
34781
|
-
});
|
|
34782
|
-
}
|
|
34783
|
-
function getMethodValue(schema) {
|
|
34784
|
-
const shape = getObjectShape(schema);
|
|
34785
|
-
const methodSchema = shape?.method;
|
|
34786
|
-
if (!methodSchema) {
|
|
34787
|
-
throw new Error("Schema is missing a method literal");
|
|
34788
|
-
}
|
|
34789
|
-
const value = getLiteralValue(methodSchema);
|
|
34790
|
-
if (typeof value === "string") {
|
|
34791
|
-
return value;
|
|
34792
|
-
}
|
|
34793
|
-
throw new Error("Schema method literal must be a string");
|
|
34794
|
-
}
|
|
34795
|
-
function createCompletionResult(suggestions) {
|
|
34796
|
-
return {
|
|
34797
|
-
completion: {
|
|
34798
|
-
values: suggestions.slice(0, 100),
|
|
34799
|
-
total: suggestions.length,
|
|
34800
|
-
hasMore: suggestions.length > 100
|
|
34801
|
-
}
|
|
34802
|
-
};
|
|
34803
|
-
}
|
|
34804
|
-
var EMPTY_COMPLETION_RESULT = {
|
|
34805
|
-
completion: {
|
|
34806
|
-
values: [],
|
|
34807
|
-
hasMore: false
|
|
34808
|
-
}
|
|
34809
|
-
};
|
|
34810
|
-
|
|
34811
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.29.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
34812
|
-
import process3 from "process";
|
|
34813
|
-
|
|
34814
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.29.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
|
|
34815
|
-
class ReadBuffer {
|
|
34816
|
-
append(chunk) {
|
|
34817
|
-
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
|
|
34818
|
-
}
|
|
34819
|
-
readMessage() {
|
|
34820
|
-
if (!this._buffer) {
|
|
34821
|
-
return null;
|
|
34822
|
-
}
|
|
34823
|
-
const index = this._buffer.indexOf(`
|
|
34824
|
-
`);
|
|
34825
|
-
if (index === -1) {
|
|
34826
|
-
return null;
|
|
34827
|
-
}
|
|
34828
|
-
const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
|
|
34829
|
-
this._buffer = this._buffer.subarray(index + 1);
|
|
34830
|
-
return deserializeMessage(line);
|
|
34831
|
-
}
|
|
34832
|
-
clear() {
|
|
34833
|
-
this._buffer = undefined;
|
|
34834
|
-
}
|
|
34835
|
-
}
|
|
34836
|
-
function deserializeMessage(line) {
|
|
34837
|
-
return JSONRPCMessageSchema.parse(JSON.parse(line));
|
|
34838
|
-
}
|
|
34839
|
-
function serializeMessage(message) {
|
|
34840
|
-
return JSON.stringify(message) + `
|
|
34841
|
-
`;
|
|
34842
|
-
}
|
|
34843
|
-
|
|
34844
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.29.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
34845
|
-
class StdioServerTransport {
|
|
34846
|
-
constructor(_stdin = process3.stdin, _stdout = process3.stdout) {
|
|
34847
|
-
this._stdin = _stdin;
|
|
34848
|
-
this._stdout = _stdout;
|
|
34849
|
-
this._readBuffer = new ReadBuffer;
|
|
34850
|
-
this._started = false;
|
|
34851
|
-
this._ondata = (chunk) => {
|
|
34852
|
-
this._readBuffer.append(chunk);
|
|
34853
|
-
this.processReadBuffer();
|
|
34854
|
-
};
|
|
34855
|
-
this._onerror = (error51) => {
|
|
34856
|
-
this.onerror?.(error51);
|
|
34857
|
-
};
|
|
34858
|
-
}
|
|
34859
|
-
async start() {
|
|
34860
|
-
if (this._started) {
|
|
34861
|
-
throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
|
|
34862
|
-
}
|
|
34863
|
-
this._started = true;
|
|
34864
|
-
this._stdin.on("data", this._ondata);
|
|
34865
|
-
this._stdin.on("error", this._onerror);
|
|
34866
|
-
}
|
|
34867
|
-
processReadBuffer() {
|
|
34868
|
-
while (true) {
|
|
34869
|
-
try {
|
|
34870
|
-
const message = this._readBuffer.readMessage();
|
|
34871
|
-
if (message === null) {
|
|
34872
|
-
break;
|
|
34873
|
-
}
|
|
34874
|
-
this.onmessage?.(message);
|
|
34875
|
-
} catch (error51) {
|
|
34876
|
-
this.onerror?.(error51);
|
|
34877
|
-
}
|
|
34878
|
-
}
|
|
34879
|
-
}
|
|
34880
|
-
async close() {
|
|
34881
|
-
this._stdin.off("data", this._ondata);
|
|
34882
|
-
this._stdin.off("error", this._onerror);
|
|
34883
|
-
const remainingDataListeners = this._stdin.listenerCount("data");
|
|
34884
|
-
if (remainingDataListeners === 0) {
|
|
34885
|
-
this._stdin.pause();
|
|
34886
|
-
}
|
|
34887
|
-
this._readBuffer.clear();
|
|
34888
|
-
this.onclose?.();
|
|
34889
|
-
}
|
|
34890
|
-
send(message) {
|
|
34891
|
-
return new Promise((resolve2) => {
|
|
34892
|
-
const json2 = serializeMessage(message);
|
|
34893
|
-
if (this._stdout.write(json2)) {
|
|
34894
|
-
resolve2();
|
|
34895
|
-
} else {
|
|
34896
|
-
this._stdout.once("drain", resolve2);
|
|
34897
|
-
}
|
|
34898
|
-
});
|
|
34899
|
-
}
|
|
34900
|
-
}
|
|
34901
|
-
|
|
34902
|
-
// src/audit/reader.ts
|
|
34903
|
-
var USAGE_KEYS = [
|
|
34904
|
-
"inputTokens",
|
|
34905
|
-
"outputTokens",
|
|
34906
|
-
"totalTokens",
|
|
34907
|
-
"cachedInputTokens",
|
|
34908
|
-
"reasoningTokens",
|
|
34909
|
-
"estimatedCostUsd"
|
|
34910
|
-
];
|
|
34911
|
-
function sumUsage(events2) {
|
|
34912
|
-
const usage = {};
|
|
34913
|
-
let any3 = false;
|
|
34914
|
-
for (const e of events2) {
|
|
34915
|
-
if (e.type !== "adapter.call.completed" || !e.usage)
|
|
34916
|
-
continue;
|
|
34917
|
-
for (const k of USAGE_KEYS) {
|
|
34918
|
-
const v = e.usage[k];
|
|
34919
|
-
if (typeof v === "number") {
|
|
34920
|
-
usage[k] = (usage[k] ?? 0) + v;
|
|
34921
|
-
any3 = true;
|
|
34922
|
-
}
|
|
34923
|
-
}
|
|
34924
|
-
}
|
|
34925
|
-
return any3 ? usage : undefined;
|
|
34926
|
-
}
|
|
34927
|
-
function findOpenCall(events2) {
|
|
34928
|
-
const open = new Map;
|
|
34929
|
-
for (const e of events2) {
|
|
34930
|
-
if (e.type === "adapter.call.started") {
|
|
34931
|
-
open.set(e.stepId, {
|
|
34932
|
-
stepId: e.stepId,
|
|
34933
|
-
participantId: e.participantId,
|
|
34934
|
-
agentId: e.agentId,
|
|
34935
|
-
since: e.ts
|
|
34936
|
-
});
|
|
34937
|
-
} else if (e.type === "adapter.call.completed") {
|
|
34938
|
-
open.delete(e.stepId);
|
|
34939
|
-
}
|
|
34940
|
-
}
|
|
34941
|
-
let latest;
|
|
34942
|
-
for (const c of open.values()) {
|
|
34943
|
-
if (latest === undefined || c.since > latest.since)
|
|
34944
|
-
latest = c;
|
|
34945
|
-
}
|
|
34946
|
-
return latest;
|
|
34947
|
-
}
|
|
34948
|
-
function describeIncomplete(s, events2) {
|
|
34949
|
-
if (s.openCall) {
|
|
34950
|
-
const c = s.openCall;
|
|
34951
|
-
return `open call: ${c.stepId} ${c.participantId}/${c.agentId} since ${c.since}; no adapter.call.completed`;
|
|
34952
|
-
}
|
|
34953
|
-
const failed = events2.find((e) => e.type === "step.failed");
|
|
34954
|
-
if (failed?.type === "step.failed") {
|
|
34955
|
-
const err = failed.error.replace(/\s+/g, " ").trim();
|
|
34956
|
-
const clipped = err.length > 200 ? `${err.slice(0, 200)}...` : err;
|
|
34957
|
-
return `failed step: ${failed.stepId}: ${clipped}`;
|
|
34958
|
-
}
|
|
34959
|
-
return "abandoned before terminal run.completed";
|
|
34960
|
-
}
|
|
34961
|
-
function summarizeRun(runId, events2) {
|
|
34962
|
-
const started = events2.find((e) => e.type === "run.started");
|
|
34963
|
-
const completed = events2.find((e) => e.type === "run.completed");
|
|
34964
|
-
const summary = {
|
|
34965
|
-
runId,
|
|
34966
|
-
manifestId: started?.type === "run.started" ? started.manifestId : "?",
|
|
34967
|
-
surface: started?.type === "run.started" ? started.surface : "?",
|
|
34968
|
-
status: completed?.type === "run.completed" ? completed.status : "incomplete",
|
|
34969
|
-
stepCount: events2.filter((e) => e.type === "step.completed").length
|
|
34970
|
-
};
|
|
34971
|
-
if (started?.type === "run.started") {
|
|
34972
|
-
summary.startedAt = started.ts;
|
|
34973
|
-
if (started.scope !== undefined)
|
|
34974
|
-
summary.scope = started.scope;
|
|
34975
|
-
if (started.loopId !== undefined)
|
|
34976
|
-
summary.loopId = started.loopId;
|
|
34977
|
-
if (started.iteration !== undefined)
|
|
34978
|
-
summary.iteration = started.iteration;
|
|
34979
|
-
}
|
|
34980
|
-
const usage = sumUsage(events2);
|
|
34981
|
-
if (usage !== undefined)
|
|
34982
|
-
summary.usage = usage;
|
|
34983
|
-
const openCall = findOpenCall(events2);
|
|
34984
|
-
if (openCall !== undefined)
|
|
34985
|
-
summary.openCall = openCall;
|
|
34986
|
-
return summary;
|
|
34987
|
-
}
|
|
34988
|
-
function safeReadEvents(store, runId) {
|
|
34989
|
-
try {
|
|
34990
|
-
return store.readEvents(runId);
|
|
34991
|
-
} catch {
|
|
34992
|
-
return [];
|
|
34993
|
-
}
|
|
34994
|
-
}
|
|
34995
|
-
function listAudit(store, limit) {
|
|
34996
|
-
const summaries = store.listRuns().map((id) => summarizeRun(id, safeReadEvents(store, id)));
|
|
34997
|
-
summaries.sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? ""));
|
|
34998
|
-
return limit !== undefined ? summaries.slice(0, limit) : summaries;
|
|
34999
|
-
}
|
|
35000
|
-
function readBody(store, runId, ref) {
|
|
35001
|
-
try {
|
|
35002
|
-
return store.readBlob(runId, ref);
|
|
35003
|
-
} catch (err) {
|
|
35004
|
-
return `<blob unavailable: ${err.message}>`;
|
|
35005
|
-
}
|
|
35006
|
-
}
|
|
35007
|
-
function isReceiptEvent(e) {
|
|
35008
|
-
return e.type !== "adapter.event";
|
|
35009
|
-
}
|
|
35010
|
-
function hiddenAdapterEventCount(events2) {
|
|
35011
|
-
return events2.reduce((n, e) => e.type === "adapter.event" ? n + 1 : n, 0);
|
|
35012
|
-
}
|
|
35013
|
-
function auditTimeline(store, runId, events2, opts) {
|
|
35014
|
-
const rows = opts.verbose ? events2 : events2.filter(isReceiptEvent);
|
|
35015
|
-
return rows.map((e) => {
|
|
35016
|
-
if (!opts.includeBodies)
|
|
35017
|
-
return e;
|
|
35018
|
-
if (e.type === "adapter.call.started") {
|
|
35019
|
-
return { ...e, input: readBody(store, runId, e.inputBlob) };
|
|
35020
|
-
}
|
|
35021
|
-
if (e.type === "adapter.event" && e.rawBlob !== undefined) {
|
|
35022
|
-
return { ...e, raw: readBody(store, runId, e.rawBlob) };
|
|
35701
|
+
tool(name, ...rest) {
|
|
35702
|
+
if (this._registeredTools[name]) {
|
|
35703
|
+
throw new Error(`Tool ${name} is already registered`);
|
|
35023
35704
|
}
|
|
35024
|
-
|
|
35025
|
-
|
|
35705
|
+
let description;
|
|
35706
|
+
let inputSchema;
|
|
35707
|
+
let outputSchema;
|
|
35708
|
+
let annotations;
|
|
35709
|
+
if (typeof rest[0] === "string") {
|
|
35710
|
+
description = rest.shift();
|
|
35026
35711
|
}
|
|
35027
|
-
if (
|
|
35028
|
-
|
|
35712
|
+
if (rest.length > 1) {
|
|
35713
|
+
const firstArg = rest[0];
|
|
35714
|
+
if (isZodRawShapeCompat(firstArg)) {
|
|
35715
|
+
inputSchema = rest.shift();
|
|
35716
|
+
if (rest.length > 1 && typeof rest[0] === "object" && rest[0] !== null && !isZodRawShapeCompat(rest[0])) {
|
|
35717
|
+
annotations = rest.shift();
|
|
35718
|
+
}
|
|
35719
|
+
} else if (typeof firstArg === "object" && firstArg !== null) {
|
|
35720
|
+
if (Object.values(firstArg).some((v) => typeof v === "object" && v !== null)) {
|
|
35721
|
+
throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
|
|
35722
|
+
}
|
|
35723
|
+
annotations = rest.shift();
|
|
35724
|
+
}
|
|
35029
35725
|
}
|
|
35030
|
-
|
|
35031
|
-
|
|
35032
|
-
}
|
|
35033
|
-
function showAudit(store, runId, opts) {
|
|
35034
|
-
const events2 = store.readEvents(runId);
|
|
35035
|
-
const summary = summarizeRun(runId, events2);
|
|
35036
|
-
const out = {
|
|
35037
|
-
summary,
|
|
35038
|
-
timeline: auditTimeline(store, runId, events2, opts)
|
|
35039
|
-
};
|
|
35040
|
-
if (summary.status === "incomplete")
|
|
35041
|
-
out.incompleteReason = describeIncomplete(summary, events2);
|
|
35042
|
-
const started = events2.find((e) => e.type === "run.started");
|
|
35043
|
-
if (started?.type === "run.started" && started.participants !== undefined) {
|
|
35044
|
-
out.participants = started.participants;
|
|
35726
|
+
const callback = rest[0];
|
|
35727
|
+
return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, undefined, callback);
|
|
35045
35728
|
}
|
|
35046
|
-
|
|
35047
|
-
|
|
35048
|
-
|
|
35049
|
-
out.note = `${hidden} raw adapter events hidden; pass verbose to include them, include_bodies to show blob bodies.`;
|
|
35729
|
+
registerTool(name, config2, cb) {
|
|
35730
|
+
if (this._registeredTools[name]) {
|
|
35731
|
+
throw new Error(`Tool ${name} is already registered`);
|
|
35050
35732
|
}
|
|
35733
|
+
const { title, description, inputSchema, outputSchema, annotations, _meta } = config2;
|
|
35734
|
+
return this._createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, _meta, cb);
|
|
35051
35735
|
}
|
|
35052
|
-
|
|
35053
|
-
|
|
35054
|
-
|
|
35055
|
-
|
|
35056
|
-
|
|
35057
|
-
|
|
35058
|
-
|
|
35059
|
-
|
|
35060
|
-
|
|
35061
|
-
|
|
35062
|
-
|
|
35063
|
-
|
|
35064
|
-
|
|
35065
|
-
|
|
35066
|
-
|
|
35067
|
-
|
|
35068
|
-
|
|
35069
|
-
var realClock2 = () => Date.now();
|
|
35070
|
-
function iso(ms) {
|
|
35071
|
-
return new Date(ms).toISOString();
|
|
35072
|
-
}
|
|
35073
|
-
function loopsDir(cwd) {
|
|
35074
|
-
return join6(cwd, ".chit", "loops");
|
|
35075
|
-
}
|
|
35076
|
-
function loopPath(cwd, loopId) {
|
|
35077
|
-
if (!SAFE_LOOP_ID.test(loopId)) {
|
|
35078
|
-
throw new LoopStoreError(`invalid loop id ${JSON.stringify(loopId)}`);
|
|
35079
|
-
}
|
|
35080
|
-
return join6(loopsDir(cwd), `${loopId}.jsonl`);
|
|
35081
|
-
}
|
|
35082
|
-
function readRecords(path, loopId) {
|
|
35083
|
-
if (!existsSync7(path)) {
|
|
35084
|
-
throw new LoopStoreError(`no loop log for ${JSON.stringify(loopId)} at ${path}`);
|
|
35085
|
-
}
|
|
35086
|
-
const records = validateLoopLog(parseLoopLog(readFileSync6(path, "utf-8")));
|
|
35087
|
-
const header = records[0];
|
|
35088
|
-
if (header.loopId !== loopId) {
|
|
35089
|
-
throw new LoopStoreError(`loop log at ${path} declares loopId ${JSON.stringify(header.loopId)}, expected ${JSON.stringify(loopId)}`);
|
|
35090
|
-
}
|
|
35091
|
-
return records;
|
|
35092
|
-
}
|
|
35093
|
-
function startLoop(cwd, opts) {
|
|
35094
|
-
const loopId = opts.loopId ?? crypto.randomUUID();
|
|
35095
|
-
const path = loopPath(cwd, loopId);
|
|
35096
|
-
if (existsSync7(path) && !opts.force) {
|
|
35097
|
-
throw new LoopStoreError(`loop log already exists at ${path} (pass force to overwrite)`);
|
|
35736
|
+
prompt(name, ...rest) {
|
|
35737
|
+
if (this._registeredPrompts[name]) {
|
|
35738
|
+
throw new Error(`Prompt ${name} is already registered`);
|
|
35739
|
+
}
|
|
35740
|
+
let description;
|
|
35741
|
+
if (typeof rest[0] === "string") {
|
|
35742
|
+
description = rest.shift();
|
|
35743
|
+
}
|
|
35744
|
+
let argsSchema;
|
|
35745
|
+
if (rest.length > 1) {
|
|
35746
|
+
argsSchema = rest.shift();
|
|
35747
|
+
}
|
|
35748
|
+
const cb = rest[0];
|
|
35749
|
+
const registeredPrompt = this._createRegisteredPrompt(name, undefined, description, argsSchema, cb);
|
|
35750
|
+
this.setPromptRequestHandlers();
|
|
35751
|
+
this.sendPromptListChanged();
|
|
35752
|
+
return registeredPrompt;
|
|
35098
35753
|
}
|
|
35099
|
-
|
|
35100
|
-
|
|
35101
|
-
|
|
35102
|
-
|
|
35103
|
-
|
|
35104
|
-
|
|
35105
|
-
|
|
35106
|
-
|
|
35107
|
-
|
|
35108
|
-
maxIterations: opts.maxIterations
|
|
35109
|
-
};
|
|
35110
|
-
writeFileSync4(path, `${serializeLoopRecord(header)}
|
|
35111
|
-
`);
|
|
35112
|
-
return { loopId, path };
|
|
35113
|
-
}
|
|
35114
|
-
function appendIteration(cwd, loopId, opts) {
|
|
35115
|
-
const path = loopPath(cwd, loopId);
|
|
35116
|
-
const records = readRecords(path, loopId);
|
|
35117
|
-
if (records.some((r) => r.type === "stop")) {
|
|
35118
|
-
throw new LoopStoreError(`loop ${JSON.stringify(loopId)} is already stopped; cannot append`);
|
|
35754
|
+
registerPrompt(name, config2, cb) {
|
|
35755
|
+
if (this._registeredPrompts[name]) {
|
|
35756
|
+
throw new Error(`Prompt ${name} is already registered`);
|
|
35757
|
+
}
|
|
35758
|
+
const { title, description, argsSchema } = config2;
|
|
35759
|
+
const registeredPrompt = this._createRegisteredPrompt(name, title, description, argsSchema, cb);
|
|
35760
|
+
this.setPromptRequestHandlers();
|
|
35761
|
+
this.sendPromptListChanged();
|
|
35762
|
+
return registeredPrompt;
|
|
35119
35763
|
}
|
|
35120
|
-
|
|
35121
|
-
|
|
35122
|
-
if (n > header.maxIterations) {
|
|
35123
|
-
throw new LoopStoreError(`loop ${JSON.stringify(loopId)} is at its iteration budget (maxIterations=${header.maxIterations}); cannot append iteration ${n}`);
|
|
35764
|
+
isConnected() {
|
|
35765
|
+
return this.server.transport !== undefined;
|
|
35124
35766
|
}
|
|
35125
|
-
|
|
35126
|
-
|
|
35127
|
-
n,
|
|
35128
|
-
implementSummary: opts.implementSummary,
|
|
35129
|
-
changedFiles: opts.changedFiles,
|
|
35130
|
-
checksRun: opts.checksRun,
|
|
35131
|
-
verdict: opts.verdict,
|
|
35132
|
-
findingCount: opts.findingCount,
|
|
35133
|
-
decision: opts.decision,
|
|
35134
|
-
checkDurationMs: opts.checkDurationMs,
|
|
35135
|
-
at: iso((opts.clock ?? realClock2)())
|
|
35136
|
-
};
|
|
35137
|
-
if (opts.detailsRef !== undefined)
|
|
35138
|
-
rec.detailsRef = opts.detailsRef;
|
|
35139
|
-
if (opts.usage !== undefined)
|
|
35140
|
-
rec.usage = opts.usage;
|
|
35141
|
-
appendFileSync2(path, `${serializeLoopRecord(rec)}
|
|
35142
|
-
`);
|
|
35143
|
-
return { n, path };
|
|
35144
|
-
}
|
|
35145
|
-
function stopLoop(cwd, loopId, opts) {
|
|
35146
|
-
const path = loopPath(cwd, loopId);
|
|
35147
|
-
const records = readRecords(path, loopId);
|
|
35148
|
-
if (records.some((r) => r.type === "stop")) {
|
|
35149
|
-
throw new LoopStoreError(`loop ${JSON.stringify(loopId)} is already stopped`);
|
|
35767
|
+
async sendLoggingMessage(params, sessionId) {
|
|
35768
|
+
return this.server.sendLoggingMessage(params, sessionId);
|
|
35150
35769
|
}
|
|
35151
|
-
|
|
35152
|
-
|
|
35153
|
-
|
|
35154
|
-
const totalElapsedMs = Math.max(0, nowMs - Date.parse(header.startedAt));
|
|
35155
|
-
const rec = {
|
|
35156
|
-
type: "stop",
|
|
35157
|
-
status: opts.status,
|
|
35158
|
-
reason: opts.reason,
|
|
35159
|
-
iterations,
|
|
35160
|
-
totalElapsedMs,
|
|
35161
|
-
endedAt: iso(nowMs)
|
|
35162
|
-
};
|
|
35163
|
-
appendFileSync2(path, `${serializeLoopRecord(rec)}
|
|
35164
|
-
`);
|
|
35165
|
-
return { iterations, totalElapsedMs, path };
|
|
35166
|
-
}
|
|
35167
|
-
function readLoop(cwd, loopId) {
|
|
35168
|
-
return readRecords(loopPath(cwd, loopId), loopId);
|
|
35169
|
-
}
|
|
35170
|
-
|
|
35171
|
-
// src/cli/default-converge-manifest.ts
|
|
35172
|
-
var DEFAULT_CONVERGE_MANIFEST = {
|
|
35173
|
-
schema: 1,
|
|
35174
|
-
id: "converge",
|
|
35175
|
-
description: "Autonomous convergence: a write-capable Claude implements a slice, then a read-only Codex reviews the diff and returns proceed/revise/block. Drive it in a loop with the `chit converge` CLI driver, or stepwise from the MCP - one chit_start per iteration, same scope so both agents keep their thread, feeding the prior review back in via inputs.prior_review. The human sequences and checkpoints (inspect the diff each round, stop if it goes sideways); chit runs the agents. Run against an isolated worktree, not the main checkout.",
|
|
35176
|
-
inputs: {
|
|
35177
|
-
task: { type: "string" },
|
|
35178
|
-
prior_review: { type: "string", optional: true }
|
|
35179
|
-
},
|
|
35180
|
-
requires: {
|
|
35181
|
-
can_show_markdown: true
|
|
35182
|
-
},
|
|
35183
|
-
participants: {
|
|
35184
|
-
implementer: {
|
|
35185
|
-
agent: "claude",
|
|
35186
|
-
role: "You implement one small, focused slice of a software task in the repository at your cwd. Make the ACTUAL code edits with your tools - do not just describe them. Stay scoped to the task; do not refactor unrelated code. If a prior review is provided, address its concrete findings. Run the project's checks if quick. Then summarize precisely: which files you changed, what each change does and why, what you deliberately did NOT do, and which checks you ran with their results.",
|
|
35187
|
-
session: "per_scope",
|
|
35188
|
-
permissions: { filesystem: "write" }
|
|
35189
|
-
},
|
|
35190
|
-
reviewer: {
|
|
35191
|
-
agent: "codex",
|
|
35192
|
-
role: "You are a skeptical implementation reviewer for a convergence loop. Claude just edited the repository at your cwd. Inspect the current git diff and the changed files, verify the work against the task, and run non-mutating checks if useful. Do not edit. Do not agree for the sake of agreeing. Use prior context from this scope. Cite file:line and command results.",
|
|
35193
|
-
session: "per_scope",
|
|
35194
|
-
permissions: { filesystem: "read_only" }
|
|
35195
|
-
}
|
|
35196
|
-
},
|
|
35197
|
-
steps: {
|
|
35198
|
-
implement: {
|
|
35199
|
-
call: "implementer",
|
|
35200
|
-
prompt: `Task:
|
|
35201
|
-
{{ inputs.task }}
|
|
35202
|
-
|
|
35203
|
-
Prior review to address (empty on the first iteration):
|
|
35204
|
-
{{ inputs.prior_review }}
|
|
35205
|
-
|
|
35206
|
-
Implement this slice now by editing files in the repo at your cwd. Keep it small and focused. Then summarize what you changed (files + what/why), what you did not do, and any checks you ran.`
|
|
35207
|
-
},
|
|
35208
|
-
review: {
|
|
35209
|
-
call: "reviewer",
|
|
35210
|
-
prompt: `Task under review:
|
|
35211
|
-
{{ inputs.task }}
|
|
35212
|
-
|
|
35213
|
-
Claude's summary of what it just implemented:
|
|
35214
|
-
{{ steps.implement.output }}
|
|
35215
|
-
|
|
35216
|
-
Inspect the current git diff and the changed files at your cwd. Verify the change against the task and run non-mutating checks if useful. Return prose with:
|
|
35217
|
-
1. Verdict: proceed / revise / block.
|
|
35218
|
-
2. Findings ordered by severity, with file:line.
|
|
35219
|
-
3. What Claude should fix next if the verdict is revise.
|
|
35220
|
-
4. Remaining risk if proceeding.
|
|
35221
|
-
|
|
35222
|
-
Then, as the LAST thing in your reply, emit a single machine-readable fenced JSON block that the driver parses (the prose above is for humans). Use exactly these keys:
|
|
35223
|
-
\`\`\`json
|
|
35224
|
-
{"verdict": "proceed | revise | block", "findingCount": 0, "checksRun": "the non-mutating checks you ran, or 'none'", "risk": "remaining risk if proceeding"}
|
|
35225
|
-
\`\`\`
|
|
35226
|
-
findingCount is the integer number of findings; checksRun is a short human string.`
|
|
35227
|
-
},
|
|
35228
|
-
out: {
|
|
35229
|
-
format: `## Converge iteration
|
|
35230
|
-
|
|
35231
|
-
### Implementer (Claude)
|
|
35232
|
-
{{ steps.implement.output }}
|
|
35233
|
-
|
|
35234
|
-
### Reviewer (Codex)
|
|
35235
|
-
{{ steps.review.output }}`
|
|
35770
|
+
sendResourceListChanged() {
|
|
35771
|
+
if (this.isConnected()) {
|
|
35772
|
+
this.server.sendResourceListChanged();
|
|
35236
35773
|
}
|
|
35237
|
-
},
|
|
35238
|
-
output: "out"
|
|
35239
|
-
};
|
|
35240
|
-
|
|
35241
|
-
// src/cli/converge.ts
|
|
35242
|
-
var defaultIO = {
|
|
35243
|
-
out: (s) => process.stdout.write(s),
|
|
35244
|
-
err: (s) => process.stderr.write(s)
|
|
35245
|
-
};
|
|
35246
|
-
var JSON_BLOCK_RE = /```json\s*([\s\S]*?)```/gi;
|
|
35247
|
-
var VERDICTS3 = new Set(["proceed", "revise", "block"]);
|
|
35248
|
-
var IMPLEMENT_STEP_ID = "implement";
|
|
35249
|
-
var REVIEW_STEP_ID = "review";
|
|
35250
|
-
var IMPLEMENT_SUMMARY_CAP = 2000;
|
|
35251
|
-
var CHECKS_RUN_FALLBACK = "unreported";
|
|
35252
|
-
function extractReviewJson(reviewText) {
|
|
35253
|
-
let last;
|
|
35254
|
-
for (const m of reviewText.matchAll(JSON_BLOCK_RE)) {
|
|
35255
|
-
if (m[1] !== undefined)
|
|
35256
|
-
last = m[1];
|
|
35257
35774
|
}
|
|
35258
|
-
|
|
35259
|
-
|
|
35260
|
-
|
|
35261
|
-
const parsed = JSON.parse(last);
|
|
35262
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
35263
|
-
return parsed;
|
|
35775
|
+
sendToolListChanged() {
|
|
35776
|
+
if (this.isConnected()) {
|
|
35777
|
+
this.server.sendToolListChanged();
|
|
35264
35778
|
}
|
|
35265
|
-
} catch {}
|
|
35266
|
-
return null;
|
|
35267
|
-
}
|
|
35268
|
-
function parseReview(reviewText) {
|
|
35269
|
-
const block = extractReviewJson(reviewText);
|
|
35270
|
-
const rawVerdict = typeof block?.verdict === "string" ? block.verdict.toLowerCase() : undefined;
|
|
35271
|
-
if (block && rawVerdict && VERDICTS3.has(rawVerdict)) {
|
|
35272
|
-
const fc = block.findingCount;
|
|
35273
|
-
const cr = block.checksRun;
|
|
35274
|
-
return {
|
|
35275
|
-
verdict: rawVerdict,
|
|
35276
|
-
findingCount: typeof fc === "number" && Number.isInteger(fc) && fc >= 0 ? fc : 0,
|
|
35277
|
-
checksRun: typeof cr === "string" && cr.trim() !== "" ? cr.trim() : CHECKS_RUN_FALLBACK
|
|
35278
|
-
};
|
|
35279
|
-
}
|
|
35280
|
-
return { verdict: "block", findingCount: 0, checksRun: CHECKS_RUN_FALLBACK };
|
|
35281
|
-
}
|
|
35282
|
-
function reviewDurationMs(trace) {
|
|
35283
|
-
for (const e of trace) {
|
|
35284
|
-
if (e.type === "step.completed" && e.stepId === REVIEW_STEP_ID)
|
|
35285
|
-
return e.durationMs;
|
|
35286
35779
|
}
|
|
35287
|
-
|
|
35288
|
-
|
|
35289
|
-
|
|
35290
|
-
"inputTokens",
|
|
35291
|
-
"outputTokens",
|
|
35292
|
-
"totalTokens",
|
|
35293
|
-
"cachedInputTokens",
|
|
35294
|
-
"reasoningTokens",
|
|
35295
|
-
"estimatedCostUsd"
|
|
35296
|
-
];
|
|
35297
|
-
function sumTraceUsage(trace) {
|
|
35298
|
-
const usage = {};
|
|
35299
|
-
let any3 = false;
|
|
35300
|
-
for (const e of trace) {
|
|
35301
|
-
if (e.type !== "step.completed" || !e.usage)
|
|
35302
|
-
continue;
|
|
35303
|
-
for (const k of USAGE_KEYS2) {
|
|
35304
|
-
const v = e.usage[k];
|
|
35305
|
-
if (typeof v === "number") {
|
|
35306
|
-
usage[k] = (usage[k] ?? 0) + v;
|
|
35307
|
-
any3 = true;
|
|
35308
|
-
}
|
|
35780
|
+
sendPromptListChanged() {
|
|
35781
|
+
if (this.isConnected()) {
|
|
35782
|
+
this.server.sendPromptListChanged();
|
|
35309
35783
|
}
|
|
35310
35784
|
}
|
|
35311
|
-
return any3 ? usage : undefined;
|
|
35312
|
-
}
|
|
35313
|
-
function capSummary(text) {
|
|
35314
|
-
if (text === "")
|
|
35315
|
-
return "(no summary)";
|
|
35316
|
-
if (text.length <= IMPLEMENT_SUMMARY_CAP)
|
|
35317
|
-
return text;
|
|
35318
|
-
return `${text.slice(0, IMPLEMENT_SUMMARY_CAP)}\u2026 (truncated, ${text.length} chars)`;
|
|
35319
35785
|
}
|
|
35320
|
-
|
|
35321
|
-
|
|
35322
|
-
|
|
35323
|
-
|
|
35324
|
-
|
|
35325
|
-
|
|
35326
|
-
});
|
|
35327
|
-
return out.split(`
|
|
35328
|
-
`).map((s) => s.trim()).filter(Boolean);
|
|
35329
|
-
} catch {
|
|
35330
|
-
return [];
|
|
35331
|
-
}
|
|
35786
|
+
var EMPTY_OBJECT_JSON_SCHEMA = {
|
|
35787
|
+
type: "object",
|
|
35788
|
+
properties: {}
|
|
35789
|
+
};
|
|
35790
|
+
function isZodTypeLike(value) {
|
|
35791
|
+
return value !== null && typeof value === "object" && "parse" in value && typeof value.parse === "function" && "safeParse" in value && typeof value.safeParse === "function";
|
|
35332
35792
|
}
|
|
35333
|
-
function
|
|
35334
|
-
|
|
35335
|
-
...gitLines(cwd, ["diff", "--name-only"]),
|
|
35336
|
-
...gitLines(cwd, ["diff", "--cached", "--name-only"]),
|
|
35337
|
-
...gitLines(cwd, ["ls-files", "--others", "--exclude-standard"])
|
|
35338
|
-
];
|
|
35339
|
-
return [...new Set(all)];
|
|
35793
|
+
function isZodSchemaInstance(obj3) {
|
|
35794
|
+
return "_def" in obj3 || "_zod" in obj3 || isZodTypeLike(obj3);
|
|
35340
35795
|
}
|
|
35341
|
-
|
|
35342
|
-
|
|
35343
|
-
|
|
35344
|
-
constructor(executeError) {
|
|
35345
|
-
super(executeError instanceof Error ? executeError.message : String(executeError));
|
|
35346
|
-
this.executeError = executeError;
|
|
35796
|
+
function isZodRawShapeCompat(obj3) {
|
|
35797
|
+
if (typeof obj3 !== "object" || obj3 === null) {
|
|
35798
|
+
return false;
|
|
35347
35799
|
}
|
|
35348
|
-
|
|
35349
|
-
|
|
35350
|
-
let result;
|
|
35351
|
-
try {
|
|
35352
|
-
result = await ctx.execute({ task: ctx.task, prior_review: ctx.prior_review }, {
|
|
35353
|
-
loopId: ctx.loopId,
|
|
35354
|
-
iteration: ctx.iteration,
|
|
35355
|
-
...ctx.signal && { signal: ctx.signal }
|
|
35356
|
-
});
|
|
35357
|
-
} catch (e) {
|
|
35358
|
-
throw new ConvergeExecuteError(e);
|
|
35800
|
+
if (isZodSchemaInstance(obj3)) {
|
|
35801
|
+
return false;
|
|
35359
35802
|
}
|
|
35360
|
-
if (
|
|
35361
|
-
return
|
|
35362
|
-
ok: false,
|
|
35363
|
-
failure: `manifest run failed at step "${result.failedStep}": ${result.error}`
|
|
35364
|
-
};
|
|
35803
|
+
if (Object.keys(obj3).length === 0) {
|
|
35804
|
+
return true;
|
|
35365
35805
|
}
|
|
35366
|
-
|
|
35367
|
-
const review = parseReview(reviewText);
|
|
35368
|
-
const usage = sumTraceUsage(result.trace);
|
|
35369
|
-
const changedFiles = gitChangedFiles(ctx.cwd);
|
|
35370
|
-
appendIteration(ctx.cwd, ctx.loopId, {
|
|
35371
|
-
implementSummary: capSummary(result.outputs.implement ?? ""),
|
|
35372
|
-
changedFiles,
|
|
35373
|
-
checksRun: review.checksRun,
|
|
35374
|
-
verdict: review.verdict,
|
|
35375
|
-
findingCount: review.findingCount,
|
|
35376
|
-
decision: review.verdict,
|
|
35377
|
-
checkDurationMs: reviewDurationMs(result.trace),
|
|
35378
|
-
...usage && { usage },
|
|
35379
|
-
...result.auditRunId && { detailsRef: `audit:${result.auditRunId}` }
|
|
35380
|
-
});
|
|
35381
|
-
const stopStatus = review.verdict === "proceed" ? "converged" : review.verdict === "block" ? "blocked" : undefined;
|
|
35382
|
-
return {
|
|
35383
|
-
ok: true,
|
|
35384
|
-
verdict: review.verdict,
|
|
35385
|
-
findingCount: review.findingCount,
|
|
35386
|
-
checksRun: review.checksRun,
|
|
35387
|
-
decision: review.verdict,
|
|
35388
|
-
changedFiles,
|
|
35389
|
-
...usage && { usage },
|
|
35390
|
-
...result.auditRunId && { auditRunId: result.auditRunId },
|
|
35391
|
-
reviewText,
|
|
35392
|
-
...stopStatus && { stopStatus }
|
|
35393
|
-
};
|
|
35806
|
+
return Object.values(obj3).some(isZodTypeLike);
|
|
35394
35807
|
}
|
|
35395
|
-
|
|
35396
|
-
|
|
35397
|
-
|
|
35398
|
-
task: opts.task,
|
|
35399
|
-
maxIterations: opts.maxIterations,
|
|
35400
|
-
loopId: opts.loopId,
|
|
35401
|
-
force: opts.force
|
|
35402
|
-
});
|
|
35403
|
-
let priorReview = "";
|
|
35404
|
-
let iterations = 0;
|
|
35405
|
-
let status;
|
|
35406
|
-
for (let i = 1;i <= opts.maxIterations; i++) {
|
|
35407
|
-
let iter;
|
|
35408
|
-
try {
|
|
35409
|
-
iter = await runConvergeIteration({
|
|
35410
|
-
cwd: opts.cwd,
|
|
35411
|
-
loopId,
|
|
35412
|
-
iteration: i,
|
|
35413
|
-
task: opts.task,
|
|
35414
|
-
prior_review: priorReview,
|
|
35415
|
-
execute: opts.execute
|
|
35416
|
-
});
|
|
35417
|
-
} catch (e) {
|
|
35418
|
-
if (e instanceof ConvergeExecuteError) {
|
|
35419
|
-
stopLoop(opts.cwd, loopId, {
|
|
35420
|
-
status: "blocked",
|
|
35421
|
-
reason: `manifest run threw: ${e.message}`
|
|
35422
|
-
});
|
|
35423
|
-
throw e.executeError;
|
|
35424
|
-
}
|
|
35425
|
-
throw e;
|
|
35426
|
-
}
|
|
35427
|
-
if (!iter.ok) {
|
|
35428
|
-
status = "blocked";
|
|
35429
|
-
stopLoop(opts.cwd, loopId, { status, reason: iter.failure });
|
|
35430
|
-
return { loopId, iterations, status, failure: iter.failure };
|
|
35431
|
-
}
|
|
35432
|
-
iterations++;
|
|
35433
|
-
if (iter.stopStatus !== undefined) {
|
|
35434
|
-
status = iter.stopStatus;
|
|
35435
|
-
break;
|
|
35436
|
-
}
|
|
35437
|
-
priorReview = iter.reviewText;
|
|
35808
|
+
function getZodSchemaObject(schema) {
|
|
35809
|
+
if (!schema) {
|
|
35810
|
+
return;
|
|
35438
35811
|
}
|
|
35439
|
-
if (
|
|
35440
|
-
|
|
35441
|
-
|
|
35442
|
-
|
|
35443
|
-
|
|
35444
|
-
}
|
|
35445
|
-
function buildExecute(manifest, registry3, scope, cwd) {
|
|
35446
|
-
const baseAdapters = {};
|
|
35447
|
-
for (const p of Object.values(manifest.participants)) {
|
|
35448
|
-
if (!(p.agent in baseAdapters)) {
|
|
35449
|
-
const agent = registry3.agents[p.agent];
|
|
35450
|
-
if (!agent)
|
|
35451
|
-
continue;
|
|
35452
|
-
baseAdapters[p.agent] = buildAdapter(agent);
|
|
35453
|
-
}
|
|
35812
|
+
if (isZodRawShapeCompat(schema)) {
|
|
35813
|
+
return objectFromShape(schema);
|
|
35814
|
+
}
|
|
35815
|
+
if (!isZodSchemaInstance(schema)) {
|
|
35816
|
+
throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
|
|
35454
35817
|
}
|
|
35455
|
-
return
|
|
35818
|
+
return schema;
|
|
35456
35819
|
}
|
|
35457
|
-
function
|
|
35458
|
-
|
|
35459
|
-
|
|
35460
|
-
|
|
35461
|
-
|
|
35462
|
-
|
|
35463
|
-
|
|
35464
|
-
|
|
35465
|
-
|
|
35466
|
-
|
|
35467
|
-
|
|
35468
|
-
}
|
|
35469
|
-
|
|
35470
|
-
|
|
35471
|
-
|
|
35472
|
-
|
|
35473
|
-
|
|
35474
|
-
|
|
35475
|
-
|
|
35476
|
-
|
|
35477
|
-
|
|
35478
|
-
|
|
35479
|
-
|
|
35480
|
-
|
|
35481
|
-
|
|
35482
|
-
|
|
35483
|
-
|
|
35484
|
-
|
|
35485
|
-
|
|
35486
|
-
|
|
35820
|
+
function promptArgumentsFromSchema(schema) {
|
|
35821
|
+
const shape = getObjectShape(schema);
|
|
35822
|
+
if (!shape)
|
|
35823
|
+
return [];
|
|
35824
|
+
return Object.entries(shape).map(([name, field]) => {
|
|
35825
|
+
const description = getSchemaDescription(field);
|
|
35826
|
+
const isOptional = isSchemaOptional(field);
|
|
35827
|
+
return {
|
|
35828
|
+
name,
|
|
35829
|
+
description,
|
|
35830
|
+
required: !isOptional
|
|
35831
|
+
};
|
|
35832
|
+
});
|
|
35833
|
+
}
|
|
35834
|
+
function getMethodValue(schema) {
|
|
35835
|
+
const shape = getObjectShape(schema);
|
|
35836
|
+
const methodSchema = shape?.method;
|
|
35837
|
+
if (!methodSchema) {
|
|
35838
|
+
throw new Error("Schema is missing a method literal");
|
|
35839
|
+
}
|
|
35840
|
+
const value = getLiteralValue(methodSchema);
|
|
35841
|
+
if (typeof value === "string") {
|
|
35842
|
+
return value;
|
|
35843
|
+
}
|
|
35844
|
+
throw new Error("Schema method literal must be a string");
|
|
35845
|
+
}
|
|
35846
|
+
function createCompletionResult(suggestions) {
|
|
35847
|
+
return {
|
|
35848
|
+
completion: {
|
|
35849
|
+
values: suggestions.slice(0, 100),
|
|
35850
|
+
total: suggestions.length,
|
|
35851
|
+
hasMore: suggestions.length > 100
|
|
35487
35852
|
}
|
|
35488
35853
|
};
|
|
35489
35854
|
}
|
|
35490
|
-
|
|
35491
|
-
|
|
35492
|
-
|
|
35493
|
-
|
|
35494
|
-
|
|
35855
|
+
var EMPTY_COMPLETION_RESULT = {
|
|
35856
|
+
completion: {
|
|
35857
|
+
values: [],
|
|
35858
|
+
hasMore: false
|
|
35859
|
+
}
|
|
35860
|
+
};
|
|
35861
|
+
|
|
35862
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.29.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
35863
|
+
import process3 from "process";
|
|
35864
|
+
|
|
35865
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.29.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
|
|
35866
|
+
class ReadBuffer {
|
|
35867
|
+
append(chunk) {
|
|
35868
|
+
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
|
|
35869
|
+
}
|
|
35870
|
+
readMessage() {
|
|
35871
|
+
if (!this._buffer) {
|
|
35872
|
+
return null;
|
|
35495
35873
|
}
|
|
35496
|
-
|
|
35497
|
-
|
|
35874
|
+
const index = this._buffer.indexOf(`
|
|
35875
|
+
`);
|
|
35876
|
+
if (index === -1) {
|
|
35877
|
+
return null;
|
|
35498
35878
|
}
|
|
35879
|
+
const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
|
|
35880
|
+
this._buffer = this._buffer.subarray(index + 1);
|
|
35881
|
+
return deserializeMessage(line);
|
|
35882
|
+
}
|
|
35883
|
+
clear() {
|
|
35884
|
+
this._buffer = undefined;
|
|
35499
35885
|
}
|
|
35500
|
-
return null;
|
|
35501
35886
|
}
|
|
35502
|
-
|
|
35503
|
-
|
|
35504
|
-
|
|
35505
|
-
|
|
35506
|
-
|
|
35507
|
-
--manifest <path> Convergence manifest. Default: the built-in converge manifest.
|
|
35508
|
-
--max-iterations <n> Iteration budget. Default: 3.
|
|
35509
|
-
--loop-id <id> Reuse/seed a loop id. Default: generated.
|
|
35510
|
-
--allow-unenforced-permissions
|
|
35511
|
-
Run even when the manifest declares permissions its
|
|
35512
|
-
adapter cannot enforce (emits a warning each run).
|
|
35513
|
-
Default off: such a manifest is refused before running.
|
|
35514
|
-
|
|
35515
|
-
Runs the implement/check loop to convergence and records it under
|
|
35516
|
-
.chit/loops/<loopId>.jsonl. Stops at the reviewer's verdict: proceed ->
|
|
35517
|
-
converged, block -> blocked, else revise and retry up to the budget. An
|
|
35518
|
-
unparseable verdict is treated as block (never an implicit proceed).
|
|
35887
|
+
function deserializeMessage(line) {
|
|
35888
|
+
return JSONRPCMessageSchema.parse(JSON.parse(line));
|
|
35889
|
+
}
|
|
35890
|
+
function serializeMessage(message) {
|
|
35891
|
+
return JSON.stringify(message) + `
|
|
35519
35892
|
`;
|
|
35520
|
-
|
|
35521
|
-
class UsageError extends Error {
|
|
35522
35893
|
}
|
|
35523
|
-
|
|
35524
|
-
|
|
35525
|
-
|
|
35526
|
-
|
|
35527
|
-
|
|
35528
|
-
|
|
35529
|
-
|
|
35530
|
-
|
|
35531
|
-
|
|
35532
|
-
|
|
35533
|
-
|
|
35534
|
-
const v = argv[++i];
|
|
35535
|
-
if (v === undefined)
|
|
35536
|
-
throw new UsageError(`${key} requires a value`);
|
|
35537
|
-
return v;
|
|
35894
|
+
|
|
35895
|
+
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.29.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
35896
|
+
class StdioServerTransport {
|
|
35897
|
+
constructor(_stdin = process3.stdin, _stdout = process3.stdout) {
|
|
35898
|
+
this._stdin = _stdin;
|
|
35899
|
+
this._stdout = _stdout;
|
|
35900
|
+
this._readBuffer = new ReadBuffer;
|
|
35901
|
+
this._started = false;
|
|
35902
|
+
this._ondata = (chunk) => {
|
|
35903
|
+
this._readBuffer.append(chunk);
|
|
35904
|
+
this.processReadBuffer();
|
|
35538
35905
|
};
|
|
35539
|
-
|
|
35540
|
-
|
|
35541
|
-
|
|
35542
|
-
|
|
35543
|
-
|
|
35544
|
-
|
|
35545
|
-
|
|
35546
|
-
|
|
35547
|
-
|
|
35548
|
-
|
|
35549
|
-
|
|
35550
|
-
|
|
35551
|
-
|
|
35552
|
-
|
|
35553
|
-
|
|
35554
|
-
|
|
35555
|
-
|
|
35906
|
+
this._onerror = (error51) => {
|
|
35907
|
+
this.onerror?.(error51);
|
|
35908
|
+
};
|
|
35909
|
+
}
|
|
35910
|
+
async start() {
|
|
35911
|
+
if (this._started) {
|
|
35912
|
+
throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
|
|
35913
|
+
}
|
|
35914
|
+
this._started = true;
|
|
35915
|
+
this._stdin.on("data", this._ondata);
|
|
35916
|
+
this._stdin.on("error", this._onerror);
|
|
35917
|
+
}
|
|
35918
|
+
processReadBuffer() {
|
|
35919
|
+
while (true) {
|
|
35920
|
+
try {
|
|
35921
|
+
const message = this._readBuffer.readMessage();
|
|
35922
|
+
if (message === null) {
|
|
35923
|
+
break;
|
|
35924
|
+
}
|
|
35925
|
+
this.onmessage?.(message);
|
|
35926
|
+
} catch (error51) {
|
|
35927
|
+
this.onerror?.(error51);
|
|
35556
35928
|
}
|
|
35557
|
-
maxIterations = n;
|
|
35558
|
-
} else {
|
|
35559
|
-
throw new UsageError(`unknown flag ${JSON.stringify(a)}`);
|
|
35560
35929
|
}
|
|
35561
35930
|
}
|
|
35562
|
-
|
|
35563
|
-
|
|
35564
|
-
|
|
35565
|
-
|
|
35566
|
-
|
|
35567
|
-
|
|
35568
|
-
|
|
35569
|
-
|
|
35570
|
-
|
|
35571
|
-
maxIterations,
|
|
35572
|
-
loopId,
|
|
35573
|
-
allowUnenforcedPermissions
|
|
35574
|
-
};
|
|
35575
|
-
}
|
|
35576
|
-
async function runConverge(argv, io = defaultIO) {
|
|
35577
|
-
if (argv[0] === "-h" || argv[0] === "--help") {
|
|
35578
|
-
io.out(CONVERGE_HELP);
|
|
35579
|
-
return 0;
|
|
35931
|
+
async close() {
|
|
35932
|
+
this._stdin.off("data", this._ondata);
|
|
35933
|
+
this._stdin.off("error", this._onerror);
|
|
35934
|
+
const remainingDataListeners = this._stdin.listenerCount("data");
|
|
35935
|
+
if (remainingDataListeners === 0) {
|
|
35936
|
+
this._stdin.pause();
|
|
35937
|
+
}
|
|
35938
|
+
this._readBuffer.clear();
|
|
35939
|
+
this.onclose?.();
|
|
35580
35940
|
}
|
|
35581
|
-
|
|
35582
|
-
|
|
35583
|
-
|
|
35584
|
-
|
|
35585
|
-
|
|
35586
|
-
|
|
35941
|
+
send(message) {
|
|
35942
|
+
return new Promise((resolve4) => {
|
|
35943
|
+
const json2 = serializeMessage(message);
|
|
35944
|
+
if (this._stdout.write(json2)) {
|
|
35945
|
+
resolve4();
|
|
35946
|
+
} else {
|
|
35947
|
+
this._stdout.once("drain", resolve4);
|
|
35948
|
+
}
|
|
35949
|
+
});
|
|
35950
|
+
}
|
|
35951
|
+
}
|
|
35587
35952
|
|
|
35588
|
-
|
|
35589
|
-
|
|
35953
|
+
// src/audit/reader.ts
|
|
35954
|
+
var USAGE_KEYS2 = [
|
|
35955
|
+
"inputTokens",
|
|
35956
|
+
"outputTokens",
|
|
35957
|
+
"totalTokens",
|
|
35958
|
+
"cachedInputTokens",
|
|
35959
|
+
"reasoningTokens",
|
|
35960
|
+
"estimatedCostUsd"
|
|
35961
|
+
];
|
|
35962
|
+
function sumUsage(events2) {
|
|
35963
|
+
const usage = {};
|
|
35964
|
+
let any3 = false;
|
|
35965
|
+
for (const e of events2) {
|
|
35966
|
+
if (e.type !== "adapter.call.completed" || !e.usage)
|
|
35967
|
+
continue;
|
|
35968
|
+
for (const k of USAGE_KEYS2) {
|
|
35969
|
+
const v = e.usage[k];
|
|
35970
|
+
if (typeof v === "number") {
|
|
35971
|
+
usage[k] = (usage[k] ?? 0) + v;
|
|
35972
|
+
any3 = true;
|
|
35973
|
+
}
|
|
35590
35974
|
}
|
|
35591
|
-
throw e;
|
|
35592
35975
|
}
|
|
35593
|
-
|
|
35594
|
-
|
|
35595
|
-
|
|
35596
|
-
|
|
35597
|
-
|
|
35598
|
-
|
|
35599
|
-
|
|
35600
|
-
|
|
35976
|
+
return any3 ? usage : undefined;
|
|
35977
|
+
}
|
|
35978
|
+
function findOpenCall(events2) {
|
|
35979
|
+
const open = new Map;
|
|
35980
|
+
for (const e of events2) {
|
|
35981
|
+
if (e.type === "adapter.call.started") {
|
|
35982
|
+
open.set(e.stepId, {
|
|
35983
|
+
stepId: e.stepId,
|
|
35984
|
+
participantId: e.participantId,
|
|
35985
|
+
agentId: e.agentId,
|
|
35986
|
+
since: e.ts
|
|
35987
|
+
});
|
|
35988
|
+
} else if (e.type === "adapter.call.completed") {
|
|
35989
|
+
open.delete(e.stepId);
|
|
35990
|
+
}
|
|
35601
35991
|
}
|
|
35602
|
-
|
|
35603
|
-
|
|
35604
|
-
|
|
35605
|
-
|
|
35606
|
-
return 1;
|
|
35992
|
+
let latest;
|
|
35993
|
+
for (const c of open.values()) {
|
|
35994
|
+
if (latest === undefined || c.since > latest.since)
|
|
35995
|
+
latest = c;
|
|
35607
35996
|
}
|
|
35608
|
-
|
|
35997
|
+
return latest;
|
|
35998
|
+
}
|
|
35999
|
+
function describeIncomplete(s, events2) {
|
|
36000
|
+
if (s.openCall) {
|
|
36001
|
+
const c = s.openCall;
|
|
36002
|
+
return `open call: ${c.stepId} ${c.participantId}/${c.agentId} since ${c.since}; no adapter.call.completed`;
|
|
36003
|
+
}
|
|
36004
|
+
const failed = events2.find((e) => e.type === "step.failed");
|
|
36005
|
+
if (failed?.type === "step.failed") {
|
|
36006
|
+
const err = failed.error.replace(/\s+/g, " ").trim();
|
|
36007
|
+
const clipped = err.length > 200 ? `${err.slice(0, 200)}...` : err;
|
|
36008
|
+
return `failed step: ${failed.stepId}: ${clipped}`;
|
|
36009
|
+
}
|
|
36010
|
+
return "abandoned before terminal run.completed";
|
|
36011
|
+
}
|
|
36012
|
+
function summarizeRun(runId, events2) {
|
|
36013
|
+
const started = events2.find((e) => e.type === "run.started");
|
|
36014
|
+
const completed = events2.find((e) => e.type === "run.completed");
|
|
36015
|
+
const summary = {
|
|
36016
|
+
runId,
|
|
36017
|
+
manifestId: started?.type === "run.started" ? started.manifestId : "?",
|
|
36018
|
+
surface: started?.type === "run.started" ? started.surface : "?",
|
|
36019
|
+
status: completed?.type === "run.completed" ? completed.status : "incomplete",
|
|
36020
|
+
stepCount: events2.filter((e) => e.type === "step.completed").length
|
|
36021
|
+
};
|
|
36022
|
+
if (started?.type === "run.started") {
|
|
36023
|
+
summary.startedAt = started.ts;
|
|
36024
|
+
if (started.scope !== undefined)
|
|
36025
|
+
summary.scope = started.scope;
|
|
36026
|
+
if (started.loopId !== undefined)
|
|
36027
|
+
summary.loopId = started.loopId;
|
|
36028
|
+
if (started.iteration !== undefined)
|
|
36029
|
+
summary.iteration = started.iteration;
|
|
36030
|
+
}
|
|
36031
|
+
const usage = sumUsage(events2);
|
|
36032
|
+
if (usage !== undefined)
|
|
36033
|
+
summary.usage = usage;
|
|
36034
|
+
const openCall = findOpenCall(events2);
|
|
36035
|
+
if (openCall !== undefined)
|
|
36036
|
+
summary.openCall = openCall;
|
|
36037
|
+
return summary;
|
|
36038
|
+
}
|
|
36039
|
+
function safeReadEvents(store, runId) {
|
|
35609
36040
|
try {
|
|
35610
|
-
|
|
35611
|
-
} catch
|
|
35612
|
-
|
|
35613
|
-
`);
|
|
35614
|
-
return 1;
|
|
36041
|
+
return store.readEvents(runId);
|
|
36042
|
+
} catch {
|
|
36043
|
+
return [];
|
|
35615
36044
|
}
|
|
35616
|
-
|
|
35617
|
-
|
|
35618
|
-
|
|
35619
|
-
|
|
35620
|
-
|
|
35621
|
-
|
|
35622
|
-
|
|
36045
|
+
}
|
|
36046
|
+
function listAudit(store, limit) {
|
|
36047
|
+
const summaries = store.listRuns().map((id) => summarizeRun(id, safeReadEvents(store, id)));
|
|
36048
|
+
summaries.sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? ""));
|
|
36049
|
+
return limit !== undefined ? summaries.slice(0, limit) : summaries;
|
|
36050
|
+
}
|
|
36051
|
+
function readBody(store, runId, ref) {
|
|
36052
|
+
try {
|
|
36053
|
+
return store.readBlob(runId, ref);
|
|
36054
|
+
} catch (err) {
|
|
36055
|
+
return `<blob unavailable: ${err.message}>`;
|
|
35623
36056
|
}
|
|
35624
|
-
|
|
35625
|
-
|
|
35626
|
-
|
|
35627
|
-
|
|
35628
|
-
|
|
35629
|
-
|
|
35630
|
-
|
|
35631
|
-
|
|
35632
|
-
|
|
35633
|
-
|
|
36057
|
+
}
|
|
36058
|
+
function isReceiptEvent(e) {
|
|
36059
|
+
return e.type !== "adapter.event";
|
|
36060
|
+
}
|
|
36061
|
+
function hiddenAdapterEventCount(events2) {
|
|
36062
|
+
return events2.reduce((n, e) => e.type === "adapter.event" ? n + 1 : n, 0);
|
|
36063
|
+
}
|
|
36064
|
+
function auditTimeline(store, runId, events2, opts) {
|
|
36065
|
+
const rows = opts.verbose ? events2 : events2.filter(isReceiptEvent);
|
|
36066
|
+
return rows.map((e) => {
|
|
36067
|
+
if (!opts.includeBodies)
|
|
36068
|
+
return e;
|
|
36069
|
+
if (e.type === "adapter.call.started") {
|
|
36070
|
+
return { ...e, input: readBody(store, runId, e.inputBlob) };
|
|
36071
|
+
}
|
|
36072
|
+
if (e.type === "adapter.event" && e.rawBlob !== undefined) {
|
|
36073
|
+
return { ...e, raw: readBody(store, runId, e.rawBlob) };
|
|
36074
|
+
}
|
|
36075
|
+
if (e.type === "adapter.call.completed") {
|
|
36076
|
+
return { ...e, output: readBody(store, runId, e.outputBlob) };
|
|
36077
|
+
}
|
|
36078
|
+
if (e.type === "step.completed" && e.outputBlob !== undefined) {
|
|
36079
|
+
return { ...e, output: readBody(store, runId, e.outputBlob) };
|
|
36080
|
+
}
|
|
36081
|
+
return e;
|
|
36082
|
+
});
|
|
36083
|
+
}
|
|
36084
|
+
function showAudit(store, runId, opts) {
|
|
36085
|
+
const events2 = store.readEvents(runId);
|
|
36086
|
+
const summary = summarizeRun(runId, events2);
|
|
36087
|
+
const out = {
|
|
36088
|
+
summary,
|
|
36089
|
+
timeline: auditTimeline(store, runId, events2, opts)
|
|
36090
|
+
};
|
|
36091
|
+
if (summary.status === "incomplete")
|
|
36092
|
+
out.incompleteReason = describeIncomplete(summary, events2);
|
|
36093
|
+
const started = events2.find((e) => e.type === "run.started");
|
|
36094
|
+
if (started?.type === "run.started" && started.participants !== undefined) {
|
|
36095
|
+
out.participants = started.participants;
|
|
35634
36096
|
}
|
|
35635
|
-
|
|
35636
|
-
|
|
35637
|
-
|
|
36097
|
+
if (!opts.verbose) {
|
|
36098
|
+
const hidden = hiddenAdapterEventCount(events2);
|
|
36099
|
+
if (hidden > 0) {
|
|
36100
|
+
out.note = `${hidden} raw adapter events hidden; pass verbose to include them, include_bodies to show blob bodies.`;
|
|
36101
|
+
}
|
|
35638
36102
|
}
|
|
35639
|
-
|
|
36103
|
+
return out;
|
|
36104
|
+
}
|
|
36105
|
+
|
|
36106
|
+
// src/jobs/health.ts
|
|
36107
|
+
var STALE_AFTER_MS = 60000;
|
|
36108
|
+
function pidAlive(pid) {
|
|
36109
|
+
if (pid === undefined)
|
|
36110
|
+
return false;
|
|
35640
36111
|
try {
|
|
35641
|
-
|
|
35642
|
-
|
|
35643
|
-
scope: parsed.scope,
|
|
35644
|
-
task: parsed.task,
|
|
35645
|
-
maxIterations: parsed.maxIterations,
|
|
35646
|
-
loopId: parsed.loopId,
|
|
35647
|
-
execute: buildExecute(manifest, registry3, parsed.scope, parsed.cwd)
|
|
35648
|
-
});
|
|
36112
|
+
process.kill(pid, 0);
|
|
36113
|
+
return true;
|
|
35649
36114
|
} catch (e) {
|
|
35650
|
-
|
|
35651
|
-
`);
|
|
35652
|
-
return 1;
|
|
36115
|
+
return e.code === "EPERM";
|
|
35653
36116
|
}
|
|
35654
|
-
|
|
35655
|
-
|
|
35656
|
-
|
|
35657
|
-
|
|
36117
|
+
}
|
|
36118
|
+
function isStale(job, nowMs, staleAfterMs = STALE_AFTER_MS) {
|
|
36119
|
+
if (job.state === "queued") {
|
|
36120
|
+
const created = Date.parse(job.createdAt);
|
|
36121
|
+
return Number.isFinite(created) && nowMs - created > staleAfterMs;
|
|
35658
36122
|
}
|
|
35659
|
-
|
|
35660
|
-
|
|
35661
|
-
|
|
35662
|
-
|
|
35663
|
-
|
|
35664
|
-
`);
|
|
35665
|
-
return 0;
|
|
36123
|
+
if (job.state !== "running")
|
|
36124
|
+
return false;
|
|
36125
|
+
const beat = job.lastHeartbeatAt ? Date.parse(job.lastHeartbeatAt) : 0;
|
|
36126
|
+
const heartbeatOld = !Number.isFinite(beat) || nowMs - beat > staleAfterMs;
|
|
36127
|
+
return heartbeatOld || !pidAlive(job.pid);
|
|
35666
36128
|
}
|
|
35667
36129
|
|
|
35668
36130
|
// src/surfaces/mcp/converge-engine.ts
|
|
@@ -35764,6 +36226,7 @@ async function runNextIteration(session, signal) {
|
|
|
35764
36226
|
findingCount: iter.findingCount,
|
|
35765
36227
|
checksRun: iter.checksRun,
|
|
35766
36228
|
changedFiles: iter.changedFiles,
|
|
36229
|
+
workspaceWarnings: iter.workspaceWarnings,
|
|
35767
36230
|
...iter.usage !== undefined && { usage: iter.usage },
|
|
35768
36231
|
...iter.auditRunId !== undefined && { auditRunId: iter.auditRunId },
|
|
35769
36232
|
...session.terminalStatus !== undefined && { stopStatus: session.terminalStatus }
|
|
@@ -35878,7 +36341,7 @@ function startRun(runId, opts) {
|
|
|
35878
36341
|
}
|
|
35879
36342
|
const needsScope = Object.values(manifest.participants).some((p) => p.session === "per_scope");
|
|
35880
36343
|
if (needsScope && opts.scope === undefined) {
|
|
35881
|
-
throw new RuntimeError(`manifest "${manifest.id}" has per_scope participant(s); a scope is required (pass scope to
|
|
36344
|
+
throw new RuntimeError(`manifest "${manifest.id}" has per_scope participant(s); a scope is required (pass scope to chit_run_start)`);
|
|
35882
36345
|
}
|
|
35883
36346
|
const baseAdapters = {};
|
|
35884
36347
|
for (const p of Object.values(manifest.participants)) {
|
|
@@ -36115,6 +36578,25 @@ function summarizeRunForStatus(run) {
|
|
|
36115
36578
|
audited: run.recorder !== undefined && run.recorder.lastError === undefined
|
|
36116
36579
|
};
|
|
36117
36580
|
}
|
|
36581
|
+
function summarizeJobForStatus(job, nowMs) {
|
|
36582
|
+
const stale = isStale(job, nowMs);
|
|
36583
|
+
const display = stale ? "stale" : job.state;
|
|
36584
|
+
const nextAction = display === "running" ? `in progress${job.phase ? ` (${job.phase})` : ""}; chit_job_status / chit_job_cancel "${job.jobId}"` : display === "queued" ? "queued; the worker is starting" : display === "stale" ? `worker appears dead; chit_job_status "${job.jobId}" to inspect, then start a fresh job` : `${display}${job.stopStatus ? ` (${job.stopStatus})` : ""}; chit_job_status "${job.jobId}" or chit_audit_show <ref>`;
|
|
36585
|
+
return {
|
|
36586
|
+
jobId: job.jobId,
|
|
36587
|
+
loopId: job.loopId,
|
|
36588
|
+
scope: job.scope,
|
|
36589
|
+
task: job.task,
|
|
36590
|
+
display,
|
|
36591
|
+
...job.phase !== undefined && { phase: job.phase },
|
|
36592
|
+
iterationsCompleted: job.iterationsCompleted,
|
|
36593
|
+
...job.lastVerdict !== undefined && { lastVerdict: job.lastVerdict },
|
|
36594
|
+
...job.stopStatus !== undefined && { stopStatus: job.stopStatus },
|
|
36595
|
+
auditRefs: job.auditRefs,
|
|
36596
|
+
createdAt: job.createdAt,
|
|
36597
|
+
nextAction
|
|
36598
|
+
};
|
|
36599
|
+
}
|
|
36118
36600
|
function byNewest(items) {
|
|
36119
36601
|
return [...items].sort((a, b) => b.startedAtMs - a.startedAtMs);
|
|
36120
36602
|
}
|
|
@@ -36127,21 +36609,34 @@ function recentRuns(auditStore, recentLimit) {
|
|
|
36127
36609
|
return [];
|
|
36128
36610
|
}
|
|
36129
36611
|
}
|
|
36130
|
-
function buildStatus(runs, convergeSessions, auditStore, recentLimit) {
|
|
36612
|
+
function buildStatus(runs, convergeSessions, auditStore, jobStore, recentLimit, nowMs) {
|
|
36131
36613
|
return {
|
|
36132
36614
|
active: {
|
|
36133
36615
|
runs: byNewest(runs.list()).map(summarizeRunForStatus),
|
|
36134
36616
|
loops: byNewest(convergeSessions.list()).map(describeConverge)
|
|
36135
36617
|
},
|
|
36618
|
+
jobs: jobsForStatus(jobStore, recentLimit, nowMs),
|
|
36136
36619
|
recent: recentRuns(auditStore, recentLimit)
|
|
36137
36620
|
};
|
|
36138
36621
|
}
|
|
36622
|
+
function jobsForStatus(jobStore, recentLimit, nowMs) {
|
|
36623
|
+
let all;
|
|
36624
|
+
try {
|
|
36625
|
+
all = jobStore.list();
|
|
36626
|
+
} catch {
|
|
36627
|
+
return [];
|
|
36628
|
+
}
|
|
36629
|
+
const inFlight = all.filter((j) => j.state === "queued" || j.state === "running");
|
|
36630
|
+
const terminal = all.filter((j) => j.state !== "queued" && j.state !== "running").slice(0, recentLimit);
|
|
36631
|
+
return [...inFlight, ...terminal].map((j) => summarizeJobForStatus(j, nowMs));
|
|
36632
|
+
}
|
|
36139
36633
|
|
|
36140
36634
|
// src/surfaces/mcp/server.ts
|
|
36141
36635
|
var runs = new RunStore;
|
|
36142
36636
|
var controllers = new Map;
|
|
36143
36637
|
var convergeSessions = new ConvergeStore;
|
|
36144
36638
|
var auditStore = new AuditStore;
|
|
36639
|
+
var jobStore = new JobStore;
|
|
36145
36640
|
var registryCache;
|
|
36146
36641
|
function getRegistry() {
|
|
36147
36642
|
registryCache ??= loadRegistry();
|
|
@@ -36179,7 +36674,7 @@ function describeRun(run) {
|
|
|
36179
36674
|
audit: run.recorder && run.recorder.lastError === undefined ? { runId: run.runId } : undefined
|
|
36180
36675
|
};
|
|
36181
36676
|
}
|
|
36182
|
-
server.registerTool("
|
|
36677
|
+
server.registerTool("chit_run_start", {
|
|
36183
36678
|
description: "Start a stepwise run of a chit manifest. Returns a run_id and the steps ready to run. chit owns the declared order; only ready steps can be run. Then call chit_run_step for each ready step.",
|
|
36184
36679
|
inputSchema: {
|
|
36185
36680
|
manifest_path: exports_external.string().describe("Path to the manifest .json (absolute, or relative to cwd)"),
|
|
@@ -36191,10 +36686,10 @@ server.registerTool("chit_start", {
|
|
|
36191
36686
|
}
|
|
36192
36687
|
}, async ({ manifest_path, inputs, scope, cwd, allow_unenforced_permissions, audit }) => {
|
|
36193
36688
|
runs.sweep(Date.now());
|
|
36194
|
-
const path =
|
|
36689
|
+
const path = isAbsolute3(manifest_path) ? manifest_path : resolve4(process.cwd(), manifest_path);
|
|
36195
36690
|
let raw;
|
|
36196
36691
|
try {
|
|
36197
|
-
raw = JSON.parse(
|
|
36692
|
+
raw = JSON.parse(readFileSync11(path, "utf-8"));
|
|
36198
36693
|
} catch (e) {
|
|
36199
36694
|
return errorResult(`could not read manifest at ${path}: ${e.message}`);
|
|
36200
36695
|
}
|
|
@@ -36215,7 +36710,7 @@ server.registerTool("chit_start", {
|
|
|
36215
36710
|
runs.add(run, Date.now());
|
|
36216
36711
|
return jsonResult(describeRun(run));
|
|
36217
36712
|
});
|
|
36218
|
-
server.registerTool("
|
|
36713
|
+
server.registerTool("chit_run_next", {
|
|
36219
36714
|
description: "List the steps ready to run next for a run, or report that the run is complete.",
|
|
36220
36715
|
inputSchema: { run_id: exports_external.string() }
|
|
36221
36716
|
}, async ({ run_id }) => {
|
|
@@ -36274,7 +36769,7 @@ server.registerTool("chit_run_step", {
|
|
|
36274
36769
|
runs.touch(run_id, Date.now());
|
|
36275
36770
|
}
|
|
36276
36771
|
});
|
|
36277
|
-
server.registerTool("
|
|
36772
|
+
server.registerTool("chit_run_cancel", {
|
|
36278
36773
|
description: "Cancel a step that is currently running: aborts its controller, which kills the agent's child process and settles the step as cancelled (terminal, blocks dependents). Returns cancelled:true if it stopped a running step, or a reason (already_done | not_running) otherwise. Use after interrupting a long step.",
|
|
36279
36774
|
inputSchema: { run_id: exports_external.string(), step_id: exports_external.string() }
|
|
36280
36775
|
}, async ({ run_id, step_id }) => {
|
|
@@ -36291,7 +36786,7 @@ server.registerTool("chit_cancel", {
|
|
|
36291
36786
|
...describeRun(run)
|
|
36292
36787
|
});
|
|
36293
36788
|
});
|
|
36294
|
-
server.registerTool("
|
|
36789
|
+
server.registerTool("chit_run_trace", {
|
|
36295
36790
|
description: "Return the transcript of a run so far: each step's status, participant, agent, elapsed, and output.",
|
|
36296
36791
|
inputSchema: { run_id: exports_external.string() }
|
|
36297
36792
|
}, async ({ run_id }) => {
|
|
@@ -36318,38 +36813,8 @@ server.registerTool("chit_trace", {
|
|
|
36318
36813
|
trace
|
|
36319
36814
|
});
|
|
36320
36815
|
});
|
|
36321
|
-
function prepareConvergeExecute(raw, scope, cwd, allowUnenforced) {
|
|
36322
|
-
let manifest;
|
|
36323
|
-
try {
|
|
36324
|
-
manifest = parseManifest(raw);
|
|
36325
|
-
} catch (e) {
|
|
36326
|
-
return { ok: false, error: e.message };
|
|
36327
|
-
}
|
|
36328
|
-
const shapeError = validateConvergeManifest(manifest);
|
|
36329
|
-
if (shapeError)
|
|
36330
|
-
return { ok: false, error: shapeError };
|
|
36331
|
-
const registry3 = getRegistry();
|
|
36332
|
-
const unknown3 = findUnknownAgents(manifest, registry3);
|
|
36333
|
-
if (unknown3.length > 0) {
|
|
36334
|
-
return {
|
|
36335
|
-
ok: false,
|
|
36336
|
-
error: `unknown agent(s): ${unknown3.map((u) => `${u.agentId} (participant "${u.participantId}")`).join(", ")}`
|
|
36337
|
-
};
|
|
36338
|
-
}
|
|
36339
|
-
const gaps = findEnforcementGaps(manifest, registry3);
|
|
36340
|
-
if (gaps.length > 0 && !allowUnenforced) {
|
|
36341
|
-
return {
|
|
36342
|
-
ok: false,
|
|
36343
|
-
error: `cannot enforce required permissions:
|
|
36344
|
-
${formatEnforcementGaps(gaps)}
|
|
36345
|
-
Pass allow_unenforced_permissions=true to run anyway.`
|
|
36346
|
-
};
|
|
36347
|
-
}
|
|
36348
|
-
const warnings = gaps.map((g) => `unenforced permission: participant "${g.participantId}" requires ${g.permission}`);
|
|
36349
|
-
return { ok: true, execute: buildExecute(manifest, registry3, scope, cwd), warnings };
|
|
36350
|
-
}
|
|
36351
36816
|
server.registerTool("chit_converge_start", {
|
|
36352
|
-
description: "Start an autonomous converge loop (a write-capable implementer slices the task, a read-only reviewer checks the diff) driven one iteration at a time. Returns a loop_id and the next action. Then call chit_converge_next per iteration. Records
|
|
36817
|
+
description: "Start an autonomous converge loop (a write-capable implementer slices the task, a read-only reviewer checks the diff) driven one iteration at a time. Returns a loop_id and the next action. Then call chit_converge_next per iteration. Records the loop under chit's state dir (keyed by repo), identical to `chit converge`.",
|
|
36353
36818
|
inputSchema: {
|
|
36354
36819
|
task: exports_external.string().describe("The slice to converge on"),
|
|
36355
36820
|
scope: exports_external.string().describe("Session scope id; both agents keep their thread across iterations"),
|
|
@@ -36371,19 +36836,19 @@ server.registerTool("chit_converge_start", {
|
|
|
36371
36836
|
allow_unenforced_permissions
|
|
36372
36837
|
}) => {
|
|
36373
36838
|
convergeSessions.sweep(Date.now());
|
|
36374
|
-
const runCwd =
|
|
36839
|
+
const runCwd = resolve4(cwd ?? process.cwd());
|
|
36375
36840
|
let raw;
|
|
36376
36841
|
if (manifest_path) {
|
|
36377
|
-
const path =
|
|
36842
|
+
const path = isAbsolute3(manifest_path) ? manifest_path : resolve4(runCwd, manifest_path);
|
|
36378
36843
|
try {
|
|
36379
|
-
raw = JSON.parse(
|
|
36844
|
+
raw = JSON.parse(readFileSync11(path, "utf-8"));
|
|
36380
36845
|
} catch (e) {
|
|
36381
36846
|
return errorResult(`could not read manifest at ${path}: ${e.message}`);
|
|
36382
36847
|
}
|
|
36383
36848
|
} else {
|
|
36384
36849
|
raw = DEFAULT_CONVERGE_MANIFEST;
|
|
36385
36850
|
}
|
|
36386
|
-
const prep = prepareConvergeExecute(raw, scope, runCwd, allow_unenforced_permissions);
|
|
36851
|
+
const prep = prepareConvergeExecute(raw, getRegistry(), scope, runCwd, allow_unenforced_permissions);
|
|
36387
36852
|
if (!prep.ok)
|
|
36388
36853
|
return errorResult(prep.error);
|
|
36389
36854
|
let session;
|
|
@@ -36425,6 +36890,15 @@ server.registerTool("chit_converge_next", {
|
|
|
36425
36890
|
}
|
|
36426
36891
|
server.sendLoggingMessage({ level: "info", data: message, logger: "chit" }).catch(() => {});
|
|
36427
36892
|
};
|
|
36893
|
+
let loopLock;
|
|
36894
|
+
try {
|
|
36895
|
+
loopLock = acquireLock(jobStore.loopLockPath(loop_id), { retryMs: 50, maxAttempts: 4 });
|
|
36896
|
+
} catch (e) {
|
|
36897
|
+
if (e instanceof LockError) {
|
|
36898
|
+
return errorResult(`loop "${loop_id}" is being advanced by a background job; cancel it with chit_job_cancel or wait, then retry`);
|
|
36899
|
+
}
|
|
36900
|
+
throw e;
|
|
36901
|
+
}
|
|
36428
36902
|
const iterationNo = session.iteration + 1;
|
|
36429
36903
|
heartbeat(`${loop_id} \xB7 iteration ${iterationNo} \xB7 starting`);
|
|
36430
36904
|
try {
|
|
@@ -36454,6 +36928,7 @@ server.registerTool("chit_converge_next", {
|
|
|
36454
36928
|
findingCount: result.findingCount,
|
|
36455
36929
|
checksRun: result.checksRun,
|
|
36456
36930
|
changedFiles: result.changedFiles,
|
|
36931
|
+
workspaceWarnings: result.workspaceWarnings,
|
|
36457
36932
|
...result.usage && { usage: result.usage },
|
|
36458
36933
|
...result.auditRunId && { auditRunId: result.auditRunId },
|
|
36459
36934
|
...result.stopStatus && { stopStatus: result.stopStatus },
|
|
@@ -36462,6 +36937,7 @@ server.registerTool("chit_converge_next", {
|
|
|
36462
36937
|
} catch (e) {
|
|
36463
36938
|
return errorResult(e.message);
|
|
36464
36939
|
} finally {
|
|
36940
|
+
releaseLock(loopLock);
|
|
36465
36941
|
convergeSessions.touch(loop_id, Date.now());
|
|
36466
36942
|
}
|
|
36467
36943
|
});
|
|
@@ -36520,12 +36996,197 @@ server.registerTool("chit_audit_show", {
|
|
|
36520
36996
|
}
|
|
36521
36997
|
});
|
|
36522
36998
|
server.registerTool("chit_status", {
|
|
36523
|
-
description: "Operator overview: the stepwise runs and converge loops live in THIS server right now (each loop with its status and next action), plus a compact list of recently audited runs (newest first). Read-only; answers 'what is active and what should I do next?'. Active state is per-session (a new session starts empty, and idle runs are evicted); recent state is durable. Drill into one item with chit_converge_status/
|
|
36999
|
+
description: "Operator overview: the stepwise runs and converge loops live in THIS server right now (each loop with its status and next action), plus a compact list of recently audited runs (newest first). Read-only; answers 'what is active and what should I do next?'. Active state is per-session (a new session starts empty, and idle runs are evicted); recent state is durable. Drill into one item with chit_converge_status/chit_run_trace, or chit_audit_show for a run's receipt.",
|
|
36524
37000
|
inputSchema: {
|
|
36525
37001
|
recent_limit: exports_external.number().int().min(0).default(5).describe("How many recently audited runs to include (newest first). Default 5; 0 for none.")
|
|
36526
37002
|
}
|
|
36527
37003
|
}, async ({ recent_limit }) => {
|
|
36528
|
-
return jsonResult(buildStatus(runs, convergeSessions, auditStore, recent_limit));
|
|
37004
|
+
return jsonResult(buildStatus(runs, convergeSessions, auditStore, jobStore, recent_limit, Date.now()));
|
|
37005
|
+
});
|
|
37006
|
+
function spawnJobWorker(jobId, cwd) {
|
|
37007
|
+
const child = spawn(String(process.argv[0]), [String(process.argv[1]), "job-run", jobId], {
|
|
37008
|
+
cwd,
|
|
37009
|
+
detached: true,
|
|
37010
|
+
stdio: "ignore"
|
|
37011
|
+
});
|
|
37012
|
+
child.unref();
|
|
37013
|
+
}
|
|
37014
|
+
server.registerTool("chit_converge_run", {
|
|
37015
|
+
description: "Start an autonomous converge loop as a BACKGROUND job (a detached worker advances it; you keep chatting). Returns immediately with a job_id and loop_id. Inspect with chit_job_status / chit_status, stop with chit_job_cancel. Use the foreground chit_converge_start/next instead when you want to checkpoint each iteration. v1 starts a NEW loop only: an existing loop_id is refused (use chit_converge_next to continue a foreground loop, or force=true / a new loop_id).",
|
|
37016
|
+
inputSchema: {
|
|
37017
|
+
task: exports_external.string().describe("The slice to converge on"),
|
|
37018
|
+
scope: exports_external.string().describe("Session scope id; both agents keep their thread across iterations"),
|
|
37019
|
+
cwd: exports_external.string().optional().describe("Repo to run in (defaults to the server cwd)"),
|
|
37020
|
+
manifest_path: exports_external.string().optional().describe("Converge manifest path (absolute or relative to cwd). Default: the built-in."),
|
|
37021
|
+
max_iterations: exports_external.number().int().min(1).default(3).describe("Iteration budget. Default 3."),
|
|
37022
|
+
loop_id: exports_external.string().optional().describe("Seed a loop id. Default: generated."),
|
|
37023
|
+
force: exports_external.boolean().default(false).describe("Overwrite an existing loop log at this loop_id rather than refusing."),
|
|
37024
|
+
allow_unenforced_permissions: exports_external.boolean().default(false).describe("Run even when a declared permission cannot be enforced (emits warnings).")
|
|
37025
|
+
}
|
|
37026
|
+
}, async ({
|
|
37027
|
+
task,
|
|
37028
|
+
scope,
|
|
37029
|
+
cwd,
|
|
37030
|
+
manifest_path,
|
|
37031
|
+
max_iterations,
|
|
37032
|
+
loop_id,
|
|
37033
|
+
force,
|
|
37034
|
+
allow_unenforced_permissions
|
|
37035
|
+
}) => {
|
|
37036
|
+
const runCwd = resolve4(cwd ?? process.cwd());
|
|
37037
|
+
let raw;
|
|
37038
|
+
let manifestAbs;
|
|
37039
|
+
if (manifest_path) {
|
|
37040
|
+
manifestAbs = isAbsolute3(manifest_path) ? manifest_path : resolve4(runCwd, manifest_path);
|
|
37041
|
+
try {
|
|
37042
|
+
raw = JSON.parse(readFileSync11(manifestAbs, "utf-8"));
|
|
37043
|
+
} catch (e) {
|
|
37044
|
+
return errorResult(`could not read manifest at ${manifestAbs}: ${e.message}`);
|
|
37045
|
+
}
|
|
37046
|
+
} else {
|
|
37047
|
+
raw = DEFAULT_CONVERGE_MANIFEST;
|
|
37048
|
+
}
|
|
37049
|
+
const prep = prepareConvergeExecute(raw, getRegistry(), scope, runCwd, allow_unenforced_permissions);
|
|
37050
|
+
if (!prep.ok)
|
|
37051
|
+
return errorResult(prep.error);
|
|
37052
|
+
const loopId = loop_id ?? crypto.randomUUID();
|
|
37053
|
+
try {
|
|
37054
|
+
startLoop(runCwd, { scope, task, maxIterations: max_iterations, loopId, force });
|
|
37055
|
+
} catch (e) {
|
|
37056
|
+
if (e instanceof LoopStoreError) {
|
|
37057
|
+
return errorResult(`${e.message}. Use chit_converge_next to continue a foreground loop, or start a background job with force=true or a new loop_id.`);
|
|
37058
|
+
}
|
|
37059
|
+
return errorResult(e.message);
|
|
37060
|
+
}
|
|
37061
|
+
const jobId = crypto.randomUUID();
|
|
37062
|
+
const job = {
|
|
37063
|
+
jobId,
|
|
37064
|
+
loopId,
|
|
37065
|
+
repoKey: repoKey(runCwd),
|
|
37066
|
+
cwd: runCwd,
|
|
37067
|
+
scope,
|
|
37068
|
+
task,
|
|
37069
|
+
...manifestAbs !== undefined && { manifestPath: manifestAbs },
|
|
37070
|
+
maxIterations: max_iterations,
|
|
37071
|
+
allowUnenforced: allow_unenforced_permissions,
|
|
37072
|
+
state: "queued",
|
|
37073
|
+
createdAt: new Date().toISOString(),
|
|
37074
|
+
iterationsCompleted: 0,
|
|
37075
|
+
auditRefs: []
|
|
37076
|
+
};
|
|
37077
|
+
try {
|
|
37078
|
+
jobStore.create(job);
|
|
37079
|
+
} catch (e) {
|
|
37080
|
+
stopLoop(runCwd, loopId, { status: "blocked", reason: "could not create job record" });
|
|
37081
|
+
return errorResult(e.message);
|
|
37082
|
+
}
|
|
37083
|
+
try {
|
|
37084
|
+
spawnJobWorker(jobId, runCwd);
|
|
37085
|
+
} catch (e) {
|
|
37086
|
+
jobStore.update(jobId, (c) => ({
|
|
37087
|
+
...c,
|
|
37088
|
+
state: "failed",
|
|
37089
|
+
failure: `could not spawn worker: ${e.message}`,
|
|
37090
|
+
endedAt: new Date().toISOString()
|
|
37091
|
+
}));
|
|
37092
|
+
stopLoop(runCwd, loopId, { status: "blocked", reason: "worker spawn failed" });
|
|
37093
|
+
return errorResult(`could not spawn background worker: ${e.message}`);
|
|
37094
|
+
}
|
|
37095
|
+
return jsonResult({
|
|
37096
|
+
jobId,
|
|
37097
|
+
loopId,
|
|
37098
|
+
repo: repoRoot(runCwd),
|
|
37099
|
+
state: "queued",
|
|
37100
|
+
nextAction: `running in the background; poll chit_job_status "${jobId}" (or chit_status), cancel with chit_job_cancel "${jobId}"`,
|
|
37101
|
+
...prep.warnings.length > 0 && { warnings: prep.warnings }
|
|
37102
|
+
});
|
|
37103
|
+
});
|
|
37104
|
+
function describeJob(job) {
|
|
37105
|
+
const now = Date.now();
|
|
37106
|
+
const stale = isStale(job, now);
|
|
37107
|
+
const display = stale ? "stale" : job.state;
|
|
37108
|
+
let latest;
|
|
37109
|
+
try {
|
|
37110
|
+
const iters = readLoop(job.cwd, job.loopId).filter((r) => r.type === "iteration");
|
|
37111
|
+
const last = iters.at(-1);
|
|
37112
|
+
if (last && last.type === "iteration") {
|
|
37113
|
+
latest = {
|
|
37114
|
+
iteration: last.n,
|
|
37115
|
+
changedFiles: last.changedFiles,
|
|
37116
|
+
workspaceWarnings: last.workspaceWarnings ?? [],
|
|
37117
|
+
...last.usage !== undefined && { usage: last.usage }
|
|
37118
|
+
};
|
|
37119
|
+
}
|
|
37120
|
+
} catch {}
|
|
37121
|
+
const nextAction = display === "running" ? "in progress; chit_job_cancel to stop, or wait and poll again" : display === "queued" ? "queued; the worker is starting" : display === "stale" ? "worker appears dead; inspect with chit_job_status (and chit_audit_show <auditRef> for transcripts), then start a fresh job" : `${display}${job.stopStatus ? ` (${job.stopStatus})` : ""}; open a transcript with chit_audit_show <auditRef>`;
|
|
37122
|
+
return {
|
|
37123
|
+
jobId: job.jobId,
|
|
37124
|
+
loopId: job.loopId,
|
|
37125
|
+
scope: job.scope,
|
|
37126
|
+
task: job.task,
|
|
37127
|
+
state: job.state,
|
|
37128
|
+
display,
|
|
37129
|
+
stale,
|
|
37130
|
+
alive: pidAlive(job.pid),
|
|
37131
|
+
...job.phase !== undefined && { phase: job.phase },
|
|
37132
|
+
...job.iteration !== undefined && { iteration: job.iteration },
|
|
37133
|
+
iterationsCompleted: job.iterationsCompleted,
|
|
37134
|
+
...job.lastVerdict !== undefined && { lastVerdict: job.lastVerdict },
|
|
37135
|
+
...job.stopStatus !== undefined && { stopStatus: job.stopStatus },
|
|
37136
|
+
...job.failure !== undefined && { failure: job.failure },
|
|
37137
|
+
...job.cancelRequestedAt !== undefined && { cancelRequestedAt: job.cancelRequestedAt },
|
|
37138
|
+
auditRefs: job.auditRefs,
|
|
37139
|
+
createdAt: job.createdAt,
|
|
37140
|
+
...job.startedAt !== undefined && { startedAt: job.startedAt },
|
|
37141
|
+
...job.endedAt !== undefined && { endedAt: job.endedAt },
|
|
37142
|
+
...job.lastHeartbeatAt !== undefined && { lastHeartbeatAt: job.lastHeartbeatAt },
|
|
37143
|
+
...latest !== undefined && { latest },
|
|
37144
|
+
nextAction
|
|
37145
|
+
};
|
|
37146
|
+
}
|
|
37147
|
+
server.registerTool("chit_job_status", {
|
|
37148
|
+
description: "Show one background job: state (queued/running/completed/cancelled/failed, or derived `stale` when the worker is gone), current phase, loop id, iterations, last verdict, audit refs, and the latest iteration's changed files / workspace warnings / usage. Read-only.",
|
|
37149
|
+
inputSchema: { job_id: exports_external.string() }
|
|
37150
|
+
}, async ({ job_id }) => {
|
|
37151
|
+
const job = jobStore.get(job_id);
|
|
37152
|
+
if (!job)
|
|
37153
|
+
return errorResult(`unknown job_id ${job_id}`);
|
|
37154
|
+
return jsonResult(describeJob(job));
|
|
37155
|
+
});
|
|
37156
|
+
server.registerTool("chit_job_cancel", {
|
|
37157
|
+
description: "Cancel a background job from any turn. Persists the cancel intent FIRST (so it survives a worker restart), then signals the worker's process group. A queued job is cancelled before it starts; a running job stops at the next safe point and records a clean `cancelled` stop. A job that already finished is reported back unchanged.",
|
|
37158
|
+
inputSchema: { job_id: exports_external.string() }
|
|
37159
|
+
}, async ({ job_id }) => {
|
|
37160
|
+
const job = jobStore.get(job_id);
|
|
37161
|
+
if (!job)
|
|
37162
|
+
return errorResult(`unknown job_id ${job_id}`);
|
|
37163
|
+
if (job.state !== "queued" && job.state !== "running") {
|
|
37164
|
+
return jsonResult({
|
|
37165
|
+
jobId: job_id,
|
|
37166
|
+
state: job.state,
|
|
37167
|
+
cancelled: false,
|
|
37168
|
+
note: `job already ${job.state}`
|
|
37169
|
+
});
|
|
37170
|
+
}
|
|
37171
|
+
const updated = jobStore.update(job_id, (c) => ({
|
|
37172
|
+
...c,
|
|
37173
|
+
cancelRequestedAt: new Date().toISOString(),
|
|
37174
|
+
...c.state === "running" && { phase: "cancelling" }
|
|
37175
|
+
}));
|
|
37176
|
+
let signaled = false;
|
|
37177
|
+
if (!isStale(updated, Date.now()) && updated.pgid !== undefined && pidAlive(updated.pid)) {
|
|
37178
|
+
try {
|
|
37179
|
+
process.kill(-updated.pgid, "SIGTERM");
|
|
37180
|
+
signaled = true;
|
|
37181
|
+
} catch {}
|
|
37182
|
+
}
|
|
37183
|
+
return jsonResult({
|
|
37184
|
+
jobId: job_id,
|
|
37185
|
+
state: updated.state,
|
|
37186
|
+
cancelRequested: true,
|
|
37187
|
+
signaled,
|
|
37188
|
+
note: "cancellation requested; the worker stops at the next safe point and records a clean cancelled stop"
|
|
37189
|
+
});
|
|
36529
37190
|
});
|
|
36530
37191
|
async function startMcpServer() {
|
|
36531
37192
|
await server.connect(new StdioServerTransport);
|
|
@@ -36755,9 +37416,9 @@ ${AUDIT_HELP}`);
|
|
|
36755
37416
|
}
|
|
36756
37417
|
|
|
36757
37418
|
// src/cli/doctor.ts
|
|
36758
|
-
import { randomUUID as
|
|
36759
|
-
import { mkdirSync as
|
|
36760
|
-
import { join as
|
|
37419
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
37420
|
+
import { mkdirSync as mkdirSync6, rmSync as rmSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
37421
|
+
import { join as join9 } from "path";
|
|
36761
37422
|
var defaultIO3 = {
|
|
36762
37423
|
out: (s) => process.stdout.write(s),
|
|
36763
37424
|
err: (s) => process.stderr.write(s)
|
|
@@ -36825,10 +37486,10 @@ function checkGitRepo(deps) {
|
|
|
36825
37486
|
}
|
|
36826
37487
|
function checkAuditDir(deps) {
|
|
36827
37488
|
try {
|
|
36828
|
-
|
|
36829
|
-
const probeFile =
|
|
36830
|
-
|
|
36831
|
-
|
|
37489
|
+
mkdirSync6(deps.auditDir, { recursive: true });
|
|
37490
|
+
const probeFile = join9(deps.auditDir, `.doctor-${randomUUID5()}`);
|
|
37491
|
+
writeFileSync6(probeFile, "ok");
|
|
37492
|
+
rmSync7(probeFile);
|
|
36832
37493
|
return { name: "audit dir", status: "pass", detail: `writable (${deps.auditDir})` };
|
|
36833
37494
|
} catch (e) {
|
|
36834
37495
|
return {
|
|
@@ -36946,7 +37607,7 @@ ${DOCTOR_HELP}`);
|
|
|
36946
37607
|
|
|
36947
37608
|
// src/cli/loop-log.ts
|
|
36948
37609
|
init_src();
|
|
36949
|
-
import { resolve as
|
|
37610
|
+
import { resolve as resolve5 } from "path";
|
|
36950
37611
|
var defaultIO4 = {
|
|
36951
37612
|
out: (s) => process.stdout.write(s),
|
|
36952
37613
|
err: (s) => process.stderr.write(s)
|
|
@@ -36959,12 +37620,13 @@ var ALLOWED = {
|
|
|
36959
37620
|
"loop-id",
|
|
36960
37621
|
"summary",
|
|
36961
37622
|
"changed-files",
|
|
37623
|
+
"workspace-warnings",
|
|
36962
37624
|
"checks-run",
|
|
36963
37625
|
"verdict",
|
|
36964
37626
|
"finding-count",
|
|
36965
37627
|
"decision",
|
|
36966
37628
|
"duration-ms",
|
|
36967
|
-
"
|
|
37629
|
+
"audit-ref"
|
|
36968
37630
|
],
|
|
36969
37631
|
bools: []
|
|
36970
37632
|
},
|
|
@@ -36976,7 +37638,8 @@ var LOOP_LOG_HELP = `chit loop-log <start|append|stop|show> [flags]
|
|
|
36976
37638
|
start --scope <s> --task <t> --max-iterations <n> [--cwd <dir>] [--loop-id <id>] [--force]
|
|
36977
37639
|
append --loop-id <id> --summary <t> --changed-files <json> --checks-run <t>
|
|
36978
37640
|
--verdict <proceed|revise|block> --finding-count <n>
|
|
36979
|
-
--decision <proceed|revise|block> --duration-ms <n>
|
|
37641
|
+
--decision <proceed|revise|block> --duration-ms <n>
|
|
37642
|
+
[--workspace-warnings <json>] [--audit-ref <r>] [--cwd <dir>]
|
|
36980
37643
|
stop --loop-id <id> --status <converged|blocked|max-iterations|needs-decision> --reason <t> [--cwd <dir>]
|
|
36981
37644
|
show --loop-id <id> [--json] [--cwd <dir>]
|
|
36982
37645
|
|
|
@@ -37026,15 +37689,15 @@ function intFlag(p, key, verb) {
|
|
|
37026
37689
|
}
|
|
37027
37690
|
return n;
|
|
37028
37691
|
}
|
|
37029
|
-
function
|
|
37692
|
+
function parseStringArray(raw, flag) {
|
|
37030
37693
|
let parsed;
|
|
37031
37694
|
try {
|
|
37032
37695
|
parsed = JSON.parse(raw);
|
|
37033
37696
|
} catch {
|
|
37034
|
-
throw new UsageError3(
|
|
37697
|
+
throw new UsageError3(`--${flag} must be a JSON array of strings`);
|
|
37035
37698
|
}
|
|
37036
37699
|
if (!Array.isArray(parsed) || parsed.some((e) => typeof e !== "string")) {
|
|
37037
|
-
throw new UsageError3(
|
|
37700
|
+
throw new UsageError3(`--${flag} must be a JSON array of strings`);
|
|
37038
37701
|
}
|
|
37039
37702
|
return parsed;
|
|
37040
37703
|
}
|
|
@@ -37053,6 +37716,11 @@ function renderLoop(records) {
|
|
|
37053
37716
|
lines.push(` ${r.n}. ${r.implementSummary}`);
|
|
37054
37717
|
lines.push(` ${r.changedFiles.length} files \xB7 check ${r.verdict.toUpperCase()} \xB7 ` + `${Math.round(r.checkDurationMs / 1000)}s \xB7 ${r.findingCount} findings`);
|
|
37055
37718
|
lines.push(` decide ${r.decision}`);
|
|
37719
|
+
if (r.workspaceWarnings && r.workspaceWarnings.length > 0) {
|
|
37720
|
+
lines.push(` workspace: ${r.workspaceWarnings.length} warning(s)`);
|
|
37721
|
+
for (const w of r.workspaceWarnings)
|
|
37722
|
+
lines.push(` - ${w}`);
|
|
37723
|
+
}
|
|
37056
37724
|
} else if (r.type === "stop") {
|
|
37057
37725
|
lines.push("");
|
|
37058
37726
|
lines.push(` stopped: ${r.status} (${r.reason}) \xB7 ${r.iterations} iters \xB7 ` + `${Math.round(r.totalElapsedMs / 1000)}s`);
|
|
@@ -37070,7 +37738,7 @@ function runLoopLog(argv, io = defaultIO4) {
|
|
|
37070
37738
|
}
|
|
37071
37739
|
try {
|
|
37072
37740
|
const p = parseFlags(verb, argv.slice(1));
|
|
37073
|
-
const cwd =
|
|
37741
|
+
const cwd = resolve5(p.flags.cwd ?? ".");
|
|
37074
37742
|
if (verb === "start") {
|
|
37075
37743
|
const res = startLoop(cwd, {
|
|
37076
37744
|
scope: req(p, "scope", verb),
|
|
@@ -37084,15 +37752,19 @@ function runLoopLog(argv, io = defaultIO4) {
|
|
|
37084
37752
|
return 0;
|
|
37085
37753
|
}
|
|
37086
37754
|
if (verb === "append") {
|
|
37755
|
+
const warningsRaw = p.flags["workspace-warnings"];
|
|
37087
37756
|
const res = appendIteration(cwd, req(p, "loop-id", verb), {
|
|
37088
37757
|
implementSummary: req(p, "summary", verb),
|
|
37089
|
-
changedFiles:
|
|
37758
|
+
changedFiles: parseStringArray(req(p, "changed-files", verb), "changed-files"),
|
|
37759
|
+
...warningsRaw !== undefined && {
|
|
37760
|
+
workspaceWarnings: parseStringArray(warningsRaw, "workspace-warnings")
|
|
37761
|
+
},
|
|
37090
37762
|
checksRun: req(p, "checks-run", verb),
|
|
37091
37763
|
verdict: req(p, "verdict", verb),
|
|
37092
37764
|
findingCount: intFlag(p, "finding-count", verb),
|
|
37093
37765
|
decision: req(p, "decision", verb),
|
|
37094
37766
|
checkDurationMs: intFlag(p, "duration-ms", verb),
|
|
37095
|
-
|
|
37767
|
+
auditRef: p.flags["audit-ref"]
|
|
37096
37768
|
});
|
|
37097
37769
|
io.out(`${JSON.stringify(res)}
|
|
37098
37770
|
`);
|
|
@@ -37439,6 +38111,16 @@ async function runMain(argv) {
|
|
|
37439
38111
|
return runAudit(argv.slice(1));
|
|
37440
38112
|
if (argv[0] === "doctor")
|
|
37441
38113
|
return runDoctor(argv.slice(1));
|
|
38114
|
+
if (argv[0] === "job-run") {
|
|
38115
|
+
const jobId = argv[1];
|
|
38116
|
+
if (!jobId) {
|
|
38117
|
+
process.stderr.write(`chit job-run: requires a <jobId>
|
|
38118
|
+
`);
|
|
38119
|
+
return 2;
|
|
38120
|
+
}
|
|
38121
|
+
await runJobWorker(jobId, { jobStore: new JobStore });
|
|
38122
|
+
return 0;
|
|
38123
|
+
}
|
|
37442
38124
|
let args;
|
|
37443
38125
|
try {
|
|
37444
38126
|
args = parseArgs(argv);
|
|
@@ -37479,7 +38161,7 @@ ${HELP}`);
|
|
|
37479
38161
|
}
|
|
37480
38162
|
let manifestRaw;
|
|
37481
38163
|
try {
|
|
37482
|
-
manifestRaw = JSON.parse(
|
|
38164
|
+
manifestRaw = JSON.parse(readFileSync16(args.manifestPath, "utf-8"));
|
|
37483
38165
|
} catch (e) {
|
|
37484
38166
|
process.stderr.write(`chit: failed to read manifest ${args.manifestPath}: ${e.message}
|
|
37485
38167
|
`);
|
|
@@ -37690,7 +38372,7 @@ ${HELP}`);
|
|
|
37690
38372
|
`);
|
|
37691
38373
|
return 2;
|
|
37692
38374
|
}
|
|
37693
|
-
const outputDir = args.outputDir ??
|
|
38375
|
+
const outputDir = args.outputDir ?? join14(homedir8(), ".claude", "skills");
|
|
37694
38376
|
const runtimePath = args.runtimePath ?? defaultRuntimePath();
|
|
37695
38377
|
try {
|
|
37696
38378
|
const result = installClaudeSkill({
|
|
@@ -37733,7 +38415,7 @@ ${HELP}`);
|
|
|
37733
38415
|
}
|
|
37734
38416
|
let raw2;
|
|
37735
38417
|
try {
|
|
37736
|
-
raw2 = JSON.parse(
|
|
38418
|
+
raw2 = JSON.parse(readFileSync16(args.manifestPath, "utf-8"));
|
|
37737
38419
|
} catch (e) {
|
|
37738
38420
|
process.stderr.write(`chit: failed to read manifest ${args.manifestPath}: ${e.message}
|
|
37739
38421
|
`);
|
|
@@ -37857,7 +38539,8 @@ async function runStudio(args) {
|
|
|
37857
38539
|
cwd: process.cwd(),
|
|
37858
38540
|
explicitPath: args.manifestPath,
|
|
37859
38541
|
registry: registry3,
|
|
37860
|
-
lifecycle: buildStudioLifecycle()
|
|
38542
|
+
lifecycle: buildStudioLifecycle(),
|
|
38543
|
+
loopsDir: loopLogDir(process.cwd())
|
|
37861
38544
|
});
|
|
37862
38545
|
} catch (e) {
|
|
37863
38546
|
if (e instanceof PathError2) {
|
|
@@ -37871,13 +38554,13 @@ async function runStudio(args) {
|
|
|
37871
38554
|
`);
|
|
37872
38555
|
process.stdout.write(`Press Ctrl-C to stop.
|
|
37873
38556
|
`);
|
|
37874
|
-
await new Promise((
|
|
38557
|
+
await new Promise((resolve7) => {
|
|
37875
38558
|
process.on("SIGINT", () => {
|
|
37876
38559
|
process.stdout.write(`
|
|
37877
38560
|
chit studio: stopped
|
|
37878
38561
|
`);
|
|
37879
38562
|
handle.stop();
|
|
37880
|
-
|
|
38563
|
+
resolve7();
|
|
37881
38564
|
});
|
|
37882
38565
|
});
|
|
37883
38566
|
return 0;
|