@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.kxm/README.md +4 -1
- package/.kxm/agents/coordinator.yaml +1 -0
- package/.kxm/agents/critic-arch.yaml +1 -4
- package/.kxm/agents/critic-cli.yaml +1 -4
- package/.kxm/agents/implementer.yaml +1 -4
- package/.kxm/workflows/implement-only.yaml +1 -1
- package/.kxm/workflows/review-arch-only.yaml +1 -1
- package/.kxm/workflows/review-cli-only.yaml +1 -1
- package/CHANGELOG.md +42 -1
- package/docs/architecture/inventory.md +1 -1
- package/docs/concepts/architecture.md +1 -1
- package/docs/concepts/data-and-storage.md +1 -1
- package/docs/contracts/README.md +1 -1
- package/docs/contracts/migration.md +12 -8
- package/docs/contracts/routing.md +3 -3
- package/docs/contributing/assignment-runner.md +14 -15
- package/docs/contributing/development.md +22 -6
- package/docs/contributing/harness-routing-internals.md +8 -8
- package/docs/glossary.md +1 -1
- package/docs/guides/agent-skills.md +1 -1
- package/docs/operations/backup-and-restore.md +7 -7
- package/docs/operations/upgrade.md +2 -2
- package/docs/reference/cli-reference.md +37 -14
- package/docs/reference/config-reference.md +81 -155
- package/docs/reference/harness-routing.md +29 -21
- package/docs/reference/workflow-catalog.md +4 -4
- package/docs/start/first-workflow.md +1 -1
- package/examples/project/.kxm/agents/coordinator.yaml +1 -2
- package/examples/project/.kxm/agents/critic-1.yaml +1 -3
- package/examples/project/.kxm/agents/critic-2.yaml +1 -2
- package/examples/project/.kxm/agents/critic-3.yaml +2 -3
- package/examples/project/.kxm/agents/implementer.yaml +1 -2
- package/examples/project/.kxm/agents/planner.yaml +1 -2
- package/examples/project/.kxm/agents/reproducer.yaml +1 -2
- package/examples/project/.kxm/agents/reviewer.yaml +1 -2
- package/examples/project/.kxm/workflows/fix.yaml +0 -12
- package/package.json +8 -8
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +2565 -465
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +683 -348
- package/plugins/kxm/dist/runtime.js +694 -358
- package/plugins/kxm/dist/server.js +17 -14
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +8 -0
- package/plugins/kxm/src/cli/lanes.ts +109 -4
- package/plugins/kxm/src/cli/project.ts +6 -6
- package/plugins/kxm/src/database.ts +25 -19
- package/plugins/kxm/src/engine.ts +168 -163
- package/plugins/kxm/src/init-guide-setup.ts +75 -15
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/oneshot-producer.ts +3 -15
- package/plugins/kxm/src/project-config.ts +83 -70
- package/plugins/kxm/src/routes.ts +26 -18
- package/plugins/kxm/src/runtime-service.ts +1 -0
- package/plugins/kxm/src/runtime-store.ts +390 -77
- package/plugins/kxm/src/runtime-supervisor.ts +87 -21
- package/plugins/kxm/src/suggest.ts +2 -2
- package/plugins/kxm/src/template.ts +2 -14
- package/schemas/agent.schema.json +8 -0
- package/scripts/roster-policy.d.mts +2 -1
- package/scripts/roster-policy.mjs +119 -22
- 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)
|
|
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,
|
|
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
|
-
|
|
18044
|
-
|
|
18045
|
-
|
|
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
|
-
|
|
18048
|
-
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
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
|
-
|
|
18090
|
-
|
|
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
|
|
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/
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
-
/**
|
|
19706
|
-
|
|
19707
|
-
*
|
|
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
|
|
19721
|
-
|
|
19722
|
-
|
|
19723
|
-
|
|
19724
|
-
|
|
19725
|
-
|
|
19726
|
-
|
|
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
|
-
|
|
19744
|
-
|
|
19745
|
-
|
|
19746
|
-
|
|
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.
|
|
19766
|
-
|
|
19767
|
-
projectId
|
|
19768
|
-
|
|
19769
|
-
|
|
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
|
|
19777
|
-
|
|
19778
|
-
|
|
19779
|
-
|
|
19780
|
-
|
|
19781
|
-
|
|
19782
|
-
|
|
19783
|
-
|
|
19784
|
-
|
|
19785
|
-
|
|
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
|
|
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) &&
|
|
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: []
|
|
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") : []
|
|
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
|
|
25228
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
24959
25229
|
function gitText(cwd, args) {
|
|
24960
|
-
const result =
|
|
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
|
|
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
|
|
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) =>
|
|
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 =
|
|
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
|
|
26620
|
-
import { isAbsolute as
|
|
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 === "" || !
|
|
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 =
|
|
26904
|
+
realRoot = realpathSync4(lexicalRoot);
|
|
26635
26905
|
} catch {
|
|
26636
26906
|
return { ok: false, error: "artifact_unreadable", path: lexicalPath };
|
|
26637
26907
|
}
|
|
26638
26908
|
try {
|
|
26639
|
-
realPath =
|
|
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 =
|
|
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
|
|
28545
|
-
|
|
28546
|
-
|
|
28547
|
-
|
|
28548
|
-
|
|
28549
|
-
|
|
28550
|
-
|
|
28551
|
-
|
|
28552
|
-
|
|
28553
|
-
|
|
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
|
-
|
|
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
|
|
28847
|
+
detail: "producer_route_unsupported: agent step model is not honored"
|
|
28617
28848
|
}
|
|
28618
28849
|
};
|
|
28619
28850
|
}
|
|
28620
|
-
const
|
|
28621
|
-
|
|
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:
|
|
28858
|
+
detail: "producer_route_unsupported: agent has no role"
|
|
28627
28859
|
}
|
|
28628
28860
|
};
|
|
28629
28861
|
}
|
|
28630
|
-
|
|
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:
|
|
28869
|
+
detail: `producer_route_unsupported: role '${role}' has no roster`
|
|
28636
28870
|
}
|
|
28637
28871
|
};
|
|
28638
28872
|
}
|
|
28639
|
-
const
|
|
28640
|
-
const
|
|
28641
|
-
|
|
28642
|
-
|
|
28643
|
-
|
|
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
|
-
|
|
28646
|
-
|
|
28647
|
-
|
|
28648
|
-
|
|
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
|
-
|
|
28653
|
-
|
|
28654
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
28976
|
-
|
|
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 ? {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
29907
|
-
if (!
|
|
29908
|
-
|
|
29909
|
-
|
|
29910
|
-
|
|
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
|
|
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
|
|
30655
|
+
function requireHarness(request) {
|
|
30374
30656
|
if (request.harness) return request.harness;
|
|
30375
|
-
|
|
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 =
|
|
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
|
|
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 (!
|
|
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 &&
|
|
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 &&
|
|
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
|
|
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 (!
|
|
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 &&
|
|
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 &&
|
|
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
|
|
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 =
|
|
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 (!
|
|
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
|
|
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
|
|
33683
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync16, readdirSync as readdirSync9, rmSync as rmSync5, statSync as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ??
|
|
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,
|