@kontextmind/kxm 0.7.22 → 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.22",
3
+ "version": "0.7.24",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync
3
3
  import { join, resolve } from "node:path";
4
4
  import { redactSecrets } from "../redact.ts";
5
5
  import { defaultProjectName } from "../project-name.ts";
6
+ import { resolveClientHubAuthToken } from "../hub-env.ts";
6
7
  import { agentWorker, type Worker } from "../envelope.ts";
7
8
  import { MESH_TUI_PANELS, runMeshTui, type MeshTuiPanel } from "../tui.ts";
8
9
  import { formatSessionBriefText, loadSessionBriefAsync, type SessionHubStatus } from "../session-work.ts";
@@ -132,7 +133,7 @@ export async function cmdDash(runtime: Runtime, options: { screen?: string | und
132
133
  return 2;
133
134
  }
134
135
  const project = defaultProjectName(runtime.dirs.workdir, runtime.env) || "project";
135
- const authToken = runtime.env.KXM_AUTH_TOKEN?.trim();
136
+ const authToken = resolveClientHubAuthToken(runtime.env, project);
136
137
  return await runMeshTui({
137
138
  serverUrl: runtime.serverUrl,
138
139
  dataPath,
@@ -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) {
@@ -18,7 +18,7 @@ import { Command, CommanderError } from "commander";
18
18
  import { readInstalledKxmVersion } from "./kxm-update.ts";
19
19
  import { findKxmRepoRoot } from "./repo-root.ts";
20
20
  import { HubClient } from "./client.ts";
21
- import { readHubEnvRecord } from "./hub-env.ts";
21
+ import { resolveClientHubAuthToken } from "./hub-env.ts";
22
22
  import { defaultProjectName } from "./project-name.ts";
23
23
  import {
24
24
  AGENT_COMMANDS_MAP,
@@ -158,16 +158,6 @@ export { hubContextPost };
158
158
 
159
159
  const CLI_NAME = "kxm";
160
160
 
161
- /** Authenticate CLI hub clients: explicit KXM_AUTH_TOKEN first, then the
162
- * persisted hub env record (project token for this project, then the admin
163
- * token), matching the credential precedence `kxm hub start` prints. */
164
- function resolveCliAuthToken(runtime: Runtime, project: string): string | undefined {
165
- const envToken = runtime.env.KXM_AUTH_TOKEN?.trim();
166
- if (envToken) return envToken;
167
- const record = readHubEnvRecord(runtime.env);
168
- return record?.projectTokens?.[project]?.trim() || record?.authToken?.trim() || undefined;
169
- }
170
-
171
161
  const USAGE_ERROR_CODES = new Set([
172
162
  "commander.help",
173
163
  "commander.helpDisplayed",
@@ -194,7 +184,7 @@ async function ensureCliClient(runtime: Runtime): Promise<HubClient> {
194
184
  const project = defaultProjectName(runtime.cwd, runtime.env);
195
185
  const name = runtime.env.KXM_AGENT_NAME?.trim() || `cli-${process.pid}`;
196
186
  const purpose = runtime.env.KXM_AGENT_PURPOSE?.trim() || "CLI agent client";
197
- const authToken = resolveCliAuthToken(runtime, project);
187
+ const authToken = resolveClientHubAuthToken(runtime.env, project);
198
188
  const client = new HubClient({
199
189
  serverUrl,
200
190
  name,
@@ -392,7 +382,7 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
392
382
  }));
393
383
  const runCmd = addGlobalOptions(program.command("runs").description("Inspect vNext runs"));
394
384
  runCmd.helpCommand("help", "Show runs help");
395
- 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)"))
396
386
  .argument("<runId>", "Run id")
397
387
  .action(async function runStatusAction(this: Command, runId: string) {
398
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;
@@ -191,3 +191,18 @@ export function resolveHubCredentials(options: ResolveHubCredentialsOptions = {}
191
191
  written: needsPersist === true,
192
192
  };
193
193
  }
194
+
195
+ /** Read-only token for one-shot hub clients (CLI, MCP server, dashboards).
196
+ *
197
+ * Precedence: explicit KXM_AUTH_TOKEN, then the persisted project token for
198
+ * the resolved project, then the persisted admin token. Never generates or
199
+ * writes; a malformed persisted record fails closed with HubEnvError. This
200
+ * matches the credential precedence `kxm hub start` announces, so a hub
201
+ * started fresh (generated token persisted) accepts authenticated client
202
+ * commands without the operator exporting the token. */
203
+ export function resolveClientHubAuthToken(env: NodeJS.ProcessEnv, project: string): string | undefined {
204
+ const envToken = env.KXM_AUTH_TOKEN?.trim();
205
+ if (envToken) return envToken;
206
+ const record = readHubEnvRecord(env);
207
+ return record?.projectTokens?.[project]?.trim() || record?.authToken?.trim() || undefined;
208
+ }