@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
|
@@ -14783,7 +14783,7 @@ import { spawn as spawn3 } from "node:child_process";
|
|
|
14783
14783
|
import { createHash as createHash15, createHmac, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
14784
14784
|
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";
|
|
14785
14785
|
import { createServer } from "node:http";
|
|
14786
|
-
import { dirname as dirname11, isAbsolute as
|
|
14786
|
+
import { dirname as dirname11, isAbsolute as isAbsolute8, join as join18 } from "node:path";
|
|
14787
14787
|
|
|
14788
14788
|
// plugins/kxm/src/repo-root.ts
|
|
14789
14789
|
import { existsSync } from "node:fs";
|
|
@@ -15663,21 +15663,9 @@ function coreTemplate(projectId, projectName, variant) {
|
|
|
15663
15663
|
]);
|
|
15664
15664
|
if (variant === "v4-registry") {
|
|
15665
15665
|
const coordinator = files.get(".kxm/agents/coordinator.yaml");
|
|
15666
|
-
if (coordinator) {
|
|
15667
|
-
files.set(".kxm/agents/coordinator.yaml", {
|
|
15668
|
-
...coordinator,
|
|
15669
|
-
harness: "claude",
|
|
15670
|
-
model: { provider: "anthropic", model: "fable" }
|
|
15671
|
-
});
|
|
15672
|
-
}
|
|
15666
|
+
if (coordinator) files.set(".kxm/agents/coordinator.yaml", { ...coordinator, role: "planner" });
|
|
15673
15667
|
const implementer = files.get(".kxm/agents/implementer.yaml");
|
|
15674
|
-
if (implementer) {
|
|
15675
|
-
files.set(".kxm/agents/implementer.yaml", {
|
|
15676
|
-
...implementer,
|
|
15677
|
-
harness: "grok",
|
|
15678
|
-
model: { provider: "xai", model: "grok-4.6" }
|
|
15679
|
-
});
|
|
15680
|
-
}
|
|
15668
|
+
if (implementer) files.set(".kxm/agents/implementer.yaml", { ...implementer, role: "writer" });
|
|
15681
15669
|
const workflow = files.get(".kxm/workflows/default.yaml");
|
|
15682
15670
|
if (workflow) {
|
|
15683
15671
|
const limits = { ...workflow.limits };
|
|
@@ -17316,7 +17304,7 @@ function readTemplateProvenance(registry, root) {
|
|
|
17316
17304
|
}
|
|
17317
17305
|
return value;
|
|
17318
17306
|
}
|
|
17319
|
-
function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId) {
|
|
17307
|
+
function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId, collected) {
|
|
17320
17308
|
const resources = /* @__PURE__ */ new Map();
|
|
17321
17309
|
if (!existsSync3(directory)) return resources;
|
|
17322
17310
|
const stat = lstatSync(directory);
|
|
@@ -17355,7 +17343,10 @@ function listNamedResources(registry, root, directory, logicalDirectory, kind, r
|
|
|
17355
17343
|
else throw error;
|
|
17356
17344
|
}
|
|
17357
17345
|
}
|
|
17358
|
-
if (issues.length > 0)
|
|
17346
|
+
if (issues.length > 0) {
|
|
17347
|
+
if (collected) collected.push(...issues);
|
|
17348
|
+
else throw new KxmConfigError(issues);
|
|
17349
|
+
}
|
|
17359
17350
|
return resources;
|
|
17360
17351
|
}
|
|
17361
17352
|
function valuesOf(object, field) {
|
|
@@ -17619,6 +17610,9 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17619
17610
|
for (const repositoryId of Object.keys(objectValue(step.repositories) ?? {})) {
|
|
17620
17611
|
if (!repositories.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", file, `${stepId} references unknown repository ${repositoryId}`));
|
|
17621
17612
|
}
|
|
17613
|
+
if ((kind === "agent" || kind === "moa") && step.model !== void 0) {
|
|
17614
|
+
issues.push(issue3("semantic", "producer_route_unsupported", file, `${stepId} model is not honored; remove model from the step`));
|
|
17615
|
+
}
|
|
17622
17616
|
selectorCandidates(step.model, models, file, `${stepId}.model`, issues);
|
|
17623
17617
|
const assignment = objectValue(step.assignments);
|
|
17624
17618
|
const declaredAgents = assignment ? names(assignment.allowedAgents) : [];
|
|
@@ -17801,7 +17795,7 @@ function validateModelReferences(models, issues) {
|
|
|
17801
17795
|
};
|
|
17802
17796
|
for (const id of models.keys()) walk(id, []);
|
|
17803
17797
|
}
|
|
17804
|
-
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot,
|
|
17798
|
+
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot, roles = /* @__PURE__ */ new Map()) {
|
|
17805
17799
|
const issues = [];
|
|
17806
17800
|
const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
|
|
17807
17801
|
const repositoryIds = /* @__PURE__ */ new Set();
|
|
@@ -17842,84 +17836,84 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
17842
17836
|
const defaultHarness = stringValue(project.value.defaultHarness) ?? DEFAULT_HARNESS;
|
|
17843
17837
|
if (!harnesses.has(defaultHarness)) issues.push(issue3("reference", "harness_unknown", project.logicalPath, `defaultHarness ${defaultHarness} is not registered`));
|
|
17844
17838
|
for (const agent of agents.values()) {
|
|
17839
|
+
if (agent.value.model !== void 0 || agent.value.harness !== void 0) {
|
|
17840
|
+
issues.push(issue3("semantic", "retired_agent_routing_fields", agent.logicalPath, "routing resolves from role; remove model and harness"));
|
|
17841
|
+
}
|
|
17845
17842
|
validateToolPolicy(agent, objectValue(agent.value.tools), "tools", issues);
|
|
17846
17843
|
const executor = stringValue(agent.value.executor);
|
|
17847
17844
|
if (executor && !executors.has(executor)) issues.push(issue3("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
|
|
17848
|
-
const harness = stringValue(agent.value.harness) ?? defaultHarness;
|
|
17849
|
-
if (!harnesses.has(harness)) issues.push(issue3("reference", "harness_unknown", agent.logicalPath, `harness ${harness} is not registered`));
|
|
17850
17845
|
const preset = stringValue(objectValue(agent.value.tools)?.preset);
|
|
17851
17846
|
if (preset && !presets.has(preset)) issues.push(issue3("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
|
|
17852
17847
|
for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
|
|
17853
17848
|
if (!repositoryIds.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
|
|
17854
17849
|
}
|
|
17855
|
-
const candidates = selectorCandidates(agent.value.model, models, agent.logicalPath, "model", issues);
|
|
17856
|
-
const declaredHarness = stringValue(agent.value.harness);
|
|
17857
|
-
if (declaredHarness && harnesses.has(declaredHarness)) {
|
|
17858
|
-
for (const candidate of candidates) {
|
|
17859
|
-
const candidateProvider = stringValue(candidate.value.provider);
|
|
17860
|
-
const candidateModel = stringValue(candidate.value.model);
|
|
17861
|
-
const validation = validateHarnessModelPair(declaredHarness, { provider: candidateProvider, model: candidateModel });
|
|
17862
|
-
if (!validation.valid) {
|
|
17863
|
-
issues.push(issue3("semantic", validation.issue ?? "harness_unhosted_model", agent.logicalPath, validation.message ?? `harness ${declaredHarness} cannot host model ${candidateModel ?? candidate.logicalPath}`));
|
|
17864
|
-
}
|
|
17865
|
-
}
|
|
17866
|
-
}
|
|
17867
17850
|
}
|
|
17868
17851
|
validateModelReferences(models, issues);
|
|
17869
17852
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
17870
17853
|
if (!workflows.has(defaultWorkflow)) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
17871
17854
|
for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
|
|
17855
|
+
if (projectRoot && existsSync3(join2(projectRoot, ".kxm", "roster.yaml"))) {
|
|
17856
|
+
issues.push(issue3("semantic", "retired_roster_file", ".kxm/roster.yaml", "create the role and model files and delete roster.yaml"));
|
|
17857
|
+
}
|
|
17858
|
+
if (projectRoot && existsSync3(join2(projectRoot, ".kxm", "roles"))) {
|
|
17859
|
+
issues.push(...developerRolePolicyIssues(projectRoot));
|
|
17860
|
+
}
|
|
17872
17861
|
if (projectRoot) {
|
|
17873
|
-
|
|
17874
|
-
|
|
17875
|
-
|
|
17862
|
+
issues.push(...rosterModelIssues(projectRoot, roles));
|
|
17863
|
+
issues.push(...originHashIssues(projectRoot, models));
|
|
17864
|
+
}
|
|
17865
|
+
return sortIssues3(issues);
|
|
17866
|
+
}
|
|
17867
|
+
function rosterModelIssues(projectRoot, roles) {
|
|
17868
|
+
const issues = [];
|
|
17869
|
+
for (const role of roles.values()) {
|
|
17870
|
+
for (const entry of valuesOf(role.value, "roster")) {
|
|
17871
|
+
const route = stringValue(objectValue(entry)?.route);
|
|
17872
|
+
if (!route) continue;
|
|
17873
|
+
const logical = `.kxm/models/${route}.yaml`;
|
|
17874
|
+
const file = join2(projectRoot, ".kxm", "models", `${route}.yaml`);
|
|
17875
|
+
let harness;
|
|
17876
17876
|
try {
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
|
|
17882
|
-
|
|
17883
|
-
const named = direct ? [direct] : [];
|
|
17884
|
-
if (route) {
|
|
17885
|
-
const modelFile = join2(projectRoot, ".kxm", "models", `${route}.yaml`);
|
|
17886
|
-
if (existsSync3(modelFile)) {
|
|
17887
|
-
try {
|
|
17888
|
-
const modelDoc = objectValue(parseRestrictedYaml2(readFileSync(modelFile, "utf8"), modelFile));
|
|
17889
|
-
const model = modelDoc ? stringValue(modelDoc.model) : void 0;
|
|
17890
|
-
const vendor = modelDoc ? stringValue(modelDoc.vendor) : void 0;
|
|
17891
|
-
const harness = modelDoc ? stringValue(modelDoc.harness) : void 0;
|
|
17892
|
-
if (model) named.push(model);
|
|
17893
|
-
if (vendor && model) named.push(`${vendor}/${model}`);
|
|
17894
|
-
if (harness && model) named.push(`${harness}/${model}`);
|
|
17895
|
-
} catch {
|
|
17896
|
-
}
|
|
17897
|
-
}
|
|
17898
|
-
}
|
|
17899
|
-
return named;
|
|
17900
|
-
});
|
|
17901
|
-
const agentModelObj = objectValue(implementerAgent.value.model);
|
|
17902
|
-
const agentModelStr = stringValue(implementerAgent.value.model);
|
|
17903
|
-
const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
|
|
17904
|
-
const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
|
|
17905
|
-
const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
|
|
17906
|
-
if (enabledRosterModels.length > 0 && canonicalAgentModel) {
|
|
17907
|
-
const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
|
|
17908
|
-
if (!matches) {
|
|
17909
|
-
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}`));
|
|
17910
|
-
}
|
|
17911
|
-
}
|
|
17912
|
-
} catch (error) {
|
|
17913
|
-
if (error instanceof KxmConfigError) {
|
|
17914
|
-
issues.push(...error.issues);
|
|
17915
|
-
}
|
|
17877
|
+
if (existsSync3(file)) harness = stringValue(parseRestrictedYaml2(readFileSync(file, "utf8"), logical).harness);
|
|
17878
|
+
} catch {
|
|
17879
|
+
harness = void 0;
|
|
17880
|
+
}
|
|
17881
|
+
if (!harness) {
|
|
17882
|
+
issues.push(issue3("semantic", "roster_model_unreadable", role.logicalPath, `roster route ${route} does not resolve to a readable ${logical} carrying harness`));
|
|
17916
17883
|
}
|
|
17917
17884
|
}
|
|
17918
17885
|
}
|
|
17919
|
-
|
|
17920
|
-
|
|
17886
|
+
return issues;
|
|
17887
|
+
}
|
|
17888
|
+
function originHashIssues(projectRoot, models) {
|
|
17889
|
+
const issues = [];
|
|
17890
|
+
for (const model of models.values()) {
|
|
17891
|
+
const origin = objectValue(model.value.origin);
|
|
17892
|
+
const source = origin ? stringValue(origin.source) : void 0;
|
|
17893
|
+
const digest2 = origin ? stringValue(origin.sha256) : void 0;
|
|
17894
|
+
if (!source || !digest2) continue;
|
|
17895
|
+
const file = resolve(projectRoot, source);
|
|
17896
|
+
const escaped = relative(projectRoot, file);
|
|
17897
|
+
if (isAbsolute(source) || escaped.startsWith("..") || isAbsolute(escaped)) {
|
|
17898
|
+
issues.push(issue3("semantic", "origin_evidence_missing", model.logicalPath, `missing evidence bytes for ${source}`));
|
|
17899
|
+
continue;
|
|
17900
|
+
}
|
|
17901
|
+
let actual;
|
|
17902
|
+
try {
|
|
17903
|
+
actual = createHash3("sha256").update(readFileSync(file, "utf8"), "utf8").digest("hex");
|
|
17904
|
+
} catch (error) {
|
|
17905
|
+
const code = error.code;
|
|
17906
|
+
if (code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "EISDIR") {
|
|
17907
|
+
issues.push(issue3("semantic", "origin_evidence_missing", model.logicalPath, `missing evidence bytes for ${source}`));
|
|
17908
|
+
continue;
|
|
17909
|
+
}
|
|
17910
|
+
throw error;
|
|
17911
|
+
}
|
|
17912
|
+
if (actual !== digest2) {
|
|
17913
|
+
issues.push(issue3("semantic", "origin_hash_mismatch", model.logicalPath, "origin evidence hash does not match supplied bytes"));
|
|
17914
|
+
}
|
|
17921
17915
|
}
|
|
17922
|
-
return
|
|
17916
|
+
return issues;
|
|
17923
17917
|
}
|
|
17924
17918
|
function developerCeilings() {
|
|
17925
17919
|
const script = join2(findKxmRepoRoot(import.meta.url), "scripts", "harness-run.mjs");
|
|
@@ -17948,6 +17942,7 @@ function developerCeilings() {
|
|
|
17948
17942
|
}
|
|
17949
17943
|
}
|
|
17950
17944
|
function developerRolePolicyIssues(projectRoot) {
|
|
17945
|
+
if (resolve(projectRoot) !== findKxmRepoRoot(import.meta.url)) return [];
|
|
17951
17946
|
const rolesDir = join2(projectRoot, ".kxm", "roles");
|
|
17952
17947
|
const modelsDir = join2(projectRoot, ".kxm", "models");
|
|
17953
17948
|
const roles = {};
|
|
@@ -17968,6 +17963,7 @@ function developerRolePolicyIssues(projectRoot) {
|
|
|
17968
17963
|
parseIssues.push(...error.issues);
|
|
17969
17964
|
continue;
|
|
17970
17965
|
}
|
|
17966
|
+
throw error;
|
|
17971
17967
|
}
|
|
17972
17968
|
}
|
|
17973
17969
|
};
|
|
@@ -17982,7 +17978,7 @@ function developerRolePolicyIssues(projectRoot) {
|
|
|
17982
17978
|
}
|
|
17983
17979
|
const ceilings = developerCeilings();
|
|
17984
17980
|
if (!ceilings.ok) {
|
|
17985
|
-
return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/
|
|
17981
|
+
return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/models", `developer ceilings could not be loaded: ${ceilings.detail}`)];
|
|
17986
17982
|
}
|
|
17987
17983
|
const result = validatePolicyDraft({ models, roles, evidence }, {
|
|
17988
17984
|
ceilings: ceilings.ROUTES,
|
|
@@ -18071,7 +18067,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
18071
18067
|
}
|
|
18072
18068
|
if (earlyIssues.length > 0) throw new KxmConfigError(earlyIssues);
|
|
18073
18069
|
const agents = listNamedResources(registry, root, join2(root, ".kxm", "agents"), ".kxm/agents", "agent");
|
|
18074
|
-
const
|
|
18070
|
+
const modelIssues = [];
|
|
18071
|
+
const models = listNamedResources(registry, root, join2(root, ".kxm", "models"), ".kxm/models", "model", void 0, modelIssues);
|
|
18075
18072
|
const roles = listNamedResources(registry, root, join2(root, ".kxm", "roles"), ".kxm/roles", "role", writerRole === void 0 ? void 0 : "writer");
|
|
18076
18073
|
if (writerRole !== void 0) {
|
|
18077
18074
|
const logicalPath = ".kxm/roles/writer.yaml";
|
|
@@ -18198,8 +18195,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
18198
18195
|
}
|
|
18199
18196
|
}
|
|
18200
18197
|
}
|
|
18201
|
-
if (loadIssues.length > 0) throw new KxmConfigError(loadIssues);
|
|
18202
|
-
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root,
|
|
18198
|
+
if (loadIssues.length > 0) throw new KxmConfigError([...loadIssues, ...modelIssues]);
|
|
18199
|
+
const issues = [...modelIssues, ...validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, roles)];
|
|
18203
18200
|
if (issues.length > 0) throw new KxmConfigError(issues);
|
|
18204
18201
|
const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...roles.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits4(left.logicalPath, right.logicalPath));
|
|
18205
18202
|
return {
|
|
@@ -18244,9 +18241,10 @@ function legacyConfigFilesAt(root) {
|
|
|
18244
18241
|
}
|
|
18245
18242
|
|
|
18246
18243
|
// plugins/kxm/src/runtime-store.ts
|
|
18244
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
18247
18245
|
import { createHash as createHash6, randomUUID } from "node:crypto";
|
|
18248
|
-
import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18249
|
-
import { dirname as dirname5, resolve as resolve6 } from "node:path";
|
|
18246
|
+
import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync3, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18247
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, resolve as resolve6 } from "node:path";
|
|
18250
18248
|
|
|
18251
18249
|
// plugins/kxm/src/sqlite.ts
|
|
18252
18250
|
import { existsSync as existsSync4 } from "node:fs";
|
|
@@ -18760,22 +18758,25 @@ function ensureWalJournalMode(database, file, description, timeoutMs = 5e3) {
|
|
|
18760
18758
|
Atomics.wait(sleeper, 0, 0, 10);
|
|
18761
18759
|
}
|
|
18762
18760
|
}
|
|
18761
|
+
function assertDatabaseFile(file, description) {
|
|
18762
|
+
checkedParent(file, description);
|
|
18763
|
+
const stat = lstatSync2(file, { throwIfNoEntry: false });
|
|
18764
|
+
if (stat) {
|
|
18765
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
18766
|
+
throw databaseError("runtime_path_invalid", description, `${description} must be a regular file, not a link or directory`);
|
|
18767
|
+
}
|
|
18768
|
+
}
|
|
18769
|
+
for (const sidecar of [`${file}-wal`, `${file}-shm`]) {
|
|
18770
|
+
const info = lstatSync2(sidecar, { throwIfNoEntry: false });
|
|
18771
|
+
if (info?.isSymbolicLink()) {
|
|
18772
|
+
throw databaseError("runtime_path_invalid", description, `${description} sidecar must not be a link`);
|
|
18773
|
+
}
|
|
18774
|
+
}
|
|
18775
|
+
}
|
|
18763
18776
|
function openDatabase(file, description, spec) {
|
|
18764
18777
|
const isMemory = file === ":memory:";
|
|
18765
18778
|
if (!isMemory) {
|
|
18766
|
-
|
|
18767
|
-
const stat = lstatSync2(file, { throwIfNoEntry: false });
|
|
18768
|
-
if (stat) {
|
|
18769
|
-
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
18770
|
-
throw databaseError("runtime_path_invalid", description, `${description} must be a regular file, not a link or directory`);
|
|
18771
|
-
}
|
|
18772
|
-
}
|
|
18773
|
-
for (const sidecar of [`${file}-wal`, `${file}-shm`]) {
|
|
18774
|
-
const info = lstatSync2(sidecar, { throwIfNoEntry: false });
|
|
18775
|
-
if (info?.isSymbolicLink()) {
|
|
18776
|
-
throw databaseError("runtime_path_invalid", description, `${description} sidecar must not be a link`);
|
|
18777
|
-
}
|
|
18778
|
-
}
|
|
18779
|
+
assertDatabaseFile(file, description);
|
|
18779
18780
|
}
|
|
18780
18781
|
const database = new DatabaseSync(file);
|
|
18781
18782
|
let transaction = false;
|
|
@@ -18804,7 +18805,7 @@ function openDatabase(file, description, spec) {
|
|
|
18804
18805
|
throw databaseError(
|
|
18805
18806
|
"runtime_schema_outdated",
|
|
18806
18807
|
file,
|
|
18807
|
-
`${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
|
|
18808
|
+
`${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`
|
|
18808
18809
|
);
|
|
18809
18810
|
}
|
|
18810
18811
|
if (spec.tables) {
|
|
@@ -18917,10 +18918,10 @@ function runtimeIssue(phase, code, file, message) {
|
|
|
18917
18918
|
function runtimeError(code, file, message) {
|
|
18918
18919
|
return new KxmConfigError([runtimeIssue("semantic", code, file, message)]);
|
|
18919
18920
|
}
|
|
18920
|
-
var KXM_REGISTRY_SCHEMA_VERSION =
|
|
18921
|
+
var KXM_REGISTRY_SCHEMA_VERSION = 2;
|
|
18921
18922
|
var REGISTRY_TABLES = {
|
|
18922
18923
|
supervisor: ["singleton_id", "runtime_id", "pid", "port", "token_hash", "started_at", "heartbeat_at", "state"],
|
|
18923
|
-
projects: ["project_id", "project_root", "project_key", "home_runtime_id", "config_revision", "registered_at"]
|
|
18924
|
+
projects: ["project_id", "project_root", "project_key", "home_runtime_id", "config_revision", "registered_at", "lane_of"]
|
|
18924
18925
|
};
|
|
18925
18926
|
var REGISTRY_SCHEMA = `
|
|
18926
18927
|
CREATE TABLE supervisor (
|
|
@@ -18934,14 +18935,117 @@ CREATE TABLE supervisor (
|
|
|
18934
18935
|
state TEXT NOT NULL
|
|
18935
18936
|
) STRICT;
|
|
18936
18937
|
CREATE TABLE projects (
|
|
18937
|
-
|
|
18938
|
+
project_key TEXT PRIMARY KEY,
|
|
18939
|
+
project_id TEXT NOT NULL,
|
|
18938
18940
|
project_root TEXT NOT NULL,
|
|
18939
|
-
project_key TEXT NOT NULL UNIQUE,
|
|
18940
18941
|
home_runtime_id TEXT NOT NULL,
|
|
18941
18942
|
config_revision TEXT,
|
|
18942
|
-
registered_at TEXT NOT NULL
|
|
18943
|
+
registered_at TEXT NOT NULL,
|
|
18944
|
+
lane_of TEXT
|
|
18943
18945
|
) STRICT;
|
|
18946
|
+
CREATE INDEX projects_by_project_id ON projects (project_id);
|
|
18944
18947
|
`;
|
|
18948
|
+
var PROJECT_COLUMNS = "project_id, project_root, project_key, home_runtime_id, config_revision, registered_at, lane_of";
|
|
18949
|
+
function registrationFromRow(row) {
|
|
18950
|
+
return {
|
|
18951
|
+
projectId: row.project_id,
|
|
18952
|
+
projectRoot: row.project_root,
|
|
18953
|
+
projectKey: row.project_key,
|
|
18954
|
+
homeRuntimeId: row.home_runtime_id,
|
|
18955
|
+
...row.config_revision !== null ? { configRevision: row.config_revision } : {},
|
|
18956
|
+
...row.lane_of !== null ? { laneOf: row.lane_of } : {},
|
|
18957
|
+
registeredAt: row.registered_at
|
|
18958
|
+
};
|
|
18959
|
+
}
|
|
18960
|
+
function controlRootDirectoryExists(projectRoot) {
|
|
18961
|
+
const stat = statSync(projectRoot, { throwIfNoEntry: false });
|
|
18962
|
+
return Boolean(stat?.isDirectory());
|
|
18963
|
+
}
|
|
18964
|
+
function gitEnv() {
|
|
18965
|
+
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
|
|
18966
|
+
}
|
|
18967
|
+
function gitCommonDirectory(projectRoot) {
|
|
18968
|
+
const result = spawnSync3("git", ["-C", projectRoot, "rev-parse", "--git-common-dir"], {
|
|
18969
|
+
encoding: "utf8",
|
|
18970
|
+
env: gitEnv(),
|
|
18971
|
+
timeout: 3e4,
|
|
18972
|
+
windowsHide: true
|
|
18973
|
+
});
|
|
18974
|
+
if (result.status !== 0) return void 0;
|
|
18975
|
+
const text = (result.stdout ?? "").trim();
|
|
18976
|
+
if (!text) return void 0;
|
|
18977
|
+
const absolute = isAbsolute4(text) ? text : resolve6(projectRoot, text);
|
|
18978
|
+
let canonical;
|
|
18979
|
+
try {
|
|
18980
|
+
canonical = realpathSync3.native(absolute);
|
|
18981
|
+
} catch {
|
|
18982
|
+
canonical = resolve6(absolute);
|
|
18983
|
+
}
|
|
18984
|
+
return process.platform === "win32" ? canonical.toLocaleLowerCase("en-US") : canonical;
|
|
18985
|
+
}
|
|
18986
|
+
function isPrimaryWorktree(projectRoot, commonDir) {
|
|
18987
|
+
if (commonDir === void 0) return false;
|
|
18988
|
+
const dotGit = resolve6(projectRoot, ".git");
|
|
18989
|
+
let canonical;
|
|
18990
|
+
try {
|
|
18991
|
+
canonical = realpathSync3.native(dotGit);
|
|
18992
|
+
} catch {
|
|
18993
|
+
canonical = resolve6(dotGit);
|
|
18994
|
+
}
|
|
18995
|
+
if (process.platform === "win32") canonical = canonical.toLocaleLowerCase("en-US");
|
|
18996
|
+
return canonical === commonDir;
|
|
18997
|
+
}
|
|
18998
|
+
function migrateRegistryLanes(file) {
|
|
18999
|
+
const stat = lstatSync3(file, { throwIfNoEntry: false });
|
|
19000
|
+
if (!stat) return;
|
|
19001
|
+
assertDatabaseFile(file, "runtime registry");
|
|
19002
|
+
const database = new DatabaseSync(file);
|
|
19003
|
+
let transaction = false;
|
|
19004
|
+
try {
|
|
19005
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
19006
|
+
const current = database.prepare("PRAGMA user_version").get();
|
|
19007
|
+
if ((current?.user_version ?? 0) !== 1) return;
|
|
19008
|
+
database.exec("BEGIN IMMEDIATE");
|
|
19009
|
+
transaction = true;
|
|
19010
|
+
const locked = database.prepare("PRAGMA user_version").get();
|
|
19011
|
+
if ((locked?.user_version ?? 0) !== 1) {
|
|
19012
|
+
database.exec("ROLLBACK");
|
|
19013
|
+
transaction = false;
|
|
19014
|
+
return;
|
|
19015
|
+
}
|
|
19016
|
+
if (!tableColumns(database, "projects").includes("lane_of")) {
|
|
19017
|
+
database.exec(`
|
|
19018
|
+
CREATE TABLE projects_v2 (
|
|
19019
|
+
project_key TEXT PRIMARY KEY,
|
|
19020
|
+
project_id TEXT NOT NULL,
|
|
19021
|
+
project_root TEXT NOT NULL,
|
|
19022
|
+
home_runtime_id TEXT NOT NULL,
|
|
19023
|
+
config_revision TEXT,
|
|
19024
|
+
registered_at TEXT NOT NULL,
|
|
19025
|
+
lane_of TEXT
|
|
19026
|
+
) STRICT;
|
|
19027
|
+
INSERT INTO projects_v2 (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
|
|
19028
|
+
SELECT project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, NULL FROM projects;
|
|
19029
|
+
DROP TABLE projects;
|
|
19030
|
+
ALTER TABLE projects_v2 RENAME TO projects;
|
|
19031
|
+
CREATE INDEX projects_by_project_id ON projects (project_id);
|
|
19032
|
+
`);
|
|
19033
|
+
}
|
|
19034
|
+
database.exec("PRAGMA user_version = 2");
|
|
19035
|
+
database.exec("COMMIT");
|
|
19036
|
+
transaction = false;
|
|
19037
|
+
} catch (error) {
|
|
19038
|
+
if (transaction) {
|
|
19039
|
+
try {
|
|
19040
|
+
database.exec("ROLLBACK");
|
|
19041
|
+
} catch {
|
|
19042
|
+
}
|
|
19043
|
+
}
|
|
19044
|
+
throw error;
|
|
19045
|
+
} finally {
|
|
19046
|
+
database.close();
|
|
19047
|
+
}
|
|
19048
|
+
}
|
|
18945
19049
|
function readKxmSupervisorRecord(database) {
|
|
18946
19050
|
const row = database.prepare("SELECT runtime_id, pid, port, token_hash, started_at, heartbeat_at, state FROM supervisor WHERE singleton_id = 1").get();
|
|
18947
19051
|
return row ? {
|
|
@@ -18959,6 +19063,7 @@ var KxmRuntimeRegistry = class {
|
|
|
18959
19063
|
database;
|
|
18960
19064
|
constructor(path) {
|
|
18961
19065
|
this.path = resolve6(path);
|
|
19066
|
+
migrateRegistryLanes(this.path);
|
|
18962
19067
|
this.database = openDatabase(this.path, "runtime registry", {
|
|
18963
19068
|
schema: REGISTRY_SCHEMA,
|
|
18964
19069
|
version: KXM_REGISTRY_SCHEMA_VERSION,
|
|
@@ -19037,57 +19142,188 @@ var KxmRuntimeRegistry = class {
|
|
|
19037
19142
|
supervisor() {
|
|
19038
19143
|
return this.readSupervisorRow();
|
|
19039
19144
|
}
|
|
19040
|
-
/**
|
|
19041
|
-
|
|
19042
|
-
*
|
|
19043
|
-
* syncing and presence keeps beating. */
|
|
19145
|
+
/** All projects registered to this Runtime, including lane roots, for restart
|
|
19146
|
+
* recovery: the supervisor reopens their contexts so pending outbox rows
|
|
19147
|
+
* resume syncing and presence keeps beating. */
|
|
19044
19148
|
projectsForRuntime(homeRuntimeId) {
|
|
19045
19149
|
const rows = this.database.prepare(
|
|
19046
19150
|
"SELECT project_root, project_id FROM projects WHERE home_runtime_id = ? ORDER BY registered_at"
|
|
19047
19151
|
).all(homeRuntimeId);
|
|
19048
19152
|
return rows.map((row) => ({ projectRoot: row.project_root, projectId: row.project_id }));
|
|
19049
19153
|
}
|
|
19154
|
+
/**
|
|
19155
|
+
* Register or revalidate a control root. The home runtime of a live row is
|
|
19156
|
+
* immutable. A second root whose git common directory matches a live row
|
|
19157
|
+
* for the same project is a lane, unless the registering root is the primary
|
|
19158
|
+
* worktree and the live home row is not: then the primary becomes the home
|
|
19159
|
+
* row (same home runtime id) and every other live row of that repository
|
|
19160
|
+
* becomes its lane. A primary never becomes a lane. A root whose directory
|
|
19161
|
+
* is gone is replaced. A live root that is a different repository is
|
|
19162
|
+
* `project_home_conflict`.
|
|
19163
|
+
*/
|
|
19050
19164
|
registerProject(registration) {
|
|
19051
19165
|
const projectRoot = resolve6(registration.projectRoot);
|
|
19052
19166
|
const projectKey = projectRuntimeKey(projectRoot);
|
|
19167
|
+
const candidateCommon = gitCommonDirectory(projectRoot);
|
|
19168
|
+
const commonByRoot = /* @__PURE__ */ new Map();
|
|
19169
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
19170
|
+
this.database.exec("BEGIN IMMEDIATE");
|
|
19171
|
+
let rootsToMeasure;
|
|
19172
|
+
try {
|
|
19173
|
+
const byKey = this.projectRow("SELECT " + PROJECT_COLUMNS + " FROM projects WHERE project_key = ?", projectKey);
|
|
19174
|
+
if (byKey) {
|
|
19175
|
+
const problems = [];
|
|
19176
|
+
if (byKey.project_id !== registration.projectId) problems.push(`control root is already bound to a different project id ${byKey.project_id}`);
|
|
19177
|
+
if (byKey.home_runtime_id !== registration.homeRuntimeId) problems.push(`project ${registration.projectId} home runtime is immutable and cannot be rebound`);
|
|
19178
|
+
if (problems.length > 0) {
|
|
19179
|
+
this.database.exec("ROLLBACK");
|
|
19180
|
+
throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
|
|
19181
|
+
}
|
|
19182
|
+
const result = registrationFromRow(byKey);
|
|
19183
|
+
this.database.exec("COMMIT");
|
|
19184
|
+
return result;
|
|
19185
|
+
}
|
|
19186
|
+
const rows = this.database.prepare(
|
|
19187
|
+
`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_id = ? ORDER BY registered_at`
|
|
19188
|
+
).all(registration.projectId);
|
|
19189
|
+
const live = rows.filter((row) => controlRootDirectoryExists(row.project_root));
|
|
19190
|
+
const dead = rows.filter((row) => !controlRootDirectoryExists(row.project_root));
|
|
19191
|
+
const unknown = live.filter((row) => !commonByRoot.has(row.project_root));
|
|
19192
|
+
if (unknown.length > 0) {
|
|
19193
|
+
rootsToMeasure = unknown.map((row) => row.project_root);
|
|
19194
|
+
this.database.exec("ROLLBACK");
|
|
19195
|
+
} else if (live.length > 0) {
|
|
19196
|
+
const anchor = live.find((row) => {
|
|
19197
|
+
const dir = commonByRoot.get(row.project_root);
|
|
19198
|
+
return candidateCommon !== void 0 && dir === candidateCommon;
|
|
19199
|
+
});
|
|
19200
|
+
if (!anchor) {
|
|
19201
|
+
this.database.exec("ROLLBACK");
|
|
19202
|
+
throw runtimeError(
|
|
19203
|
+
"project_home_conflict",
|
|
19204
|
+
".kxm/project.yaml",
|
|
19205
|
+
`project ${registration.projectId} is already bound to a different control root`
|
|
19206
|
+
);
|
|
19207
|
+
}
|
|
19208
|
+
if (live.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
|
|
19209
|
+
this.database.exec("ROLLBACK");
|
|
19210
|
+
throw runtimeError(
|
|
19211
|
+
"project_home_conflict",
|
|
19212
|
+
".kxm/project.yaml",
|
|
19213
|
+
`project ${registration.projectId} home runtime is immutable and cannot be rebound`
|
|
19214
|
+
);
|
|
19215
|
+
}
|
|
19216
|
+
const sameRepo = live.filter((row) => commonByRoot.get(row.project_root) === candidateCommon);
|
|
19217
|
+
const home = sameRepo.find((row) => row.lane_of === null);
|
|
19218
|
+
const registeringIsPrimary = isPrimaryWorktree(projectRoot, candidateCommon);
|
|
19219
|
+
const homeIsPrimary = home !== void 0 && isPrimaryWorktree(home.project_root, commonByRoot.get(home.project_root));
|
|
19220
|
+
if (registeringIsPrimary && !homeIsPrimary) {
|
|
19221
|
+
this.insertHome(registration, projectRoot, projectKey);
|
|
19222
|
+
for (const row of sameRepo) {
|
|
19223
|
+
this.database.prepare("UPDATE projects SET lane_of = ? WHERE project_key = ?").run(projectKey, row.project_key);
|
|
19224
|
+
}
|
|
19225
|
+
this.database.exec("COMMIT");
|
|
19226
|
+
registration.logger?.({
|
|
19227
|
+
event: "project_home_promoted",
|
|
19228
|
+
projectId: registration.projectId,
|
|
19229
|
+
projectRoot,
|
|
19230
|
+
homeRuntimeId: registration.homeRuntimeId,
|
|
19231
|
+
previousHomeRoot: home?.project_root ?? null,
|
|
19232
|
+
previousHomeKey: home?.project_key ?? null
|
|
19233
|
+
});
|
|
19234
|
+
return this.insertedRegistration(registration, projectRoot, projectKey);
|
|
19235
|
+
}
|
|
19236
|
+
if (registeringIsPrimary) {
|
|
19237
|
+
this.database.exec("ROLLBACK");
|
|
19238
|
+
throw runtimeError(
|
|
19239
|
+
"project_home_conflict",
|
|
19240
|
+
".kxm/project.yaml",
|
|
19241
|
+
`project ${registration.projectId} primary checkout is already the home row`
|
|
19242
|
+
);
|
|
19243
|
+
}
|
|
19244
|
+
const laneOf = anchor.lane_of ?? anchor.project_key;
|
|
19245
|
+
this.database.prepare(`
|
|
19246
|
+
INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
|
|
19247
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
19248
|
+
`).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now, laneOf);
|
|
19249
|
+
const result = this.insertedRegistration(registration, projectRoot, projectKey, laneOf);
|
|
19250
|
+
this.database.exec("COMMIT");
|
|
19251
|
+
return result;
|
|
19252
|
+
} else if (dead.length > 0) {
|
|
19253
|
+
const replaced = dead[0];
|
|
19254
|
+
if (dead.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
|
|
19255
|
+
this.database.exec("ROLLBACK");
|
|
19256
|
+
throw runtimeError(
|
|
19257
|
+
"project_home_conflict",
|
|
19258
|
+
".kxm/project.yaml",
|
|
19259
|
+
`project ${registration.projectId} home runtime is immutable and cannot be rebound`
|
|
19260
|
+
);
|
|
19261
|
+
}
|
|
19262
|
+
this.database.prepare("DELETE FROM projects WHERE project_id = ?").run(registration.projectId);
|
|
19263
|
+
this.insertHome(registration, projectRoot, projectKey);
|
|
19264
|
+
this.database.exec("COMMIT");
|
|
19265
|
+
registration.logger?.({
|
|
19266
|
+
event: "project_registration_replaced",
|
|
19267
|
+
projectId: registration.projectId,
|
|
19268
|
+
projectRoot,
|
|
19269
|
+
replacedRoot: replaced.project_root,
|
|
19270
|
+
replacedCount: dead.length,
|
|
19271
|
+
replacedHomeRuntimeId: replaced.home_runtime_id,
|
|
19272
|
+
replacedProjectKey: replaced.project_key
|
|
19273
|
+
});
|
|
19274
|
+
return this.insertedRegistration(registration, projectRoot, projectKey);
|
|
19275
|
+
} else {
|
|
19276
|
+
this.insertHome(registration, projectRoot, projectKey);
|
|
19277
|
+
this.database.exec("COMMIT");
|
|
19278
|
+
return this.insertedRegistration(registration, projectRoot, projectKey);
|
|
19279
|
+
}
|
|
19280
|
+
} catch (error) {
|
|
19281
|
+
try {
|
|
19282
|
+
this.database.exec("ROLLBACK");
|
|
19283
|
+
} catch {
|
|
19284
|
+
}
|
|
19285
|
+
throw error;
|
|
19286
|
+
}
|
|
19287
|
+
for (const root of rootsToMeasure ?? []) {
|
|
19288
|
+
commonByRoot.set(root, gitCommonDirectory(root));
|
|
19289
|
+
}
|
|
19290
|
+
}
|
|
19291
|
+
throw runtimeError(
|
|
19292
|
+
"runtime_registry_busy",
|
|
19293
|
+
projectRoot,
|
|
19294
|
+
`project ${registration.projectId} registration could not be rechecked`
|
|
19295
|
+
);
|
|
19296
|
+
}
|
|
19297
|
+
/** Remove one control root. Returns false when that root was not registered. */
|
|
19298
|
+
unregisterProject(projectRoot) {
|
|
19053
19299
|
this.database.exec("BEGIN IMMEDIATE");
|
|
19054
19300
|
try {
|
|
19055
|
-
const
|
|
19056
|
-
|
|
19057
|
-
|
|
19058
|
-
|
|
19059
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
19062
|
-
if (existing.home_runtime_id !== registration.homeRuntimeId) problems.push(`project ${registration.projectId} home runtime is immutable and cannot be rebound`);
|
|
19063
|
-
if (problems.length > 0) {
|
|
19064
|
-
this.database.exec("ROLLBACK");
|
|
19065
|
-
throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
|
|
19066
|
-
}
|
|
19067
|
-
const result = {
|
|
19068
|
-
projectId: existing.project_id,
|
|
19069
|
-
projectRoot: existing.project_root,
|
|
19070
|
-
projectKey: existing.project_key,
|
|
19071
|
-
homeRuntimeId: existing.home_runtime_id,
|
|
19072
|
-
...existing.config_revision !== null ? { configRevision: existing.config_revision } : {},
|
|
19073
|
-
registeredAt: existing.registered_at
|
|
19074
|
-
};
|
|
19075
|
-
this.database.exec("COMMIT");
|
|
19076
|
-
return result;
|
|
19301
|
+
const removed = this.deleteRegisteredRoot(projectRoot);
|
|
19302
|
+
this.database.exec("COMMIT");
|
|
19303
|
+
return removed;
|
|
19304
|
+
} catch (error) {
|
|
19305
|
+
try {
|
|
19306
|
+
this.database.exec("ROLLBACK");
|
|
19307
|
+
} catch {
|
|
19077
19308
|
}
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19309
|
+
throw error;
|
|
19310
|
+
}
|
|
19311
|
+
}
|
|
19312
|
+
/**
|
|
19313
|
+
* Delete the row only when `supervisorIsLive` is false for the supervisor
|
|
19314
|
+
* row read inside this write transaction. A live supervisor keeps the row;
|
|
19315
|
+
* the caller uses the supervisor route instead of editing the file.
|
|
19316
|
+
*/
|
|
19317
|
+
unregisterProjectIfIdle(projectRoot, supervisorIsLive) {
|
|
19318
|
+
this.database.exec("BEGIN IMMEDIATE");
|
|
19319
|
+
try {
|
|
19320
|
+
if (supervisorIsLive(this.readSupervisorRow())) {
|
|
19321
|
+
this.database.exec("ROLLBACK");
|
|
19322
|
+
return "supervisor_live";
|
|
19323
|
+
}
|
|
19324
|
+
const removed = this.deleteRegisteredRoot(projectRoot);
|
|
19082
19325
|
this.database.exec("COMMIT");
|
|
19083
|
-
return
|
|
19084
|
-
projectId: registration.projectId,
|
|
19085
|
-
projectRoot,
|
|
19086
|
-
projectKey,
|
|
19087
|
-
homeRuntimeId: registration.homeRuntimeId,
|
|
19088
|
-
...registration.configRevision !== void 0 ? { configRevision: registration.configRevision } : {},
|
|
19089
|
-
registeredAt: registration.now
|
|
19090
|
-
};
|
|
19326
|
+
return removed ? "removed" : "absent";
|
|
19091
19327
|
} catch (error) {
|
|
19092
19328
|
try {
|
|
19093
19329
|
this.database.exec("ROLLBACK");
|
|
@@ -19096,28 +19332,65 @@ var KxmRuntimeRegistry = class {
|
|
|
19096
19332
|
throw error;
|
|
19097
19333
|
}
|
|
19098
19334
|
}
|
|
19335
|
+
/** How many other rows share this home root's project. Zero when this root is a lane or missing. */
|
|
19336
|
+
homeLaneCount(projectRoot) {
|
|
19337
|
+
const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(resolve6(projectRoot)));
|
|
19338
|
+
if (!row || row.lane_of !== null) return 0;
|
|
19339
|
+
const count = this.database.prepare(
|
|
19340
|
+
"SELECT COUNT(*) AS total FROM projects WHERE project_id = ? AND project_key != ?"
|
|
19341
|
+
).get(row.project_id, row.project_key);
|
|
19342
|
+
return Number(count?.total ?? 0);
|
|
19343
|
+
}
|
|
19344
|
+
/** Caller holds the write transaction. Does not commit. */
|
|
19345
|
+
deleteRegisteredRoot(projectRoot) {
|
|
19346
|
+
const projectKey = projectRuntimeKey(resolve6(projectRoot));
|
|
19347
|
+
const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectKey);
|
|
19348
|
+
if (!row) return false;
|
|
19349
|
+
this.database.prepare("DELETE FROM projects WHERE project_key = ?").run(projectKey);
|
|
19350
|
+
if (row.lane_of === null) {
|
|
19351
|
+
const successor = this.database.prepare(
|
|
19352
|
+
"SELECT project_key FROM projects WHERE project_id = ? ORDER BY registered_at LIMIT 1"
|
|
19353
|
+
).get(row.project_id);
|
|
19354
|
+
if (successor) {
|
|
19355
|
+
this.database.prepare("UPDATE projects SET lane_of = NULL WHERE project_key = ?").run(successor.project_key);
|
|
19356
|
+
this.database.prepare(
|
|
19357
|
+
"UPDATE projects SET lane_of = ? WHERE project_id = ? AND project_key != ? AND (lane_of IS NULL OR lane_of = ?)"
|
|
19358
|
+
).run(successor.project_key, row.project_id, successor.project_key, projectKey);
|
|
19359
|
+
}
|
|
19360
|
+
}
|
|
19361
|
+
return true;
|
|
19362
|
+
}
|
|
19363
|
+
/** The home row for a project id, or the earliest row when every row is a lane. */
|
|
19099
19364
|
project(projectId) {
|
|
19100
|
-
const row = this.
|
|
19101
|
-
|
|
19102
|
-
projectId
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
homeRuntimeId: row.home_runtime_id,
|
|
19106
|
-
...row.config_revision !== null ? { configRevision: row.config_revision } : {},
|
|
19107
|
-
registeredAt: row.registered_at
|
|
19108
|
-
} : void 0;
|
|
19365
|
+
const row = this.projectRow(
|
|
19366
|
+
`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`,
|
|
19367
|
+
projectId
|
|
19368
|
+
);
|
|
19369
|
+
return row ? registrationFromRow(row) : void 0;
|
|
19109
19370
|
}
|
|
19110
19371
|
projectByRoot(projectRoot) {
|
|
19111
|
-
const
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
|
|
19116
|
-
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
|
|
19372
|
+
const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(projectRoot));
|
|
19373
|
+
return row ? registrationFromRow(row) : void 0;
|
|
19374
|
+
}
|
|
19375
|
+
projectRow(sql, parameter) {
|
|
19376
|
+
return this.database.prepare(sql).get(parameter);
|
|
19377
|
+
}
|
|
19378
|
+
insertHome(registration, projectRoot, projectKey) {
|
|
19379
|
+
this.database.prepare(`
|
|
19380
|
+
INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
|
|
19381
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL)
|
|
19382
|
+
`).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now);
|
|
19383
|
+
}
|
|
19384
|
+
insertedRegistration(registration, projectRoot, projectKey, laneOf) {
|
|
19385
|
+
return {
|
|
19386
|
+
projectId: registration.projectId,
|
|
19387
|
+
projectRoot,
|
|
19388
|
+
projectKey,
|
|
19389
|
+
homeRuntimeId: registration.homeRuntimeId,
|
|
19390
|
+
...registration.configRevision !== void 0 ? { configRevision: registration.configRevision } : {},
|
|
19391
|
+
...laneOf !== void 0 ? { laneOf } : {},
|
|
19392
|
+
registeredAt: registration.now
|
|
19393
|
+
};
|
|
19121
19394
|
}
|
|
19122
19395
|
};
|
|
19123
19396
|
var KXM_RUN_EVENT_SCHEMA = "kxm.run-event.v1";
|
|
@@ -23559,7 +23832,8 @@ function openKxmRuntimeContext(projectRoot, options) {
|
|
|
23559
23832
|
projectRoot: bundle.projectRoot,
|
|
23560
23833
|
homeRuntimeId: options.homeRuntimeId,
|
|
23561
23834
|
configRevision: bundle.configRevision,
|
|
23562
|
-
now: options.now ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
23835
|
+
now: options.now ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
23836
|
+
...options.logger ? { logger: options.logger } : {}
|
|
23563
23837
|
});
|
|
23564
23838
|
const eventStore = new KxmRunEventStore(join7(paths.projectsDir, registration.projectKey, "run-events.db"));
|
|
23565
23839
|
registerKxmRuntimeHandle(eventStore.path);
|
|
@@ -24022,7 +24296,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
24022
24296
|
// plugins/kxm/src/prices.ts
|
|
24023
24297
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
24024
24298
|
import { createHash as createHash10 } from "node:crypto";
|
|
24025
|
-
import { existsSync as existsSync8, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
24299
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
24026
24300
|
import { join as join9 } from "node:path";
|
|
24027
24301
|
|
|
24028
24302
|
// plugins/kxm/src/price-calc.ts
|
|
@@ -24175,7 +24449,7 @@ function parsePriceCatalog(text) {
|
|
|
24175
24449
|
return catalog;
|
|
24176
24450
|
}
|
|
24177
24451
|
function loadPriceCatalog(rootOrPath) {
|
|
24178
|
-
const candidatePath = existsSync8(join9(rootOrPath, ".kxm", "prices.yaml")) ? join9(rootOrPath, ".kxm", "prices.yaml") : existsSync8(join9(rootOrPath, "prices.yaml")) ? join9(rootOrPath, "prices.yaml") : existsSync8(rootOrPath) &&
|
|
24452
|
+
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;
|
|
24179
24453
|
if (!candidatePath || !existsSync8(candidatePath)) {
|
|
24180
24454
|
return void 0;
|
|
24181
24455
|
}
|
|
@@ -24202,14 +24476,14 @@ var import_yaml4 = __toESM(require_dist(), 1);
|
|
|
24202
24476
|
|
|
24203
24477
|
// plugins/kxm/src/routes.ts
|
|
24204
24478
|
var RETIRED_POLICY = ".kxm/producers.yaml";
|
|
24205
|
-
var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: []
|
|
24479
|
+
var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [] });
|
|
24206
24480
|
function loadRoutePolicy(root) {
|
|
24207
24481
|
if (existsSync9(join10(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
|
|
24208
24482
|
const path = join10(root, ".kxm", "routes.yaml");
|
|
24209
24483
|
if (!existsSync9(path)) return empty();
|
|
24210
24484
|
const value = (0, import_yaml6.parse)(readFileSync7(path, "utf8"));
|
|
24211
24485
|
if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
|
|
24212
|
-
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") : []
|
|
24486
|
+
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") : [] };
|
|
24213
24487
|
}
|
|
24214
24488
|
function listRoleBindings(root) {
|
|
24215
24489
|
const dir = join10(root, ".kxm", "roles");
|
|
@@ -24222,15 +24496,11 @@ function listRoleBindings(root) {
|
|
|
24222
24496
|
}
|
|
24223
24497
|
return result;
|
|
24224
24498
|
}
|
|
24225
|
-
function isRouteAdmitted(root, modelId) {
|
|
24226
|
-
const policy = loadRoutePolicy(root);
|
|
24227
|
-
return policy.admitted.includes(modelId) && !policy.disabled.includes(modelId);
|
|
24228
|
-
}
|
|
24229
24499
|
|
|
24230
24500
|
// plugins/kxm/src/worktree-witness.ts
|
|
24231
|
-
import { spawnSync as
|
|
24501
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
24232
24502
|
function gitText(cwd, args) {
|
|
24233
|
-
const result =
|
|
24503
|
+
const result = spawnSync4("git", ["-C", cwd, ...args], {
|
|
24234
24504
|
encoding: "utf8",
|
|
24235
24505
|
windowsHide: true,
|
|
24236
24506
|
timeout: 15e3
|
|
@@ -24543,7 +24813,7 @@ function pruneContextPacket(packet, tokenBudget) {
|
|
|
24543
24813
|
}
|
|
24544
24814
|
|
|
24545
24815
|
// plugins/kxm/src/dispatch-context.ts
|
|
24546
|
-
import { spawnSync as
|
|
24816
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
24547
24817
|
import { readdirSync as readdirSync8 } from "node:fs";
|
|
24548
24818
|
import { extname as extname3, join as join13 } from "node:path";
|
|
24549
24819
|
|
|
@@ -25303,7 +25573,7 @@ function loadAuthoredMemory(repoRoot) {
|
|
|
25303
25573
|
// plugins/kxm/src/skills.ts
|
|
25304
25574
|
var import_yaml8 = __toESM(require_dist(), 1);
|
|
25305
25575
|
import { createHash as createHash11 } from "node:crypto";
|
|
25306
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, readFileSync as readFileSync9, renameSync, rmSync, statSync as
|
|
25576
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, readFileSync as readFileSync9, renameSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
25307
25577
|
import { dirname as dirname7, join as join12 } from "node:path";
|
|
25308
25578
|
var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
|
|
25309
25579
|
var SKILL_EVALUATION_SCHEMA = "kxm.skill-evaluation.v1";
|
|
@@ -25683,7 +25953,7 @@ function boundedList(value, field) {
|
|
|
25683
25953
|
return [...new Set(refs)];
|
|
25684
25954
|
}
|
|
25685
25955
|
function readdirSorted(dir) {
|
|
25686
|
-
return readdirSync7(dir).filter((entry) =>
|
|
25956
|
+
return readdirSync7(dir).filter((entry) => statSync3(join12(dir, entry)).isDirectory()).sort();
|
|
25687
25957
|
}
|
|
25688
25958
|
|
|
25689
25959
|
// plugins/kxm/src/dispatch-context.ts
|
|
@@ -25744,7 +26014,7 @@ function gitEnvironment2() {
|
|
|
25744
26014
|
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
|
|
25745
26015
|
}
|
|
25746
26016
|
function committedGateGap(projectRoot) {
|
|
25747
|
-
const result =
|
|
26017
|
+
const result = spawnSync5("git", [
|
|
25748
26018
|
"--no-optional-locks",
|
|
25749
26019
|
"-C",
|
|
25750
26020
|
projectRoot,
|
|
@@ -25886,11 +26156,11 @@ function assembleDispatchContext(sources, input) {
|
|
|
25886
26156
|
import { join as join14 } from "node:path";
|
|
25887
26157
|
|
|
25888
26158
|
// plugins/kxm/src/artifacts-exist.ts
|
|
25889
|
-
import { lstatSync as lstatSync5, realpathSync as
|
|
25890
|
-
import { isAbsolute as
|
|
26159
|
+
import { lstatSync as lstatSync5, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
|
|
26160
|
+
import { isAbsolute as isAbsolute5, relative as relative4, resolve as resolve8 } from "node:path";
|
|
25891
26161
|
function staysUnder(root, candidate) {
|
|
25892
26162
|
const child = relative4(root, candidate);
|
|
25893
|
-
return child === "" || !
|
|
26163
|
+
return child === "" || !isAbsolute5(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
|
|
25894
26164
|
}
|
|
25895
26165
|
function verifyArtifactExists(rootInput, pathInput) {
|
|
25896
26166
|
const lexicalRoot = resolve8(rootInput);
|
|
@@ -25901,12 +26171,12 @@ function verifyArtifactExists(rootInput, pathInput) {
|
|
|
25901
26171
|
let realRoot;
|
|
25902
26172
|
let realPath;
|
|
25903
26173
|
try {
|
|
25904
|
-
realRoot =
|
|
26174
|
+
realRoot = realpathSync4(lexicalRoot);
|
|
25905
26175
|
} catch {
|
|
25906
26176
|
return { ok: false, error: "artifact_unreadable", path: lexicalPath };
|
|
25907
26177
|
}
|
|
25908
26178
|
try {
|
|
25909
|
-
realPath =
|
|
26179
|
+
realPath = realpathSync4(lexicalPath);
|
|
25910
26180
|
} catch {
|
|
25911
26181
|
return { ok: false, error: "artifact_missing", path: lexicalPath };
|
|
25912
26182
|
}
|
|
@@ -25918,7 +26188,7 @@ function verifyArtifactExists(rootInput, pathInput) {
|
|
|
25918
26188
|
if (link.isSymbolicLink()) {
|
|
25919
26189
|
return { ok: false, error: "artifact_outside_workspace_assets", path: lexicalPath };
|
|
25920
26190
|
}
|
|
25921
|
-
const artifact =
|
|
26191
|
+
const artifact = statSync4(realPath);
|
|
25922
26192
|
if (!artifact.isFile()) return { ok: false, error: "artifact_not_file", path: lexicalPath };
|
|
25923
26193
|
if (artifact.size < 1) return { ok: false, error: "artifact_empty", path: lexicalPath };
|
|
25924
26194
|
return { ok: true, path: realPath, bytes: artifact.size };
|
|
@@ -27793,25 +28063,18 @@ function commitCommandUncertainty(context, prepared, token, reason, observation,
|
|
|
27793
28063
|
}
|
|
27794
28064
|
}
|
|
27795
28065
|
var kxmPanelDispatchSeams = {};
|
|
27796
|
-
function
|
|
27797
|
-
|
|
27798
|
-
|
|
27799
|
-
|
|
27800
|
-
|
|
27801
|
-
|
|
27802
|
-
|
|
27803
|
-
|
|
27804
|
-
|
|
27805
|
-
|
|
27806
|
-
|
|
27807
|
-
const model = typeof parsed.model === "string" ? parsed.model : "";
|
|
27808
|
-
const vendor = typeof parsed.vendor === "string" ? parsed.vendor : "";
|
|
27809
|
-
const harness = typeof parsed.harness === "string" ? parsed.harness : "";
|
|
27810
|
-
if (model) selectors.add(model);
|
|
27811
|
-
if (vendor && model) selectors.add(`${vendor}/${model}`);
|
|
27812
|
-
if (harness && model) selectors.add(`${harness}/${model}`);
|
|
28066
|
+
function readYamlFile(file) {
|
|
28067
|
+
return readModelDocument(file);
|
|
28068
|
+
}
|
|
28069
|
+
function readModelDocument(file) {
|
|
28070
|
+
if (!existsSync12(file)) return void 0;
|
|
28071
|
+
try {
|
|
28072
|
+
const parsed = (0, import_yaml9.parse)(readFileSync10(file, "utf8"));
|
|
28073
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
28074
|
+
return parsed;
|
|
28075
|
+
} catch {
|
|
28076
|
+
return void 0;
|
|
27813
28077
|
}
|
|
27814
|
-
return [...selectors];
|
|
27815
28078
|
}
|
|
27816
28079
|
function unreconciledPanelAttemptId(state) {
|
|
27817
28080
|
const current = state.currentStep;
|
|
@@ -27828,82 +28091,118 @@ function unreconciledPanelAttemptId(state) {
|
|
|
27828
28091
|
return void 0;
|
|
27829
28092
|
}
|
|
27830
28093
|
function resolveProducerRoute(projectRoot, step, agentId) {
|
|
27831
|
-
|
|
27832
|
-
const agentFile = join15(projectRoot, ".kxm", "agents", `${agentId}.yaml`);
|
|
27833
|
-
if (existsSync12(agentFile)) {
|
|
27834
|
-
try {
|
|
27835
|
-
const parsed = (0, import_yaml9.parse)(readFileSync10(agentFile, "utf8"));
|
|
27836
|
-
if (typeof parsed?.model === "string") {
|
|
27837
|
-
agentModel = parsed.model;
|
|
27838
|
-
} else if (parsed?.model && typeof parsed.model === "object" && !Array.isArray(parsed.model)) {
|
|
27839
|
-
const declared = parsed.model;
|
|
27840
|
-
const providerOk = typeof declared.provider === "string" && declared.provider.length > 0 && !declared.provider.includes("/");
|
|
27841
|
-
const modelOk = typeof declared.model === "string" && declared.model.length > 0 && !declared.model.startsWith("/") && !declared.model.endsWith("/");
|
|
27842
|
-
if (providerOk && modelOk) {
|
|
27843
|
-
agentModel = `${declared.provider}/${declared.model}`;
|
|
27844
|
-
} else {
|
|
27845
|
-
return {
|
|
27846
|
-
error: {
|
|
27847
|
-
reason: "step_unsupported",
|
|
27848
|
-
field: "model",
|
|
27849
|
-
detail: "producer_route_unsupported: invalid model declaration"
|
|
27850
|
-
}
|
|
27851
|
-
};
|
|
27852
|
-
}
|
|
27853
|
-
}
|
|
27854
|
-
} catch {
|
|
27855
|
-
}
|
|
27856
|
-
}
|
|
27857
|
-
let selector = typeof step.model === "string" ? step.model : agentModel;
|
|
27858
|
-
if (!selector) {
|
|
27859
|
-
if (agentId === "implementer") {
|
|
27860
|
-
selector = "xai/grok-4.6";
|
|
27861
|
-
}
|
|
27862
|
-
}
|
|
27863
|
-
if (!selector) {
|
|
28094
|
+
if ((step.kind === "agent" || step.kind === "moa") && step.model !== void 0) {
|
|
27864
28095
|
return {
|
|
27865
28096
|
error: {
|
|
27866
28097
|
reason: "step_unsupported",
|
|
27867
28098
|
field: "model",
|
|
27868
|
-
detail: "producer_route_unsupported: agent
|
|
28099
|
+
detail: "producer_route_unsupported: agent step model is not honored"
|
|
27869
28100
|
}
|
|
27870
28101
|
};
|
|
27871
28102
|
}
|
|
27872
|
-
const
|
|
27873
|
-
|
|
28103
|
+
const agent = readYamlFile(join15(projectRoot, ".kxm", "agents", `${agentId}.yaml`));
|
|
28104
|
+
const role = typeof agent?.role === "string" ? agent.role : "";
|
|
28105
|
+
if (!role) {
|
|
27874
28106
|
return {
|
|
27875
28107
|
error: {
|
|
27876
28108
|
reason: "step_unsupported",
|
|
27877
28109
|
field: "model",
|
|
27878
|
-
detail:
|
|
28110
|
+
detail: "producer_route_unsupported: agent has no role"
|
|
27879
28111
|
}
|
|
27880
28112
|
};
|
|
27881
28113
|
}
|
|
27882
|
-
|
|
28114
|
+
const roleDoc = readYamlFile(join15(projectRoot, ".kxm", "roles", `${role}.yaml`));
|
|
28115
|
+
const roster = Array.isArray(roleDoc?.roster) ? roleDoc.roster : void 0;
|
|
28116
|
+
if (!roster) {
|
|
27883
28117
|
return {
|
|
27884
28118
|
error: {
|
|
27885
28119
|
reason: "step_unsupported",
|
|
27886
28120
|
field: "model",
|
|
27887
|
-
detail: `producer_route_unsupported:
|
|
28121
|
+
detail: `producer_route_unsupported: role '${role}' has no roster`
|
|
27888
28122
|
}
|
|
27889
28123
|
};
|
|
27890
28124
|
}
|
|
27891
|
-
const
|
|
27892
|
-
const
|
|
27893
|
-
|
|
27894
|
-
|
|
27895
|
-
|
|
28125
|
+
const policy = loadRoutePolicy(projectRoot);
|
|
28126
|
+
for (const entry of roster) {
|
|
28127
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
28128
|
+
const record2 = entry;
|
|
28129
|
+
if (typeof record2.route !== "string" || record2.route.length === 0) continue;
|
|
28130
|
+
const modelPath = join15(projectRoot, ".kxm", "models", `${record2.route}.yaml`);
|
|
28131
|
+
const doc = readModelDocument(modelPath);
|
|
28132
|
+
if (!doc) {
|
|
28133
|
+
return {
|
|
28134
|
+
error: {
|
|
28135
|
+
reason: "step_unsupported",
|
|
28136
|
+
field: "model",
|
|
28137
|
+
detail: `producer_route_unsupported: model file '.kxm/models/${record2.route}.yaml' is missing or unreadable`
|
|
28138
|
+
}
|
|
28139
|
+
};
|
|
28140
|
+
}
|
|
28141
|
+
if (doc.status !== "admitted") continue;
|
|
28142
|
+
const modelName = typeof doc.model === "string" ? doc.model : "";
|
|
28143
|
+
const vendor = typeof doc.vendor === "string" ? doc.vendor : "";
|
|
28144
|
+
const harness = typeof doc.harness === "string" ? doc.harness : "";
|
|
28145
|
+
if (!modelName || !vendor || !harness) continue;
|
|
28146
|
+
const candidates = [modelName, `${vendor}/${modelName}`, `${harness}/${modelName}`];
|
|
28147
|
+
const admittedSelector = candidates.find((candidate) => policy.admitted.includes(candidate));
|
|
28148
|
+
const disabledSelector = candidates.find((candidate) => policy.disabled.includes(candidate));
|
|
28149
|
+
if (!admittedSelector && disabledSelector) {
|
|
28150
|
+
return {
|
|
28151
|
+
error: {
|
|
28152
|
+
reason: "step_unsupported",
|
|
28153
|
+
field: "model",
|
|
28154
|
+
detail: `producer_route_unsupported: first admitted route '${record2.route}' for role '${role}' is disabled`
|
|
28155
|
+
}
|
|
28156
|
+
};
|
|
28157
|
+
}
|
|
28158
|
+
const selector = admittedSelector ?? (modelName.includes("/") ? modelName : `${vendor}/${modelName}`);
|
|
28159
|
+
if (policy.disabled.includes(selector)) {
|
|
28160
|
+
return {
|
|
28161
|
+
error: {
|
|
28162
|
+
reason: "step_unsupported",
|
|
28163
|
+
field: "model",
|
|
28164
|
+
detail: `producer_route_unsupported: first admitted route '${record2.route}' for role '${role}' is disabled`
|
|
28165
|
+
}
|
|
28166
|
+
};
|
|
28167
|
+
}
|
|
28168
|
+
if (!policy.admitted.includes(selector)) {
|
|
28169
|
+
return {
|
|
28170
|
+
error: {
|
|
28171
|
+
reason: "step_unsupported",
|
|
28172
|
+
field: "model",
|
|
28173
|
+
detail: `producer_route_unsupported: model '${selector}' is not admitted`
|
|
28174
|
+
}
|
|
28175
|
+
};
|
|
28176
|
+
}
|
|
28177
|
+
const slash = selector.indexOf("/");
|
|
28178
|
+
if (slash <= 0 || slash === selector.length - 1) {
|
|
28179
|
+
return {
|
|
28180
|
+
error: {
|
|
28181
|
+
reason: "step_unsupported",
|
|
28182
|
+
field: "model",
|
|
28183
|
+
detail: `producer_route_unsupported: invalid selector '${selector}'`
|
|
28184
|
+
}
|
|
28185
|
+
};
|
|
28186
|
+
}
|
|
28187
|
+
const permissions = Array.isArray(doc.permissions) ? doc.permissions.filter((item) => typeof item === "string") : [];
|
|
27896
28188
|
return {
|
|
27897
|
-
|
|
27898
|
-
|
|
27899
|
-
|
|
27900
|
-
|
|
27901
|
-
|
|
28189
|
+
provider: selector.slice(0, slash),
|
|
28190
|
+
model: selector.slice(slash + 1),
|
|
28191
|
+
selector,
|
|
28192
|
+
harness,
|
|
28193
|
+
routeId: record2.route,
|
|
28194
|
+
role,
|
|
28195
|
+
permissions,
|
|
28196
|
+
...typeof record2.effort === "string" ? { effort: record2.effort } : {}
|
|
27902
28197
|
};
|
|
27903
28198
|
}
|
|
27904
|
-
|
|
27905
|
-
|
|
27906
|
-
|
|
28199
|
+
return {
|
|
28200
|
+
error: {
|
|
28201
|
+
reason: "step_unsupported",
|
|
28202
|
+
field: "model",
|
|
28203
|
+
detail: `producer_route_unsupported: role '${role}' has no admitted route`
|
|
28204
|
+
}
|
|
28205
|
+
};
|
|
27907
28206
|
}
|
|
27908
28207
|
function projectAgentStepTimeoutMs(context, run) {
|
|
27909
28208
|
return loadKxmRunPlanEnvelope(context.eventStore, run).projectLimits.agentStepTimeoutMs ?? KXM_DEFAULT_AGENT_STEP_TIMEOUT_MS;
|
|
@@ -28121,8 +28420,7 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
|
|
|
28121
28420
|
const writeRefusal = unsupportedLiveWrite(
|
|
28122
28421
|
context.projectRoot,
|
|
28123
28422
|
step,
|
|
28124
|
-
|
|
28125
|
-
resolvedRoute.selector,
|
|
28423
|
+
resolvedRoute,
|
|
28126
28424
|
loadKxmRunPlanEnvelope(context.eventStore, run).projectLimits.maxConcurrentRuns
|
|
28127
28425
|
);
|
|
28128
28426
|
if (writeRefusal) return { kind: "return", state, handoff: { ...writeRefusal, stepId } };
|
|
@@ -28151,7 +28449,7 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
|
|
|
28151
28449
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
28152
28450
|
const entered = foldStoredKxmRun(context, run);
|
|
28153
28451
|
persistKxmRunState(context, runId, entered, events[events.length - 1].sequence);
|
|
28154
|
-
const
|
|
28452
|
+
const firstBirth = birthMember(context, {
|
|
28155
28453
|
run,
|
|
28156
28454
|
plan,
|
|
28157
28455
|
step,
|
|
@@ -28162,6 +28460,10 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
|
|
|
28162
28460
|
resolvedRoute,
|
|
28163
28461
|
dispatchSources
|
|
28164
28462
|
});
|
|
28463
|
+
if ("handoff" in firstBirth) {
|
|
28464
|
+
return { kind: "return", state: entered, handoff: firstBirth.handoff };
|
|
28465
|
+
}
|
|
28466
|
+
const first = firstBirth;
|
|
28165
28467
|
return {
|
|
28166
28468
|
kind: "panel",
|
|
28167
28469
|
panel: {
|
|
@@ -28224,9 +28526,10 @@ function birthMember(context, input) {
|
|
|
28224
28526
|
let resolvedRoute = input.resolvedRoute;
|
|
28225
28527
|
if (!resolvedRoute && input.producerId && input.producerId !== "driver-simulated") {
|
|
28226
28528
|
const routeResult = resolveProducerRoute(context.projectRoot, input.step, agentId);
|
|
28227
|
-
if (
|
|
28228
|
-
|
|
28529
|
+
if ("error" in routeResult) {
|
|
28530
|
+
return { handoff: { ...routeResult.error, stepId: input.stepId } };
|
|
28229
28531
|
}
|
|
28532
|
+
resolvedRoute = routeResult;
|
|
28230
28533
|
}
|
|
28231
28534
|
const dispatchContext = assembleDispatchContext(input.dispatchSources, {
|
|
28232
28535
|
projectId: run.projectId,
|
|
@@ -28333,11 +28636,11 @@ function birthMember(context, input) {
|
|
|
28333
28636
|
prompt: input.step.instructions ? `${input.step.instructions}
|
|
28334
28637
|
|
|
28335
28638
|
${generatedPrompt}` : generatedPrompt,
|
|
28336
|
-
thinking: input.stepAttempt <= 1 ? "low" : "medium",
|
|
28337
28639
|
permission: Object.values(input.step.repositories).some((access) => access === "write") ? "edit" : "read-only",
|
|
28338
28640
|
...(input.step.kind === "agent" || input.step.kind === "moa") && input.step.timeoutMs !== void 0 ? { timeoutMs: input.step.timeoutMs } : {},
|
|
28339
28641
|
contextPacket,
|
|
28340
|
-
...resolvedRoute ? {
|
|
28642
|
+
...resolvedRoute?.effort !== void 0 ? { thinking: resolvedRoute.effort } : {},
|
|
28643
|
+
...resolvedRoute ? { harness: resolvedRoute.harness, provider: resolvedRoute.provider, model: resolvedRoute.model } : {}
|
|
28341
28644
|
},
|
|
28342
28645
|
controller,
|
|
28343
28646
|
state: next
|
|
@@ -28482,13 +28785,14 @@ async function drivePanel(context, panel, producer) {
|
|
|
28482
28785
|
})();
|
|
28483
28786
|
pending.set(member.attemptId, work);
|
|
28484
28787
|
};
|
|
28788
|
+
let routeHandoff;
|
|
28485
28789
|
const tryBirth = () => {
|
|
28486
|
-
if (stopBirths) return void 0;
|
|
28790
|
+
if (stopBirths || routeHandoff) return void 0;
|
|
28487
28791
|
return context.eventStore.transaction(() => {
|
|
28488
28792
|
const run = requireRun(context, runId);
|
|
28489
28793
|
const state = foldStoredKxmRun(context, run);
|
|
28490
28794
|
if (!birthAllowed(state, panel.step)) return void 0;
|
|
28491
|
-
|
|
28795
|
+
const born = birthMember(context, {
|
|
28492
28796
|
run,
|
|
28493
28797
|
plan: panel.plan,
|
|
28494
28798
|
step: panel.step,
|
|
@@ -28497,6 +28801,11 @@ async function drivePanel(context, panel, producer) {
|
|
|
28497
28801
|
producerId: producer.id,
|
|
28498
28802
|
dispatchSources: panel.dispatchSources
|
|
28499
28803
|
});
|
|
28804
|
+
if ("handoff" in born) {
|
|
28805
|
+
routeHandoff = born.handoff;
|
|
28806
|
+
return void 0;
|
|
28807
|
+
}
|
|
28808
|
+
return born;
|
|
28500
28809
|
});
|
|
28501
28810
|
};
|
|
28502
28811
|
const settleInvoked = (member, produced) => {
|
|
@@ -28540,6 +28849,10 @@ async function drivePanel(context, panel, producer) {
|
|
|
28540
28849
|
while (!stopBirths && !settlementFailed) {
|
|
28541
28850
|
try {
|
|
28542
28851
|
const next = tryBirth();
|
|
28852
|
+
if (routeHandoff) {
|
|
28853
|
+
stopBirths = true;
|
|
28854
|
+
break;
|
|
28855
|
+
}
|
|
28543
28856
|
if (!next) break;
|
|
28544
28857
|
launch(next);
|
|
28545
28858
|
} catch {
|
|
@@ -28550,6 +28863,13 @@ async function drivePanel(context, panel, producer) {
|
|
|
28550
28863
|
return unreconciledHandoff(state, panel.stepId);
|
|
28551
28864
|
}
|
|
28552
28865
|
}
|
|
28866
|
+
if (routeHandoff) {
|
|
28867
|
+
const handoff = routeHandoff;
|
|
28868
|
+
abortOwned();
|
|
28869
|
+
await drainPendingInvoked();
|
|
28870
|
+
const state = foldStoredKxmRun(context, requireRun(context, runId));
|
|
28871
|
+
return { state, handoff };
|
|
28872
|
+
}
|
|
28553
28873
|
if (pending.size === 0) break;
|
|
28554
28874
|
const finished = await Promise.race(pending.values());
|
|
28555
28875
|
pending.delete(finished.attemptId);
|
|
@@ -29113,25 +29433,7 @@ function unsupportedStep(plan, step, producerId) {
|
|
|
29113
29433
|
}
|
|
29114
29434
|
return void 0;
|
|
29115
29435
|
}
|
|
29116
|
-
function
|
|
29117
|
-
if (!existsSync12(path)) return void 0;
|
|
29118
|
-
try {
|
|
29119
|
-
const parsed = (0, import_yaml9.parse)(readFileSync10(path, "utf8"));
|
|
29120
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
29121
|
-
} catch {
|
|
29122
|
-
return void 0;
|
|
29123
|
-
}
|
|
29124
|
-
return void 0;
|
|
29125
|
-
}
|
|
29126
|
-
function agentHarness(projectRoot, agentId) {
|
|
29127
|
-
const harness = readYamlRecord(join15(projectRoot, ".kxm", "agents", `${agentId}.yaml`))?.harness;
|
|
29128
|
-
return typeof harness === "string" && harness.length > 0 ? harness : void 0;
|
|
29129
|
-
}
|
|
29130
|
-
function projectDefaultHarness(projectRoot) {
|
|
29131
|
-
const harness = readYamlRecord(join15(projectRoot, ".kxm", "project.yaml"))?.defaultHarness;
|
|
29132
|
-
return typeof harness === "string" && harness.length > 0 ? harness : "pi";
|
|
29133
|
-
}
|
|
29134
|
-
function unsupportedLiveWrite(projectRoot, step, agentId, selector, maxConcurrentRuns) {
|
|
29436
|
+
function unsupportedLiveWrite(projectRoot, step, route, maxConcurrentRuns) {
|
|
29135
29437
|
if (!Object.values(step.repositories).some((access) => access === "write")) return void 0;
|
|
29136
29438
|
if (step.assignments.maximum !== 1) {
|
|
29137
29439
|
return {
|
|
@@ -29147,42 +29449,23 @@ function unsupportedLiveWrite(projectRoot, step, agentId, selector, maxConcurren
|
|
|
29147
29449
|
detail: "live write steps require limits.maxConcurrentRuns of 1; concurrent runs share one checkout"
|
|
29148
29450
|
};
|
|
29149
29451
|
}
|
|
29150
|
-
|
|
29151
|
-
if (!oneShotWriterArgs(harness)) {
|
|
29452
|
+
if (!oneShotWriterArgs(route.harness)) {
|
|
29152
29453
|
return {
|
|
29153
29454
|
reason: "step_unsupported",
|
|
29154
29455
|
field: "repositories",
|
|
29155
|
-
detail: `live write steps require an audited writer profile; ${harness} has none`
|
|
29456
|
+
detail: `live write steps require an audited writer profile; ${route.harness} has none`
|
|
29156
29457
|
};
|
|
29157
29458
|
}
|
|
29158
|
-
const
|
|
29159
|
-
if (!
|
|
29160
|
-
|
|
29161
|
-
|
|
29162
|
-
|
|
29163
|
-
|
|
29164
|
-
const routes = roster.routes;
|
|
29165
|
-
const lineup = roster.lineup;
|
|
29166
|
-
const writerIds = lineup && typeof lineup === "object" && !Array.isArray(lineup) ? lineup.writer : void 0;
|
|
29167
|
-
if (!routes || typeof routes !== "object" || Array.isArray(routes) || !Array.isArray(writerIds)) {
|
|
29168
|
-
return { reason: "step_unsupported", field: "model", detail: "developer roster has no writer lineup" };
|
|
29169
|
-
}
|
|
29170
|
-
const allowed = writerIds.some((id) => {
|
|
29171
|
-
if (typeof id !== "string") return false;
|
|
29172
|
-
const route = routes[id];
|
|
29173
|
-
if (!route || typeof route !== "object" || Array.isArray(route)) return false;
|
|
29174
|
-
const record2 = route;
|
|
29175
|
-
if (record2.harness !== harness || record2.status !== "admitted") return false;
|
|
29176
|
-
if (!Array.isArray(record2.permissions) || !record2.permissions.includes("edit")) return false;
|
|
29177
|
-
const model = typeof record2.model === "string" ? record2.model : "";
|
|
29178
|
-
const vendor = typeof record2.vendor === "string" ? record2.vendor : "";
|
|
29179
|
-
return model === selector || vendor.length > 0 && `${vendor}/${model}` === selector;
|
|
29180
|
-
});
|
|
29181
|
-
if (!allowed) {
|
|
29459
|
+
const writerIds = listRoleBindings(projectRoot).writer;
|
|
29460
|
+
if (!Array.isArray(writerIds) || writerIds.length === 0) {
|
|
29461
|
+
return { reason: "step_unsupported", field: "model", detail: "writer roster has no admitted edit route" };
|
|
29462
|
+
}
|
|
29463
|
+
const named = writerIds.includes(route.routeId);
|
|
29464
|
+
if (!named || !route.permissions.includes("edit")) {
|
|
29182
29465
|
return {
|
|
29183
29466
|
reason: "step_unsupported",
|
|
29184
29467
|
field: "model",
|
|
29185
|
-
detail: `live write route ${selector} on ${harness} is not on the
|
|
29468
|
+
detail: `live write route ${route.selector} on ${route.harness} is not an admitted edit route on the writer roster`
|
|
29186
29469
|
};
|
|
29187
29470
|
}
|
|
29188
29471
|
return void 0;
|
|
@@ -29619,20 +29902,11 @@ function determineOutcome(text, allowedOutcomes) {
|
|
|
29619
29902
|
return "failed";
|
|
29620
29903
|
}
|
|
29621
29904
|
function createKxmOneShotProducer(options = {}) {
|
|
29622
|
-
const defaultHarness = options.defaultHarness ?? "claude";
|
|
29623
29905
|
const running = /* @__PURE__ */ new Map();
|
|
29624
29906
|
let closed = false;
|
|
29625
|
-
function
|
|
29907
|
+
function requireHarness(request) {
|
|
29626
29908
|
if (request.harness) return request.harness;
|
|
29627
|
-
|
|
29628
|
-
const resolved = options.resolveHarness(request.agentId, request.runId);
|
|
29629
|
-
if (resolved) return resolved;
|
|
29630
|
-
}
|
|
29631
|
-
if (options.resolveModel) {
|
|
29632
|
-
const resolved = options.resolveModel(request.agentId, request.runId);
|
|
29633
|
-
if (resolved?.harness) return resolved.harness;
|
|
29634
|
-
}
|
|
29635
|
-
return defaultHarness;
|
|
29909
|
+
throw new Error("producer_harness_required");
|
|
29636
29910
|
}
|
|
29637
29911
|
function parseModelString(spec, harness) {
|
|
29638
29912
|
const trimmed = spec.trim();
|
|
@@ -29694,7 +29968,7 @@ function createKxmOneShotProducer(options = {}) {
|
|
|
29694
29968
|
}
|
|
29695
29969
|
async function executeRequest(request) {
|
|
29696
29970
|
const startTime = Date.now();
|
|
29697
|
-
const harness =
|
|
29971
|
+
const harness = requireHarness(request);
|
|
29698
29972
|
const resolved = resolveModelForRequest(request, harness);
|
|
29699
29973
|
if (closed) throw new Error("oneshot_producer_closed");
|
|
29700
29974
|
const cancelled = () => ({
|
|
@@ -30041,7 +30315,7 @@ var RuntimeHubClient = class {
|
|
|
30041
30315
|
// plugins/kxm/src/hub-binding.ts
|
|
30042
30316
|
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
30043
30317
|
import { homedir as homedir2 } from "node:os";
|
|
30044
|
-
import { dirname as dirname8, isAbsolute as
|
|
30318
|
+
import { dirname as dirname8, isAbsolute as isAbsolute6, join as join16, resolve as resolve9 } from "node:path";
|
|
30045
30319
|
var HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
|
|
30046
30320
|
var HubBindingError = class extends Error {
|
|
30047
30321
|
constructor(message) {
|
|
@@ -30052,17 +30326,17 @@ var HubBindingError = class extends Error {
|
|
|
30052
30326
|
function resolveUserStateRoot(env) {
|
|
30053
30327
|
const explicit = env.KXM_STATE_HOME?.trim();
|
|
30054
30328
|
if (explicit) {
|
|
30055
|
-
if (!
|
|
30329
|
+
if (!isAbsolute6(explicit)) throw new HubBindingError("local_state_root_not_absolute");
|
|
30056
30330
|
return resolve9(explicit);
|
|
30057
30331
|
}
|
|
30058
30332
|
if (process.platform === "win32") {
|
|
30059
30333
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
30060
|
-
const base2 = localAppData &&
|
|
30334
|
+
const base2 = localAppData && isAbsolute6(localAppData) ? localAppData : join16(homedir2(), "AppData", "Local");
|
|
30061
30335
|
return resolve9(base2, "KXM");
|
|
30062
30336
|
}
|
|
30063
30337
|
if (process.platform === "darwin") return resolve9(homedir2(), "Library", "Application Support", "KXM");
|
|
30064
30338
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
30065
|
-
const base = xdgState &&
|
|
30339
|
+
const base = xdgState && isAbsolute6(xdgState) ? xdgState : join16(homedir2(), ".local", "state");
|
|
30066
30340
|
return resolve9(base, "kxm");
|
|
30067
30341
|
}
|
|
30068
30342
|
function hubBindingFile(env = process.env) {
|
|
@@ -30113,7 +30387,7 @@ function readHubBinding(env = process.env) {
|
|
|
30113
30387
|
// plugins/kxm/src/hub-env.ts
|
|
30114
30388
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync12, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
|
|
30115
30389
|
import { homedir as homedir3 } from "node:os";
|
|
30116
|
-
import { dirname as dirname9, isAbsolute as
|
|
30390
|
+
import { dirname as dirname9, isAbsolute as isAbsolute7, join as join17, resolve as resolve10 } from "node:path";
|
|
30117
30391
|
var HUB_ENV_SCHEMA = "kxm.hub-env.v1";
|
|
30118
30392
|
var HubEnvError = class extends Error {
|
|
30119
30393
|
constructor(message) {
|
|
@@ -30124,17 +30398,17 @@ var HubEnvError = class extends Error {
|
|
|
30124
30398
|
function resolveUserStateRoot2(env) {
|
|
30125
30399
|
const explicit = env.KXM_STATE_HOME?.trim();
|
|
30126
30400
|
if (explicit) {
|
|
30127
|
-
if (!
|
|
30401
|
+
if (!isAbsolute7(explicit)) throw new HubEnvError("local_state_root_not_absolute");
|
|
30128
30402
|
return resolve10(explicit);
|
|
30129
30403
|
}
|
|
30130
30404
|
if (process.platform === "win32") {
|
|
30131
30405
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
30132
|
-
const base2 = localAppData &&
|
|
30406
|
+
const base2 = localAppData && isAbsolute7(localAppData) ? localAppData : join17(homedir3(), "AppData", "Local");
|
|
30133
30407
|
return resolve10(base2, "KXM");
|
|
30134
30408
|
}
|
|
30135
30409
|
if (process.platform === "darwin") return resolve10(homedir3(), "Library", "Application Support", "KXM");
|
|
30136
30410
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
30137
|
-
const base = xdgState &&
|
|
30411
|
+
const base = xdgState && isAbsolute7(xdgState) ? xdgState : join17(homedir3(), ".local", "state");
|
|
30138
30412
|
return resolve10(base, "kxm");
|
|
30139
30413
|
}
|
|
30140
30414
|
function hubEnvFile(env = process.env) {
|
|
@@ -30185,7 +30459,7 @@ function resolveClientHubAuthToken(env, project) {
|
|
|
30185
30459
|
}
|
|
30186
30460
|
|
|
30187
30461
|
// plugins/kxm/src/logger.ts
|
|
30188
|
-
import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync as
|
|
30462
|
+
import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync as statSync5, unlinkSync as unlinkSync2 } from "node:fs";
|
|
30189
30463
|
import { dirname as dirname10 } from "node:path";
|
|
30190
30464
|
var LOG_LEVEL_PRIORITY = {
|
|
30191
30465
|
debug: 10,
|
|
@@ -30256,7 +30530,7 @@ function createLogger(options) {
|
|
|
30256
30530
|
let currentSize = 0;
|
|
30257
30531
|
if (filePath && existsSync15(filePath)) {
|
|
30258
30532
|
try {
|
|
30259
|
-
currentSize =
|
|
30533
|
+
currentSize = statSync5(filePath).size;
|
|
30260
30534
|
} catch {
|
|
30261
30535
|
currentSize = 0;
|
|
30262
30536
|
}
|
|
@@ -30403,6 +30677,24 @@ function readRecentSupervisorError(paths) {
|
|
|
30403
30677
|
return void 0;
|
|
30404
30678
|
}
|
|
30405
30679
|
}
|
|
30680
|
+
var SETTLED_RUN_STATUS = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
30681
|
+
function kxmSupervisorRecordIsLive(record2) {
|
|
30682
|
+
return supervisorStatusOf(record2).running;
|
|
30683
|
+
}
|
|
30684
|
+
function unsettledRunIds(context) {
|
|
30685
|
+
const ids = [];
|
|
30686
|
+
for (const stored of context.eventStore.runsForProject(context.projectId, 1e4)) {
|
|
30687
|
+
let status = stored.status;
|
|
30688
|
+
try {
|
|
30689
|
+
status = projectKxmRunReadOnly(context, stored.runId).status;
|
|
30690
|
+
} catch {
|
|
30691
|
+
ids.push(stored.runId);
|
|
30692
|
+
continue;
|
|
30693
|
+
}
|
|
30694
|
+
if (!SETTLED_RUN_STATUS.has(status)) ids.push(stored.runId);
|
|
30695
|
+
}
|
|
30696
|
+
return ids;
|
|
30697
|
+
}
|
|
30406
30698
|
function supervisorStatusOf(record2) {
|
|
30407
30699
|
if (!record2) return { running: false };
|
|
30408
30700
|
const heartbeatAgeMs = Date.now() - Date.parse(record2.heartbeatAt);
|
|
@@ -30751,7 +31043,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
30751
31043
|
}
|
|
30752
31044
|
};
|
|
30753
31045
|
const contextFor = (projectRoot) => {
|
|
30754
|
-
if (!
|
|
31046
|
+
if (!isAbsolute8(projectRoot)) {
|
|
30755
31047
|
throw runtimeError("runtime_request_invalid", "projectRoot", "projectRoot must be an absolute path");
|
|
30756
31048
|
}
|
|
30757
31049
|
const key = projectRuntimeKey(projectRoot);
|
|
@@ -30817,6 +31109,62 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
30817
31109
|
sendJson(response, 200, { ok: true, projectId: context.projectId, retried, outbox: context.eventStore.outboxStatus() });
|
|
30818
31110
|
return;
|
|
30819
31111
|
}
|
|
31112
|
+
if (request.method === "POST" && url.pathname === "/v1/projects/unregister") {
|
|
31113
|
+
const body = await readJsonBody(request);
|
|
31114
|
+
const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
|
|
31115
|
+
const force = body.force === true;
|
|
31116
|
+
if (!projectRoot || !isAbsolute8(projectRoot)) {
|
|
31117
|
+
sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: "projectRoot must be an absolute path" });
|
|
31118
|
+
return;
|
|
31119
|
+
}
|
|
31120
|
+
if (!force) {
|
|
31121
|
+
const key2 = projectRuntimeKey(projectRoot);
|
|
31122
|
+
const existing2 = contexts.get(key2);
|
|
31123
|
+
let openRunIds = [];
|
|
31124
|
+
if (existing2) {
|
|
31125
|
+
openRunIds = unsettledRunIds(existing2);
|
|
31126
|
+
} else {
|
|
31127
|
+
const registration = registry.projectByRoot(projectRoot);
|
|
31128
|
+
const eventsPath = join18(paths.projectsDir, registration?.projectKey ?? projectRuntimeKey(projectRoot), "run-events.db");
|
|
31129
|
+
if (registration && existsSync16(eventsPath)) {
|
|
31130
|
+
const store = new KxmRunEventStore(eventsPath);
|
|
31131
|
+
try {
|
|
31132
|
+
openRunIds = store.runsForProject(registration.projectId, 1e4).filter((run) => !SETTLED_RUN_STATUS.has(run.status)).map((run) => run.runId);
|
|
31133
|
+
} finally {
|
|
31134
|
+
store.close();
|
|
31135
|
+
}
|
|
31136
|
+
}
|
|
31137
|
+
}
|
|
31138
|
+
if (openRunIds.length > 0) {
|
|
31139
|
+
sendJson(response, 409, {
|
|
31140
|
+
ok: false,
|
|
31141
|
+
error: "runtime_project_busy",
|
|
31142
|
+
message: `unsettled run ${openRunIds.join(", ")}`,
|
|
31143
|
+
runIds: openRunIds
|
|
31144
|
+
});
|
|
31145
|
+
return;
|
|
31146
|
+
}
|
|
31147
|
+
const laneCount = registry.homeLaneCount(projectRoot);
|
|
31148
|
+
if (laneCount > 0) {
|
|
31149
|
+
sendJson(response, 409, {
|
|
31150
|
+
ok: false,
|
|
31151
|
+
error: "runtime_project_has_lanes",
|
|
31152
|
+
message: `home root still has ${laneCount} lane(s)`
|
|
31153
|
+
});
|
|
31154
|
+
return;
|
|
31155
|
+
}
|
|
31156
|
+
}
|
|
31157
|
+
const key = projectRuntimeKey(projectRoot);
|
|
31158
|
+
const existing = contexts.get(key);
|
|
31159
|
+
const unregistered = registry.unregisterProject(projectRoot);
|
|
31160
|
+
if (existing) {
|
|
31161
|
+
contexts.delete(key);
|
|
31162
|
+
syncStatuses.delete(key);
|
|
31163
|
+
closeKxmRuntimeContext(existing);
|
|
31164
|
+
}
|
|
31165
|
+
sendJson(response, 200, { ok: true, unregistered });
|
|
31166
|
+
return;
|
|
31167
|
+
}
|
|
30820
31168
|
if (request.method === "POST" && url.pathname === "/v1/runs") {
|
|
30821
31169
|
const body = await readJsonBody(request);
|
|
30822
31170
|
const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
|
|
@@ -30870,24 +31218,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
30870
31218
|
const delayMs = typeof body.delayMs === "number" && body.delayMs > 0 ? body.delayMs : 0;
|
|
30871
31219
|
const createProducer = () => body.mode === "live" ? createKxmOneShotProducer({
|
|
30872
31220
|
projectRoot,
|
|
30873
|
-
timeoutMs: kxmProjectAdmissionLimits(bundle).agentStepTimeoutMs
|
|
30874
|
-
defaultHarness: String(bundle.project.value.defaultHarness ?? "pi"),
|
|
30875
|
-
resolveHarness: (agentId) => {
|
|
30876
|
-
const agent = bundle.agents.get(agentId);
|
|
30877
|
-
return typeof agent?.value.harness === "string" ? agent.value.harness : void 0;
|
|
30878
|
-
},
|
|
30879
|
-
resolveModel: (agentId) => {
|
|
30880
|
-
const agent = bundle.agents.get(agentId);
|
|
30881
|
-
const model = agent?.value.model;
|
|
30882
|
-
if (!model || typeof model !== "object" || Array.isArray(model)) return void 0;
|
|
30883
|
-
const value = model;
|
|
30884
|
-
const provider = typeof value.provider === "string" ? value.provider : void 0;
|
|
30885
|
-
const modelName = typeof value.model === "string" ? value.model : void 0;
|
|
30886
|
-
if (!provider || !modelName || !isRouteAdmitted(projectRoot, `${provider}/${modelName}`)) {
|
|
30887
|
-
throw new Error("producer_route_not_admitted");
|
|
30888
|
-
}
|
|
30889
|
-
return { provider, model: modelName };
|
|
30890
|
-
}
|
|
31221
|
+
timeoutMs: kxmProjectAdmissionLimits(bundle).agentStepTimeoutMs
|
|
30891
31222
|
}) : createKxmSimulatedProducer(async () => {
|
|
30892
31223
|
if (delayMs > 0) {
|
|
30893
31224
|
await new Promise((r) => setTimeout(r, delayMs));
|
|
@@ -31040,7 +31371,10 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
31040
31371
|
sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: `project ${requestedProjectId} is not the bound project ${context.projectId}` });
|
|
31041
31372
|
return;
|
|
31042
31373
|
}
|
|
31043
|
-
const
|
|
31374
|
+
const limitText = url.searchParams.get("limit");
|
|
31375
|
+
const parsedLimit = limitText === null ? 50 : Number(limitText);
|
|
31376
|
+
const limit = Number.isInteger(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 1e4) : 50;
|
|
31377
|
+
const runs = context.eventStore.runsForProject(requestedProjectId, limit).map((stored) => {
|
|
31044
31378
|
try {
|
|
31045
31379
|
return projectKxmRunReadOnly(context, stored.runId);
|
|
31046
31380
|
} catch (error) {
|
|
@@ -31314,6 +31648,7 @@ export {
|
|
|
31314
31648
|
hashKxmSupervisorToken,
|
|
31315
31649
|
hashKxmTokenProof,
|
|
31316
31650
|
kxmRuntimeRequest,
|
|
31651
|
+
kxmSupervisorRecordIsLive,
|
|
31317
31652
|
kxmSupervisorStatus,
|
|
31318
31653
|
kxmSupervisorTokenFile,
|
|
31319
31654
|
readKxmSupervisorToken,
|