@kontextmind/kxm 0.7.24 → 0.7.25
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 +13 -1
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +203 -23
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +203 -23
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/vnext.ts +20 -1
- 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 +8 -1
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +1 -1
- 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
|
@@ -18962,6 +18962,16 @@ var ATTEMPT_EDGES = {
|
|
|
18962
18962
|
executing: /* @__PURE__ */ new Set(["settling"]),
|
|
18963
18963
|
settling: /* @__PURE__ */ new Set(["terminal"])
|
|
18964
18964
|
};
|
|
18965
|
+
function effectiveRunDurationBudget(planLimits, projectLimits) {
|
|
18966
|
+
const workflow = planLimits?.maxRunDurationMs;
|
|
18967
|
+
const project = projectLimits?.maxRunDurationMs;
|
|
18968
|
+
if (workflow === void 0 && project === void 0) return void 0;
|
|
18969
|
+
if (workflow !== void 0 && project !== void 0) {
|
|
18970
|
+
return { budgetMs: Math.min(workflow, project), source: "both" };
|
|
18971
|
+
}
|
|
18972
|
+
if (workflow !== void 0) return { budgetMs: workflow, source: "workflow" };
|
|
18973
|
+
return { budgetMs: project, source: "project" };
|
|
18974
|
+
}
|
|
18965
18975
|
function emptyPanel() {
|
|
18966
18976
|
return { order: [], assignments: /* @__PURE__ */ Object.create(null) };
|
|
18967
18977
|
}
|
|
@@ -19193,7 +19203,7 @@ function foldVnextRunState(run, plan, events, options = {}) {
|
|
|
19193
19203
|
foldRunStatus(state, plan, event);
|
|
19194
19204
|
break;
|
|
19195
19205
|
case "run.cancel_requested":
|
|
19196
|
-
foldCancelRequested(state, event);
|
|
19206
|
+
foldCancelRequested(state, event, plan, options);
|
|
19197
19207
|
break;
|
|
19198
19208
|
case "run.drive_opened":
|
|
19199
19209
|
foldDriveOpened(state, event);
|
|
@@ -19322,6 +19332,9 @@ function foldRunStatus(state, plan, event) {
|
|
|
19322
19332
|
if ((status === "running" || status === "cancelling") && !plan) {
|
|
19323
19333
|
throw runtimeError("run_plan_missing", state.runId, `status ${status} requires a pinned run plan`);
|
|
19324
19334
|
}
|
|
19335
|
+
if (status === "running" && state.runningSince === void 0) {
|
|
19336
|
+
state.runningSince = event.occurredAt;
|
|
19337
|
+
}
|
|
19325
19338
|
if (TERMINAL_RUN.has(status)) {
|
|
19326
19339
|
assertTerminalRunStatus(state, plan, status, event);
|
|
19327
19340
|
if (typeof event.payload.reason === "string") state.terminalReason = event.payload.reason;
|
|
@@ -19420,10 +19433,36 @@ function foldDriveOpened(state, event) {
|
|
|
19420
19433
|
}
|
|
19421
19434
|
state.drive = { driveId, mode, openedSequence: event.sequence };
|
|
19422
19435
|
}
|
|
19423
|
-
function foldCancelRequested(state, event) {
|
|
19436
|
+
function foldCancelRequested(state, event, plan, options) {
|
|
19424
19437
|
if (state.cancelRequested || TERMINAL_RUN.has(state.status)) {
|
|
19425
19438
|
throw runtimeError("run_events_illegal", state.runId, "duplicate run.cancel_requested is not legal");
|
|
19426
19439
|
}
|
|
19440
|
+
if (event.payload.reason === "budget_run_duration") {
|
|
19441
|
+
const budget = event.payload.budget;
|
|
19442
|
+
if (!budget || typeof budget !== "object" || Array.isArray(budget)) {
|
|
19443
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration requires a budget payload");
|
|
19444
|
+
}
|
|
19445
|
+
const body = budget;
|
|
19446
|
+
const budgetMs = body.budgetMs;
|
|
19447
|
+
const elapsedMs = body.elapsedMs;
|
|
19448
|
+
const source = body.source;
|
|
19449
|
+
if (typeof budgetMs !== "number" || !Number.isInteger(budgetMs) || budgetMs < 0) {
|
|
19450
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration budgetMs is invalid");
|
|
19451
|
+
}
|
|
19452
|
+
if (typeof elapsedMs !== "number" || !Number.isInteger(elapsedMs) || elapsedMs < 0) {
|
|
19453
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration elapsedMs is invalid");
|
|
19454
|
+
}
|
|
19455
|
+
if (source !== "workflow" && source !== "project" && source !== "both") {
|
|
19456
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration source is invalid");
|
|
19457
|
+
}
|
|
19458
|
+
const expected = effectiveRunDurationBudget(plan?.limits, options.projectLimits);
|
|
19459
|
+
if (!expected || expected.budgetMs !== budgetMs || expected.source !== source) {
|
|
19460
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration budgetMs does not match the plan");
|
|
19461
|
+
}
|
|
19462
|
+
if (elapsedMs < budgetMs) {
|
|
19463
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration elapsedMs is below budgetMs");
|
|
19464
|
+
}
|
|
19465
|
+
}
|
|
19427
19466
|
state.cancelRequested = true;
|
|
19428
19467
|
if (event.commandId) state.cancelCommandId = event.commandId;
|
|
19429
19468
|
}
|
|
@@ -20069,7 +20108,8 @@ function freezeState(state) {
|
|
|
20069
20108
|
cancelRequested: state.cancelRequested,
|
|
20070
20109
|
...state.terminalReason !== void 0 ? { terminalReason: state.terminalReason } : {},
|
|
20071
20110
|
...state.terminalReason !== void 0 && state.status === "failed" ? { failureReason: state.terminalReason } : {},
|
|
20072
|
-
...state.drive !== void 0 ? { drive: Object.freeze({ ...state.drive }) } : {}
|
|
20111
|
+
...state.drive !== void 0 ? { drive: Object.freeze({ ...state.drive }) } : {},
|
|
20112
|
+
...state.runningSince !== void 0 ? { runningSince: state.runningSince } : {}
|
|
20073
20113
|
};
|
|
20074
20114
|
return Object.freeze(frozen);
|
|
20075
20115
|
}
|
|
@@ -21959,7 +21999,8 @@ function foldStoredVnextRun(context, run) {
|
|
|
21959
21999
|
const step = envelope.plan.steps[stepId];
|
|
21960
22000
|
if (!step || step.kind !== "gate") return void 0;
|
|
21961
22001
|
return envelope.gates.definitions[step.gate]?.kind;
|
|
21962
|
-
}
|
|
22002
|
+
},
|
|
22003
|
+
projectLimits: envelope.projectLimits
|
|
21963
22004
|
} : {});
|
|
21964
22005
|
if (envelope) verifyVnextGateEvidence(context.eventStore, run, envelope, events, state);
|
|
21965
22006
|
assertStoredProjection(context, run);
|
|
@@ -22066,10 +22107,17 @@ function cancelVnextRun(context, runId, options = {}) {
|
|
|
22066
22107
|
};
|
|
22067
22108
|
const cancelReason = options.reason ?? "operator_cancel";
|
|
22068
22109
|
const activeAttempt = Boolean(folded.currentStep?.attemptId) || vnextFoldPanelAttemptIds(folded.currentStep).length > 0;
|
|
22069
|
-
|
|
22110
|
+
const cancelPayload = {
|
|
22070
22111
|
actor: { kind: "runtime", id: context.homeRuntimeId },
|
|
22071
22112
|
reason: cancelReason
|
|
22072
|
-
}
|
|
22113
|
+
};
|
|
22114
|
+
if (cancelReason === "budget_run_duration") {
|
|
22115
|
+
if (!options.budget) {
|
|
22116
|
+
throw runtimeError("run_events_illegal", runId, "budget_run_duration requires a budget payload");
|
|
22117
|
+
}
|
|
22118
|
+
cancelPayload.budget = options.budget;
|
|
22119
|
+
}
|
|
22120
|
+
push("run.cancel_requested", cancelPayload);
|
|
22073
22121
|
let status = "cancelled";
|
|
22074
22122
|
if (folded.status === "running" && activeAttempt) {
|
|
22075
22123
|
status = "cancelling";
|
|
@@ -23406,7 +23454,7 @@ function recordGateNoStartInTransaction(context, run, envelope, stepId, observat
|
|
|
23406
23454
|
const cancelled = observation.stopCause === "cancel";
|
|
23407
23455
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23408
23456
|
const seq = sequencer(context, run, now);
|
|
23409
|
-
|
|
23457
|
+
const cancelReason = cancelled ? ensureOperatorCancel(seq, ids.state, run, context) : void 0;
|
|
23410
23458
|
const observationId = newVnextObservationId();
|
|
23411
23459
|
const observed = seq.push("effect.observed", {
|
|
23412
23460
|
effect: { id: ids.effectId, policy: { class: ids.gateKind === "artifacts-exist" ? "read-only" : "unknown", sharedMutable: false } },
|
|
@@ -23438,7 +23486,7 @@ function recordGateNoStartInTransaction(context, run, envelope, stepId, observat
|
|
|
23438
23486
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, status: "terminal" });
|
|
23439
23487
|
seq.push("assignment.terminal", { assignmentId: ids.assignmentId, outcome: "cancelled", status: "terminal" });
|
|
23440
23488
|
seq.push("step.status_changed", { stepId, status: "cancelled", previousStatus: "running" });
|
|
23441
|
-
seq.push("run.status_changed", { status: "cancelled", reason: "operator_cancel", stepId });
|
|
23489
|
+
seq.push("run.status_changed", { status: "cancelled", reason: cancelReason ?? "operator_cancel", stepId });
|
|
23442
23490
|
} else {
|
|
23443
23491
|
seq.push("assignment.result_recorded", { assignmentId: ids.assignmentId, resultClass: "producer_rejected", status: "result_recorded" });
|
|
23444
23492
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, status: "terminal" });
|
|
@@ -23492,7 +23540,7 @@ function recordGateCancelObservedInTransaction(context, run, envelope, stepId, o
|
|
|
23492
23540
|
}
|
|
23493
23541
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23494
23542
|
const seq = sequencer(context, run, now);
|
|
23495
|
-
ensureOperatorCancel(seq, ids.state, run);
|
|
23543
|
+
const cancelReason = ensureOperatorCancel(seq, ids.state, run, context);
|
|
23496
23544
|
if (ids.gateKind === "artifacts-exist") {
|
|
23497
23545
|
seq.push("assignment.executing", { assignmentId: ids.assignmentId, stepId, stepAttempt: ids.stepAttempt, attemptId: ids.attemptId, status: "executing" });
|
|
23498
23546
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, assignmentId: ids.assignmentId, stepId, stepAttempt: ids.stepAttempt, status: "executing" });
|
|
@@ -23535,7 +23583,7 @@ function recordGateCancelObservedInTransaction(context, run, envelope, stepId, o
|
|
|
23535
23583
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, status: "terminal" });
|
|
23536
23584
|
seq.push("assignment.terminal", { assignmentId: ids.assignmentId, outcome: "cancelled", status: "terminal" });
|
|
23537
23585
|
seq.push("step.status_changed", { stepId, status: "cancelled", previousStatus: "running" });
|
|
23538
|
-
seq.push("run.status_changed", { status: "cancelled", reason:
|
|
23586
|
+
seq.push("run.status_changed", { status: "cancelled", reason: cancelReason, stepId });
|
|
23539
23587
|
for (const event of seq.events) context.eventStore.appendEvent(event);
|
|
23540
23588
|
context.eventStore.insertGateObservation(observationRow);
|
|
23541
23589
|
context.eventStore.settleCapability(ids.attemptId, "settled");
|
|
@@ -23620,8 +23668,8 @@ function requireIntent(context, run, envelope, stepId) {
|
|
|
23620
23668
|
state: folded
|
|
23621
23669
|
};
|
|
23622
23670
|
}
|
|
23623
|
-
function ensureOperatorCancel(seq, state, run) {
|
|
23624
|
-
if (state.status === "cancelling") return;
|
|
23671
|
+
function ensureOperatorCancel(seq, state, run, context) {
|
|
23672
|
+
if (state.status === "cancelling") return cancelReasonFromLog(context, run.runId);
|
|
23625
23673
|
if (state.status !== "running") {
|
|
23626
23674
|
throw runtimeError("run_events_illegal", run.runId, "cancelled gate proof requires a running or cancelling run");
|
|
23627
23675
|
}
|
|
@@ -23630,6 +23678,17 @@ function ensureOperatorCancel(seq, state, run) {
|
|
|
23630
23678
|
reason: "operator_cancel"
|
|
23631
23679
|
});
|
|
23632
23680
|
seq.push("run.status_changed", { status: "cancelling", reason: "operator_cancel" });
|
|
23681
|
+
return "operator_cancel";
|
|
23682
|
+
}
|
|
23683
|
+
function cancelReasonFromLog(context, runId) {
|
|
23684
|
+
const events = context.eventStore.events(runId, 0, 1e6);
|
|
23685
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
23686
|
+
const event = events[index];
|
|
23687
|
+
if (event.eventType === "run.cancel_requested" && typeof event.payload.reason === "string") {
|
|
23688
|
+
return event.payload.reason;
|
|
23689
|
+
}
|
|
23690
|
+
}
|
|
23691
|
+
return "operator_cancel";
|
|
23633
23692
|
}
|
|
23634
23693
|
function sequencer(context, run, now) {
|
|
23635
23694
|
let sequence = context.eventStore.nextSequence(run.runId);
|
|
@@ -23968,6 +24027,8 @@ async function driveAdmitted(context, runId, producer, token, options = {}) {
|
|
|
23968
24027
|
}
|
|
23969
24028
|
let latest = { state: foldStoredVnextRun(context, requireRun(context, runId)) };
|
|
23970
24029
|
while (latest.state.status === "running") {
|
|
24030
|
+
const overBudget = cancelRunDurationIfExceeded(context, runId);
|
|
24031
|
+
if (overBudget) return overBudget;
|
|
23971
24032
|
const before = context.eventStore.runState(runId)?.lastSequence;
|
|
23972
24033
|
latest = await stepLocked(context, runId, producer, token);
|
|
23973
24034
|
if (latest.handoff) return latest;
|
|
@@ -24027,6 +24088,7 @@ function recordDriveReceipt(context, session, closeInfo) {
|
|
|
24027
24088
|
const last = events[events.length - 1];
|
|
24028
24089
|
const openedSequence = opened?.openedSequence ?? openedEvent?.sequence;
|
|
24029
24090
|
if (openedSequence === void 0) return;
|
|
24091
|
+
const closedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24030
24092
|
const receipt = {
|
|
24031
24093
|
schema: VNEXT_DRIVE_RECEIPT_SCHEMA,
|
|
24032
24094
|
driveId: session.driveId,
|
|
@@ -24036,7 +24098,7 @@ function recordDriveReceipt(context, session, closeInfo) {
|
|
|
24036
24098
|
mode: session.mode,
|
|
24037
24099
|
openedAt: openedEvent?.occurredAt ?? session.openedAt,
|
|
24038
24100
|
openedSequence,
|
|
24039
|
-
closedAt
|
|
24101
|
+
closedAt,
|
|
24040
24102
|
lastSequence: last.sequence,
|
|
24041
24103
|
logHash: hashVnextDriveLog(events.filter((event) => event.sequence >= 1 && event.sequence <= last.sequence)),
|
|
24042
24104
|
settlement: {
|
|
@@ -24046,7 +24108,7 @@ function recordDriveReceipt(context, session, closeInfo) {
|
|
|
24046
24108
|
...closeInfo.kind === "handoff" && closeInfo.handoff ? { handoff: receiptHandoff(closeInfo.handoff) } : {},
|
|
24047
24109
|
...closeInfo.error !== void 0 ? { error: closeInfo.error } : {}
|
|
24048
24110
|
},
|
|
24049
|
-
budget:
|
|
24111
|
+
budget: driveReceiptBudget(context, run.runId, state, events, closedAt),
|
|
24050
24112
|
producer: { id: closeInfo.producerId, closed: closeInfo.producerClosed }
|
|
24051
24113
|
};
|
|
24052
24114
|
context.eventStore.insertDriveReceipt(receipt);
|
|
@@ -24235,6 +24297,8 @@ var VnextRunScheduler = class _VnextRunScheduler {
|
|
|
24235
24297
|
}
|
|
24236
24298
|
const driveId = newDriveId(runId, token, this.context.homeRuntimeId, vnextMonotonicNs());
|
|
24237
24299
|
const openedMeta = appendDriveOpened(this.context, runId, driveId, options.mode);
|
|
24300
|
+
const startedState = foldStoredVnextRun(this.context, requireRun(this.context, runId));
|
|
24301
|
+
const deadlineAt = driveDeadlineAt(this.context, runId, startedState);
|
|
24238
24302
|
const session = {
|
|
24239
24303
|
driveId,
|
|
24240
24304
|
runId,
|
|
@@ -24244,7 +24308,8 @@ var VnextRunScheduler = class _VnextRunScheduler {
|
|
|
24244
24308
|
openedAt: openedMeta.occurredAt,
|
|
24245
24309
|
producerId: producer.id,
|
|
24246
24310
|
controller: new AbortController(),
|
|
24247
|
-
settled
|
|
24311
|
+
settled,
|
|
24312
|
+
...deadlineAt !== void 0 ? { deadlineAt } : {}
|
|
24248
24313
|
};
|
|
24249
24314
|
attachVnextDriveSession(this.context.eventStore.path, runId, token, session);
|
|
24250
24315
|
opened = true;
|
|
@@ -24323,11 +24388,13 @@ async function stepLocked(context, runId, producer, token) {
|
|
|
24323
24388
|
throw runtimeError("run_events_illegal", prepared.run.runId, "prepared gate is not artifacts-exist");
|
|
24324
24389
|
}
|
|
24325
24390
|
registerVnextAttemptController(context.eventStore.path, runId, { attemptId: prepared.attemptId, controller: prepared.controller });
|
|
24391
|
+
const clearBudget = armRunDurationBudgetTimer(context, runId);
|
|
24326
24392
|
try {
|
|
24327
24393
|
const observation = evaluateArtifactsGate(context, definition);
|
|
24328
24394
|
vnextGateDispatchSeams.afterEvaluate?.(prepared);
|
|
24329
24395
|
return context.eventStore.transaction(() => settlePreparedGate(context, prepared, observation));
|
|
24330
24396
|
} finally {
|
|
24397
|
+
clearBudget();
|
|
24331
24398
|
unregisterVnextAttemptController(context.eventStore.path, runId, prepared.attemptId);
|
|
24332
24399
|
}
|
|
24333
24400
|
}
|
|
@@ -24358,6 +24425,7 @@ async function runPreparedCommandGate(context, prepared, definition, token) {
|
|
|
24358
24425
|
});
|
|
24359
24426
|
armVnextGateHold(storePath, prepared.run.runId, token, prepared.attemptId, () => observer.requestStop("cancel"));
|
|
24360
24427
|
registerVnextAttemptController(storePath, prepared.run.runId, { attemptId: prepared.attemptId, controller: prepared.controller });
|
|
24428
|
+
const clearBudget = armRunDurationBudgetTimer(context, prepared.run.runId);
|
|
24361
24429
|
const unhookClose = registerVnextRuntimeCloseHook(context, () => {
|
|
24362
24430
|
try {
|
|
24363
24431
|
markVnextGateHoldUnsettled(storePath, prepared.run.runId, token, prepared.attemptId);
|
|
@@ -24398,6 +24466,7 @@ async function runPreparedCommandGate(context, prepared, definition, token) {
|
|
|
24398
24466
|
return commitCommandUncertainty(context, prepared, token, outcome.reason, outcome.observation);
|
|
24399
24467
|
} finally {
|
|
24400
24468
|
unhookClose();
|
|
24469
|
+
clearBudget();
|
|
24401
24470
|
unregisterVnextAttemptController(storePath, prepared.run.runId, prepared.attemptId);
|
|
24402
24471
|
}
|
|
24403
24472
|
}
|
|
@@ -25023,8 +25092,10 @@ async function drivePanel(context, panel, producer) {
|
|
|
25023
25092
|
const pending = /* @__PURE__ */ new Map();
|
|
25024
25093
|
let stopBirths = false;
|
|
25025
25094
|
let settlementFailed = false;
|
|
25095
|
+
const budgetClears = /* @__PURE__ */ new Map();
|
|
25026
25096
|
const register = (member) => {
|
|
25027
25097
|
registerVnextAttemptController(context.eventStore.path, runId, { attemptId: member.attemptId, controller: member.controller });
|
|
25098
|
+
budgetClears.set(member.attemptId, armRunDurationBudgetTimer(context, runId));
|
|
25028
25099
|
owned.set(member.attemptId, member);
|
|
25029
25100
|
};
|
|
25030
25101
|
const abortOwned = () => {
|
|
@@ -25207,6 +25278,7 @@ async function drivePanel(context, panel, producer) {
|
|
|
25207
25278
|
throw error;
|
|
25208
25279
|
} finally {
|
|
25209
25280
|
for (const attemptId of owned.keys()) {
|
|
25281
|
+
budgetClears.get(attemptId)?.();
|
|
25210
25282
|
unregisterVnextAttemptController(context.eventStore.path, runId, attemptId);
|
|
25211
25283
|
}
|
|
25212
25284
|
}
|
|
@@ -25386,7 +25458,7 @@ function joinPanel(context, panel) {
|
|
|
25386
25458
|
};
|
|
25387
25459
|
const cancelRun = () => {
|
|
25388
25460
|
push("step.status_changed", { stepId: panel.stepId, status: "cancelled", previousStatus: "running" });
|
|
25389
|
-
push("run.status_changed", { status: "cancelled", reason:
|
|
25461
|
+
push("run.status_changed", { status: "cancelled", reason: cancelReasonFromLog2(context, run.runId) });
|
|
25390
25462
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
25391
25463
|
const next2 = foldStoredVnextRun(context, run);
|
|
25392
25464
|
persistVnextRunState(context, run.runId, next2, events[events.length - 1].sequence);
|
|
@@ -25493,16 +25565,124 @@ function transitionBudgetFailure(plan, state, fromStepId, outcome) {
|
|
|
25493
25565
|
}
|
|
25494
25566
|
return void 0;
|
|
25495
25567
|
}
|
|
25496
|
-
|
|
25497
|
-
|
|
25498
|
-
|
|
25568
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
25569
|
+
function elapsedMsBetween(fromIso, toIso) {
|
|
25570
|
+
const from = Date.parse(fromIso);
|
|
25571
|
+
const to = Date.parse(toIso);
|
|
25572
|
+
if (!Number.isFinite(from) || !Number.isFinite(to)) return 0;
|
|
25573
|
+
return Math.max(0, Math.trunc(to - from));
|
|
25574
|
+
}
|
|
25575
|
+
function declaredRunDurationBudget(context, runId) {
|
|
25576
|
+
const run = context.eventStore.run(runId);
|
|
25577
|
+
if (!run) return void 0;
|
|
25578
|
+
try {
|
|
25579
|
+
const envelope = loadVnextRunPlanEnvelope(context.eventStore, run);
|
|
25580
|
+
return effectiveRunDurationBudget(envelope.plan.limits, envelope.projectLimits);
|
|
25581
|
+
} catch {
|
|
25582
|
+
return void 0;
|
|
25499
25583
|
}
|
|
25584
|
+
}
|
|
25585
|
+
function runDurationOverrunPayload(context, runId, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
25586
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25587
|
+
if (!declared) return void 0;
|
|
25588
|
+
const run = context.eventStore.run(runId);
|
|
25589
|
+
if (!run) return void 0;
|
|
25590
|
+
const state = foldStoredVnextRun(context, run);
|
|
25591
|
+
if (!state.runningSince) return void 0;
|
|
25592
|
+
if (isTerminalRunStatus(state.status) || state.status === "cancelling") return void 0;
|
|
25593
|
+
const elapsedMs = elapsedMsBetween(state.runningSince, nowIso);
|
|
25594
|
+
if (elapsedMs < declared.budgetMs) return void 0;
|
|
25595
|
+
return { budgetMs: declared.budgetMs, elapsedMs, source: declared.source };
|
|
25596
|
+
}
|
|
25597
|
+
function cancelRunDurationIfExceeded(context, runId) {
|
|
25598
|
+
if (isVnextRuntimeContextClosed(context)) return void 0;
|
|
25599
|
+
const payload = runDurationOverrunPayload(context, runId);
|
|
25600
|
+
if (!payload) return void 0;
|
|
25601
|
+
cancelVnextRun(context, runId, { reason: "budget_run_duration", budget: payload });
|
|
25602
|
+
return { state: foldStoredVnextRun(context, requireRun(context, runId)) };
|
|
25603
|
+
}
|
|
25604
|
+
function driveDeadlineAt(context, runId, state) {
|
|
25605
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25606
|
+
if (!declared || !state.runningSince) return void 0;
|
|
25607
|
+
const start = Date.parse(state.runningSince);
|
|
25608
|
+
if (!Number.isFinite(start)) return void 0;
|
|
25609
|
+
return new Date(start + declared.budgetMs).toISOString();
|
|
25610
|
+
}
|
|
25611
|
+
function driveReceiptBudget(context, runId, state, events, closedAt) {
|
|
25612
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25613
|
+
if (!declared) return null;
|
|
25614
|
+
const elapsedMs = state.runningSince ? elapsedMsBetween(state.runningSince, closedAt) : 0;
|
|
25615
|
+
const overrun = events.some((event) => event.eventType === "run.cancel_requested" && event.payload.reason === "budget_run_duration");
|
|
25616
|
+
return {
|
|
25617
|
+
budgetMs: declared.budgetMs,
|
|
25618
|
+
source: declared.source,
|
|
25619
|
+
elapsedMs,
|
|
25620
|
+
overrun
|
|
25621
|
+
};
|
|
25622
|
+
}
|
|
25623
|
+
function cancelReasonFromLog2(context, runId) {
|
|
25624
|
+
const events = context.eventStore.events(runId, 0, 1e6);
|
|
25625
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
25626
|
+
const event = events[index];
|
|
25627
|
+
if (event.eventType === "run.cancel_requested" && typeof event.payload.reason === "string") {
|
|
25628
|
+
return event.payload.reason;
|
|
25629
|
+
}
|
|
25630
|
+
}
|
|
25631
|
+
return "operator_cancel";
|
|
25632
|
+
}
|
|
25633
|
+
function armRunDurationBudgetTimer(context, runId) {
|
|
25634
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25635
|
+
if (!declared) return () => void 0;
|
|
25636
|
+
const run = context.eventStore.run(runId);
|
|
25637
|
+
if (!run) return () => void 0;
|
|
25638
|
+
const state = foldStoredVnextRun(context, run);
|
|
25639
|
+
if (!state.runningSince) return () => void 0;
|
|
25640
|
+
const startMs = Date.parse(state.runningSince);
|
|
25641
|
+
if (!Number.isFinite(startMs)) return () => void 0;
|
|
25642
|
+
const deadlineMs = startMs + declared.budgetMs;
|
|
25643
|
+
let timer;
|
|
25644
|
+
let cleared = false;
|
|
25645
|
+
const fire = () => {
|
|
25646
|
+
timer = void 0;
|
|
25647
|
+
if (cleared || isVnextRuntimeContextClosed(context)) return;
|
|
25648
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
25649
|
+
const elapsedMs = elapsedMsBetween(state.runningSince, nowIso);
|
|
25650
|
+
if (elapsedMs < declared.budgetMs) {
|
|
25651
|
+
arm(declared.budgetMs - elapsedMs);
|
|
25652
|
+
return;
|
|
25653
|
+
}
|
|
25654
|
+
try {
|
|
25655
|
+
cancelVnextRun(context, runId, {
|
|
25656
|
+
reason: "budget_run_duration",
|
|
25657
|
+
budget: { budgetMs: declared.budgetMs, elapsedMs, source: declared.source }
|
|
25658
|
+
});
|
|
25659
|
+
} catch {
|
|
25660
|
+
}
|
|
25661
|
+
};
|
|
25662
|
+
const arm = (remaining) => {
|
|
25663
|
+
if (cleared) return;
|
|
25664
|
+
const delay = Math.min(Math.max(0, remaining), MAX_TIMER_DELAY_MS);
|
|
25665
|
+
timer = setTimeout(fire, delay);
|
|
25666
|
+
timer.unref();
|
|
25667
|
+
};
|
|
25668
|
+
const unhook = registerVnextRuntimeCloseHook(context, () => {
|
|
25669
|
+
cleared = true;
|
|
25670
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
25671
|
+
timer = void 0;
|
|
25672
|
+
});
|
|
25673
|
+
arm(deadlineMs - Date.now());
|
|
25674
|
+
return () => {
|
|
25675
|
+
if (cleared) return;
|
|
25676
|
+
cleared = true;
|
|
25677
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
25678
|
+
timer = void 0;
|
|
25679
|
+
unhook();
|
|
25680
|
+
};
|
|
25681
|
+
}
|
|
25682
|
+
function unsupportedLimit(envelope) {
|
|
25500
25683
|
if (envelope.plan.limits.maxAgentTimeMs !== void 0) {
|
|
25501
25684
|
return { reason: "limit_unsupported", field: "limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
|
|
25502
25685
|
}
|
|
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
25686
|
if (envelope.projectLimits.maxAgentTimeMs !== void 0) {
|
|
25507
25687
|
return { reason: "limit_unsupported", field: "project.limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
|
|
25508
25688
|
}
|
|
@@ -26679,7 +26859,7 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26679
26859
|
const scheduler = VnextRunScheduler.for(context, bundle);
|
|
26680
26860
|
const opening = scheduler.openDriveSession(runId, {
|
|
26681
26861
|
mode: body.mode,
|
|
26682
|
-
allowLimits:
|
|
26862
|
+
allowLimits: false,
|
|
26683
26863
|
liveMode: body.mode === "live",
|
|
26684
26864
|
createProducer
|
|
26685
26865
|
});
|
package/plugins/kxm/package.json
CHANGED
|
@@ -484,6 +484,10 @@ interface VnextStatusDrive {
|
|
|
484
484
|
divergence?: string;
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
+
function formatCancelledStatus(reason: string): string {
|
|
488
|
+
return reason.length > 0 ? `cancelled (${reason})` : "cancelled";
|
|
489
|
+
}
|
|
490
|
+
|
|
487
491
|
function formatDriveStatusLine(runStatus: string, drive: VnextStatusDrive | undefined): string | undefined {
|
|
488
492
|
if (!drive || typeof drive.driveId !== "string" || drive.driveId.length === 0) return undefined;
|
|
489
493
|
const receipt = drive.receipt ?? null;
|
|
@@ -503,12 +507,27 @@ function formatDriveStatusLine(runStatus: string, drive: VnextStatusDrive | unde
|
|
|
503
507
|
? `drive ${drive.driveId}: handoff (receipt verified)`
|
|
504
508
|
: `drive ${drive.driveId}: handoff`;
|
|
505
509
|
}
|
|
510
|
+
if (receipt.settlement?.status === "cancelled") {
|
|
511
|
+
const label = formatCancelledStatus(reason);
|
|
512
|
+
return drive.verified === true
|
|
513
|
+
? `drive ${drive.driveId}: ${label} (receipt verified)`
|
|
514
|
+
: `drive ${drive.driveId}: ${label}`;
|
|
515
|
+
}
|
|
506
516
|
if (drive.verified === true) {
|
|
507
517
|
return `drive ${drive.driveId}: completed (receipt verified)`;
|
|
508
518
|
}
|
|
509
519
|
return `drive ${drive.driveId}: completed`;
|
|
510
520
|
}
|
|
511
521
|
|
|
522
|
+
function formatRunStatusLine(
|
|
523
|
+
run: { runId: string; status: string; workflowId: string; updatedAt: string },
|
|
524
|
+
drive: VnextStatusDrive | undefined,
|
|
525
|
+
): string {
|
|
526
|
+
const reason = typeof drive?.receipt?.settlement?.reason === "string" ? drive.receipt.settlement.reason : "";
|
|
527
|
+
const statusLabel = run.status === "cancelled" ? formatCancelledStatus(reason) : run.status;
|
|
528
|
+
return `run ${run.runId}: ${statusLabel} (workflow ${run.workflowId}, updated ${run.updatedAt})`;
|
|
529
|
+
}
|
|
530
|
+
|
|
512
531
|
export async function cmdVnextRunStatus(runtime: Runtime, runId: string): Promise<number> {
|
|
513
532
|
try {
|
|
514
533
|
const projectRoot = discoverVnextProjectRoot(runtime.cwd);
|
|
@@ -525,7 +544,7 @@ export async function cmdVnextRunStatus(runtime: Runtime, runId: string): Promis
|
|
|
525
544
|
runtime.io,
|
|
526
545
|
runtime.json,
|
|
527
546
|
{ ok: true, command: "runs status", run, ...(drive !== undefined ? { drive } : {}) },
|
|
528
|
-
|
|
547
|
+
`${formatRunStatusLine(run, drive)}${driveLine ? `\n${driveLine}` : ""}`,
|
|
529
548
|
);
|
|
530
549
|
return 0;
|
|
531
550
|
} catch (error) {
|
|
@@ -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.25";
|
|
12
12
|
const inbox = new Map<string, MessageRecord>();
|
|
13
13
|
const notifiedInbox = new Set<string>();
|
|
14
14
|
let meshClient: HubClient | undefined;
|
|
@@ -62,6 +62,30 @@ export interface VnextFoldOptions {
|
|
|
62
62
|
observationId: string,
|
|
63
63
|
) => { completeness: "complete" | "incomplete" | "no-start" } | undefined;
|
|
64
64
|
readonly gateKind?: (stepId: string) => string | undefined;
|
|
65
|
+
readonly projectLimits?: {
|
|
66
|
+
readonly maxRunDurationMs?: number;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type VnextRunDurationBudgetSource = "workflow" | "project" | "both";
|
|
71
|
+
|
|
72
|
+
export interface VnextRunDurationBudget {
|
|
73
|
+
readonly budgetMs: number;
|
|
74
|
+
readonly source: VnextRunDurationBudgetSource;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function effectiveRunDurationBudget(
|
|
78
|
+
planLimits: { readonly maxRunDurationMs?: number } | undefined,
|
|
79
|
+
projectLimits: { readonly maxRunDurationMs?: number } | undefined,
|
|
80
|
+
): VnextRunDurationBudget | undefined {
|
|
81
|
+
const workflow = planLimits?.maxRunDurationMs;
|
|
82
|
+
const project = projectLimits?.maxRunDurationMs;
|
|
83
|
+
if (workflow === undefined && project === undefined) return undefined;
|
|
84
|
+
if (workflow !== undefined && project !== undefined) {
|
|
85
|
+
return { budgetMs: Math.min(workflow, project), source: "both" };
|
|
86
|
+
}
|
|
87
|
+
if (workflow !== undefined) return { budgetMs: workflow, source: "workflow" };
|
|
88
|
+
return { budgetMs: project!, source: "project" };
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
export interface VnextFoldAttemptState {
|
|
@@ -127,6 +151,7 @@ export interface VnextRunState {
|
|
|
127
151
|
readonly terminalReason?: string | undefined;
|
|
128
152
|
readonly failureReason?: string | undefined;
|
|
129
153
|
readonly drive?: VnextRunDriveBinding | undefined;
|
|
154
|
+
readonly runningSince?: string | undefined;
|
|
130
155
|
}
|
|
131
156
|
|
|
132
157
|
interface MutableAttempt {
|
|
@@ -179,6 +204,7 @@ interface MutableState {
|
|
|
179
204
|
cancelCommandId?: string | undefined;
|
|
180
205
|
lastTerminalTransition?: VnextTerminalStatus | undefined;
|
|
181
206
|
drive?: VnextRunDriveBinding | undefined;
|
|
207
|
+
runningSince?: string | undefined;
|
|
182
208
|
}
|
|
183
209
|
|
|
184
210
|
function emptyPanel(): MutablePanel {
|
|
@@ -468,7 +494,7 @@ export function foldVnextRunState(
|
|
|
468
494
|
foldRunStatus(state, plan, event);
|
|
469
495
|
break;
|
|
470
496
|
case "run.cancel_requested":
|
|
471
|
-
foldCancelRequested(state, event);
|
|
497
|
+
foldCancelRequested(state, event, plan, options);
|
|
472
498
|
break;
|
|
473
499
|
case "run.drive_opened":
|
|
474
500
|
foldDriveOpened(state, event);
|
|
@@ -606,6 +632,9 @@ function foldRunStatus(state: MutableState, plan: VnextCompiledPlan | undefined,
|
|
|
606
632
|
if ((status === "running" || status === "cancelling") && !plan) {
|
|
607
633
|
throw runtimeError("run_plan_missing", state.runId, `status ${status} requires a pinned run plan`);
|
|
608
634
|
}
|
|
635
|
+
if (status === "running" && state.runningSince === undefined) {
|
|
636
|
+
state.runningSince = event.occurredAt;
|
|
637
|
+
}
|
|
609
638
|
if (TERMINAL_RUN.has(status)) {
|
|
610
639
|
assertTerminalRunStatus(state, plan, status, event);
|
|
611
640
|
if (typeof event.payload.reason === "string") state.terminalReason = event.payload.reason;
|
|
@@ -717,10 +746,41 @@ function foldDriveOpened(state: MutableState, event: VnextRunEvent): void {
|
|
|
717
746
|
state.drive = { driveId, mode, openedSequence: event.sequence };
|
|
718
747
|
}
|
|
719
748
|
|
|
720
|
-
function foldCancelRequested(
|
|
749
|
+
function foldCancelRequested(
|
|
750
|
+
state: MutableState,
|
|
751
|
+
event: VnextRunEvent,
|
|
752
|
+
plan: VnextCompiledPlan | undefined,
|
|
753
|
+
options: VnextFoldOptions,
|
|
754
|
+
): void {
|
|
721
755
|
if (state.cancelRequested || TERMINAL_RUN.has(state.status)) {
|
|
722
756
|
throw runtimeError("run_events_illegal", state.runId, "duplicate run.cancel_requested is not legal");
|
|
723
757
|
}
|
|
758
|
+
if (event.payload.reason === "budget_run_duration") {
|
|
759
|
+
const budget = event.payload.budget;
|
|
760
|
+
if (!budget || typeof budget !== "object" || Array.isArray(budget)) {
|
|
761
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration requires a budget payload");
|
|
762
|
+
}
|
|
763
|
+
const body = budget as Record<string, unknown>;
|
|
764
|
+
const budgetMs = body.budgetMs;
|
|
765
|
+
const elapsedMs = body.elapsedMs;
|
|
766
|
+
const source = body.source;
|
|
767
|
+
if (typeof budgetMs !== "number" || !Number.isInteger(budgetMs) || budgetMs < 0) {
|
|
768
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration budgetMs is invalid");
|
|
769
|
+
}
|
|
770
|
+
if (typeof elapsedMs !== "number" || !Number.isInteger(elapsedMs) || elapsedMs < 0) {
|
|
771
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration elapsedMs is invalid");
|
|
772
|
+
}
|
|
773
|
+
if (source !== "workflow" && source !== "project" && source !== "both") {
|
|
774
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration source is invalid");
|
|
775
|
+
}
|
|
776
|
+
const expected = effectiveRunDurationBudget(plan?.limits, options.projectLimits);
|
|
777
|
+
if (!expected || expected.budgetMs !== budgetMs || expected.source !== source) {
|
|
778
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration budgetMs does not match the plan");
|
|
779
|
+
}
|
|
780
|
+
if (elapsedMs < budgetMs) {
|
|
781
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration elapsedMs is below budgetMs");
|
|
782
|
+
}
|
|
783
|
+
}
|
|
724
784
|
state.cancelRequested = true;
|
|
725
785
|
if (event.commandId) state.cancelCommandId = event.commandId;
|
|
726
786
|
}
|
|
@@ -1403,6 +1463,7 @@ function freezeState(state: MutableState): VnextRunState {
|
|
|
1403
1463
|
...(state.terminalReason !== undefined ? { terminalReason: state.terminalReason } : {}),
|
|
1404
1464
|
...(state.terminalReason !== undefined && state.status === "failed" ? { failureReason: state.terminalReason } : {}),
|
|
1405
1465
|
...(state.drive !== undefined ? { drive: Object.freeze({ ...state.drive }) } : {}),
|
|
1466
|
+
...(state.runningSince !== undefined ? { runningSince: state.runningSince } : {}),
|
|
1406
1467
|
};
|
|
1407
1468
|
return Object.freeze(frozen);
|
|
1408
1469
|
}
|