@kontextmind/kxm 0.7.128 → 0.7.131

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 (65) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/README.md +4 -1
  3. package/.kxm/agents/coordinator.yaml +1 -0
  4. package/.kxm/agents/critic-arch.yaml +1 -4
  5. package/.kxm/agents/critic-cli.yaml +1 -4
  6. package/.kxm/agents/implementer.yaml +1 -4
  7. package/.kxm/workflows/implement-only.yaml +1 -1
  8. package/.kxm/workflows/review-arch-only.yaml +1 -1
  9. package/.kxm/workflows/review-cli-only.yaml +1 -1
  10. package/CHANGELOG.md +42 -1
  11. package/docs/architecture/inventory.md +1 -1
  12. package/docs/concepts/architecture.md +1 -1
  13. package/docs/concepts/data-and-storage.md +1 -1
  14. package/docs/contracts/README.md +1 -1
  15. package/docs/contracts/migration.md +12 -8
  16. package/docs/contracts/routing.md +3 -3
  17. package/docs/contributing/assignment-runner.md +14 -15
  18. package/docs/contributing/development.md +22 -6
  19. package/docs/contributing/harness-routing-internals.md +8 -8
  20. package/docs/glossary.md +1 -1
  21. package/docs/guides/agent-skills.md +1 -1
  22. package/docs/operations/backup-and-restore.md +7 -7
  23. package/docs/operations/upgrade.md +2 -2
  24. package/docs/reference/cli-reference.md +37 -14
  25. package/docs/reference/config-reference.md +81 -155
  26. package/docs/reference/harness-routing.md +29 -21
  27. package/docs/reference/workflow-catalog.md +4 -4
  28. package/docs/start/first-workflow.md +1 -1
  29. package/examples/project/.kxm/agents/coordinator.yaml +1 -2
  30. package/examples/project/.kxm/agents/critic-1.yaml +1 -3
  31. package/examples/project/.kxm/agents/critic-2.yaml +1 -2
  32. package/examples/project/.kxm/agents/critic-3.yaml +2 -3
  33. package/examples/project/.kxm/agents/implementer.yaml +1 -2
  34. package/examples/project/.kxm/agents/planner.yaml +1 -2
  35. package/examples/project/.kxm/agents/reproducer.yaml +1 -2
  36. package/examples/project/.kxm/agents/reviewer.yaml +1 -2
  37. package/examples/project/.kxm/workflows/fix.yaml +0 -12
  38. package/package.json +8 -8
  39. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  40. package/plugins/kxm/dist/cli.js +2565 -465
  41. package/plugins/kxm/dist/mcp-server.js +1 -1
  42. package/plugins/kxm/dist/runtime-supervisor.js +683 -348
  43. package/plugins/kxm/dist/runtime.js +694 -358
  44. package/plugins/kxm/dist/server.js +17 -14
  45. package/plugins/kxm/package.json +1 -1
  46. package/plugins/kxm/skills/kxm/SKILL.md +1 -1
  47. package/plugins/kxm/skills/kxm-definitions/SKILL.md +8 -0
  48. package/plugins/kxm/src/cli/lanes.ts +109 -4
  49. package/plugins/kxm/src/cli/project.ts +6 -6
  50. package/plugins/kxm/src/database.ts +25 -19
  51. package/plugins/kxm/src/engine.ts +168 -163
  52. package/plugins/kxm/src/init-guide-setup.ts +75 -15
  53. package/plugins/kxm/src/mcp-server.ts +1 -1
  54. package/plugins/kxm/src/oneshot-producer.ts +3 -15
  55. package/plugins/kxm/src/project-config.ts +83 -70
  56. package/plugins/kxm/src/routes.ts +26 -18
  57. package/plugins/kxm/src/runtime-service.ts +1 -0
  58. package/plugins/kxm/src/runtime-store.ts +390 -77
  59. package/plugins/kxm/src/runtime-supervisor.ts +87 -21
  60. package/plugins/kxm/src/suggest.ts +2 -2
  61. package/plugins/kxm/src/template.ts +2 -14
  62. package/schemas/agent.schema.json +8 -0
  63. package/scripts/roster-policy.d.mts +2 -1
  64. package/scripts/roster-policy.mjs +119 -22
  65. package/scripts/run-bounded.mjs +53 -0
@@ -15636,21 +15636,9 @@ function coreTemplate(projectId, projectName, variant) {
15636
15636
  ]);
15637
15637
  if (variant === "v4-registry") {
15638
15638
  const coordinator = files.get(".kxm/agents/coordinator.yaml");
15639
- if (coordinator) {
15640
- files.set(".kxm/agents/coordinator.yaml", {
15641
- ...coordinator,
15642
- harness: "claude",
15643
- model: { provider: "anthropic", model: "fable" }
15644
- });
15645
- }
15639
+ if (coordinator) files.set(".kxm/agents/coordinator.yaml", { ...coordinator, role: "planner" });
15646
15640
  const implementer = files.get(".kxm/agents/implementer.yaml");
15647
- if (implementer) {
15648
- files.set(".kxm/agents/implementer.yaml", {
15649
- ...implementer,
15650
- harness: "grok",
15651
- model: { provider: "xai", model: "grok-4.6" }
15652
- });
15653
- }
15641
+ if (implementer) files.set(".kxm/agents/implementer.yaml", { ...implementer, role: "writer" });
15654
15642
  const workflow = files.get(".kxm/workflows/default.yaml");
15655
15643
  if (workflow) {
15656
15644
  const limits = { ...workflow.limits };
@@ -17486,7 +17474,7 @@ function readTemplateProvenance(registry, root) {
17486
17474
  }
17487
17475
  return value;
17488
17476
  }
17489
- function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId) {
17477
+ function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId, collected) {
17490
17478
  const resources = /* @__PURE__ */ new Map();
17491
17479
  if (!existsSync3(directory)) return resources;
17492
17480
  const stat = lstatSync(directory);
@@ -17525,7 +17513,10 @@ function listNamedResources(registry, root, directory, logicalDirectory, kind, r
17525
17513
  else throw error;
17526
17514
  }
17527
17515
  }
17528
- if (issues.length > 0) throw new KxmConfigError(issues);
17516
+ if (issues.length > 0) {
17517
+ if (collected) collected.push(...issues);
17518
+ else throw new KxmConfigError(issues);
17519
+ }
17529
17520
  return resources;
17530
17521
  }
17531
17522
  function valuesOf(object, field) {
@@ -17789,6 +17780,9 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
17789
17780
  for (const repositoryId of Object.keys(objectValue(step.repositories) ?? {})) {
17790
17781
  if (!repositories.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", file, `${stepId} references unknown repository ${repositoryId}`));
17791
17782
  }
17783
+ if ((kind === "agent" || kind === "moa") && step.model !== void 0) {
17784
+ issues.push(issue3("semantic", "producer_route_unsupported", file, `${stepId} model is not honored; remove model from the step`));
17785
+ }
17792
17786
  selectorCandidates(step.model, models, file, `${stepId}.model`, issues);
17793
17787
  const assignment = objectValue(step.assignments);
17794
17788
  const declaredAgents = assignment ? names(assignment.allowedAgents) : [];
@@ -17971,7 +17965,7 @@ function validateModelReferences(models, issues) {
17971
17965
  };
17972
17966
  for (const id of models.keys()) walk(id, []);
17973
17967
  }
17974
- function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot, writerRole) {
17968
+ function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot, roles = /* @__PURE__ */ new Map()) {
17975
17969
  const issues = [];
17976
17970
  const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
17977
17971
  const repositoryIds = /* @__PURE__ */ new Set();
@@ -18012,84 +18006,84 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
18012
18006
  const defaultHarness = stringValue(project.value.defaultHarness) ?? DEFAULT_HARNESS;
18013
18007
  if (!harnesses.has(defaultHarness)) issues.push(issue3("reference", "harness_unknown", project.logicalPath, `defaultHarness ${defaultHarness} is not registered`));
18014
18008
  for (const agent of agents.values()) {
18009
+ if (agent.value.model !== void 0 || agent.value.harness !== void 0) {
18010
+ issues.push(issue3("semantic", "retired_agent_routing_fields", agent.logicalPath, "routing resolves from role; remove model and harness"));
18011
+ }
18015
18012
  validateToolPolicy(agent, objectValue(agent.value.tools), "tools", issues);
18016
18013
  const executor = stringValue(agent.value.executor);
18017
18014
  if (executor && !executors.has(executor)) issues.push(issue3("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
18018
- const harness = stringValue(agent.value.harness) ?? defaultHarness;
18019
- if (!harnesses.has(harness)) issues.push(issue3("reference", "harness_unknown", agent.logicalPath, `harness ${harness} is not registered`));
18020
18015
  const preset = stringValue(objectValue(agent.value.tools)?.preset);
18021
18016
  if (preset && !presets.has(preset)) issues.push(issue3("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
18022
18017
  for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
18023
18018
  if (!repositoryIds.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
18024
18019
  }
18025
- const candidates = selectorCandidates(agent.value.model, models, agent.logicalPath, "model", issues);
18026
- const declaredHarness = stringValue(agent.value.harness);
18027
- if (declaredHarness && harnesses.has(declaredHarness)) {
18028
- for (const candidate of candidates) {
18029
- const candidateProvider = stringValue(candidate.value.provider);
18030
- const candidateModel = stringValue(candidate.value.model);
18031
- const validation = validateHarnessModelPair(declaredHarness, { provider: candidateProvider, model: candidateModel });
18032
- if (!validation.valid) {
18033
- issues.push(issue3("semantic", validation.issue ?? "harness_unhosted_model", agent.logicalPath, validation.message ?? `harness ${declaredHarness} cannot host model ${candidateModel ?? candidate.logicalPath}`));
18034
- }
18035
- }
18036
- }
18037
18020
  }
18038
18021
  validateModelReferences(models, issues);
18039
18022
  const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
18040
18023
  if (!workflows.has(defaultWorkflow)) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
18041
18024
  for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
18025
+ if (projectRoot && existsSync3(join2(projectRoot, ".kxm", "roster.yaml"))) {
18026
+ issues.push(issue3("semantic", "retired_roster_file", ".kxm/roster.yaml", "create the role and model files and delete roster.yaml"));
18027
+ }
18028
+ if (projectRoot && existsSync3(join2(projectRoot, ".kxm", "roles"))) {
18029
+ issues.push(...developerRolePolicyIssues(projectRoot));
18030
+ }
18042
18031
  if (projectRoot) {
18043
- const writerRolePath = join2(projectRoot, ".kxm", "roles", "writer.yaml");
18044
- const implementerAgent = agents.get("implementer") ?? agents.get("writer");
18045
- if ((writerRole !== void 0 || existsSync3(writerRolePath)) && implementerAgent) {
18032
+ issues.push(...rosterModelIssues(projectRoot, roles));
18033
+ issues.push(...originHashIssues(projectRoot, models));
18034
+ }
18035
+ return sortIssues3(issues);
18036
+ }
18037
+ function rosterModelIssues(projectRoot, roles) {
18038
+ const issues = [];
18039
+ for (const role of roles.values()) {
18040
+ for (const entry of valuesOf(role.value, "roster")) {
18041
+ const route = stringValue(objectValue(entry)?.route);
18042
+ if (!route) continue;
18043
+ const logical = `.kxm/models/${route}.yaml`;
18044
+ const file = join2(projectRoot, ".kxm", "models", `${route}.yaml`);
18045
+ let harness;
18046
18046
  try {
18047
- const rawRole = parseRestrictedYaml2(writerRole ?? readFileSync(writerRolePath, "utf8"));
18048
- const roleObj = objectValue(rawRole);
18049
- const rosterEntries = valuesOf(roleObj ?? {}, "roster").map((candidate) => objectValue(candidate)).filter((entry) => Boolean(entry));
18050
- const enabledRosterModels = rosterEntries.flatMap((entry) => {
18051
- const direct = stringValue(entry.model);
18052
- const route = stringValue(entry.route);
18053
- const named = direct ? [direct] : [];
18054
- if (route) {
18055
- const modelFile = join2(projectRoot, ".kxm", "models", `${route}.yaml`);
18056
- if (existsSync3(modelFile)) {
18057
- try {
18058
- const modelDoc = objectValue(parseRestrictedYaml2(readFileSync(modelFile, "utf8"), modelFile));
18059
- const model = modelDoc ? stringValue(modelDoc.model) : void 0;
18060
- const vendor = modelDoc ? stringValue(modelDoc.vendor) : void 0;
18061
- const harness = modelDoc ? stringValue(modelDoc.harness) : void 0;
18062
- if (model) named.push(model);
18063
- if (vendor && model) named.push(`${vendor}/${model}`);
18064
- if (harness && model) named.push(`${harness}/${model}`);
18065
- } catch {
18066
- }
18067
- }
18068
- }
18069
- return named;
18070
- });
18071
- const agentModelObj = objectValue(implementerAgent.value.model);
18072
- const agentModelStr = stringValue(implementerAgent.value.model);
18073
- const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
18074
- const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
18075
- const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
18076
- if (enabledRosterModels.length > 0 && canonicalAgentModel) {
18077
- const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
18078
- if (!matches) {
18079
- issues.push(issue3("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}`));
18080
- }
18081
- }
18082
- } catch (error) {
18083
- if (error instanceof KxmConfigError) {
18084
- issues.push(...error.issues);
18085
- }
18047
+ if (existsSync3(file)) harness = stringValue(parseRestrictedYaml2(readFileSync(file, "utf8"), logical).harness);
18048
+ } catch {
18049
+ harness = void 0;
18050
+ }
18051
+ if (!harness) {
18052
+ issues.push(issue3("semantic", "roster_model_unreadable", role.logicalPath, `roster route ${route} does not resolve to a readable ${logical} carrying harness`));
18086
18053
  }
18087
18054
  }
18088
18055
  }
18089
- if (projectRoot && existsSync3(join2(projectRoot, ".kxm", "roster.yaml"))) {
18090
- issues.push(...developerRolePolicyIssues(projectRoot));
18056
+ return issues;
18057
+ }
18058
+ function originHashIssues(projectRoot, models) {
18059
+ const issues = [];
18060
+ for (const model of models.values()) {
18061
+ const origin = objectValue(model.value.origin);
18062
+ const source = origin ? stringValue(origin.source) : void 0;
18063
+ const digest2 = origin ? stringValue(origin.sha256) : void 0;
18064
+ if (!source || !digest2) continue;
18065
+ const file = resolve(projectRoot, source);
18066
+ const escaped = relative(projectRoot, file);
18067
+ if (isAbsolute(source) || escaped.startsWith("..") || isAbsolute(escaped)) {
18068
+ issues.push(issue3("semantic", "origin_evidence_missing", model.logicalPath, `missing evidence bytes for ${source}`));
18069
+ continue;
18070
+ }
18071
+ let actual;
18072
+ try {
18073
+ actual = createHash3("sha256").update(readFileSync(file, "utf8"), "utf8").digest("hex");
18074
+ } catch (error) {
18075
+ const code = error.code;
18076
+ if (code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "EISDIR") {
18077
+ issues.push(issue3("semantic", "origin_evidence_missing", model.logicalPath, `missing evidence bytes for ${source}`));
18078
+ continue;
18079
+ }
18080
+ throw error;
18081
+ }
18082
+ if (actual !== digest2) {
18083
+ issues.push(issue3("semantic", "origin_hash_mismatch", model.logicalPath, "origin evidence hash does not match supplied bytes"));
18084
+ }
18091
18085
  }
18092
- return sortIssues3(issues);
18086
+ return issues;
18093
18087
  }
18094
18088
  function developerCeilings() {
18095
18089
  const script = join2(findKxmRepoRoot(import.meta.url), "scripts", "harness-run.mjs");
@@ -18118,6 +18112,7 @@ function developerCeilings() {
18118
18112
  }
18119
18113
  }
18120
18114
  function developerRolePolicyIssues(projectRoot) {
18115
+ if (resolve(projectRoot) !== findKxmRepoRoot(import.meta.url)) return [];
18121
18116
  const rolesDir = join2(projectRoot, ".kxm", "roles");
18122
18117
  const modelsDir = join2(projectRoot, ".kxm", "models");
18123
18118
  const roles = {};
@@ -18138,6 +18133,7 @@ function developerRolePolicyIssues(projectRoot) {
18138
18133
  parseIssues.push(...error.issues);
18139
18134
  continue;
18140
18135
  }
18136
+ throw error;
18141
18137
  }
18142
18138
  }
18143
18139
  };
@@ -18152,7 +18148,7 @@ function developerRolePolicyIssues(projectRoot) {
18152
18148
  }
18153
18149
  const ceilings = developerCeilings();
18154
18150
  if (!ceilings.ok) {
18155
- return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/roster.yaml", `developer ceilings could not be loaded: ${ceilings.detail}`)];
18151
+ return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/models", `developer ceilings could not be loaded: ${ceilings.detail}`)];
18156
18152
  }
18157
18153
  const result = validatePolicyDraft({ models, roles, evidence }, {
18158
18154
  ceilings: ceilings.ROUTES,
@@ -18241,7 +18237,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
18241
18237
  }
18242
18238
  if (earlyIssues.length > 0) throw new KxmConfigError(earlyIssues);
18243
18239
  const agents = listNamedResources(registry, root, join2(root, ".kxm", "agents"), ".kxm/agents", "agent");
18244
- const models = listNamedResources(registry, root, join2(root, ".kxm", "models"), ".kxm/models", "model");
18240
+ const modelIssues = [];
18241
+ const models = listNamedResources(registry, root, join2(root, ".kxm", "models"), ".kxm/models", "model", void 0, modelIssues);
18245
18242
  const roles = listNamedResources(registry, root, join2(root, ".kxm", "roles"), ".kxm/roles", "role", writerRole === void 0 ? void 0 : "writer");
18246
18243
  if (writerRole !== void 0) {
18247
18244
  const logicalPath = ".kxm/roles/writer.yaml";
@@ -18368,8 +18365,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
18368
18365
  }
18369
18366
  }
18370
18367
  }
18371
- if (loadIssues.length > 0) throw new KxmConfigError(loadIssues);
18372
- const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, writerRole);
18368
+ if (loadIssues.length > 0) throw new KxmConfigError([...loadIssues, ...modelIssues]);
18369
+ const issues = [...modelIssues, ...validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, roles)];
18373
18370
  if (issues.length > 0) throw new KxmConfigError(issues);
18374
18371
  const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...roles.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits4(left.logicalPath, right.logicalPath));
18375
18372
  return {
@@ -18414,9 +18411,10 @@ function legacyConfigFilesAt(root) {
18414
18411
  }
18415
18412
 
18416
18413
  // plugins/kxm/src/runtime-store.ts
18414
+ import { spawnSync as spawnSync3 } from "node:child_process";
18417
18415
  import { createHash as createHash7, randomUUID } from "node:crypto";
18418
- import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
18419
- import { dirname as dirname5, resolve as resolve6 } from "node:path";
18416
+ import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync3, statSync, writeFileSync as writeFileSync2 } from "node:fs";
18417
+ import { dirname as dirname5, isAbsolute as isAbsolute4, resolve as resolve6 } from "node:path";
18420
18418
 
18421
18419
  // plugins/kxm/src/sqlite.ts
18422
18420
  import { existsSync as existsSync4 } from "node:fs";
@@ -18934,22 +18932,25 @@ function ensureWalJournalMode(database, file, description, timeoutMs = 5e3) {
18934
18932
  Atomics.wait(sleeper, 0, 0, 10);
18935
18933
  }
18936
18934
  }
18935
+ function assertDatabaseFile(file, description) {
18936
+ checkedParent(file, description);
18937
+ const stat = lstatSync2(file, { throwIfNoEntry: false });
18938
+ if (stat) {
18939
+ if (stat.isSymbolicLink() || !stat.isFile()) {
18940
+ throw databaseError("runtime_path_invalid", description, `${description} must be a regular file, not a link or directory`);
18941
+ }
18942
+ }
18943
+ for (const sidecar of [`${file}-wal`, `${file}-shm`]) {
18944
+ const info = lstatSync2(sidecar, { throwIfNoEntry: false });
18945
+ if (info?.isSymbolicLink()) {
18946
+ throw databaseError("runtime_path_invalid", description, `${description} sidecar must not be a link`);
18947
+ }
18948
+ }
18949
+ }
18937
18950
  function openDatabase(file, description, spec) {
18938
18951
  const isMemory = file === ":memory:";
18939
18952
  if (!isMemory) {
18940
- checkedParent(file, description);
18941
- const stat = lstatSync2(file, { throwIfNoEntry: false });
18942
- if (stat) {
18943
- if (stat.isSymbolicLink() || !stat.isFile()) {
18944
- throw databaseError("runtime_path_invalid", description, `${description} must be a regular file, not a link or directory`);
18945
- }
18946
- }
18947
- for (const sidecar of [`${file}-wal`, `${file}-shm`]) {
18948
- const info = lstatSync2(sidecar, { throwIfNoEntry: false });
18949
- if (info?.isSymbolicLink()) {
18950
- throw databaseError("runtime_path_invalid", description, `${description} sidecar must not be a link`);
18951
- }
18952
- }
18953
+ assertDatabaseFile(file, description);
18953
18954
  }
18954
18955
  const database = new DatabaseSync(file);
18955
18956
  let transaction = false;
@@ -18978,7 +18979,7 @@ function openDatabase(file, description, spec) {
18978
18979
  throw databaseError(
18979
18980
  "runtime_schema_outdated",
18980
18981
  file,
18981
- `${description} is schema version ${version}; this build requires ${spec.version}. Delete the state file to start fresh and let its owning process recreate it (\`kxm hub start\` for hub state, the Runtime for registry/event stores); \`kxm init\` is project-only and rebuilds no database \u2014 upgrading old state in place is deliberately unsupported`
18982
+ `${description} is schema version ${version}; this build requires ${spec.version}. Delete the state file to start fresh and let its owning process recreate it (\`kxm hub start\` for hub state, the Runtime for event stores). The Runtime registry copy from schema 1 to 2 is the one exception. \`kxm init\` is project-only and rebuilds no database. Upgrading any other old state in place is deliberately unsupported`
18982
18983
  );
18983
18984
  }
18984
18985
  if (spec.tables) {
@@ -19216,7 +19217,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
19216
19217
  }
19217
19218
  var KXM_BACKUP_CEILINGS = {
19218
19219
  "hub-store": 5,
19219
- registry: 1,
19220
+ registry: 2,
19220
19221
  "binding-store": 1,
19221
19222
  events: 7
19222
19223
  };
@@ -19582,10 +19583,10 @@ function projectRuntimeOwnsRun(projectRoot, runId, env) {
19582
19583
  database.close();
19583
19584
  }
19584
19585
  }
19585
- var KXM_REGISTRY_SCHEMA_VERSION = 1;
19586
+ var KXM_REGISTRY_SCHEMA_VERSION = 2;
19586
19587
  var REGISTRY_TABLES = {
19587
19588
  supervisor: ["singleton_id", "runtime_id", "pid", "port", "token_hash", "started_at", "heartbeat_at", "state"],
19588
- projects: ["project_id", "project_root", "project_key", "home_runtime_id", "config_revision", "registered_at"]
19589
+ projects: ["project_id", "project_root", "project_key", "home_runtime_id", "config_revision", "registered_at", "lane_of"]
19589
19590
  };
19590
19591
  var REGISTRY_SCHEMA = `
19591
19592
  CREATE TABLE supervisor (
@@ -19599,14 +19600,117 @@ CREATE TABLE supervisor (
19599
19600
  state TEXT NOT NULL
19600
19601
  ) STRICT;
19601
19602
  CREATE TABLE projects (
19602
- project_id TEXT PRIMARY KEY,
19603
+ project_key TEXT PRIMARY KEY,
19604
+ project_id TEXT NOT NULL,
19603
19605
  project_root TEXT NOT NULL,
19604
- project_key TEXT NOT NULL UNIQUE,
19605
19606
  home_runtime_id TEXT NOT NULL,
19606
19607
  config_revision TEXT,
19607
- registered_at TEXT NOT NULL
19608
+ registered_at TEXT NOT NULL,
19609
+ lane_of TEXT
19608
19610
  ) STRICT;
19611
+ CREATE INDEX projects_by_project_id ON projects (project_id);
19609
19612
  `;
19613
+ var PROJECT_COLUMNS = "project_id, project_root, project_key, home_runtime_id, config_revision, registered_at, lane_of";
19614
+ function registrationFromRow(row) {
19615
+ return {
19616
+ projectId: row.project_id,
19617
+ projectRoot: row.project_root,
19618
+ projectKey: row.project_key,
19619
+ homeRuntimeId: row.home_runtime_id,
19620
+ ...row.config_revision !== null ? { configRevision: row.config_revision } : {},
19621
+ ...row.lane_of !== null ? { laneOf: row.lane_of } : {},
19622
+ registeredAt: row.registered_at
19623
+ };
19624
+ }
19625
+ function controlRootDirectoryExists(projectRoot) {
19626
+ const stat = statSync(projectRoot, { throwIfNoEntry: false });
19627
+ return Boolean(stat?.isDirectory());
19628
+ }
19629
+ function gitEnv() {
19630
+ return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
19631
+ }
19632
+ function gitCommonDirectory(projectRoot) {
19633
+ const result = spawnSync3("git", ["-C", projectRoot, "rev-parse", "--git-common-dir"], {
19634
+ encoding: "utf8",
19635
+ env: gitEnv(),
19636
+ timeout: 3e4,
19637
+ windowsHide: true
19638
+ });
19639
+ if (result.status !== 0) return void 0;
19640
+ const text = (result.stdout ?? "").trim();
19641
+ if (!text) return void 0;
19642
+ const absolute = isAbsolute4(text) ? text : resolve6(projectRoot, text);
19643
+ let canonical;
19644
+ try {
19645
+ canonical = realpathSync3.native(absolute);
19646
+ } catch {
19647
+ canonical = resolve6(absolute);
19648
+ }
19649
+ return process.platform === "win32" ? canonical.toLocaleLowerCase("en-US") : canonical;
19650
+ }
19651
+ function isPrimaryWorktree(projectRoot, commonDir) {
19652
+ if (commonDir === void 0) return false;
19653
+ const dotGit = resolve6(projectRoot, ".git");
19654
+ let canonical;
19655
+ try {
19656
+ canonical = realpathSync3.native(dotGit);
19657
+ } catch {
19658
+ canonical = resolve6(dotGit);
19659
+ }
19660
+ if (process.platform === "win32") canonical = canonical.toLocaleLowerCase("en-US");
19661
+ return canonical === commonDir;
19662
+ }
19663
+ function migrateRegistryLanes(file) {
19664
+ const stat = lstatSync3(file, { throwIfNoEntry: false });
19665
+ if (!stat) return;
19666
+ assertDatabaseFile(file, "runtime registry");
19667
+ const database = new DatabaseSync(file);
19668
+ let transaction = false;
19669
+ try {
19670
+ database.exec("PRAGMA busy_timeout = 5000");
19671
+ const current = database.prepare("PRAGMA user_version").get();
19672
+ if ((current?.user_version ?? 0) !== 1) return;
19673
+ database.exec("BEGIN IMMEDIATE");
19674
+ transaction = true;
19675
+ const locked = database.prepare("PRAGMA user_version").get();
19676
+ if ((locked?.user_version ?? 0) !== 1) {
19677
+ database.exec("ROLLBACK");
19678
+ transaction = false;
19679
+ return;
19680
+ }
19681
+ if (!tableColumns(database, "projects").includes("lane_of")) {
19682
+ database.exec(`
19683
+ CREATE TABLE projects_v2 (
19684
+ project_key TEXT PRIMARY KEY,
19685
+ project_id TEXT NOT NULL,
19686
+ project_root TEXT NOT NULL,
19687
+ home_runtime_id TEXT NOT NULL,
19688
+ config_revision TEXT,
19689
+ registered_at TEXT NOT NULL,
19690
+ lane_of TEXT
19691
+ ) STRICT;
19692
+ INSERT INTO projects_v2 (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
19693
+ SELECT project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, NULL FROM projects;
19694
+ DROP TABLE projects;
19695
+ ALTER TABLE projects_v2 RENAME TO projects;
19696
+ CREATE INDEX projects_by_project_id ON projects (project_id);
19697
+ `);
19698
+ }
19699
+ database.exec("PRAGMA user_version = 2");
19700
+ database.exec("COMMIT");
19701
+ transaction = false;
19702
+ } catch (error) {
19703
+ if (transaction) {
19704
+ try {
19705
+ database.exec("ROLLBACK");
19706
+ } catch {
19707
+ }
19708
+ }
19709
+ throw error;
19710
+ } finally {
19711
+ database.close();
19712
+ }
19713
+ }
19610
19714
  function readKxmSupervisorRecord(database) {
19611
19715
  const row = database.prepare("SELECT runtime_id, pid, port, token_hash, started_at, heartbeat_at, state FROM supervisor WHERE singleton_id = 1").get();
19612
19716
  return row ? {
@@ -19624,6 +19728,7 @@ var KxmRuntimeRegistry = class {
19624
19728
  database;
19625
19729
  constructor(path) {
19626
19730
  this.path = resolve6(path);
19731
+ migrateRegistryLanes(this.path);
19627
19732
  this.database = openDatabase(this.path, "runtime registry", {
19628
19733
  schema: REGISTRY_SCHEMA,
19629
19734
  version: KXM_REGISTRY_SCHEMA_VERSION,
@@ -19702,57 +19807,188 @@ var KxmRuntimeRegistry = class {
19702
19807
  supervisor() {
19703
19808
  return this.readSupervisorRow();
19704
19809
  }
19705
- /** Register or revalidate a project's home binding. Home Runtime is immutable. */
19706
- /** All projects registered to this Runtime, for restart recovery: the
19707
- * supervisor needs to reopen their contexts so pending outbox rows resume
19708
- * syncing and presence keeps beating. */
19810
+ /** All projects registered to this Runtime, including lane roots, for restart
19811
+ * recovery: the supervisor reopens their contexts so pending outbox rows
19812
+ * resume syncing and presence keeps beating. */
19709
19813
  projectsForRuntime(homeRuntimeId) {
19710
19814
  const rows = this.database.prepare(
19711
19815
  "SELECT project_root, project_id FROM projects WHERE home_runtime_id = ? ORDER BY registered_at"
19712
19816
  ).all(homeRuntimeId);
19713
19817
  return rows.map((row) => ({ projectRoot: row.project_root, projectId: row.project_id }));
19714
19818
  }
19819
+ /**
19820
+ * Register or revalidate a control root. The home runtime of a live row is
19821
+ * immutable. A second root whose git common directory matches a live row
19822
+ * for the same project is a lane, unless the registering root is the primary
19823
+ * worktree and the live home row is not: then the primary becomes the home
19824
+ * row (same home runtime id) and every other live row of that repository
19825
+ * becomes its lane. A primary never becomes a lane. A root whose directory
19826
+ * is gone is replaced. A live root that is a different repository is
19827
+ * `project_home_conflict`.
19828
+ */
19715
19829
  registerProject(registration) {
19716
19830
  const projectRoot = resolve6(registration.projectRoot);
19717
19831
  const projectKey = projectRuntimeKey(projectRoot);
19832
+ const candidateCommon = gitCommonDirectory(projectRoot);
19833
+ const commonByRoot = /* @__PURE__ */ new Map();
19834
+ for (let attempt = 0; attempt < 8; attempt += 1) {
19835
+ this.database.exec("BEGIN IMMEDIATE");
19836
+ let rootsToMeasure;
19837
+ try {
19838
+ const byKey = this.projectRow("SELECT " + PROJECT_COLUMNS + " FROM projects WHERE project_key = ?", projectKey);
19839
+ if (byKey) {
19840
+ const problems = [];
19841
+ if (byKey.project_id !== registration.projectId) problems.push(`control root is already bound to a different project id ${byKey.project_id}`);
19842
+ if (byKey.home_runtime_id !== registration.homeRuntimeId) problems.push(`project ${registration.projectId} home runtime is immutable and cannot be rebound`);
19843
+ if (problems.length > 0) {
19844
+ this.database.exec("ROLLBACK");
19845
+ throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
19846
+ }
19847
+ const result = registrationFromRow(byKey);
19848
+ this.database.exec("COMMIT");
19849
+ return result;
19850
+ }
19851
+ const rows = this.database.prepare(
19852
+ `SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_id = ? ORDER BY registered_at`
19853
+ ).all(registration.projectId);
19854
+ const live = rows.filter((row) => controlRootDirectoryExists(row.project_root));
19855
+ const dead = rows.filter((row) => !controlRootDirectoryExists(row.project_root));
19856
+ const unknown = live.filter((row) => !commonByRoot.has(row.project_root));
19857
+ if (unknown.length > 0) {
19858
+ rootsToMeasure = unknown.map((row) => row.project_root);
19859
+ this.database.exec("ROLLBACK");
19860
+ } else if (live.length > 0) {
19861
+ const anchor = live.find((row) => {
19862
+ const dir = commonByRoot.get(row.project_root);
19863
+ return candidateCommon !== void 0 && dir === candidateCommon;
19864
+ });
19865
+ if (!anchor) {
19866
+ this.database.exec("ROLLBACK");
19867
+ throw runtimeError(
19868
+ "project_home_conflict",
19869
+ ".kxm/project.yaml",
19870
+ `project ${registration.projectId} is already bound to a different control root`
19871
+ );
19872
+ }
19873
+ if (live.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
19874
+ this.database.exec("ROLLBACK");
19875
+ throw runtimeError(
19876
+ "project_home_conflict",
19877
+ ".kxm/project.yaml",
19878
+ `project ${registration.projectId} home runtime is immutable and cannot be rebound`
19879
+ );
19880
+ }
19881
+ const sameRepo = live.filter((row) => commonByRoot.get(row.project_root) === candidateCommon);
19882
+ const home = sameRepo.find((row) => row.lane_of === null);
19883
+ const registeringIsPrimary = isPrimaryWorktree(projectRoot, candidateCommon);
19884
+ const homeIsPrimary = home !== void 0 && isPrimaryWorktree(home.project_root, commonByRoot.get(home.project_root));
19885
+ if (registeringIsPrimary && !homeIsPrimary) {
19886
+ this.insertHome(registration, projectRoot, projectKey);
19887
+ for (const row of sameRepo) {
19888
+ this.database.prepare("UPDATE projects SET lane_of = ? WHERE project_key = ?").run(projectKey, row.project_key);
19889
+ }
19890
+ this.database.exec("COMMIT");
19891
+ registration.logger?.({
19892
+ event: "project_home_promoted",
19893
+ projectId: registration.projectId,
19894
+ projectRoot,
19895
+ homeRuntimeId: registration.homeRuntimeId,
19896
+ previousHomeRoot: home?.project_root ?? null,
19897
+ previousHomeKey: home?.project_key ?? null
19898
+ });
19899
+ return this.insertedRegistration(registration, projectRoot, projectKey);
19900
+ }
19901
+ if (registeringIsPrimary) {
19902
+ this.database.exec("ROLLBACK");
19903
+ throw runtimeError(
19904
+ "project_home_conflict",
19905
+ ".kxm/project.yaml",
19906
+ `project ${registration.projectId} primary checkout is already the home row`
19907
+ );
19908
+ }
19909
+ const laneOf = anchor.lane_of ?? anchor.project_key;
19910
+ this.database.prepare(`
19911
+ INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
19912
+ VALUES (?, ?, ?, ?, ?, ?, ?)
19913
+ `).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now, laneOf);
19914
+ const result = this.insertedRegistration(registration, projectRoot, projectKey, laneOf);
19915
+ this.database.exec("COMMIT");
19916
+ return result;
19917
+ } else if (dead.length > 0) {
19918
+ const replaced = dead[0];
19919
+ if (dead.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
19920
+ this.database.exec("ROLLBACK");
19921
+ throw runtimeError(
19922
+ "project_home_conflict",
19923
+ ".kxm/project.yaml",
19924
+ `project ${registration.projectId} home runtime is immutable and cannot be rebound`
19925
+ );
19926
+ }
19927
+ this.database.prepare("DELETE FROM projects WHERE project_id = ?").run(registration.projectId);
19928
+ this.insertHome(registration, projectRoot, projectKey);
19929
+ this.database.exec("COMMIT");
19930
+ registration.logger?.({
19931
+ event: "project_registration_replaced",
19932
+ projectId: registration.projectId,
19933
+ projectRoot,
19934
+ replacedRoot: replaced.project_root,
19935
+ replacedCount: dead.length,
19936
+ replacedHomeRuntimeId: replaced.home_runtime_id,
19937
+ replacedProjectKey: replaced.project_key
19938
+ });
19939
+ return this.insertedRegistration(registration, projectRoot, projectKey);
19940
+ } else {
19941
+ this.insertHome(registration, projectRoot, projectKey);
19942
+ this.database.exec("COMMIT");
19943
+ return this.insertedRegistration(registration, projectRoot, projectKey);
19944
+ }
19945
+ } catch (error) {
19946
+ try {
19947
+ this.database.exec("ROLLBACK");
19948
+ } catch {
19949
+ }
19950
+ throw error;
19951
+ }
19952
+ for (const root of rootsToMeasure ?? []) {
19953
+ commonByRoot.set(root, gitCommonDirectory(root));
19954
+ }
19955
+ }
19956
+ throw runtimeError(
19957
+ "runtime_registry_busy",
19958
+ projectRoot,
19959
+ `project ${registration.projectId} registration could not be rechecked`
19960
+ );
19961
+ }
19962
+ /** Remove one control root. Returns false when that root was not registered. */
19963
+ unregisterProject(projectRoot) {
19718
19964
  this.database.exec("BEGIN IMMEDIATE");
19719
19965
  try {
19720
- const byId = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_id = ?").get(registration.projectId);
19721
- const byKey = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_key = ?").get(projectKey);
19722
- const existing = byId ?? byKey;
19723
- if (existing) {
19724
- const problems = [];
19725
- if (byId && byId.project_key !== projectKey) problems.push(`project ${registration.projectId} is already bound to a different control root`);
19726
- if (byKey && byKey.project_id !== registration.projectId) problems.push(`control root is already bound to a different project id ${byKey.project_id}`);
19727
- if (existing.home_runtime_id !== registration.homeRuntimeId) problems.push(`project ${registration.projectId} home runtime is immutable and cannot be rebound`);
19728
- if (problems.length > 0) {
19729
- this.database.exec("ROLLBACK");
19730
- throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
19731
- }
19732
- const result = {
19733
- projectId: existing.project_id,
19734
- projectRoot: existing.project_root,
19735
- projectKey: existing.project_key,
19736
- homeRuntimeId: existing.home_runtime_id,
19737
- ...existing.config_revision !== null ? { configRevision: existing.config_revision } : {},
19738
- registeredAt: existing.registered_at
19739
- };
19740
- this.database.exec("COMMIT");
19741
- return result;
19966
+ const removed = this.deleteRegisteredRoot(projectRoot);
19967
+ this.database.exec("COMMIT");
19968
+ return removed;
19969
+ } catch (error) {
19970
+ try {
19971
+ this.database.exec("ROLLBACK");
19972
+ } catch {
19742
19973
  }
19743
- this.database.prepare(`
19744
- INSERT INTO projects (project_id, project_root, project_key, home_runtime_id, config_revision, registered_at)
19745
- VALUES (?, ?, ?, ?, ?, ?)
19746
- `).run(registration.projectId, projectRoot, projectKey, registration.homeRuntimeId, registration.configRevision ?? null, registration.now);
19974
+ throw error;
19975
+ }
19976
+ }
19977
+ /**
19978
+ * Delete the row only when `supervisorIsLive` is false for the supervisor
19979
+ * row read inside this write transaction. A live supervisor keeps the row;
19980
+ * the caller uses the supervisor route instead of editing the file.
19981
+ */
19982
+ unregisterProjectIfIdle(projectRoot, supervisorIsLive) {
19983
+ this.database.exec("BEGIN IMMEDIATE");
19984
+ try {
19985
+ if (supervisorIsLive(this.readSupervisorRow())) {
19986
+ this.database.exec("ROLLBACK");
19987
+ return "supervisor_live";
19988
+ }
19989
+ const removed = this.deleteRegisteredRoot(projectRoot);
19747
19990
  this.database.exec("COMMIT");
19748
- return {
19749
- projectId: registration.projectId,
19750
- projectRoot,
19751
- projectKey,
19752
- homeRuntimeId: registration.homeRuntimeId,
19753
- ...registration.configRevision !== void 0 ? { configRevision: registration.configRevision } : {},
19754
- registeredAt: registration.now
19755
- };
19991
+ return removed ? "removed" : "absent";
19756
19992
  } catch (error) {
19757
19993
  try {
19758
19994
  this.database.exec("ROLLBACK");
@@ -19761,28 +19997,65 @@ var KxmRuntimeRegistry = class {
19761
19997
  throw error;
19762
19998
  }
19763
19999
  }
20000
+ /** How many other rows share this home root's project. Zero when this root is a lane or missing. */
20001
+ homeLaneCount(projectRoot) {
20002
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(resolve6(projectRoot)));
20003
+ if (!row || row.lane_of !== null) return 0;
20004
+ const count = this.database.prepare(
20005
+ "SELECT COUNT(*) AS total FROM projects WHERE project_id = ? AND project_key != ?"
20006
+ ).get(row.project_id, row.project_key);
20007
+ return Number(count?.total ?? 0);
20008
+ }
20009
+ /** Caller holds the write transaction. Does not commit. */
20010
+ deleteRegisteredRoot(projectRoot) {
20011
+ const projectKey = projectRuntimeKey(resolve6(projectRoot));
20012
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectKey);
20013
+ if (!row) return false;
20014
+ this.database.prepare("DELETE FROM projects WHERE project_key = ?").run(projectKey);
20015
+ if (row.lane_of === null) {
20016
+ const successor = this.database.prepare(
20017
+ "SELECT project_key FROM projects WHERE project_id = ? ORDER BY registered_at LIMIT 1"
20018
+ ).get(row.project_id);
20019
+ if (successor) {
20020
+ this.database.prepare("UPDATE projects SET lane_of = NULL WHERE project_key = ?").run(successor.project_key);
20021
+ this.database.prepare(
20022
+ "UPDATE projects SET lane_of = ? WHERE project_id = ? AND project_key != ? AND (lane_of IS NULL OR lane_of = ?)"
20023
+ ).run(successor.project_key, row.project_id, successor.project_key, projectKey);
20024
+ }
20025
+ }
20026
+ return true;
20027
+ }
20028
+ /** The home row for a project id, or the earliest row when every row is a lane. */
19764
20029
  project(projectId) {
19765
- const row = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_id = ?").get(projectId);
19766
- return row ? {
19767
- projectId: row.project_id,
19768
- projectRoot: row.project_root,
19769
- projectKey: row.project_key,
19770
- homeRuntimeId: row.home_runtime_id,
19771
- ...row.config_revision !== null ? { configRevision: row.config_revision } : {},
19772
- registeredAt: row.registered_at
19773
- } : void 0;
20030
+ const row = this.projectRow(
20031
+ `SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_id = ? ORDER BY CASE WHEN lane_of IS NULL THEN 0 ELSE 1 END, registered_at LIMIT 1`,
20032
+ projectId
20033
+ );
20034
+ return row ? registrationFromRow(row) : void 0;
19774
20035
  }
19775
20036
  projectByRoot(projectRoot) {
19776
- const key = projectRuntimeKey(projectRoot);
19777
- const row = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_key = ?").get(key);
19778
- return row ? {
19779
- projectId: row.project_id,
19780
- projectRoot: row.project_root,
19781
- projectKey: row.project_key,
19782
- homeRuntimeId: row.home_runtime_id,
19783
- ...row.config_revision !== null ? { configRevision: row.config_revision } : {},
19784
- registeredAt: row.registered_at
19785
- } : void 0;
20037
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(projectRoot));
20038
+ return row ? registrationFromRow(row) : void 0;
20039
+ }
20040
+ projectRow(sql, parameter) {
20041
+ return this.database.prepare(sql).get(parameter);
20042
+ }
20043
+ insertHome(registration, projectRoot, projectKey) {
20044
+ this.database.prepare(`
20045
+ INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
20046
+ VALUES (?, ?, ?, ?, ?, ?, NULL)
20047
+ `).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now);
20048
+ }
20049
+ insertedRegistration(registration, projectRoot, projectKey, laneOf) {
20050
+ return {
20051
+ projectId: registration.projectId,
20052
+ projectRoot,
20053
+ projectKey,
20054
+ homeRuntimeId: registration.homeRuntimeId,
20055
+ ...registration.configRevision !== void 0 ? { configRevision: registration.configRevision } : {},
20056
+ ...laneOf !== void 0 ? { laneOf } : {},
20057
+ registeredAt: registration.now
20058
+ };
19786
20059
  }
19787
20060
  };
19788
20061
  var KXM_RUN_EVENT_SCHEMA = "kxm.run-event.v1";
@@ -24228,7 +24501,8 @@ function openKxmRuntimeContext(projectRoot, options) {
24228
24501
  projectRoot: bundle.projectRoot,
24229
24502
  homeRuntimeId: options.homeRuntimeId,
24230
24503
  configRevision: bundle.configRevision,
24231
- now: options.now ?? (/* @__PURE__ */ new Date()).toISOString()
24504
+ now: options.now ?? (/* @__PURE__ */ new Date()).toISOString(),
24505
+ ...options.logger ? { logger: options.logger } : {}
24232
24506
  });
24233
24507
  const eventStore = new KxmRunEventStore(join7(paths.projectsDir, registration.projectKey, "run-events.db"));
24234
24508
  registerKxmRuntimeHandle(eventStore.path);
@@ -24582,7 +24856,7 @@ import { spawn as spawn3 } from "node:child_process";
24582
24856
  import { createHash as createHash16, createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
24583
24857
  import { chmodSync as chmodSync2, existsSync as existsSync16, lstatSync as lstatSync6, mkdirSync as mkdirSync9, readFileSync as readFileSync13, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
24584
24858
  import { createServer } from "node:http";
24585
- import { dirname as dirname11, isAbsolute as isAbsolute7, join as join18 } from "node:path";
24859
+ import { dirname as dirname11, isAbsolute as isAbsolute8, join as join18 } from "node:path";
24586
24860
 
24587
24861
  // plugins/kxm/src/oneshot-evidence.ts
24588
24862
  import { createHash as createHash10, randomUUID as randomUUID3 } from "node:crypto";
@@ -24737,7 +25011,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
24737
25011
  // plugins/kxm/src/prices.ts
24738
25012
  var import_yaml3 = __toESM(require_dist(), 1);
24739
25013
  import { createHash as createHash11 } from "node:crypto";
24740
- import { existsSync as existsSync8, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync3 } from "node:fs";
25014
+ import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
24741
25015
  import { join as join9 } from "node:path";
24742
25016
 
24743
25017
  // plugins/kxm/src/price-calc.ts
@@ -24890,7 +25164,7 @@ function parsePriceCatalog(text) {
24890
25164
  return catalog;
24891
25165
  }
24892
25166
  function loadPriceCatalog(rootOrPath) {
24893
- const candidatePath = existsSync8(join9(rootOrPath, ".kxm", "prices.yaml")) ? join9(rootOrPath, ".kxm", "prices.yaml") : existsSync8(join9(rootOrPath, "prices.yaml")) ? join9(rootOrPath, "prices.yaml") : existsSync8(rootOrPath) && statSync(rootOrPath).isFile() ? rootOrPath : void 0;
25167
+ const candidatePath = existsSync8(join9(rootOrPath, ".kxm", "prices.yaml")) ? join9(rootOrPath, ".kxm", "prices.yaml") : existsSync8(join9(rootOrPath, "prices.yaml")) ? join9(rootOrPath, "prices.yaml") : existsSync8(rootOrPath) && statSync2(rootOrPath).isFile() ? rootOrPath : void 0;
24894
25168
  if (!candidatePath || !existsSync8(candidatePath)) {
24895
25169
  return void 0;
24896
25170
  }
@@ -24929,14 +25203,14 @@ var import_yaml4 = __toESM(require_dist(), 1);
24929
25203
 
24930
25204
  // plugins/kxm/src/routes.ts
24931
25205
  var RETIRED_POLICY = ".kxm/producers.yaml";
24932
- var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [], roles: {} });
25206
+ var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [] });
24933
25207
  function loadRoutePolicy(root) {
24934
25208
  if (existsSync9(join10(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
24935
25209
  const path = join10(root, ".kxm", "routes.yaml");
24936
25210
  if (!existsSync9(path)) return empty();
24937
25211
  const value = (0, import_yaml6.parse)(readFileSync7(path, "utf8"));
24938
25212
  if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
24939
- return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : (/* @__PURE__ */ new Date()).toISOString(), admitted: value.admitted.filter((x) => typeof x === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x) => typeof x === "string") : [], roles: value.roles && typeof value.roles === "object" ? Object.fromEntries(Object.entries(value.roles).filter(([, v]) => Array.isArray(v)).map(([k, v]) => [k, v.filter((x) => typeof x === "string")])) : {} };
25213
+ return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : (/* @__PURE__ */ new Date()).toISOString(), admitted: value.admitted.filter((x) => typeof x === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x) => typeof x === "string") : [] };
24940
25214
  }
24941
25215
  function listRoleBindings(root) {
24942
25216
  const dir = join10(root, ".kxm", "roles");
@@ -24949,15 +25223,11 @@ function listRoleBindings(root) {
24949
25223
  }
24950
25224
  return result;
24951
25225
  }
24952
- function isRouteAdmitted(root, modelId) {
24953
- const policy = loadRoutePolicy(root);
24954
- return policy.admitted.includes(modelId) && !policy.disabled.includes(modelId);
24955
- }
24956
25226
 
24957
25227
  // plugins/kxm/src/worktree-witness.ts
24958
- import { spawnSync as spawnSync3 } from "node:child_process";
25228
+ import { spawnSync as spawnSync4 } from "node:child_process";
24959
25229
  function gitText(cwd, args) {
24960
- const result = spawnSync3("git", ["-C", cwd, ...args], {
25230
+ const result = spawnSync4("git", ["-C", cwd, ...args], {
24961
25231
  encoding: "utf8",
24962
25232
  windowsHide: true,
24963
25233
  timeout: 15e3
@@ -25270,7 +25540,7 @@ function pruneContextPacket(packet, tokenBudget) {
25270
25540
  }
25271
25541
 
25272
25542
  // plugins/kxm/src/dispatch-context.ts
25273
- import { spawnSync as spawnSync4 } from "node:child_process";
25543
+ import { spawnSync as spawnSync5 } from "node:child_process";
25274
25544
  import { readdirSync as readdirSync8 } from "node:fs";
25275
25545
  import { extname as extname3, join as join13 } from "node:path";
25276
25546
 
@@ -26033,7 +26303,7 @@ function loadAuthoredMemory(repoRoot) {
26033
26303
  // plugins/kxm/src/skills.ts
26034
26304
  var import_yaml8 = __toESM(require_dist(), 1);
26035
26305
  import { createHash as createHash12 } from "node:crypto";
26036
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, readFileSync as readFileSync9, renameSync, rmSync, statSync as statSync2, writeFileSync as writeFileSync6 } from "node:fs";
26306
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, readFileSync as readFileSync9, renameSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync6 } from "node:fs";
26037
26307
  import { dirname as dirname7, join as join12 } from "node:path";
26038
26308
  var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
26039
26309
  var SKILL_EVALUATION_SCHEMA = "kxm.skill-evaluation.v1";
@@ -26413,7 +26683,7 @@ function boundedList(value, field) {
26413
26683
  return [...new Set(refs)];
26414
26684
  }
26415
26685
  function readdirSorted(dir) {
26416
- return readdirSync7(dir).filter((entry) => statSync2(join12(dir, entry)).isDirectory()).sort();
26686
+ return readdirSync7(dir).filter((entry) => statSync3(join12(dir, entry)).isDirectory()).sort();
26417
26687
  }
26418
26688
 
26419
26689
  // plugins/kxm/src/dispatch-context.ts
@@ -26474,7 +26744,7 @@ function gitEnvironment2() {
26474
26744
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
26475
26745
  }
26476
26746
  function committedGateGap(projectRoot) {
26477
- const result = spawnSync4("git", [
26747
+ const result = spawnSync5("git", [
26478
26748
  "--no-optional-locks",
26479
26749
  "-C",
26480
26750
  projectRoot,
@@ -26616,11 +26886,11 @@ function assembleDispatchContext(sources, input) {
26616
26886
  import { join as join14 } from "node:path";
26617
26887
 
26618
26888
  // plugins/kxm/src/artifacts-exist.ts
26619
- import { lstatSync as lstatSync5, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
26620
- import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve8 } from "node:path";
26889
+ import { lstatSync as lstatSync5, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
26890
+ import { isAbsolute as isAbsolute5, relative as relative4, resolve as resolve8 } from "node:path";
26621
26891
  function staysUnder(root, candidate) {
26622
26892
  const child = relative4(root, candidate);
26623
- return child === "" || !isAbsolute4(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
26893
+ return child === "" || !isAbsolute5(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
26624
26894
  }
26625
26895
  function verifyArtifactExists(rootInput, pathInput) {
26626
26896
  const lexicalRoot = resolve8(rootInput);
@@ -26631,12 +26901,12 @@ function verifyArtifactExists(rootInput, pathInput) {
26631
26901
  let realRoot;
26632
26902
  let realPath;
26633
26903
  try {
26634
- realRoot = realpathSync3(lexicalRoot);
26904
+ realRoot = realpathSync4(lexicalRoot);
26635
26905
  } catch {
26636
26906
  return { ok: false, error: "artifact_unreadable", path: lexicalPath };
26637
26907
  }
26638
26908
  try {
26639
- realPath = realpathSync3(lexicalPath);
26909
+ realPath = realpathSync4(lexicalPath);
26640
26910
  } catch {
26641
26911
  return { ok: false, error: "artifact_missing", path: lexicalPath };
26642
26912
  }
@@ -26648,7 +26918,7 @@ function verifyArtifactExists(rootInput, pathInput) {
26648
26918
  if (link.isSymbolicLink()) {
26649
26919
  return { ok: false, error: "artifact_outside_workspace_assets", path: lexicalPath };
26650
26920
  }
26651
- const artifact = statSync3(realPath);
26921
+ const artifact = statSync4(realPath);
26652
26922
  if (!artifact.isFile()) return { ok: false, error: "artifact_not_file", path: lexicalPath };
26653
26923
  if (artifact.size < 1) return { ok: false, error: "artifact_empty", path: lexicalPath };
26654
26924
  return { ok: true, path: realPath, bytes: artifact.size };
@@ -28541,25 +28811,18 @@ function commitCommandUncertainty(context, prepared, token, reason, observation,
28541
28811
  }
28542
28812
  }
28543
28813
  var kxmPanelDispatchSeams = {};
28544
- function selectorsNamedByRoutes(projectRoot, routeIds) {
28545
- const selectors = /* @__PURE__ */ new Set();
28546
- for (const routeId2 of routeIds) {
28547
- const file = join15(projectRoot, ".kxm", "models", `${routeId2}.yaml`);
28548
- if (!existsSync12(file)) continue;
28549
- let parsed;
28550
- try {
28551
- parsed = (0, import_yaml9.parse)(readFileSync10(file, "utf8"));
28552
- } catch {
28553
- continue;
28554
- }
28555
- const model = typeof parsed.model === "string" ? parsed.model : "";
28556
- const vendor = typeof parsed.vendor === "string" ? parsed.vendor : "";
28557
- const harness = typeof parsed.harness === "string" ? parsed.harness : "";
28558
- if (model) selectors.add(model);
28559
- if (vendor && model) selectors.add(`${vendor}/${model}`);
28560
- if (harness && model) selectors.add(`${harness}/${model}`);
28814
+ function readYamlFile(file) {
28815
+ return readModelDocument(file);
28816
+ }
28817
+ function readModelDocument(file) {
28818
+ if (!existsSync12(file)) return void 0;
28819
+ try {
28820
+ const parsed = (0, import_yaml9.parse)(readFileSync10(file, "utf8"));
28821
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
28822
+ return parsed;
28823
+ } catch {
28824
+ return void 0;
28561
28825
  }
28562
- return [...selectors];
28563
28826
  }
28564
28827
  function unreconciledPanelAttemptId(state) {
28565
28828
  const current = state.currentStep;
@@ -28576,82 +28839,118 @@ function unreconciledPanelAttemptId(state) {
28576
28839
  return void 0;
28577
28840
  }
28578
28841
  function resolveProducerRoute(projectRoot, step, agentId) {
28579
- let agentModel;
28580
- const agentFile = join15(projectRoot, ".kxm", "agents", `${agentId}.yaml`);
28581
- if (existsSync12(agentFile)) {
28582
- try {
28583
- const parsed = (0, import_yaml9.parse)(readFileSync10(agentFile, "utf8"));
28584
- if (typeof parsed?.model === "string") {
28585
- agentModel = parsed.model;
28586
- } else if (parsed?.model && typeof parsed.model === "object" && !Array.isArray(parsed.model)) {
28587
- const declared = parsed.model;
28588
- const providerOk = typeof declared.provider === "string" && declared.provider.length > 0 && !declared.provider.includes("/");
28589
- const modelOk = typeof declared.model === "string" && declared.model.length > 0 && !declared.model.startsWith("/") && !declared.model.endsWith("/");
28590
- if (providerOk && modelOk) {
28591
- agentModel = `${declared.provider}/${declared.model}`;
28592
- } else {
28593
- return {
28594
- error: {
28595
- reason: "step_unsupported",
28596
- field: "model",
28597
- detail: "producer_route_unsupported: invalid model declaration"
28598
- }
28599
- };
28600
- }
28601
- }
28602
- } catch {
28603
- }
28604
- }
28605
- let selector = typeof step.model === "string" ? step.model : agentModel;
28606
- if (!selector) {
28607
- if (agentId === "implementer") {
28608
- selector = "xai/grok-4.6";
28609
- }
28610
- }
28611
- if (!selector) {
28842
+ if ((step.kind === "agent" || step.kind === "moa") && step.model !== void 0) {
28612
28843
  return {
28613
28844
  error: {
28614
28845
  reason: "step_unsupported",
28615
28846
  field: "model",
28616
- detail: "producer_route_unsupported: agent or step has no model declared"
28847
+ detail: "producer_route_unsupported: agent step model is not honored"
28617
28848
  }
28618
28849
  };
28619
28850
  }
28620
- const slash = selector.indexOf("/");
28621
- if (slash <= 0 || slash === selector.length - 1) {
28851
+ const agent = readYamlFile(join15(projectRoot, ".kxm", "agents", `${agentId}.yaml`));
28852
+ const role = typeof agent?.role === "string" ? agent.role : "";
28853
+ if (!role) {
28622
28854
  return {
28623
28855
  error: {
28624
28856
  reason: "step_unsupported",
28625
28857
  field: "model",
28626
- detail: `producer_route_unsupported: invalid selector '${selector}'`
28858
+ detail: "producer_route_unsupported: agent has no role"
28627
28859
  }
28628
28860
  };
28629
28861
  }
28630
- if (!isRouteAdmitted(projectRoot, selector)) {
28862
+ const roleDoc = readYamlFile(join15(projectRoot, ".kxm", "roles", `${role}.yaml`));
28863
+ const roster = Array.isArray(roleDoc?.roster) ? roleDoc.roster : void 0;
28864
+ if (!roster) {
28631
28865
  return {
28632
28866
  error: {
28633
28867
  reason: "step_unsupported",
28634
28868
  field: "model",
28635
- detail: `producer_route_unsupported: model '${selector}' is not admitted`
28869
+ detail: `producer_route_unsupported: role '${role}' has no roster`
28636
28870
  }
28637
28871
  };
28638
28872
  }
28639
- const role = agentId === "implementer" ? "writer" : agentId;
28640
- const roleBindings = listRoleBindings(projectRoot);
28641
- const roster = roleBindings[role];
28642
- const rosterSelectors = roster ? selectorsNamedByRoutes(projectRoot, roster) : void 0;
28643
- if (rosterSelectors && !rosterSelectors.includes(selector)) {
28873
+ const policy = loadRoutePolicy(projectRoot);
28874
+ for (const entry of roster) {
28875
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
28876
+ const record2 = entry;
28877
+ if (typeof record2.route !== "string" || record2.route.length === 0) continue;
28878
+ const modelPath = join15(projectRoot, ".kxm", "models", `${record2.route}.yaml`);
28879
+ const doc = readModelDocument(modelPath);
28880
+ if (!doc) {
28881
+ return {
28882
+ error: {
28883
+ reason: "step_unsupported",
28884
+ field: "model",
28885
+ detail: `producer_route_unsupported: model file '.kxm/models/${record2.route}.yaml' is missing or unreadable`
28886
+ }
28887
+ };
28888
+ }
28889
+ if (doc.status !== "admitted") continue;
28890
+ const modelName = typeof doc.model === "string" ? doc.model : "";
28891
+ const vendor = typeof doc.vendor === "string" ? doc.vendor : "";
28892
+ const harness = typeof doc.harness === "string" ? doc.harness : "";
28893
+ if (!modelName || !vendor || !harness) continue;
28894
+ const candidates = [modelName, `${vendor}/${modelName}`, `${harness}/${modelName}`];
28895
+ const admittedSelector = candidates.find((candidate) => policy.admitted.includes(candidate));
28896
+ const disabledSelector = candidates.find((candidate) => policy.disabled.includes(candidate));
28897
+ if (!admittedSelector && disabledSelector) {
28898
+ return {
28899
+ error: {
28900
+ reason: "step_unsupported",
28901
+ field: "model",
28902
+ detail: `producer_route_unsupported: first admitted route '${record2.route}' for role '${role}' is disabled`
28903
+ }
28904
+ };
28905
+ }
28906
+ const selector = admittedSelector ?? (modelName.includes("/") ? modelName : `${vendor}/${modelName}`);
28907
+ if (policy.disabled.includes(selector)) {
28908
+ return {
28909
+ error: {
28910
+ reason: "step_unsupported",
28911
+ field: "model",
28912
+ detail: `producer_route_unsupported: first admitted route '${record2.route}' for role '${role}' is disabled`
28913
+ }
28914
+ };
28915
+ }
28916
+ if (!policy.admitted.includes(selector)) {
28917
+ return {
28918
+ error: {
28919
+ reason: "step_unsupported",
28920
+ field: "model",
28921
+ detail: `producer_route_unsupported: model '${selector}' is not admitted`
28922
+ }
28923
+ };
28924
+ }
28925
+ const slash = selector.indexOf("/");
28926
+ if (slash <= 0 || slash === selector.length - 1) {
28927
+ return {
28928
+ error: {
28929
+ reason: "step_unsupported",
28930
+ field: "model",
28931
+ detail: `producer_route_unsupported: invalid selector '${selector}'`
28932
+ }
28933
+ };
28934
+ }
28935
+ const permissions = Array.isArray(doc.permissions) ? doc.permissions.filter((item) => typeof item === "string") : [];
28644
28936
  return {
28645
- error: {
28646
- reason: "step_unsupported",
28647
- field: "model",
28648
- detail: `producer_route_unsupported: model '${selector}' not in role '${role}' roster`
28649
- }
28937
+ provider: selector.slice(0, slash),
28938
+ model: selector.slice(slash + 1),
28939
+ selector,
28940
+ harness,
28941
+ routeId: record2.route,
28942
+ role,
28943
+ permissions,
28944
+ ...typeof record2.effort === "string" ? { effort: record2.effort } : {}
28650
28945
  };
28651
28946
  }
28652
- const provider = selector.slice(0, slash);
28653
- const model = selector.slice(slash + 1);
28654
- return { provider, model, selector };
28947
+ return {
28948
+ error: {
28949
+ reason: "step_unsupported",
28950
+ field: "model",
28951
+ detail: `producer_route_unsupported: role '${role}' has no admitted route`
28952
+ }
28953
+ };
28655
28954
  }
28656
28955
  function projectAgentStepTimeoutMs(context, run) {
28657
28956
  return loadKxmRunPlanEnvelope(context.eventStore, run).projectLimits.agentStepTimeoutMs ?? KXM_DEFAULT_AGENT_STEP_TIMEOUT_MS;
@@ -28869,8 +29168,7 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
28869
29168
  const writeRefusal = unsupportedLiveWrite(
28870
29169
  context.projectRoot,
28871
29170
  step,
28872
- agentId,
28873
- resolvedRoute.selector,
29171
+ resolvedRoute,
28874
29172
  loadKxmRunPlanEnvelope(context.eventStore, run).projectLimits.maxConcurrentRuns
28875
29173
  );
28876
29174
  if (writeRefusal) return { kind: "return", state, handoff: { ...writeRefusal, stepId } };
@@ -28899,7 +29197,7 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
28899
29197
  for (const event of events) context.eventStore.appendEvent(event);
28900
29198
  const entered = foldStoredKxmRun(context, run);
28901
29199
  persistKxmRunState(context, runId, entered, events[events.length - 1].sequence);
28902
- const first = birthMember(context, {
29200
+ const firstBirth = birthMember(context, {
28903
29201
  run,
28904
29202
  plan,
28905
29203
  step,
@@ -28910,6 +29208,10 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
28910
29208
  resolvedRoute,
28911
29209
  dispatchSources
28912
29210
  });
29211
+ if ("handoff" in firstBirth) {
29212
+ return { kind: "return", state: entered, handoff: firstBirth.handoff };
29213
+ }
29214
+ const first = firstBirth;
28913
29215
  return {
28914
29216
  kind: "panel",
28915
29217
  panel: {
@@ -28972,9 +29274,10 @@ function birthMember(context, input) {
28972
29274
  let resolvedRoute = input.resolvedRoute;
28973
29275
  if (!resolvedRoute && input.producerId && input.producerId !== "driver-simulated") {
28974
29276
  const routeResult = resolveProducerRoute(context.projectRoot, input.step, agentId);
28975
- if (!("error" in routeResult)) {
28976
- resolvedRoute = routeResult;
29277
+ if ("error" in routeResult) {
29278
+ return { handoff: { ...routeResult.error, stepId: input.stepId } };
28977
29279
  }
29280
+ resolvedRoute = routeResult;
28978
29281
  }
28979
29282
  const dispatchContext = assembleDispatchContext(input.dispatchSources, {
28980
29283
  projectId: run.projectId,
@@ -29081,11 +29384,11 @@ function birthMember(context, input) {
29081
29384
  prompt: input.step.instructions ? `${input.step.instructions}
29082
29385
 
29083
29386
  ${generatedPrompt}` : generatedPrompt,
29084
- thinking: input.stepAttempt <= 1 ? "low" : "medium",
29085
29387
  permission: Object.values(input.step.repositories).some((access) => access === "write") ? "edit" : "read-only",
29086
29388
  ...(input.step.kind === "agent" || input.step.kind === "moa") && input.step.timeoutMs !== void 0 ? { timeoutMs: input.step.timeoutMs } : {},
29087
29389
  contextPacket,
29088
- ...resolvedRoute ? { provider: resolvedRoute.provider, model: resolvedRoute.model } : {}
29390
+ ...resolvedRoute?.effort !== void 0 ? { thinking: resolvedRoute.effort } : {},
29391
+ ...resolvedRoute ? { harness: resolvedRoute.harness, provider: resolvedRoute.provider, model: resolvedRoute.model } : {}
29089
29392
  },
29090
29393
  controller,
29091
29394
  state: next
@@ -29230,13 +29533,14 @@ async function drivePanel(context, panel, producer) {
29230
29533
  })();
29231
29534
  pending.set(member.attemptId, work);
29232
29535
  };
29536
+ let routeHandoff;
29233
29537
  const tryBirth = () => {
29234
- if (stopBirths) return void 0;
29538
+ if (stopBirths || routeHandoff) return void 0;
29235
29539
  return context.eventStore.transaction(() => {
29236
29540
  const run = requireRun(context, runId);
29237
29541
  const state = foldStoredKxmRun(context, run);
29238
29542
  if (!birthAllowed(state, panel.step)) return void 0;
29239
- return birthMember(context, {
29543
+ const born = birthMember(context, {
29240
29544
  run,
29241
29545
  plan: panel.plan,
29242
29546
  step: panel.step,
@@ -29245,6 +29549,11 @@ async function drivePanel(context, panel, producer) {
29245
29549
  producerId: producer.id,
29246
29550
  dispatchSources: panel.dispatchSources
29247
29551
  });
29552
+ if ("handoff" in born) {
29553
+ routeHandoff = born.handoff;
29554
+ return void 0;
29555
+ }
29556
+ return born;
29248
29557
  });
29249
29558
  };
29250
29559
  const settleInvoked = (member, produced) => {
@@ -29288,6 +29597,10 @@ async function drivePanel(context, panel, producer) {
29288
29597
  while (!stopBirths && !settlementFailed) {
29289
29598
  try {
29290
29599
  const next = tryBirth();
29600
+ if (routeHandoff) {
29601
+ stopBirths = true;
29602
+ break;
29603
+ }
29291
29604
  if (!next) break;
29292
29605
  launch(next);
29293
29606
  } catch {
@@ -29298,6 +29611,13 @@ async function drivePanel(context, panel, producer) {
29298
29611
  return unreconciledHandoff(state, panel.stepId);
29299
29612
  }
29300
29613
  }
29614
+ if (routeHandoff) {
29615
+ const handoff = routeHandoff;
29616
+ abortOwned();
29617
+ await drainPendingInvoked();
29618
+ const state = foldStoredKxmRun(context, requireRun(context, runId));
29619
+ return { state, handoff };
29620
+ }
29301
29621
  if (pending.size === 0) break;
29302
29622
  const finished = await Promise.race(pending.values());
29303
29623
  pending.delete(finished.attemptId);
@@ -29861,25 +30181,7 @@ function unsupportedStep(plan, step, producerId) {
29861
30181
  }
29862
30182
  return void 0;
29863
30183
  }
29864
- function readYamlRecord(path) {
29865
- if (!existsSync12(path)) return void 0;
29866
- try {
29867
- const parsed = (0, import_yaml9.parse)(readFileSync10(path, "utf8"));
29868
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
29869
- } catch {
29870
- return void 0;
29871
- }
29872
- return void 0;
29873
- }
29874
- function agentHarness(projectRoot, agentId) {
29875
- const harness = readYamlRecord(join15(projectRoot, ".kxm", "agents", `${agentId}.yaml`))?.harness;
29876
- return typeof harness === "string" && harness.length > 0 ? harness : void 0;
29877
- }
29878
- function projectDefaultHarness(projectRoot) {
29879
- const harness = readYamlRecord(join15(projectRoot, ".kxm", "project.yaml"))?.defaultHarness;
29880
- return typeof harness === "string" && harness.length > 0 ? harness : "pi";
29881
- }
29882
- function unsupportedLiveWrite(projectRoot, step, agentId, selector, maxConcurrentRuns) {
30184
+ function unsupportedLiveWrite(projectRoot, step, route, maxConcurrentRuns) {
29883
30185
  if (!Object.values(step.repositories).some((access) => access === "write")) return void 0;
29884
30186
  if (step.assignments.maximum !== 1) {
29885
30187
  return {
@@ -29895,42 +30197,23 @@ function unsupportedLiveWrite(projectRoot, step, agentId, selector, maxConcurren
29895
30197
  detail: "live write steps require limits.maxConcurrentRuns of 1; concurrent runs share one checkout"
29896
30198
  };
29897
30199
  }
29898
- const harness = agentHarness(projectRoot, agentId) ?? projectDefaultHarness(projectRoot);
29899
- if (!oneShotWriterArgs(harness)) {
30200
+ if (!oneShotWriterArgs(route.harness)) {
29900
30201
  return {
29901
30202
  reason: "step_unsupported",
29902
30203
  field: "repositories",
29903
- detail: `live write steps require an audited writer profile; ${harness} has none`
29904
- };
29905
- }
29906
- const rosterPath = join15(projectRoot, ".kxm", "roster.yaml");
29907
- if (!existsSync12(rosterPath)) return void 0;
29908
- const roster = readYamlRecord(rosterPath);
29909
- if (!roster || roster.schema !== "kxm.developer-roster.v1") {
29910
- return { reason: "step_unsupported", field: "model", detail: "live write steps require a readable kxm.developer-roster.v1" };
29911
- }
29912
- const routes = roster.routes;
29913
- const lineup = roster.lineup;
29914
- const writerIds = lineup && typeof lineup === "object" && !Array.isArray(lineup) ? lineup.writer : void 0;
29915
- if (!routes || typeof routes !== "object" || Array.isArray(routes) || !Array.isArray(writerIds)) {
29916
- return { reason: "step_unsupported", field: "model", detail: "developer roster has no writer lineup" };
29917
- }
29918
- const allowed = writerIds.some((id) => {
29919
- if (typeof id !== "string") return false;
29920
- const route = routes[id];
29921
- if (!route || typeof route !== "object" || Array.isArray(route)) return false;
29922
- const record2 = route;
29923
- if (record2.harness !== harness || record2.status !== "admitted") return false;
29924
- if (!Array.isArray(record2.permissions) || !record2.permissions.includes("edit")) return false;
29925
- const model = typeof record2.model === "string" ? record2.model : "";
29926
- const vendor = typeof record2.vendor === "string" ? record2.vendor : "";
29927
- return model === selector || vendor.length > 0 && `${vendor}/${model}` === selector;
29928
- });
29929
- if (!allowed) {
30204
+ detail: `live write steps require an audited writer profile; ${route.harness} has none`
30205
+ };
30206
+ }
30207
+ const writerIds = listRoleBindings(projectRoot).writer;
30208
+ if (!Array.isArray(writerIds) || writerIds.length === 0) {
30209
+ return { reason: "step_unsupported", field: "model", detail: "writer roster has no admitted edit route" };
30210
+ }
30211
+ const named = writerIds.includes(route.routeId);
30212
+ if (!named || !route.permissions.includes("edit")) {
29930
30213
  return {
29931
30214
  reason: "step_unsupported",
29932
30215
  field: "model",
29933
- detail: `live write route ${selector} on ${harness} is not on the developer roster writer lineup`
30216
+ detail: `live write route ${route.selector} on ${route.harness} is not an admitted edit route on the writer roster`
29934
30217
  };
29935
30218
  }
29936
30219
  return void 0;
@@ -30367,20 +30650,11 @@ function determineOutcome(text, allowedOutcomes) {
30367
30650
  return "failed";
30368
30651
  }
30369
30652
  function createKxmOneShotProducer(options = {}) {
30370
- const defaultHarness = options.defaultHarness ?? "claude";
30371
30653
  const running = /* @__PURE__ */ new Map();
30372
30654
  let closed = false;
30373
- function resolveHarnessForRequest(request) {
30655
+ function requireHarness(request) {
30374
30656
  if (request.harness) return request.harness;
30375
- if (options.resolveHarness) {
30376
- const resolved = options.resolveHarness(request.agentId, request.runId);
30377
- if (resolved) return resolved;
30378
- }
30379
- if (options.resolveModel) {
30380
- const resolved = options.resolveModel(request.agentId, request.runId);
30381
- if (resolved?.harness) return resolved.harness;
30382
- }
30383
- return defaultHarness;
30657
+ throw new Error("producer_harness_required");
30384
30658
  }
30385
30659
  function parseModelString(spec, harness) {
30386
30660
  const trimmed = spec.trim();
@@ -30442,7 +30716,7 @@ function createKxmOneShotProducer(options = {}) {
30442
30716
  }
30443
30717
  async function executeRequest(request) {
30444
30718
  const startTime = Date.now();
30445
- const harness = resolveHarnessForRequest(request);
30719
+ const harness = requireHarness(request);
30446
30720
  const resolved = resolveModelForRequest(request, harness);
30447
30721
  if (closed) throw new Error("oneshot_producer_closed");
30448
30722
  const cancelled = () => ({
@@ -30789,7 +31063,7 @@ var RuntimeHubClient = class {
30789
31063
  // plugins/kxm/src/hub-binding.ts
30790
31064
  import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
30791
31065
  import { homedir as homedir2 } from "node:os";
30792
- import { dirname as dirname8, isAbsolute as isAbsolute5, join as join16, resolve as resolve9 } from "node:path";
31066
+ import { dirname as dirname8, isAbsolute as isAbsolute6, join as join16, resolve as resolve9 } from "node:path";
30793
31067
  var HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
30794
31068
  var HubBindingError = class extends Error {
30795
31069
  constructor(message) {
@@ -30800,17 +31074,17 @@ var HubBindingError = class extends Error {
30800
31074
  function resolveUserStateRoot(env) {
30801
31075
  const explicit = env.KXM_STATE_HOME?.trim();
30802
31076
  if (explicit) {
30803
- if (!isAbsolute5(explicit)) throw new HubBindingError("local_state_root_not_absolute");
31077
+ if (!isAbsolute6(explicit)) throw new HubBindingError("local_state_root_not_absolute");
30804
31078
  return resolve9(explicit);
30805
31079
  }
30806
31080
  if (process.platform === "win32") {
30807
31081
  const localAppData = env.LOCALAPPDATA?.trim();
30808
- const base2 = localAppData && isAbsolute5(localAppData) ? localAppData : join16(homedir2(), "AppData", "Local");
31082
+ const base2 = localAppData && isAbsolute6(localAppData) ? localAppData : join16(homedir2(), "AppData", "Local");
30809
31083
  return resolve9(base2, "KXM");
30810
31084
  }
30811
31085
  if (process.platform === "darwin") return resolve9(homedir2(), "Library", "Application Support", "KXM");
30812
31086
  const xdgState = env.XDG_STATE_HOME?.trim();
30813
- const base = xdgState && isAbsolute5(xdgState) ? xdgState : join16(homedir2(), ".local", "state");
31087
+ const base = xdgState && isAbsolute6(xdgState) ? xdgState : join16(homedir2(), ".local", "state");
30814
31088
  return resolve9(base, "kxm");
30815
31089
  }
30816
31090
  function hubBindingFile(env = process.env) {
@@ -30861,7 +31135,7 @@ function readHubBinding(env = process.env) {
30861
31135
  // plugins/kxm/src/hub-env.ts
30862
31136
  import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync12, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
30863
31137
  import { homedir as homedir3 } from "node:os";
30864
- import { dirname as dirname9, isAbsolute as isAbsolute6, join as join17, resolve as resolve10 } from "node:path";
31138
+ import { dirname as dirname9, isAbsolute as isAbsolute7, join as join17, resolve as resolve10 } from "node:path";
30865
31139
  var HUB_ENV_SCHEMA = "kxm.hub-env.v1";
30866
31140
  var HubEnvError = class extends Error {
30867
31141
  constructor(message) {
@@ -30872,17 +31146,17 @@ var HubEnvError = class extends Error {
30872
31146
  function resolveUserStateRoot2(env) {
30873
31147
  const explicit = env.KXM_STATE_HOME?.trim();
30874
31148
  if (explicit) {
30875
- if (!isAbsolute6(explicit)) throw new HubEnvError("local_state_root_not_absolute");
31149
+ if (!isAbsolute7(explicit)) throw new HubEnvError("local_state_root_not_absolute");
30876
31150
  return resolve10(explicit);
30877
31151
  }
30878
31152
  if (process.platform === "win32") {
30879
31153
  const localAppData = env.LOCALAPPDATA?.trim();
30880
- const base2 = localAppData && isAbsolute6(localAppData) ? localAppData : join17(homedir3(), "AppData", "Local");
31154
+ const base2 = localAppData && isAbsolute7(localAppData) ? localAppData : join17(homedir3(), "AppData", "Local");
30881
31155
  return resolve10(base2, "KXM");
30882
31156
  }
30883
31157
  if (process.platform === "darwin") return resolve10(homedir3(), "Library", "Application Support", "KXM");
30884
31158
  const xdgState = env.XDG_STATE_HOME?.trim();
30885
- const base = xdgState && isAbsolute6(xdgState) ? xdgState : join17(homedir3(), ".local", "state");
31159
+ const base = xdgState && isAbsolute7(xdgState) ? xdgState : join17(homedir3(), ".local", "state");
30886
31160
  return resolve10(base, "kxm");
30887
31161
  }
30888
31162
  function hubEnvFile(env = process.env) {
@@ -30933,7 +31207,7 @@ function resolveClientHubAuthToken(env, project) {
30933
31207
  }
30934
31208
 
30935
31209
  // plugins/kxm/src/logger.ts
30936
- import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync as statSync4, unlinkSync as unlinkSync2 } from "node:fs";
31210
+ import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync as statSync5, unlinkSync as unlinkSync2 } from "node:fs";
30937
31211
  import { dirname as dirname10 } from "node:path";
30938
31212
  var LOG_LEVEL_PRIORITY = {
30939
31213
  debug: 10,
@@ -31004,7 +31278,7 @@ function createLogger(options) {
31004
31278
  let currentSize = 0;
31005
31279
  if (filePath && existsSync15(filePath)) {
31006
31280
  try {
31007
- currentSize = statSync4(filePath).size;
31281
+ currentSize = statSync5(filePath).size;
31008
31282
  } catch {
31009
31283
  currentSize = 0;
31010
31284
  }
@@ -31151,6 +31425,24 @@ function readRecentSupervisorError(paths) {
31151
31425
  return void 0;
31152
31426
  }
31153
31427
  }
31428
+ var SETTLED_RUN_STATUS = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
31429
+ function kxmSupervisorRecordIsLive(record2) {
31430
+ return supervisorStatusOf(record2).running;
31431
+ }
31432
+ function unsettledRunIds(context) {
31433
+ const ids = [];
31434
+ for (const stored of context.eventStore.runsForProject(context.projectId, 1e4)) {
31435
+ let status = stored.status;
31436
+ try {
31437
+ status = projectKxmRunReadOnly(context, stored.runId).status;
31438
+ } catch {
31439
+ ids.push(stored.runId);
31440
+ continue;
31441
+ }
31442
+ if (!SETTLED_RUN_STATUS.has(status)) ids.push(stored.runId);
31443
+ }
31444
+ return ids;
31445
+ }
31154
31446
  function supervisorStatusOf(record2) {
31155
31447
  if (!record2) return { running: false };
31156
31448
  const heartbeatAgeMs = Date.now() - Date.parse(record2.heartbeatAt);
@@ -31499,7 +31791,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
31499
31791
  }
31500
31792
  };
31501
31793
  const contextFor = (projectRoot) => {
31502
- if (!isAbsolute7(projectRoot)) {
31794
+ if (!isAbsolute8(projectRoot)) {
31503
31795
  throw runtimeError("runtime_request_invalid", "projectRoot", "projectRoot must be an absolute path");
31504
31796
  }
31505
31797
  const key = projectRuntimeKey(projectRoot);
@@ -31565,6 +31857,62 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
31565
31857
  sendJson(response, 200, { ok: true, projectId: context.projectId, retried, outbox: context.eventStore.outboxStatus() });
31566
31858
  return;
31567
31859
  }
31860
+ if (request.method === "POST" && url.pathname === "/v1/projects/unregister") {
31861
+ const body = await readJsonBody(request);
31862
+ const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
31863
+ const force = body.force === true;
31864
+ if (!projectRoot || !isAbsolute8(projectRoot)) {
31865
+ sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: "projectRoot must be an absolute path" });
31866
+ return;
31867
+ }
31868
+ if (!force) {
31869
+ const key2 = projectRuntimeKey(projectRoot);
31870
+ const existing2 = contexts.get(key2);
31871
+ let openRunIds = [];
31872
+ if (existing2) {
31873
+ openRunIds = unsettledRunIds(existing2);
31874
+ } else {
31875
+ const registration = registry.projectByRoot(projectRoot);
31876
+ const eventsPath = join18(paths.projectsDir, registration?.projectKey ?? projectRuntimeKey(projectRoot), "run-events.db");
31877
+ if (registration && existsSync16(eventsPath)) {
31878
+ const store = new KxmRunEventStore(eventsPath);
31879
+ try {
31880
+ openRunIds = store.runsForProject(registration.projectId, 1e4).filter((run) => !SETTLED_RUN_STATUS.has(run.status)).map((run) => run.runId);
31881
+ } finally {
31882
+ store.close();
31883
+ }
31884
+ }
31885
+ }
31886
+ if (openRunIds.length > 0) {
31887
+ sendJson(response, 409, {
31888
+ ok: false,
31889
+ error: "runtime_project_busy",
31890
+ message: `unsettled run ${openRunIds.join(", ")}`,
31891
+ runIds: openRunIds
31892
+ });
31893
+ return;
31894
+ }
31895
+ const laneCount = registry.homeLaneCount(projectRoot);
31896
+ if (laneCount > 0) {
31897
+ sendJson(response, 409, {
31898
+ ok: false,
31899
+ error: "runtime_project_has_lanes",
31900
+ message: `home root still has ${laneCount} lane(s)`
31901
+ });
31902
+ return;
31903
+ }
31904
+ }
31905
+ const key = projectRuntimeKey(projectRoot);
31906
+ const existing = contexts.get(key);
31907
+ const unregistered = registry.unregisterProject(projectRoot);
31908
+ if (existing) {
31909
+ contexts.delete(key);
31910
+ syncStatuses.delete(key);
31911
+ closeKxmRuntimeContext(existing);
31912
+ }
31913
+ sendJson(response, 200, { ok: true, unregistered });
31914
+ return;
31915
+ }
31568
31916
  if (request.method === "POST" && url.pathname === "/v1/runs") {
31569
31917
  const body = await readJsonBody(request);
31570
31918
  const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
@@ -31618,24 +31966,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
31618
31966
  const delayMs = typeof body.delayMs === "number" && body.delayMs > 0 ? body.delayMs : 0;
31619
31967
  const createProducer = () => body.mode === "live" ? createKxmOneShotProducer({
31620
31968
  projectRoot,
31621
- timeoutMs: kxmProjectAdmissionLimits(bundle).agentStepTimeoutMs,
31622
- defaultHarness: String(bundle.project.value.defaultHarness ?? "pi"),
31623
- resolveHarness: (agentId) => {
31624
- const agent = bundle.agents.get(agentId);
31625
- return typeof agent?.value.harness === "string" ? agent.value.harness : void 0;
31626
- },
31627
- resolveModel: (agentId) => {
31628
- const agent = bundle.agents.get(agentId);
31629
- const model = agent?.value.model;
31630
- if (!model || typeof model !== "object" || Array.isArray(model)) return void 0;
31631
- const value = model;
31632
- const provider = typeof value.provider === "string" ? value.provider : void 0;
31633
- const modelName = typeof value.model === "string" ? value.model : void 0;
31634
- if (!provider || !modelName || !isRouteAdmitted(projectRoot, `${provider}/${modelName}`)) {
31635
- throw new Error("producer_route_not_admitted");
31636
- }
31637
- return { provider, model: modelName };
31638
- }
31969
+ timeoutMs: kxmProjectAdmissionLimits(bundle).agentStepTimeoutMs
31639
31970
  }) : createKxmSimulatedProducer(async () => {
31640
31971
  if (delayMs > 0) {
31641
31972
  await new Promise((r) => setTimeout(r, delayMs));
@@ -31788,7 +32119,10 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
31788
32119
  sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: `project ${requestedProjectId} is not the bound project ${context.projectId}` });
31789
32120
  return;
31790
32121
  }
31791
- const runs = context.eventStore.runsForProject(requestedProjectId, 50).map((stored) => {
32122
+ const limitText = url.searchParams.get("limit");
32123
+ const parsedLimit = limitText === null ? 50 : Number(limitText);
32124
+ const limit = Number.isInteger(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 1e4) : 50;
32125
+ const runs = context.eventStore.runsForProject(requestedProjectId, limit).map((stored) => {
31792
32126
  try {
31793
32127
  return projectKxmRunReadOnly(context, stored.runId);
31794
32128
  } catch (error) {
@@ -33679,8 +34013,8 @@ ${divider}
33679
34013
  }
33680
34014
 
33681
34015
  // plugins/kxm/src/ssh-remote.ts
33682
- import { spawnSync as spawnSync5 } from "node:child_process";
33683
- import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync16, readdirSync as readdirSync9, rmSync as rmSync5, statSync as statSync5 } from "node:fs";
34016
+ import { spawnSync as spawnSync6 } from "node:child_process";
34017
+ import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync16, readdirSync as readdirSync9, rmSync as rmSync5, statSync as statSync6 } from "node:fs";
33684
34018
  import { homedir as homedir4 } from "node:os";
33685
34019
  import { join as join22, resolve as resolve12 } from "node:path";
33686
34020
  var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
@@ -33755,7 +34089,7 @@ function parseSshConfig(configPath) {
33755
34089
  return [];
33756
34090
  }
33757
34091
  }
33758
- function resolveSshHostG(host, execFn = spawnSync5) {
34092
+ function resolveSshHostG(host, execFn = spawnSync6) {
33759
34093
  try {
33760
34094
  const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
33761
34095
  if (result.status !== 0 || !result.stdout) {
@@ -33821,7 +34155,7 @@ function buildSshArgs(options) {
33821
34155
  args.push(options.host);
33822
34156
  return args;
33823
34157
  }
33824
- function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync5) {
34158
+ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync6) {
33825
34159
  const resolvedDir = ensureSocketDir(socketDir);
33826
34160
  const controlPath = join22(resolvedDir, "%C");
33827
34161
  try {
@@ -33833,7 +34167,7 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
33833
34167
  return false;
33834
34168
  }
33835
34169
  }
33836
- function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync5) {
34170
+ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync6) {
33837
34171
  const resolvedDir = ensureSocketDir(socketDir);
33838
34172
  const controlPath = join22(resolvedDir, "%C");
33839
34173
  try {
@@ -33853,7 +34187,7 @@ function pruneSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
33853
34187
  for (const entry of entries) {
33854
34188
  const fullPath = join22(resolvedDir, entry);
33855
34189
  try {
33856
- const stat = statSync5(fullPath);
34190
+ const stat = statSync6(fullPath);
33857
34191
  if (stat.isSocket()) {
33858
34192
  rmSync5(fullPath, { force: true });
33859
34193
  removed++;
@@ -33867,7 +34201,7 @@ function pruneSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
33867
34201
  }
33868
34202
  function executeSshRun(params) {
33869
34203
  const startTime = Date.now();
33870
- const execSyncFn = params.execFn ?? spawnSync5;
34204
+ const execSyncFn = params.execFn ?? spawnSync6;
33871
34205
  if (params.action === "info") {
33872
34206
  if (params.host) {
33873
34207
  const hostInfo = resolveSshHostG(params.host, execSyncFn);
@@ -34314,6 +34648,7 @@ export {
34314
34648
  acceptKxmRun,
34315
34649
  applyRestorePlan,
34316
34650
  assertClosedGateObservation,
34651
+ assertDatabaseFile,
34317
34652
  assertKxmConfigError,
34318
34653
  attachKxmSupervisor,
34319
34654
  backupDatabaseFile,
@@ -34381,6 +34716,7 @@ export {
34381
34716
  kxmRunRevisionDrift,
34382
34717
  kxmRuntimePaths,
34383
34718
  kxmRuntimeRequest,
34719
+ kxmSupervisorRecordIsLive,
34384
34720
  kxmSupervisorStatus,
34385
34721
  kxmSupervisorTokenFile,
34386
34722
  kxmToolPolicyRevision,