@chit-run/cli 0.6.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 +1935 -1345
- package/package.json +1 -1
package/dist/chit.js
CHANGED
|
@@ -4679,7 +4679,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
4679
4679
|
const schOrFunc = root.refs[ref];
|
|
4680
4680
|
if (schOrFunc)
|
|
4681
4681
|
return schOrFunc;
|
|
4682
|
-
let _sch =
|
|
4682
|
+
let _sch = resolve4.call(this, root, ref);
|
|
4683
4683
|
if (_sch === undefined) {
|
|
4684
4684
|
const schema = (_a3 = root.localRefs) === null || _a3 === undefined ? undefined : _a3[ref];
|
|
4685
4685
|
const { schemaId } = this.opts;
|
|
@@ -4706,7 +4706,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
4706
4706
|
function sameSchemaEnv(s1, s2) {
|
|
4707
4707
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
4708
4708
|
}
|
|
4709
|
-
function
|
|
4709
|
+
function resolve4(root, ref) {
|
|
4710
4710
|
let sch;
|
|
4711
4711
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
4712
4712
|
ref = sch;
|
|
@@ -5292,7 +5292,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5292
5292
|
}
|
|
5293
5293
|
return uri;
|
|
5294
5294
|
}
|
|
5295
|
-
function
|
|
5295
|
+
function resolve4(baseURI, relativeURI, options) {
|
|
5296
5296
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
5297
5297
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
5298
5298
|
schemelessOptions.skipEscape = true;
|
|
@@ -5551,7 +5551,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5551
5551
|
var fastUri = {
|
|
5552
5552
|
SCHEMES,
|
|
5553
5553
|
normalize,
|
|
5554
|
-
resolve:
|
|
5554
|
+
resolve: resolve4,
|
|
5555
5555
|
resolveComponent,
|
|
5556
5556
|
equal,
|
|
5557
5557
|
serialize,
|
|
@@ -9967,12 +9967,12 @@ var init_dist = __esm(() => {
|
|
|
9967
9967
|
});
|
|
9968
9968
|
|
|
9969
9969
|
// ../studio/src/server/audit.ts
|
|
9970
|
-
import { existsSync as
|
|
9971
|
-
import { homedir as
|
|
9972
|
-
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";
|
|
9973
9973
|
function defaultAuditDir2() {
|
|
9974
|
-
const xdg = process.env.XDG_STATE_HOME ||
|
|
9975
|
-
return
|
|
9974
|
+
const xdg = process.env.XDG_STATE_HOME || join10(homedir7(), ".local", "state");
|
|
9975
|
+
return join10(xdg, "chit", "audit");
|
|
9976
9976
|
}
|
|
9977
9977
|
function blobRefs(e) {
|
|
9978
9978
|
switch (e.type) {
|
|
@@ -9991,13 +9991,13 @@ function blobRefs(e) {
|
|
|
9991
9991
|
function readAuditRun(auditDir, runId, includeBlobs) {
|
|
9992
9992
|
if (!SAFE_RUN_ID2.test(runId))
|
|
9993
9993
|
return { kind: "invalid-id" };
|
|
9994
|
-
const runDir =
|
|
9995
|
-
const eventsPath =
|
|
9996
|
-
if (!
|
|
9994
|
+
const runDir = join10(auditDir, "runs", runId);
|
|
9995
|
+
const eventsPath = join10(runDir, "events.jsonl");
|
|
9996
|
+
if (!existsSync9(eventsPath))
|
|
9997
9997
|
return { kind: "not-found" };
|
|
9998
9998
|
let events2;
|
|
9999
9999
|
try {
|
|
10000
|
-
events2 = parseAuditLog(
|
|
10000
|
+
events2 = parseAuditLog(readFileSync12(eventsPath, "utf-8"));
|
|
10001
10001
|
} catch (e) {
|
|
10002
10002
|
if (e instanceof AuditEventError)
|
|
10003
10003
|
return { kind: "invalid-log", message: e.message };
|
|
@@ -10005,15 +10005,15 @@ function readAuditRun(auditDir, runId, includeBlobs) {
|
|
|
10005
10005
|
}
|
|
10006
10006
|
if (!includeBlobs)
|
|
10007
10007
|
return { kind: "ok", events: events2 };
|
|
10008
|
-
const blobsDir =
|
|
10008
|
+
const blobsDir = join10(runDir, "blobs");
|
|
10009
10009
|
const blobs = {};
|
|
10010
10010
|
for (const e of events2) {
|
|
10011
10011
|
for (const ref of blobRefs(e)) {
|
|
10012
10012
|
if (!SHA256_HEX2.test(ref) || ref in blobs)
|
|
10013
10013
|
continue;
|
|
10014
|
-
const blobPath =
|
|
10015
|
-
if (
|
|
10016
|
-
blobs[ref] =
|
|
10014
|
+
const blobPath = join10(blobsDir, ref);
|
|
10015
|
+
if (existsSync9(blobPath))
|
|
10016
|
+
blobs[ref] = readFileSync12(blobPath, "utf-8");
|
|
10017
10017
|
}
|
|
10018
10018
|
}
|
|
10019
10019
|
return { kind: "ok", events: events2, blobs };
|
|
@@ -10071,12 +10071,12 @@ var init_auth = __esm(() => {
|
|
|
10071
10071
|
});
|
|
10072
10072
|
|
|
10073
10073
|
// ../studio/src/server/paths.ts
|
|
10074
|
-
import { existsSync as
|
|
10075
|
-
import { isAbsolute as
|
|
10074
|
+
import { existsSync as existsSync10, statSync as statSync3 } from "fs";
|
|
10075
|
+
import { isAbsolute as isAbsolute4, resolve as resolve6 } from "path";
|
|
10076
10076
|
function resolveExplicitPath(userPath, cwd) {
|
|
10077
|
-
const candidate =
|
|
10078
|
-
const canonical =
|
|
10079
|
-
if (!
|
|
10077
|
+
const candidate = isAbsolute4(userPath) ? userPath : resolve6(cwd, userPath);
|
|
10078
|
+
const canonical = resolve6(candidate);
|
|
10079
|
+
if (!existsSync10(canonical)) {
|
|
10080
10080
|
throw new PathError("not-found", `path "${userPath}" does not exist`);
|
|
10081
10081
|
}
|
|
10082
10082
|
if (!statSync3(canonical).isFile()) {
|
|
@@ -10097,11 +10097,11 @@ var init_paths = __esm(() => {
|
|
|
10097
10097
|
});
|
|
10098
10098
|
|
|
10099
10099
|
// ../studio/src/server/discovery.ts
|
|
10100
|
-
import { readdirSync as
|
|
10101
|
-
import { basename, join as
|
|
10100
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync13 } from "fs";
|
|
10101
|
+
import { basename, join as join11, relative } from "path";
|
|
10102
10102
|
function safeParseChit(absolutePath) {
|
|
10103
10103
|
try {
|
|
10104
|
-
const raw2 = JSON.parse(
|
|
10104
|
+
const raw2 = JSON.parse(readFileSync13(absolutePath, "utf-8"));
|
|
10105
10105
|
parseManifest(raw2);
|
|
10106
10106
|
return true;
|
|
10107
10107
|
} catch {
|
|
@@ -10121,14 +10121,14 @@ function discover(opts) {
|
|
|
10121
10121
|
relPath: relPathFromCwd(absolutePath, opts.cwd)
|
|
10122
10122
|
};
|
|
10123
10123
|
}
|
|
10124
|
-
const entries =
|
|
10124
|
+
const entries = readdirSync4(opts.cwd, { withFileTypes: true });
|
|
10125
10125
|
const candidates = [];
|
|
10126
10126
|
for (const entry of entries) {
|
|
10127
10127
|
if (!entry.isFile())
|
|
10128
10128
|
continue;
|
|
10129
10129
|
if (!entry.name.endsWith(".json"))
|
|
10130
10130
|
continue;
|
|
10131
|
-
const absolutePath =
|
|
10131
|
+
const absolutePath = join11(opts.cwd, entry.name);
|
|
10132
10132
|
if (!safeParseChit(absolutePath))
|
|
10133
10133
|
continue;
|
|
10134
10134
|
candidates.push({
|
|
@@ -10154,7 +10154,7 @@ var init_discovery = __esm(() => {
|
|
|
10154
10154
|
|
|
10155
10155
|
// ../studio/src/server/docs.ts
|
|
10156
10156
|
import { createHash as createHash6 } from "crypto";
|
|
10157
|
-
import { readFileSync as
|
|
10157
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
10158
10158
|
import { basename as basename2, relative as relative2 } from "path";
|
|
10159
10159
|
function canonicalize(draft) {
|
|
10160
10160
|
return JSON.stringify(draft, null, "\t");
|
|
@@ -10187,7 +10187,7 @@ class DocStore {
|
|
|
10187
10187
|
if (!entry)
|
|
10188
10188
|
return null;
|
|
10189
10189
|
try {
|
|
10190
|
-
return hashRaw(
|
|
10190
|
+
return hashRaw(readFileSync14(entry.absolutePath, "utf-8"));
|
|
10191
10191
|
} catch {
|
|
10192
10192
|
return null;
|
|
10193
10193
|
}
|
|
@@ -10229,7 +10229,7 @@ class DocStore {
|
|
|
10229
10229
|
return null;
|
|
10230
10230
|
let raw2;
|
|
10231
10231
|
try {
|
|
10232
|
-
raw2 =
|
|
10232
|
+
raw2 = readFileSync14(entry.absolutePath, "utf-8");
|
|
10233
10233
|
} catch (e) {
|
|
10234
10234
|
const errorDoc = {
|
|
10235
10235
|
id: docId,
|
|
@@ -10285,7 +10285,7 @@ class DocStore {
|
|
|
10285
10285
|
return { kind: "not-found" };
|
|
10286
10286
|
let currentRaw;
|
|
10287
10287
|
try {
|
|
10288
|
-
currentRaw =
|
|
10288
|
+
currentRaw = readFileSync14(entry.absolutePath, "utf-8");
|
|
10289
10289
|
} catch {
|
|
10290
10290
|
return { kind: "not-found" };
|
|
10291
10291
|
}
|
|
@@ -10297,7 +10297,7 @@ class DocStore {
|
|
|
10297
10297
|
const manifest = parseManifest(draft);
|
|
10298
10298
|
const graphModel = buildGraphModel(manifest, this.registry, surface);
|
|
10299
10299
|
const canonicalRaw = canonicalize(draft);
|
|
10300
|
-
|
|
10300
|
+
writeFileSync7(entry.absolutePath, canonicalRaw, "utf-8");
|
|
10301
10301
|
const newHash = hashRaw(canonicalRaw);
|
|
10302
10302
|
return {
|
|
10303
10303
|
kind: "saved",
|
|
@@ -10366,8 +10366,8 @@ var init_docs = __esm(() => {
|
|
|
10366
10366
|
});
|
|
10367
10367
|
|
|
10368
10368
|
// ../studio/src/server/loops.ts
|
|
10369
|
-
import { existsSync as
|
|
10370
|
-
import { join as
|
|
10369
|
+
import { existsSync as existsSync11, readdirSync as readdirSync5, readFileSync as readFileSync15 } from "fs";
|
|
10370
|
+
import { join as join12 } from "path";
|
|
10371
10371
|
function summarize(loopId, records) {
|
|
10372
10372
|
const header = records[0];
|
|
10373
10373
|
if (header?.type !== "loop")
|
|
@@ -10385,11 +10385,11 @@ function summarize(loopId, records) {
|
|
|
10385
10385
|
};
|
|
10386
10386
|
}
|
|
10387
10387
|
function readLoopFrom(dir, loopId) {
|
|
10388
|
-
const path =
|
|
10389
|
-
if (!
|
|
10388
|
+
const path = join12(dir, `${loopId}.jsonl`);
|
|
10389
|
+
if (!existsSync11(path))
|
|
10390
10390
|
return { kind: "not-found" };
|
|
10391
10391
|
try {
|
|
10392
|
-
const records = validateLoopLog(parseLoopLog(
|
|
10392
|
+
const records = validateLoopLog(parseLoopLog(readFileSync15(path, "utf-8")));
|
|
10393
10393
|
const header = records[0];
|
|
10394
10394
|
if (header?.type !== "loop" || header.loopId !== loopId) {
|
|
10395
10395
|
return { kind: "invalid-log", message: "header loopId does not match the file name" };
|
|
@@ -10402,10 +10402,10 @@ function readLoopFrom(dir, loopId) {
|
|
|
10402
10402
|
}
|
|
10403
10403
|
}
|
|
10404
10404
|
function listLoops(loopsDir) {
|
|
10405
|
-
if (!loopsDir || !
|
|
10405
|
+
if (!loopsDir || !existsSync11(loopsDir))
|
|
10406
10406
|
return [];
|
|
10407
10407
|
const summaries = [];
|
|
10408
|
-
for (const name of
|
|
10408
|
+
for (const name of readdirSync5(loopsDir)) {
|
|
10409
10409
|
if (!name.endsWith(".jsonl"))
|
|
10410
10410
|
continue;
|
|
10411
10411
|
const loopId = name.slice(0, -".jsonl".length);
|
|
@@ -10462,8 +10462,8 @@ __export(exports_server, {
|
|
|
10462
10462
|
buildApp: () => buildApp,
|
|
10463
10463
|
PathError: () => PathError
|
|
10464
10464
|
});
|
|
10465
|
-
import { existsSync as
|
|
10466
|
-
import { join as
|
|
10465
|
+
import { existsSync as existsSync12 } from "fs";
|
|
10466
|
+
import { join as join13 } from "path";
|
|
10467
10467
|
async function startStudio(opts) {
|
|
10468
10468
|
const hostname3 = opts.hostname ?? "127.0.0.1";
|
|
10469
10469
|
const requestedPort = opts.port ?? 0;
|
|
@@ -10516,8 +10516,8 @@ function buildApp(opts) {
|
|
|
10516
10516
|
const asset = c.req.param("asset");
|
|
10517
10517
|
if (!CLIENT_ASSETS.has(asset))
|
|
10518
10518
|
return c.text("not found", 404);
|
|
10519
|
-
const path =
|
|
10520
|
-
if (!
|
|
10519
|
+
const path = join13(opts.clientDistDir, asset);
|
|
10520
|
+
if (!existsSync12(path)) {
|
|
10521
10521
|
return c.text(`client bundle missing at ${path}. Run: bun run studio:build`, 503);
|
|
10522
10522
|
}
|
|
10523
10523
|
return new Response(Bun.file(path));
|
|
@@ -10692,15 +10692,15 @@ var init_server = __esm(() => {
|
|
|
10692
10692
|
init_loops();
|
|
10693
10693
|
init_token();
|
|
10694
10694
|
init_paths();
|
|
10695
|
-
CLIENT_DIST =
|
|
10695
|
+
CLIENT_DIST = join13(import.meta.dir, "..", "..", "dist", "client");
|
|
10696
10696
|
CLIENT_ASSETS = new Set(["index.js", "index.css"]);
|
|
10697
10697
|
});
|
|
10698
10698
|
|
|
10699
10699
|
// src/cli/run.ts
|
|
10700
10700
|
init_src();
|
|
10701
|
-
import { readFileSync as
|
|
10702
|
-
import { homedir as
|
|
10703
|
-
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";
|
|
10704
10704
|
|
|
10705
10705
|
// src/adapters/sanitize.ts
|
|
10706
10706
|
var SENSITIVE_KEY = /key|token|secret|password|auth/i;
|
|
@@ -11604,12 +11604,172 @@ function wrapAdaptersWithAudit(adapters, recorder) {
|
|
|
11604
11604
|
return out;
|
|
11605
11605
|
}
|
|
11606
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
|
+
|
|
11607
11767
|
// src/loops/location.ts
|
|
11608
11768
|
import { spawnSync } from "child_process";
|
|
11609
11769
|
import { createHash as createHash2 } from "crypto";
|
|
11610
11770
|
import { realpathSync } from "fs";
|
|
11611
|
-
import { homedir as
|
|
11612
|
-
import { join as
|
|
11771
|
+
import { homedir as homedir4 } from "os";
|
|
11772
|
+
import { join as join4 } from "path";
|
|
11613
11773
|
function gitTopLevel(cwd) {
|
|
11614
11774
|
try {
|
|
11615
11775
|
const out = spawnSync("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
|
|
@@ -11636,15 +11796,125 @@ function repoKey(cwd) {
|
|
|
11636
11796
|
return createHash2("sha256").update(repoRoot(cwd)).digest("hex").slice(0, 16);
|
|
11637
11797
|
}
|
|
11638
11798
|
function loopStateDir() {
|
|
11639
|
-
const xdg = process.env.XDG_STATE_HOME ||
|
|
11640
|
-
return
|
|
11799
|
+
const xdg = process.env.XDG_STATE_HOME || join4(homedir4(), ".local", "state");
|
|
11800
|
+
return join4(xdg, "chit", "loops");
|
|
11641
11801
|
}
|
|
11642
11802
|
function loopLogDir(cwd) {
|
|
11643
|
-
return
|
|
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);
|
|
11644
11914
|
}
|
|
11645
11915
|
|
|
11646
11916
|
// src/runtime/render.ts
|
|
11647
|
-
import { existsSync as
|
|
11917
|
+
import { existsSync as existsSync5 } from "fs";
|
|
11648
11918
|
import { isAbsolute, resolve } from "path";
|
|
11649
11919
|
|
|
11650
11920
|
class RuntimeError extends Error {
|
|
@@ -11692,7 +11962,7 @@ function renderFilePaths(paths, invocationCwd, inputName) {
|
|
|
11692
11962
|
const resolved = [];
|
|
11693
11963
|
for (const p of paths) {
|
|
11694
11964
|
const abs = isAbsolute(p) ? p : resolve(invocationCwd, p);
|
|
11695
|
-
if (!
|
|
11965
|
+
if (!existsSync5(abs)) {
|
|
11696
11966
|
throw new RuntimeError(`input "${inputName}" references missing file: ${p}`);
|
|
11697
11967
|
}
|
|
11698
11968
|
resolved.push(abs);
|
|
@@ -11933,20 +12203,20 @@ function buildSessionAdapter(inner, manifestId, scope, trackedFingerprints, stor
|
|
|
11933
12203
|
}
|
|
11934
12204
|
|
|
11935
12205
|
// src/sessions/store.ts
|
|
11936
|
-
import { createHash as createHash4, randomUUID as
|
|
12206
|
+
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
11937
12207
|
import {
|
|
11938
|
-
closeSync,
|
|
11939
|
-
existsSync as
|
|
11940
|
-
mkdirSync as
|
|
11941
|
-
openSync,
|
|
11942
|
-
readFileSync as
|
|
11943
|
-
renameSync as
|
|
11944
|
-
rmSync as
|
|
11945
|
-
writeFileSync as
|
|
11946
|
-
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
|
|
11947
12217
|
} from "fs";
|
|
11948
|
-
import { homedir as
|
|
11949
|
-
import { dirname, join as
|
|
12218
|
+
import { homedir as homedir5 } from "os";
|
|
12219
|
+
import { dirname, join as join6 } from "path";
|
|
11950
12220
|
function isObject4(v) {
|
|
11951
12221
|
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
11952
12222
|
}
|
|
@@ -11957,14 +12227,14 @@ function entryKey(participantId, fingerprint) {
|
|
|
11957
12227
|
return `${participantId}--${fingerprint}`;
|
|
11958
12228
|
}
|
|
11959
12229
|
var HASH_SEP = String.fromCharCode(0);
|
|
11960
|
-
function
|
|
12230
|
+
function sleepSync2(ms) {
|
|
11961
12231
|
if (ms <= 0)
|
|
11962
12232
|
return;
|
|
11963
12233
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
11964
12234
|
}
|
|
11965
12235
|
function defaultSessionDir() {
|
|
11966
|
-
const xdg = process.env.XDG_STATE_HOME ||
|
|
11967
|
-
return
|
|
12236
|
+
const xdg = process.env.XDG_STATE_HOME || join6(homedir5(), ".local", "state");
|
|
12237
|
+
return join6(xdg, "chit", "sessions");
|
|
11968
12238
|
}
|
|
11969
12239
|
|
|
11970
12240
|
class FileSessionStore {
|
|
@@ -11978,11 +12248,11 @@ class FileSessionStore {
|
|
|
11978
12248
|
}
|
|
11979
12249
|
load(key) {
|
|
11980
12250
|
const path = this.filePath(key);
|
|
11981
|
-
if (!
|
|
12251
|
+
if (!existsSync6(path))
|
|
11982
12252
|
return;
|
|
11983
12253
|
let raw;
|
|
11984
12254
|
try {
|
|
11985
|
-
raw = JSON.parse(
|
|
12255
|
+
raw = JSON.parse(readFileSync6(path, "utf-8"));
|
|
11986
12256
|
} catch {
|
|
11987
12257
|
return;
|
|
11988
12258
|
}
|
|
@@ -11992,13 +12262,13 @@ class FileSessionStore {
|
|
|
11992
12262
|
}
|
|
11993
12263
|
save(key, payload) {
|
|
11994
12264
|
const path = this.filePath(key);
|
|
11995
|
-
|
|
12265
|
+
mkdirSync4(dirname(path), { recursive: true });
|
|
11996
12266
|
const lock = this.acquireLock(path);
|
|
11997
12267
|
try {
|
|
11998
12268
|
let data = {};
|
|
11999
|
-
if (
|
|
12269
|
+
if (existsSync6(path)) {
|
|
12000
12270
|
try {
|
|
12001
|
-
const raw = JSON.parse(
|
|
12271
|
+
const raw = JSON.parse(readFileSync6(path, "utf-8"));
|
|
12002
12272
|
if (isObject4(raw))
|
|
12003
12273
|
data = raw;
|
|
12004
12274
|
} catch {
|
|
@@ -12006,13 +12276,13 @@ class FileSessionStore {
|
|
|
12006
12276
|
}
|
|
12007
12277
|
}
|
|
12008
12278
|
data[entryKey(key.participantId, key.fingerprint)] = payload;
|
|
12009
|
-
const tmpPath = `${path}.${
|
|
12010
|
-
|
|
12279
|
+
const tmpPath = `${path}.${randomUUID4()}.tmp`;
|
|
12280
|
+
writeFileSync4(tmpPath, JSON.stringify(data, null, 2));
|
|
12011
12281
|
try {
|
|
12012
12282
|
this.assertOwned(lock);
|
|
12013
|
-
|
|
12283
|
+
renameSync3(tmpPath, path);
|
|
12014
12284
|
} catch (err) {
|
|
12015
|
-
|
|
12285
|
+
rmSync4(tmpPath, { force: true });
|
|
12016
12286
|
throw err;
|
|
12017
12287
|
}
|
|
12018
12288
|
} finally {
|
|
@@ -12021,20 +12291,20 @@ class FileSessionStore {
|
|
|
12021
12291
|
}
|
|
12022
12292
|
acquireLock(path) {
|
|
12023
12293
|
const lockPath = `${path}.lock`;
|
|
12024
|
-
const token =
|
|
12294
|
+
const token = randomUUID4();
|
|
12025
12295
|
for (let attempt = 0;attempt < this.lockMaxAttempts; attempt++) {
|
|
12026
12296
|
try {
|
|
12027
|
-
const fd =
|
|
12297
|
+
const fd = openSync2(lockPath, "wx");
|
|
12028
12298
|
try {
|
|
12029
|
-
|
|
12299
|
+
writeSync2(fd, token);
|
|
12030
12300
|
} finally {
|
|
12031
|
-
|
|
12301
|
+
closeSync2(fd);
|
|
12032
12302
|
}
|
|
12033
12303
|
return { path: lockPath, token };
|
|
12034
12304
|
} catch (err) {
|
|
12035
12305
|
if (err.code !== "EEXIST")
|
|
12036
12306
|
throw err;
|
|
12037
|
-
|
|
12307
|
+
sleepSync2(this.lockRetryMs);
|
|
12038
12308
|
}
|
|
12039
12309
|
}
|
|
12040
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)}`);
|
|
@@ -12042,7 +12312,7 @@ class FileSessionStore {
|
|
|
12042
12312
|
assertOwned(lock) {
|
|
12043
12313
|
let current;
|
|
12044
12314
|
try {
|
|
12045
|
-
current =
|
|
12315
|
+
current = readFileSync6(lock.path, "utf-8");
|
|
12046
12316
|
} catch {
|
|
12047
12317
|
current = undefined;
|
|
12048
12318
|
}
|
|
@@ -12052,265 +12322,999 @@ class FileSessionStore {
|
|
|
12052
12322
|
}
|
|
12053
12323
|
releaseLock(lock) {
|
|
12054
12324
|
try {
|
|
12055
|
-
if (
|
|
12056
|
-
|
|
12325
|
+
if (readFileSync6(lock.path, "utf-8") === lock.token) {
|
|
12326
|
+
rmSync4(lock.path, { force: true });
|
|
12057
12327
|
}
|
|
12058
12328
|
} catch {}
|
|
12059
12329
|
}
|
|
12060
12330
|
filePath(key) {
|
|
12061
12331
|
const readable = `${safeSegment(key.scope)}--${safeSegment(key.manifestId)}`;
|
|
12062
12332
|
const hash = createHash4("sha256").update(`${key.scope}${HASH_SEP}${key.manifestId}`).digest("hex").slice(0, 12);
|
|
12063
|
-
return
|
|
12333
|
+
return join6(this.baseDir, `${readable}--${hash}.json`);
|
|
12064
12334
|
}
|
|
12065
12335
|
}
|
|
12066
12336
|
|
|
12067
|
-
// src/
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
}
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
} catch (e) {
|
|
12099
|
-
throw new SurfaceInstallError(`invalid manifest at ${opts.manifestPath}: ${e.message}`);
|
|
12100
|
-
}
|
|
12101
|
-
const missingCaps = findMissingCapabilities(manifest, CLAUDE_SKILL_CAPABILITIES);
|
|
12102
|
-
if (missingCaps.length > 0) {
|
|
12103
|
-
throw new SurfaceInstallError(`claude-skill surface does not provide capabilities required by "${manifest.id}": ${missingCaps.join(", ")}`);
|
|
12104
|
-
}
|
|
12105
|
-
const inputNames = Object.keys(manifest.inputs);
|
|
12106
|
-
const primaryInput = inputNames[0];
|
|
12107
|
-
if (!primaryInput) {
|
|
12108
|
-
throw new SurfaceInstallError(`manifest "${manifest.id}" has no inputs; nothing to wire from $ARGUMENTS`);
|
|
12109
|
-
}
|
|
12110
|
-
const primaryInputSchema = manifest.inputs[primaryInput];
|
|
12111
|
-
if (primaryInputSchema?.type !== "string") {
|
|
12112
|
-
throw new SurfaceInstallError(`manifest "${manifest.id}": claude-skill surface only supports a string-typed primary input ` + `(got "${primaryInput}": ${primaryInputSchema?.type ?? "missing"})`);
|
|
12113
|
-
}
|
|
12114
|
-
if (inputNames.length > 1) {
|
|
12115
|
-
throw new SurfaceInstallError(`manifest "${manifest.id}" declares multiple inputs (${inputNames.join(", ")}); ` + `claude-skill surface in PR6 supports exactly one string input`);
|
|
12116
|
-
}
|
|
12117
|
-
const registry2 = opts.registry ?? loadRegistry();
|
|
12118
|
-
const unknownAgents = findUnknownAgents(manifest, registry2);
|
|
12119
|
-
if (unknownAgents.length > 0) {
|
|
12120
|
-
const lines = unknownAgents.map((u) => ` - participant "${u.participantId}" references unknown agent "${u.agentId}"`).join(`
|
|
12121
|
-
`);
|
|
12122
|
-
throw new SurfaceInstallError(`manifest "${manifest.id}" references agents that are not in the registry:
|
|
12123
|
-
${lines}`);
|
|
12124
|
-
}
|
|
12125
|
-
const gaps = findEnforcementGaps(manifest, registry2);
|
|
12126
|
-
if (gaps.length > 0 && !allowUnenforced) {
|
|
12127
|
-
throw new SurfaceInstallError(`cannot enforce required permissions for "${manifest.id}":
|
|
12128
|
-
${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 }}
|
|
12129
12368
|
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
const installName = opts.overrideName ?? manifest.id;
|
|
12133
|
-
const skillDir = join5(outputDir, installName);
|
|
12134
|
-
if (existsSync5(skillDir)) {
|
|
12135
|
-
if (!opts.force) {
|
|
12136
|
-
throw new SurfaceInstallError(`skill directory already exists: ${skillDir}
|
|
12369
|
+
Prior review to address (empty on the first iteration):
|
|
12370
|
+
{{ inputs.prior_review }}
|
|
12137
12371
|
|
|
12138
|
-
|
|
12139
|
-
}
|
|
12140
|
-
|
|
12141
|
-
|
|
12142
|
-
|
|
12143
|
-
|
|
12144
|
-
const manifestPath = join5(skillDir, "manifest.json");
|
|
12145
|
-
const markerPath = join5(skillDir, INSTALL_MARKER_FILENAME);
|
|
12146
|
-
const manifestJson = `${JSON.stringify(rawJson, null, 2)}
|
|
12147
|
-
`;
|
|
12148
|
-
writeFileSync3(manifestPath, manifestJson);
|
|
12149
|
-
writeFileSync3(skillMdPath, buildSkillMd({
|
|
12150
|
-
manifest,
|
|
12151
|
-
runtimePath,
|
|
12152
|
-
primaryInputName: primaryInput,
|
|
12153
|
-
allowUnenforced: gaps.length > 0,
|
|
12154
|
-
trace: opts.trace === true,
|
|
12155
|
-
heredocDelimiter: generateHeredocDelimiter(),
|
|
12156
|
-
installName
|
|
12157
|
-
}));
|
|
12158
|
-
const marker = {
|
|
12159
|
-
schema: 1,
|
|
12160
|
-
surface: "claude-skill",
|
|
12161
|
-
installName,
|
|
12162
|
-
manifestId: manifest.id,
|
|
12163
|
-
runtimePath,
|
|
12164
|
-
installedAt: new Date().toISOString(),
|
|
12165
|
-
manifestHash: createHash5("sha256").update(manifestJson).digest("hex")
|
|
12166
|
-
};
|
|
12167
|
-
writeFileSync3(markerPath, `${JSON.stringify(marker, null, 2)}
|
|
12168
|
-
`);
|
|
12169
|
-
return { skillDir, skillMdPath, manifestPath, markerPath, enforcementGaps: gaps };
|
|
12170
|
-
}
|
|
12171
|
-
function generateHeredocDelimiter() {
|
|
12172
|
-
return `CHIT_INPUT_${randomBytes(8).toString("hex").toUpperCase()}_EOF`;
|
|
12173
|
-
}
|
|
12174
|
-
function buildSkillMd(opts) {
|
|
12175
|
-
const {
|
|
12176
|
-
manifest,
|
|
12177
|
-
runtimePath,
|
|
12178
|
-
primaryInputName,
|
|
12179
|
-
allowUnenforced,
|
|
12180
|
-
trace,
|
|
12181
|
-
heredocDelimiter,
|
|
12182
|
-
installName
|
|
12183
|
-
} = opts;
|
|
12184
|
-
const allowFlag = allowUnenforced ? `\\
|
|
12185
|
-
--allow-unenforced-permissions ` : "";
|
|
12186
|
-
const traceFlag = trace ? `\\
|
|
12187
|
-
--trace ` : "";
|
|
12188
|
-
return `---
|
|
12189
|
-
name: ${installName}
|
|
12190
|
-
description: ${escapeFrontmatter(manifest.description)}
|
|
12191
|
-
argument-hint: <${primaryInputName}>
|
|
12192
|
-
disable-model-invocation: true
|
|
12193
|
-
---
|
|
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 }}
|
|
12194
12378
|
|
|
12195
|
-
|
|
12379
|
+
Claude's summary of what it just implemented:
|
|
12380
|
+
{{ steps.implement.output }}
|
|
12196
12381
|
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
# the real session id baked in (always non-empty); when running outside,
|
|
12203
|
-
# bash evaluates the env var (typically empty, so we fail fast).
|
|
12204
|
-
if [ -z "\${CLAUDE_SESSION_ID}" ]; then
|
|
12205
|
-
echo "chit: CLAUDE_SESSION_ID is required; this skill must run inside Claude Code" >&2
|
|
12206
|
-
exit 2
|
|
12207
|
-
fi
|
|
12208
|
-
WORKTREE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
12209
|
-
SCOPE_HASH=$(printf '%s' "$WORKTREE" | (shasum -a 256 2>/dev/null || sha256sum) | cut -c1-12)
|
|
12210
|
-
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.
|
|
12211
12387
|
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
--invocation-cwd "$WORKTREE" ${allowFlag}${traceFlag}\\
|
|
12216
|
-
--input-stdin ${primaryInputName} <<'${heredocDelimiter}'
|
|
12217
|
-
$ARGUMENTS
|
|
12218
|
-
${heredocDelimiter}
|
|
12219
|
-
} 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"}
|
|
12220
12391
|
\`\`\`
|
|
12392
|
+
findingCount is the integer number of findings; checksRun is a short human string.`
|
|
12393
|
+
},
|
|
12394
|
+
out: {
|
|
12395
|
+
format: `## Converge iteration
|
|
12221
12396
|
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
}
|
|
12225
|
-
function escapeFrontmatter(s) {
|
|
12226
|
-
return s.replace(/\r?\n/g, " ").replace(/"/g, "'");
|
|
12227
|
-
}
|
|
12397
|
+
### Implementer (Claude)
|
|
12398
|
+
{{ steps.implement.output }}
|
|
12228
12399
|
|
|
12229
|
-
|
|
12230
|
-
|
|
12231
|
-
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
return join6(homedir5(), ".claude", "skills");
|
|
12236
|
-
}
|
|
12400
|
+
### Reviewer (Codex)
|
|
12401
|
+
{{ steps.review.output }}`
|
|
12402
|
+
}
|
|
12403
|
+
},
|
|
12404
|
+
output: "out"
|
|
12405
|
+
};
|
|
12237
12406
|
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
this.name = "LifecycleError";
|
|
12242
|
-
}
|
|
12407
|
+
// src/cli/workspace.ts
|
|
12408
|
+
function isChitOwned(path) {
|
|
12409
|
+
return path === ".chit" || path.startsWith(".chit/");
|
|
12243
12410
|
}
|
|
12244
|
-
function
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
const out = [];
|
|
12248
|
-
const entries = readdirSync2(parentDir);
|
|
12249
|
-
for (const name of entries) {
|
|
12250
|
-
const skillDir = join6(parentDir, name);
|
|
12251
|
-
let stat;
|
|
12252
|
-
try {
|
|
12253
|
-
stat = statSync2(skillDir);
|
|
12254
|
-
} catch {
|
|
12255
|
-
continue;
|
|
12256
|
-
}
|
|
12257
|
-
if (!stat.isDirectory())
|
|
12258
|
-
continue;
|
|
12259
|
-
const markerPath = join6(skillDir, INSTALL_MARKER_FILENAME);
|
|
12260
|
-
if (!existsSync6(markerPath))
|
|
12261
|
-
continue;
|
|
12262
|
-
let raw;
|
|
12263
|
-
try {
|
|
12264
|
-
raw = JSON.parse(readFileSync5(markerPath, "utf-8"));
|
|
12265
|
-
} catch {
|
|
12266
|
-
continue;
|
|
12267
|
-
}
|
|
12268
|
-
try {
|
|
12269
|
-
const marker = parseInstallMarker(raw, markerPath);
|
|
12270
|
-
out.push({ skillDir, markerPath, marker });
|
|
12271
|
-
} catch {}
|
|
12272
|
-
}
|
|
12273
|
-
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";
|
|
12274
12414
|
}
|
|
12275
|
-
function
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
if (!existsSync6(skillDir)) {
|
|
12281
|
-
throw new LifecycleError(`no install at ${skillDir}`);
|
|
12282
|
-
}
|
|
12283
|
-
if (!statSync2(skillDir).isDirectory()) {
|
|
12284
|
-
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);
|
|
12285
12420
|
}
|
|
12286
|
-
const
|
|
12287
|
-
|
|
12288
|
-
|
|
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);
|
|
12289
12429
|
}
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
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];
|
|
12295
12449
|
}
|
|
12296
|
-
|
|
12450
|
+
if (last === undefined)
|
|
12451
|
+
return null;
|
|
12297
12452
|
try {
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
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;
|
|
12302
12456
|
}
|
|
12303
|
-
|
|
12457
|
+
} catch {}
|
|
12458
|
+
return null;
|
|
12459
|
+
}
|
|
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
|
+
};
|
|
12304
12471
|
}
|
|
12305
|
-
|
|
12306
|
-
rmSync4(skillDir, { recursive: true, force: true });
|
|
12307
|
-
return record;
|
|
12472
|
+
return { verdict: "block", findingCount: 0, checksRun: CHECKS_RUN_FALLBACK };
|
|
12308
12473
|
}
|
|
12309
|
-
|
|
12310
|
-
|
|
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
|
|
12311
13072
|
init_src();
|
|
12312
|
-
import {
|
|
12313
|
-
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";
|
|
12314
13318
|
|
|
12315
13319
|
// ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v3/helpers/util.js
|
|
12316
13320
|
var util;
|
|
@@ -33178,7 +34182,7 @@ class Protocol {
|
|
|
33178
34182
|
return;
|
|
33179
34183
|
}
|
|
33180
34184
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
|
|
33181
|
-
await new Promise((
|
|
34185
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
33182
34186
|
options?.signal?.throwIfAborted();
|
|
33183
34187
|
}
|
|
33184
34188
|
} catch (error51) {
|
|
@@ -33190,7 +34194,7 @@ class Protocol {
|
|
|
33190
34194
|
}
|
|
33191
34195
|
request(request, resultSchema, options) {
|
|
33192
34196
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
33193
|
-
return new Promise((
|
|
34197
|
+
return new Promise((resolve4, reject) => {
|
|
33194
34198
|
const earlyReject = (error51) => {
|
|
33195
34199
|
reject(error51);
|
|
33196
34200
|
};
|
|
@@ -33268,7 +34272,7 @@ class Protocol {
|
|
|
33268
34272
|
if (!parseResult.success) {
|
|
33269
34273
|
reject(parseResult.error);
|
|
33270
34274
|
} else {
|
|
33271
|
-
|
|
34275
|
+
resolve4(parseResult.data);
|
|
33272
34276
|
}
|
|
33273
34277
|
} catch (error51) {
|
|
33274
34278
|
reject(error51);
|
|
@@ -33459,12 +34463,12 @@ class Protocol {
|
|
|
33459
34463
|
interval = task.pollInterval;
|
|
33460
34464
|
}
|
|
33461
34465
|
} catch {}
|
|
33462
|
-
return new Promise((
|
|
34466
|
+
return new Promise((resolve4, reject) => {
|
|
33463
34467
|
if (signal.aborted) {
|
|
33464
34468
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
33465
34469
|
return;
|
|
33466
34470
|
}
|
|
33467
|
-
const timeoutId = setTimeout(
|
|
34471
|
+
const timeoutId = setTimeout(resolve4, interval);
|
|
33468
34472
|
signal.addEventListener("abort", () => {
|
|
33469
34473
|
clearTimeout(timeoutId);
|
|
33470
34474
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -34317,7 +35321,7 @@ class McpServer {
|
|
|
34317
35321
|
let task = createTaskResult.task;
|
|
34318
35322
|
const pollInterval = task.pollInterval ?? 5000;
|
|
34319
35323
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
34320
|
-
await new Promise((
|
|
35324
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
34321
35325
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
34322
35326
|
if (!updatedTask) {
|
|
34323
35327
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -34716,1032 +35720,411 @@ class McpServer {
|
|
|
34716
35720
|
if (Object.values(firstArg).some((v) => typeof v === "object" && v !== null)) {
|
|
34717
35721
|
throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
|
|
34718
35722
|
}
|
|
34719
|
-
annotations = rest.shift();
|
|
34720
|
-
}
|
|
34721
|
-
}
|
|
34722
|
-
const callback = rest[0];
|
|
34723
|
-
return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, undefined, callback);
|
|
34724
|
-
}
|
|
34725
|
-
registerTool(name, config2, cb) {
|
|
34726
|
-
if (this._registeredTools[name]) {
|
|
34727
|
-
throw new Error(`Tool ${name} is already registered`);
|
|
34728
|
-
}
|
|
34729
|
-
const { title, description, inputSchema, outputSchema, annotations, _meta } = config2;
|
|
34730
|
-
return this._createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, _meta, cb);
|
|
34731
|
-
}
|
|
34732
|
-
prompt(name, ...rest) {
|
|
34733
|
-
if (this._registeredPrompts[name]) {
|
|
34734
|
-
throw new Error(`Prompt ${name} is already registered`);
|
|
34735
|
-
}
|
|
34736
|
-
let description;
|
|
34737
|
-
if (typeof rest[0] === "string") {
|
|
34738
|
-
description = rest.shift();
|
|
34739
|
-
}
|
|
34740
|
-
let argsSchema;
|
|
34741
|
-
if (rest.length > 1) {
|
|
34742
|
-
argsSchema = rest.shift();
|
|
34743
|
-
}
|
|
34744
|
-
const cb = rest[0];
|
|
34745
|
-
const registeredPrompt = this._createRegisteredPrompt(name, undefined, description, argsSchema, cb);
|
|
34746
|
-
this.setPromptRequestHandlers();
|
|
34747
|
-
this.sendPromptListChanged();
|
|
34748
|
-
return registeredPrompt;
|
|
34749
|
-
}
|
|
34750
|
-
registerPrompt(name, config2, cb) {
|
|
34751
|
-
if (this._registeredPrompts[name]) {
|
|
34752
|
-
throw new Error(`Prompt ${name} is already registered`);
|
|
34753
|
-
}
|
|
34754
|
-
const { title, description, argsSchema } = config2;
|
|
34755
|
-
const registeredPrompt = this._createRegisteredPrompt(name, title, description, argsSchema, cb);
|
|
34756
|
-
this.setPromptRequestHandlers();
|
|
34757
|
-
this.sendPromptListChanged();
|
|
34758
|
-
return registeredPrompt;
|
|
34759
|
-
}
|
|
34760
|
-
isConnected() {
|
|
34761
|
-
return this.server.transport !== undefined;
|
|
34762
|
-
}
|
|
34763
|
-
async sendLoggingMessage(params, sessionId) {
|
|
34764
|
-
return this.server.sendLoggingMessage(params, sessionId);
|
|
34765
|
-
}
|
|
34766
|
-
sendResourceListChanged() {
|
|
34767
|
-
if (this.isConnected()) {
|
|
34768
|
-
this.server.sendResourceListChanged();
|
|
34769
|
-
}
|
|
34770
|
-
}
|
|
34771
|
-
sendToolListChanged() {
|
|
34772
|
-
if (this.isConnected()) {
|
|
34773
|
-
this.server.sendToolListChanged();
|
|
34774
|
-
}
|
|
34775
|
-
}
|
|
34776
|
-
sendPromptListChanged() {
|
|
34777
|
-
if (this.isConnected()) {
|
|
34778
|
-
this.server.sendPromptListChanged();
|
|
34779
|
-
}
|
|
34780
|
-
}
|
|
34781
|
-
}
|
|
34782
|
-
var EMPTY_OBJECT_JSON_SCHEMA = {
|
|
34783
|
-
type: "object",
|
|
34784
|
-
properties: {}
|
|
34785
|
-
};
|
|
34786
|
-
function isZodTypeLike(value) {
|
|
34787
|
-
return value !== null && typeof value === "object" && "parse" in value && typeof value.parse === "function" && "safeParse" in value && typeof value.safeParse === "function";
|
|
34788
|
-
}
|
|
34789
|
-
function isZodSchemaInstance(obj3) {
|
|
34790
|
-
return "_def" in obj3 || "_zod" in obj3 || isZodTypeLike(obj3);
|
|
34791
|
-
}
|
|
34792
|
-
function isZodRawShapeCompat(obj3) {
|
|
34793
|
-
if (typeof obj3 !== "object" || obj3 === null) {
|
|
34794
|
-
return false;
|
|
34795
|
-
}
|
|
34796
|
-
if (isZodSchemaInstance(obj3)) {
|
|
34797
|
-
return false;
|
|
34798
|
-
}
|
|
34799
|
-
if (Object.keys(obj3).length === 0) {
|
|
34800
|
-
return true;
|
|
34801
|
-
}
|
|
34802
|
-
return Object.values(obj3).some(isZodTypeLike);
|
|
34803
|
-
}
|
|
34804
|
-
function getZodSchemaObject(schema) {
|
|
34805
|
-
if (!schema) {
|
|
34806
|
-
return;
|
|
34807
|
-
}
|
|
34808
|
-
if (isZodRawShapeCompat(schema)) {
|
|
34809
|
-
return objectFromShape(schema);
|
|
34810
|
-
}
|
|
34811
|
-
if (!isZodSchemaInstance(schema)) {
|
|
34812
|
-
throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
|
|
34813
|
-
}
|
|
34814
|
-
return schema;
|
|
34815
|
-
}
|
|
34816
|
-
function promptArgumentsFromSchema(schema) {
|
|
34817
|
-
const shape = getObjectShape(schema);
|
|
34818
|
-
if (!shape)
|
|
34819
|
-
return [];
|
|
34820
|
-
return Object.entries(shape).map(([name, field]) => {
|
|
34821
|
-
const description = getSchemaDescription(field);
|
|
34822
|
-
const isOptional = isSchemaOptional(field);
|
|
34823
|
-
return {
|
|
34824
|
-
name,
|
|
34825
|
-
description,
|
|
34826
|
-
required: !isOptional
|
|
34827
|
-
};
|
|
34828
|
-
});
|
|
34829
|
-
}
|
|
34830
|
-
function getMethodValue(schema) {
|
|
34831
|
-
const shape = getObjectShape(schema);
|
|
34832
|
-
const methodSchema = shape?.method;
|
|
34833
|
-
if (!methodSchema) {
|
|
34834
|
-
throw new Error("Schema is missing a method literal");
|
|
34835
|
-
}
|
|
34836
|
-
const value = getLiteralValue(methodSchema);
|
|
34837
|
-
if (typeof value === "string") {
|
|
34838
|
-
return value;
|
|
34839
|
-
}
|
|
34840
|
-
throw new Error("Schema method literal must be a string");
|
|
34841
|
-
}
|
|
34842
|
-
function createCompletionResult(suggestions) {
|
|
34843
|
-
return {
|
|
34844
|
-
completion: {
|
|
34845
|
-
values: suggestions.slice(0, 100),
|
|
34846
|
-
total: suggestions.length,
|
|
34847
|
-
hasMore: suggestions.length > 100
|
|
34848
|
-
}
|
|
34849
|
-
};
|
|
34850
|
-
}
|
|
34851
|
-
var EMPTY_COMPLETION_RESULT = {
|
|
34852
|
-
completion: {
|
|
34853
|
-
values: [],
|
|
34854
|
-
hasMore: false
|
|
34855
|
-
}
|
|
34856
|
-
};
|
|
34857
|
-
|
|
34858
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.29.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
34859
|
-
import process3 from "process";
|
|
34860
|
-
|
|
34861
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.29.0/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
|
|
34862
|
-
class ReadBuffer {
|
|
34863
|
-
append(chunk) {
|
|
34864
|
-
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
|
|
34865
|
-
}
|
|
34866
|
-
readMessage() {
|
|
34867
|
-
if (!this._buffer) {
|
|
34868
|
-
return null;
|
|
34869
|
-
}
|
|
34870
|
-
const index = this._buffer.indexOf(`
|
|
34871
|
-
`);
|
|
34872
|
-
if (index === -1) {
|
|
34873
|
-
return null;
|
|
34874
|
-
}
|
|
34875
|
-
const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
|
|
34876
|
-
this._buffer = this._buffer.subarray(index + 1);
|
|
34877
|
-
return deserializeMessage(line);
|
|
34878
|
-
}
|
|
34879
|
-
clear() {
|
|
34880
|
-
this._buffer = undefined;
|
|
34881
|
-
}
|
|
34882
|
-
}
|
|
34883
|
-
function deserializeMessage(line) {
|
|
34884
|
-
return JSONRPCMessageSchema.parse(JSON.parse(line));
|
|
34885
|
-
}
|
|
34886
|
-
function serializeMessage(message) {
|
|
34887
|
-
return JSON.stringify(message) + `
|
|
34888
|
-
`;
|
|
34889
|
-
}
|
|
34890
|
-
|
|
34891
|
-
// ../../node_modules/.bun/@modelcontextprotocol+sdk@1.29.0/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
34892
|
-
class StdioServerTransport {
|
|
34893
|
-
constructor(_stdin = process3.stdin, _stdout = process3.stdout) {
|
|
34894
|
-
this._stdin = _stdin;
|
|
34895
|
-
this._stdout = _stdout;
|
|
34896
|
-
this._readBuffer = new ReadBuffer;
|
|
34897
|
-
this._started = false;
|
|
34898
|
-
this._ondata = (chunk) => {
|
|
34899
|
-
this._readBuffer.append(chunk);
|
|
34900
|
-
this.processReadBuffer();
|
|
34901
|
-
};
|
|
34902
|
-
this._onerror = (error51) => {
|
|
34903
|
-
this.onerror?.(error51);
|
|
34904
|
-
};
|
|
34905
|
-
}
|
|
34906
|
-
async start() {
|
|
34907
|
-
if (this._started) {
|
|
34908
|
-
throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
|
|
34909
|
-
}
|
|
34910
|
-
this._started = true;
|
|
34911
|
-
this._stdin.on("data", this._ondata);
|
|
34912
|
-
this._stdin.on("error", this._onerror);
|
|
34913
|
-
}
|
|
34914
|
-
processReadBuffer() {
|
|
34915
|
-
while (true) {
|
|
34916
|
-
try {
|
|
34917
|
-
const message = this._readBuffer.readMessage();
|
|
34918
|
-
if (message === null) {
|
|
34919
|
-
break;
|
|
34920
|
-
}
|
|
34921
|
-
this.onmessage?.(message);
|
|
34922
|
-
} catch (error51) {
|
|
34923
|
-
this.onerror?.(error51);
|
|
34924
|
-
}
|
|
34925
|
-
}
|
|
34926
|
-
}
|
|
34927
|
-
async close() {
|
|
34928
|
-
this._stdin.off("data", this._ondata);
|
|
34929
|
-
this._stdin.off("error", this._onerror);
|
|
34930
|
-
const remainingDataListeners = this._stdin.listenerCount("data");
|
|
34931
|
-
if (remainingDataListeners === 0) {
|
|
34932
|
-
this._stdin.pause();
|
|
34933
|
-
}
|
|
34934
|
-
this._readBuffer.clear();
|
|
34935
|
-
this.onclose?.();
|
|
34936
|
-
}
|
|
34937
|
-
send(message) {
|
|
34938
|
-
return new Promise((resolve2) => {
|
|
34939
|
-
const json2 = serializeMessage(message);
|
|
34940
|
-
if (this._stdout.write(json2)) {
|
|
34941
|
-
resolve2();
|
|
34942
|
-
} else {
|
|
34943
|
-
this._stdout.once("drain", resolve2);
|
|
34944
|
-
}
|
|
34945
|
-
});
|
|
34946
|
-
}
|
|
34947
|
-
}
|
|
34948
|
-
|
|
34949
|
-
// src/audit/reader.ts
|
|
34950
|
-
var USAGE_KEYS = [
|
|
34951
|
-
"inputTokens",
|
|
34952
|
-
"outputTokens",
|
|
34953
|
-
"totalTokens",
|
|
34954
|
-
"cachedInputTokens",
|
|
34955
|
-
"reasoningTokens",
|
|
34956
|
-
"estimatedCostUsd"
|
|
34957
|
-
];
|
|
34958
|
-
function sumUsage(events2) {
|
|
34959
|
-
const usage = {};
|
|
34960
|
-
let any3 = false;
|
|
34961
|
-
for (const e of events2) {
|
|
34962
|
-
if (e.type !== "adapter.call.completed" || !e.usage)
|
|
34963
|
-
continue;
|
|
34964
|
-
for (const k of USAGE_KEYS) {
|
|
34965
|
-
const v = e.usage[k];
|
|
34966
|
-
if (typeof v === "number") {
|
|
34967
|
-
usage[k] = (usage[k] ?? 0) + v;
|
|
34968
|
-
any3 = true;
|
|
34969
|
-
}
|
|
34970
|
-
}
|
|
34971
|
-
}
|
|
34972
|
-
return any3 ? usage : undefined;
|
|
34973
|
-
}
|
|
34974
|
-
function findOpenCall(events2) {
|
|
34975
|
-
const open = new Map;
|
|
34976
|
-
for (const e of events2) {
|
|
34977
|
-
if (e.type === "adapter.call.started") {
|
|
34978
|
-
open.set(e.stepId, {
|
|
34979
|
-
stepId: e.stepId,
|
|
34980
|
-
participantId: e.participantId,
|
|
34981
|
-
agentId: e.agentId,
|
|
34982
|
-
since: e.ts
|
|
34983
|
-
});
|
|
34984
|
-
} else if (e.type === "adapter.call.completed") {
|
|
34985
|
-
open.delete(e.stepId);
|
|
34986
|
-
}
|
|
34987
|
-
}
|
|
34988
|
-
let latest;
|
|
34989
|
-
for (const c of open.values()) {
|
|
34990
|
-
if (latest === undefined || c.since > latest.since)
|
|
34991
|
-
latest = c;
|
|
34992
|
-
}
|
|
34993
|
-
return latest;
|
|
34994
|
-
}
|
|
34995
|
-
function describeIncomplete(s, events2) {
|
|
34996
|
-
if (s.openCall) {
|
|
34997
|
-
const c = s.openCall;
|
|
34998
|
-
return `open call: ${c.stepId} ${c.participantId}/${c.agentId} since ${c.since}; no adapter.call.completed`;
|
|
34999
|
-
}
|
|
35000
|
-
const failed = events2.find((e) => e.type === "step.failed");
|
|
35001
|
-
if (failed?.type === "step.failed") {
|
|
35002
|
-
const err = failed.error.replace(/\s+/g, " ").trim();
|
|
35003
|
-
const clipped = err.length > 200 ? `${err.slice(0, 200)}...` : err;
|
|
35004
|
-
return `failed step: ${failed.stepId}: ${clipped}`;
|
|
35005
|
-
}
|
|
35006
|
-
return "abandoned before terminal run.completed";
|
|
35007
|
-
}
|
|
35008
|
-
function summarizeRun(runId, events2) {
|
|
35009
|
-
const started = events2.find((e) => e.type === "run.started");
|
|
35010
|
-
const completed = events2.find((e) => e.type === "run.completed");
|
|
35011
|
-
const summary = {
|
|
35012
|
-
runId,
|
|
35013
|
-
manifestId: started?.type === "run.started" ? started.manifestId : "?",
|
|
35014
|
-
surface: started?.type === "run.started" ? started.surface : "?",
|
|
35015
|
-
status: completed?.type === "run.completed" ? completed.status : "incomplete",
|
|
35016
|
-
stepCount: events2.filter((e) => e.type === "step.completed").length
|
|
35017
|
-
};
|
|
35018
|
-
if (started?.type === "run.started") {
|
|
35019
|
-
summary.startedAt = started.ts;
|
|
35020
|
-
if (started.scope !== undefined)
|
|
35021
|
-
summary.scope = started.scope;
|
|
35022
|
-
if (started.loopId !== undefined)
|
|
35023
|
-
summary.loopId = started.loopId;
|
|
35024
|
-
if (started.iteration !== undefined)
|
|
35025
|
-
summary.iteration = started.iteration;
|
|
35026
|
-
}
|
|
35027
|
-
const usage = sumUsage(events2);
|
|
35028
|
-
if (usage !== undefined)
|
|
35029
|
-
summary.usage = usage;
|
|
35030
|
-
const openCall = findOpenCall(events2);
|
|
35031
|
-
if (openCall !== undefined)
|
|
35032
|
-
summary.openCall = openCall;
|
|
35033
|
-
return summary;
|
|
35034
|
-
}
|
|
35035
|
-
function safeReadEvents(store, runId) {
|
|
35036
|
-
try {
|
|
35037
|
-
return store.readEvents(runId);
|
|
35038
|
-
} catch {
|
|
35039
|
-
return [];
|
|
35040
|
-
}
|
|
35041
|
-
}
|
|
35042
|
-
function listAudit(store, limit) {
|
|
35043
|
-
const summaries = store.listRuns().map((id) => summarizeRun(id, safeReadEvents(store, id)));
|
|
35044
|
-
summaries.sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? ""));
|
|
35045
|
-
return limit !== undefined ? summaries.slice(0, limit) : summaries;
|
|
35046
|
-
}
|
|
35047
|
-
function readBody(store, runId, ref) {
|
|
35048
|
-
try {
|
|
35049
|
-
return store.readBlob(runId, ref);
|
|
35050
|
-
} catch (err) {
|
|
35051
|
-
return `<blob unavailable: ${err.message}>`;
|
|
35052
|
-
}
|
|
35053
|
-
}
|
|
35054
|
-
function isReceiptEvent(e) {
|
|
35055
|
-
return e.type !== "adapter.event";
|
|
35056
|
-
}
|
|
35057
|
-
function hiddenAdapterEventCount(events2) {
|
|
35058
|
-
return events2.reduce((n, e) => e.type === "adapter.event" ? n + 1 : n, 0);
|
|
35059
|
-
}
|
|
35060
|
-
function auditTimeline(store, runId, events2, opts) {
|
|
35061
|
-
const rows = opts.verbose ? events2 : events2.filter(isReceiptEvent);
|
|
35062
|
-
return rows.map((e) => {
|
|
35063
|
-
if (!opts.includeBodies)
|
|
35064
|
-
return e;
|
|
35065
|
-
if (e.type === "adapter.call.started") {
|
|
35066
|
-
return { ...e, input: readBody(store, runId, e.inputBlob) };
|
|
35067
|
-
}
|
|
35068
|
-
if (e.type === "adapter.event" && e.rawBlob !== undefined) {
|
|
35069
|
-
return { ...e, raw: readBody(store, runId, e.rawBlob) };
|
|
35070
|
-
}
|
|
35071
|
-
if (e.type === "adapter.call.completed") {
|
|
35072
|
-
return { ...e, output: readBody(store, runId, e.outputBlob) };
|
|
35073
|
-
}
|
|
35074
|
-
if (e.type === "step.completed" && e.outputBlob !== undefined) {
|
|
35075
|
-
return { ...e, output: readBody(store, runId, e.outputBlob) };
|
|
35076
|
-
}
|
|
35077
|
-
return e;
|
|
35078
|
-
});
|
|
35079
|
-
}
|
|
35080
|
-
function showAudit(store, runId, opts) {
|
|
35081
|
-
const events2 = store.readEvents(runId);
|
|
35082
|
-
const summary = summarizeRun(runId, events2);
|
|
35083
|
-
const out = {
|
|
35084
|
-
summary,
|
|
35085
|
-
timeline: auditTimeline(store, runId, events2, opts)
|
|
35086
|
-
};
|
|
35087
|
-
if (summary.status === "incomplete")
|
|
35088
|
-
out.incompleteReason = describeIncomplete(summary, events2);
|
|
35089
|
-
const started = events2.find((e) => e.type === "run.started");
|
|
35090
|
-
if (started?.type === "run.started" && started.participants !== undefined) {
|
|
35091
|
-
out.participants = started.participants;
|
|
35092
|
-
}
|
|
35093
|
-
if (!opts.verbose) {
|
|
35094
|
-
const hidden = hiddenAdapterEventCount(events2);
|
|
35095
|
-
if (hidden > 0) {
|
|
35096
|
-
out.note = `${hidden} raw adapter events hidden; pass verbose to include them, include_bodies to show blob bodies.`;
|
|
35723
|
+
annotations = rest.shift();
|
|
35724
|
+
}
|
|
35097
35725
|
}
|
|
35726
|
+
const callback = rest[0];
|
|
35727
|
+
return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, undefined, callback);
|
|
35098
35728
|
}
|
|
35099
|
-
|
|
35100
|
-
|
|
35101
|
-
|
|
35102
|
-
|
|
35103
|
-
|
|
35104
|
-
|
|
35105
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
35106
|
-
import { resolve as resolve2 } from "path";
|
|
35107
|
-
|
|
35108
|
-
// src/loops/log-store.ts
|
|
35109
|
-
init_src();
|
|
35110
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
35111
|
-
import { join as join7 } from "path";
|
|
35112
|
-
class LoopStoreError extends Error {
|
|
35113
|
-
}
|
|
35114
|
-
var SAFE_LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
35115
|
-
var realClock2 = () => Date.now();
|
|
35116
|
-
function iso(ms) {
|
|
35117
|
-
return new Date(ms).toISOString();
|
|
35118
|
-
}
|
|
35119
|
-
function safeId(loopId) {
|
|
35120
|
-
if (!SAFE_LOOP_ID.test(loopId)) {
|
|
35121
|
-
throw new LoopStoreError(`invalid loop id ${JSON.stringify(loopId)}`);
|
|
35729
|
+
registerTool(name, config2, cb) {
|
|
35730
|
+
if (this._registeredTools[name]) {
|
|
35731
|
+
throw new Error(`Tool ${name} is already registered`);
|
|
35732
|
+
}
|
|
35733
|
+
const { title, description, inputSchema, outputSchema, annotations, _meta } = config2;
|
|
35734
|
+
return this._createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, _meta, cb);
|
|
35122
35735
|
}
|
|
35123
|
-
|
|
35124
|
-
|
|
35125
|
-
|
|
35126
|
-
|
|
35127
|
-
|
|
35128
|
-
|
|
35129
|
-
|
|
35130
|
-
|
|
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;
|
|
35131
35753
|
}
|
|
35132
|
-
|
|
35133
|
-
|
|
35134
|
-
|
|
35135
|
-
|
|
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;
|
|
35136
35763
|
}
|
|
35137
|
-
|
|
35138
|
-
|
|
35139
|
-
function startLoop(cwd, opts) {
|
|
35140
|
-
const loopId = opts.loopId ?? crypto.randomUUID();
|
|
35141
|
-
const path = loopPath(cwd, loopId);
|
|
35142
|
-
if (existsSync7(path) && !opts.force) {
|
|
35143
|
-
throw new LoopStoreError(`loop log already exists at ${path} (pass force to overwrite)`);
|
|
35764
|
+
isConnected() {
|
|
35765
|
+
return this.server.transport !== undefined;
|
|
35144
35766
|
}
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
type: "loop",
|
|
35148
|
-
schema: 1,
|
|
35149
|
-
loopId,
|
|
35150
|
-
scope: opts.scope,
|
|
35151
|
-
task: opts.task,
|
|
35152
|
-
repo: repoRoot(cwd),
|
|
35153
|
-
repoKey: repoKey(cwd),
|
|
35154
|
-
startedAt: iso((opts.clock ?? realClock2)()),
|
|
35155
|
-
maxIterations: opts.maxIterations
|
|
35156
|
-
};
|
|
35157
|
-
writeFileSync4(path, `${serializeLoopRecord(header)}
|
|
35158
|
-
`);
|
|
35159
|
-
return { loopId, path };
|
|
35160
|
-
}
|
|
35161
|
-
function appendIteration(cwd, loopId, opts) {
|
|
35162
|
-
const path = loopPath(cwd, loopId);
|
|
35163
|
-
const records = readRecords(path, loopId);
|
|
35164
|
-
if (records.some((r) => r.type === "stop")) {
|
|
35165
|
-
throw new LoopStoreError(`loop ${JSON.stringify(loopId)} is already stopped; cannot append`);
|
|
35767
|
+
async sendLoggingMessage(params, sessionId) {
|
|
35768
|
+
return this.server.sendLoggingMessage(params, sessionId);
|
|
35166
35769
|
}
|
|
35167
|
-
|
|
35168
|
-
|
|
35169
|
-
|
|
35170
|
-
|
|
35770
|
+
sendResourceListChanged() {
|
|
35771
|
+
if (this.isConnected()) {
|
|
35772
|
+
this.server.sendResourceListChanged();
|
|
35773
|
+
}
|
|
35171
35774
|
}
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
changedFiles: opts.changedFiles,
|
|
35177
|
-
checksRun: opts.checksRun,
|
|
35178
|
-
verdict: opts.verdict,
|
|
35179
|
-
findingCount: opts.findingCount,
|
|
35180
|
-
decision: opts.decision,
|
|
35181
|
-
checkDurationMs: opts.checkDurationMs,
|
|
35182
|
-
at: iso((opts.clock ?? realClock2)())
|
|
35183
|
-
};
|
|
35184
|
-
if (opts.workspaceWarnings !== undefined && opts.workspaceWarnings.length > 0) {
|
|
35185
|
-
rec.workspaceWarnings = opts.workspaceWarnings;
|
|
35775
|
+
sendToolListChanged() {
|
|
35776
|
+
if (this.isConnected()) {
|
|
35777
|
+
this.server.sendToolListChanged();
|
|
35778
|
+
}
|
|
35186
35779
|
}
|
|
35187
|
-
|
|
35188
|
-
|
|
35189
|
-
|
|
35190
|
-
|
|
35191
|
-
appendFileSync2(path, `${serializeLoopRecord(rec)}
|
|
35192
|
-
`);
|
|
35193
|
-
return { n, path };
|
|
35194
|
-
}
|
|
35195
|
-
function stopLoop(cwd, loopId, opts) {
|
|
35196
|
-
const path = loopPath(cwd, loopId);
|
|
35197
|
-
const records = readRecords(path, loopId);
|
|
35198
|
-
if (records.some((r) => r.type === "stop")) {
|
|
35199
|
-
throw new LoopStoreError(`loop ${JSON.stringify(loopId)} is already stopped`);
|
|
35780
|
+
sendPromptListChanged() {
|
|
35781
|
+
if (this.isConnected()) {
|
|
35782
|
+
this.server.sendPromptListChanged();
|
|
35783
|
+
}
|
|
35200
35784
|
}
|
|
35201
|
-
const header = records[0];
|
|
35202
|
-
const iterations = records.filter((r) => r.type === "iteration").length;
|
|
35203
|
-
const nowMs = (opts.clock ?? realClock2)();
|
|
35204
|
-
const totalElapsedMs = Math.max(0, nowMs - Date.parse(header.startedAt));
|
|
35205
|
-
const rec = {
|
|
35206
|
-
type: "stop",
|
|
35207
|
-
status: opts.status,
|
|
35208
|
-
reason: opts.reason,
|
|
35209
|
-
iterations,
|
|
35210
|
-
totalElapsedMs,
|
|
35211
|
-
endedAt: iso(nowMs)
|
|
35212
|
-
};
|
|
35213
|
-
appendFileSync2(path, `${serializeLoopRecord(rec)}
|
|
35214
|
-
`);
|
|
35215
|
-
return { iterations, totalElapsedMs, path };
|
|
35216
|
-
}
|
|
35217
|
-
function readLoop(cwd, loopId) {
|
|
35218
|
-
return readRecords(loopPath(cwd, loopId), loopId);
|
|
35219
35785
|
}
|
|
35220
|
-
|
|
35221
|
-
|
|
35222
|
-
|
|
35223
|
-
schema: 1,
|
|
35224
|
-
id: "converge",
|
|
35225
|
-
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.",
|
|
35226
|
-
inputs: {
|
|
35227
|
-
task: { type: "string" },
|
|
35228
|
-
prior_review: { type: "string", optional: true }
|
|
35229
|
-
},
|
|
35230
|
-
requires: {
|
|
35231
|
-
can_show_markdown: true
|
|
35232
|
-
},
|
|
35233
|
-
participants: {
|
|
35234
|
-
implementer: {
|
|
35235
|
-
agent: "claude",
|
|
35236
|
-
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.",
|
|
35237
|
-
session: "per_scope",
|
|
35238
|
-
permissions: { filesystem: "write" }
|
|
35239
|
-
},
|
|
35240
|
-
reviewer: {
|
|
35241
|
-
agent: "codex",
|
|
35242
|
-
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.",
|
|
35243
|
-
session: "per_scope",
|
|
35244
|
-
permissions: { filesystem: "read_only" }
|
|
35245
|
-
}
|
|
35246
|
-
},
|
|
35247
|
-
steps: {
|
|
35248
|
-
implement: {
|
|
35249
|
-
call: "implementer",
|
|
35250
|
-
prompt: `Task:
|
|
35251
|
-
{{ inputs.task }}
|
|
35252
|
-
|
|
35253
|
-
Prior review to address (empty on the first iteration):
|
|
35254
|
-
{{ inputs.prior_review }}
|
|
35255
|
-
|
|
35256
|
-
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.`
|
|
35257
|
-
},
|
|
35258
|
-
review: {
|
|
35259
|
-
call: "reviewer",
|
|
35260
|
-
prompt: `Task under review:
|
|
35261
|
-
{{ inputs.task }}
|
|
35262
|
-
|
|
35263
|
-
Claude's summary of what it just implemented:
|
|
35264
|
-
{{ steps.implement.output }}
|
|
35265
|
-
|
|
35266
|
-
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:
|
|
35267
|
-
1. Verdict: proceed / revise / block.
|
|
35268
|
-
2. Findings ordered by severity, with file:line.
|
|
35269
|
-
3. What Claude should fix next if the verdict is revise.
|
|
35270
|
-
4. Remaining risk if proceeding.
|
|
35271
|
-
|
|
35272
|
-
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:
|
|
35273
|
-
\`\`\`json
|
|
35274
|
-
{"verdict": "proceed | revise | block", "findingCount": 0, "checksRun": "the non-mutating checks you ran, or 'none'", "risk": "remaining risk if proceeding"}
|
|
35275
|
-
\`\`\`
|
|
35276
|
-
findingCount is the integer number of findings; checksRun is a short human string.`
|
|
35277
|
-
},
|
|
35278
|
-
out: {
|
|
35279
|
-
format: `## Converge iteration
|
|
35280
|
-
|
|
35281
|
-
### Implementer (Claude)
|
|
35282
|
-
{{ steps.implement.output }}
|
|
35283
|
-
|
|
35284
|
-
### Reviewer (Codex)
|
|
35285
|
-
{{ steps.review.output }}`
|
|
35286
|
-
}
|
|
35287
|
-
},
|
|
35288
|
-
output: "out"
|
|
35786
|
+
var EMPTY_OBJECT_JSON_SCHEMA = {
|
|
35787
|
+
type: "object",
|
|
35788
|
+
properties: {}
|
|
35289
35789
|
};
|
|
35290
|
-
|
|
35291
|
-
|
|
35292
|
-
function isChitOwned(path) {
|
|
35293
|
-
return path === ".chit" || path.startsWith(".chit/");
|
|
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";
|
|
35294
35792
|
}
|
|
35295
|
-
function
|
|
35296
|
-
|
|
35297
|
-
return path === "__pycache__" || path.startsWith("__pycache__/") || path.includes("/__pycache__/") || path.endsWith(".pyc") || path.endsWith(".pyo") || base === ".DS_Store";
|
|
35793
|
+
function isZodSchemaInstance(obj3) {
|
|
35794
|
+
return "_def" in obj3 || "_zod" in obj3 || isZodTypeLike(obj3);
|
|
35298
35795
|
}
|
|
35299
|
-
function
|
|
35300
|
-
|
|
35301
|
-
|
|
35302
|
-
if (!isChitOwned(f))
|
|
35303
|
-
changed.add(f);
|
|
35796
|
+
function isZodRawShapeCompat(obj3) {
|
|
35797
|
+
if (typeof obj3 !== "object" || obj3 === null) {
|
|
35798
|
+
return false;
|
|
35304
35799
|
}
|
|
35305
|
-
|
|
35306
|
-
|
|
35307
|
-
if (isChitOwned(f))
|
|
35308
|
-
continue;
|
|
35309
|
-
if (isGeneratedArtifact(f))
|
|
35310
|
-
workspaceWarnings.push(`untracked generated artifact: ${f}`);
|
|
35311
|
-
else
|
|
35312
|
-
changed.add(f);
|
|
35800
|
+
if (isZodSchemaInstance(obj3)) {
|
|
35801
|
+
return false;
|
|
35313
35802
|
}
|
|
35314
|
-
|
|
35315
|
-
|
|
35316
|
-
|
|
35317
|
-
// src/cli/converge.ts
|
|
35318
|
-
var defaultIO = {
|
|
35319
|
-
out: (s) => process.stdout.write(s),
|
|
35320
|
-
err: (s) => process.stderr.write(s)
|
|
35321
|
-
};
|
|
35322
|
-
var JSON_BLOCK_RE = /```json\s*([\s\S]*?)```/gi;
|
|
35323
|
-
var VERDICTS3 = new Set(["proceed", "revise", "block"]);
|
|
35324
|
-
var IMPLEMENT_STEP_ID = "implement";
|
|
35325
|
-
var REVIEW_STEP_ID = "review";
|
|
35326
|
-
var IMPLEMENT_SUMMARY_CAP = 2000;
|
|
35327
|
-
var CHECKS_RUN_FALLBACK = "unreported";
|
|
35328
|
-
function extractReviewJson(reviewText) {
|
|
35329
|
-
let last;
|
|
35330
|
-
for (const m of reviewText.matchAll(JSON_BLOCK_RE)) {
|
|
35331
|
-
if (m[1] !== undefined)
|
|
35332
|
-
last = m[1];
|
|
35803
|
+
if (Object.keys(obj3).length === 0) {
|
|
35804
|
+
return true;
|
|
35333
35805
|
}
|
|
35334
|
-
|
|
35335
|
-
return null;
|
|
35336
|
-
try {
|
|
35337
|
-
const parsed = JSON.parse(last);
|
|
35338
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
35339
|
-
return parsed;
|
|
35340
|
-
}
|
|
35341
|
-
} catch {}
|
|
35342
|
-
return null;
|
|
35806
|
+
return Object.values(obj3).some(isZodTypeLike);
|
|
35343
35807
|
}
|
|
35344
|
-
function
|
|
35345
|
-
|
|
35346
|
-
|
|
35347
|
-
if (block && rawVerdict && VERDICTS3.has(rawVerdict)) {
|
|
35348
|
-
const fc = block.findingCount;
|
|
35349
|
-
const cr = block.checksRun;
|
|
35350
|
-
return {
|
|
35351
|
-
verdict: rawVerdict,
|
|
35352
|
-
findingCount: typeof fc === "number" && Number.isInteger(fc) && fc >= 0 ? fc : 0,
|
|
35353
|
-
checksRun: typeof cr === "string" && cr.trim() !== "" ? cr.trim() : CHECKS_RUN_FALLBACK
|
|
35354
|
-
};
|
|
35808
|
+
function getZodSchemaObject(schema) {
|
|
35809
|
+
if (!schema) {
|
|
35810
|
+
return;
|
|
35355
35811
|
}
|
|
35356
|
-
|
|
35357
|
-
|
|
35358
|
-
function reviewDurationMs(trace) {
|
|
35359
|
-
for (const e of trace) {
|
|
35360
|
-
if (e.type === "step.completed" && e.stepId === REVIEW_STEP_ID)
|
|
35361
|
-
return e.durationMs;
|
|
35812
|
+
if (isZodRawShapeCompat(schema)) {
|
|
35813
|
+
return objectFromShape(schema);
|
|
35362
35814
|
}
|
|
35363
|
-
|
|
35364
|
-
|
|
35365
|
-
var USAGE_KEYS2 = [
|
|
35366
|
-
"inputTokens",
|
|
35367
|
-
"outputTokens",
|
|
35368
|
-
"totalTokens",
|
|
35369
|
-
"cachedInputTokens",
|
|
35370
|
-
"reasoningTokens",
|
|
35371
|
-
"estimatedCostUsd"
|
|
35372
|
-
];
|
|
35373
|
-
function sumTraceUsage(trace) {
|
|
35374
|
-
const usage = {};
|
|
35375
|
-
let any3 = false;
|
|
35376
|
-
for (const e of trace) {
|
|
35377
|
-
if (e.type !== "step.completed" || !e.usage)
|
|
35378
|
-
continue;
|
|
35379
|
-
for (const k of USAGE_KEYS2) {
|
|
35380
|
-
const v = e.usage[k];
|
|
35381
|
-
if (typeof v === "number") {
|
|
35382
|
-
usage[k] = (usage[k] ?? 0) + v;
|
|
35383
|
-
any3 = true;
|
|
35384
|
-
}
|
|
35385
|
-
}
|
|
35815
|
+
if (!isZodSchemaInstance(schema)) {
|
|
35816
|
+
throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
|
|
35386
35817
|
}
|
|
35387
|
-
return
|
|
35388
|
-
}
|
|
35389
|
-
function capSummary(text) {
|
|
35390
|
-
if (text === "")
|
|
35391
|
-
return "(no summary)";
|
|
35392
|
-
if (text.length <= IMPLEMENT_SUMMARY_CAP)
|
|
35393
|
-
return text;
|
|
35394
|
-
return `${text.slice(0, IMPLEMENT_SUMMARY_CAP)}\u2026 (truncated, ${text.length} chars)`;
|
|
35818
|
+
return schema;
|
|
35395
35819
|
}
|
|
35396
|
-
function
|
|
35397
|
-
|
|
35398
|
-
|
|
35399
|
-
cwd,
|
|
35400
|
-
encoding: "utf-8",
|
|
35401
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
35402
|
-
});
|
|
35403
|
-
return out.split(`
|
|
35404
|
-
`).map((s) => s.trim()).filter(Boolean);
|
|
35405
|
-
} catch {
|
|
35820
|
+
function promptArgumentsFromSchema(schema) {
|
|
35821
|
+
const shape = getObjectShape(schema);
|
|
35822
|
+
if (!shape)
|
|
35406
35823
|
return [];
|
|
35407
|
-
|
|
35408
|
-
|
|
35409
|
-
|
|
35410
|
-
return classifyWorkspace({
|
|
35411
|
-
tracked: [
|
|
35412
|
-
...gitLines(cwd, ["diff", "--name-only"]),
|
|
35413
|
-
...gitLines(cwd, ["diff", "--cached", "--name-only"])
|
|
35414
|
-
],
|
|
35415
|
-
untracked: gitLines(cwd, ["ls-files", "--others", "--exclude-standard"])
|
|
35416
|
-
});
|
|
35417
|
-
}
|
|
35418
|
-
|
|
35419
|
-
class ConvergeExecuteError extends Error {
|
|
35420
|
-
executeError;
|
|
35421
|
-
constructor(executeError) {
|
|
35422
|
-
super(executeError instanceof Error ? executeError.message : String(executeError));
|
|
35423
|
-
this.executeError = executeError;
|
|
35424
|
-
}
|
|
35425
|
-
}
|
|
35426
|
-
async function runConvergeIteration(ctx) {
|
|
35427
|
-
let result;
|
|
35428
|
-
try {
|
|
35429
|
-
result = await ctx.execute({ task: ctx.task, prior_review: ctx.prior_review }, {
|
|
35430
|
-
loopId: ctx.loopId,
|
|
35431
|
-
iteration: ctx.iteration,
|
|
35432
|
-
...ctx.signal && { signal: ctx.signal }
|
|
35433
|
-
});
|
|
35434
|
-
} catch (e) {
|
|
35435
|
-
throw new ConvergeExecuteError(e);
|
|
35436
|
-
}
|
|
35437
|
-
if (!result.ok) {
|
|
35824
|
+
return Object.entries(shape).map(([name, field]) => {
|
|
35825
|
+
const description = getSchemaDescription(field);
|
|
35826
|
+
const isOptional = isSchemaOptional(field);
|
|
35438
35827
|
return {
|
|
35439
|
-
|
|
35440
|
-
|
|
35828
|
+
name,
|
|
35829
|
+
description,
|
|
35830
|
+
required: !isOptional
|
|
35441
35831
|
};
|
|
35442
|
-
}
|
|
35443
|
-
const reviewText = result.outputs.review ?? "";
|
|
35444
|
-
const review = parseReview(reviewText);
|
|
35445
|
-
const usage = sumTraceUsage(result.trace);
|
|
35446
|
-
const { changedFiles, workspaceWarnings } = gitWorkspace(ctx.cwd);
|
|
35447
|
-
appendIteration(ctx.cwd, ctx.loopId, {
|
|
35448
|
-
implementSummary: capSummary(result.outputs.implement ?? ""),
|
|
35449
|
-
changedFiles,
|
|
35450
|
-
workspaceWarnings,
|
|
35451
|
-
checksRun: review.checksRun,
|
|
35452
|
-
verdict: review.verdict,
|
|
35453
|
-
findingCount: review.findingCount,
|
|
35454
|
-
decision: review.verdict,
|
|
35455
|
-
checkDurationMs: reviewDurationMs(result.trace),
|
|
35456
|
-
...usage && { usage },
|
|
35457
|
-
...result.auditRunId && { auditRef: result.auditRunId }
|
|
35458
|
-
});
|
|
35459
|
-
const stopStatus = review.verdict === "proceed" ? "converged" : review.verdict === "block" ? "blocked" : undefined;
|
|
35460
|
-
return {
|
|
35461
|
-
ok: true,
|
|
35462
|
-
verdict: review.verdict,
|
|
35463
|
-
findingCount: review.findingCount,
|
|
35464
|
-
checksRun: review.checksRun,
|
|
35465
|
-
decision: review.verdict,
|
|
35466
|
-
changedFiles,
|
|
35467
|
-
workspaceWarnings,
|
|
35468
|
-
...usage && { usage },
|
|
35469
|
-
...result.auditRunId && { auditRunId: result.auditRunId },
|
|
35470
|
-
reviewText,
|
|
35471
|
-
...stopStatus && { stopStatus }
|
|
35472
|
-
};
|
|
35473
|
-
}
|
|
35474
|
-
async function convergeLoop(opts) {
|
|
35475
|
-
const { loopId } = startLoop(opts.cwd, {
|
|
35476
|
-
scope: opts.scope,
|
|
35477
|
-
task: opts.task,
|
|
35478
|
-
maxIterations: opts.maxIterations,
|
|
35479
|
-
loopId: opts.loopId,
|
|
35480
|
-
force: opts.force
|
|
35481
35832
|
});
|
|
35482
|
-
|
|
35483
|
-
|
|
35484
|
-
|
|
35485
|
-
|
|
35486
|
-
|
|
35487
|
-
|
|
35488
|
-
|
|
35489
|
-
|
|
35490
|
-
|
|
35491
|
-
|
|
35492
|
-
|
|
35493
|
-
|
|
35494
|
-
|
|
35495
|
-
|
|
35496
|
-
|
|
35497
|
-
|
|
35498
|
-
|
|
35499
|
-
|
|
35500
|
-
|
|
35501
|
-
});
|
|
35502
|
-
throw e.executeError;
|
|
35503
|
-
}
|
|
35504
|
-
throw e;
|
|
35505
|
-
}
|
|
35506
|
-
if (!iter.ok) {
|
|
35507
|
-
status = "blocked";
|
|
35508
|
-
stopLoop(opts.cwd, loopId, { status, reason: iter.failure });
|
|
35509
|
-
return { loopId, iterations, status, failure: iter.failure };
|
|
35510
|
-
}
|
|
35511
|
-
iterations++;
|
|
35512
|
-
if (iter.stopStatus !== undefined) {
|
|
35513
|
-
status = iter.stopStatus;
|
|
35514
|
-
break;
|
|
35515
|
-
}
|
|
35516
|
-
priorReview = iter.reviewText;
|
|
35517
|
-
}
|
|
35518
|
-
if (status === undefined)
|
|
35519
|
-
status = "max-iterations";
|
|
35520
|
-
const reason = status === "converged" ? "reviewer returned proceed" : status === "blocked" ? "reviewer returned block" : `reached max iterations (${opts.maxIterations}) without converging`;
|
|
35521
|
-
stopLoop(opts.cwd, loopId, { status, reason });
|
|
35522
|
-
return { loopId, iterations, status };
|
|
35523
|
-
}
|
|
35524
|
-
function buildExecute(manifest, registry3, scope, cwd) {
|
|
35525
|
-
const baseAdapters = {};
|
|
35526
|
-
for (const p of Object.values(manifest.participants)) {
|
|
35527
|
-
if (!(p.agent in baseAdapters)) {
|
|
35528
|
-
const agent = registry3.agents[p.agent];
|
|
35529
|
-
if (!agent)
|
|
35530
|
-
continue;
|
|
35531
|
-
baseAdapters[p.agent] = buildAdapter(agent);
|
|
35532
|
-
}
|
|
35533
|
-
}
|
|
35534
|
-
return makeAuditedExecute(manifest, baseAdapters, registry3, scope, cwd, new FileSessionStore(defaultSessionDir()), new AuditStore);
|
|
35535
|
-
}
|
|
35536
|
-
function makeAuditedExecute(manifest, baseAdapters, registry3, scope, cwd, sessionStore, auditStore) {
|
|
35537
|
-
return async (inputs, ctx) => {
|
|
35538
|
-
const runId = crypto.randomUUID();
|
|
35539
|
-
const recorder = new AuditRecorder(auditStore, runId, {
|
|
35540
|
-
manifestId: manifest.id,
|
|
35541
|
-
cwd,
|
|
35542
|
-
surface: "converge",
|
|
35543
|
-
scope,
|
|
35544
|
-
...ctx?.loopId !== undefined && { loopId: ctx.loopId },
|
|
35545
|
-
...ctx?.iteration !== undefined && { iteration: ctx.iteration },
|
|
35546
|
-
participants: resolveParticipantSnapshots(manifest, registry3)
|
|
35547
|
-
});
|
|
35548
|
-
recorder.runStarted();
|
|
35549
|
-
const adapters = wrapAdaptersWithSessions(wrapAdaptersWithAudit(baseAdapters, recorder), manifest, registry3, scope, sessionStore);
|
|
35550
|
-
const startedAt = Date.now();
|
|
35551
|
-
try {
|
|
35552
|
-
const result = await executeManifest(manifest, {
|
|
35553
|
-
inputs,
|
|
35554
|
-
adapters,
|
|
35555
|
-
invocationCwd: cwd,
|
|
35556
|
-
onTrace: (e) => recorder.fromTrace(e),
|
|
35557
|
-
...ctx?.signal && { signal: ctx.signal }
|
|
35558
|
-
});
|
|
35559
|
-
recorder.runCompleted(result.ok ? "ok" : "failed", Date.now() - startedAt);
|
|
35560
|
-
recorder.prune();
|
|
35561
|
-
return recorder.lastError === undefined ? { ...result, auditRunId: runId } : result;
|
|
35562
|
-
} catch (e) {
|
|
35563
|
-
recorder.runCompleted("failed", Date.now() - startedAt);
|
|
35564
|
-
recorder.prune();
|
|
35565
|
-
throw e;
|
|
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
|
|
35566
35852
|
}
|
|
35567
35853
|
};
|
|
35568
35854
|
}
|
|
35569
|
-
|
|
35570
|
-
|
|
35571
|
-
|
|
35572
|
-
|
|
35573
|
-
|
|
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;
|
|
35574
35873
|
}
|
|
35575
|
-
|
|
35576
|
-
|
|
35874
|
+
const index = this._buffer.indexOf(`
|
|
35875
|
+
`);
|
|
35876
|
+
if (index === -1) {
|
|
35877
|
+
return null;
|
|
35577
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;
|
|
35578
35885
|
}
|
|
35579
|
-
return null;
|
|
35580
35886
|
}
|
|
35581
|
-
|
|
35582
|
-
|
|
35583
|
-
|
|
35584
|
-
|
|
35585
|
-
|
|
35586
|
-
--manifest <path> Convergence manifest. Default: the built-in converge manifest.
|
|
35587
|
-
--max-iterations <n> Iteration budget. Default: 3.
|
|
35588
|
-
--loop-id <id> Reuse/seed a loop id. Default: generated.
|
|
35589
|
-
--allow-unenforced-permissions
|
|
35590
|
-
Run even when the manifest declares permissions its
|
|
35591
|
-
adapter cannot enforce (emits a warning each run).
|
|
35592
|
-
Default off: such a manifest is refused before running.
|
|
35593
|
-
|
|
35594
|
-
Runs the implement/check loop to convergence and records it under chit's
|
|
35595
|
-
state dir (keyed by repo, not in the worktree). Stops at the reviewer's verdict: proceed ->
|
|
35596
|
-
converged, block -> blocked, else revise and retry up to the budget. An
|
|
35597
|
-
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) + `
|
|
35598
35892
|
`;
|
|
35599
|
-
|
|
35600
|
-
class UsageError extends Error {
|
|
35601
35893
|
}
|
|
35602
|
-
|
|
35603
|
-
|
|
35604
|
-
|
|
35605
|
-
|
|
35606
|
-
|
|
35607
|
-
|
|
35608
|
-
|
|
35609
|
-
|
|
35610
|
-
|
|
35611
|
-
|
|
35612
|
-
|
|
35613
|
-
const v = argv[++i];
|
|
35614
|
-
if (v === undefined)
|
|
35615
|
-
throw new UsageError(`${key} requires a value`);
|
|
35616
|
-
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();
|
|
35617
35905
|
};
|
|
35618
|
-
|
|
35619
|
-
|
|
35620
|
-
|
|
35621
|
-
|
|
35622
|
-
|
|
35623
|
-
|
|
35624
|
-
|
|
35625
|
-
|
|
35626
|
-
|
|
35627
|
-
|
|
35628
|
-
|
|
35629
|
-
|
|
35630
|
-
|
|
35631
|
-
|
|
35632
|
-
|
|
35633
|
-
|
|
35634
|
-
|
|
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);
|
|
35635
35928
|
}
|
|
35636
|
-
maxIterations = n;
|
|
35637
|
-
} else {
|
|
35638
|
-
throw new UsageError(`unknown flag ${JSON.stringify(a)}`);
|
|
35639
35929
|
}
|
|
35640
35930
|
}
|
|
35641
|
-
|
|
35642
|
-
|
|
35643
|
-
|
|
35644
|
-
|
|
35645
|
-
|
|
35646
|
-
|
|
35647
|
-
|
|
35648
|
-
|
|
35649
|
-
|
|
35650
|
-
maxIterations,
|
|
35651
|
-
loopId,
|
|
35652
|
-
allowUnenforcedPermissions
|
|
35653
|
-
};
|
|
35654
|
-
}
|
|
35655
|
-
async function runConverge(argv, io = defaultIO) {
|
|
35656
|
-
if (argv[0] === "-h" || argv[0] === "--help") {
|
|
35657
|
-
io.out(CONVERGE_HELP);
|
|
35658
|
-
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?.();
|
|
35659
35940
|
}
|
|
35660
|
-
|
|
35661
|
-
|
|
35662
|
-
|
|
35663
|
-
|
|
35664
|
-
|
|
35665
|
-
|
|
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
|
+
}
|
|
35666
35952
|
|
|
35667
|
-
|
|
35668
|
-
|
|
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
|
+
}
|
|
35669
35974
|
}
|
|
35670
|
-
throw e;
|
|
35671
35975
|
}
|
|
35672
|
-
|
|
35673
|
-
|
|
35674
|
-
|
|
35675
|
-
|
|
35676
|
-
|
|
35677
|
-
|
|
35678
|
-
|
|
35679
|
-
|
|
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
|
+
}
|
|
35680
35991
|
}
|
|
35681
|
-
|
|
35682
|
-
|
|
35683
|
-
|
|
35684
|
-
|
|
35685
|
-
return 1;
|
|
35992
|
+
let latest;
|
|
35993
|
+
for (const c of open.values()) {
|
|
35994
|
+
if (latest === undefined || c.since > latest.since)
|
|
35995
|
+
latest = c;
|
|
35686
35996
|
}
|
|
35687
|
-
|
|
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) {
|
|
35688
36040
|
try {
|
|
35689
|
-
|
|
35690
|
-
} catch
|
|
35691
|
-
|
|
35692
|
-
`);
|
|
35693
|
-
return 1;
|
|
36041
|
+
return store.readEvents(runId);
|
|
36042
|
+
} catch {
|
|
36043
|
+
return [];
|
|
35694
36044
|
}
|
|
35695
|
-
|
|
35696
|
-
|
|
35697
|
-
|
|
35698
|
-
|
|
35699
|
-
|
|
35700
|
-
|
|
35701
|
-
|
|
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}>`;
|
|
35702
36056
|
}
|
|
35703
|
-
|
|
35704
|
-
|
|
35705
|
-
|
|
35706
|
-
|
|
35707
|
-
|
|
35708
|
-
|
|
35709
|
-
|
|
35710
|
-
|
|
35711
|
-
|
|
35712
|
-
|
|
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;
|
|
35713
36096
|
}
|
|
35714
|
-
|
|
35715
|
-
|
|
35716
|
-
|
|
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
|
+
}
|
|
35717
36102
|
}
|
|
35718
|
-
|
|
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;
|
|
35719
36111
|
try {
|
|
35720
|
-
|
|
35721
|
-
|
|
35722
|
-
scope: parsed.scope,
|
|
35723
|
-
task: parsed.task,
|
|
35724
|
-
maxIterations: parsed.maxIterations,
|
|
35725
|
-
loopId: parsed.loopId,
|
|
35726
|
-
execute: buildExecute(manifest, registry3, parsed.scope, parsed.cwd)
|
|
35727
|
-
});
|
|
36112
|
+
process.kill(pid, 0);
|
|
36113
|
+
return true;
|
|
35728
36114
|
} catch (e) {
|
|
35729
|
-
|
|
35730
|
-
`);
|
|
35731
|
-
return 1;
|
|
36115
|
+
return e.code === "EPERM";
|
|
35732
36116
|
}
|
|
35733
|
-
|
|
35734
|
-
|
|
35735
|
-
|
|
35736
|
-
|
|
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;
|
|
35737
36122
|
}
|
|
35738
|
-
|
|
35739
|
-
|
|
35740
|
-
|
|
35741
|
-
|
|
35742
|
-
|
|
35743
|
-
`);
|
|
35744
|
-
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);
|
|
35745
36128
|
}
|
|
35746
36129
|
|
|
35747
36130
|
// src/surfaces/mcp/converge-engine.ts
|
|
@@ -36195,6 +36578,25 @@ function summarizeRunForStatus(run) {
|
|
|
36195
36578
|
audited: run.recorder !== undefined && run.recorder.lastError === undefined
|
|
36196
36579
|
};
|
|
36197
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
|
+
}
|
|
36198
36600
|
function byNewest(items) {
|
|
36199
36601
|
return [...items].sort((a, b) => b.startedAtMs - a.startedAtMs);
|
|
36200
36602
|
}
|
|
@@ -36207,21 +36609,34 @@ function recentRuns(auditStore, recentLimit) {
|
|
|
36207
36609
|
return [];
|
|
36208
36610
|
}
|
|
36209
36611
|
}
|
|
36210
|
-
function buildStatus(runs, convergeSessions, auditStore, recentLimit) {
|
|
36612
|
+
function buildStatus(runs, convergeSessions, auditStore, jobStore, recentLimit, nowMs) {
|
|
36211
36613
|
return {
|
|
36212
36614
|
active: {
|
|
36213
36615
|
runs: byNewest(runs.list()).map(summarizeRunForStatus),
|
|
36214
36616
|
loops: byNewest(convergeSessions.list()).map(describeConverge)
|
|
36215
36617
|
},
|
|
36618
|
+
jobs: jobsForStatus(jobStore, recentLimit, nowMs),
|
|
36216
36619
|
recent: recentRuns(auditStore, recentLimit)
|
|
36217
36620
|
};
|
|
36218
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
|
+
}
|
|
36219
36633
|
|
|
36220
36634
|
// src/surfaces/mcp/server.ts
|
|
36221
36635
|
var runs = new RunStore;
|
|
36222
36636
|
var controllers = new Map;
|
|
36223
36637
|
var convergeSessions = new ConvergeStore;
|
|
36224
36638
|
var auditStore = new AuditStore;
|
|
36639
|
+
var jobStore = new JobStore;
|
|
36225
36640
|
var registryCache;
|
|
36226
36641
|
function getRegistry() {
|
|
36227
36642
|
registryCache ??= loadRegistry();
|
|
@@ -36271,10 +36686,10 @@ server.registerTool("chit_run_start", {
|
|
|
36271
36686
|
}
|
|
36272
36687
|
}, async ({ manifest_path, inputs, scope, cwd, allow_unenforced_permissions, audit }) => {
|
|
36273
36688
|
runs.sweep(Date.now());
|
|
36274
|
-
const path =
|
|
36689
|
+
const path = isAbsolute3(manifest_path) ? manifest_path : resolve4(process.cwd(), manifest_path);
|
|
36275
36690
|
let raw;
|
|
36276
36691
|
try {
|
|
36277
|
-
raw = JSON.parse(
|
|
36692
|
+
raw = JSON.parse(readFileSync11(path, "utf-8"));
|
|
36278
36693
|
} catch (e) {
|
|
36279
36694
|
return errorResult(`could not read manifest at ${path}: ${e.message}`);
|
|
36280
36695
|
}
|
|
@@ -36398,36 +36813,6 @@ server.registerTool("chit_run_trace", {
|
|
|
36398
36813
|
trace
|
|
36399
36814
|
});
|
|
36400
36815
|
});
|
|
36401
|
-
function prepareConvergeExecute(raw, scope, cwd, allowUnenforced) {
|
|
36402
|
-
let manifest;
|
|
36403
|
-
try {
|
|
36404
|
-
manifest = parseManifest(raw);
|
|
36405
|
-
} catch (e) {
|
|
36406
|
-
return { ok: false, error: e.message };
|
|
36407
|
-
}
|
|
36408
|
-
const shapeError = validateConvergeManifest(manifest);
|
|
36409
|
-
if (shapeError)
|
|
36410
|
-
return { ok: false, error: shapeError };
|
|
36411
|
-
const registry3 = getRegistry();
|
|
36412
|
-
const unknown3 = findUnknownAgents(manifest, registry3);
|
|
36413
|
-
if (unknown3.length > 0) {
|
|
36414
|
-
return {
|
|
36415
|
-
ok: false,
|
|
36416
|
-
error: `unknown agent(s): ${unknown3.map((u) => `${u.agentId} (participant "${u.participantId}")`).join(", ")}`
|
|
36417
|
-
};
|
|
36418
|
-
}
|
|
36419
|
-
const gaps = findEnforcementGaps(manifest, registry3);
|
|
36420
|
-
if (gaps.length > 0 && !allowUnenforced) {
|
|
36421
|
-
return {
|
|
36422
|
-
ok: false,
|
|
36423
|
-
error: `cannot enforce required permissions:
|
|
36424
|
-
${formatEnforcementGaps(gaps)}
|
|
36425
|
-
Pass allow_unenforced_permissions=true to run anyway.`
|
|
36426
|
-
};
|
|
36427
|
-
}
|
|
36428
|
-
const warnings = gaps.map((g) => `unenforced permission: participant "${g.participantId}" requires ${g.permission}`);
|
|
36429
|
-
return { ok: true, execute: buildExecute(manifest, registry3, scope, cwd), warnings };
|
|
36430
|
-
}
|
|
36431
36816
|
server.registerTool("chit_converge_start", {
|
|
36432
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`.",
|
|
36433
36818
|
inputSchema: {
|
|
@@ -36451,19 +36836,19 @@ server.registerTool("chit_converge_start", {
|
|
|
36451
36836
|
allow_unenforced_permissions
|
|
36452
36837
|
}) => {
|
|
36453
36838
|
convergeSessions.sweep(Date.now());
|
|
36454
|
-
const runCwd =
|
|
36839
|
+
const runCwd = resolve4(cwd ?? process.cwd());
|
|
36455
36840
|
let raw;
|
|
36456
36841
|
if (manifest_path) {
|
|
36457
|
-
const path =
|
|
36842
|
+
const path = isAbsolute3(manifest_path) ? manifest_path : resolve4(runCwd, manifest_path);
|
|
36458
36843
|
try {
|
|
36459
|
-
raw = JSON.parse(
|
|
36844
|
+
raw = JSON.parse(readFileSync11(path, "utf-8"));
|
|
36460
36845
|
} catch (e) {
|
|
36461
36846
|
return errorResult(`could not read manifest at ${path}: ${e.message}`);
|
|
36462
36847
|
}
|
|
36463
36848
|
} else {
|
|
36464
36849
|
raw = DEFAULT_CONVERGE_MANIFEST;
|
|
36465
36850
|
}
|
|
36466
|
-
const prep = prepareConvergeExecute(raw, scope, runCwd, allow_unenforced_permissions);
|
|
36851
|
+
const prep = prepareConvergeExecute(raw, getRegistry(), scope, runCwd, allow_unenforced_permissions);
|
|
36467
36852
|
if (!prep.ok)
|
|
36468
36853
|
return errorResult(prep.error);
|
|
36469
36854
|
let session;
|
|
@@ -36505,6 +36890,15 @@ server.registerTool("chit_converge_next", {
|
|
|
36505
36890
|
}
|
|
36506
36891
|
server.sendLoggingMessage({ level: "info", data: message, logger: "chit" }).catch(() => {});
|
|
36507
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
|
+
}
|
|
36508
36902
|
const iterationNo = session.iteration + 1;
|
|
36509
36903
|
heartbeat(`${loop_id} \xB7 iteration ${iterationNo} \xB7 starting`);
|
|
36510
36904
|
try {
|
|
@@ -36543,6 +36937,7 @@ server.registerTool("chit_converge_next", {
|
|
|
36543
36937
|
} catch (e) {
|
|
36544
36938
|
return errorResult(e.message);
|
|
36545
36939
|
} finally {
|
|
36940
|
+
releaseLock(loopLock);
|
|
36546
36941
|
convergeSessions.touch(loop_id, Date.now());
|
|
36547
36942
|
}
|
|
36548
36943
|
});
|
|
@@ -36606,7 +37001,192 @@ server.registerTool("chit_status", {
|
|
|
36606
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.")
|
|
36607
37002
|
}
|
|
36608
37003
|
}, async ({ recent_limit }) => {
|
|
36609
|
-
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
|
+
});
|
|
36610
37190
|
});
|
|
36611
37191
|
async function startMcpServer() {
|
|
36612
37192
|
await server.connect(new StdioServerTransport);
|
|
@@ -36836,9 +37416,9 @@ ${AUDIT_HELP}`);
|
|
|
36836
37416
|
}
|
|
36837
37417
|
|
|
36838
37418
|
// src/cli/doctor.ts
|
|
36839
|
-
import { randomUUID as
|
|
36840
|
-
import { mkdirSync as
|
|
36841
|
-
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";
|
|
36842
37422
|
var defaultIO3 = {
|
|
36843
37423
|
out: (s) => process.stdout.write(s),
|
|
36844
37424
|
err: (s) => process.stderr.write(s)
|
|
@@ -36906,10 +37486,10 @@ function checkGitRepo(deps) {
|
|
|
36906
37486
|
}
|
|
36907
37487
|
function checkAuditDir(deps) {
|
|
36908
37488
|
try {
|
|
36909
|
-
|
|
36910
|
-
const probeFile =
|
|
36911
|
-
|
|
36912
|
-
|
|
37489
|
+
mkdirSync6(deps.auditDir, { recursive: true });
|
|
37490
|
+
const probeFile = join9(deps.auditDir, `.doctor-${randomUUID5()}`);
|
|
37491
|
+
writeFileSync6(probeFile, "ok");
|
|
37492
|
+
rmSync7(probeFile);
|
|
36913
37493
|
return { name: "audit dir", status: "pass", detail: `writable (${deps.auditDir})` };
|
|
36914
37494
|
} catch (e) {
|
|
36915
37495
|
return {
|
|
@@ -37027,7 +37607,7 @@ ${DOCTOR_HELP}`);
|
|
|
37027
37607
|
|
|
37028
37608
|
// src/cli/loop-log.ts
|
|
37029
37609
|
init_src();
|
|
37030
|
-
import { resolve as
|
|
37610
|
+
import { resolve as resolve5 } from "path";
|
|
37031
37611
|
var defaultIO4 = {
|
|
37032
37612
|
out: (s) => process.stdout.write(s),
|
|
37033
37613
|
err: (s) => process.stderr.write(s)
|
|
@@ -37158,7 +37738,7 @@ function runLoopLog(argv, io = defaultIO4) {
|
|
|
37158
37738
|
}
|
|
37159
37739
|
try {
|
|
37160
37740
|
const p = parseFlags(verb, argv.slice(1));
|
|
37161
|
-
const cwd =
|
|
37741
|
+
const cwd = resolve5(p.flags.cwd ?? ".");
|
|
37162
37742
|
if (verb === "start") {
|
|
37163
37743
|
const res = startLoop(cwd, {
|
|
37164
37744
|
scope: req(p, "scope", verb),
|
|
@@ -37531,6 +38111,16 @@ async function runMain(argv) {
|
|
|
37531
38111
|
return runAudit(argv.slice(1));
|
|
37532
38112
|
if (argv[0] === "doctor")
|
|
37533
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
|
+
}
|
|
37534
38124
|
let args;
|
|
37535
38125
|
try {
|
|
37536
38126
|
args = parseArgs(argv);
|
|
@@ -37571,7 +38161,7 @@ ${HELP}`);
|
|
|
37571
38161
|
}
|
|
37572
38162
|
let manifestRaw;
|
|
37573
38163
|
try {
|
|
37574
|
-
manifestRaw = JSON.parse(
|
|
38164
|
+
manifestRaw = JSON.parse(readFileSync16(args.manifestPath, "utf-8"));
|
|
37575
38165
|
} catch (e) {
|
|
37576
38166
|
process.stderr.write(`chit: failed to read manifest ${args.manifestPath}: ${e.message}
|
|
37577
38167
|
`);
|
|
@@ -37782,7 +38372,7 @@ ${HELP}`);
|
|
|
37782
38372
|
`);
|
|
37783
38373
|
return 2;
|
|
37784
38374
|
}
|
|
37785
|
-
const outputDir = args.outputDir ??
|
|
38375
|
+
const outputDir = args.outputDir ?? join14(homedir8(), ".claude", "skills");
|
|
37786
38376
|
const runtimePath = args.runtimePath ?? defaultRuntimePath();
|
|
37787
38377
|
try {
|
|
37788
38378
|
const result = installClaudeSkill({
|
|
@@ -37825,7 +38415,7 @@ ${HELP}`);
|
|
|
37825
38415
|
}
|
|
37826
38416
|
let raw2;
|
|
37827
38417
|
try {
|
|
37828
|
-
raw2 = JSON.parse(
|
|
38418
|
+
raw2 = JSON.parse(readFileSync16(args.manifestPath, "utf-8"));
|
|
37829
38419
|
} catch (e) {
|
|
37830
38420
|
process.stderr.write(`chit: failed to read manifest ${args.manifestPath}: ${e.message}
|
|
37831
38421
|
`);
|
|
@@ -37964,13 +38554,13 @@ async function runStudio(args) {
|
|
|
37964
38554
|
`);
|
|
37965
38555
|
process.stdout.write(`Press Ctrl-C to stop.
|
|
37966
38556
|
`);
|
|
37967
|
-
await new Promise((
|
|
38557
|
+
await new Promise((resolve7) => {
|
|
37968
38558
|
process.on("SIGINT", () => {
|
|
37969
38559
|
process.stdout.write(`
|
|
37970
38560
|
chit studio: stopped
|
|
37971
38561
|
`);
|
|
37972
38562
|
handle.stop();
|
|
37973
|
-
|
|
38563
|
+
resolve7();
|
|
37974
38564
|
});
|
|
37975
38565
|
});
|
|
37976
38566
|
return 0;
|