@kontextmind/kxm 0.7.14 → 0.7.16

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.
Files changed (47) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/docs/configuration.md +1 -2
  3. package/docs/operator-pi-packages.md +5 -2
  4. package/package.json +1 -1
  5. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  6. package/plugins/kxm/dist/cli.js +20 -3
  7. package/plugins/kxm/dist/core.js +4 -0
  8. package/plugins/kxm/dist/extension.js +3066 -2
  9. package/plugins/kxm/dist/mcp-server.js +1 -1
  10. package/plugins/kxm/dist/runtime.js +263 -70
  11. package/plugins/kxm/dist/server.js +4 -0
  12. package/plugins/kxm/dist/vnext-runtime-supervisor.js +259 -70
  13. package/plugins/kxm/package.json +1 -1
  14. package/plugins/kxm/src/cli/vnext.ts +20 -3
  15. package/plugins/kxm/src/extension.ts +9 -0
  16. package/plugins/kxm/src/mcp-server.ts +1 -1
  17. package/plugins/kxm/src/providers/antigravity/LICENSE +21 -0
  18. package/plugins/kxm/src/providers/antigravity/README.md +19 -0
  19. package/plugins/kxm/src/providers/antigravity/auth/index.ts +17 -0
  20. package/plugins/kxm/src/providers/antigravity/auth/oauth.ts +449 -0
  21. package/plugins/kxm/src/providers/antigravity/client/client.ts +562 -0
  22. package/plugins/kxm/src/providers/antigravity/client/index.ts +1 -0
  23. package/plugins/kxm/src/providers/antigravity/diagnostics/diagnostics.ts +97 -0
  24. package/plugins/kxm/src/providers/antigravity/diagnostics/index.ts +1 -0
  25. package/plugins/kxm/src/providers/antigravity/index.ts +24 -0
  26. package/plugins/kxm/src/providers/antigravity/models/discovery.ts +155 -0
  27. package/plugins/kxm/src/providers/antigravity/models/grouping.ts +425 -0
  28. package/plugins/kxm/src/providers/antigravity/models/index.ts +3 -0
  29. package/plugins/kxm/src/providers/antigravity/models/models.ts +501 -0
  30. package/plugins/kxm/src/providers/antigravity/pi-compat.ts +279 -0
  31. package/plugins/kxm/src/providers/antigravity/register.ts +123 -0
  32. package/plugins/kxm/src/providers/antigravity/stream/index.ts +1 -0
  33. package/plugins/kxm/src/providers/antigravity/stream/stream.ts +1461 -0
  34. package/plugins/kxm/src/providers/antigravity/types/enums.ts +50 -0
  35. package/plugins/kxm/src/providers/antigravity/types/index.ts +2 -0
  36. package/plugins/kxm/src/providers/antigravity/types/types.ts +293 -0
  37. package/plugins/kxm/src/providers/antigravity/usage/index.ts +1 -0
  38. package/plugins/kxm/src/providers/antigravity/usage/usage.ts +361 -0
  39. package/plugins/kxm/src/providers/antigravity/utils/http.ts +32 -0
  40. package/plugins/kxm/src/providers/antigravity/utils/index.ts +3 -0
  41. package/plugins/kxm/src/providers/antigravity/utils/security.ts +75 -0
  42. package/plugins/kxm/src/providers/antigravity/utils/util.ts +133 -0
  43. package/plugins/kxm/src/redact.ts +4 -0
  44. package/plugins/kxm/src/vnext-engine.ts +183 -3
  45. package/plugins/kxm/src/vnext-runtime-owner.ts +50 -0
  46. package/plugins/kxm/src/vnext-runtime-supervisor.ts +83 -58
  47. package/plugins/kxm/src/vnext-runtime.ts +9 -8
@@ -24,7 +24,8 @@ import {
24
24
  } from "./vnext-runtime.ts";
25
25
  import { createVnextOneShotProducer } from "./vnext-oneshot-producer.ts";
26
26
  import { isProducerAdmitted } from "./producers.ts";
27
- import { VnextRunScheduler, createVnextSimulatedProducer, driveVnextRun, pinVnextCompiledPlan, recoverVnextRun, startVnextRun } from "./vnext-engine.ts";
27
+ import { VnextRunScheduler, createVnextSimulatedProducer, recoverVnextRun } from "./vnext-engine.ts";
28
+ import { vnextOpenDriveSessions } from "./vnext-runtime-owner.ts";
28
29
 
29
30
  /* ------------------------------------------------------------------ *
30
31
  * Token management
@@ -280,6 +281,44 @@ function sendJson(response: ServerResponse, status: number, payload: unknown): v
280
281
  response.end(body);
281
282
  }
282
283
 
284
+ export const DEFAULT_RUNTIME_STOP_GRACE_MS = 30_000;
285
+ /** Documented upper bound: 10 minutes, well under Node's 2^31-1 ms timer range. */
286
+ export const MAX_RUNTIME_STOP_GRACE_MS = 600_000;
287
+
288
+ export function runtimeStopGraceMs(env: NodeJS.ProcessEnv = process.env): number {
289
+ const raw = env.KXM_RUNTIME_STOP_GRACE_MS;
290
+ if (raw === undefined || raw.trim() === "") return DEFAULT_RUNTIME_STOP_GRACE_MS;
291
+ const parsed = Number(raw.trim());
292
+ if (
293
+ !Number.isFinite(parsed)
294
+ || !Number.isInteger(parsed)
295
+ || parsed < 0
296
+ || parsed > MAX_RUNTIME_STOP_GRACE_MS
297
+ ) {
298
+ process.stderr.write(
299
+ `KXM_RUNTIME_STOP_GRACE_MS must be an integer between 0 and ${MAX_RUNTIME_STOP_GRACE_MS} ms (10 minutes); using default ${DEFAULT_RUNTIME_STOP_GRACE_MS}\n`,
300
+ );
301
+ return DEFAULT_RUNTIME_STOP_GRACE_MS;
302
+ }
303
+ return parsed;
304
+ }
305
+
306
+ async function waitForDriveSessions(settled: Promise<unknown>[], graceMs: number): Promise<void> {
307
+ if (settled.length === 0) return;
308
+ let timeout: ReturnType<typeof setTimeout> | undefined;
309
+ try {
310
+ await Promise.race([
311
+ Promise.allSettled(settled),
312
+ new Promise<void>((resolve) => {
313
+ timeout = setTimeout(resolve, graceMs);
314
+ timeout.unref?.();
315
+ }),
316
+ ]);
317
+ } finally {
318
+ if (timeout !== undefined) clearTimeout(timeout);
319
+ }
320
+ }
321
+
283
322
  export interface VnextRuntimeSupervisor {
284
323
  server: Server;
285
324
  port: number;
@@ -322,7 +361,6 @@ async function startVnextRuntimeSupervisorInner(
322
361
  const runtimeId = `rtm_${createHash("sha256").update(`${paths.stateRoot}\0${process.pid}\0${now()}\0${randomBytes(16).toString("hex")}`, "utf8").digest("hex").slice(0, 24)}`;
323
362
  const requestedPort = requestedPortOption ?? 0;
324
363
  let activeRuntimeId = runtimeId;
325
- const activeDrives = new Map<string, Promise<unknown>>();
326
364
 
327
365
  const contexts = new Map<string, VnextRuntimeContext>();
328
366
  const contextFor = (projectRoot: string): VnextRuntimeContext => {
@@ -420,32 +458,9 @@ async function startVnextRuntimeSupervisorInner(
420
458
  }
421
459
  const run = context.eventStore.run(runId);
422
460
  if (!run) throw runtimeError("run_unknown", runId, "run not found");
423
- if (activeDrives.has(runId)) {
424
- sendJson(response, 409, { ok: false, error: "run_busy", message: `run ${runId} is already executing` });
425
- return;
426
- }
427
- if (run.status === "completed" || run.status === "failed" || run.status === "cancelled" || run.status === "cancelling") {
428
- sendJson(response, 409, { ok: false, error: "run_busy", message: `run ${runId} is already ${run.status}` });
429
- return;
430
- }
431
- let releaseActive = () => {};
432
- const activePromise = new Promise<void>((resolve) => { releaseActive = resolve; });
433
- activeDrives.set(runId, activePromise);
434
- const cleanupActive = () => {
435
- activeDrives.delete(runId);
436
- releaseActive();
437
- };
438
-
439
- if (run.status === "created") {
440
- pinVnextCompiledPlan(context, bundle, runId);
441
- const plan = startVnextRun(context, runId, { allowLimits: true });
442
- if (plan.handoff) {
443
- cleanupActive();
444
- sendJson(response, 409, { ok: false, error: "run_handoff_required", handoff: plan.handoff });
445
- return;
446
- }
447
- }
448
- const producer = body.mode === "live"
461
+
462
+ const delayMs = typeof body.delayMs === "number" && body.delayMs > 0 ? body.delayMs : 0;
463
+ const createProducer = () => body.mode === "live"
449
464
  ? createVnextOneShotProducer({
450
465
  projectRoot,
451
466
  defaultHarness: String(bundle.project.value.defaultHarness ?? "pi"),
@@ -466,56 +481,53 @@ async function startVnextRuntimeSupervisorInner(
466
481
  return { provider, model: modelName };
467
482
  },
468
483
  })
469
- : (() => {
470
- const delayMs = typeof body.delayMs === "number" && body.delayMs > 0 ? body.delayMs : 0;
471
- return createVnextSimulatedProducer(async () => {
472
- if (delayMs > 0) {
473
- await new Promise((r) => setTimeout(r, delayMs));
474
- }
475
- return { outcome: "passed" };
476
- });
477
- })();
478
-
479
- let drivePromise: Promise<unknown> | undefined;
484
+ : createVnextSimulatedProducer(async () => {
485
+ if (delayMs > 0) {
486
+ await new Promise((r) => setTimeout(r, delayMs));
487
+ }
488
+ return { outcome: "passed" };
489
+ });
490
+
491
+ let session: Awaited<ReturnType<VnextRunScheduler["openDriveSession"]>> | undefined;
480
492
  let earlyError: unknown;
481
493
  try {
482
494
  const scheduler = VnextRunScheduler.for(context, bundle);
483
- drivePromise = scheduler.enqueue(runId, producer, { allowLimits: true, liveMode: body.mode === "live" });
484
- drivePromise.catch((err) => { earlyError = err; });
495
+ const opening = scheduler.openDriveSession(runId, {
496
+ mode: body.mode,
497
+ allowLimits: true,
498
+ liveMode: body.mode === "live",
499
+ createProducer,
500
+ });
501
+ opening.catch((err) => { earlyError = err; });
502
+ session = await opening;
485
503
  } catch (err) {
486
504
  earlyError = err;
487
505
  }
488
506
 
489
- // Yield microtask to catch synchronous duplicate queue check (e.g. run_busy)
490
- await Promise.resolve();
491
-
492
507
  if (earlyError) {
493
- cleanupActive();
494
508
  const errStr = String(earlyError);
509
+ if (errStr.includes("run_handoff_required")) {
510
+ const handoff = (earlyError as { handoff?: unknown }).handoff;
511
+ sendJson(response, 409, { ok: false, error: "run_handoff_required", ...(handoff !== undefined ? { handoff } : {}) });
512
+ return;
513
+ }
495
514
  if (errStr.includes("run_busy") || errStr.includes("scheduler_policy_conflict")) {
496
- if ("close" in producer && typeof producer.close === "function") {
497
- try { await producer.close(); } catch { /* ignore */ }
498
- }
499
515
  sendJson(response, 409, { ok: false, error: "run_busy", message: `run ${runId} is already admitted or queued` });
500
516
  return;
501
517
  }
502
518
  throw earlyError;
503
519
  }
504
520
 
505
- void drivePromise!.finally(async () => {
506
- cleanupActive();
507
- if ("close" in producer && typeof producer.close === "function") {
508
- try { await producer.close(); } catch { /* ignore */ }
509
- }
510
- }).catch(() => {
511
- // Derived finally() re-rejects when the admitted drive or cleanup
512
- // throws; void does not consume that. Keep the request 202.
521
+ void session!.settled.catch(() => {
522
+ // Post-202 drive failures stay on the session; keep the request 202.
513
523
  });
514
524
 
525
+ if (response.writableEnded) return;
515
526
  sendJson(response, 202, {
516
527
  ok: true,
517
528
  status: "accepted",
518
529
  runId,
530
+ driveId: session!.driveId,
519
531
  poll: `/v1/runs/${runId}`,
520
532
  mode: body.mode,
521
533
  });
@@ -657,9 +669,22 @@ async function startVnextRuntimeSupervisorInner(
657
669
  stopping = true;
658
670
  clearInterval(heartbeat);
659
671
  try { registry.markStopping(process.pid, now()); } catch { /* best effort */ }
660
- if (activeDrives.size > 0) {
661
- await Promise.allSettled([...activeDrives.values()]);
672
+ const openSessions = [...contexts.values()].flatMap((context) => (
673
+ vnextOpenDriveSessions(context.eventStore.path).map((session) => ({ context, session }))
674
+ ));
675
+ for (const { context, session } of openSessions) {
676
+ try {
677
+ cancelVnextRun(context, session.runId, { reason: "runtime_shutdown" });
678
+ } catch {
679
+ // Terminal or already cancelling runs still release on session settle.
680
+ }
681
+ try {
682
+ session.controller.abort();
683
+ } catch {
684
+ // Session abort is best-effort after cancel is recorded.
685
+ }
662
686
  }
687
+ await waitForDriveSessions(openSessions.map(({ session }) => session.settled), runtimeStopGraceMs());
663
688
  for (const context of contexts.values()) closeVnextRuntimeContext(context);
664
689
  contexts.clear();
665
690
  const closed = new Promise<void>((resolveStop) => server.close(() => resolveStop()));
@@ -551,7 +551,7 @@ export interface VnextRunCancelResult {
551
551
  export function cancelVnextRun(
552
552
  context: VnextRuntimeContext,
553
553
  runId: string,
554
- options: { commandId?: string; now?: string; monotonicNs?: string } = {},
554
+ options: { commandId?: string; now?: string; monotonicNs?: string; reason?: string } = {},
555
555
  ): VnextRunCancelResult {
556
556
  const commandId = options.commandId ?? newVnextCommandId();
557
557
  const now = options.now ?? new Date().toISOString();
@@ -598,16 +598,17 @@ export function cancelVnextRun(
598
598
  return event;
599
599
  };
600
600
 
601
+ const cancelReason = options.reason ?? "operator_cancel";
601
602
  const activeAttempt = Boolean(folded.currentStep?.attemptId) || vnextFoldPanelAttemptIds(folded.currentStep).length > 0;
602
603
  push("run.cancel_requested", {
603
604
  actor: { kind: "runtime", id: context.homeRuntimeId },
604
- reason: "operator_cancel",
605
+ reason: cancelReason,
605
606
  });
606
607
 
607
608
  let status: VnextRunStatus = "cancelled";
608
609
  if (folded.status === "running" && activeAttempt) {
609
610
  status = "cancelling";
610
- push("run.status_changed", { status: "cancelling", reason: "operator_cancel" });
611
+ push("run.status_changed", { status: "cancelling", reason: cancelReason });
611
612
  const attemptIds = new Set(vnextFoldPanelAttemptIds(folded.currentStep));
612
613
  if (folded.currentStep?.attemptId) attemptIds.add(folded.currentStep.attemptId);
613
614
  for (const attemptId of attemptIds) {
@@ -620,7 +621,7 @@ export function cancelVnextRun(
620
621
  abortControllers.push(owned.controller);
621
622
  }
622
623
  } else if (folded.status === "blocked_uncertain") {
623
- push("run.status_changed", { status: "cancelling", reason: "operator_cancel" });
624
+ push("run.status_changed", { status: "cancelling", reason: cancelReason });
624
625
  const attemptId = folded.currentStep?.attemptId;
625
626
  if (attemptId) {
626
627
  const capability = context.eventStore.capabilityByAttempt(attemptId);
@@ -632,13 +633,13 @@ export function cancelVnextRun(
632
633
  for (const owned of vnextAttemptControllers(context.eventStore.path, runId)) {
633
634
  abortControllers.push(owned.controller);
634
635
  }
635
- push("run.status_changed", { status: "cancelled", reason: "operator_cancel" });
636
+ push("run.status_changed", { status: "cancelled", reason: cancelReason });
636
637
  status = "cancelled";
637
638
  } else if (folded.status === "running" && !activeAttempt) {
638
- push("run.status_changed", { status: "cancelling", reason: "operator_cancel" });
639
- push("run.status_changed", { status: "cancelled", reason: "operator_cancel" });
639
+ push("run.status_changed", { status: "cancelling", reason: cancelReason });
640
+ push("run.status_changed", { status: "cancelled", reason: cancelReason });
640
641
  } else {
641
- push("run.status_changed", { status: "cancelled", reason: "operator_cancel" });
642
+ push("run.status_changed", { status: "cancelled", reason: cancelReason });
642
643
  }
643
644
 
644
645
  for (const event of events) context.eventStore.appendEvent(event);