@kontextmind/kxm 0.7.23 → 0.7.24

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.
@@ -16261,6 +16261,7 @@ var VnextSchemaRegistry = class {
16261
16261
  migrationReceiptValidator;
16262
16262
  permissionDiffValidator;
16263
16263
  runEventValidator;
16264
+ driveReceiptValidator;
16264
16265
  constructor(schemasDir = DEFAULT_SCHEMA_DIR) {
16265
16266
  this.schemasDir = resolve(schemasDir);
16266
16267
  this.ajv = new import__.Ajv2020({ allErrors: true, strict: true, strictRequired: false });
@@ -16277,6 +16278,7 @@ var VnextSchemaRegistry = class {
16277
16278
  const migrationReceiptFile = "migration-receipt.schema.json";
16278
16279
  const permissionDiffFile = "permission-diff.schema.json";
16279
16280
  const runEventFile = "run-event.schema.json";
16281
+ const driveReceiptFile = "drive-receipt.schema.json";
16280
16282
  this.ajv.addSchema(readJsonObject(join2(this.schemasDir, localBindingsFile)));
16281
16283
  this.ajv.addSchema(readJsonObject(join2(this.schemasDir, templateProvenanceFile)));
16282
16284
  this.ajv.addSchema(readJsonObject(join2(this.schemasDir, initOperationFile)));
@@ -16285,6 +16287,7 @@ var VnextSchemaRegistry = class {
16285
16287
  this.ajv.addSchema(readJsonObject(join2(this.schemasDir, migrationReceiptFile)));
16286
16288
  this.ajv.addSchema(readJsonObject(join2(this.schemasDir, permissionDiffFile)));
16287
16289
  this.ajv.addSchema(readJsonObject(join2(this.schemasDir, runEventFile)));
16290
+ this.ajv.addSchema(readJsonObject(join2(this.schemasDir, driveReceiptFile)));
16288
16291
  for (const [kind, definition] of Object.entries(RESOURCE_SCHEMA)) {
16289
16292
  const validator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${definition.file}`);
16290
16293
  if (!validator) throw new Error(`schema did not compile: ${definition.file}`);
@@ -16298,6 +16301,7 @@ var VnextSchemaRegistry = class {
16298
16301
  const migrationReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${migrationReceiptFile}`);
16299
16302
  const permissionDiffValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${permissionDiffFile}`);
16300
16303
  const runEventValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${runEventFile}`);
16304
+ const driveReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${driveReceiptFile}`);
16301
16305
  if (!localBindingsValidator) throw new Error(`schema did not compile: ${localBindingsFile}`);
16302
16306
  if (!templateProvenanceValidator) throw new Error(`schema did not compile: ${templateProvenanceFile}`);
16303
16307
  if (!initOperationValidator) throw new Error(`schema did not compile: ${initOperationFile}`);
@@ -16306,6 +16310,7 @@ var VnextSchemaRegistry = class {
16306
16310
  if (!migrationReceiptValidator) throw new Error(`schema did not compile: ${migrationReceiptFile}`);
16307
16311
  if (!permissionDiffValidator) throw new Error(`schema did not compile: ${permissionDiffFile}`);
16308
16312
  if (!runEventValidator) throw new Error(`schema did not compile: ${runEventFile}`);
16313
+ if (!driveReceiptValidator) throw new Error(`schema did not compile: ${driveReceiptFile}`);
16309
16314
  this.localBindingsValidator = localBindingsValidator;
16310
16315
  this.templateProvenanceValidator = templateProvenanceValidator;
16311
16316
  this.initOperationValidator = initOperationValidator;
@@ -16314,6 +16319,7 @@ var VnextSchemaRegistry = class {
16314
16319
  this.migrationReceiptValidator = migrationReceiptValidator;
16315
16320
  this.permissionDiffValidator = permissionDiffValidator;
16316
16321
  this.runEventValidator = runEventValidator;
16322
+ this.driveReceiptValidator = driveReceiptValidator;
16317
16323
  }
16318
16324
  validate(kind, value, file) {
16319
16325
  const definition = RESOURCE_SCHEMA[kind];
@@ -16346,6 +16352,9 @@ var VnextSchemaRegistry = class {
16346
16352
  validatePermissionDiff(value, file) {
16347
16353
  return this.validateAuxiliary(value, file, "kxm.permission-diff.v1", this.permissionDiffValidator);
16348
16354
  }
16355
+ validateDriveReceipt(value, file) {
16356
+ return this.validateAuxiliary(value, file, "kxm.drive-receipt.v1", this.driveReceiptValidator);
16357
+ }
16349
16358
  validateAuxiliary(value, file, identity, validator) {
16350
16359
  if (value.schema !== identity) {
16351
16360
  return [issue2("schema", "schema_identity_mismatch", file, `expected ${identity}, received ${String(value.schema)}`)];
@@ -16366,6 +16375,25 @@ function validateRunEvent(value, file) {
16366
16375
  )]);
16367
16376
  }
16368
16377
  }
16378
+ function validateDriveReceipt(value, file) {
16379
+ const registry = cachedRunEventRegistry ??= new VnextSchemaRegistry();
16380
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.drive-receipt.v1") {
16381
+ throw new VnextConfigError([issue2(
16382
+ "schema",
16383
+ "drive_receipt_invalid",
16384
+ file,
16385
+ "expected kxm.drive-receipt.v1"
16386
+ )]);
16387
+ }
16388
+ if (!registry.driveReceiptValidator(value)) {
16389
+ throw new VnextConfigError([issue2(
16390
+ "schema",
16391
+ "drive_receipt_invalid",
16392
+ file,
16393
+ registry.ajv.errorsText(registry.driveReceiptValidator.errors, { separator: "; " })
16394
+ )]);
16395
+ }
16396
+ }
16369
16397
  function schemaIssue(file, error) {
16370
16398
  const location = error.instancePath || "/";
16371
16399
  const suffix = error.params && "additionalProperty" in error.params ? ` (${String(error.params.additionalProperty)})` : "";
@@ -17817,7 +17845,36 @@ var VnextRuntimeRegistry = class {
17817
17845
  }
17818
17846
  };
17819
17847
  var VNEXT_RUN_EVENT_SCHEMA = "kxm.run-event.v1";
17820
- var VNEXT_EVENT_STORE_SCHEMA_VERSION = 3;
17848
+ function hashVnextDriveLog(events) {
17849
+ const body = events.map((event) => `${event.eventId}:${event.sequence}`).join("\n");
17850
+ return `sha256:${createHash3("sha256").update(body, "utf8").digest("hex")}`;
17851
+ }
17852
+ function verifyVnextDriveReceipt(receipt, events, foldedStatus, binding) {
17853
+ const reasons = [];
17854
+ if (receipt.runId !== binding.runId) {
17855
+ reasons.push(`runId ${receipt.runId} != binding ${binding.runId}`);
17856
+ }
17857
+ if (receipt.driveId !== binding.driveId) {
17858
+ reasons.push(`driveId ${receipt.driveId} != binding ${binding.driveId}`);
17859
+ }
17860
+ const last = events[events.length - 1];
17861
+ const currentLast = last?.sequence ?? 0;
17862
+ if (receipt.lastSequence !== currentLast) {
17863
+ reasons.push(`lastSequence ${receipt.lastSequence} != log ${currentLast}`);
17864
+ }
17865
+ const prefix = events.filter((event) => event.sequence >= 1 && event.sequence <= receipt.lastSequence);
17866
+ if (hashVnextDriveLog(prefix) !== receipt.logHash) {
17867
+ reasons.push("logHash mismatch");
17868
+ }
17869
+ if (receipt.settlement.status !== foldedStatus) {
17870
+ reasons.push(`settlement.status ${receipt.settlement.status} != folded ${foldedStatus}`);
17871
+ }
17872
+ if (reasons.length === 0) return { verified: true };
17873
+ return { verified: false, divergence: reasons.join("; ") };
17874
+ }
17875
+ var VNEXT_EVENT_STORE_SCHEMA_VERSION = 4;
17876
+ var VNEXT_DRIVE_RECEIPT_SCHEMA = "kxm.drive-receipt.v1";
17877
+ var DRIVE_RECEIPT_MAX_BYTES = 8 * 1024;
17821
17878
  var EVENT_STORE_TABLES = {
17822
17879
  runs: ["run_id", "project_id", "home_runtime_id", "workflow_id", "prompt_sha256", "status", "config_revision", "memory_revision", "executor_policy_revision", "tool_policy_revision", "created_at", "updated_at"],
17823
17880
  events: ["project_id", "run_id", "sequence", "event_id", "event_type", "command_id", "occurred_at", "recorded_at", "monotonic_ns", "config_revision", "memory_revision", "executor_policy_revision", "tool_policy_revision", "payload", "schema", "home_runtime_id"],
@@ -17896,7 +17953,8 @@ var EVENT_STORE_TABLES = {
17896
17953
  "outcome",
17897
17954
  "settled_event_id",
17898
17955
  "content_hash"
17899
- ]
17956
+ ],
17957
+ drive_receipts: ["drive_id", "run_id", "project_id", "opened_sequence", "last_sequence", "closed_at", "schema", "receipt"]
17900
17958
  };
17901
17959
  var EVENT_STORE_SCHEMA = `
17902
17960
  CREATE TABLE runs (
@@ -18038,7 +18096,40 @@ CREATE TABLE gate_evidence (
18038
18096
  ) STRICT;
18039
18097
  CREATE UNIQUE INDEX gate_evidence_key ON gate_evidence(run_id, step_id, step_attempt, evidence_key) WHERE evidence_key IS NOT NULL;
18040
18098
  CREATE INDEX gate_evidence_run ON gate_evidence(run_id);
18099
+ CREATE TABLE drive_receipts (
18100
+ drive_id TEXT PRIMARY KEY,
18101
+ run_id TEXT NOT NULL,
18102
+ project_id TEXT NOT NULL,
18103
+ opened_sequence INTEGER NOT NULL,
18104
+ last_sequence INTEGER NOT NULL,
18105
+ closed_at TEXT NOT NULL,
18106
+ schema TEXT NOT NULL,
18107
+ receipt TEXT NOT NULL
18108
+ ) STRICT;
18109
+ CREATE INDEX drive_receipts_run ON drive_receipts(run_id);
18110
+ `;
18111
+ var DRIVE_RECEIPTS_DDL = `
18112
+ CREATE TABLE IF NOT EXISTS drive_receipts (
18113
+ drive_id TEXT PRIMARY KEY,
18114
+ run_id TEXT NOT NULL,
18115
+ project_id TEXT NOT NULL,
18116
+ opened_sequence INTEGER NOT NULL,
18117
+ last_sequence INTEGER NOT NULL,
18118
+ closed_at TEXT NOT NULL,
18119
+ schema TEXT NOT NULL,
18120
+ receipt TEXT NOT NULL
18121
+ ) STRICT;
18122
+ CREATE INDEX IF NOT EXISTS drive_receipts_run ON drive_receipts(run_id);
18041
18123
  `;
18124
+ var EVENT_STORE_MIGRATIONS = [
18125
+ {
18126
+ fromVersion: 3,
18127
+ toVersion: 4,
18128
+ migrate(database) {
18129
+ database.exec(DRIVE_RECEIPTS_DDL);
18130
+ }
18131
+ }
18132
+ ];
18042
18133
  var VnextRunEventStore = class {
18043
18134
  path;
18044
18135
  database;
@@ -18047,7 +18138,8 @@ var VnextRunEventStore = class {
18047
18138
  this.database = openDatabase(this.path, "run event store", {
18048
18139
  schema: EVENT_STORE_SCHEMA,
18049
18140
  version: VNEXT_EVENT_STORE_SCHEMA_VERSION,
18050
- tables: EVENT_STORE_TABLES
18141
+ tables: EVENT_STORE_TABLES,
18142
+ migrations: EVENT_STORE_MIGRATIONS
18051
18143
  });
18052
18144
  }
18053
18145
  close() {
@@ -18204,6 +18296,38 @@ var VnextRunEventStore = class {
18204
18296
  ON CONFLICT(run_id) DO UPDATE SET last_sequence = excluded.last_sequence, state = excluded.state
18205
18297
  `).run(row.runId, row.lastSequence, row.state);
18206
18298
  }
18299
+ insertDriveReceipt(receipt) {
18300
+ validateDriveReceipt(receipt, "drive-receipt");
18301
+ const canonical = vnextCanonicalJson(receipt);
18302
+ if (Buffer.byteLength(canonical, "utf8") > DRIVE_RECEIPT_MAX_BYTES) {
18303
+ throw runtimeError("drive_receipt_invalid", receipt.driveId, `drive receipt exceeds ${DRIVE_RECEIPT_MAX_BYTES} bytes`);
18304
+ }
18305
+ const result = this.database.prepare(`
18306
+ INSERT INTO drive_receipts (drive_id, run_id, project_id, opened_sequence, last_sequence, closed_at, schema, receipt)
18307
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
18308
+ ON CONFLICT(drive_id) DO NOTHING
18309
+ `).run(
18310
+ receipt.driveId,
18311
+ receipt.runId,
18312
+ receipt.projectId,
18313
+ receipt.openedSequence,
18314
+ receipt.lastSequence,
18315
+ receipt.closedAt,
18316
+ receipt.schema,
18317
+ canonical
18318
+ );
18319
+ return { inserted: Number(result.changes) === 1 };
18320
+ }
18321
+ driveReceipt(driveId) {
18322
+ const row = this.database.prepare("SELECT receipt FROM drive_receipts WHERE drive_id = ?").get(driveId);
18323
+ return row ? parseDriveReceipt(row.receipt) : void 0;
18324
+ }
18325
+ driveReceiptsForRun(runId, limit = 20) {
18326
+ const rows = this.database.prepare(`
18327
+ SELECT receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
18328
+ `).all(runId, limit);
18329
+ return rows.map((row) => parseDriveReceipt(row.receipt));
18330
+ }
18207
18331
  insertCapability(row) {
18208
18332
  this.database.prepare(`
18209
18333
  INSERT INTO attempt_capabilities (attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state)
@@ -18438,6 +18562,11 @@ var VnextRunEventStore = class {
18438
18562
  }
18439
18563
  }
18440
18564
  };
18565
+ function parseDriveReceipt(raw) {
18566
+ const parsed = JSON.parse(raw);
18567
+ validateDriveReceipt(parsed, "drive-receipt");
18568
+ return parsed;
18569
+ }
18441
18570
  function runFromRow(row) {
18442
18571
  if (typeof row.memory_revision !== "string" || row.memory_revision.length === 0) {
18443
18572
  throw runtimeError("runtime_schema_shape_invalid", row.run_id, "memory_revision is required");
@@ -19066,6 +19195,9 @@ function foldVnextRunState(run, plan, events, options = {}) {
19066
19195
  case "run.cancel_requested":
19067
19196
  foldCancelRequested(state, event);
19068
19197
  break;
19198
+ case "run.drive_opened":
19199
+ foldDriveOpened(state, event);
19200
+ break;
19069
19201
  case "step.entered":
19070
19202
  foldStepEntered(state, plan, event);
19071
19203
  break;
@@ -19277,6 +19409,17 @@ function isProvenFailure(state, plan) {
19277
19409
  const used = state.stepAttempts[stepId] ?? 0;
19278
19410
  return Boolean(step && used >= step.maxAttempts);
19279
19411
  }
19412
+ function foldDriveOpened(state, event) {
19413
+ if (state.status !== "running" && state.status !== "blocked_uncertain") {
19414
+ throw runtimeError("run_events_illegal", state.runId, "run.drive_opened is only legal while running or blocked_uncertain");
19415
+ }
19416
+ const driveId = stringPayload(event, "driveId");
19417
+ const mode = stringPayload(event, "mode");
19418
+ if (mode !== "simulated" && mode !== "live") {
19419
+ throw runtimeError("run_events_illegal", state.runId, "run.drive_opened mode must be simulated or live");
19420
+ }
19421
+ state.drive = { driveId, mode, openedSequence: event.sequence };
19422
+ }
19280
19423
  function foldCancelRequested(state, event) {
19281
19424
  if (state.cancelRequested || TERMINAL_RUN.has(state.status)) {
19282
19425
  throw runtimeError("run_events_illegal", state.runId, "duplicate run.cancel_requested is not legal");
@@ -19925,7 +20068,8 @@ function freezeState(state) {
19925
20068
  transitionsUsed: state.transitionsUsed,
19926
20069
  cancelRequested: state.cancelRequested,
19927
20070
  ...state.terminalReason !== void 0 ? { terminalReason: state.terminalReason } : {},
19928
- ...state.terminalReason !== void 0 && state.status === "failed" ? { failureReason: state.terminalReason } : {}
20071
+ ...state.terminalReason !== void 0 && state.status === "failed" ? { failureReason: state.terminalReason } : {},
20072
+ ...state.drive !== void 0 ? { drive: Object.freeze({ ...state.drive }) } : {}
19929
20073
  };
19930
20074
  return Object.freeze(frozen);
19931
20075
  }
@@ -23839,6 +23983,110 @@ async function driveAdmitted(context, runId, producer, token, options = {}) {
23839
23983
  function newDriveId(runId, token, runtimeId, monotonicNs) {
23840
23984
  return `drv_${createHash10("sha256").update(`${runId}\0${token}\0${runtimeId}\0${monotonicNs}`, "utf8").digest("hex").slice(0, 24)}`;
23841
23985
  }
23986
+ function driveErrorSummary(error) {
23987
+ const raw = error instanceof VnextConfigError ? error.issues[0]?.code : void 0;
23988
+ const className = raw && /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/.test(raw) ? raw : "drive-failure";
23989
+ return { class: className, component: "vnext-engine", retryable: false };
23990
+ }
23991
+ function receiptHandoff(handoff) {
23992
+ return {
23993
+ reason: handoff.reason,
23994
+ detail: handoff.detail,
23995
+ ...handoff.field !== void 0 ? { field: handoff.field } : {},
23996
+ ...handoff.stepId !== void 0 ? { stepId: handoff.stepId } : {}
23997
+ };
23998
+ }
23999
+ function appendDriveOpened(context, runId, driveId, mode) {
24000
+ return context.eventStore.transaction(() => {
24001
+ const run = requireRun(context, runId);
24002
+ const now = (/* @__PURE__ */ new Date()).toISOString();
24003
+ const sequence = context.eventStore.nextSequence(runId);
24004
+ const event = {
24005
+ ...vnextEventBase(context, run, now, vnextMonotonicNs()),
24006
+ eventId: newVnextEventId(),
24007
+ eventType: "run.drive_opened",
24008
+ sequence,
24009
+ payload: { driveId, mode }
24010
+ };
24011
+ context.eventStore.appendEvent(event);
24012
+ const next = foldStoredVnextRun(context, run);
24013
+ persistVnextRunState(context, runId, next, sequence);
24014
+ return { sequence, occurredAt: now };
24015
+ });
24016
+ }
24017
+ function recordDriveReceipt(context, session, closeInfo) {
24018
+ if (isVnextRuntimeContextClosed(context)) return;
24019
+ try {
24020
+ const run = context.eventStore.run(session.runId);
24021
+ if (!run) return;
24022
+ const state = foldStoredVnextRun(context, run);
24023
+ const events = context.eventStore.events(session.runId, 0, 1e6);
24024
+ if (events.length === 0) return;
24025
+ const opened = state.drive;
24026
+ const openedEvent = opened ? events.find((event) => event.sequence === opened.openedSequence && event.eventType === "run.drive_opened") : events.find((event) => event.eventType === "run.drive_opened" && event.payload.driveId === session.driveId);
24027
+ const last = events[events.length - 1];
24028
+ const openedSequence = opened?.openedSequence ?? openedEvent?.sequence;
24029
+ if (openedSequence === void 0) return;
24030
+ const receipt = {
24031
+ schema: VNEXT_DRIVE_RECEIPT_SCHEMA,
24032
+ driveId: session.driveId,
24033
+ runId: session.runId,
24034
+ projectId: run.projectId,
24035
+ homeRuntimeId: session.homeRuntimeId,
24036
+ mode: session.mode,
24037
+ openedAt: openedEvent?.occurredAt ?? session.openedAt,
24038
+ openedSequence,
24039
+ closedAt: (/* @__PURE__ */ new Date()).toISOString(),
24040
+ lastSequence: last.sequence,
24041
+ logHash: hashVnextDriveLog(events.filter((event) => event.sequence >= 1 && event.sequence <= last.sequence)),
24042
+ settlement: {
24043
+ kind: closeInfo.kind,
24044
+ status: state.status,
24045
+ reason: closeInfo.reason ?? state.terminalReason ?? "",
24046
+ ...closeInfo.kind === "handoff" && closeInfo.handoff ? { handoff: receiptHandoff(closeInfo.handoff) } : {},
24047
+ ...closeInfo.error !== void 0 ? { error: closeInfo.error } : {}
24048
+ },
24049
+ budget: null,
24050
+ producer: { id: closeInfo.producerId, closed: closeInfo.producerClosed }
24051
+ };
24052
+ context.eventStore.insertDriveReceipt(receipt);
24053
+ } catch {
24054
+ }
24055
+ }
24056
+ function vnextDrivePollProjection(context, runId, state) {
24057
+ if (!state.drive) return void 0;
24058
+ const events = context.eventStore.events(runId, 0, 1e6);
24059
+ const openedEvent = events.find((event) => event.sequence === state.drive.openedSequence);
24060
+ let receipt = null;
24061
+ let unreadable = false;
24062
+ try {
24063
+ receipt = context.eventStore.driveReceipt(state.drive.driveId) ?? null;
24064
+ } catch {
24065
+ unreadable = true;
24066
+ }
24067
+ const projection = {
24068
+ driveId: state.drive.driveId,
24069
+ mode: state.drive.mode,
24070
+ openedAt: openedEvent?.occurredAt ?? "",
24071
+ receipt: unreadable ? null : receipt,
24072
+ verified: false
24073
+ };
24074
+ if (unreadable) {
24075
+ projection.divergence = "receipt unreadable";
24076
+ return projection;
24077
+ }
24078
+ if (!receipt) {
24079
+ projection.divergence = "no receipt";
24080
+ return projection;
24081
+ }
24082
+ const checked = verifyVnextDriveReceipt(receipt, events, state.status, {
24083
+ runId,
24084
+ driveId: state.drive.driveId
24085
+ });
24086
+ projection.verified = checked.verified;
24087
+ if (checked.divergence !== void 0) projection.divergence = checked.divergence;
24088
+ return projection;
24089
+ }
23842
24090
  function inflightAttemptId(state) {
23843
24091
  const leftover = unreconciledPanelAttemptId(state);
23844
24092
  if (leftover) return leftover;
@@ -23986,13 +24234,15 @@ var VnextRunScheduler = class _VnextRunScheduler {
23986
24234
  throw error;
23987
24235
  }
23988
24236
  const driveId = newDriveId(runId, token, this.context.homeRuntimeId, vnextMonotonicNs());
24237
+ const openedMeta = appendDriveOpened(this.context, runId, driveId, options.mode);
23989
24238
  const session = {
23990
24239
  driveId,
23991
24240
  runId,
23992
24241
  token,
23993
24242
  homeRuntimeId: this.context.homeRuntimeId,
23994
24243
  mode: options.mode,
23995
- openedAt: (/* @__PURE__ */ new Date()).toISOString(),
24244
+ openedAt: openedMeta.occurredAt,
24245
+ producerId: producer.id,
23996
24246
  controller: new AbortController(),
23997
24247
  settled
23998
24248
  };
@@ -24001,10 +24251,30 @@ var VnextRunScheduler = class _VnextRunScheduler {
24001
24251
  resolveOpen({ driveId, settled });
24002
24252
  try {
24003
24253
  const result = await driveAdmitted(this.context, runId, producer, token, driveOptions);
24254
+ const kind = result.handoff ? "handoff" : isTerminalRunStatus(result.state.status) ? "terminal" : "unsettled";
24255
+ recordDriveReceipt(this.context, session, {
24256
+ kind,
24257
+ ...result.state.terminalReason !== void 0 ? { reason: result.state.terminalReason } : result.handoff ? { reason: result.handoff.reason } : {},
24258
+ ...result.handoff ? { handoff: result.handoff } : {},
24259
+ producerId: producer.id,
24260
+ producerClosed: false
24261
+ });
24004
24262
  pending.resolve(result);
24005
24263
  return result;
24006
24264
  } catch (error) {
24007
24265
  recordBareDriveFailure(this.context, runId);
24266
+ let foldedStatus;
24267
+ try {
24268
+ foldedStatus = foldStoredVnextRun(this.context, requireRun(this.context, runId)).status;
24269
+ } catch {
24270
+ foldedStatus = void 0;
24271
+ }
24272
+ recordDriveReceipt(this.context, session, {
24273
+ kind: foldedStatus !== void 0 && isTerminalRunStatus(foldedStatus) ? "terminal" : "unsettled",
24274
+ error: driveErrorSummary(error),
24275
+ producerId: producer.id,
24276
+ producerClosed: false
24277
+ });
24008
24278
  pending.reject(error);
24009
24279
  throw error;
24010
24280
  }
@@ -26248,6 +26518,19 @@ async function waitForDriveSessions(settled, graceMs) {
26248
26518
  if (timeout !== void 0) clearTimeout(timeout);
26249
26519
  }
26250
26520
  }
26521
+ async function driveSessionStillPending(settled) {
26522
+ let pending = true;
26523
+ void settled.then(
26524
+ () => {
26525
+ pending = false;
26526
+ },
26527
+ () => {
26528
+ pending = false;
26529
+ }
26530
+ );
26531
+ await Promise.resolve();
26532
+ return pending;
26533
+ }
26251
26534
  async function startVnextRuntimeSupervisor(options = {}) {
26252
26535
  const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
26253
26536
  const paths = vnextRuntimePaths(options.stateRoot !== void 0 ? { stateRoot: options.stateRoot } : {});
@@ -26351,7 +26634,9 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
26351
26634
  const bundle = loadVnextProject(projectRoot, {});
26352
26635
  if (request.method === "GET" && !sub) {
26353
26636
  const projected = rebuildVnextRunProjection(context, runId);
26354
- sendJson(response, 200, { ok: true, run: projected });
26637
+ const folded = foldStoredVnextRun(context, projected);
26638
+ const drive = vnextDrivePollProjection(context, runId, folded);
26639
+ sendJson(response, 200, { ok: true, run: projected, ...drive !== void 0 ? { drive } : {} });
26355
26640
  return;
26356
26641
  }
26357
26642
  if (request.method === "POST" && sub === "drive") {
@@ -26568,6 +26853,15 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
26568
26853
  }
26569
26854
  }
26570
26855
  await waitForDriveSessions(openSessions.map(({ session }) => session.settled), runtimeStopGraceMs());
26856
+ for (const { context, session } of openSessions) {
26857
+ if (!await driveSessionStillPending(session.settled)) continue;
26858
+ recordDriveReceipt(context, session, {
26859
+ kind: "unsettled",
26860
+ reason: "runtime_shutdown_grace_expired",
26861
+ producerId: session.producerId,
26862
+ producerClosed: false
26863
+ });
26864
+ }
26571
26865
  for (const context of contexts.values()) closeVnextRuntimeContext(context);
26572
26866
  contexts.clear();
26573
26867
  const closed = new Promise((resolveStop) => server.close(() => resolveStop()));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kxm-claude-plugin",
3
- "version": "0.7.23",
3
+ "version": "0.7.24",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -473,6 +473,42 @@ export async function cmdVnextRun(runtime: Runtime, workflow: string | undefined
473
473
  }
474
474
  }
475
475
 
476
+ interface VnextStatusDrive {
477
+ driveId?: string;
478
+ mode?: string;
479
+ openedAt?: string;
480
+ receipt?: {
481
+ settlement?: { kind?: string; status?: string; reason?: string };
482
+ } | null;
483
+ verified?: boolean;
484
+ divergence?: string;
485
+ }
486
+
487
+ function formatDriveStatusLine(runStatus: string, drive: VnextStatusDrive | undefined): string | undefined {
488
+ if (!drive || typeof drive.driveId !== "string" || drive.driveId.length === 0) return undefined;
489
+ const receipt = drive.receipt ?? null;
490
+ if (receipt === null) {
491
+ if (runStatus === "running" || runStatus === "blocked_uncertain") {
492
+ return `drive ${drive.driveId}: open`;
493
+ }
494
+ return `drive ${drive.driveId}: no receipt (orphaned)`;
495
+ }
496
+ const kind = receipt.settlement?.kind;
497
+ const reason = typeof receipt.settlement?.reason === "string" ? receipt.settlement.reason : "";
498
+ if (kind === "unsettled") {
499
+ return `drive ${drive.driveId}: unsettled ${reason}`.trimEnd();
500
+ }
501
+ if (kind === "handoff") {
502
+ return drive.verified === true
503
+ ? `drive ${drive.driveId}: handoff (receipt verified)`
504
+ : `drive ${drive.driveId}: handoff`;
505
+ }
506
+ if (drive.verified === true) {
507
+ return `drive ${drive.driveId}: completed (receipt verified)`;
508
+ }
509
+ return `drive ${drive.driveId}: completed`;
510
+ }
511
+
476
512
  export async function cmdVnextRunStatus(runtime: Runtime, runId: string): Promise<number> {
477
513
  try {
478
514
  const projectRoot = discoverVnextProjectRoot(runtime.cwd);
@@ -480,10 +516,17 @@ export async function cmdVnextRunStatus(runtime: Runtime, runId: string): Promis
480
516
  print(runtime.io, runtime.json, { ok: false, command: "runs status", error: "project_required" }, "kxm runs status requires a vNext project (run kxm init first)");
481
517
  return 1;
482
518
  }
483
- const supervisor = await ensureVnextSupervisor({ env: runtime.env });
484
- const result = await vnextRuntimeRequest(supervisor, "GET", `/v1/runs/${encodeURIComponent(runId)}?projectRoot=${encodeURIComponent(projectRoot)}`);
519
+ const supervisor = await (vnextDriveCliSeams.ensureSupervisor ?? ensureVnextSupervisor)({ env: runtime.env });
520
+ const result = await (vnextDriveCliSeams.runtimeRequest ?? vnextRuntimeRequest)(supervisor, "GET", `/v1/runs/${encodeURIComponent(runId)}?projectRoot=${encodeURIComponent(projectRoot)}`);
485
521
  const run = result.run as { runId: string; status: string; workflowId: string; configRevision: string; updatedAt: string };
486
- print(runtime.io, runtime.json, { ok: true, command: "runs status", run }, `run ${run.runId}: ${run.status} (workflow ${run.workflowId}, updated ${run.updatedAt})`);
522
+ const drive = result.drive as VnextStatusDrive | undefined;
523
+ const driveLine = formatDriveStatusLine(run.status, drive);
524
+ print(
525
+ runtime.io,
526
+ runtime.json,
527
+ { ok: true, command: "runs status", run, ...(drive !== undefined ? { drive } : {}) },
528
+ `run ${run.runId}: ${run.status} (workflow ${run.workflowId}, updated ${run.updatedAt})${driveLine ? `\n${driveLine}` : ""}`,
529
+ );
487
530
  return 0;
488
531
  } catch (error) {
489
532
  if (error instanceof VnextConfigError) {
@@ -382,7 +382,7 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
382
382
  }));
383
383
  const runCmd = addGlobalOptions(program.command("runs").description("Inspect vNext runs"));
384
384
  runCmd.helpCommand("help", "Show runs help");
385
- addGlobalOptions(runCmd.command("status").description("Show the projected status of a run"))
385
+ addGlobalOptions(runCmd.command("status").description("Show the projected status of a run, including durable drive receipt state (open / receipt verified / unsettled / orphaned)"))
386
386
  .argument("<runId>", "Run id")
387
387
  .action(async function runStatusAction(this: Command, runId: string) {
388
388
  result.code = await cmdVnextRunStatus(runtimeFrom(ctx, this), runId);
@@ -420,7 +420,7 @@ export function discoverProjectStores(projectRoot: string, options: { hubDataPat
420
420
  stores.push({
421
421
  storeId: `events:${key}`,
422
422
  sourcePath: join(eventsDir, entry.name),
423
- maxSupportedVersion: 3,
423
+ maxSupportedVersion: 4,
424
424
  });
425
425
  }
426
426
  }
@@ -537,6 +537,8 @@ export function restoreBackup(
537
537
  let maxSupported = 3;
538
538
  if (store.storeId === "registry" || store.storeId === "binding-store") {
539
539
  maxSupported = 1;
540
+ } else if (store.storeId.startsWith("events:")) {
541
+ maxSupported = 4;
540
542
  }
541
543
 
542
544
  let targetPath = store.sourcePath;
@@ -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.23";
11
+ const VERSION = "0.7.24";
12
12
  const inbox = new Map<string, MessageRecord>();
13
13
  const notifiedInbox = new Set<string>();
14
14
  let meshClient: HubClient | undefined;
@@ -166,6 +166,7 @@ export class VnextSchemaRegistry {
166
166
  readonly migrationReceiptValidator: ValidateFunction;
167
167
  readonly permissionDiffValidator: ValidateFunction;
168
168
  readonly runEventValidator: ValidateFunction;
169
+ readonly driveReceiptValidator: ValidateFunction;
169
170
 
170
171
  constructor(schemasDir = DEFAULT_SCHEMA_DIR) {
171
172
  this.schemasDir = resolve(schemasDir);
@@ -183,6 +184,7 @@ export class VnextSchemaRegistry {
183
184
  const migrationReceiptFile = "migration-receipt.schema.json";
184
185
  const permissionDiffFile = "permission-diff.schema.json";
185
186
  const runEventFile = "run-event.schema.json";
187
+ const driveReceiptFile = "drive-receipt.schema.json";
186
188
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, localBindingsFile)));
187
189
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, templateProvenanceFile)));
188
190
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, initOperationFile)));
@@ -191,6 +193,7 @@ export class VnextSchemaRegistry {
191
193
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, migrationReceiptFile)));
192
194
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, permissionDiffFile)));
193
195
  this.ajv.addSchema(readJsonObject(join(this.schemasDir, runEventFile)));
196
+ this.ajv.addSchema(readJsonObject(join(this.schemasDir, driveReceiptFile)));
194
197
  for (const [kind, definition] of Object.entries(RESOURCE_SCHEMA) as [VnextResourceKind, { identity: string; file: string }][]) {
195
198
  const validator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${definition.file}`);
196
199
  if (!validator) throw new Error(`schema did not compile: ${definition.file}`);
@@ -204,6 +207,7 @@ export class VnextSchemaRegistry {
204
207
  const migrationReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${migrationReceiptFile}`);
205
208
  const permissionDiffValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${permissionDiffFile}`);
206
209
  const runEventValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${runEventFile}`);
210
+ const driveReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${driveReceiptFile}`);
207
211
  if (!localBindingsValidator) throw new Error(`schema did not compile: ${localBindingsFile}`);
208
212
  if (!templateProvenanceValidator) throw new Error(`schema did not compile: ${templateProvenanceFile}`);
209
213
  if (!initOperationValidator) throw new Error(`schema did not compile: ${initOperationFile}`);
@@ -212,6 +216,7 @@ export class VnextSchemaRegistry {
212
216
  if (!migrationReceiptValidator) throw new Error(`schema did not compile: ${migrationReceiptFile}`);
213
217
  if (!permissionDiffValidator) throw new Error(`schema did not compile: ${permissionDiffFile}`);
214
218
  if (!runEventValidator) throw new Error(`schema did not compile: ${runEventFile}`);
219
+ if (!driveReceiptValidator) throw new Error(`schema did not compile: ${driveReceiptFile}`);
215
220
  this.localBindingsValidator = localBindingsValidator;
216
221
  this.templateProvenanceValidator = templateProvenanceValidator;
217
222
  this.initOperationValidator = initOperationValidator;
@@ -220,6 +225,7 @@ export class VnextSchemaRegistry {
220
225
  this.migrationReceiptValidator = migrationReceiptValidator;
221
226
  this.permissionDiffValidator = permissionDiffValidator;
222
227
  this.runEventValidator = runEventValidator;
228
+ this.driveReceiptValidator = driveReceiptValidator;
223
229
  }
224
230
 
225
231
  validate(kind: VnextResourceKind, value: JsonObject, file: string): VnextConfigIssue[] {
@@ -261,6 +267,10 @@ export class VnextSchemaRegistry {
261
267
  return this.validateAuxiliary(value, file, "kxm.permission-diff.v1", this.permissionDiffValidator);
262
268
  }
263
269
 
270
+ validateDriveReceipt(value: JsonObject, file: string): VnextConfigIssue[] {
271
+ return this.validateAuxiliary(value, file, "kxm.drive-receipt.v1", this.driveReceiptValidator);
272
+ }
273
+
264
274
  private validateAuxiliary(value: JsonObject, file: string, identity: string, validator: ValidateFunction): VnextConfigIssue[] {
265
275
  if (value.schema !== identity) {
266
276
  return [issue("schema", "schema_identity_mismatch", file, `expected ${identity}, received ${String(value.schema)}`)];
@@ -285,6 +295,27 @@ export function validateRunEvent(value: unknown, file: string): void {
285
295
  }
286
296
  }
287
297
 
298
+ /** Validate a drive receipt against `kxm.drive-receipt.v1`. Throws `drive_receipt_invalid`. */
299
+ export function validateDriveReceipt(value: unknown, file: string): void {
300
+ const registry = (cachedRunEventRegistry ??= new VnextSchemaRegistry());
301
+ if (!value || typeof value !== "object" || Array.isArray(value) || (value as JsonObject).schema !== "kxm.drive-receipt.v1") {
302
+ throw new VnextConfigError([issue(
303
+ "schema",
304
+ "drive_receipt_invalid",
305
+ file,
306
+ "expected kxm.drive-receipt.v1",
307
+ )]);
308
+ }
309
+ if (!registry.driveReceiptValidator(value)) {
310
+ throw new VnextConfigError([issue(
311
+ "schema",
312
+ "drive_receipt_invalid",
313
+ file,
314
+ registry.ajv.errorsText(registry.driveReceiptValidator.errors, { separator: "; " }),
315
+ )]);
316
+ }
317
+ }
318
+
288
319
  function schemaIssue(file: string, error: ErrorObject): VnextConfigIssue {
289
320
  const location = error.instancePath || "/";
290
321
  const suffix = error.params && "additionalProperty" in error.params