@kontextmind/kxm 0.7.0 → 0.7.10

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 (94) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/roles/writer.yaml +2 -0
  3. package/docs/README.md +3 -0
  4. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
  5. package/docs/agent-skills.md +19 -2
  6. package/docs/browser-automation.md +116 -0
  7. package/docs/configuration.md +10 -1
  8. package/docs/getting-started.md +21 -0
  9. package/docs/kb/how-credentials-retrieved-safely.md +31 -0
  10. package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
  11. package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
  12. package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
  13. package/docs/kb/how-to-resume-after-mfa.md +28 -0
  14. package/docs/kb/how-to-take-over-session.md +32 -0
  15. package/docs/kb/why-authentication-disappeared.md +32 -0
  16. package/docs/kb/why-automation-opened-different-browser.md +32 -0
  17. package/docs/kb/why-session-viewer-cannot-control.md +31 -0
  18. package/docs/operations.md +24 -0
  19. package/docs/prompts/browser-annotate-feedback.md +41 -0
  20. package/docs/prompts/browser-diagnose-recover.md +38 -0
  21. package/docs/prompts/browser-explore.md +42 -0
  22. package/docs/prompts/browser-repro-fix.md +48 -0
  23. package/docs/prompts/browser-start.md +41 -0
  24. package/docs/prompts/browser-takeover.md +50 -0
  25. package/docs/skills/repo-work-delivery.md +5 -0
  26. package/docs/skills.md +2 -0
  27. package/docs/troubleshooting.md +22 -1
  28. package/package.json +1 -1
  29. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  30. package/plugins/kxm/dist/cli.js +41203 -38364
  31. package/plugins/kxm/dist/core.js +57 -0
  32. package/plugins/kxm/dist/extension.js +40 -3
  33. package/plugins/kxm/dist/mcp-server.js +1 -1
  34. package/plugins/kxm/dist/runtime.js +1582 -81
  35. package/plugins/kxm/dist/server.js +129 -4
  36. package/plugins/kxm/dist/vnext-runtime-supervisor.js +221 -41
  37. package/plugins/kxm/package.json +1 -1
  38. package/plugins/kxm/skills/hints.json +30 -0
  39. package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
  40. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
  41. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
  42. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
  43. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
  44. package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
  45. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
  46. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +9 -0
  47. package/plugins/kxm/skills/kxm-project-setup/SKILL.md +9 -2
  48. package/plugins/kxm/src/autocomplete.ts +1 -1
  49. package/plugins/kxm/src/browser.ts +603 -0
  50. package/plugins/kxm/src/cli/context-skills.ts +373 -0
  51. package/plugins/kxm/src/cli/hub.ts +614 -0
  52. package/plugins/kxm/src/cli/roles.ts +615 -0
  53. package/plugins/kxm/src/cli/system.ts +906 -0
  54. package/plugins/kxm/src/cli/tasks.ts +364 -0
  55. package/plugins/kxm/src/cli/types.ts +270 -0
  56. package/plugins/kxm/src/cli/vnext.ts +698 -0
  57. package/plugins/kxm/src/cli/workflows.ts +699 -0
  58. package/plugins/kxm/src/cli.ts +238 -3791
  59. package/plugins/kxm/src/completion-install.ts +223 -0
  60. package/plugins/kxm/src/database.ts +1 -1
  61. package/plugins/kxm/src/external-effects.ts +1 -1
  62. package/plugins/kxm/src/hub-env.ts +193 -0
  63. package/plugins/kxm/src/init-guide-setup.ts +547 -0
  64. package/plugins/kxm/src/local-snapshot.ts +1 -1
  65. package/plugins/kxm/src/mcp-server.ts +1 -1
  66. package/plugins/kxm/src/model-inventory.ts +8 -8
  67. package/plugins/kxm/src/modes.ts +348 -0
  68. package/plugins/kxm/src/protocol.ts +111 -0
  69. package/plugins/kxm/src/role.ts +335 -0
  70. package/plugins/kxm/src/runtime.ts +4 -0
  71. package/plugins/kxm/src/safety-integrity.ts +76 -0
  72. package/plugins/kxm/src/sqlite.ts +76 -0
  73. package/plugins/kxm/src/ssh-remote.ts +560 -0
  74. package/plugins/kxm/src/store.ts +1 -1
  75. package/plugins/kxm/src/subagent-control.ts +312 -0
  76. package/plugins/kxm/src/vnext-bindings.ts +1 -1
  77. package/plugins/kxm/src/vnext-config.ts +38 -1
  78. package/plugins/kxm/src/vnext-engine-command.ts +2 -0
  79. package/plugins/kxm/src/vnext-engine.ts +16 -0
  80. package/plugins/kxm/src/vnext-harness.ts +92 -22
  81. package/plugins/kxm/src/vnext-oneshot-evidence.ts +39 -7
  82. package/plugins/kxm/src/vnext-oneshot-process.ts +46 -8
  83. package/plugins/kxm/src/vnext-oneshot-producer.ts +22 -1
  84. package/plugins/kxm/src/vnext-pi-producer.ts +11 -7
  85. package/plugins/kxm/src/vnext-runtime-store.ts +1 -1
  86. package/plugins/kxm/src/vnext-runtime-supervisor.ts +5 -3
  87. package/plugins/kxm/src/workflow-tui.ts +1 -1
  88. package/plugins/kxm/src/workflow.ts +144 -0
  89. package/schemas/vnext/modes.schema.json +56 -0
  90. package/scripts/kxm-bump-version.mjs +146 -0
  91. package/scripts/kxm-hub.mjs +145 -3
  92. package/scripts/kxm-publish-npm.mjs +3 -1
  93. package/scripts/kxm-release-github.mjs +3 -1
  94. package/scripts/kxm.mjs +0 -0
@@ -7431,6 +7431,61 @@ function parseBoundedInteger(value, field, fallback, min, max) {
7431
7431
  }
7432
7432
  return value;
7433
7433
  }
7434
+ var TERMINAL_RECEIPT_SCHEMA = "kxm.terminal-receipt.v1";
7435
+ var VALID_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["accepted", "audit_escalation", "rejected", "error"]);
7436
+ function validateTerminalReceipt(value) {
7437
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
7438
+ throw new ProtocolError(400, "terminal receipt must be an object", "invalid_terminal_receipt");
7439
+ }
7440
+ const record = value;
7441
+ if (record.schema !== void 0 && record.schema !== TERMINAL_RECEIPT_SCHEMA) {
7442
+ throw new ProtocolError(400, `terminal receipt schema must be ${TERMINAL_RECEIPT_SCHEMA}`, "invalid_terminal_receipt");
7443
+ }
7444
+ const status = record.status;
7445
+ if (!status || !VALID_TERMINAL_STATUSES.has(status)) {
7446
+ throw new ProtocolError(
7447
+ 400,
7448
+ `terminal receipt status must be one of: ${Array.from(VALID_TERMINAL_STATUSES).join(", ")}`,
7449
+ "invalid_terminal_receipt"
7450
+ );
7451
+ }
7452
+ const seat = requireString(record.seat, "seat", { max: 64 });
7453
+ const runId = requireString(record.runId, "runId", { max: 128 });
7454
+ const stageId = requireString(record.stageId, "stageId", { max: 128 });
7455
+ const timestamp = requireString(record.timestamp, "timestamp", { max: 64 });
7456
+ const host2 = requireString(record.host, "host", { max: 64 });
7457
+ const model = requireString(record.model, "model", { max: 128 });
7458
+ let evidence;
7459
+ if (record.evidence !== void 0) {
7460
+ if (!record.evidence || typeof record.evidence !== "object" || Array.isArray(record.evidence)) {
7461
+ throw new ProtocolError(400, "terminal receipt evidence must be an object", "invalid_terminal_receipt");
7462
+ }
7463
+ evidence = record.evidence;
7464
+ }
7465
+ let metrics;
7466
+ if (record.metrics !== void 0) {
7467
+ if (!record.metrics || typeof record.metrics !== "object" || Array.isArray(record.metrics)) {
7468
+ throw new ProtocolError(400, "terminal receipt metrics must be an object", "invalid_terminal_receipt");
7469
+ }
7470
+ metrics = record.metrics;
7471
+ }
7472
+ const escalationReason = optionalString(record.escalationReason, "escalationReason", 1024);
7473
+ const ruling = optionalString(record.ruling, "ruling", 2048);
7474
+ return {
7475
+ schema: TERMINAL_RECEIPT_SCHEMA,
7476
+ status,
7477
+ seat,
7478
+ runId,
7479
+ stageId,
7480
+ timestamp,
7481
+ host: host2,
7482
+ model,
7483
+ ...evidence ? { evidence } : {},
7484
+ ...metrics ? { metrics } : {},
7485
+ ...escalationReason ? { escalationReason } : {},
7486
+ ...ruling ? { ruling } : {}
7487
+ };
7488
+ }
7434
7489
 
7435
7490
  // plugins/kxm/src/diagnostics.ts
7436
7491
  function workflowScopeExtras(operation, assignedCoordinatorName) {
@@ -7982,12 +8037,17 @@ function parseWorkflowDefinitions(raw, environment = process.env, onWarning) {
7982
8037
  if (stageMaxTransitions !== void 0 && (!Number.isInteger(stageMaxTransitions) || stageMaxTransitions < 1 || stageMaxTransitions > 100)) {
7983
8038
  throw new Error(`stage ${stageId} maxTransitions must be an integer between 1 and 100`);
7984
8039
  }
8040
+ const autoResumeLimit = stage.autoResumeLimit;
8041
+ if (autoResumeLimit !== void 0 && (!Number.isInteger(autoResumeLimit) || autoResumeLimit < 1 || autoResumeLimit > 20)) {
8042
+ throw new Error(`stage ${stageId} autoResumeLimit must be an integer between 1 and 20`);
8043
+ }
7985
8044
  return {
7986
8045
  id: stageId,
7987
8046
  label: requireString(stage.label ?? stageId, "stage.label", { max: 128 }),
7988
8047
  instructions: requireString(stage.instructions, "stage.instructions", { max: 4e3 }),
7989
8048
  requiredEvidence,
7990
8049
  maxAttempts,
8050
+ ...autoResumeLimit !== void 0 ? { autoResumeLimit } : {},
7991
8051
  ...area ? { area } : {},
7992
8052
  ...evidencePolicies ? { evidencePolicies } : {},
7993
8053
  ...on ? { on } : {},
@@ -8266,6 +8326,31 @@ function checkpointRun(run, stageId, status, summary, evidence, timestamp, verif
8266
8326
  delete run.currentStage;
8267
8327
  return { retry: false, completed: false, run };
8268
8328
  }
8329
+ if (stage.autoResumeLimit !== void 0 && stage.attempts >= stage.autoResumeLimit) {
8330
+ stage.status = "in_progress";
8331
+ const reason = summary || `autoResumeLimit of ${stage.autoResumeLimit} reached on stage ${stage.id}`;
8332
+ const receipt = validateTerminalReceipt({
8333
+ schema: TERMINAL_RECEIPT_SCHEMA,
8334
+ status: "audit_escalation",
8335
+ seat: stage.id,
8336
+ runId: run.id,
8337
+ stageId: stage.id,
8338
+ timestamp,
8339
+ host: "pi",
8340
+ model: "default",
8341
+ escalationReason: reason
8342
+ });
8343
+ const expiresAt = new Date(Date.parse(timestamp) + 24 * 60 * 60 * 1e3).toISOString();
8344
+ waitForWorkflowSignal(run, stage.id, "audit_escalation", reason, timestamp, expiresAt);
8345
+ stage.receipt = receipt;
8346
+ stage.auditEscalation = {
8347
+ reason,
8348
+ timestamp,
8349
+ receipt
8350
+ };
8351
+ return { retry: false, completed: false, run };
8352
+ }
8353
+ stage.status = status;
8269
8354
  const outcomeKey = outcome ?? status;
8270
8355
  const rule = resolveOutcomeRule(stage, outcomeKey);
8271
8356
  if (rule && stage.attempts < stage.maxAttempts) {
@@ -10833,7 +10918,45 @@ import {
10833
10918
  writeFileSync as writeFileSync4
10834
10919
  } from "node:fs";
10835
10920
  import { basename as basename3, dirname as dirname3, join as join4, resolve as resolve4 } from "node:path";
10836
- import { DatabaseSync } from "node:sqlite";
10921
+
10922
+ // plugins/kxm/src/sqlite.ts
10923
+ import { createRequire } from "node:module";
10924
+ var requireFromHere = createRequire(import.meta.url);
10925
+ function loadNative() {
10926
+ try {
10927
+ const mod = requireFromHere("node:sqlite");
10928
+ if (mod.DatabaseSync) return { Ctor: mod.DatabaseSync, bun: false };
10929
+ } catch {
10930
+ }
10931
+ try {
10932
+ const mod = requireFromHere("bun:sqlite");
10933
+ const Ctor = mod?.DatabaseSync ?? mod?.Database;
10934
+ if (Ctor) return { Ctor, bun: true };
10935
+ } catch {
10936
+ }
10937
+ throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
10938
+ }
10939
+ var native = loadNative();
10940
+ var DatabaseSync = class {
10941
+ inner;
10942
+ constructor(path, options) {
10943
+ let normalized = options;
10944
+ if (native.bun && options) {
10945
+ const { readOnly, ...rest } = options;
10946
+ normalized = readOnly === void 0 ? rest : { ...rest, readonly: readOnly };
10947
+ }
10948
+ this.inner = normalized === void 0 ? new native.Ctor(path) : new native.Ctor(path, normalized);
10949
+ }
10950
+ prepare(sql) {
10951
+ return this.inner.prepare(sql);
10952
+ }
10953
+ exec(sql) {
10954
+ return this.inner.exec(sql);
10955
+ }
10956
+ close() {
10957
+ this.inner.close();
10958
+ }
10959
+ };
10837
10960
 
10838
10961
  // plugins/kxm/src/vnext-config.ts
10839
10962
  import { basename as basename2, dirname as dirname2, extname as extname2, isAbsolute, join as join3, relative, resolve as resolve3, sep } from "node:path";
@@ -11090,7 +11213,9 @@ function parseAgyOneShotUsage(stdout, _stderr) {
11090
11213
  var READ_ONLY_ONESHOT_ARGS = Object.freeze({
11091
11214
  claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
11092
11215
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
11093
- grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"])
11216
+ grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
11217
+ agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]),
11218
+ kimi: Object.freeze(["--plan"])
11094
11219
  });
11095
11220
  function oneShotReadOnlyArgs(harness) {
11096
11221
  return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness] : void 0;
@@ -11144,7 +11269,7 @@ var BUILTIN_HARNESSES = Object.freeze([
11144
11269
  authArgs: ["provider", "list"],
11145
11270
  update: { self: ["upgrade"] },
11146
11271
  oneShot: {
11147
- argv: ["--output-format", "stream-json", "-p"],
11272
+ argv: [...oneShotReadOnlyArgs("kimi"), "--output-format", "stream-json", "-p"],
11148
11273
  promptVia: "arg",
11149
11274
  outputFormat: "stream-json",
11150
11275
  usageParser: parseKimiOneShotUsage
@@ -11207,7 +11332,7 @@ var BUILTIN_HARNESSES = Object.freeze([
11207
11332
  authArgs: ["models"],
11208
11333
  update: { self: ["update"] },
11209
11334
  oneShot: {
11210
- argv: ["--output-format", "json", "-p"],
11335
+ argv: [...oneShotReadOnlyArgs("agy"), "--output-format", "json", "-p"],
11211
11336
  promptVia: "arg",
11212
11337
  outputFormat: "json",
11213
11338
  usageParser: parseAgyOneShotUsage
@@ -15126,6 +15126,19 @@ var OUTPUT_LIMIT = 8 * 1024 * 1024;
15126
15126
  var KILL_GRACE_MS = 250;
15127
15127
  var DRAIN_GRACE_MS = 500;
15128
15128
  var REAP_GRACE_MS = 1e3;
15129
+ function killProcessTree(child, signal = "SIGKILL") {
15130
+ if (process.platform !== "win32" && child.pid) {
15131
+ try {
15132
+ process.kill(-child.pid, signal);
15133
+ return;
15134
+ } catch {
15135
+ }
15136
+ }
15137
+ try {
15138
+ child.kill(signal);
15139
+ } catch {
15140
+ }
15141
+ }
15129
15142
  function defaultSpawn(command, args, options) {
15130
15143
  if (options.signal?.aborted) {
15131
15144
  return Promise.resolve({ stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") });
@@ -15150,6 +15163,7 @@ function defaultSpawn(command, args, options) {
15150
15163
  let child;
15151
15164
  const finish = () => {
15152
15165
  if (finished) return;
15166
+ if (stopping && !killSent) return;
15153
15167
  finished = true;
15154
15168
  for (const timer of [wallTimer, killTimer, drainTimer, reapTimer]) clearTimeout(timer);
15155
15169
  options.signal?.removeEventListener("abort", onAbort);
@@ -15159,14 +15173,21 @@ function defaultSpawn(command, args, options) {
15159
15173
  child?.unref();
15160
15174
  const started = Boolean(child?.pid);
15161
15175
  if (started && !observedChildExit) error ??= new Error("process_exit_unobserved");
15162
- resolve7({ stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8"), code, signal, started, observedChildExit, terminationRequested: stopping, ...error ? { error } : {} });
15176
+ const unverifiedDescendants = stopping || started && !observedChildExit;
15177
+ resolve7({
15178
+ stdout: Buffer.concat(stdout).toString("utf8"),
15179
+ stderr: Buffer.concat(stderr).toString("utf8"),
15180
+ code,
15181
+ signal,
15182
+ started,
15183
+ observedChildExit,
15184
+ terminationRequested: stopping,
15185
+ ...unverifiedDescendants ? { unverifiedDescendants: true } : {},
15186
+ ...error ? { error } : {}
15187
+ });
15163
15188
  };
15164
15189
  const kill = (requested) => {
15165
- try {
15166
- if (process.platform !== "win32" && child.pid) process.kill(-child.pid, requested);
15167
- else child.kill(requested);
15168
- } catch {
15169
- }
15190
+ killProcessTree(child, requested);
15170
15191
  };
15171
15192
  const stop = (reason) => {
15172
15193
  if (finished) return;
@@ -15194,7 +15215,7 @@ function defaultSpawn(command, args, options) {
15194
15215
  cwd: options.cwd,
15195
15216
  env: options.env ?? process.env,
15196
15217
  stdio: ["pipe", "pipe", "pipe"],
15197
- shell: false,
15218
+ shell: options.shell === true,
15198
15219
  windowsHide: true,
15199
15220
  detached: process.platform !== "win32"
15200
15221
  });
@@ -15238,7 +15259,7 @@ function defaultSpawn(command, args, options) {
15238
15259
  closed = true;
15239
15260
  code = exitCode;
15240
15261
  signal = exitSignal;
15241
- if (!stopping || killSent) finish();
15262
+ finish();
15242
15263
  });
15243
15264
  options.signal?.addEventListener("abort", onAbort, { once: true });
15244
15265
  if (options.signal?.aborted) onAbort();
@@ -15493,7 +15514,9 @@ function parseAgyOneShotUsage(stdout, _stderr) {
15493
15514
  var READ_ONLY_ONESHOT_ARGS = Object.freeze({
15494
15515
  claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
15495
15516
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
15496
- grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"])
15517
+ grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
15518
+ agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]),
15519
+ kimi: Object.freeze(["--plan"])
15497
15520
  });
15498
15521
  function oneShotReadOnlyArgs(harness) {
15499
15522
  return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness] : void 0;
@@ -15547,7 +15570,7 @@ var BUILTIN_HARNESSES = Object.freeze([
15547
15570
  authArgs: ["provider", "list"],
15548
15571
  update: { self: ["upgrade"] },
15549
15572
  oneShot: {
15550
- argv: ["--output-format", "stream-json", "-p"],
15573
+ argv: [...oneShotReadOnlyArgs("kimi"), "--output-format", "stream-json", "-p"],
15551
15574
  promptVia: "arg",
15552
15575
  outputFormat: "stream-json",
15553
15576
  usageParser: parseKimiOneShotUsage
@@ -15610,7 +15633,7 @@ var BUILTIN_HARNESSES = Object.freeze([
15610
15633
  authArgs: ["models"],
15611
15634
  update: { self: ["update"] },
15612
15635
  oneShot: {
15613
- argv: ["--output-format", "json", "-p"],
15636
+ argv: [...oneShotReadOnlyArgs("agy"), "--output-format", "json", "-p"],
15614
15637
  promptVia: "arg",
15615
15638
  outputFormat: "json",
15616
15639
  usageParser: parseAgyOneShotUsage
@@ -15677,6 +15700,31 @@ function defaultRunner(env) {
15677
15700
  }
15678
15701
  };
15679
15702
  }
15703
+ function commandResultFromSpawn(result) {
15704
+ const errno = result.error && "code" in result.error ? result.error.code : void 0;
15705
+ const error = (typeof errno === "string" && errno ? errno : void 0) ?? result.error?.message ?? (result.observedChildExit !== true ? "auth_probe_exit_unobserved" : void 0);
15706
+ return {
15707
+ ok: result.code === 0 && !error,
15708
+ code: result.code,
15709
+ stdout: result.stdout,
15710
+ stderr: result.stderr,
15711
+ ...error ? { error } : {}
15712
+ };
15713
+ }
15714
+ function defaultAsyncRunner(env, signal) {
15715
+ return async (command, args, timeoutMs) => {
15716
+ try {
15717
+ return commandResultFromSpawn(await defaultSpawn(command, args, {
15718
+ env,
15719
+ timeoutMs,
15720
+ signal,
15721
+ shell: harnessSpawnUsesShell(command)
15722
+ }));
15723
+ } catch (error) {
15724
+ return { ok: false, code: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : "spawn_failed" };
15725
+ }
15726
+ };
15727
+ }
15680
15728
  function firstLine(text) {
15681
15729
  const line = text.split(/\r?\n/).map((candidate) => candidate.trim()).find(Boolean);
15682
15730
  return line && line.length <= 200 ? line : void 0;
@@ -16092,35 +16140,31 @@ var PendingHarnessCommand = class {
16092
16140
  this.key = key;
16093
16141
  }
16094
16142
  };
16095
- async function probeHarnessAssignmentAsync(options) {
16143
+ async function replayHarnessProbe(run, execute, limit) {
16096
16144
  const observed = /* @__PURE__ */ new Map();
16097
- const run = options.runCommand ?? (async (command, args, timeoutMs) => {
16098
- const result = await defaultSpawn(command, args, { env: options.env, timeoutMs, signal: options.signal });
16099
- const error = result.error?.message ?? (result.observedChildExit !== true ? "auth_probe_exit_unobserved" : void 0);
16100
- return {
16101
- ok: result.code === 0 && !error,
16102
- code: result.code,
16103
- stdout: result.stdout,
16104
- stderr: result.stderr,
16105
- ...error ? { error } : {}
16106
- };
16107
- });
16108
- for (let commands = 0; commands <= 32; commands++) {
16145
+ for (let commands = 0; commands <= limit; commands++) {
16109
16146
  try {
16110
- return probeHarnessAssignment({ ...options, runCommand(command, args, timeoutMs) {
16147
+ return execute((command, args, timeoutMs) => {
16111
16148
  const key = JSON.stringify([command, args, timeoutMs]);
16112
16149
  const result = observed.get(key);
16113
16150
  if (result) return result;
16114
16151
  throw new PendingHarnessCommand(command, args, timeoutMs, key);
16115
- } });
16152
+ });
16116
16153
  } catch (pending) {
16117
16154
  if (!(pending instanceof PendingHarnessCommand)) throw pending;
16118
- if (commands === 32) throw new Error("auth_probe_command_limit");
16155
+ if (commands === limit) throw new Error("auth_probe_command_limit");
16119
16156
  observed.set(pending.key, await run(pending.command, pending.args, pending.timeoutMs));
16120
16157
  }
16121
16158
  }
16122
16159
  throw new Error("auth_probe_command_limit");
16123
16160
  }
16161
+ async function probeHarnessAssignmentAsync(options) {
16162
+ return replayHarnessProbe(
16163
+ options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal),
16164
+ (runCommand) => probeHarnessAssignment({ ...options, runCommand }),
16165
+ 32
16166
+ );
16167
+ }
16124
16168
 
16125
16169
  // plugins/kxm/src/vnext-config.ts
16126
16170
  var VnextConfigError = class extends Error {
@@ -16905,7 +16949,7 @@ function validateModelReferences(models, issues) {
16905
16949
  };
16906
16950
  for (const id of models.keys()) walk(id, []);
16907
16951
  }
16908
- function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry) {
16952
+ function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot) {
16909
16953
  const issues = [];
16910
16954
  const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
16911
16955
  const repositoryIds = /* @__PURE__ */ new Set();
@@ -16973,6 +17017,33 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
16973
17017
  const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
16974
17018
  if (!workflows.has(defaultWorkflow)) issues.push(issue2("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
16975
17019
  for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
17020
+ if (projectRoot) {
17021
+ const writerRolePath = join(projectRoot, ".kxm", "roles", "writer.yaml");
17022
+ const implementerAgent = agents.get("implementer") ?? agents.get("writer");
17023
+ if (existsSync2(writerRolePath) && implementerAgent) {
17024
+ try {
17025
+ const rawRole = parseRestrictedYaml2(readFileSync(writerRolePath, "utf8"));
17026
+ const roleObj = objectValue(rawRole);
17027
+ const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
17028
+ const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m) => Boolean(m));
17029
+ const agentModelObj = objectValue(implementerAgent.value.model);
17030
+ const agentModelStr = stringValue(implementerAgent.value.model);
17031
+ const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
17032
+ const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
17033
+ const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
17034
+ if (enabledRosterModels.length > 0 && canonicalAgentModel) {
17035
+ const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
17036
+ if (!matches) {
17037
+ issues.push(issue2("semantic", "role_roster_conflicts_with_agent", ".kxm/roles/writer.yaml", `role roster in .kxm/roles/writer.yaml does not include agent model ${canonicalAgentModel} from ${implementerAgent.logicalPath}`));
17038
+ }
17039
+ }
17040
+ } catch (error) {
17041
+ if (error instanceof VnextConfigError) {
17042
+ issues.push(...error.issues);
17043
+ }
17044
+ }
17045
+ }
17046
+ }
16976
17047
  return sortIssues2(issues);
16977
17048
  }
16978
17049
  function vnextCanonicalJson(value) {
@@ -17153,7 +17224,7 @@ function loadVnextProject(projectRoot, options = {}) {
17153
17224
  }
17154
17225
  }
17155
17226
  if (loadIssues.length > 0) throw new VnextConfigError(loadIssues);
17156
- const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry);
17227
+ const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
17157
17228
  if (issues.length > 0) throw new VnextConfigError(issues);
17158
17229
  const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
17159
17230
  return {
@@ -17265,7 +17336,47 @@ import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:pa
17265
17336
 
17266
17337
  // plugins/kxm/src/vnext-bindings.ts
17267
17338
  import { homedir } from "node:os";
17268
- import { DatabaseSync } from "node:sqlite";
17339
+
17340
+ // plugins/kxm/src/sqlite.ts
17341
+ import { createRequire } from "node:module";
17342
+ var requireFromHere = createRequire(import.meta.url);
17343
+ function loadNative() {
17344
+ try {
17345
+ const mod = requireFromHere("node:sqlite");
17346
+ if (mod.DatabaseSync) return { Ctor: mod.DatabaseSync, bun: false };
17347
+ } catch {
17348
+ }
17349
+ try {
17350
+ const mod = requireFromHere("bun:sqlite");
17351
+ const Ctor = mod?.DatabaseSync ?? mod?.Database;
17352
+ if (Ctor) return { Ctor, bun: true };
17353
+ } catch {
17354
+ }
17355
+ throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
17356
+ }
17357
+ var native = loadNative();
17358
+ var DatabaseSync = class {
17359
+ inner;
17360
+ constructor(path, options) {
17361
+ let normalized = options;
17362
+ if (native.bun && options) {
17363
+ const { readOnly, ...rest } = options;
17364
+ normalized = readOnly === void 0 ? rest : { ...rest, readonly: readOnly };
17365
+ }
17366
+ this.inner = normalized === void 0 ? new native.Ctor(path) : new native.Ctor(path, normalized);
17367
+ }
17368
+ prepare(sql) {
17369
+ return this.inner.prepare(sql);
17370
+ }
17371
+ exec(sql) {
17372
+ return this.inner.exec(sql);
17373
+ }
17374
+ close() {
17375
+ this.inner.close();
17376
+ }
17377
+ };
17378
+
17379
+ // plugins/kxm/src/vnext-bindings.ts
17269
17380
  import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, parse, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
17270
17381
  var MAX_BINDING_RECORD_BYTES = 256 * 1024;
17271
17382
  var BINDING_LABEL = "Runtime-local repository bindings";
@@ -17308,7 +17419,6 @@ import {
17308
17419
  writeFileSync
17309
17420
  } from "node:fs";
17310
17421
  import { basename as basename2, dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
17311
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
17312
17422
  function databaseError(code, file, message) {
17313
17423
  const issue3 = { phase: "semantic", code, file, message };
17314
17424
  return new VnextConfigError([issue3]);
@@ -17388,7 +17498,7 @@ function openDatabase(file, description, spec) {
17388
17498
  }
17389
17499
  }
17390
17500
  }
17391
- const database = new DatabaseSync2(file);
17501
+ const database = new DatabaseSync(file);
17392
17502
  let transaction = false;
17393
17503
  try {
17394
17504
  database.exec(`PRAGMA busy_timeout = ${spec.timeoutMs ?? 5e3}`);
@@ -21827,9 +21937,28 @@ import { join as join11 } from "node:path";
21827
21937
  import { createHash as createHash6, randomUUID as randomUUID3 } from "node:crypto";
21828
21938
  import { lstat, mkdir, open, rename } from "node:fs/promises";
21829
21939
  import { join as join6 } from "node:path";
21940
+ var ONESHOT_EVIDENCE_SCHEMA = "kxm.oneshot-evidence.v2";
21941
+ var RETIRED_ONESHOT_EVIDENCE_SCHEMAS = /* @__PURE__ */ new Set(["kxm.oneshot-evidence.v1"]);
21830
21942
  var TEXT_LIMIT = 4 * 1024 * 1024;
21943
+ var ARGV_LIMIT = 64 * 1024;
21831
21944
  var RECORD_LIMIT = 16 * 1024 * 1024;
21832
21945
  var digest = (value) => `sha256:${createHash6("sha256").update(value).digest("hex")}`;
21946
+ function parseOneShotEvidenceRecord(bytes) {
21947
+ let parsed;
21948
+ try {
21949
+ parsed = JSON.parse(bytes);
21950
+ } catch {
21951
+ throw new Error("oneshot_evidence_record_invalid");
21952
+ }
21953
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
21954
+ throw new Error("oneshot_evidence_record_invalid");
21955
+ }
21956
+ const schema = parsed.schema;
21957
+ if (typeof schema !== "string") throw new Error("oneshot_evidence_record_invalid");
21958
+ if (RETIRED_ONESHOT_EVIDENCE_SCHEMAS.has(schema)) throw new Error("oneshot_evidence_schema_retired");
21959
+ if (schema !== ONESHOT_EVIDENCE_SCHEMA) throw new Error("oneshot_evidence_schema_unknown");
21960
+ return parsed;
21961
+ }
21833
21962
  async function beginOneShotEvidence(root, intent, sensitive) {
21834
21963
  await mkdir(root, { recursive: true, mode: 448 });
21835
21964
  const stat = await lstat(root);
@@ -21843,11 +21972,11 @@ async function beginOneShotEvidence(root, intent, sensitive) {
21843
21972
  const redact = (value) => {
21844
21973
  let text = value;
21845
21974
  for (const secret of secrets) text = text.replaceAll(secret, "[REDACTED]");
21846
- return text.replace(/(\b(?:authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\b["']?\s*[:=]\s*["']?)(?:Bearer\s+)?[^\s"',;}]+/gi, "$1[REDACTED]");
21975
+ return text.replace(/(\b(?:authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\b["']?\s*[:=]\s*["']?)(?:Bearer\s+)?[^\s"',;}]+/gi, "$1[REDACTED]").replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]");
21847
21976
  };
21848
- const bounded = (value) => {
21977
+ const bounded = (value, max = TEXT_LIMIT) => {
21849
21978
  const bytes = Buffer.from(redact(value));
21850
- return { text: bytes.subarray(0, TEXT_LIMIT).toString("utf8"), truncated: bytes.length > TEXT_LIMIT, sha256: digest(value) };
21979
+ return { text: bytes.subarray(0, max).toString("utf8"), truncated: bytes.length > max, sha256: digest(redact(value)) };
21851
21980
  };
21852
21981
  const write = async (name, value) => {
21853
21982
  const current = await lstat(dir);
@@ -21856,6 +21985,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
21856
21985
  }
21857
21986
  const bytes = JSON.stringify(value);
21858
21987
  if (Buffer.byteLength(bytes) > RECORD_LIMIT) throw new Error("oneshot_evidence_record_limit");
21988
+ parseOneShotEvidenceRecord(bytes);
21859
21989
  const target = join6(dir, name);
21860
21990
  const partial = `${target}.partial`;
21861
21991
  const file = await open(partial, "wx", 384);
@@ -21877,7 +22007,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
21877
22007
  return digest(bytes);
21878
22008
  };
21879
22009
  await write("intent.json", {
21880
- schema: "kxm.oneshot-evidence.v1",
22010
+ schema: ONESHOT_EVIDENCE_SCHEMA,
21881
22011
  id,
21882
22012
  recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
21883
22013
  runId: intent.runId,
@@ -21888,9 +22018,10 @@ async function beginOneShotEvidence(root, intent, sensitive) {
21888
22018
  provider: intent.provider,
21889
22019
  requestedModel: intent.model,
21890
22020
  cwd: intent.cwd,
21891
- command: intent.command,
21892
- argv: bounded(JSON.stringify(intent.args)),
22021
+ command: redact(intent.command),
22022
+ argv: bounded(JSON.stringify(intent.args), ARGV_LIMIT),
21893
22023
  stdin: intent.input === void 0 ? null : bounded(intent.input),
22024
+ env: null,
21894
22025
  acceptance: false
21895
22026
  });
21896
22027
  let finished = false;
@@ -21900,7 +22031,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
21900
22031
  if (finished) throw new Error("oneshot_evidence_already_finished");
21901
22032
  finished = true;
21902
22033
  return write("result.json", {
21903
- schema: "kxm.oneshot-evidence.v1",
22034
+ schema: ONESHOT_EVIDENCE_SCHEMA,
21904
22035
  id,
21905
22036
  recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
21906
22037
  stdout: bounded(result.stdout),
@@ -21910,6 +22041,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
21910
22041
  started: result.started ?? null,
21911
22042
  observedChildExit: result.observedChildExit ?? null,
21912
22043
  terminationRequested: result.terminationRequested ?? null,
22044
+ unverifiedDescendants: result.unverifiedDescendants ?? null,
21913
22045
  error: result.error ? bounded(result.error.message) : null,
21914
22046
  observation: bounded(JSON.stringify(observation)),
21915
22047
  acceptance: false
@@ -22465,6 +22597,32 @@ function evaluateArtifactsGate(context, definition) {
22465
22597
  // plugins/kxm/src/vnext-engine-command.ts
22466
22598
  import { spawn as spawn2 } from "node:child_process";
22467
22599
  import { createHash as createHash8 } from "node:crypto";
22600
+
22601
+ // plugins/kxm/src/safety-integrity.ts
22602
+ var DESTRUCTIVE_COMMAND_PATTERNS = Object.freeze([
22603
+ /^\s*rm\s+.*-[a-zA-Z]*r[a-zA-Z]*f/i,
22604
+ /^\s*rm\s+.*-[a-zA-Z]*f[a-zA-Z]*r/i,
22605
+ /^\s*rm\s+.*(-[a-zA-Z]*r[a-zA-Z]*\s+.*-[a-zA-Z]*f[a-zA-Z]*|-[a-zA-Z]*f[a-zA-Z]*\s+.*-[a-zA-Z]*r[a-zA-Z]*)/i,
22606
+ /^\s*git\s+reset\s+--hard/i,
22607
+ /^\s*git\s+clean\s+-[a-zA-Z]*f/i,
22608
+ /^\s*git\s+checkout\s+--\s+/i,
22609
+ /^\s*git\s+restore\s+(\.|\*|--staged\s+(\.|\*))/i
22610
+ ]);
22611
+ function assertCommandSeatbelt(command) {
22612
+ for (const pattern of DESTRUCTIVE_COMMAND_PATTERNS) {
22613
+ if (pattern.test(command)) {
22614
+ throw new Error(
22615
+ `Command blocked by KXM safety seatbelt: "${command}". Destructive workspace mutations require explicit operator override.`
22616
+ );
22617
+ }
22618
+ }
22619
+ }
22620
+ var INSECURE_SSH_HOST_KEY_PATTERNS = Object.freeze([
22621
+ /StrictHostKeyChecking=(accept-new|no|off)/i,
22622
+ /UserKnownHostsFile=\/dev\/null/i
22623
+ ]);
22624
+
22625
+ // plugins/kxm/src/vnext-engine-command.ts
22468
22626
  var MAX_DIRECT_TIMER_MS = 2147483647;
22469
22627
  var COMMAND_TERM_GRACE_MS = 2e3;
22470
22628
  var COMMAND_FINAL_WAIT_MS = 2e3;
@@ -22581,6 +22739,7 @@ var CommandObserver = class {
22581
22739
  if (!Number.isInteger(this.definition.timeoutMs) || this.definition.timeoutMs < 1 || this.definition.timeoutMs > MAX_DIRECT_TIMER_MS) {
22582
22740
  throw runtimeError("run_events_illegal", "command", "command timeoutMs exceeds the direct timer bound");
22583
22741
  }
22742
+ assertCommandSeatbelt(this.definition.argv.join(" "));
22584
22743
  this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
22585
22744
  let child;
22586
22745
  try {
@@ -23706,6 +23865,7 @@ async function stepLocked(context, runId, producer, token) {
23706
23865
  return drivePanel(context, prepared.panel, producer);
23707
23866
  }
23708
23867
  async function runPreparedCommandGate(context, prepared, definition, token) {
23868
+ assertCommandSeatbelt(definition.argv.join(" "));
23709
23869
  const storePath = context.eventStore.path;
23710
23870
  const observer = createCommandObserver({
23711
23871
  definition,
@@ -25483,6 +25643,24 @@ function createVnextOneShotProducer(options = {}) {
25483
25643
  "--output-format",
25484
25644
  "json"
25485
25645
  ];
25646
+ } else if (harness === "agy") {
25647
+ args = [
25648
+ "-m",
25649
+ resolved.model,
25650
+ ...permissionArgs,
25651
+ "--output-format",
25652
+ "json",
25653
+ "-p"
25654
+ ];
25655
+ } else if (harness === "kimi") {
25656
+ args = [
25657
+ "-m",
25658
+ resolved.model,
25659
+ ...permissionArgs,
25660
+ "--output-format",
25661
+ "stream-json",
25662
+ "-p"
25663
+ ];
25486
25664
  } else {
25487
25665
  throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
25488
25666
  }
@@ -25525,7 +25703,8 @@ Return a final JSON object with an "outcome" field chosen from ${JSON.stringify(
25525
25703
  parsed = { text: "", isError: true };
25526
25704
  }
25527
25705
  const aborted = request.signal.aborted || procResult.error?.message === "process_aborted";
25528
- const effectUncertain = procResult.terminationRequested === true || Boolean(procResult.signal) || procResult.error?.message === "process_exit_unobserved" || procResult.observedChildExit === false && procResult.started !== false;
25706
+ const unverifiedDescendants = procResult.unverifiedDescendants === true || procResult.terminationRequested === true || procResult.error?.message === "process_exit_unobserved" || procResult.observedChildExit === false && procResult.started !== false;
25707
+ const effectUncertain = unverifiedDescendants || Boolean(procResult.signal);
25529
25708
  const transportFailed = procResult.code !== 0 || Boolean(procResult.error) || effectUncertain;
25530
25709
  const outcome = aborted ? "cancelled" : transportFailed || parsed.isError ? "failed" : determineOutcome(parsed.text, request.allowedOutcomes);
25531
25710
  const providerMetadata = {
@@ -25537,6 +25716,7 @@ Return a final JSON object with an "outcome" field chosen from ${JSON.stringify(
25537
25716
  if (procResult.observedChildExit !== void 0) providerMetadata.observedChildExit = procResult.observedChildExit;
25538
25717
  if (procResult.started !== void 0) providerMetadata.processStarted = procResult.started;
25539
25718
  if (procResult.terminationRequested !== void 0) providerMetadata.terminationRequested = procResult.terminationRequested;
25719
+ if (unverifiedDescendants) providerMetadata.descendantEffects = "unverified";
25540
25720
  if (procResult.error) {
25541
25721
  const reason = procResult.error.message;
25542
25722
  providerMetadata.processError = /^process_(aborted|timeout|output_limit|stdin_error|stdio_error|stdio_unclosed|exit_unobserved)$/.test(reason) ? reason : "process_error";
@@ -26017,7 +26197,6 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
26017
26197
  });
26018
26198
  } catch (err) {
26019
26199
  earlyError = err;
26020
- drivePromise = Promise.reject(err);
26021
26200
  }
26022
26201
  await Promise.resolve();
26023
26202
  if (earlyError) {
@@ -26043,6 +26222,7 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
26043
26222
  } catch {
26044
26223
  }
26045
26224
  }
26225
+ }).catch(() => {
26046
26226
  });
26047
26227
  sendJson(response, 202, {
26048
26228
  ok: true,