@kontextmind/kxm 0.7.23 → 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.
@@ -23,7 +23,7 @@ import {
23
23
  } from "./vnext-runtime.ts";
24
24
  import { createVnextOneShotProducer } from "./vnext-oneshot-producer.ts";
25
25
  import { isProducerAdmitted } from "./producers.ts";
26
- import { VnextRunScheduler, createVnextSimulatedProducer, recoverVnextRun } from "./vnext-engine.ts";
26
+ import { VnextRunScheduler, createVnextSimulatedProducer, recordDriveReceipt, recoverVnextRun, vnextDrivePollProjection } from "./vnext-engine.ts";
27
27
  import { vnextOpenDriveSessions } from "./vnext-runtime-owner.ts";
28
28
 
29
29
  /* ------------------------------------------------------------------ *
@@ -318,6 +318,16 @@ async function waitForDriveSessions(settled: Promise<unknown>[], graceMs: number
318
318
  }
319
319
  }
320
320
 
321
+ async function driveSessionStillPending(settled: Promise<unknown>): Promise<boolean> {
322
+ let pending = true;
323
+ void settled.then(
324
+ () => { pending = false; },
325
+ () => { pending = false; },
326
+ );
327
+ await Promise.resolve();
328
+ return pending;
329
+ }
330
+
321
331
  export interface VnextRuntimeSupervisor {
322
332
  server: Server;
323
333
  port: number;
@@ -446,7 +456,9 @@ async function startVnextRuntimeSupervisorInner(
446
456
  const bundle = loadVnextProject(projectRoot, {});
447
457
  if (request.method === "GET" && !sub) {
448
458
  const projected = rebuildVnextRunProjection(context, runId);
449
- sendJson(response, 200, { ok: true, run: projected });
459
+ const folded = foldStoredVnextRun(context, projected);
460
+ const drive = vnextDrivePollProjection(context, runId, folded);
461
+ sendJson(response, 200, { ok: true, run: projected, ...(drive !== undefined ? { drive } : {}) });
450
462
  return;
451
463
  }
452
464
  if (request.method === "POST" && sub === "drive") {
@@ -493,7 +505,7 @@ async function startVnextRuntimeSupervisorInner(
493
505
  const scheduler = VnextRunScheduler.for(context, bundle);
494
506
  const opening = scheduler.openDriveSession(runId, {
495
507
  mode: body.mode,
496
- allowLimits: true,
508
+ allowLimits: false,
497
509
  liveMode: body.mode === "live",
498
510
  createProducer,
499
511
  });
@@ -684,6 +696,15 @@ async function startVnextRuntimeSupervisorInner(
684
696
  }
685
697
  }
686
698
  await waitForDriveSessions(openSessions.map(({ session }) => session.settled), runtimeStopGraceMs());
699
+ for (const { context, session } of openSessions) {
700
+ if (!(await driveSessionStillPending(session.settled))) continue;
701
+ recordDriveReceipt(context, session, {
702
+ kind: "unsettled",
703
+ reason: "runtime_shutdown_grace_expired",
704
+ producerId: session.producerId,
705
+ producerClosed: false,
706
+ });
707
+ }
687
708
  for (const context of contexts.values()) closeVnextRuntimeContext(context);
688
709
  contexts.clear();
689
710
  const closed = new Promise<void>((resolveStop) => server.close(() => resolveStop()));
@@ -464,6 +464,7 @@ export function foldStoredVnextRun(context: VnextRuntimeContext, run: VnextRunRe
464
464
  if (!step || step.kind !== "gate") return undefined;
465
465
  return envelope.gates.definitions[step.gate]?.kind;
466
466
  },
467
+ projectLimits: envelope.projectLimits,
467
468
  }
468
469
  : {});
469
470
  if (envelope) verifyVnextGateEvidence(context.eventStore, run, envelope, events, state);
@@ -551,7 +552,13 @@ export interface VnextRunCancelResult {
551
552
  export function cancelVnextRun(
552
553
  context: VnextRuntimeContext,
553
554
  runId: string,
554
- options: { commandId?: string; now?: string; monotonicNs?: string; reason?: string } = {},
555
+ options: {
556
+ commandId?: string;
557
+ now?: string;
558
+ monotonicNs?: string;
559
+ reason?: string;
560
+ budget?: { budgetMs: number; elapsedMs: number; source: "workflow" | "project" | "both" };
561
+ } = {},
555
562
  ): VnextRunCancelResult {
556
563
  const commandId = options.commandId ?? newVnextCommandId();
557
564
  const now = options.now ?? new Date().toISOString();
@@ -600,10 +607,17 @@ export function cancelVnextRun(
600
607
 
601
608
  const cancelReason = options.reason ?? "operator_cancel";
602
609
  const activeAttempt = Boolean(folded.currentStep?.attemptId) || vnextFoldPanelAttemptIds(folded.currentStep).length > 0;
603
- push("run.cancel_requested", {
610
+ const cancelPayload: Record<string, unknown> = {
604
611
  actor: { kind: "runtime", id: context.homeRuntimeId },
605
612
  reason: cancelReason,
606
- });
613
+ };
614
+ if (cancelReason === "budget_run_duration") {
615
+ if (!options.budget) {
616
+ throw runtimeError("run_events_illegal", runId, "budget_run_duration requires a budget payload");
617
+ }
618
+ cancelPayload.budget = options.budget;
619
+ }
620
+ push("run.cancel_requested", cancelPayload);
607
621
 
608
622
  let status: VnextRunStatus = "cancelled";
609
623
  if (folded.status === "running" && activeAttempt) {
@@ -164,6 +164,7 @@
164
164
  "run.created",
165
165
  "run.status_changed",
166
166
  "run.cancel_requested",
167
+ "run.drive_opened",
167
168
  "step.entered",
168
169
  "step.status_changed",
169
170
  "step.outcome_recorded",
@@ -0,0 +1,95 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.kxm.dev/vnext/drive-receipt.schema.json",
4
+ "title": "KXM vNext drive session receipt",
5
+ "type": "object",
6
+ "required": [
7
+ "schema",
8
+ "driveId",
9
+ "runId",
10
+ "projectId",
11
+ "homeRuntimeId",
12
+ "mode",
13
+ "openedAt",
14
+ "openedSequence",
15
+ "closedAt",
16
+ "lastSequence",
17
+ "logHash",
18
+ "settlement",
19
+ "budget",
20
+ "producer"
21
+ ],
22
+ "properties": {
23
+ "schema": { "const": "kxm.drive-receipt.v1" },
24
+ "driveId": { "$ref": "common.schema.json#/$defs/opaqueId" },
25
+ "runId": { "$ref": "common.schema.json#/$defs/opaqueId" },
26
+ "projectId": { "$ref": "common.schema.json#/$defs/opaqueId" },
27
+ "homeRuntimeId": { "$ref": "common.schema.json#/$defs/opaqueId" },
28
+ "mode": { "enum": ["simulated", "live"] },
29
+ "openedAt": { "$ref": "common.schema.json#/$defs/timestamp" },
30
+ "openedSequence": { "type": "integer", "minimum": 1 },
31
+ "closedAt": { "$ref": "common.schema.json#/$defs/timestamp" },
32
+ "lastSequence": { "type": "integer", "minimum": 1 },
33
+ "logHash": { "$ref": "common.schema.json#/$defs/sha256" },
34
+ "settlement": { "$ref": "#/$defs/settlement" },
35
+ "budget": { "$ref": "#/$defs/budget" },
36
+ "producer": {
37
+ "type": "object",
38
+ "required": ["id", "closed"],
39
+ "properties": {
40
+ "id": { "$ref": "common.schema.json#/$defs/identifier" },
41
+ "closed": { "type": "boolean" }
42
+ },
43
+ "additionalProperties": false
44
+ }
45
+ },
46
+ "additionalProperties": false,
47
+ "$defs": {
48
+ "settlement": {
49
+ "type": "object",
50
+ "required": ["kind", "status", "reason"],
51
+ "properties": {
52
+ "kind": { "enum": ["terminal", "handoff", "unsettled"] },
53
+ "status": { "$ref": "common.schema.json#/$defs/lifecycleStatus" },
54
+ "reason": { "type": "string", "maxLength": 4000 },
55
+ "handoff": { "$ref": "#/$defs/handoff" },
56
+ "error": { "$ref": "common.schema.json#/$defs/errorSummary" }
57
+ },
58
+ "additionalProperties": false,
59
+ "allOf": [
60
+ {
61
+ "if": { "properties": { "kind": { "const": "handoff" } }, "required": ["kind"] },
62
+ "then": { "required": ["handoff"] },
63
+ "else": { "not": { "required": ["handoff"] } }
64
+ }
65
+ ]
66
+ },
67
+ "budget": {
68
+ "oneOf": [
69
+ { "type": "null" },
70
+ {
71
+ "type": "object",
72
+ "required": ["budgetMs", "source", "elapsedMs", "overrun"],
73
+ "properties": {
74
+ "budgetMs": { "$ref": "common.schema.json#/$defs/durationMs" },
75
+ "source": { "enum": ["workflow", "project", "both"] },
76
+ "elapsedMs": { "type": "integer", "minimum": 0 },
77
+ "overrun": { "type": "boolean" }
78
+ },
79
+ "additionalProperties": false
80
+ }
81
+ ]
82
+ },
83
+ "handoff": {
84
+ "type": "object",
85
+ "required": ["reason", "detail"],
86
+ "properties": {
87
+ "reason": { "type": "string", "minLength": 1, "maxLength": 80 },
88
+ "field": { "type": "string", "minLength": 1, "maxLength": 128 },
89
+ "stepId": { "$ref": "common.schema.json#/$defs/identifier" },
90
+ "detail": { "type": "string", "minLength": 1, "maxLength": 4000 }
91
+ },
92
+ "additionalProperties": false
93
+ }
94
+ }
95
+ }
@@ -90,6 +90,24 @@
90
90
  }
91
91
  }
92
92
  },
93
+ {
94
+ "if": {
95
+ "properties": {
96
+ "eventType": { "const": "run.cancel_requested" },
97
+ "payload": {
98
+ "type": "object",
99
+ "properties": { "reason": { "const": "budget_run_duration" } },
100
+ "required": ["reason"]
101
+ }
102
+ },
103
+ "required": ["eventType", "payload"]
104
+ },
105
+ "then": {
106
+ "properties": {
107
+ "payload": { "type": "object", "required": ["actor", "reason", "budget"] }
108
+ }
109
+ }
110
+ },
93
111
  {
94
112
  "if": { "properties": { "eventType": { "const": "step.entered" } }, "required": ["eventType"] },
95
113
  "then": {
@@ -278,10 +296,28 @@
278
296
  "payload": { "type": "object", "required": ["routing"] }
279
297
  }
280
298
  }
299
+ },
300
+ {
301
+ "if": { "properties": { "eventType": { "const": "run.drive_opened" } }, "required": ["eventType"] },
302
+ "then": {
303
+ "properties": {
304
+ "payload": { "type": "object", "required": ["driveId", "mode"] }
305
+ }
306
+ }
281
307
  }
282
308
  ],
283
309
  "additionalProperties": false,
284
310
  "$defs": {
311
+ "runDurationBudget": {
312
+ "type": "object",
313
+ "required": ["budgetMs", "elapsedMs", "source"],
314
+ "properties": {
315
+ "budgetMs": { "$ref": "common.schema.json#/$defs/durationMs" },
316
+ "elapsedMs": { "type": "integer", "minimum": 0 },
317
+ "source": { "enum": ["workflow", "project", "both"] }
318
+ },
319
+ "additionalProperties": false
320
+ },
285
321
  "repositorySnapshot": {
286
322
  "type": "object",
287
323
  "required": ["repositoryId", "baseCommit", "contentSnapshotHash"],
@@ -383,6 +419,7 @@
383
419
  "timing": { "$ref": "common.schema.json#/$defs/timing" },
384
420
  "usage": { "$ref": "common.schema.json#/$defs/usage" },
385
421
  "error": { "$ref": "common.schema.json#/$defs/errorSummary" },
422
+ "budget": { "$ref": "#/$defs/runDurationBudget" },
386
423
  "actor": { "$ref": "common.schema.json#/$defs/actor" },
387
424
  "resolvedBy": { "$ref": "common.schema.json#/$defs/actor" },
388
425
  "resolutionAction": {
@@ -394,6 +431,8 @@
394
431
  "maxItems": 64
395
432
  },
396
433
  "lease": { "$ref": "common.schema.json#/$defs/lease" },
434
+ "driveId": { "$ref": "common.schema.json#/$defs/opaqueId" },
435
+ "mode": { "enum": ["simulated", "live"] },
397
436
  "candidateId": { "$ref": "common.schema.json#/$defs/opaqueId" },
398
437
  "counts": {
399
438
  "type": "object",