@kontextmind/kxm 0.7.25 → 0.7.26
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/.claude-plugin/marketplace.json +1 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +104 -11
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +62 -2
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +62 -2
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-runs/SKILL.md +4 -0
- package/plugins/kxm/src/cli/vnext.ts +113 -9
- package/plugins/kxm/src/cli.ts +14 -2
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/vnext-runtime-store.ts +14 -4
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +61 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "kxm",
|
|
4
4
|
"displayName": "KXM",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.26",
|
|
6
6
|
"description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "KontextMind",
|
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -33705,7 +33705,21 @@ ${driveLine}` : ""}`
|
|
|
33705
33705
|
return 1;
|
|
33706
33706
|
}
|
|
33707
33707
|
}
|
|
33708
|
-
|
|
33708
|
+
var DEFAULT_DRIVE_WAIT_MS = 6e4;
|
|
33709
|
+
var MAX_DRIVE_WAIT_MS = 6e5;
|
|
33710
|
+
function parseDriveWaitTimeoutMs(raw) {
|
|
33711
|
+
if (raw === void 0) return DEFAULT_DRIVE_WAIT_MS;
|
|
33712
|
+
const value = typeof raw === "number" ? raw : Number(typeof raw === "string" ? raw.trim() : raw);
|
|
33713
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_DRIVE_WAIT_MS) return void 0;
|
|
33714
|
+
return value;
|
|
33715
|
+
}
|
|
33716
|
+
function sleepMs(ms) {
|
|
33717
|
+
return new Promise((resolve31) => setTimeout(resolve31, ms));
|
|
33718
|
+
}
|
|
33719
|
+
function driveWaitCompleted(drive) {
|
|
33720
|
+
return drive?.verified === true && drive.receipt?.settlement?.kind === "terminal" && drive.receipt.settlement.status === "completed";
|
|
33721
|
+
}
|
|
33722
|
+
async function cmdVnextRunDrive(runtime, runId, simulated, options = {}) {
|
|
33709
33723
|
try {
|
|
33710
33724
|
const projectRoot = discoverVnextProjectRoot(runtime.cwd);
|
|
33711
33725
|
if (!projectRoot) {
|
|
@@ -33717,8 +33731,19 @@ async function cmdVnextRunDrive(runtime, runId, simulated) {
|
|
|
33717
33731
|
print(runtime.io, runtime.json, { ok: true, command: "runs drive", dryRun: true, runId, mode }, `drive plan: run ${runId} in ${mode} mode (no events written)`);
|
|
33718
33732
|
return 0;
|
|
33719
33733
|
}
|
|
33734
|
+
const timeoutMs = options.wait === true ? parseDriveWaitTimeoutMs(options.timeoutMs) : DEFAULT_DRIVE_WAIT_MS;
|
|
33735
|
+
if (options.wait === true && timeoutMs === void 0) {
|
|
33736
|
+
print(
|
|
33737
|
+
runtime.io,
|
|
33738
|
+
runtime.json,
|
|
33739
|
+
{ ok: false, command: "runs drive", error: "run_drive_timeout_invalid" },
|
|
33740
|
+
`run drive --wait --timeout-ms must be an integer between 1 and ${MAX_DRIVE_WAIT_MS}`
|
|
33741
|
+
);
|
|
33742
|
+
return 1;
|
|
33743
|
+
}
|
|
33720
33744
|
const supervisor = await (vnextDriveCliSeams.ensureSupervisor ?? ensureVnextSupervisor)({ env: runtime.env });
|
|
33721
|
-
const
|
|
33745
|
+
const request = vnextDriveCliSeams.runtimeRequest ?? vnextRuntimeRequest;
|
|
33746
|
+
const result = await request(supervisor, "POST", `/v1/runs/${encodeURIComponent(runId)}/drive?projectRoot=${encodeURIComponent(projectRoot)}`, { mode });
|
|
33722
33747
|
const driveId = result.driveId;
|
|
33723
33748
|
const poll = result.poll;
|
|
33724
33749
|
const status = result.status;
|
|
@@ -33726,13 +33751,36 @@ async function cmdVnextRunDrive(runtime, runId, simulated) {
|
|
|
33726
33751
|
print(runtime.io, runtime.json, { ok: false, command: "runs drive", error: "run_drive_io_failed" }, "run drive failed because a local operation did not complete");
|
|
33727
33752
|
return 1;
|
|
33728
33753
|
}
|
|
33729
|
-
|
|
33730
|
-
|
|
33731
|
-
|
|
33732
|
-
|
|
33733
|
-
|
|
33734
|
-
|
|
33735
|
-
|
|
33754
|
+
if (options.wait !== true) {
|
|
33755
|
+
print(
|
|
33756
|
+
runtime.io,
|
|
33757
|
+
runtime.json,
|
|
33758
|
+
{ ok: true, command: "runs drive", runId, driveId, poll, mode, status },
|
|
33759
|
+
`drive ${driveId}: accepted (poll ${poll})`
|
|
33760
|
+
);
|
|
33761
|
+
return 0;
|
|
33762
|
+
}
|
|
33763
|
+
const deadline = Date.now() + timeoutMs;
|
|
33764
|
+
let interval = 25;
|
|
33765
|
+
while (true) {
|
|
33766
|
+
const stateResult = await request(supervisor, "GET", `/v1/runs/${encodeURIComponent(runId)}?projectRoot=${encodeURIComponent(projectRoot)}`);
|
|
33767
|
+
const drive = stateResult.drive;
|
|
33768
|
+
if (drive?.receipt) {
|
|
33769
|
+
print(
|
|
33770
|
+
runtime.io,
|
|
33771
|
+
runtime.json,
|
|
33772
|
+
{ ok: driveWaitCompleted(drive), command: "runs drive", receipt: drive.receipt, verified: drive.verified === true },
|
|
33773
|
+
JSON.stringify(drive.receipt, null, 2)
|
|
33774
|
+
);
|
|
33775
|
+
return driveWaitCompleted(drive) ? 0 : 1;
|
|
33776
|
+
}
|
|
33777
|
+
const remaining = deadline - Date.now();
|
|
33778
|
+
if (remaining <= 0) break;
|
|
33779
|
+
await sleepMs(Math.min(interval, remaining));
|
|
33780
|
+
interval = Math.min(Math.floor(interval * 1.5), 250);
|
|
33781
|
+
}
|
|
33782
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs drive", error: "timeout" }, "drive wait timed out");
|
|
33783
|
+
return 1;
|
|
33736
33784
|
} catch (error) {
|
|
33737
33785
|
if (error instanceof VnextConfigError) {
|
|
33738
33786
|
print(runtime.io, runtime.json, { ok: false, command: "runs drive", error: "run_drive_failed", issues: error.issues }, `run drive failed: ${error.message}`);
|
|
@@ -33920,6 +33968,45 @@ async function cmdVnextRuntime(runtime, action) {
|
|
|
33920
33968
|
return 1;
|
|
33921
33969
|
}
|
|
33922
33970
|
}
|
|
33971
|
+
async function cmdVnextRunReceipt(runtime, runId, options = {}) {
|
|
33972
|
+
try {
|
|
33973
|
+
const projectRoot = discoverVnextProjectRoot(runtime.cwd);
|
|
33974
|
+
if (!projectRoot) {
|
|
33975
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "project_required" }, "kxm runs receipt requires a vNext project (run kxm init first)");
|
|
33976
|
+
return 1;
|
|
33977
|
+
}
|
|
33978
|
+
const supervisor = await (vnextDriveCliSeams.ensureSupervisor ?? ensureVnextSupervisor)({ env: runtime.env });
|
|
33979
|
+
const result = await (vnextDriveCliSeams.runtimeRequest ?? vnextRuntimeRequest)(
|
|
33980
|
+
supervisor,
|
|
33981
|
+
"GET",
|
|
33982
|
+
`/v1/runs/${encodeURIComponent(runId)}/drive?projectRoot=${encodeURIComponent(projectRoot)}`
|
|
33983
|
+
);
|
|
33984
|
+
if (!Array.isArray(result.receipts)) {
|
|
33985
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "run_receipt_io_failed" }, "run receipt failed because a local operation did not complete");
|
|
33986
|
+
return 1;
|
|
33987
|
+
}
|
|
33988
|
+
const receipts = result.receipts;
|
|
33989
|
+
if (receipts.length === 0) {
|
|
33990
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "no_receipts" }, `run ${runId} has no drive receipts`);
|
|
33991
|
+
return 1;
|
|
33992
|
+
}
|
|
33993
|
+
if (options.all === true) {
|
|
33994
|
+
print(runtime.io, runtime.json, { ok: true, command: "runs receipt", receipts }, JSON.stringify(receipts, null, 2));
|
|
33995
|
+
return 0;
|
|
33996
|
+
}
|
|
33997
|
+
const latest = receipts[0];
|
|
33998
|
+
const settlement = latest && typeof latest === "object" && "settlement" in latest ? latest.settlement : latest;
|
|
33999
|
+
print(runtime.io, runtime.json, { ok: true, command: "runs receipt", receipt: latest }, JSON.stringify(settlement, null, 2));
|
|
34000
|
+
return 0;
|
|
34001
|
+
} catch (error) {
|
|
34002
|
+
if (error instanceof VnextConfigError) {
|
|
34003
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "run_receipt_failed", issues: error.issues }, `run receipt failed: ${error.message}`);
|
|
34004
|
+
return 1;
|
|
34005
|
+
}
|
|
34006
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "run_receipt_io_failed" }, "run receipt failed because a local operation did not complete");
|
|
34007
|
+
return 1;
|
|
34008
|
+
}
|
|
34009
|
+
}
|
|
33923
34010
|
|
|
33924
34011
|
// plugins/kxm/src/cli/tasks.ts
|
|
33925
34012
|
async function cmdSuggest(runtime, promptParts) {
|
|
@@ -48695,8 +48782,14 @@ function createProgram(ctx, result) {
|
|
|
48695
48782
|
addGlobalOptions(runCmd.command("status").description("Show the projected status of a run, including durable drive receipt state (open / receipt verified / unsettled / orphaned)")).argument("<runId>", "Run id").action(async function runStatusAction(runId) {
|
|
48696
48783
|
result.code = await cmdVnextRunStatus(runtimeFrom(ctx, this), runId);
|
|
48697
48784
|
});
|
|
48698
|
-
addGlobalOptions(runCmd.command("drive").description("Drive a run with an explicit model-free simulation")).argument("<runId>", "Run id").option("--simulated", "Use the model-free simulation producer").action(async function runDriveAction(runId, options) {
|
|
48699
|
-
result.code = await cmdVnextRunDrive(runtimeFrom(ctx, this), runId, options.simulated === true
|
|
48785
|
+
addGlobalOptions(runCmd.command("drive").description("Drive a run with an explicit model-free simulation")).argument("<runId>", "Run id").option("--simulated", "Use the model-free simulation producer").option("--wait", "Wait until a drive receipt is recorded; exits 0 only for a VERIFIED COMPLETED settlement").option("--timeout-ms <n>", "Wait timeout in milliseconds (default 60000, max 600000)").action(async function runDriveAction(runId, options) {
|
|
48786
|
+
result.code = await cmdVnextRunDrive(runtimeFrom(ctx, this), runId, options.simulated === true, {
|
|
48787
|
+
wait: options.wait === true,
|
|
48788
|
+
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {}
|
|
48789
|
+
});
|
|
48790
|
+
});
|
|
48791
|
+
addGlobalOptions(runCmd.command("receipt").description("Print the newest drive receipt for a run")).argument("<runId>", "Run id").option("--all", "Print the capped receipt list for the run").action(async function runReceiptAction(runId, options) {
|
|
48792
|
+
result.code = await cmdVnextRunReceipt(runtimeFrom(ctx, this), runId, { all: options.all === true });
|
|
48700
48793
|
});
|
|
48701
48794
|
addGlobalOptions(runCmd.command("cancel").description("Durably request cancellation of a run")).argument("<runId>", "Run id").action(async function runCancelAction(runId) {
|
|
48702
48795
|
result.code = await cmdVnextRunCancel(runtimeFrom(ctx, this), runId);
|
|
@@ -17121,7 +17121,7 @@ async function deliverInboxNotification(messageId, delivered, notify) {
|
|
|
17121
17121
|
}
|
|
17122
17122
|
|
|
17123
17123
|
// plugins/kxm/src/mcp-server.ts
|
|
17124
|
-
var VERSION = "0.7.
|
|
17124
|
+
var VERSION = "0.7.26";
|
|
17125
17125
|
var inbox = /* @__PURE__ */ new Map();
|
|
17126
17126
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
|
17127
17127
|
var meshClient;
|
|
@@ -18764,9 +18764,15 @@ var VnextRunEventStore = class {
|
|
|
18764
18764
|
}
|
|
18765
18765
|
driveReceiptsForRun(runId, limit = 20) {
|
|
18766
18766
|
const rows = this.database.prepare(`
|
|
18767
|
-
SELECT receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
|
|
18767
|
+
SELECT drive_id, receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
|
|
18768
18768
|
`).all(runId, limit);
|
|
18769
|
-
return rows.map((row) =>
|
|
18769
|
+
return rows.map((row) => {
|
|
18770
|
+
try {
|
|
18771
|
+
return parseDriveReceipt(row.receipt);
|
|
18772
|
+
} catch {
|
|
18773
|
+
return { driveId: row.drive_id, divergence: "receipt unreadable" };
|
|
18774
|
+
}
|
|
18775
|
+
});
|
|
18770
18776
|
}
|
|
18771
18777
|
insertCapability(row) {
|
|
18772
18778
|
this.database.prepare(`
|
|
@@ -22103,6 +22109,9 @@ function clearVnextDriveSession(storePath, runId, token) {
|
|
|
22103
22109
|
if (!current || current.token !== token) return;
|
|
22104
22110
|
delete current.driveSession;
|
|
22105
22111
|
}
|
|
22112
|
+
function vnextDriveSession(storePath, runId) {
|
|
22113
|
+
return owners.get(storePath)?.admitted.get(runId)?.driveSession;
|
|
22114
|
+
}
|
|
22106
22115
|
function vnextOpenDriveSessions(storePath) {
|
|
22107
22116
|
const owner = owners.get(storePath);
|
|
22108
22117
|
if (!owner) return [];
|
|
@@ -27388,6 +27397,20 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
27388
27397
|
sendJson(response, 200, { ok: true, events });
|
|
27389
27398
|
return;
|
|
27390
27399
|
}
|
|
27400
|
+
if (request.method === "GET" && sub === "drive") {
|
|
27401
|
+
const run = context.eventStore.run(runId);
|
|
27402
|
+
if (!run) throw runtimeError("run_unknown", runId, "run not found");
|
|
27403
|
+
const activeSession = vnextDriveSession(context.eventStore.path, runId);
|
|
27404
|
+
const session = activeSession ? {
|
|
27405
|
+
driveId: activeSession.driveId,
|
|
27406
|
+
mode: activeSession.mode,
|
|
27407
|
+
openedAt: activeSession.openedAt,
|
|
27408
|
+
deadlineAt: activeSession.deadlineAt ?? null
|
|
27409
|
+
} : null;
|
|
27410
|
+
const receipts = context.eventStore.driveReceiptsForRun(runId, 20);
|
|
27411
|
+
sendJson(response, 200, { ok: true, session, receipts });
|
|
27412
|
+
return;
|
|
27413
|
+
}
|
|
27391
27414
|
if (request.method === "POST" && sub === "cancel") {
|
|
27392
27415
|
const body = await readJsonBody(request);
|
|
27393
27416
|
const result = cancelVnextRun(context, runId, {
|
|
@@ -27421,6 +27444,43 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
27421
27444
|
return;
|
|
27422
27445
|
}
|
|
27423
27446
|
}
|
|
27447
|
+
const driveMatch = /^\/v1\/drives\/([A-Za-z0-9_-]+)$/.exec(url.pathname);
|
|
27448
|
+
if (request.method === "GET" && driveMatch) {
|
|
27449
|
+
const driveId = driveMatch[1];
|
|
27450
|
+
const projectRoot = url.searchParams.get("projectRoot") ?? "";
|
|
27451
|
+
if (!projectRoot) {
|
|
27452
|
+
sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: "projectRoot query parameter is required" });
|
|
27453
|
+
return;
|
|
27454
|
+
}
|
|
27455
|
+
const context = contextFor(projectRoot);
|
|
27456
|
+
let receipt;
|
|
27457
|
+
try {
|
|
27458
|
+
receipt = context.eventStore.driveReceipt(driveId);
|
|
27459
|
+
} catch {
|
|
27460
|
+
sendJson(response, 200, { ok: true, receipt: null, verified: false, divergence: "receipt unreadable" });
|
|
27461
|
+
return;
|
|
27462
|
+
}
|
|
27463
|
+
if (!receipt) {
|
|
27464
|
+
sendJson(response, 404, { ok: false, error: "drive_receipt_missing", message: "receipt not found" });
|
|
27465
|
+
return;
|
|
27466
|
+
}
|
|
27467
|
+
const runId = receipt.runId;
|
|
27468
|
+
const stored = context.eventStore.run(runId);
|
|
27469
|
+
if (!stored) {
|
|
27470
|
+
sendJson(response, 200, { ok: true, receipt, verified: false, divergence: "run missing" });
|
|
27471
|
+
return;
|
|
27472
|
+
}
|
|
27473
|
+
const state = foldStoredVnextRun(context, stored);
|
|
27474
|
+
const events = context.eventStore.events(runId, 0, 1e6);
|
|
27475
|
+
const checked = verifyVnextDriveReceipt(receipt, events, state.status, { runId, driveId });
|
|
27476
|
+
sendJson(response, 200, {
|
|
27477
|
+
ok: true,
|
|
27478
|
+
receipt,
|
|
27479
|
+
verified: checked.verified,
|
|
27480
|
+
...checked.divergence !== void 0 ? { divergence: checked.divergence } : {}
|
|
27481
|
+
});
|
|
27482
|
+
return;
|
|
27483
|
+
}
|
|
27424
27484
|
const projectRunsMatch = /^\/v1\/projects\/(prj_[A-Za-z0-9_-]+)\/runs$/.exec(url.pathname);
|
|
27425
27485
|
if (request.method === "GET" && projectRunsMatch) {
|
|
27426
27486
|
const projectRoot = url.searchParams.get("projectRoot") ?? "";
|
|
@@ -18324,9 +18324,15 @@ var VnextRunEventStore = class {
|
|
|
18324
18324
|
}
|
|
18325
18325
|
driveReceiptsForRun(runId, limit = 20) {
|
|
18326
18326
|
const rows = this.database.prepare(`
|
|
18327
|
-
SELECT receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
|
|
18327
|
+
SELECT drive_id, receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
|
|
18328
18328
|
`).all(runId, limit);
|
|
18329
|
-
return rows.map((row) =>
|
|
18329
|
+
return rows.map((row) => {
|
|
18330
|
+
try {
|
|
18331
|
+
return parseDriveReceipt(row.receipt);
|
|
18332
|
+
} catch {
|
|
18333
|
+
return { driveId: row.drive_id, divergence: "receipt unreadable" };
|
|
18334
|
+
}
|
|
18335
|
+
});
|
|
18330
18336
|
}
|
|
18331
18337
|
insertCapability(row) {
|
|
18332
18338
|
this.database.prepare(`
|
|
@@ -21668,6 +21674,9 @@ function clearVnextDriveSession(storePath, runId, token) {
|
|
|
21668
21674
|
if (!current || current.token !== token) return;
|
|
21669
21675
|
delete current.driveSession;
|
|
21670
21676
|
}
|
|
21677
|
+
function vnextDriveSession(storePath, runId) {
|
|
21678
|
+
return owners.get(storePath)?.admitted.get(runId)?.driveSession;
|
|
21679
|
+
}
|
|
21671
21680
|
function vnextOpenDriveSessions(storePath) {
|
|
21672
21681
|
const owner = owners.get(storePath);
|
|
21673
21682
|
if (!owner) return [];
|
|
@@ -26902,6 +26911,20 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26902
26911
|
sendJson(response, 200, { ok: true, events });
|
|
26903
26912
|
return;
|
|
26904
26913
|
}
|
|
26914
|
+
if (request.method === "GET" && sub === "drive") {
|
|
26915
|
+
const run = context.eventStore.run(runId);
|
|
26916
|
+
if (!run) throw runtimeError("run_unknown", runId, "run not found");
|
|
26917
|
+
const activeSession = vnextDriveSession(context.eventStore.path, runId);
|
|
26918
|
+
const session = activeSession ? {
|
|
26919
|
+
driveId: activeSession.driveId,
|
|
26920
|
+
mode: activeSession.mode,
|
|
26921
|
+
openedAt: activeSession.openedAt,
|
|
26922
|
+
deadlineAt: activeSession.deadlineAt ?? null
|
|
26923
|
+
} : null;
|
|
26924
|
+
const receipts = context.eventStore.driveReceiptsForRun(runId, 20);
|
|
26925
|
+
sendJson(response, 200, { ok: true, session, receipts });
|
|
26926
|
+
return;
|
|
26927
|
+
}
|
|
26905
26928
|
if (request.method === "POST" && sub === "cancel") {
|
|
26906
26929
|
const body = await readJsonBody(request);
|
|
26907
26930
|
const result = cancelVnextRun(context, runId, {
|
|
@@ -26935,6 +26958,43 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26935
26958
|
return;
|
|
26936
26959
|
}
|
|
26937
26960
|
}
|
|
26961
|
+
const driveMatch = /^\/v1\/drives\/([A-Za-z0-9_-]+)$/.exec(url.pathname);
|
|
26962
|
+
if (request.method === "GET" && driveMatch) {
|
|
26963
|
+
const driveId = driveMatch[1];
|
|
26964
|
+
const projectRoot = url.searchParams.get("projectRoot") ?? "";
|
|
26965
|
+
if (!projectRoot) {
|
|
26966
|
+
sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: "projectRoot query parameter is required" });
|
|
26967
|
+
return;
|
|
26968
|
+
}
|
|
26969
|
+
const context = contextFor(projectRoot);
|
|
26970
|
+
let receipt;
|
|
26971
|
+
try {
|
|
26972
|
+
receipt = context.eventStore.driveReceipt(driveId);
|
|
26973
|
+
} catch {
|
|
26974
|
+
sendJson(response, 200, { ok: true, receipt: null, verified: false, divergence: "receipt unreadable" });
|
|
26975
|
+
return;
|
|
26976
|
+
}
|
|
26977
|
+
if (!receipt) {
|
|
26978
|
+
sendJson(response, 404, { ok: false, error: "drive_receipt_missing", message: "receipt not found" });
|
|
26979
|
+
return;
|
|
26980
|
+
}
|
|
26981
|
+
const runId = receipt.runId;
|
|
26982
|
+
const stored = context.eventStore.run(runId);
|
|
26983
|
+
if (!stored) {
|
|
26984
|
+
sendJson(response, 200, { ok: true, receipt, verified: false, divergence: "run missing" });
|
|
26985
|
+
return;
|
|
26986
|
+
}
|
|
26987
|
+
const state = foldStoredVnextRun(context, stored);
|
|
26988
|
+
const events = context.eventStore.events(runId, 0, 1e6);
|
|
26989
|
+
const checked = verifyVnextDriveReceipt(receipt, events, state.status, { runId, driveId });
|
|
26990
|
+
sendJson(response, 200, {
|
|
26991
|
+
ok: true,
|
|
26992
|
+
receipt,
|
|
26993
|
+
verified: checked.verified,
|
|
26994
|
+
...checked.divergence !== void 0 ? { divergence: checked.divergence } : {}
|
|
26995
|
+
});
|
|
26996
|
+
return;
|
|
26997
|
+
}
|
|
26938
26998
|
const projectRunsMatch = /^\/v1\/projects\/(prj_[A-Za-z0-9_-]+)\/runs$/.exec(url.pathname);
|
|
26939
26999
|
if (request.method === "GET" && projectRunsMatch) {
|
|
26940
27000
|
const projectRoot = url.searchParams.get("projectRoot") ?? "";
|
package/plugins/kxm/package.json
CHANGED
|
@@ -16,12 +16,16 @@ verbs under `runs`.
|
|
|
16
16
|
| `kxm run [workflow] [prompt...]` | Create a vNext run (prompt is hashed, never stored raw) | `--json` |
|
|
17
17
|
| `kxm runs list` | List recent runs for the current project | `--json` |
|
|
18
18
|
| `kxm runs status <runId>` | Show projected run status | `--json` |
|
|
19
|
+
| `kxm runs receipt <runId>` | Print the newest drive receipt for a run | `--json`, `--all` |
|
|
20
|
+
| `kxm runs drive <runId> --wait` | Drive a run, then wait for a receipt; exits 0 only for a VERIFIED COMPLETED settlement | `--json`, `--simulated`, `--timeout-ms` (default 60000, max 600000) |
|
|
19
21
|
| `kxm runs cancel <runId>` | Durably request cancellation | `--json` |
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
24
|
kxm run default "implement the bounded slice" --json
|
|
23
25
|
kxm runs list --json
|
|
24
26
|
kxm runs status run_12345 --json
|
|
27
|
+
kxm runs receipt run_12345 --json
|
|
28
|
+
kxm runs drive run_12345 --simulated --wait --timeout-ms 60000 --json
|
|
25
29
|
kxm runs cancel run_12345 --json
|
|
26
30
|
```
|
|
27
31
|
|
|
@@ -557,7 +557,32 @@ export async function cmdVnextRunStatus(runtime: Runtime, runId: string): Promis
|
|
|
557
557
|
}
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
-
|
|
560
|
+
const DEFAULT_DRIVE_WAIT_MS = 60_000;
|
|
561
|
+
const MAX_DRIVE_WAIT_MS = 600_000;
|
|
562
|
+
|
|
563
|
+
function parseDriveWaitTimeoutMs(raw: number | string | undefined): number | undefined {
|
|
564
|
+
if (raw === undefined) return DEFAULT_DRIVE_WAIT_MS;
|
|
565
|
+
const value = typeof raw === "number" ? raw : Number(typeof raw === "string" ? raw.trim() : raw);
|
|
566
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_DRIVE_WAIT_MS) return undefined;
|
|
567
|
+
return value;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function sleepMs(ms: number): Promise<void> {
|
|
571
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function driveWaitCompleted(drive: VnextStatusDrive | undefined): boolean {
|
|
575
|
+
return drive?.verified === true
|
|
576
|
+
&& drive.receipt?.settlement?.kind === "terminal"
|
|
577
|
+
&& drive.receipt.settlement.status === "completed";
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
export async function cmdVnextRunDrive(
|
|
581
|
+
runtime: Runtime,
|
|
582
|
+
runId: string,
|
|
583
|
+
simulated: boolean,
|
|
584
|
+
options: { wait?: boolean; timeoutMs?: number | string } = {},
|
|
585
|
+
): Promise<number> {
|
|
561
586
|
try {
|
|
562
587
|
const projectRoot = discoverVnextProjectRoot(runtime.cwd);
|
|
563
588
|
if (!projectRoot) {
|
|
@@ -569,8 +594,19 @@ export async function cmdVnextRunDrive(runtime: Runtime, runId: string, simulate
|
|
|
569
594
|
print(runtime.io, runtime.json, { ok: true, command: "runs drive", dryRun: true, runId, mode }, `drive plan: run ${runId} in ${mode} mode (no events written)`);
|
|
570
595
|
return 0;
|
|
571
596
|
}
|
|
597
|
+
const timeoutMs = options.wait === true ? parseDriveWaitTimeoutMs(options.timeoutMs) : DEFAULT_DRIVE_WAIT_MS;
|
|
598
|
+
if (options.wait === true && timeoutMs === undefined) {
|
|
599
|
+
print(
|
|
600
|
+
runtime.io,
|
|
601
|
+
runtime.json,
|
|
602
|
+
{ ok: false, command: "runs drive", error: "run_drive_timeout_invalid" },
|
|
603
|
+
`run drive --wait --timeout-ms must be an integer between 1 and ${MAX_DRIVE_WAIT_MS}`,
|
|
604
|
+
);
|
|
605
|
+
return 1;
|
|
606
|
+
}
|
|
572
607
|
const supervisor = await (vnextDriveCliSeams.ensureSupervisor ?? ensureVnextSupervisor)({ env: runtime.env });
|
|
573
|
-
const
|
|
608
|
+
const request = vnextDriveCliSeams.runtimeRequest ?? vnextRuntimeRequest;
|
|
609
|
+
const result = await request(supervisor, "POST", `/v1/runs/${encodeURIComponent(runId)}/drive?projectRoot=${encodeURIComponent(projectRoot)}`, { mode });
|
|
574
610
|
const driveId = result.driveId;
|
|
575
611
|
const poll = result.poll;
|
|
576
612
|
const status = result.status;
|
|
@@ -578,13 +614,39 @@ export async function cmdVnextRunDrive(runtime: Runtime, runId: string, simulate
|
|
|
578
614
|
print(runtime.io, runtime.json, { ok: false, command: "runs drive", error: "run_drive_io_failed" }, "run drive failed because a local operation did not complete");
|
|
579
615
|
return 1;
|
|
580
616
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
617
|
+
|
|
618
|
+
if (options.wait !== true) {
|
|
619
|
+
print(
|
|
620
|
+
runtime.io,
|
|
621
|
+
runtime.json,
|
|
622
|
+
{ ok: true, command: "runs drive", runId, driveId, poll, mode, status },
|
|
623
|
+
`drive ${driveId}: accepted (poll ${poll})`,
|
|
624
|
+
);
|
|
625
|
+
return 0;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const deadline = Date.now() + timeoutMs!;
|
|
629
|
+
let interval = 25;
|
|
630
|
+
while (true) {
|
|
631
|
+
const stateResult = await request(supervisor, "GET", `/v1/runs/${encodeURIComponent(runId)}?projectRoot=${encodeURIComponent(projectRoot)}`);
|
|
632
|
+
const drive = stateResult.drive as VnextStatusDrive | undefined;
|
|
633
|
+
if (drive?.receipt) {
|
|
634
|
+
print(
|
|
635
|
+
runtime.io,
|
|
636
|
+
runtime.json,
|
|
637
|
+
{ ok: driveWaitCompleted(drive), command: "runs drive", receipt: drive.receipt, verified: drive.verified === true },
|
|
638
|
+
JSON.stringify(drive.receipt, null, 2),
|
|
639
|
+
);
|
|
640
|
+
return driveWaitCompleted(drive) ? 0 : 1;
|
|
641
|
+
}
|
|
642
|
+
const remaining = deadline - Date.now();
|
|
643
|
+
if (remaining <= 0) break;
|
|
644
|
+
await sleepMs(Math.min(interval, remaining));
|
|
645
|
+
interval = Math.min(Math.floor(interval * 1.5), 250);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs drive", error: "timeout" }, "drive wait timed out");
|
|
649
|
+
return 1;
|
|
588
650
|
} catch (error) {
|
|
589
651
|
if (error instanceof VnextConfigError) {
|
|
590
652
|
print(runtime.io, runtime.json, { ok: false, command: "runs drive", error: "run_drive_failed", issues: error.issues }, `run drive failed: ${error.message}`);
|
|
@@ -772,3 +834,45 @@ export async function cmdVnextRuntime(runtime: Runtime, action: string): Promise
|
|
|
772
834
|
return 1;
|
|
773
835
|
}
|
|
774
836
|
}
|
|
837
|
+
|
|
838
|
+
export async function cmdVnextRunReceipt(runtime: Runtime, runId: string, options: { all?: boolean } = {}): Promise<number> {
|
|
839
|
+
try {
|
|
840
|
+
const projectRoot = discoverVnextProjectRoot(runtime.cwd);
|
|
841
|
+
if (!projectRoot) {
|
|
842
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "project_required" }, "kxm runs receipt requires a vNext project (run kxm init first)");
|
|
843
|
+
return 1;
|
|
844
|
+
}
|
|
845
|
+
const supervisor = await (vnextDriveCliSeams.ensureSupervisor ?? ensureVnextSupervisor)({ env: runtime.env });
|
|
846
|
+
const result = await (vnextDriveCliSeams.runtimeRequest ?? vnextRuntimeRequest)(
|
|
847
|
+
supervisor,
|
|
848
|
+
"GET",
|
|
849
|
+
`/v1/runs/${encodeURIComponent(runId)}/drive?projectRoot=${encodeURIComponent(projectRoot)}`,
|
|
850
|
+
);
|
|
851
|
+
if (!Array.isArray(result.receipts)) {
|
|
852
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "run_receipt_io_failed" }, "run receipt failed because a local operation did not complete");
|
|
853
|
+
return 1;
|
|
854
|
+
}
|
|
855
|
+
const receipts = result.receipts as unknown[];
|
|
856
|
+
if (receipts.length === 0) {
|
|
857
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "no_receipts" }, `run ${runId} has no drive receipts`);
|
|
858
|
+
return 1;
|
|
859
|
+
}
|
|
860
|
+
if (options.all === true) {
|
|
861
|
+
print(runtime.io, runtime.json, { ok: true, command: "runs receipt", receipts }, JSON.stringify(receipts, null, 2));
|
|
862
|
+
return 0;
|
|
863
|
+
}
|
|
864
|
+
const latest = receipts[0];
|
|
865
|
+
const settlement = latest && typeof latest === "object" && "settlement" in latest
|
|
866
|
+
? (latest as { settlement: unknown }).settlement
|
|
867
|
+
: latest;
|
|
868
|
+
print(runtime.io, runtime.json, { ok: true, command: "runs receipt", receipt: latest }, JSON.stringify(settlement, null, 2));
|
|
869
|
+
return 0;
|
|
870
|
+
} catch (error) {
|
|
871
|
+
if (error instanceof VnextConfigError) {
|
|
872
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "run_receipt_failed", issues: error.issues }, `run receipt failed: ${error.message}`);
|
|
873
|
+
return 1;
|
|
874
|
+
}
|
|
875
|
+
print(runtime.io, runtime.json, { ok: false, command: "runs receipt", error: "run_receipt_io_failed" }, "run receipt failed because a local operation did not complete");
|
|
876
|
+
return 1;
|
|
877
|
+
}
|
|
878
|
+
}
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -107,6 +107,7 @@ import {
|
|
|
107
107
|
cmdVnextRun,
|
|
108
108
|
cmdVnextRunStatus,
|
|
109
109
|
cmdVnextRunDrive,
|
|
110
|
+
cmdVnextRunReceipt,
|
|
110
111
|
cmdVnextRunCancel,
|
|
111
112
|
cmdVnextRunList,
|
|
112
113
|
cmdHarnessList,
|
|
@@ -390,8 +391,19 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
|
|
|
390
391
|
addGlobalOptions(runCmd.command("drive").description("Drive a run with an explicit model-free simulation"))
|
|
391
392
|
.argument("<runId>", "Run id")
|
|
392
393
|
.option("--simulated", "Use the model-free simulation producer")
|
|
393
|
-
.
|
|
394
|
-
|
|
394
|
+
.option("--wait", "Wait until a drive receipt is recorded; exits 0 only for a VERIFIED COMPLETED settlement")
|
|
395
|
+
.option("--timeout-ms <n>", "Wait timeout in milliseconds (default 60000, max 600000)")
|
|
396
|
+
.action(async function runDriveAction(this: Command, runId: string, options: { simulated?: boolean; wait?: boolean; timeoutMs?: string }) {
|
|
397
|
+
result.code = await cmdVnextRunDrive(runtimeFrom(ctx, this), runId, options.simulated === true, {
|
|
398
|
+
wait: options.wait === true,
|
|
399
|
+
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
addGlobalOptions(runCmd.command("receipt").description("Print the newest drive receipt for a run"))
|
|
403
|
+
.argument("<runId>", "Run id")
|
|
404
|
+
.option("--all", "Print the capped receipt list for the run")
|
|
405
|
+
.action(async function runReceiptAction(this: Command, runId: string, options: { all?: boolean }) {
|
|
406
|
+
result.code = await cmdVnextRunReceipt(runtimeFrom(ctx, this), runId, { all: options.all === true });
|
|
395
407
|
});
|
|
396
408
|
addGlobalOptions(runCmd.command("cancel").description("Durably request cancellation of a run"))
|
|
397
409
|
.argument("<runId>", "Run id")
|
|
@@ -8,7 +8,7 @@ import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } fr
|
|
|
8
8
|
import { deliverInboxNotification } from "./inbox.ts";
|
|
9
9
|
import type { HubEvent, MessageRecord } from "./protocol.ts";
|
|
10
10
|
|
|
11
|
-
const VERSION = "0.7.
|
|
11
|
+
const VERSION = "0.7.26";
|
|
12
12
|
const inbox = new Map<string, MessageRecord>();
|
|
13
13
|
const notifiedInbox = new Set<string>();
|
|
14
14
|
let meshClient: HubClient | undefined;
|
|
@@ -396,6 +396,10 @@ export interface VnextDriveReceipt {
|
|
|
396
396
|
producer: { id: string; closed: boolean };
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
export type VnextDriveReceiptListItem =
|
|
400
|
+
| VnextDriveReceipt
|
|
401
|
+
| { readonly driveId: string; readonly divergence: string };
|
|
402
|
+
|
|
399
403
|
export interface VnextDriveReceiptBudget {
|
|
400
404
|
budgetMs: number;
|
|
401
405
|
source: "workflow" | "project" | "both";
|
|
@@ -1014,11 +1018,17 @@ export class VnextRunEventStore {
|
|
|
1014
1018
|
return row ? parseDriveReceipt(row.receipt) : undefined;
|
|
1015
1019
|
}
|
|
1016
1020
|
|
|
1017
|
-
driveReceiptsForRun(runId: string, limit = 20):
|
|
1021
|
+
driveReceiptsForRun(runId: string, limit = 20): VnextDriveReceiptListItem[] {
|
|
1018
1022
|
const rows = this.database.prepare(`
|
|
1019
|
-
SELECT receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
|
|
1020
|
-
`).all(runId, limit) as Array<{ receipt: string }>;
|
|
1021
|
-
return rows.map((row) =>
|
|
1023
|
+
SELECT drive_id, receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
|
|
1024
|
+
`).all(runId, limit) as Array<{ drive_id: string; receipt: string }>;
|
|
1025
|
+
return rows.map((row) => {
|
|
1026
|
+
try {
|
|
1027
|
+
return parseDriveReceipt(row.receipt);
|
|
1028
|
+
} catch {
|
|
1029
|
+
return { driveId: row.drive_id, divergence: "receipt unreadable" };
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1022
1032
|
}
|
|
1023
1033
|
|
|
1024
1034
|
insertCapability(row: VnextAttemptCapabilityRow): void {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
VnextRuntimeRegistry,
|
|
10
10
|
projectRuntimeKey,
|
|
11
11
|
runtimeError,
|
|
12
|
+
verifyVnextDriveReceipt,
|
|
12
13
|
vnextRuntimePaths,
|
|
13
14
|
type VnextRuntimePaths,
|
|
14
15
|
} from "./vnext-runtime-store.ts";
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
import { createVnextOneShotProducer } from "./vnext-oneshot-producer.ts";
|
|
25
26
|
import { isProducerAdmitted } from "./producers.ts";
|
|
26
27
|
import { VnextRunScheduler, createVnextSimulatedProducer, recordDriveReceipt, recoverVnextRun, vnextDrivePollProjection } from "./vnext-engine.ts";
|
|
27
|
-
import { vnextOpenDriveSessions } from "./vnext-runtime-owner.ts";
|
|
28
|
+
import { vnextDriveSession, vnextOpenDriveSessions } from "./vnext-runtime-owner.ts";
|
|
28
29
|
|
|
29
30
|
/* ------------------------------------------------------------------ *
|
|
30
31
|
* Token management
|
|
@@ -551,6 +552,24 @@ async function startVnextRuntimeSupervisorInner(
|
|
|
551
552
|
sendJson(response, 200, { ok: true, events });
|
|
552
553
|
return;
|
|
553
554
|
}
|
|
555
|
+
|
|
556
|
+
if (request.method === "GET" && sub === "drive") {
|
|
557
|
+
const run = context.eventStore.run(runId);
|
|
558
|
+
if (!run) throw runtimeError("run_unknown", runId, "run not found");
|
|
559
|
+
|
|
560
|
+
const activeSession = vnextDriveSession(context.eventStore.path, runId);
|
|
561
|
+
const session = activeSession
|
|
562
|
+
? {
|
|
563
|
+
driveId: activeSession.driveId,
|
|
564
|
+
mode: activeSession.mode,
|
|
565
|
+
openedAt: activeSession.openedAt,
|
|
566
|
+
deadlineAt: activeSession.deadlineAt ?? null,
|
|
567
|
+
}
|
|
568
|
+
: null;
|
|
569
|
+
const receipts = context.eventStore.driveReceiptsForRun(runId, 20);
|
|
570
|
+
sendJson(response, 200, { ok: true, session, receipts });
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
554
573
|
if (request.method === "POST" && sub === "cancel") {
|
|
555
574
|
const body = await readJsonBody(request);
|
|
556
575
|
const result = cancelVnextRun(context, runId, {
|
|
@@ -585,6 +604,47 @@ async function startVnextRuntimeSupervisorInner(
|
|
|
585
604
|
}
|
|
586
605
|
}
|
|
587
606
|
|
|
607
|
+
const driveMatch = /^\/v1\/drives\/([A-Za-z0-9_-]+)$/.exec(url.pathname);
|
|
608
|
+
if (request.method === "GET" && driveMatch) {
|
|
609
|
+
const driveId = driveMatch[1] as string;
|
|
610
|
+
const projectRoot = url.searchParams.get("projectRoot") ?? "";
|
|
611
|
+
if (!projectRoot) {
|
|
612
|
+
sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: "projectRoot query parameter is required" });
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const context = contextFor(projectRoot);
|
|
616
|
+
|
|
617
|
+
let receipt;
|
|
618
|
+
try {
|
|
619
|
+
receipt = context.eventStore.driveReceipt(driveId);
|
|
620
|
+
} catch {
|
|
621
|
+
sendJson(response, 200, { ok: true, receipt: null, verified: false, divergence: "receipt unreadable" });
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (!receipt) {
|
|
626
|
+
sendJson(response, 404, { ok: false, error: "drive_receipt_missing", message: "receipt not found" });
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const runId = receipt.runId;
|
|
631
|
+
const stored = context.eventStore.run(runId);
|
|
632
|
+
if (!stored) {
|
|
633
|
+
sendJson(response, 200, { ok: true, receipt, verified: false, divergence: "run missing" });
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const state = foldStoredVnextRun(context, stored);
|
|
637
|
+
const events = context.eventStore.events(runId, 0, 1_000_000);
|
|
638
|
+
const checked = verifyVnextDriveReceipt(receipt, events, state.status, { runId, driveId });
|
|
639
|
+
sendJson(response, 200, {
|
|
640
|
+
ok: true,
|
|
641
|
+
receipt,
|
|
642
|
+
verified: checked.verified,
|
|
643
|
+
...(checked.divergence !== undefined ? { divergence: checked.divergence } : {}),
|
|
644
|
+
});
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
|
|
588
648
|
const projectRunsMatch = /^\/v1\/projects\/(prj_[A-Za-z0-9_-]+)\/runs$/.exec(url.pathname);
|
|
589
649
|
if (request.method === "GET" && projectRunsMatch) {
|
|
590
650
|
const projectRoot = url.searchParams.get("projectRoot") ?? "";
|