@kontextmind/kxm 0.7.24 → 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 +117 -12
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +265 -25
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +265 -25
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-runs/SKILL.md +4 -0
- package/plugins/kxm/src/cli/vnext.ts +133 -10
- package/plugins/kxm/src/cli.ts +14 -2
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/vnext-engine-fold.ts +63 -2
- package/plugins/kxm/src/vnext-engine-gate-records.ts +19 -6
- package/plugins/kxm/src/vnext-engine.ts +155 -9
- package/plugins/kxm/src/vnext-runtime-owner.ts +1 -0
- package/plugins/kxm/src/vnext-runtime-store.ts +22 -5
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +62 -2
- package/plugins/kxm/src/vnext-runtime.ts +17 -3
- package/schemas/vnext/drive-receipt.schema.json +17 -1
- package/schemas/vnext/run-event.schema.json +29 -0
|
@@ -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(`
|
|
@@ -18962,6 +18968,16 @@ var ATTEMPT_EDGES = {
|
|
|
18962
18968
|
executing: /* @__PURE__ */ new Set(["settling"]),
|
|
18963
18969
|
settling: /* @__PURE__ */ new Set(["terminal"])
|
|
18964
18970
|
};
|
|
18971
|
+
function effectiveRunDurationBudget(planLimits, projectLimits) {
|
|
18972
|
+
const workflow = planLimits?.maxRunDurationMs;
|
|
18973
|
+
const project = projectLimits?.maxRunDurationMs;
|
|
18974
|
+
if (workflow === void 0 && project === void 0) return void 0;
|
|
18975
|
+
if (workflow !== void 0 && project !== void 0) {
|
|
18976
|
+
return { budgetMs: Math.min(workflow, project), source: "both" };
|
|
18977
|
+
}
|
|
18978
|
+
if (workflow !== void 0) return { budgetMs: workflow, source: "workflow" };
|
|
18979
|
+
return { budgetMs: project, source: "project" };
|
|
18980
|
+
}
|
|
18965
18981
|
function emptyPanel() {
|
|
18966
18982
|
return { order: [], assignments: /* @__PURE__ */ Object.create(null) };
|
|
18967
18983
|
}
|
|
@@ -19193,7 +19209,7 @@ function foldVnextRunState(run, plan, events, options = {}) {
|
|
|
19193
19209
|
foldRunStatus(state, plan, event);
|
|
19194
19210
|
break;
|
|
19195
19211
|
case "run.cancel_requested":
|
|
19196
|
-
foldCancelRequested(state, event);
|
|
19212
|
+
foldCancelRequested(state, event, plan, options);
|
|
19197
19213
|
break;
|
|
19198
19214
|
case "run.drive_opened":
|
|
19199
19215
|
foldDriveOpened(state, event);
|
|
@@ -19322,6 +19338,9 @@ function foldRunStatus(state, plan, event) {
|
|
|
19322
19338
|
if ((status === "running" || status === "cancelling") && !plan) {
|
|
19323
19339
|
throw runtimeError("run_plan_missing", state.runId, `status ${status} requires a pinned run plan`);
|
|
19324
19340
|
}
|
|
19341
|
+
if (status === "running" && state.runningSince === void 0) {
|
|
19342
|
+
state.runningSince = event.occurredAt;
|
|
19343
|
+
}
|
|
19325
19344
|
if (TERMINAL_RUN.has(status)) {
|
|
19326
19345
|
assertTerminalRunStatus(state, plan, status, event);
|
|
19327
19346
|
if (typeof event.payload.reason === "string") state.terminalReason = event.payload.reason;
|
|
@@ -19420,10 +19439,36 @@ function foldDriveOpened(state, event) {
|
|
|
19420
19439
|
}
|
|
19421
19440
|
state.drive = { driveId, mode, openedSequence: event.sequence };
|
|
19422
19441
|
}
|
|
19423
|
-
function foldCancelRequested(state, event) {
|
|
19442
|
+
function foldCancelRequested(state, event, plan, options) {
|
|
19424
19443
|
if (state.cancelRequested || TERMINAL_RUN.has(state.status)) {
|
|
19425
19444
|
throw runtimeError("run_events_illegal", state.runId, "duplicate run.cancel_requested is not legal");
|
|
19426
19445
|
}
|
|
19446
|
+
if (event.payload.reason === "budget_run_duration") {
|
|
19447
|
+
const budget = event.payload.budget;
|
|
19448
|
+
if (!budget || typeof budget !== "object" || Array.isArray(budget)) {
|
|
19449
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration requires a budget payload");
|
|
19450
|
+
}
|
|
19451
|
+
const body = budget;
|
|
19452
|
+
const budgetMs = body.budgetMs;
|
|
19453
|
+
const elapsedMs = body.elapsedMs;
|
|
19454
|
+
const source = body.source;
|
|
19455
|
+
if (typeof budgetMs !== "number" || !Number.isInteger(budgetMs) || budgetMs < 0) {
|
|
19456
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration budgetMs is invalid");
|
|
19457
|
+
}
|
|
19458
|
+
if (typeof elapsedMs !== "number" || !Number.isInteger(elapsedMs) || elapsedMs < 0) {
|
|
19459
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration elapsedMs is invalid");
|
|
19460
|
+
}
|
|
19461
|
+
if (source !== "workflow" && source !== "project" && source !== "both") {
|
|
19462
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration source is invalid");
|
|
19463
|
+
}
|
|
19464
|
+
const expected = effectiveRunDurationBudget(plan?.limits, options.projectLimits);
|
|
19465
|
+
if (!expected || expected.budgetMs !== budgetMs || expected.source !== source) {
|
|
19466
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration budgetMs does not match the plan");
|
|
19467
|
+
}
|
|
19468
|
+
if (elapsedMs < budgetMs) {
|
|
19469
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration elapsedMs is below budgetMs");
|
|
19470
|
+
}
|
|
19471
|
+
}
|
|
19427
19472
|
state.cancelRequested = true;
|
|
19428
19473
|
if (event.commandId) state.cancelCommandId = event.commandId;
|
|
19429
19474
|
}
|
|
@@ -20069,7 +20114,8 @@ function freezeState(state) {
|
|
|
20069
20114
|
cancelRequested: state.cancelRequested,
|
|
20070
20115
|
...state.terminalReason !== void 0 ? { terminalReason: state.terminalReason } : {},
|
|
20071
20116
|
...state.terminalReason !== void 0 && state.status === "failed" ? { failureReason: state.terminalReason } : {},
|
|
20072
|
-
...state.drive !== void 0 ? { drive: Object.freeze({ ...state.drive }) } : {}
|
|
20117
|
+
...state.drive !== void 0 ? { drive: Object.freeze({ ...state.drive }) } : {},
|
|
20118
|
+
...state.runningSince !== void 0 ? { runningSince: state.runningSince } : {}
|
|
20073
20119
|
};
|
|
20074
20120
|
return Object.freeze(frozen);
|
|
20075
20121
|
}
|
|
@@ -21628,6 +21674,9 @@ function clearVnextDriveSession(storePath, runId, token) {
|
|
|
21628
21674
|
if (!current || current.token !== token) return;
|
|
21629
21675
|
delete current.driveSession;
|
|
21630
21676
|
}
|
|
21677
|
+
function vnextDriveSession(storePath, runId) {
|
|
21678
|
+
return owners.get(storePath)?.admitted.get(runId)?.driveSession;
|
|
21679
|
+
}
|
|
21631
21680
|
function vnextOpenDriveSessions(storePath) {
|
|
21632
21681
|
const owner = owners.get(storePath);
|
|
21633
21682
|
if (!owner) return [];
|
|
@@ -21959,7 +22008,8 @@ function foldStoredVnextRun(context, run) {
|
|
|
21959
22008
|
const step = envelope.plan.steps[stepId];
|
|
21960
22009
|
if (!step || step.kind !== "gate") return void 0;
|
|
21961
22010
|
return envelope.gates.definitions[step.gate]?.kind;
|
|
21962
|
-
}
|
|
22011
|
+
},
|
|
22012
|
+
projectLimits: envelope.projectLimits
|
|
21963
22013
|
} : {});
|
|
21964
22014
|
if (envelope) verifyVnextGateEvidence(context.eventStore, run, envelope, events, state);
|
|
21965
22015
|
assertStoredProjection(context, run);
|
|
@@ -22066,10 +22116,17 @@ function cancelVnextRun(context, runId, options = {}) {
|
|
|
22066
22116
|
};
|
|
22067
22117
|
const cancelReason = options.reason ?? "operator_cancel";
|
|
22068
22118
|
const activeAttempt = Boolean(folded.currentStep?.attemptId) || vnextFoldPanelAttemptIds(folded.currentStep).length > 0;
|
|
22069
|
-
|
|
22119
|
+
const cancelPayload = {
|
|
22070
22120
|
actor: { kind: "runtime", id: context.homeRuntimeId },
|
|
22071
22121
|
reason: cancelReason
|
|
22072
|
-
}
|
|
22122
|
+
};
|
|
22123
|
+
if (cancelReason === "budget_run_duration") {
|
|
22124
|
+
if (!options.budget) {
|
|
22125
|
+
throw runtimeError("run_events_illegal", runId, "budget_run_duration requires a budget payload");
|
|
22126
|
+
}
|
|
22127
|
+
cancelPayload.budget = options.budget;
|
|
22128
|
+
}
|
|
22129
|
+
push("run.cancel_requested", cancelPayload);
|
|
22073
22130
|
let status = "cancelled";
|
|
22074
22131
|
if (folded.status === "running" && activeAttempt) {
|
|
22075
22132
|
status = "cancelling";
|
|
@@ -23406,7 +23463,7 @@ function recordGateNoStartInTransaction(context, run, envelope, stepId, observat
|
|
|
23406
23463
|
const cancelled = observation.stopCause === "cancel";
|
|
23407
23464
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23408
23465
|
const seq = sequencer(context, run, now);
|
|
23409
|
-
|
|
23466
|
+
const cancelReason = cancelled ? ensureOperatorCancel(seq, ids.state, run, context) : void 0;
|
|
23410
23467
|
const observationId = newVnextObservationId();
|
|
23411
23468
|
const observed = seq.push("effect.observed", {
|
|
23412
23469
|
effect: { id: ids.effectId, policy: { class: ids.gateKind === "artifacts-exist" ? "read-only" : "unknown", sharedMutable: false } },
|
|
@@ -23438,7 +23495,7 @@ function recordGateNoStartInTransaction(context, run, envelope, stepId, observat
|
|
|
23438
23495
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, status: "terminal" });
|
|
23439
23496
|
seq.push("assignment.terminal", { assignmentId: ids.assignmentId, outcome: "cancelled", status: "terminal" });
|
|
23440
23497
|
seq.push("step.status_changed", { stepId, status: "cancelled", previousStatus: "running" });
|
|
23441
|
-
seq.push("run.status_changed", { status: "cancelled", reason: "operator_cancel", stepId });
|
|
23498
|
+
seq.push("run.status_changed", { status: "cancelled", reason: cancelReason ?? "operator_cancel", stepId });
|
|
23442
23499
|
} else {
|
|
23443
23500
|
seq.push("assignment.result_recorded", { assignmentId: ids.assignmentId, resultClass: "producer_rejected", status: "result_recorded" });
|
|
23444
23501
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, status: "terminal" });
|
|
@@ -23492,7 +23549,7 @@ function recordGateCancelObservedInTransaction(context, run, envelope, stepId, o
|
|
|
23492
23549
|
}
|
|
23493
23550
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23494
23551
|
const seq = sequencer(context, run, now);
|
|
23495
|
-
ensureOperatorCancel(seq, ids.state, run);
|
|
23552
|
+
const cancelReason = ensureOperatorCancel(seq, ids.state, run, context);
|
|
23496
23553
|
if (ids.gateKind === "artifacts-exist") {
|
|
23497
23554
|
seq.push("assignment.executing", { assignmentId: ids.assignmentId, stepId, stepAttempt: ids.stepAttempt, attemptId: ids.attemptId, status: "executing" });
|
|
23498
23555
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, assignmentId: ids.assignmentId, stepId, stepAttempt: ids.stepAttempt, status: "executing" });
|
|
@@ -23535,7 +23592,7 @@ function recordGateCancelObservedInTransaction(context, run, envelope, stepId, o
|
|
|
23535
23592
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, status: "terminal" });
|
|
23536
23593
|
seq.push("assignment.terminal", { assignmentId: ids.assignmentId, outcome: "cancelled", status: "terminal" });
|
|
23537
23594
|
seq.push("step.status_changed", { stepId, status: "cancelled", previousStatus: "running" });
|
|
23538
|
-
seq.push("run.status_changed", { status: "cancelled", reason:
|
|
23595
|
+
seq.push("run.status_changed", { status: "cancelled", reason: cancelReason, stepId });
|
|
23539
23596
|
for (const event of seq.events) context.eventStore.appendEvent(event);
|
|
23540
23597
|
context.eventStore.insertGateObservation(observationRow);
|
|
23541
23598
|
context.eventStore.settleCapability(ids.attemptId, "settled");
|
|
@@ -23620,8 +23677,8 @@ function requireIntent(context, run, envelope, stepId) {
|
|
|
23620
23677
|
state: folded
|
|
23621
23678
|
};
|
|
23622
23679
|
}
|
|
23623
|
-
function ensureOperatorCancel(seq, state, run) {
|
|
23624
|
-
if (state.status === "cancelling") return;
|
|
23680
|
+
function ensureOperatorCancel(seq, state, run, context) {
|
|
23681
|
+
if (state.status === "cancelling") return cancelReasonFromLog(context, run.runId);
|
|
23625
23682
|
if (state.status !== "running") {
|
|
23626
23683
|
throw runtimeError("run_events_illegal", run.runId, "cancelled gate proof requires a running or cancelling run");
|
|
23627
23684
|
}
|
|
@@ -23630,6 +23687,17 @@ function ensureOperatorCancel(seq, state, run) {
|
|
|
23630
23687
|
reason: "operator_cancel"
|
|
23631
23688
|
});
|
|
23632
23689
|
seq.push("run.status_changed", { status: "cancelling", reason: "operator_cancel" });
|
|
23690
|
+
return "operator_cancel";
|
|
23691
|
+
}
|
|
23692
|
+
function cancelReasonFromLog(context, runId) {
|
|
23693
|
+
const events = context.eventStore.events(runId, 0, 1e6);
|
|
23694
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
23695
|
+
const event = events[index];
|
|
23696
|
+
if (event.eventType === "run.cancel_requested" && typeof event.payload.reason === "string") {
|
|
23697
|
+
return event.payload.reason;
|
|
23698
|
+
}
|
|
23699
|
+
}
|
|
23700
|
+
return "operator_cancel";
|
|
23633
23701
|
}
|
|
23634
23702
|
function sequencer(context, run, now) {
|
|
23635
23703
|
let sequence = context.eventStore.nextSequence(run.runId);
|
|
@@ -23968,6 +24036,8 @@ async function driveAdmitted(context, runId, producer, token, options = {}) {
|
|
|
23968
24036
|
}
|
|
23969
24037
|
let latest = { state: foldStoredVnextRun(context, requireRun(context, runId)) };
|
|
23970
24038
|
while (latest.state.status === "running") {
|
|
24039
|
+
const overBudget = cancelRunDurationIfExceeded(context, runId);
|
|
24040
|
+
if (overBudget) return overBudget;
|
|
23971
24041
|
const before = context.eventStore.runState(runId)?.lastSequence;
|
|
23972
24042
|
latest = await stepLocked(context, runId, producer, token);
|
|
23973
24043
|
if (latest.handoff) return latest;
|
|
@@ -24027,6 +24097,7 @@ function recordDriveReceipt(context, session, closeInfo) {
|
|
|
24027
24097
|
const last = events[events.length - 1];
|
|
24028
24098
|
const openedSequence = opened?.openedSequence ?? openedEvent?.sequence;
|
|
24029
24099
|
if (openedSequence === void 0) return;
|
|
24100
|
+
const closedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24030
24101
|
const receipt = {
|
|
24031
24102
|
schema: VNEXT_DRIVE_RECEIPT_SCHEMA,
|
|
24032
24103
|
driveId: session.driveId,
|
|
@@ -24036,7 +24107,7 @@ function recordDriveReceipt(context, session, closeInfo) {
|
|
|
24036
24107
|
mode: session.mode,
|
|
24037
24108
|
openedAt: openedEvent?.occurredAt ?? session.openedAt,
|
|
24038
24109
|
openedSequence,
|
|
24039
|
-
closedAt
|
|
24110
|
+
closedAt,
|
|
24040
24111
|
lastSequence: last.sequence,
|
|
24041
24112
|
logHash: hashVnextDriveLog(events.filter((event) => event.sequence >= 1 && event.sequence <= last.sequence)),
|
|
24042
24113
|
settlement: {
|
|
@@ -24046,7 +24117,7 @@ function recordDriveReceipt(context, session, closeInfo) {
|
|
|
24046
24117
|
...closeInfo.kind === "handoff" && closeInfo.handoff ? { handoff: receiptHandoff(closeInfo.handoff) } : {},
|
|
24047
24118
|
...closeInfo.error !== void 0 ? { error: closeInfo.error } : {}
|
|
24048
24119
|
},
|
|
24049
|
-
budget:
|
|
24120
|
+
budget: driveReceiptBudget(context, run.runId, state, events, closedAt),
|
|
24050
24121
|
producer: { id: closeInfo.producerId, closed: closeInfo.producerClosed }
|
|
24051
24122
|
};
|
|
24052
24123
|
context.eventStore.insertDriveReceipt(receipt);
|
|
@@ -24235,6 +24306,8 @@ var VnextRunScheduler = class _VnextRunScheduler {
|
|
|
24235
24306
|
}
|
|
24236
24307
|
const driveId = newDriveId(runId, token, this.context.homeRuntimeId, vnextMonotonicNs());
|
|
24237
24308
|
const openedMeta = appendDriveOpened(this.context, runId, driveId, options.mode);
|
|
24309
|
+
const startedState = foldStoredVnextRun(this.context, requireRun(this.context, runId));
|
|
24310
|
+
const deadlineAt = driveDeadlineAt(this.context, runId, startedState);
|
|
24238
24311
|
const session = {
|
|
24239
24312
|
driveId,
|
|
24240
24313
|
runId,
|
|
@@ -24244,7 +24317,8 @@ var VnextRunScheduler = class _VnextRunScheduler {
|
|
|
24244
24317
|
openedAt: openedMeta.occurredAt,
|
|
24245
24318
|
producerId: producer.id,
|
|
24246
24319
|
controller: new AbortController(),
|
|
24247
|
-
settled
|
|
24320
|
+
settled,
|
|
24321
|
+
...deadlineAt !== void 0 ? { deadlineAt } : {}
|
|
24248
24322
|
};
|
|
24249
24323
|
attachVnextDriveSession(this.context.eventStore.path, runId, token, session);
|
|
24250
24324
|
opened = true;
|
|
@@ -24323,11 +24397,13 @@ async function stepLocked(context, runId, producer, token) {
|
|
|
24323
24397
|
throw runtimeError("run_events_illegal", prepared.run.runId, "prepared gate is not artifacts-exist");
|
|
24324
24398
|
}
|
|
24325
24399
|
registerVnextAttemptController(context.eventStore.path, runId, { attemptId: prepared.attemptId, controller: prepared.controller });
|
|
24400
|
+
const clearBudget = armRunDurationBudgetTimer(context, runId);
|
|
24326
24401
|
try {
|
|
24327
24402
|
const observation = evaluateArtifactsGate(context, definition);
|
|
24328
24403
|
vnextGateDispatchSeams.afterEvaluate?.(prepared);
|
|
24329
24404
|
return context.eventStore.transaction(() => settlePreparedGate(context, prepared, observation));
|
|
24330
24405
|
} finally {
|
|
24406
|
+
clearBudget();
|
|
24331
24407
|
unregisterVnextAttemptController(context.eventStore.path, runId, prepared.attemptId);
|
|
24332
24408
|
}
|
|
24333
24409
|
}
|
|
@@ -24358,6 +24434,7 @@ async function runPreparedCommandGate(context, prepared, definition, token) {
|
|
|
24358
24434
|
});
|
|
24359
24435
|
armVnextGateHold(storePath, prepared.run.runId, token, prepared.attemptId, () => observer.requestStop("cancel"));
|
|
24360
24436
|
registerVnextAttemptController(storePath, prepared.run.runId, { attemptId: prepared.attemptId, controller: prepared.controller });
|
|
24437
|
+
const clearBudget = armRunDurationBudgetTimer(context, prepared.run.runId);
|
|
24361
24438
|
const unhookClose = registerVnextRuntimeCloseHook(context, () => {
|
|
24362
24439
|
try {
|
|
24363
24440
|
markVnextGateHoldUnsettled(storePath, prepared.run.runId, token, prepared.attemptId);
|
|
@@ -24398,6 +24475,7 @@ async function runPreparedCommandGate(context, prepared, definition, token) {
|
|
|
24398
24475
|
return commitCommandUncertainty(context, prepared, token, outcome.reason, outcome.observation);
|
|
24399
24476
|
} finally {
|
|
24400
24477
|
unhookClose();
|
|
24478
|
+
clearBudget();
|
|
24401
24479
|
unregisterVnextAttemptController(storePath, prepared.run.runId, prepared.attemptId);
|
|
24402
24480
|
}
|
|
24403
24481
|
}
|
|
@@ -25023,8 +25101,10 @@ async function drivePanel(context, panel, producer) {
|
|
|
25023
25101
|
const pending = /* @__PURE__ */ new Map();
|
|
25024
25102
|
let stopBirths = false;
|
|
25025
25103
|
let settlementFailed = false;
|
|
25104
|
+
const budgetClears = /* @__PURE__ */ new Map();
|
|
25026
25105
|
const register = (member) => {
|
|
25027
25106
|
registerVnextAttemptController(context.eventStore.path, runId, { attemptId: member.attemptId, controller: member.controller });
|
|
25107
|
+
budgetClears.set(member.attemptId, armRunDurationBudgetTimer(context, runId));
|
|
25028
25108
|
owned.set(member.attemptId, member);
|
|
25029
25109
|
};
|
|
25030
25110
|
const abortOwned = () => {
|
|
@@ -25207,6 +25287,7 @@ async function drivePanel(context, panel, producer) {
|
|
|
25207
25287
|
throw error;
|
|
25208
25288
|
} finally {
|
|
25209
25289
|
for (const attemptId of owned.keys()) {
|
|
25290
|
+
budgetClears.get(attemptId)?.();
|
|
25210
25291
|
unregisterVnextAttemptController(context.eventStore.path, runId, attemptId);
|
|
25211
25292
|
}
|
|
25212
25293
|
}
|
|
@@ -25386,7 +25467,7 @@ function joinPanel(context, panel) {
|
|
|
25386
25467
|
};
|
|
25387
25468
|
const cancelRun = () => {
|
|
25388
25469
|
push("step.status_changed", { stepId: panel.stepId, status: "cancelled", previousStatus: "running" });
|
|
25389
|
-
push("run.status_changed", { status: "cancelled", reason:
|
|
25470
|
+
push("run.status_changed", { status: "cancelled", reason: cancelReasonFromLog2(context, run.runId) });
|
|
25390
25471
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
25391
25472
|
const next2 = foldStoredVnextRun(context, run);
|
|
25392
25473
|
persistVnextRunState(context, run.runId, next2, events[events.length - 1].sequence);
|
|
@@ -25493,16 +25574,124 @@ function transitionBudgetFailure(plan, state, fromStepId, outcome) {
|
|
|
25493
25574
|
}
|
|
25494
25575
|
return void 0;
|
|
25495
25576
|
}
|
|
25496
|
-
|
|
25497
|
-
|
|
25498
|
-
|
|
25577
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
25578
|
+
function elapsedMsBetween(fromIso, toIso) {
|
|
25579
|
+
const from = Date.parse(fromIso);
|
|
25580
|
+
const to = Date.parse(toIso);
|
|
25581
|
+
if (!Number.isFinite(from) || !Number.isFinite(to)) return 0;
|
|
25582
|
+
return Math.max(0, Math.trunc(to - from));
|
|
25583
|
+
}
|
|
25584
|
+
function declaredRunDurationBudget(context, runId) {
|
|
25585
|
+
const run = context.eventStore.run(runId);
|
|
25586
|
+
if (!run) return void 0;
|
|
25587
|
+
try {
|
|
25588
|
+
const envelope = loadVnextRunPlanEnvelope(context.eventStore, run);
|
|
25589
|
+
return effectiveRunDurationBudget(envelope.plan.limits, envelope.projectLimits);
|
|
25590
|
+
} catch {
|
|
25591
|
+
return void 0;
|
|
25592
|
+
}
|
|
25593
|
+
}
|
|
25594
|
+
function runDurationOverrunPayload(context, runId, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
25595
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25596
|
+
if (!declared) return void 0;
|
|
25597
|
+
const run = context.eventStore.run(runId);
|
|
25598
|
+
if (!run) return void 0;
|
|
25599
|
+
const state = foldStoredVnextRun(context, run);
|
|
25600
|
+
if (!state.runningSince) return void 0;
|
|
25601
|
+
if (isTerminalRunStatus(state.status) || state.status === "cancelling") return void 0;
|
|
25602
|
+
const elapsedMs = elapsedMsBetween(state.runningSince, nowIso);
|
|
25603
|
+
if (elapsedMs < declared.budgetMs) return void 0;
|
|
25604
|
+
return { budgetMs: declared.budgetMs, elapsedMs, source: declared.source };
|
|
25605
|
+
}
|
|
25606
|
+
function cancelRunDurationIfExceeded(context, runId) {
|
|
25607
|
+
if (isVnextRuntimeContextClosed(context)) return void 0;
|
|
25608
|
+
const payload = runDurationOverrunPayload(context, runId);
|
|
25609
|
+
if (!payload) return void 0;
|
|
25610
|
+
cancelVnextRun(context, runId, { reason: "budget_run_duration", budget: payload });
|
|
25611
|
+
return { state: foldStoredVnextRun(context, requireRun(context, runId)) };
|
|
25612
|
+
}
|
|
25613
|
+
function driveDeadlineAt(context, runId, state) {
|
|
25614
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25615
|
+
if (!declared || !state.runningSince) return void 0;
|
|
25616
|
+
const start = Date.parse(state.runningSince);
|
|
25617
|
+
if (!Number.isFinite(start)) return void 0;
|
|
25618
|
+
return new Date(start + declared.budgetMs).toISOString();
|
|
25619
|
+
}
|
|
25620
|
+
function driveReceiptBudget(context, runId, state, events, closedAt) {
|
|
25621
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25622
|
+
if (!declared) return null;
|
|
25623
|
+
const elapsedMs = state.runningSince ? elapsedMsBetween(state.runningSince, closedAt) : 0;
|
|
25624
|
+
const overrun = events.some((event) => event.eventType === "run.cancel_requested" && event.payload.reason === "budget_run_duration");
|
|
25625
|
+
return {
|
|
25626
|
+
budgetMs: declared.budgetMs,
|
|
25627
|
+
source: declared.source,
|
|
25628
|
+
elapsedMs,
|
|
25629
|
+
overrun
|
|
25630
|
+
};
|
|
25631
|
+
}
|
|
25632
|
+
function cancelReasonFromLog2(context, runId) {
|
|
25633
|
+
const events = context.eventStore.events(runId, 0, 1e6);
|
|
25634
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
25635
|
+
const event = events[index];
|
|
25636
|
+
if (event.eventType === "run.cancel_requested" && typeof event.payload.reason === "string") {
|
|
25637
|
+
return event.payload.reason;
|
|
25638
|
+
}
|
|
25499
25639
|
}
|
|
25640
|
+
return "operator_cancel";
|
|
25641
|
+
}
|
|
25642
|
+
function armRunDurationBudgetTimer(context, runId) {
|
|
25643
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25644
|
+
if (!declared) return () => void 0;
|
|
25645
|
+
const run = context.eventStore.run(runId);
|
|
25646
|
+
if (!run) return () => void 0;
|
|
25647
|
+
const state = foldStoredVnextRun(context, run);
|
|
25648
|
+
if (!state.runningSince) return () => void 0;
|
|
25649
|
+
const startMs = Date.parse(state.runningSince);
|
|
25650
|
+
if (!Number.isFinite(startMs)) return () => void 0;
|
|
25651
|
+
const deadlineMs = startMs + declared.budgetMs;
|
|
25652
|
+
let timer;
|
|
25653
|
+
let cleared = false;
|
|
25654
|
+
const fire = () => {
|
|
25655
|
+
timer = void 0;
|
|
25656
|
+
if (cleared || isVnextRuntimeContextClosed(context)) return;
|
|
25657
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
25658
|
+
const elapsedMs = elapsedMsBetween(state.runningSince, nowIso);
|
|
25659
|
+
if (elapsedMs < declared.budgetMs) {
|
|
25660
|
+
arm(declared.budgetMs - elapsedMs);
|
|
25661
|
+
return;
|
|
25662
|
+
}
|
|
25663
|
+
try {
|
|
25664
|
+
cancelVnextRun(context, runId, {
|
|
25665
|
+
reason: "budget_run_duration",
|
|
25666
|
+
budget: { budgetMs: declared.budgetMs, elapsedMs, source: declared.source }
|
|
25667
|
+
});
|
|
25668
|
+
} catch {
|
|
25669
|
+
}
|
|
25670
|
+
};
|
|
25671
|
+
const arm = (remaining) => {
|
|
25672
|
+
if (cleared) return;
|
|
25673
|
+
const delay = Math.min(Math.max(0, remaining), MAX_TIMER_DELAY_MS);
|
|
25674
|
+
timer = setTimeout(fire, delay);
|
|
25675
|
+
timer.unref();
|
|
25676
|
+
};
|
|
25677
|
+
const unhook = registerVnextRuntimeCloseHook(context, () => {
|
|
25678
|
+
cleared = true;
|
|
25679
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
25680
|
+
timer = void 0;
|
|
25681
|
+
});
|
|
25682
|
+
arm(deadlineMs - Date.now());
|
|
25683
|
+
return () => {
|
|
25684
|
+
if (cleared) return;
|
|
25685
|
+
cleared = true;
|
|
25686
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
25687
|
+
timer = void 0;
|
|
25688
|
+
unhook();
|
|
25689
|
+
};
|
|
25690
|
+
}
|
|
25691
|
+
function unsupportedLimit(envelope) {
|
|
25500
25692
|
if (envelope.plan.limits.maxAgentTimeMs !== void 0) {
|
|
25501
25693
|
return { reason: "limit_unsupported", field: "limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
|
|
25502
25694
|
}
|
|
25503
|
-
if (envelope.projectLimits.maxRunDurationMs !== void 0) {
|
|
25504
|
-
return { reason: "limit_unsupported", field: "project.limits.maxRunDurationMs", detail: "duration budget enforcement is not available in this slice" };
|
|
25505
|
-
}
|
|
25506
25695
|
if (envelope.projectLimits.maxAgentTimeMs !== void 0) {
|
|
25507
25696
|
return { reason: "limit_unsupported", field: "project.limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
|
|
25508
25697
|
}
|
|
@@ -26679,7 +26868,7 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26679
26868
|
const scheduler = VnextRunScheduler.for(context, bundle);
|
|
26680
26869
|
const opening = scheduler.openDriveSession(runId, {
|
|
26681
26870
|
mode: body.mode,
|
|
26682
|
-
allowLimits:
|
|
26871
|
+
allowLimits: false,
|
|
26683
26872
|
liveMode: body.mode === "live",
|
|
26684
26873
|
createProducer
|
|
26685
26874
|
});
|
|
@@ -26722,6 +26911,20 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26722
26911
|
sendJson(response, 200, { ok: true, events });
|
|
26723
26912
|
return;
|
|
26724
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
|
+
}
|
|
26725
26928
|
if (request.method === "POST" && sub === "cancel") {
|
|
26726
26929
|
const body = await readJsonBody(request);
|
|
26727
26930
|
const result = cancelVnextRun(context, runId, {
|
|
@@ -26755,6 +26958,43 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26755
26958
|
return;
|
|
26756
26959
|
}
|
|
26757
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
|
+
}
|
|
26758
26998
|
const projectRunsMatch = /^\/v1\/projects\/(prj_[A-Za-z0-9_-]+)\/runs$/.exec(url.pathname);
|
|
26759
26999
|
if (request.method === "GET" && projectRunsMatch) {
|
|
26760
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
|
|