@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
@@ -2986,7 +2986,7 @@ var require_compile = __commonJS({
2986
2986
  const schOrFunc = root.refs[ref];
2987
2987
  if (schOrFunc)
2988
2988
  return schOrFunc;
2989
- let _sch = resolve8.call(this, root, ref);
2989
+ let _sch = resolve9.call(this, root, ref);
2990
2990
  if (_sch === void 0) {
2991
2991
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2992
2992
  const { schemaId } = this.opts;
@@ -3013,7 +3013,7 @@ var require_compile = __commonJS({
3013
3013
  function sameSchemaEnv(s1, s2) {
3014
3014
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3015
3015
  }
3016
- function resolve8(root, ref) {
3016
+ function resolve9(root, ref) {
3017
3017
  let sch;
3018
3018
  while (typeof (sch = this.refs[ref]) == "string")
3019
3019
  ref = sch;
@@ -3834,11 +3834,11 @@ var require_fast_uri = __commonJS({
3834
3834
  normalizeString(uri, options);
3835
3835
  } else if (typeof uri === "object") {
3836
3836
  uri = /** @type {T} */
3837
- parse5(serialize(uri, options), options);
3837
+ parse6(serialize(uri, options), options);
3838
3838
  }
3839
3839
  return uri;
3840
3840
  }
3841
- function resolve8(baseURI, relativeURI, options) {
3841
+ function resolve9(baseURI, relativeURI, options) {
3842
3842
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3843
3843
  const {
3844
3844
  parsed: baseParsed,
@@ -3874,8 +3874,8 @@ var require_fast_uri = __commonJS({
3874
3874
  function resolveComponent(base, relative5, options, skipNormalization) {
3875
3875
  const target = {};
3876
3876
  if (!skipNormalization) {
3877
- base = parse5(serialize(base, options), options);
3878
- relative5 = parse5(serialize(relative5, options), options);
3877
+ base = parse6(serialize(base, options), options);
3878
+ relative5 = parse6(serialize(relative5, options), options);
3879
3879
  }
3880
3880
  options = options || {};
3881
3881
  if (!options.tolerant && relative5.scheme) {
@@ -4167,7 +4167,7 @@ var require_fast_uri = __commonJS({
4167
4167
  }
4168
4168
  return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
4169
4169
  }
4170
- function parse5(uri, opts) {
4170
+ function parse6(uri, opts) {
4171
4171
  return parseWithStatus(uri, opts).parsed;
4172
4172
  }
4173
4173
  function normalizeString(uri, opts) {
@@ -4200,11 +4200,11 @@ var require_fast_uri = __commonJS({
4200
4200
  var fastUri = {
4201
4201
  SCHEMES,
4202
4202
  normalize,
4203
- resolve: resolve8,
4203
+ resolve: resolve9,
4204
4204
  resolveComponent,
4205
4205
  equal,
4206
4206
  serialize,
4207
- parse: parse5
4207
+ parse: parse6
4208
4208
  };
4209
4209
  module.exports = fastUri;
4210
4210
  module.exports.default = fastUri;
@@ -14674,7 +14674,7 @@ var require_public_api = __commonJS({
14674
14674
  }
14675
14675
  return doc;
14676
14676
  }
14677
- function parse5(src, reviver, options) {
14677
+ function parse6(src, reviver, options) {
14678
14678
  let _reviver = void 0;
14679
14679
  if (typeof reviver === "function") {
14680
14680
  _reviver = reviver;
@@ -14715,7 +14715,7 @@ var require_public_api = __commonJS({
14715
14715
  return value.toString(options);
14716
14716
  return new Document.Document(value, _replacer, options).toString(options);
14717
14717
  }
14718
- exports.parse = parse5;
14718
+ exports.parse = parse6;
14719
14719
  exports.parseAllDocuments = parseAllDocuments;
14720
14720
  exports.parseDocument = parseDocument2;
14721
14721
  exports.stringify = stringify3;
@@ -15122,11 +15122,24 @@ var OUTPUT_LIMIT = 8 * 1024 * 1024;
15122
15122
  var KILL_GRACE_MS = 250;
15123
15123
  var DRAIN_GRACE_MS = 500;
15124
15124
  var REAP_GRACE_MS = 1e3;
15125
+ function killProcessTree(child, signal = "SIGKILL") {
15126
+ if (process.platform !== "win32" && child.pid) {
15127
+ try {
15128
+ process.kill(-child.pid, signal);
15129
+ return;
15130
+ } catch {
15131
+ }
15132
+ }
15133
+ try {
15134
+ child.kill(signal);
15135
+ } catch {
15136
+ }
15137
+ }
15125
15138
  function defaultSpawn(command, args, options) {
15126
15139
  if (options.signal?.aborted) {
15127
15140
  return Promise.resolve({ stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") });
15128
15141
  }
15129
- return new Promise((resolve8) => {
15142
+ return new Promise((resolve9) => {
15130
15143
  const stdout = [];
15131
15144
  const stderr = [];
15132
15145
  let outputBytes = 0;
@@ -15146,6 +15159,7 @@ function defaultSpawn(command, args, options) {
15146
15159
  let child;
15147
15160
  const finish = () => {
15148
15161
  if (finished) return;
15162
+ if (stopping && !killSent) return;
15149
15163
  finished = true;
15150
15164
  for (const timer of [wallTimer, killTimer, drainTimer, reapTimer]) clearTimeout(timer);
15151
15165
  options.signal?.removeEventListener("abort", onAbort);
@@ -15155,14 +15169,21 @@ function defaultSpawn(command, args, options) {
15155
15169
  child?.unref();
15156
15170
  const started = Boolean(child?.pid);
15157
15171
  if (started && !observedChildExit) error ??= new Error("process_exit_unobserved");
15158
- resolve8({ stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8"), code, signal, started, observedChildExit, terminationRequested: stopping, ...error ? { error } : {} });
15172
+ const unverifiedDescendants = stopping || started && !observedChildExit;
15173
+ resolve9({
15174
+ stdout: Buffer.concat(stdout).toString("utf8"),
15175
+ stderr: Buffer.concat(stderr).toString("utf8"),
15176
+ code,
15177
+ signal,
15178
+ started,
15179
+ observedChildExit,
15180
+ terminationRequested: stopping,
15181
+ ...unverifiedDescendants ? { unverifiedDescendants: true } : {},
15182
+ ...error ? { error } : {}
15183
+ });
15159
15184
  };
15160
15185
  const kill = (requested) => {
15161
- try {
15162
- if (process.platform !== "win32" && child.pid) process.kill(-child.pid, requested);
15163
- else child.kill(requested);
15164
- } catch {
15165
- }
15186
+ killProcessTree(child, requested);
15166
15187
  };
15167
15188
  const stop = (reason) => {
15168
15189
  if (finished) return;
@@ -15190,7 +15211,7 @@ function defaultSpawn(command, args, options) {
15190
15211
  cwd: options.cwd,
15191
15212
  env: options.env ?? process.env,
15192
15213
  stdio: ["pipe", "pipe", "pipe"],
15193
- shell: false,
15214
+ shell: options.shell === true,
15194
15215
  windowsHide: true,
15195
15216
  detached: process.platform !== "win32"
15196
15217
  });
@@ -15234,7 +15255,7 @@ function defaultSpawn(command, args, options) {
15234
15255
  closed = true;
15235
15256
  code = exitCode;
15236
15257
  signal = exitSignal;
15237
- if (!stopping || killSent) finish();
15258
+ finish();
15238
15259
  });
15239
15260
  options.signal?.addEventListener("abort", onAbort, { once: true });
15240
15261
  if (options.signal?.aborted) onAbort();
@@ -15489,7 +15510,9 @@ function parseAgyOneShotUsage(stdout, _stderr) {
15489
15510
  var READ_ONLY_ONESHOT_ARGS = Object.freeze({
15490
15511
  claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]),
15491
15512
  codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']),
15492
- grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"])
15513
+ grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]),
15514
+ agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]),
15515
+ kimi: Object.freeze(["--plan"])
15493
15516
  });
15494
15517
  function oneShotReadOnlyArgs(harness) {
15495
15518
  return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness] : void 0;
@@ -15543,7 +15566,7 @@ var BUILTIN_HARNESSES = Object.freeze([
15543
15566
  authArgs: ["provider", "list"],
15544
15567
  update: { self: ["upgrade"] },
15545
15568
  oneShot: {
15546
- argv: ["--output-format", "stream-json", "-p"],
15569
+ argv: [...oneShotReadOnlyArgs("kimi"), "--output-format", "stream-json", "-p"],
15547
15570
  promptVia: "arg",
15548
15571
  outputFormat: "stream-json",
15549
15572
  usageParser: parseKimiOneShotUsage
@@ -15606,7 +15629,7 @@ var BUILTIN_HARNESSES = Object.freeze([
15606
15629
  authArgs: ["models"],
15607
15630
  update: { self: ["update"] },
15608
15631
  oneShot: {
15609
- argv: ["--output-format", "json", "-p"],
15632
+ argv: [...oneShotReadOnlyArgs("agy"), "--output-format", "json", "-p"],
15610
15633
  promptVia: "arg",
15611
15634
  outputFormat: "json",
15612
15635
  usageParser: parseAgyOneShotUsage
@@ -15673,6 +15696,31 @@ function defaultRunner(env) {
15673
15696
  }
15674
15697
  };
15675
15698
  }
15699
+ function commandResultFromSpawn(result) {
15700
+ const errno = result.error && "code" in result.error ? result.error.code : void 0;
15701
+ const error = (typeof errno === "string" && errno ? errno : void 0) ?? result.error?.message ?? (result.observedChildExit !== true ? "auth_probe_exit_unobserved" : void 0);
15702
+ return {
15703
+ ok: result.code === 0 && !error,
15704
+ code: result.code,
15705
+ stdout: result.stdout,
15706
+ stderr: result.stderr,
15707
+ ...error ? { error } : {}
15708
+ };
15709
+ }
15710
+ function defaultAsyncRunner(env, signal) {
15711
+ return async (command, args, timeoutMs) => {
15712
+ try {
15713
+ return commandResultFromSpawn(await defaultSpawn(command, args, {
15714
+ env,
15715
+ timeoutMs,
15716
+ signal,
15717
+ shell: harnessSpawnUsesShell(command)
15718
+ }));
15719
+ } catch (error) {
15720
+ return { ok: false, code: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : "spawn_failed" };
15721
+ }
15722
+ };
15723
+ }
15676
15724
  function firstLine(text) {
15677
15725
  const line = text.split(/\r?\n/).map((candidate) => candidate.trim()).find(Boolean);
15678
15726
  return line && line.length <= 200 ? line : void 0;
@@ -15871,6 +15919,13 @@ function probeHarnesses(options = {}) {
15871
15919
  harnesses: BUILTIN_HARNESSES.map((entry) => probeEntry(entry, runCommand, timeoutMs, platform, options))
15872
15920
  };
15873
15921
  }
15922
+ async function probeHarnessesAsync(options = {}) {
15923
+ return replayHarnessProbe(
15924
+ options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal),
15925
+ (runCommand) => probeHarnesses({ ...options, runCommand }),
15926
+ 256
15927
+ );
15928
+ }
15874
15929
  function eligibleHarnesses(inventory) {
15875
15930
  const eligible = inventory.harnesses.filter((entry) => entry !== void 0 && entry.detected && entry.authenticated === true).map((entry) => entry.id);
15876
15931
  if (eligible.length === 0) throw new Error("no_authenticated_harness");
@@ -16107,35 +16162,31 @@ var PendingHarnessCommand = class {
16107
16162
  this.key = key;
16108
16163
  }
16109
16164
  };
16110
- async function probeHarnessAssignmentAsync(options) {
16165
+ async function replayHarnessProbe(run, execute, limit) {
16111
16166
  const observed = /* @__PURE__ */ new Map();
16112
- const run = options.runCommand ?? (async (command, args, timeoutMs) => {
16113
- const result = await defaultSpawn(command, args, { env: options.env, timeoutMs, signal: options.signal });
16114
- const error = result.error?.message ?? (result.observedChildExit !== true ? "auth_probe_exit_unobserved" : void 0);
16115
- return {
16116
- ok: result.code === 0 && !error,
16117
- code: result.code,
16118
- stdout: result.stdout,
16119
- stderr: result.stderr,
16120
- ...error ? { error } : {}
16121
- };
16122
- });
16123
- for (let commands = 0; commands <= 32; commands++) {
16167
+ for (let commands = 0; commands <= limit; commands++) {
16124
16168
  try {
16125
- return probeHarnessAssignment({ ...options, runCommand(command, args, timeoutMs) {
16169
+ return execute((command, args, timeoutMs) => {
16126
16170
  const key = JSON.stringify([command, args, timeoutMs]);
16127
16171
  const result = observed.get(key);
16128
16172
  if (result) return result;
16129
16173
  throw new PendingHarnessCommand(command, args, timeoutMs, key);
16130
- } });
16174
+ });
16131
16175
  } catch (pending) {
16132
16176
  if (!(pending instanceof PendingHarnessCommand)) throw pending;
16133
- if (commands === 32) throw new Error("auth_probe_command_limit");
16177
+ if (commands === limit) throw new Error("auth_probe_command_limit");
16134
16178
  observed.set(pending.key, await run(pending.command, pending.args, pending.timeoutMs));
16135
16179
  }
16136
16180
  }
16137
16181
  throw new Error("auth_probe_command_limit");
16138
16182
  }
16183
+ async function probeHarnessAssignmentAsync(options) {
16184
+ return replayHarnessProbe(
16185
+ options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal),
16186
+ (runCommand) => probeHarnessAssignment({ ...options, runCommand }),
16187
+ 32
16188
+ );
16189
+ }
16139
16190
  function probeHarnessesForModel(modelSpec, options = {}) {
16140
16191
  const timeoutMs = options.timeoutMs ?? 3e3;
16141
16192
  const runCommand = options.runCommand ?? defaultRunner(options.env ?? process.env);
@@ -16156,6 +16207,13 @@ function probeHarnessesForModel(modelSpec, options = {}) {
16156
16207
  )
16157
16208
  };
16158
16209
  }
16210
+ async function probeHarnessesForModelAsync(modelSpec, options = {}) {
16211
+ return replayHarnessProbe(
16212
+ options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal),
16213
+ (runCommand) => probeHarnessesForModel(modelSpec, { ...options, runCommand }),
16214
+ 256
16215
+ );
16216
+ }
16159
16217
  function scopesFor(scope, entry) {
16160
16218
  if (scope === "self") return ["self"];
16161
16219
  if (scope === "extensions") return entry.update.extensions ? ["extensions"] : [];
@@ -16914,8 +16972,8 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
16914
16972
  const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
16915
16973
  if (maxWriteRepositories > writable) issues.push(issue2("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
16916
16974
  }
16917
- const join15 = objectValue(step.join);
16918
- const minimumPassed = join15 && typeof join15.minimumPassed === "number" ? join15.minimumPassed : void 0;
16975
+ const join17 = objectValue(step.join);
16976
+ const minimumPassed = join17 && typeof join17.minimumPassed === "number" ? join17.minimumPassed : void 0;
16919
16977
  if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue2("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
16920
16978
  const distinctBy = names(assignment?.distinctBy);
16921
16979
  if (distinctBy.length > 0) {
@@ -17060,7 +17118,7 @@ function validateModelReferences(models, issues) {
17060
17118
  };
17061
17119
  for (const id of models.keys()) walk(id, []);
17062
17120
  }
17063
- function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry) {
17121
+ function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot) {
17064
17122
  const issues = [];
17065
17123
  const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
17066
17124
  const repositoryIds = /* @__PURE__ */ new Set();
@@ -17128,6 +17186,33 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
17128
17186
  const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
17129
17187
  if (!workflows.has(defaultWorkflow)) issues.push(issue2("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
17130
17188
  for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
17189
+ if (projectRoot) {
17190
+ const writerRolePath = join(projectRoot, ".kxm", "roles", "writer.yaml");
17191
+ const implementerAgent = agents.get("implementer") ?? agents.get("writer");
17192
+ if (existsSync2(writerRolePath) && implementerAgent) {
17193
+ try {
17194
+ const rawRole = parseRestrictedYaml2(readFileSync(writerRolePath, "utf8"));
17195
+ const roleObj = objectValue(rawRole);
17196
+ const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
17197
+ const enabledRosterModels = rosterEntries.filter((entry) => entry.enabled !== false).map((entry) => stringValue(entry.model)).filter((m) => Boolean(m));
17198
+ const agentModelObj = objectValue(implementerAgent.value.model);
17199
+ const agentModelStr = stringValue(implementerAgent.value.model);
17200
+ const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
17201
+ const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
17202
+ const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
17203
+ if (enabledRosterModels.length > 0 && canonicalAgentModel) {
17204
+ const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
17205
+ if (!matches) {
17206
+ 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}`));
17207
+ }
17208
+ }
17209
+ } catch (error) {
17210
+ if (error instanceof VnextConfigError) {
17211
+ issues.push(...error.issues);
17212
+ }
17213
+ }
17214
+ }
17215
+ }
17131
17216
  return sortIssues2(issues);
17132
17217
  }
17133
17218
  function vnextCanonicalJson(value) {
@@ -17308,7 +17393,7 @@ function loadVnextProject(projectRoot, options = {}) {
17308
17393
  }
17309
17394
  }
17310
17395
  if (loadIssues.length > 0) throw new VnextConfigError(loadIssues);
17311
- const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry);
17396
+ const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root);
17312
17397
  if (issues.length > 0) throw new VnextConfigError(issues);
17313
17398
  const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits3(left.logicalPath, right.logicalPath));
17314
17399
  return {
@@ -17420,7 +17505,47 @@ import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:pa
17420
17505
 
17421
17506
  // plugins/kxm/src/vnext-bindings.ts
17422
17507
  import { homedir } from "node:os";
17423
- import { DatabaseSync } from "node:sqlite";
17508
+
17509
+ // plugins/kxm/src/sqlite.ts
17510
+ import { createRequire } from "node:module";
17511
+ var requireFromHere = createRequire(import.meta.url);
17512
+ function loadNative() {
17513
+ try {
17514
+ const mod = requireFromHere("node:sqlite");
17515
+ if (mod.DatabaseSync) return { Ctor: mod.DatabaseSync, bun: false };
17516
+ } catch {
17517
+ }
17518
+ try {
17519
+ const mod = requireFromHere("bun:sqlite");
17520
+ const Ctor = mod?.DatabaseSync ?? mod?.Database;
17521
+ if (Ctor) return { Ctor, bun: true };
17522
+ } catch {
17523
+ }
17524
+ throw new Error("kxm: no supported sqlite module found (need node:sqlite or bun:sqlite)");
17525
+ }
17526
+ var native = loadNative();
17527
+ var DatabaseSync = class {
17528
+ inner;
17529
+ constructor(path, options) {
17530
+ let normalized = options;
17531
+ if (native.bun && options) {
17532
+ const { readOnly, ...rest } = options;
17533
+ normalized = readOnly === void 0 ? rest : { ...rest, readonly: readOnly };
17534
+ }
17535
+ this.inner = normalized === void 0 ? new native.Ctor(path) : new native.Ctor(path, normalized);
17536
+ }
17537
+ prepare(sql) {
17538
+ return this.inner.prepare(sql);
17539
+ }
17540
+ exec(sql) {
17541
+ return this.inner.exec(sql);
17542
+ }
17543
+ close() {
17544
+ this.inner.close();
17545
+ }
17546
+ };
17547
+
17548
+ // plugins/kxm/src/vnext-bindings.ts
17424
17549
  import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, parse, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
17425
17550
  var MAX_BINDING_RECORD_BYTES = 256 * 1024;
17426
17551
  var BINDING_LABEL = "Runtime-local repository bindings";
@@ -17464,7 +17589,6 @@ import {
17464
17589
  writeFileSync
17465
17590
  } from "node:fs";
17466
17591
  import { basename as basename2, dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
17467
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
17468
17592
  function databaseError(code, file, message) {
17469
17593
  const issue3 = { phase: "semantic", code, file, message };
17470
17594
  return new VnextConfigError([issue3]);
@@ -17544,7 +17668,7 @@ function openDatabase(file, description, spec) {
17544
17668
  }
17545
17669
  }
17546
17670
  }
17547
- const database = new DatabaseSync2(file);
17671
+ const database = new DatabaseSync(file);
17548
17672
  let transaction = false;
17549
17673
  try {
17550
17674
  database.exec(`PRAGMA busy_timeout = ${spec.timeoutMs ?? 5e3}`);
@@ -17648,7 +17772,7 @@ function backupDatabaseFile(sourcePath, targetPath, storeId) {
17648
17772
  if (existsSync3(resolvedTarget)) {
17649
17773
  unlinkSync(resolvedTarget);
17650
17774
  }
17651
- const sourceDb = new DatabaseSync2(resolvedSource);
17775
+ const sourceDb = new DatabaseSync(resolvedSource);
17652
17776
  let schemaVersion = 0;
17653
17777
  try {
17654
17778
  sourceDb.exec("PRAGMA busy_timeout = 5000");
@@ -17663,7 +17787,7 @@ function backupDatabaseFile(sourcePath, targetPath, storeId) {
17663
17787
  } finally {
17664
17788
  sourceDb.close();
17665
17789
  }
17666
- const targetDb = new DatabaseSync2(resolvedTarget);
17790
+ const targetDb = new DatabaseSync(resolvedTarget);
17667
17791
  try {
17668
17792
  targetDb.exec("PRAGMA busy_timeout = 5000");
17669
17793
  if (!checkIntegrity(targetDb)) {
@@ -17695,7 +17819,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
17695
17819
  if (!backupStat || !backupStat.isFile() || backupStat.isSymbolicLink()) {
17696
17820
  throw databaseError("runtime_path_invalid", resolvedBackup, `backup database ${resolvedBackup} must be a regular file, not a link or directory`);
17697
17821
  }
17698
- const backupDb = new DatabaseSync2(resolvedBackup);
17822
+ const backupDb = new DatabaseSync(resolvedBackup);
17699
17823
  let schemaVersion = 0;
17700
17824
  try {
17701
17825
  backupDb.exec("PRAGMA busy_timeout = 5000");
@@ -17735,7 +17859,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
17735
17859
  chmodSync(resolvedTarget, 384);
17736
17860
  } catch {
17737
17861
  }
17738
- const targetDb = new DatabaseSync2(resolvedTarget);
17862
+ const targetDb = new DatabaseSync(resolvedTarget);
17739
17863
  try {
17740
17864
  targetDb.exec("PRAGMA busy_timeout = 5000");
17741
17865
  if (!checkIntegrity(targetDb)) {
@@ -20377,7 +20501,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
20377
20501
  const maxAttempts = compileCountField(step.maxAttempts, 1, `${id}.maxAttempts`, id, sink);
20378
20502
  const timeoutMs = compileOptionalDuration(step.timeoutMs, `${id}.timeoutMs`, id, sink);
20379
20503
  const assignments = compileAssignments(step, id, agent, sink);
20380
- const join15 = compileJoin(step, id, sink);
20504
+ const join17 = compileJoin(step, id, sink);
20381
20505
  const requiredEvidence = compileEvidence(step, id, sink);
20382
20506
  const transitions = compileTransitions(step, id, index, stepIndex, sink);
20383
20507
  const outcomes = Object.keys(transitions).sort(compareCodeUnits4);
@@ -20405,7 +20529,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
20405
20529
  transitions: orderedTransitions,
20406
20530
  requiresPlanHash: requirePlanHash.includes(id),
20407
20531
  assignments,
20408
- join: join15
20532
+ join: join17
20409
20533
  };
20410
20534
  if (kind === "agent" || kind === "moa") {
20411
20535
  if (!agent) return void 0;
@@ -20455,15 +20579,15 @@ function compileAssignments(step, stepId, primaryAgentId, sink) {
20455
20579
  };
20456
20580
  }
20457
20581
  function compileJoin(step, stepId, sink) {
20458
- const join15 = objectValue2(step.join);
20459
- if (!join15) return { strategy: "all" };
20460
- const declared = stringValue2(join15.strategy);
20582
+ const join17 = objectValue2(step.join);
20583
+ if (!join17) return { strategy: "all" };
20584
+ const declared = stringValue2(join17.strategy);
20461
20585
  const strategy = declared && JOIN_STRATEGIES.has(declared) ? declared : "all";
20462
- const minimumPassed = compileOptionalCount(join15.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
20586
+ const minimumPassed = compileOptionalCount(join17.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
20463
20587
  const compiled = {
20464
20588
  strategy,
20465
20589
  ...minimumPassed !== void 0 ? { minimumPassed } : {},
20466
- ...typeof join15.cancelRemaining === "boolean" ? { cancelRemaining: join15.cancelRemaining } : {}
20590
+ ...typeof join17.cancelRemaining === "boolean" ? { cancelRemaining: join17.cancelRemaining } : {}
20467
20591
  };
20468
20592
  return compiled;
20469
20593
  }
@@ -21758,12 +21882,12 @@ function enqueueVnextScheduledRun(storePath, runId, configRevision, bound, start
21758
21882
  if (owner.admitted.has(runId) || owner.queue.some((item) => item.runId === runId)) {
21759
21883
  return Promise.reject(runtimeError("run_busy", runId, `run ${runId} is already admitted or queued`));
21760
21884
  }
21761
- return new Promise((resolve8, reject) => {
21885
+ return new Promise((resolve9, reject) => {
21762
21886
  owner.queue.push({
21763
21887
  runId,
21764
21888
  configRevision,
21765
21889
  bound,
21766
- start: (token) => start(token).then(resolve8, reject),
21890
+ start: (token) => start(token).then(resolve9, reject),
21767
21891
  fail: (error) => reject(error)
21768
21892
  });
21769
21893
  pump(storePath, owner);
@@ -22274,9 +22398,28 @@ import { join as join11 } from "node:path";
22274
22398
  import { createHash as createHash7, randomUUID as randomUUID3 } from "node:crypto";
22275
22399
  import { lstat, mkdir, open, rename } from "node:fs/promises";
22276
22400
  import { join as join6 } from "node:path";
22401
+ var ONESHOT_EVIDENCE_SCHEMA = "kxm.oneshot-evidence.v2";
22402
+ var RETIRED_ONESHOT_EVIDENCE_SCHEMAS = /* @__PURE__ */ new Set(["kxm.oneshot-evidence.v1"]);
22277
22403
  var TEXT_LIMIT = 4 * 1024 * 1024;
22404
+ var ARGV_LIMIT = 64 * 1024;
22278
22405
  var RECORD_LIMIT = 16 * 1024 * 1024;
22279
22406
  var digest = (value) => `sha256:${createHash7("sha256").update(value).digest("hex")}`;
22407
+ function parseOneShotEvidenceRecord(bytes) {
22408
+ let parsed;
22409
+ try {
22410
+ parsed = JSON.parse(bytes);
22411
+ } catch {
22412
+ throw new Error("oneshot_evidence_record_invalid");
22413
+ }
22414
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
22415
+ throw new Error("oneshot_evidence_record_invalid");
22416
+ }
22417
+ const schema = parsed.schema;
22418
+ if (typeof schema !== "string") throw new Error("oneshot_evidence_record_invalid");
22419
+ if (RETIRED_ONESHOT_EVIDENCE_SCHEMAS.has(schema)) throw new Error("oneshot_evidence_schema_retired");
22420
+ if (schema !== ONESHOT_EVIDENCE_SCHEMA) throw new Error("oneshot_evidence_schema_unknown");
22421
+ return parsed;
22422
+ }
22280
22423
  async function beginOneShotEvidence(root, intent, sensitive) {
22281
22424
  await mkdir(root, { recursive: true, mode: 448 });
22282
22425
  const stat = await lstat(root);
@@ -22290,11 +22433,11 @@ async function beginOneShotEvidence(root, intent, sensitive) {
22290
22433
  const redact = (value) => {
22291
22434
  let text = value;
22292
22435
  for (const secret of secrets) text = text.replaceAll(secret, "[REDACTED]");
22293
- return text.replace(/(\b(?:authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\b["']?\s*[:=]\s*["']?)(?:Bearer\s+)?[^\s"',;}]+/gi, "$1[REDACTED]");
22436
+ 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]");
22294
22437
  };
22295
- const bounded = (value) => {
22438
+ const bounded = (value, max = TEXT_LIMIT) => {
22296
22439
  const bytes = Buffer.from(redact(value));
22297
- return { text: bytes.subarray(0, TEXT_LIMIT).toString("utf8"), truncated: bytes.length > TEXT_LIMIT, sha256: digest(value) };
22440
+ return { text: bytes.subarray(0, max).toString("utf8"), truncated: bytes.length > max, sha256: digest(redact(value)) };
22298
22441
  };
22299
22442
  const write = async (name, value) => {
22300
22443
  const current = await lstat(dir);
@@ -22303,6 +22446,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
22303
22446
  }
22304
22447
  const bytes = JSON.stringify(value);
22305
22448
  if (Buffer.byteLength(bytes) > RECORD_LIMIT) throw new Error("oneshot_evidence_record_limit");
22449
+ parseOneShotEvidenceRecord(bytes);
22306
22450
  const target = join6(dir, name);
22307
22451
  const partial = `${target}.partial`;
22308
22452
  const file = await open(partial, "wx", 384);
@@ -22324,7 +22468,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
22324
22468
  return digest(bytes);
22325
22469
  };
22326
22470
  await write("intent.json", {
22327
- schema: "kxm.oneshot-evidence.v1",
22471
+ schema: ONESHOT_EVIDENCE_SCHEMA,
22328
22472
  id,
22329
22473
  recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
22330
22474
  runId: intent.runId,
@@ -22335,9 +22479,10 @@ async function beginOneShotEvidence(root, intent, sensitive) {
22335
22479
  provider: intent.provider,
22336
22480
  requestedModel: intent.model,
22337
22481
  cwd: intent.cwd,
22338
- command: intent.command,
22339
- argv: bounded(JSON.stringify(intent.args)),
22482
+ command: redact(intent.command),
22483
+ argv: bounded(JSON.stringify(intent.args), ARGV_LIMIT),
22340
22484
  stdin: intent.input === void 0 ? null : bounded(intent.input),
22485
+ env: null,
22341
22486
  acceptance: false
22342
22487
  });
22343
22488
  let finished = false;
@@ -22347,7 +22492,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
22347
22492
  if (finished) throw new Error("oneshot_evidence_already_finished");
22348
22493
  finished = true;
22349
22494
  return write("result.json", {
22350
- schema: "kxm.oneshot-evidence.v1",
22495
+ schema: ONESHOT_EVIDENCE_SCHEMA,
22351
22496
  id,
22352
22497
  recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
22353
22498
  stdout: bounded(result.stdout),
@@ -22357,6 +22502,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
22357
22502
  started: result.started ?? null,
22358
22503
  observedChildExit: result.observedChildExit ?? null,
22359
22504
  terminationRequested: result.terminationRequested ?? null,
22505
+ unverifiedDescendants: result.unverifiedDescendants ?? null,
22360
22506
  error: result.error ? bounded(result.error.message) : null,
22361
22507
  observation: bounded(JSON.stringify(observation)),
22362
22508
  acceptance: false
@@ -22912,6 +23058,43 @@ function evaluateArtifactsGate(context, definition) {
22912
23058
  // plugins/kxm/src/vnext-engine-command.ts
22913
23059
  import { spawn as spawn2 } from "node:child_process";
22914
23060
  import { createHash as createHash9 } from "node:crypto";
23061
+
23062
+ // plugins/kxm/src/safety-integrity.ts
23063
+ var DESTRUCTIVE_COMMAND_PATTERNS = Object.freeze([
23064
+ /^\s*rm\s+.*-[a-zA-Z]*r[a-zA-Z]*f/i,
23065
+ /^\s*rm\s+.*-[a-zA-Z]*f[a-zA-Z]*r/i,
23066
+ /^\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,
23067
+ /^\s*git\s+reset\s+--hard/i,
23068
+ /^\s*git\s+clean\s+-[a-zA-Z]*f/i,
23069
+ /^\s*git\s+checkout\s+--\s+/i,
23070
+ /^\s*git\s+restore\s+(\.|\*|--staged\s+(\.|\*))/i
23071
+ ]);
23072
+ function assertCommandSeatbelt(command) {
23073
+ for (const pattern of DESTRUCTIVE_COMMAND_PATTERNS) {
23074
+ if (pattern.test(command)) {
23075
+ throw new Error(
23076
+ `Command blocked by KXM safety seatbelt: "${command}". Destructive workspace mutations require explicit operator override.`
23077
+ );
23078
+ }
23079
+ }
23080
+ }
23081
+ var INSECURE_SSH_HOST_KEY_PATTERNS = Object.freeze([
23082
+ /StrictHostKeyChecking=(accept-new|no|off)/i,
23083
+ /UserKnownHostsFile=\/dev\/null/i
23084
+ ]);
23085
+ function assertPinnedSshHostKeyPolicy(sshArgs) {
23086
+ for (const arg of sshArgs) {
23087
+ for (const pattern of INSECURE_SSH_HOST_KEY_PATTERNS) {
23088
+ if (pattern.test(arg)) {
23089
+ throw new Error(
23090
+ `Insecure SSH host key policy rejected: "${arg}". KXM headless SSH requires pinned host keys with StrictHostKeyChecking=yes and BatchMode=yes.`
23091
+ );
23092
+ }
23093
+ }
23094
+ }
23095
+ }
23096
+
23097
+ // plugins/kxm/src/vnext-engine-command.ts
22915
23098
  var MAX_DIRECT_TIMER_MS = 2147483647;
22916
23099
  var COMMAND_TERM_GRACE_MS = 2e3;
22917
23100
  var COMMAND_FINAL_WAIT_MS = 2e3;
@@ -22997,8 +23180,8 @@ var CommandObserver = class {
22997
23180
  this.requestStop("error", "lost-close");
22998
23181
  }
22999
23182
  run() {
23000
- return new Promise((resolve8) => {
23001
- this.resolveOutcome = resolve8;
23183
+ return new Promise((resolve9) => {
23184
+ this.resolveOutcome = resolve9;
23002
23185
  try {
23003
23186
  this.start();
23004
23187
  } catch (error) {
@@ -23028,6 +23211,7 @@ var CommandObserver = class {
23028
23211
  if (!Number.isInteger(this.definition.timeoutMs) || this.definition.timeoutMs < 1 || this.definition.timeoutMs > MAX_DIRECT_TIMER_MS) {
23029
23212
  throw runtimeError("run_events_illegal", "command", "command timeoutMs exceeds the direct timer bound");
23030
23213
  }
23214
+ assertCommandSeatbelt(this.definition.argv.join(" "));
23031
23215
  this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
23032
23216
  let child;
23033
23217
  try {
@@ -24153,6 +24337,7 @@ async function stepLocked(context, runId, producer, token) {
24153
24337
  return drivePanel(context, prepared.panel, producer);
24154
24338
  }
24155
24339
  async function runPreparedCommandGate(context, prepared, definition, token) {
24340
+ assertCommandSeatbelt(definition.argv.join(" "));
24156
24341
  const storePath = context.eventStore.path;
24157
24342
  const observer = createCommandObserver({
24158
24343
  definition,
@@ -25930,6 +26115,24 @@ function createVnextOneShotProducer(options = {}) {
25930
26115
  "--output-format",
25931
26116
  "json"
25932
26117
  ];
26118
+ } else if (harness === "agy") {
26119
+ args = [
26120
+ "-m",
26121
+ resolved.model,
26122
+ ...permissionArgs,
26123
+ "--output-format",
26124
+ "json",
26125
+ "-p"
26126
+ ];
26127
+ } else if (harness === "kimi") {
26128
+ args = [
26129
+ "-m",
26130
+ resolved.model,
26131
+ ...permissionArgs,
26132
+ "--output-format",
26133
+ "stream-json",
26134
+ "-p"
26135
+ ];
25933
26136
  } else {
25934
26137
  throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
25935
26138
  }
@@ -25972,7 +26175,8 @@ Return a final JSON object with an "outcome" field chosen from ${JSON.stringify(
25972
26175
  parsed = { text: "", isError: true };
25973
26176
  }
25974
26177
  const aborted = request.signal.aborted || procResult.error?.message === "process_aborted";
25975
- const effectUncertain = procResult.terminationRequested === true || Boolean(procResult.signal) || procResult.error?.message === "process_exit_unobserved" || procResult.observedChildExit === false && procResult.started !== false;
26178
+ const unverifiedDescendants = procResult.unverifiedDescendants === true || procResult.terminationRequested === true || procResult.error?.message === "process_exit_unobserved" || procResult.observedChildExit === false && procResult.started !== false;
26179
+ const effectUncertain = unverifiedDescendants || Boolean(procResult.signal);
25976
26180
  const transportFailed = procResult.code !== 0 || Boolean(procResult.error) || effectUncertain;
25977
26181
  const outcome = aborted ? "cancelled" : transportFailed || parsed.isError ? "failed" : determineOutcome(parsed.text, request.allowedOutcomes);
25978
26182
  const providerMetadata = {
@@ -25984,6 +26188,7 @@ Return a final JSON object with an "outcome" field chosen from ${JSON.stringify(
25984
26188
  if (procResult.observedChildExit !== void 0) providerMetadata.observedChildExit = procResult.observedChildExit;
25985
26189
  if (procResult.started !== void 0) providerMetadata.processStarted = procResult.started;
25986
26190
  if (procResult.terminationRequested !== void 0) providerMetadata.terminationRequested = procResult.terminationRequested;
26191
+ if (unverifiedDescendants) providerMetadata.descendantEffects = "unverified";
25987
26192
  if (procResult.error) {
25988
26193
  const reason = procResult.error.message;
25989
26194
  providerMetadata.processError = /^process_(aborted|timeout|output_limit|stdin_error|stdio_error|stdio_unclosed|exit_unobserved)$/.test(reason) ? reason : "process_error";
@@ -26409,8 +26614,8 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
26409
26614
  }
26410
26615
  let releaseActive = () => {
26411
26616
  };
26412
- const activePromise = new Promise((resolve8) => {
26413
- releaseActive = resolve8;
26617
+ const activePromise = new Promise((resolve9) => {
26618
+ releaseActive = resolve9;
26414
26619
  });
26415
26620
  activeDrives.set(runId, activePromise);
26416
26621
  const cleanupActive = () => {
@@ -26464,7 +26669,6 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
26464
26669
  });
26465
26670
  } catch (err) {
26466
26671
  earlyError = err;
26467
- drivePromise = Promise.reject(err);
26468
26672
  }
26469
26673
  await Promise.resolve();
26470
26674
  if (earlyError) {
@@ -26490,6 +26694,7 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
26490
26694
  } catch {
26491
26695
  }
26492
26696
  }
26697
+ }).catch(() => {
26493
26698
  });
26494
26699
  sendJson(response, 202, {
26495
26700
  ok: true,
@@ -26866,8 +27071,8 @@ var PiSession = class {
26866
27071
  }
26867
27072
  const id = `cmd_${++this.commandCounter}`;
26868
27073
  const payload = { ...command, id };
26869
- return new Promise((resolve8, reject) => {
26870
- this.pendingCommands.set(id, { resolve: resolve8, reject });
27074
+ return new Promise((resolve9, reject) => {
27075
+ this.pendingCommands.set(id, { resolve: resolve9, reject });
26871
27076
  try {
26872
27077
  this.process.stdin.write(JSON.stringify(payload) + "\n");
26873
27078
  } catch (err) {
@@ -26888,7 +27093,7 @@ var PiSession = class {
26888
27093
  return { outcome, text: "aborted", usage: {} };
26889
27094
  }
26890
27095
  this.status = "busy";
26891
- return new Promise((resolve8, reject) => {
27096
+ return new Promise((resolve9, reject) => {
26892
27097
  let signalCleanup;
26893
27098
  if (signal) {
26894
27099
  const onAbort = () => {
@@ -26902,7 +27107,7 @@ var PiSession = class {
26902
27107
  signalCleanup = () => signal.removeEventListener("abort", onAbort);
26903
27108
  }
26904
27109
  this.activePrompt = {
26905
- resolve: resolve8,
27110
+ resolve: resolve9,
26906
27111
  reject,
26907
27112
  allowedOutcomes,
26908
27113
  text: "",
@@ -26975,19 +27180,21 @@ function createVnextPiProducer(options = {}) {
26975
27180
  const parsed = parseModelString(defaultModel);
26976
27181
  return { provider: parsed.provider, model: parsed.model };
26977
27182
  }
26978
- function checkPiAuth(provider, model) {
27183
+ async function checkPiAuth(provider, model, signal) {
26979
27184
  if (options.inventory) {
26980
27185
  const piEntry = options.inventory.harnesses.find((h) => h.id === "pi");
26981
27186
  if (!piEntry || !piEntry.detected || piEntry.authenticated === false) {
26982
27187
  throw new Error("pi_not_authenticated: pi harness not authenticated in inventory");
26983
27188
  }
26984
27189
  }
26985
- const probeFn = options.probeHarness ?? probeHarnessAssignment;
26986
- const probe = probeFn({
27190
+ const probeFn = options.probeHarness ?? probeHarnessAssignmentAsync;
27191
+ const probe = await probeFn({
26987
27192
  harness: "pi",
26988
27193
  provider,
26989
27194
  model,
26990
- env: options.env
27195
+ env: options.env,
27196
+ signal,
27197
+ timeoutMs: 1e4
26991
27198
  });
26992
27199
  if (!probe.detected) {
26993
27200
  throw new Error(`pi_not_authenticated: pi harness not detected (${probe.issues.join(", ")})`);
@@ -27063,7 +27270,7 @@ function createVnextPiProducer(options = {}) {
27063
27270
  async produce(request) {
27064
27271
  const startTime = Date.now();
27065
27272
  const resolved = resolveModelForRequest(request);
27066
- checkPiAuth(resolved.provider, resolved.model);
27273
+ await checkPiAuth(resolved.provider, resolved.model, request.signal);
27067
27274
  const session = await getOrCreateSession(request, resolved.model);
27068
27275
  const promptMessage = request.prompt ?? `Execute step ${request.stepId} (attempt ${request.stepAttempt}) for agent ${request.agentId}. Allowed outcomes: ${request.allowedOutcomes.join(", ")}.`;
27069
27276
  let promptResult;
@@ -27627,21 +27834,1291 @@ function evaluatePromotionPolicy(candidate, policy = "manual_pr", options) {
27627
27834
  reason: `Unknown promotion policy: ${String(policy)}`
27628
27835
  };
27629
27836
  }
27837
+
27838
+ // plugins/kxm/src/browser.ts
27839
+ import { execSync } from "node:child_process";
27840
+ var VIEWPORT_PRESETS = Object.freeze({
27841
+ "mobile-sm": { name: "Mobile Small (SE)", category: "mobile", width: 375, height: 667, isMobile: true, hasTouch: true },
27842
+ "mobile": { name: "Mobile (iPhone 16 / 15 Pro)", category: "mobile", width: 393, height: 852, isMobile: true, hasTouch: true },
27843
+ "mobile-lg": { name: "Mobile Large (Pro Max)", category: "mobile", width: 430, height: 932, isMobile: true, hasTouch: true },
27844
+ "pixel": { name: "Google Pixel 8/9", category: "mobile", width: 412, height: 924, isMobile: true, hasTouch: true },
27845
+ "galaxy": { name: "Samsung Galaxy S24", category: "mobile", width: 360, height: 780, isMobile: true, hasTouch: true },
27846
+ "tablet": { name: "Tablet (iPad Air / Mini)", category: "tablet", width: 820, height: 1180, isMobile: true, hasTouch: true },
27847
+ "tablet-lg": { name: "Tablet Large (iPad Pro 12.9)", category: "tablet", width: 1024, height: 1366, isMobile: true, hasTouch: true },
27848
+ "laptop": { name: "Standard Laptop", category: "laptop", width: 1366, height: 768 },
27849
+ "macbook-13": { name: "MacBook Air 13", category: "laptop", width: 1440, height: 900 },
27850
+ "macbook-16": { name: "MacBook Pro 16", category: "laptop", width: 1728, height: 1117 },
27851
+ "desktop": { name: "Desktop FHD (1080p)", category: "desktop", width: 1920, height: 1080 },
27852
+ "desktop-2k": { name: "Desktop QHD (1440p)", category: "desktop", width: 2560, height: 1440 },
27853
+ "desktop-4k": { name: "Desktop 4K UHD", category: "desktop", width: 3840, height: 2160 }
27854
+ });
27855
+ function resolveViewportDimensions(presetOrDims) {
27856
+ if (!presetOrDims) {
27857
+ return { width: 1920, height: 1080 };
27858
+ }
27859
+ if (typeof presetOrDims === "string") {
27860
+ const matched = VIEWPORT_PRESETS[presetOrDims.toLowerCase()];
27861
+ if (matched) {
27862
+ return { width: matched.width, height: matched.height };
27863
+ }
27864
+ return { width: 1920, height: 1080 };
27865
+ }
27866
+ return presetOrDims;
27867
+ }
27868
+ function resolvePassCliApiKey(execFn = (cmd) => execSync(cmd, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], timeout: 5e3 })) {
27869
+ if (typeof process === "undefined" || process.env.USE_PASS_CLI === "false") {
27870
+ return void 0;
27871
+ }
27872
+ if (process.env.PASS_CLI_OUTPUT_MOCK) {
27873
+ try {
27874
+ const parsed = JSON.parse(process.env.PASS_CLI_OUTPUT_MOCK);
27875
+ const extraFields = parsed?.item?.content?.extra_fields || [];
27876
+ const customSections = parsed?.item?.content?.content?.Custom?.sections || [];
27877
+ const sectionFields = customSections.flatMap((s) => s.section_fields || []);
27878
+ const allFields = [...extraFields, ...sectionFields];
27879
+ return allFields.find((f) => f.name === "STEEL_API_KEY")?.content?.Hidden;
27880
+ } catch {
27881
+ return void 0;
27882
+ }
27883
+ }
27884
+ try {
27885
+ const output = execFn(
27886
+ 'pass-cli item view --vault-name "AI Provider Keys" --item-title "Steel Browser (KontextMind DOKS)" --output json'
27887
+ );
27888
+ const parsed = JSON.parse(output);
27889
+ const extraFields = parsed?.item?.content?.extra_fields || [];
27890
+ const customSections = parsed?.item?.content?.content?.Custom?.sections || [];
27891
+ const sectionFields = customSections.flatMap((s) => s.section_fields || []);
27892
+ const allFields = [...extraFields, ...sectionFields];
27893
+ return allFields.find((f) => f.name === "STEEL_API_KEY")?.content?.Hidden;
27894
+ } catch {
27895
+ return void 0;
27896
+ }
27897
+ }
27898
+ function resolveSteelConfig(overrides) {
27899
+ const apiUrl = overrides?.apiUrl || process.env.STEEL_API_URL || "https://steel.kontextmind.com";
27900
+ const apiKey = overrides?.apiKey || process.env.STEEL_API_KEY || resolvePassCliApiKey();
27901
+ const uiUrl = overrides?.uiUrl || (overrides?.apiUrl ? `${overrides.apiUrl.replace(/\/$/, "")}/ui` : void 0) || process.env.STEEL_UI_URL || `${apiUrl.replace(/\/$/, "")}/ui`;
27902
+ return {
27903
+ apiUrl: apiUrl.replace(/\/$/, ""),
27904
+ apiKey,
27905
+ uiUrl,
27906
+ timeoutMs: overrides?.timeoutMs || 3e5
27907
+ // 5 minutes default
27908
+ };
27909
+ }
27910
+ function formatCDPEndpoint(session, config) {
27911
+ const baseApi = config.apiUrl;
27912
+ const urlObj = new URL(baseApi);
27913
+ const isSecure = urlObj.protocol === "https:";
27914
+ const wsProtocol = isSecure ? "wss:" : "ws:";
27915
+ const host = urlObj.host;
27916
+ const searchParams = new URLSearchParams();
27917
+ searchParams.set("sessionId", session.id);
27918
+ if (config.apiKey) {
27919
+ searchParams.set("apiKey", config.apiKey);
27920
+ }
27921
+ return `${wsProtocol}//${host}/v1/devtools?${searchParams.toString()}`;
27922
+ }
27923
+ function sanitizeLogOutput(input) {
27924
+ if (typeof input === "string") {
27925
+ return input.replace(/apiKey=[^&]+/g, "apiKey=[REDACTED]").replace(/steel_[a-f0-9]+/g, "steel_[REDACTED]");
27926
+ }
27927
+ if (Array.isArray(input)) {
27928
+ return input.map(sanitizeLogOutput);
27929
+ }
27930
+ if (input !== null && typeof input === "object") {
27931
+ const copy = {};
27932
+ for (const [k, v] of Object.entries(input)) {
27933
+ if (/key|secret|token|auth|password/i.test(k) && typeof v === "string") {
27934
+ copy[k] = "[REDACTED]";
27935
+ } else {
27936
+ copy[k] = sanitizeLogOutput(v);
27937
+ }
27938
+ }
27939
+ return copy;
27940
+ }
27941
+ return input;
27942
+ }
27943
+ function createAnnotationFeedback(feedback) {
27944
+ return {
27945
+ ...feedback,
27946
+ capturedAt: feedback.capturedAt || (/* @__PURE__ */ new Date()).toISOString()
27947
+ };
27948
+ }
27949
+ function formatAnnotationFeedbackPrompt(feedback) {
27950
+ let output = `## Visual Feedback & Section Annotation
27951
+
27952
+ `;
27953
+ output += `- **Target URL**: ${feedback.url}
27954
+ `;
27955
+ if (feedback.sessionId) {
27956
+ output += `- **Session ID**: ${feedback.sessionId}
27957
+ `;
27958
+ }
27959
+ if (feedback.sectionSelector) {
27960
+ output += `- **Section Target Selector**: \`${feedback.sectionSelector}\`
27961
+ `;
27962
+ }
27963
+ if (feedback.screenshotPath) {
27964
+ output += `- **Screenshot Artifact**: \`${feedback.screenshotPath}\`
27965
+ `;
27966
+ }
27967
+ output += `- **Captured At**: ${feedback.capturedAt}
27968
+
27969
+ `;
27970
+ output += `### Summary
27971
+ ${feedback.overallSummary}
27972
+
27973
+ `;
27974
+ if (feedback.annotations.length > 0) {
27975
+ output += `### Annotated Elements & Notes
27976
+
27977
+ `;
27978
+ feedback.annotations.forEach((item, idx) => {
27979
+ output += `${idx + 1}. **${item.label}**`;
27980
+ if (item.severity) {
27981
+ output += ` [${item.severity.toUpperCase()}]`;
27982
+ }
27983
+ output += `
27984
+ - **Note**: ${item.note}
27985
+ `;
27986
+ if (item.selector) {
27987
+ output += ` - **Selector**: \`${item.selector}\`
27988
+ `;
27989
+ }
27990
+ if (item.boundingBox) {
27991
+ output += ` - **Region (Box)**: x=${item.boundingBox.x}, y=${item.boundingBox.y}, w=${item.boundingBox.width}, h=${item.boundingBox.height}
27992
+ `;
27993
+ }
27994
+ });
27995
+ output += `
27996
+ `;
27997
+ }
27998
+ if (feedback.requestedChanges.length > 0) {
27999
+ output += `### Actionable Change List
28000
+
28001
+ `;
28002
+ feedback.requestedChanges.forEach((change, idx) => {
28003
+ output += `- [ ] ${change}
28004
+ `;
28005
+ });
28006
+ }
28007
+ return output;
28008
+ }
28009
+ var SteelClient = class {
28010
+ config;
28011
+ activeSessions = /* @__PURE__ */ new Map();
28012
+ constructor(config) {
28013
+ this.config = resolveSteelConfig(config);
28014
+ }
28015
+ getConfig() {
28016
+ return { ...this.config };
28017
+ }
28018
+ headers() {
28019
+ const h = {
28020
+ "Content-Type": "application/json"
28021
+ };
28022
+ if (this.config.apiKey) {
28023
+ h["x-steel-api-key"] = this.config.apiKey;
28024
+ }
28025
+ return h;
28026
+ }
28027
+ /**
28028
+ * Launch a new Steel browser session on DOKS.
28029
+ */
28030
+ async createSession(options) {
28031
+ const timeoutMs = options?.timeoutMs ?? this.config.timeoutMs ?? 3e5;
28032
+ const body = {
28033
+ timeout: timeoutMs
28034
+ };
28035
+ const dimensions = options?.dimensions || (options?.viewportPreset ? resolveViewportDimensions(options.viewportPreset) : void 0);
28036
+ if (dimensions) {
28037
+ body.dimensions = dimensions;
28038
+ }
28039
+ if (options?.userAgent) {
28040
+ body.userAgent = options.userAgent;
28041
+ }
28042
+ if (options?.proxy) {
28043
+ body.proxy = options.proxy;
28044
+ }
28045
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions`, {
28046
+ method: "POST",
28047
+ headers: this.headers(),
28048
+ body: JSON.stringify(body)
28049
+ });
28050
+ if (!res.ok) {
28051
+ const errText = await res.text();
28052
+ throw new Error(`Failed to create Steel session (${res.status}): ${errText}`);
28053
+ }
28054
+ const data = await res.json();
28055
+ const session = {
28056
+ id: data.id,
28057
+ createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
28058
+ status: "live",
28059
+ state: "AGENT_CONTROL",
28060
+ websocketUrl: data.websocketUrl || "",
28061
+ debugUrl: data.debugUrl || "",
28062
+ debuggerUrl: data.debuggerUrl || "",
28063
+ sessionViewerUrl: data.sessionViewerUrl || `${this.config.uiUrl}?sessionId=${data.id}`,
28064
+ timeoutMs,
28065
+ lastActiveAt: Date.now(),
28066
+ activeController: "agent",
28067
+ userAgent: data.userAgent
28068
+ };
28069
+ this.activeSessions.set(session.id, session);
28070
+ return session;
28071
+ }
28072
+ /**
28073
+ * Get details of an existing session.
28074
+ */
28075
+ async getSession(sessionId) {
28076
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions/${encodeURIComponent(sessionId)}`, {
28077
+ method: "GET",
28078
+ headers: this.headers()
28079
+ });
28080
+ if (res.status === 404) {
28081
+ const cached = this.activeSessions.get(sessionId);
28082
+ if (cached) {
28083
+ cached.state = "EXPIRED";
28084
+ cached.status = "released";
28085
+ cached.activeController = "none";
28086
+ }
28087
+ return null;
28088
+ }
28089
+ if (!res.ok) {
28090
+ throw new Error(`Failed to fetch Steel session (${res.status})`);
28091
+ }
28092
+ const data = await res.json();
28093
+ const existing = this.activeSessions.get(sessionId);
28094
+ const session = {
28095
+ id: data.id,
28096
+ createdAt: data.createdAt,
28097
+ status: data.status,
28098
+ state: existing?.state ?? (data.status === "live" ? "AGENT_CONTROL" : "RELEASED"),
28099
+ websocketUrl: data.websocketUrl || existing?.websocketUrl || "",
28100
+ debugUrl: data.debugUrl || existing?.debugUrl || "",
28101
+ debuggerUrl: data.debuggerUrl || existing?.debuggerUrl || "",
28102
+ sessionViewerUrl: existing?.sessionViewerUrl || `${this.config.uiUrl}?sessionId=${data.id}`,
28103
+ timeoutMs: data.timeout || existing?.timeoutMs || 3e5,
28104
+ lastActiveAt: Date.now(),
28105
+ activeController: existing?.activeController ?? (data.status === "live" ? "agent" : "none"),
28106
+ userAgent: data.userAgent
28107
+ };
28108
+ this.activeSessions.set(session.id, session);
28109
+ return session;
28110
+ }
28111
+ /**
28112
+ * Request human takeover for MFA, login, or consent.
28113
+ * Pauses agent automation and sets state to HUMAN_CONTROL.
28114
+ */
28115
+ requestHumanTakeover(sessionId, reason) {
28116
+ const session = this.activeSessions.get(sessionId);
28117
+ if (!session) {
28118
+ throw new Error(`Session ${sessionId} not tracked or already released`);
28119
+ }
28120
+ if (session.state === "RELEASED" || session.state === "EXPIRED" || session.state === "FAILED") {
28121
+ throw new Error(`Cannot initiate takeover on session in state ${session.state}`);
28122
+ }
28123
+ session.state = "HUMAN_CONTROL";
28124
+ session.activeController = "human";
28125
+ session.takeoverReason = reason;
28126
+ session.lastActiveAt = Date.now();
28127
+ const takeoverUrl = `${this.config.uiUrl}?sessionId=${encodeURIComponent(sessionId)}`;
28128
+ const instructions = `[HUMAN TAKEOVER REQUIRED]
28129
+ Reason: ${reason}
28130
+ Session ID: ${session.id}
28131
+ Takeover URL: ${takeoverUrl}
28132
+
28133
+ Instructions for Operator:
28134
+ 1. Open the URL above to access the session UI.
28135
+ 2. Perform the required authentication / MFA / consent action.
28136
+ 3. Return to the terminal and signal completion. Automation is paused until you confirm.`;
28137
+ return {
28138
+ session,
28139
+ takeoverUrl,
28140
+ instructions
28141
+ };
28142
+ }
28143
+ /**
28144
+ * Signal that human takeover is complete.
28145
+ * Moves state to VERIFY_AUTHENTICATION before transitioning back to AGENT_CONTROL.
28146
+ */
28147
+ signalHumanComplete(sessionId) {
28148
+ const session = this.activeSessions.get(sessionId);
28149
+ if (!session) {
28150
+ throw new Error(`Session ${sessionId} not tracked or already released`);
28151
+ }
28152
+ if (session.state !== "HUMAN_CONTROL") {
28153
+ throw new Error(`Session ${sessionId} is not in HUMAN_CONTROL state (currently ${session.state})`);
28154
+ }
28155
+ session.state = "VERIFY_AUTHENTICATION";
28156
+ session.activeController = "agent";
28157
+ session.lastActiveAt = Date.now();
28158
+ return {
28159
+ session,
28160
+ state: session.state
28161
+ };
28162
+ }
28163
+ /**
28164
+ * Confirm authentication verification passed and restore AGENT_CONTROL.
28165
+ */
28166
+ confirmAuthenticationVerified(sessionId) {
28167
+ const session = this.activeSessions.get(sessionId);
28168
+ if (!session) {
28169
+ throw new Error(`Session ${sessionId} not tracked or already released`);
28170
+ }
28171
+ if (session.state !== "VERIFY_AUTHENTICATION") {
28172
+ throw new Error(`Session ${sessionId} is not in VERIFY_AUTHENTICATION state (currently ${session.state})`);
28173
+ }
28174
+ session.state = "AGENT_CONTROL";
28175
+ session.activeController = "agent";
28176
+ session.takeoverReason = void 0;
28177
+ session.lastActiveAt = Date.now();
28178
+ return session;
28179
+ }
28180
+ /**
28181
+ * Gracefully release a session.
28182
+ */
28183
+ async releaseSession(sessionId) {
28184
+ try {
28185
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions/${encodeURIComponent(sessionId)}/release`, {
28186
+ method: "POST",
28187
+ headers: this.headers()
28188
+ });
28189
+ const session = this.activeSessions.get(sessionId);
28190
+ if (session) {
28191
+ session.status = "released";
28192
+ session.state = "RELEASED";
28193
+ session.activeController = "none";
28194
+ }
28195
+ this.activeSessions.delete(sessionId);
28196
+ return res.ok;
28197
+ } catch {
28198
+ this.activeSessions.delete(sessionId);
28199
+ return false;
28200
+ }
28201
+ }
28202
+ /**
28203
+ * Perform a direct stateless scrape without manual session management.
28204
+ */
28205
+ async scrape(url) {
28206
+ const res = await fetch(`${this.config.apiUrl}/v1/scrape`, {
28207
+ method: "POST",
28208
+ headers: this.headers(),
28209
+ body: JSON.stringify({ url })
28210
+ });
28211
+ if (!res.ok) {
28212
+ const err = await res.text();
28213
+ throw new Error(`Scrape failed (${res.status}): ${err}`);
28214
+ }
28215
+ return res.json();
28216
+ }
28217
+ /**
28218
+ * Perform a direct screenshot action.
28219
+ */
28220
+ async screenshot(url, fullPage = false) {
28221
+ const res = await fetch(`${this.config.apiUrl}/v1/screenshot`, {
28222
+ method: "POST",
28223
+ headers: this.headers(),
28224
+ body: JSON.stringify({ url, fullPage })
28225
+ });
28226
+ if (!res.ok) {
28227
+ const err = await res.text();
28228
+ throw new Error(`Screenshot failed (${res.status}): ${err}`);
28229
+ }
28230
+ return res.json();
28231
+ }
28232
+ /**
28233
+ * Detect and list orphaned or timed-out active sessions.
28234
+ */
28235
+ async checkOrphanedSessions(maxIdleMs = 6e5) {
28236
+ const res = await fetch(`${this.config.apiUrl}/v1/sessions`, {
28237
+ method: "GET",
28238
+ headers: this.headers()
28239
+ });
28240
+ if (!res.ok) {
28241
+ return [];
28242
+ }
28243
+ const data = await res.json();
28244
+ const remoteSessions = data.sessions || [];
28245
+ const now = Date.now();
28246
+ const orphaned = [];
28247
+ for (const rs of remoteSessions) {
28248
+ if (rs.status === "live" || rs.status === "idle") {
28249
+ const tracked = this.activeSessions.get(rs.id);
28250
+ if (!tracked && rs.duration > maxIdleMs) {
28251
+ orphaned.push(rs.id);
28252
+ } else if (tracked && now - tracked.lastActiveAt > maxIdleMs && tracked.state !== "HUMAN_CONTROL") {
28253
+ orphaned.push(rs.id);
28254
+ }
28255
+ }
28256
+ }
28257
+ return orphaned;
28258
+ }
28259
+ };
28260
+
28261
+ // plugins/kxm/src/modes.ts
28262
+ var import_yaml6 = __toESM(require_dist(), 1);
28263
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
28264
+ import { join as join15 } from "node:path";
28265
+ var DEFAULT_MODES_CONFIG = Object.freeze({
28266
+ schema: "kxm.modes.v1",
28267
+ majorModes: {
28268
+ coder: {
28269
+ description: "First-pass implementation, bug fixing, and test authoring",
28270
+ baseTools: ["read", "edit", "write", "bash"],
28271
+ contextFiles: ["AGENTS.md"],
28272
+ thinkingLevel: "medium",
28273
+ model: "grok/grok-4.6"
28274
+ },
28275
+ planner: {
28276
+ description: "High-level architectural planning, scoping, and decomposition",
28277
+ baseTools: ["read", "grep", "find"],
28278
+ contextFiles: ["plans/implementation-plan.md"],
28279
+ thinkingLevel: "high",
28280
+ model: "claude/fable"
28281
+ },
28282
+ auditor: {
28283
+ description: "Security, compliance, and code quality verification",
28284
+ baseTools: ["read", "grep"],
28285
+ contextFiles: ["SECURITY.md"],
28286
+ thinkingLevel: "high",
28287
+ model: "codex/gpt-5.6-sol"
28288
+ },
28289
+ browser: {
28290
+ description: "Web application exploration, screenshotting, and UI testing",
28291
+ baseTools: ["read", "bash"],
28292
+ contextFiles: ["docs/browser-automation.md"],
28293
+ thinkingLevel: "medium",
28294
+ model: "grok/grok-4.6"
28295
+ }
28296
+ },
28297
+ domains: {
28298
+ git: {
28299
+ description: "Git version control operations",
28300
+ tools: ["git_status", "git_diff", "git_commit"],
28301
+ promptSnippet: "Follow git branch conventions; never commit directly to main."
28302
+ },
28303
+ k8s: {
28304
+ description: "Kubernetes cluster inspection and deployment",
28305
+ tools: ["kubectl_get", "kubectl_describe"],
28306
+ promptSnippet: "Target local dev cluster; verify namespaces before mutating."
28307
+ },
28308
+ database: {
28309
+ description: "Database queries and schema verification",
28310
+ tools: ["sqlite_query", "sqlite_schema"],
28311
+ promptSnippet: "Database is SQLite at .kxm/state/kxm.db; use read-only queries."
28312
+ },
28313
+ browser: {
28314
+ description: "Remote Steel browser sessions and visual testing",
28315
+ tools: ["steel_session", "steel_scrape", "steel_screenshot"],
28316
+ promptSnippet: "Use Steel on DOKS for browser automation; invoke takeover on MFA."
28317
+ }
28318
+ }
28319
+ });
28320
+ function estimateTokens(charCount) {
28321
+ return Math.ceil(charCount / 3.8);
28322
+ }
28323
+ function loadModesConfig(projectRoot) {
28324
+ if (!projectRoot) {
28325
+ return DEFAULT_MODES_CONFIG;
28326
+ }
28327
+ const modesPath = join15(projectRoot, ".kxm", "modes.yaml");
28328
+ if (!existsSync12(modesPath)) {
28329
+ return DEFAULT_MODES_CONFIG;
28330
+ }
28331
+ try {
28332
+ const raw = readFileSync10(modesPath, "utf8");
28333
+ const parsed = (0, import_yaml6.parse)(raw);
28334
+ if (parsed && parsed.schema === "kxm.modes.v1" && parsed.majorModes) {
28335
+ return {
28336
+ schema: "kxm.modes.v1",
28337
+ majorModes: { ...DEFAULT_MODES_CONFIG.majorModes, ...parsed.majorModes },
28338
+ domains: { ...DEFAULT_MODES_CONFIG.domains, ...parsed.domains }
28339
+ };
28340
+ }
28341
+ } catch {
28342
+ }
28343
+ return DEFAULT_MODES_CONFIG;
28344
+ }
28345
+ function resolveActiveMode(config, majorModeName = "coder", domainNames = []) {
28346
+ const major = config.majorModes[majorModeName] || config.majorModes["coder"] || DEFAULT_MODES_CONFIG.majorModes["coder"];
28347
+ const tools = new Set(major.baseTools);
28348
+ const contextFiles = new Set(major.contextFiles || []);
28349
+ const promptSnippets = [];
28350
+ const enabledDomains = [];
28351
+ if (config.domains) {
28352
+ for (const dName of domainNames) {
28353
+ const d = config.domains[dName.toLowerCase().trim()];
28354
+ if (d) {
28355
+ enabledDomains.push(dName.toLowerCase().trim());
28356
+ d.tools.forEach((t) => tools.add(t));
28357
+ (d.contextFiles || []).forEach((f) => contextFiles.add(f));
28358
+ if (d.promptSnippet) {
28359
+ promptSnippets.push(d.promptSnippet);
28360
+ }
28361
+ }
28362
+ }
28363
+ }
28364
+ return {
28365
+ majorMode: majorModeName,
28366
+ enabledDomains,
28367
+ tools: Array.from(tools),
28368
+ contextFiles: Array.from(contextFiles),
28369
+ promptSnippets,
28370
+ thinkingLevel: major.thinkingLevel,
28371
+ model: major.model || "grok/grok-4.6"
28372
+ };
28373
+ }
28374
+ function calculatePromptFootprint(resolved, projectRoot = process.cwd(), catalog) {
28375
+ const breakdown = [];
28376
+ const baseSystemPromptChars = 3200;
28377
+ breakdown.push({
28378
+ name: `Base System Prompt (${resolved.majorMode})`,
28379
+ chars: baseSystemPromptChars,
28380
+ estimatedTokens: estimateTokens(baseSystemPromptChars)
28381
+ });
28382
+ for (const relPath of resolved.contextFiles) {
28383
+ const fullPath = join15(projectRoot, relPath);
28384
+ let chars = 0;
28385
+ if (existsSync12(fullPath)) {
28386
+ try {
28387
+ chars = readFileSync10(fullPath, "utf8").length;
28388
+ } catch {
28389
+ chars = 0;
28390
+ }
28391
+ }
28392
+ if (chars === 0) {
28393
+ chars = 1500;
28394
+ }
28395
+ breakdown.push({
28396
+ name: `Context File: ${relPath}`,
28397
+ chars,
28398
+ estimatedTokens: estimateTokens(chars)
28399
+ });
28400
+ }
28401
+ if (resolved.promptSnippets.length > 0) {
28402
+ const snippetChars = resolved.promptSnippets.join("\n").length;
28403
+ breakdown.push({
28404
+ name: `Domain Prompts (${resolved.enabledDomains.join(", ")})`,
28405
+ chars: snippetChars,
28406
+ estimatedTokens: estimateTokens(snippetChars)
28407
+ });
28408
+ }
28409
+ const toolSchemaChars = resolved.tools.length * 650;
28410
+ breakdown.push({
28411
+ name: `Tool Schemas (${resolved.tools.length} active tools)`,
28412
+ chars: toolSchemaChars,
28413
+ estimatedTokens: estimateTokens(toolSchemaChars)
28414
+ });
28415
+ const totalChars = breakdown.reduce((sum, item) => sum + item.chars, 0);
28416
+ const totalTokens = breakdown.reduce((sum, item) => sum + item.estimatedTokens, 0);
28417
+ const maxContextWindow = 2e5;
28418
+ const contextWindowRatio = Math.round(totalTokens / maxContextWindow * 1e3) / 10;
28419
+ const cat = catalog || loadPriceCatalog(projectRoot);
28420
+ const rawModel = resolved.model || "grok/grok-4.6";
28421
+ const [prov, mod] = rawModel.includes("/") ? rawModel.split("/", 2) : [void 0, rawModel];
28422
+ const inputCost = cat ? calculateModelCost(cat, {
28423
+ model: mod || rawModel,
28424
+ ...prov !== void 0 ? { provider: prov } : {},
28425
+ tokensIn: totalTokens,
28426
+ tokensOut: 0,
28427
+ cacheReadTokens: 0,
28428
+ cacheWriteTokens: 0
28429
+ }) : void 0;
28430
+ const cacheReadCost = cat ? calculateModelCost(cat, {
28431
+ model: mod || rawModel,
28432
+ ...prov !== void 0 ? { provider: prov } : {},
28433
+ tokensIn: 0,
28434
+ tokensOut: 0,
28435
+ cacheReadTokens: totalTokens,
28436
+ cacheWriteTokens: 0
28437
+ }) : void 0;
28438
+ const outputEstimateCost = cat ? calculateModelCost(cat, {
28439
+ model: mod || rawModel,
28440
+ ...prov !== void 0 ? { provider: prov } : {},
28441
+ tokensIn: 0,
28442
+ tokensOut: 1e3,
28443
+ cacheReadTokens: 0,
28444
+ cacheWriteTokens: 0
28445
+ }) : void 0;
28446
+ return {
28447
+ majorMode: resolved.majorMode,
28448
+ enabledDomains: resolved.enabledDomains,
28449
+ model: resolved.model || "grok/grok-4.6",
28450
+ breakdown,
28451
+ totalChars,
28452
+ totalTokens,
28453
+ contextWindowRatio,
28454
+ projectedCost: {
28455
+ inputCostUsd: inputCost?.costUsd ?? null,
28456
+ cacheReadCostUsd: cacheReadCost?.costUsd ?? null,
28457
+ outputCostEstimateUsd: outputEstimateCost?.costUsd ?? null
28458
+ }
28459
+ };
28460
+ }
28461
+ function formatModesExplainReport(footprint) {
28462
+ const divider = "\u2550".repeat(60);
28463
+ const subDivider = "\u2500".repeat(60);
28464
+ let out = `
28465
+ ${divider}
28466
+ `;
28467
+ out += `KXM PRE-FLIGHT CONTEXT EXPLAIN
28468
+ `;
28469
+ out += `${divider}
28470
+ `;
28471
+ out += `Major Mode: ${footprint.majorMode}
28472
+ `;
28473
+ out += `Enabled Domains: ${footprint.enabledDomains.length > 0 ? footprint.enabledDomains.join(", ") : "(none)"}
28474
+ `;
28475
+ out += `Target Model: ${footprint.model}
28476
+
28477
+ `;
28478
+ out += `CONTEXT BREAKDOWN:
28479
+ `;
28480
+ for (const item of footprint.breakdown) {
28481
+ const padName = item.name.padEnd(38, " ");
28482
+ const tokenStr = `${item.estimatedTokens.toLocaleString()} tokens`.padStart(16, " ");
28483
+ out += ` \u2022 ${padName} ${tokenStr}
28484
+ `;
28485
+ }
28486
+ out += `${subDivider}
28487
+ `;
28488
+ const totalPad = `TOTAL PROMPT FOOTPRINT:`.padEnd(38, " ");
28489
+ const totalStr = `${footprint.totalTokens.toLocaleString()} tokens`.padStart(16, " ");
28490
+ out += ` ${totalPad} ${totalStr} (${footprint.contextWindowRatio}% of 200k window)
28491
+
28492
+ `;
28493
+ out += `PROJECTED COSTS (per turn):
28494
+ `;
28495
+ out += ` \u2022 Initial Turn Input Cost: ${footprint.projectedCost.inputCostUsd !== null ? `$${footprint.projectedCost.inputCostUsd.toFixed(4)}` : "unmetered/unknown"}
28496
+ `;
28497
+ out += ` \u2022 Subsequent Cache-Read Cost: ${footprint.projectedCost.cacheReadCostUsd !== null ? `$${footprint.projectedCost.cacheReadCostUsd.toFixed(4)} (approx 90% savings)` : "unmetered/unknown"}
28498
+ `;
28499
+ out += ` \u2022 Output Estimate (1k tokens): ${footprint.projectedCost.outputCostEstimateUsd !== null ? `$${footprint.projectedCost.outputCostEstimateUsd.toFixed(4)}` : "unmetered/unknown"}
28500
+ `;
28501
+ out += `${divider}
28502
+ `;
28503
+ return out;
28504
+ }
28505
+
28506
+ // plugins/kxm/src/ssh-remote.ts
28507
+ import { spawnSync as spawnSync3 } from "node:child_process";
28508
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync5, rmSync as rmSync2, statSync as statSync4 } from "node:fs";
28509
+ import { homedir as homedir2 } from "node:os";
28510
+ import { join as join16, resolve as resolve8 } from "node:path";
28511
+ var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
28512
+ var MAX_SSH_OUTPUT_LINES = 2e3;
28513
+ var DEFAULT_SOCKET_DIR = ".kxm/run/ssh-sockets";
28514
+ var DEFAULT_CONTROL_PERSIST = "10m";
28515
+ function truncateSshOutput(raw) {
28516
+ let text = raw;
28517
+ let truncated = false;
28518
+ if (Buffer.byteLength(text, "utf-8") > MAX_SSH_OUTPUT_BYTES) {
28519
+ const buf = Buffer.from(text, "utf-8");
28520
+ text = buf.subarray(0, MAX_SSH_OUTPUT_BYTES).toString("utf-8");
28521
+ truncated = true;
28522
+ }
28523
+ const lines = text.split("\n");
28524
+ if (lines.length > MAX_SSH_OUTPUT_LINES) {
28525
+ text = lines.slice(0, MAX_SSH_OUTPUT_LINES).join("\n");
28526
+ truncated = true;
28527
+ }
28528
+ if (truncated) {
28529
+ text += `
28530
+
28531
+ [kxm: ssh output truncated to ${MAX_SSH_OUTPUT_BYTES / 1024}KB / ${MAX_SSH_OUTPUT_LINES} lines]`;
28532
+ }
28533
+ return { text, truncated };
28534
+ }
28535
+ function parseSshConfig(configPath) {
28536
+ const targetPath = configPath ?? join16(homedir2(), ".ssh", "config");
28537
+ if (!existsSync13(targetPath)) {
28538
+ return [];
28539
+ }
28540
+ try {
28541
+ const content = readFileSync11(targetPath, "utf-8");
28542
+ const lines = content.split("\n");
28543
+ const hosts = [];
28544
+ let currentHosts = [];
28545
+ for (const rawLine of lines) {
28546
+ const line = rawLine.trim();
28547
+ if (!line || line.startsWith("#")) continue;
28548
+ const spaceIdx = line.search(/\s/);
28549
+ if (spaceIdx === -1) continue;
28550
+ const key = line.slice(0, spaceIdx).trim().toLowerCase();
28551
+ const value = line.slice(spaceIdx + 1).trim();
28552
+ if (key === "host") {
28553
+ const aliases = value.split(/\s+/).filter((a) => a !== "*");
28554
+ currentHosts = [];
28555
+ for (const alias of aliases) {
28556
+ const entry = { alias, identityFiles: [] };
28557
+ currentHosts.push(entry);
28558
+ hosts.push(entry);
28559
+ }
28560
+ } else if (currentHosts.length > 0) {
28561
+ for (const currentHost of currentHosts) {
28562
+ if (key === "hostname") {
28563
+ currentHost.hostName = value;
28564
+ } else if (key === "user") {
28565
+ currentHost.user = value;
28566
+ } else if (key === "port") {
28567
+ const p = Number.parseInt(value, 10);
28568
+ if (!Number.isNaN(p)) currentHost.port = p;
28569
+ } else if (key === "proxyjump") {
28570
+ currentHost.proxyJump = value;
28571
+ } else if (key === "identityfile") {
28572
+ currentHost.identityFiles = currentHost.identityFiles ?? [];
28573
+ currentHost.identityFiles.push(value);
28574
+ }
28575
+ }
28576
+ }
28577
+ }
28578
+ return hosts;
28579
+ } catch {
28580
+ return [];
28581
+ }
28582
+ }
28583
+ function resolveSshHostG(host, execFn = spawnSync3) {
28584
+ try {
28585
+ const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
28586
+ if (result.status !== 0 || !result.stdout) {
28587
+ return { alias: host, hostName: host };
28588
+ }
28589
+ const lines = String(result.stdout).split("\n");
28590
+ const info = { alias: host, identityFiles: [] };
28591
+ for (const rawLine of lines) {
28592
+ const line = rawLine.trim();
28593
+ if (!line) continue;
28594
+ const spaceIdx = line.indexOf(" ");
28595
+ if (spaceIdx === -1) continue;
28596
+ const key = line.slice(0, spaceIdx).toLowerCase();
28597
+ const value = line.slice(spaceIdx + 1).trim();
28598
+ if (key === "hostname") {
28599
+ info.hostName = value;
28600
+ } else if (key === "user") {
28601
+ info.user = value;
28602
+ } else if (key === "port") {
28603
+ const p = Number.parseInt(value, 10);
28604
+ if (!Number.isNaN(p)) info.port = p;
28605
+ } else if (key === "proxyjump") {
28606
+ if (value && value !== "none") info.proxyJump = value;
28607
+ } else if (key === "identityfile") {
28608
+ info.identityFiles = info.identityFiles ?? [];
28609
+ info.identityFiles.push(value);
28610
+ }
28611
+ }
28612
+ return info;
28613
+ } catch {
28614
+ return { alias: host, hostName: host };
28615
+ }
28616
+ }
28617
+ function ensureSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
28618
+ const resolved = resolve8(socketDir);
28619
+ if (!existsSync13(resolved)) {
28620
+ mkdirSync7(resolved, { recursive: true, mode: 448 });
28621
+ }
28622
+ return resolved;
28623
+ }
28624
+ function buildSshArgs(options) {
28625
+ const socketDir = ensureSocketDir(options.socketDir ?? DEFAULT_SOCKET_DIR);
28626
+ const controlPath = join16(socketDir, "%C");
28627
+ const persist2 = options.controlPersist ?? DEFAULT_CONTROL_PERSIST;
28628
+ const args = [
28629
+ "-o",
28630
+ `ControlMaster=auto`,
28631
+ "-o",
28632
+ `ControlPath=${controlPath}`,
28633
+ "-o",
28634
+ `ControlPersist=${persist2}`
28635
+ ];
28636
+ if (options.batchMode !== false) {
28637
+ args.push("-o", "BatchMode=yes");
28638
+ }
28639
+ if (options.strictHostKey !== false) {
28640
+ args.push("-o", "StrictHostKeyChecking=yes");
28641
+ }
28642
+ if (options.extraArgs) {
28643
+ args.push(...options.extraArgs);
28644
+ }
28645
+ assertPinnedSshHostKeyPolicy(args);
28646
+ args.push(options.host);
28647
+ return args;
28648
+ }
28649
+ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync3) {
28650
+ const resolvedDir = ensureSocketDir(socketDir);
28651
+ const controlPath = join16(resolvedDir, "%C");
28652
+ try {
28653
+ const result = execFn("ssh", ["-O", "check", "-o", `ControlPath=${controlPath}`, host], {
28654
+ encoding: "utf-8"
28655
+ });
28656
+ return result.status === 0;
28657
+ } catch {
28658
+ return false;
28659
+ }
28660
+ }
28661
+ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync3) {
28662
+ const resolvedDir = ensureSocketDir(socketDir);
28663
+ const controlPath = join16(resolvedDir, "%C");
28664
+ try {
28665
+ const result = execFn("ssh", ["-O", "stop", "-o", `ControlPath=${controlPath}`, host], {
28666
+ encoding: "utf-8"
28667
+ });
28668
+ return result.status === 0;
28669
+ } catch {
28670
+ return false;
28671
+ }
28672
+ }
28673
+ function pruneSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
28674
+ const resolvedDir = ensureSocketDir(socketDir);
28675
+ let removed = 0;
28676
+ try {
28677
+ const entries = readdirSync5(resolvedDir);
28678
+ for (const entry of entries) {
28679
+ const fullPath = join16(resolvedDir, entry);
28680
+ try {
28681
+ const stat = statSync4(fullPath);
28682
+ if (stat.isSocket()) {
28683
+ rmSync2(fullPath, { force: true });
28684
+ removed++;
28685
+ }
28686
+ } catch {
28687
+ }
28688
+ }
28689
+ } catch {
28690
+ }
28691
+ return removed;
28692
+ }
28693
+ function executeSshRun(params) {
28694
+ const startTime = Date.now();
28695
+ const execSyncFn = params.execFn ?? spawnSync3;
28696
+ if (params.action === "info") {
28697
+ if (params.host) {
28698
+ const hostInfo = resolveSshHostG(params.host, execSyncFn);
28699
+ return {
28700
+ ok: true,
28701
+ action: "info",
28702
+ host: params.host,
28703
+ resolvedHost: hostInfo.hostName ?? params.host,
28704
+ user: hostInfo.user,
28705
+ port: hostInfo.port,
28706
+ hosts: [hostInfo],
28707
+ durationMs: Date.now() - startTime
28708
+ };
28709
+ }
28710
+ const discovered = parseSshConfig();
28711
+ return {
28712
+ ok: true,
28713
+ action: "info",
28714
+ hosts: discovered,
28715
+ durationMs: Date.now() - startTime
28716
+ };
28717
+ }
28718
+ if (!params.host) {
28719
+ return {
28720
+ ok: false,
28721
+ action: params.action,
28722
+ error: 'Parameter "host" is required for command and file actions.',
28723
+ durationMs: Date.now() - startTime
28724
+ };
28725
+ }
28726
+ const host = params.host;
28727
+ const socketDir = params.socket_dir ?? DEFAULT_SOCKET_DIR;
28728
+ const wasSocketActive = checkControlSocket(host, socketDir, execSyncFn);
28729
+ if (params.action === "command") {
28730
+ if (!params.command) {
28731
+ return {
28732
+ ok: false,
28733
+ action: "command",
28734
+ host,
28735
+ error: 'Parameter "command" is required for command action.',
28736
+ durationMs: Date.now() - startTime
28737
+ };
28738
+ }
28739
+ try {
28740
+ assertCommandSeatbelt(params.command);
28741
+ } catch (err) {
28742
+ return {
28743
+ ok: false,
28744
+ action: "command",
28745
+ host,
28746
+ error: err.message,
28747
+ durationMs: Date.now() - startTime
28748
+ };
28749
+ }
28750
+ let remoteCommand = params.command;
28751
+ let inputStdin = void 0;
28752
+ if (params.sudo) {
28753
+ const sudoPwd = params.sudo_password ?? params.password;
28754
+ if (sudoPwd) {
28755
+ inputStdin = `${sudoPwd}
28756
+ `;
28757
+ remoteCommand = `sudo -S -p '' -- sh -c ${JSON.stringify(params.command)}`;
28758
+ } else {
28759
+ remoteCommand = `sudo -- sh -c ${JSON.stringify(params.command)}`;
28760
+ }
28761
+ }
28762
+ const useSshPass = Boolean(params.password);
28763
+ const sshArgs = buildSshArgs({
28764
+ host,
28765
+ socketDir,
28766
+ controlPersist: params.control_persist,
28767
+ batchMode: !useSshPass,
28768
+ strictHostKey: params.strict_host_key
28769
+ });
28770
+ sshArgs.push("--", remoteCommand);
28771
+ const spawnEnv = { ...process.env };
28772
+ let binary = "ssh";
28773
+ let finalArgs = sshArgs;
28774
+ if (useSshPass && params.password) {
28775
+ binary = "sshpass";
28776
+ finalArgs = ["-e", "ssh", ...sshArgs];
28777
+ spawnEnv.SSHPASS = params.password;
28778
+ }
28779
+ try {
28780
+ const result = execSyncFn(binary, finalArgs, {
28781
+ encoding: "utf-8",
28782
+ env: spawnEnv,
28783
+ input: inputStdin,
28784
+ timeout: params.timeout_ms ?? 12e4,
28785
+ maxBuffer: 10 * 1024 * 1024
28786
+ });
28787
+ const stdoutRaw = String(result.stdout || "");
28788
+ const stderrRaw = String(result.stderr || "");
28789
+ const sanitizedStderr = stderrRaw.replace(/\[sudo\] password for [^:]+:\s*/gi, "");
28790
+ const stdoutTrunc = truncateSshOutput(stdoutRaw);
28791
+ const stderrTrunc = truncateSshOutput(sanitizedStderr);
28792
+ return {
28793
+ ok: result.status === 0,
28794
+ action: "command",
28795
+ host,
28796
+ exitCode: result.status ?? 1,
28797
+ stdout: stdoutTrunc.text,
28798
+ stderr: stderrTrunc.text,
28799
+ truncated: stdoutTrunc.truncated || stderrTrunc.truncated,
28800
+ socketReused: wasSocketActive,
28801
+ durationMs: Date.now() - startTime,
28802
+ error: result.error ? result.error.message : void 0
28803
+ };
28804
+ } catch (err) {
28805
+ return {
28806
+ ok: false,
28807
+ action: "command",
28808
+ host,
28809
+ error: err.message,
28810
+ durationMs: Date.now() - startTime
28811
+ };
28812
+ }
28813
+ }
28814
+ if (params.action === "file") {
28815
+ if (!params.file_path) {
28816
+ return {
28817
+ ok: false,
28818
+ action: "file",
28819
+ host,
28820
+ error: 'Parameter "file_path" is required for file action.',
28821
+ durationMs: Date.now() - startTime
28822
+ };
28823
+ }
28824
+ const filePath = params.file_path;
28825
+ const op = params.file_op ?? "write";
28826
+ if (op === "read") {
28827
+ const readCmd = params.sudo ? `sudo cat ${JSON.stringify(filePath)}` : `cat ${JSON.stringify(filePath)}`;
28828
+ return executeSshRun({
28829
+ ...params,
28830
+ action: "command",
28831
+ command: readCmd
28832
+ });
28833
+ }
28834
+ const content = params.file_content ?? "";
28835
+ const redirectOp = op === "append" ? ">>" : ">";
28836
+ const writeCmd = params.sudo ? `sudo tee ${op === "append" ? "-a " : ""}${JSON.stringify(filePath)} > /dev/null` : `cat ${redirectOp} ${JSON.stringify(filePath)}`;
28837
+ const useSshPass = Boolean(params.password);
28838
+ const sshArgs = buildSshArgs({
28839
+ host,
28840
+ socketDir,
28841
+ controlPersist: params.control_persist,
28842
+ batchMode: !useSshPass,
28843
+ strictHostKey: params.strict_host_key
28844
+ });
28845
+ sshArgs.push("--", writeCmd);
28846
+ const spawnEnv = { ...process.env };
28847
+ let binary = "ssh";
28848
+ let finalArgs = sshArgs;
28849
+ if (useSshPass && params.password) {
28850
+ binary = "sshpass";
28851
+ finalArgs = ["-e", "ssh", ...sshArgs];
28852
+ spawnEnv.SSHPASS = params.password;
28853
+ }
28854
+ try {
28855
+ const result = execSyncFn(binary, finalArgs, {
28856
+ encoding: "utf-8",
28857
+ env: spawnEnv,
28858
+ input: content,
28859
+ timeout: params.timeout_ms ?? 6e4
28860
+ });
28861
+ return {
28862
+ ok: result.status === 0,
28863
+ action: "file",
28864
+ host,
28865
+ exitCode: result.status ?? 1,
28866
+ stdout: String(result.stdout || ""),
28867
+ stderr: String(result.stderr || ""),
28868
+ fileProcessed: filePath,
28869
+ bytesProcessed: Buffer.byteLength(content, "utf-8"),
28870
+ socketReused: wasSocketActive,
28871
+ durationMs: Date.now() - startTime,
28872
+ error: result.error ? result.error.message : void 0
28873
+ };
28874
+ } catch (err) {
28875
+ return {
28876
+ ok: false,
28877
+ action: "file",
28878
+ host,
28879
+ error: err.message,
28880
+ durationMs: Date.now() - startTime
28881
+ };
28882
+ }
28883
+ }
28884
+ return {
28885
+ ok: false,
28886
+ action: params.action,
28887
+ error: `Unknown action: ${params.action}`,
28888
+ durationMs: Date.now() - startTime
28889
+ };
28890
+ }
28891
+
28892
+ // plugins/kxm/src/subagent-control.ts
28893
+ import { randomUUID as randomUUID5 } from "node:crypto";
28894
+ var SUBAGENT_TYPES = Object.freeze([
28895
+ "general",
28896
+ "Explore",
28897
+ "Plan",
28898
+ "Reviewer",
28899
+ "Auditor"
28900
+ ]);
28901
+ var DEFAULT_SUBAGENT_MODELS = Object.freeze({
28902
+ general: "grok/grok-4.6",
28903
+ Explore: "openrouter/qwen/qwen3-coder-plus",
28904
+ Plan: "claude/fable",
28905
+ Reviewer: "openai/gpt-5.6-sol",
28906
+ Auditor: "claude/fable"
28907
+ });
28908
+ function narrowSubagentTools(requestedTools, parentTools) {
28909
+ if (!requestedTools || requestedTools.length === 0) {
28910
+ return parentTools ? [...parentTools] : ["read", "grep", "find"];
28911
+ }
28912
+ if (!parentTools || parentTools.length === 0) {
28913
+ return [...requestedTools];
28914
+ }
28915
+ const parentSet = new Set(parentTools);
28916
+ const narrowed = [];
28917
+ for (const tool of requestedTools) {
28918
+ if (parentSet.has(tool)) {
28919
+ narrowed.push(tool);
28920
+ }
28921
+ }
28922
+ if (narrowed.length === 0 && requestedTools.length > 0) {
28923
+ throw new Error(
28924
+ `Cannot spawn subagent: requested tools [${requestedTools.join(", ")}] expand beyond parent capabilities.`
28925
+ );
28926
+ }
28927
+ return narrowed;
28928
+ }
28929
+ var SubagentManager = class {
28930
+ subagents = /* @__PURE__ */ new Map();
28931
+ /**
28932
+ * Spawns a new managed subagent with strict capability boundaries.
28933
+ */
28934
+ spawn(params) {
28935
+ if (!params.prompt || !params.prompt.trim()) {
28936
+ throw new Error('Subagent "prompt" is required.');
28937
+ }
28938
+ if (!params.description || !params.description.trim()) {
28939
+ throw new Error('Subagent "description" is required.');
28940
+ }
28941
+ const type = params.type ?? "general";
28942
+ const model = params.model ?? DEFAULT_SUBAGENT_MODELS[type] ?? "grok/grok-4.6";
28943
+ const thinking = params.thinking ?? "low";
28944
+ const allowedTools = narrowSubagentTools(params.allowed_tools, params.parent_tools);
28945
+ const id = `ag_${randomUUID5().replaceAll("-", "").slice(0, 10)}`;
28946
+ const now = (/* @__PURE__ */ new Date()).toISOString();
28947
+ const record2 = {
28948
+ id,
28949
+ description: params.description.trim(),
28950
+ type,
28951
+ model,
28952
+ thinking,
28953
+ allowedTools,
28954
+ status: params.background === false ? "completed" : "running",
28955
+ turnsMax: params.turns ?? 10,
28956
+ turnsCompleted: 0,
28957
+ prompt: params.prompt.trim(),
28958
+ createdAt: now,
28959
+ updatedAt: now,
28960
+ steeringMessages: []
28961
+ };
28962
+ this.subagents.set(id, record2);
28963
+ return record2;
28964
+ }
28965
+ /**
28966
+ * Dispatches an action from the `agent_control` tool.
28967
+ */
28968
+ control(params) {
28969
+ const action = params.action;
28970
+ if (action === "info") {
28971
+ const kind = params.kind ?? "active";
28972
+ if (kind === "types") {
28973
+ return {
28974
+ ok: true,
28975
+ action: "info",
28976
+ types: SUBAGENT_TYPES
28977
+ };
28978
+ }
28979
+ if (kind === "models") {
28980
+ return {
28981
+ ok: true,
28982
+ action: "info",
28983
+ result: JSON.stringify(DEFAULT_SUBAGENT_MODELS)
28984
+ };
28985
+ }
28986
+ const active = Array.from(this.subagents.values()).map((ag) => ({
28987
+ id: ag.id,
28988
+ description: ag.description,
28989
+ type: ag.type,
28990
+ status: ag.status
28991
+ }));
28992
+ return {
28993
+ ok: true,
28994
+ action: "info",
28995
+ activeAgents: active
28996
+ };
28997
+ }
28998
+ if (!params.agent_id) {
28999
+ return {
29000
+ ok: false,
29001
+ action,
29002
+ error: 'Parameter "agent_id" is required for result, steer, and stop actions.'
29003
+ };
29004
+ }
29005
+ const agent = this.subagents.get(params.agent_id);
29006
+ if (!agent) {
29007
+ return {
29008
+ ok: false,
29009
+ action,
29010
+ agent_id: params.agent_id,
29011
+ error: `Subagent "${params.agent_id}" not found.`
29012
+ };
29013
+ }
29014
+ if (action === "result") {
29015
+ return {
29016
+ ok: true,
29017
+ action: "result",
29018
+ agent_id: agent.id,
29019
+ status: agent.status,
29020
+ result: agent.output ?? `[Subagent is currently ${agent.status}]`
29021
+ };
29022
+ }
29023
+ if (action === "steer") {
29024
+ if (!params.message || !params.message.trim()) {
29025
+ return {
29026
+ ok: false,
29027
+ action: "steer",
29028
+ agent_id: agent.id,
29029
+ error: 'Parameter "message" is required for steer action.'
29030
+ };
29031
+ }
29032
+ if (agent.status === "completed" || agent.status === "stopped" || agent.status === "failed") {
29033
+ return {
29034
+ ok: false,
29035
+ action: "steer",
29036
+ agent_id: agent.id,
29037
+ error: `Cannot steer subagent "${agent.id}" because it is already ${agent.status}.`
29038
+ };
29039
+ }
29040
+ agent.steeringMessages.push({
29041
+ text: params.message.trim(),
29042
+ injectedAt: (/* @__PURE__ */ new Date()).toISOString(),
29043
+ processed: false
29044
+ });
29045
+ agent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
29046
+ return {
29047
+ ok: true,
29048
+ action: "steer",
29049
+ agent_id: agent.id,
29050
+ status: agent.status,
29051
+ steered: true
29052
+ };
29053
+ }
29054
+ if (action === "stop") {
29055
+ agent.status = "stopped";
29056
+ agent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
29057
+ return {
29058
+ ok: true,
29059
+ action: "stop",
29060
+ agent_id: agent.id,
29061
+ status: "stopped",
29062
+ stopped: true
29063
+ };
29064
+ }
29065
+ return {
29066
+ ok: false,
29067
+ action,
29068
+ error: `Unknown control action "${action}".`
29069
+ };
29070
+ }
29071
+ /**
29072
+ * Retrieves an agent record by ID.
29073
+ */
29074
+ get(agentId) {
29075
+ return this.subagents.get(agentId);
29076
+ }
29077
+ /**
29078
+ * Marks a subagent as completed with output.
29079
+ */
29080
+ complete(agentId, output) {
29081
+ const agent = this.subagents.get(agentId);
29082
+ if (agent) {
29083
+ agent.status = "completed";
29084
+ agent.output = output;
29085
+ agent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
29086
+ }
29087
+ }
29088
+ /**
29089
+ * Returns all active running subagents.
29090
+ */
29091
+ listActive() {
29092
+ return Array.from(this.subagents.values()).filter(
29093
+ (ag) => ag.status === "running" || ag.status === "pending" || ag.status === "blocked"
29094
+ );
29095
+ }
29096
+ };
27630
29097
  export {
27631
29098
  BUILTIN_HARNESSES,
27632
29099
  BUILTIN_HARNESS_IDS,
27633
29100
  CANDIDATE_SCHEMA,
29101
+ DEFAULT_CONTROL_PERSIST,
27634
29102
  DEFAULT_HARNESS,
27635
29103
  DEFAULT_LOG_MAX_BYTES,
27636
29104
  DEFAULT_LOG_MAX_FILES,
29105
+ DEFAULT_MODES_CONFIG,
29106
+ DEFAULT_SOCKET_DIR,
29107
+ DEFAULT_SUBAGENT_MODELS,
27637
29108
  IMPROVEMENT_REPORT_SCHEMA,
27638
29109
  IMPROVEMENT_REPORT_V1_SCHEMA,
27639
29110
  LOG_LEVEL_PRIORITY,
29111
+ MAX_SSH_OUTPUT_BYTES,
29112
+ MAX_SSH_OUTPUT_LINES,
27640
29113
  NATIVE_HARNESS_PROVIDERS,
27641
29114
  PI_ALLOWED_PROVIDERS,
27642
29115
  PI_NATIVE_BRAKE_PROVIDERS,
27643
29116
  PiSession,
27644
29117
  SAFE_HARNESS_COMMAND_ID,
29118
+ SUBAGENT_TYPES,
29119
+ SteelClient,
29120
+ SubagentManager,
29121
+ VIEWPORT_PRESETS,
27645
29122
  VNEXT_ABSENT_MEMORY_REVISION,
27646
29123
  VNEXT_EVENT_STORE_SCHEMA_VERSION,
27647
29124
  VNEXT_REGISTRY_SCHEMA_VERSION,
@@ -27654,15 +29131,20 @@ export {
27654
29131
  assertVnextConfigError,
27655
29132
  backupDatabaseFile,
27656
29133
  buildImprovementReport,
29134
+ buildSshArgs,
29135
+ calculatePromptFootprint,
27657
29136
  cancelVnextRun,
29137
+ checkControlSocket,
27658
29138
  checkIntegrity,
27659
29139
  checkMemoryRevisionDrift,
27660
29140
  checkedParent,
27661
29141
  checkpointWal,
27662
29142
  classifyCandidateKind,
29143
+ closeControlSocket,
27663
29144
  closeVnextRuntimeContext,
27664
29145
  computeGateEvidenceOutcome,
27665
29146
  computeVnextMemoryRevision,
29147
+ createAnnotationFeedback,
27666
29148
  createBackup,
27667
29149
  createLogger,
27668
29150
  createVnextOneShotProducer,
@@ -27671,15 +29153,21 @@ export {
27671
29153
  defaultSpawn,
27672
29154
  discoverProjectStores,
27673
29155
  eligibleHarnesses,
29156
+ ensureSocketDir,
27674
29157
  ensureVnextSupervisor,
27675
29158
  ensureWalJournalMode,
29159
+ estimateTokens,
27676
29160
  evaluatePromotionPolicy,
29161
+ executeSshRun,
27677
29162
  fileSha256,
27678
29163
  findWinNpmInnerExe,
27679
29164
  foldStoredVnextRun,
29165
+ formatAnnotationFeedbackPrompt,
29166
+ formatCDPEndpoint,
27680
29167
  formatHarnessInventory,
27681
29168
  formatHarnessUpdate,
27682
29169
  formatImprovementReport,
29170
+ formatModesExplainReport,
27683
29171
  formatPiSessionDisplayName,
27684
29172
  formatPiSessionKey,
27685
29173
  gateRowContentHash,
@@ -27691,6 +29179,8 @@ export {
27691
29179
  isKnownHarnessId,
27692
29180
  isVnextRuntimeContextClosed,
27693
29181
  isWindowsHarnessShim,
29182
+ loadModesConfig,
29183
+ narrowSubagentTools,
27694
29184
  newVnextAssignmentId,
27695
29185
  newVnextAttemptId,
27696
29186
  newVnextCommandId,
@@ -27708,26 +29198,37 @@ export {
27708
29198
  parseGenericOneShotUsage,
27709
29199
  parseGrokOneShotUsage,
27710
29200
  parseKimiOneShotUsage,
29201
+ parseSshConfig,
27711
29202
  persistVnextRunState,
27712
29203
  planHarnessUpdate,
27713
29204
  probeHarnessAssignment,
27714
29205
  probeHarnessAssignmentAsync,
27715
29206
  probeHarnesses,
29207
+ probeHarnessesAsync,
27716
29208
  probeHarnessesForModel,
29209
+ probeHarnessesForModelAsync,
27717
29210
  projectRuntimeKey,
29211
+ pruneSocketDir,
27718
29212
  readVnextRunStatus,
27719
29213
  readVnextSupervisorToken,
27720
29214
  rebuildVnextRunProjection,
27721
29215
  redactLogValue,
27722
29216
  registerVnextRuntimeCloseHook,
29217
+ resolveActiveMode,
27723
29218
  resolveDispatchStatus,
29219
+ resolvePassCliApiKey,
29220
+ resolveSshHostG,
29221
+ resolveSteelConfig,
29222
+ resolveViewportDimensions,
27724
29223
  restoreBackup,
27725
29224
  restoreDatabaseFile,
27726
29225
  rotateLogFiles,
27727
29226
  runHarnessUpdate,
27728
29227
  runtimeError,
29228
+ sanitizeLogOutput,
27729
29229
  startVnextRuntimeSupervisor,
27730
29230
  tableColumns,
29231
+ truncateSshOutput,
27731
29232
  userTables,
27732
29233
  validateHarnessModelPair,
27733
29234
  verifyExpectedTables,