@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.
@@ -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
- push("run.cancel_requested", {
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
- if (cancelled) ensureOperatorCancel(seq, ids.state, run);
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: "operator_cancel", stepId });
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: (/* @__PURE__ */ new Date()).toISOString(),
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: null,
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: "operator_cancel" });
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
- function unsupportedLimit(envelope) {
25497
- if (envelope.plan.limits.maxRunDurationMs !== void 0) {
25498
- return { reason: "limit_unsupported", field: "limits.maxRunDurationMs", detail: "duration budget enforcement is not available in this slice" };
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: true,
26862
+ allowLimits: false,
26683
26863
  liveMode: body.mode === "live",
26684
26864
  createProducer
26685
26865
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kxm-claude-plugin",
3
- "version": "0.7.24",
3
+ "version": "0.7.25",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -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
- `run ${run.runId}: ${run.status} (workflow ${run.workflowId}, updated ${run.updatedAt})${driveLine ? `\n${driveLine}` : ""}`,
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.24";
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(state: MutableState, event: VnextRunEvent): void {
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
  }