@kontextmind/kxm 0.7.135 → 0.7.138
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/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +215 -101
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +14 -0
- package/plugins/kxm/src/cli/project.ts +53 -0
- package/plugins/kxm/src/cli.ts +11 -0
- package/plugins/kxm/src/mcp-server.ts +1 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "kxm",
|
|
4
4
|
"displayName": "KXM",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.138",
|
|
6
6
|
"description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "KontextMind",
|
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -24791,6 +24791,10 @@ function listRoleBindings(root) {
|
|
|
24791
24791
|
}
|
|
24792
24792
|
return result;
|
|
24793
24793
|
}
|
|
24794
|
+
function isRouteAdmitted(root, modelId2) {
|
|
24795
|
+
const policy = loadRoutePolicy(root);
|
|
24796
|
+
return policy.admitted.includes(modelId2) && !policy.disabled.includes(modelId2);
|
|
24797
|
+
}
|
|
24794
24798
|
function listInventoryModels(root) {
|
|
24795
24799
|
const path4 = join14(root, ".kxm", "models", "inventory.yaml");
|
|
24796
24800
|
if (!existsSync14(path4)) return [];
|
|
@@ -27513,10 +27517,73 @@ var init_model_inventory = __esm({
|
|
|
27513
27517
|
}
|
|
27514
27518
|
});
|
|
27515
27519
|
|
|
27520
|
+
// plugins/kxm/src/vision-gate.ts
|
|
27521
|
+
import { execFileSync } from "node:child_process";
|
|
27522
|
+
import { existsSync as existsSync24, statSync as statSync5 } from "node:fs";
|
|
27523
|
+
function visionGatePrompt(question, imagePath) {
|
|
27524
|
+
const bounded = question.trim().slice(0, QUESTION_MAX_CHARS);
|
|
27525
|
+
const reference = imagePath ? `Look at the image @${imagePath}.` : "Look at the image attached to this prompt.";
|
|
27526
|
+
return [
|
|
27527
|
+
"You are judging a UI screenshot for an automated verification gate.",
|
|
27528
|
+
reference,
|
|
27529
|
+
bounded,
|
|
27530
|
+
'Reply with exactly one JSON object and nothing else: {"verdict": true} or {"verdict": false}'
|
|
27531
|
+
].join(" ");
|
|
27532
|
+
}
|
|
27533
|
+
function parseVisionVerdict(raw) {
|
|
27534
|
+
const match = /"verdict"\s*:\s*(true|false)/i.exec(raw);
|
|
27535
|
+
return match ? match[1] === "true" : null;
|
|
27536
|
+
}
|
|
27537
|
+
function defaultTransport(prompt, timeoutMs) {
|
|
27538
|
+
return execFileSync(
|
|
27539
|
+
"pi",
|
|
27540
|
+
["--provider", "zai-coding-cn", "--model", "glm-5.3-flash", "-p", "--no-session", prompt],
|
|
27541
|
+
{ encoding: "utf8", timeout: timeoutMs, windowsHide: true }
|
|
27542
|
+
);
|
|
27543
|
+
}
|
|
27544
|
+
async function runVisionGate(options) {
|
|
27545
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
27546
|
+
const route = options.route ?? VISION_GATE_DEFAULT_ROUTE;
|
|
27547
|
+
const timeoutMs = options.timeoutMs ?? 9e4;
|
|
27548
|
+
const base = { verdict: null, route, raw: "" };
|
|
27549
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 5e3 || timeoutMs > 3e5) {
|
|
27550
|
+
return { ...base, divergence: "timeout_out_of_bounds" };
|
|
27551
|
+
}
|
|
27552
|
+
if (!options.imagePath || !existsSync24(options.imagePath) || !statSync5(options.imagePath).isFile()) {
|
|
27553
|
+
return { ...base, divergence: "image unreadable" };
|
|
27554
|
+
}
|
|
27555
|
+
if (!isRouteAdmitted(projectRoot, route)) {
|
|
27556
|
+
return { ...base, divergence: "route_not_admitted" };
|
|
27557
|
+
}
|
|
27558
|
+
const prompt = visionGatePrompt(options.question, options.imagePath);
|
|
27559
|
+
let raw;
|
|
27560
|
+
try {
|
|
27561
|
+
raw = options.transport ? await options.transport(prompt) : defaultTransport(prompt, timeoutMs);
|
|
27562
|
+
} catch (error) {
|
|
27563
|
+
return { ...base, divergence: `transport failed: ${String(error?.message ?? error).slice(0, 200)}` };
|
|
27564
|
+
}
|
|
27565
|
+
const boundedRaw = raw.slice(0, RAW_MAX_CHARS);
|
|
27566
|
+
const verdict = parseVisionVerdict(boundedRaw);
|
|
27567
|
+
if (verdict === null) {
|
|
27568
|
+
return { ...base, raw: boundedRaw, divergence: "verdict unreadable" };
|
|
27569
|
+
}
|
|
27570
|
+
return { ...base, verdict, raw: boundedRaw };
|
|
27571
|
+
}
|
|
27572
|
+
var VISION_GATE_DEFAULT_ROUTE, QUESTION_MAX_CHARS, RAW_MAX_CHARS;
|
|
27573
|
+
var init_vision_gate = __esm({
|
|
27574
|
+
"plugins/kxm/src/vision-gate.ts"() {
|
|
27575
|
+
"use strict";
|
|
27576
|
+
init_routes();
|
|
27577
|
+
VISION_GATE_DEFAULT_ROUTE = "zai-coding-cn/glm-5.3-flash";
|
|
27578
|
+
QUESTION_MAX_CHARS = 500;
|
|
27579
|
+
RAW_MAX_CHARS = 2e3;
|
|
27580
|
+
}
|
|
27581
|
+
});
|
|
27582
|
+
|
|
27516
27583
|
// plugins/kxm/src/permission.ts
|
|
27517
27584
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
27518
27585
|
import { createHash as createHash17 } from "node:crypto";
|
|
27519
|
-
import { existsSync as
|
|
27586
|
+
import { existsSync as existsSync25, mkdtempSync, mkdirSync as mkdirSync20, readFileSync as readFileSync24, rmSync as rmSync8, writeFileSync as writeFileSync19 } from "node:fs";
|
|
27520
27587
|
import { tmpdir } from "node:os";
|
|
27521
27588
|
import { dirname as dirname14, join as join28, resolve as resolve19, sep as sep4 } from "node:path";
|
|
27522
27589
|
function canonical(value) {
|
|
@@ -28097,7 +28164,7 @@ function loadKxmProjectAtRevision(root, revision, options = {}) {
|
|
|
28097
28164
|
}
|
|
28098
28165
|
const boundPath = options.repositoryBindings?.[member.repositoryId];
|
|
28099
28166
|
const memberWorktree = boundPath ? resolve19(boundPath) : member.pathHint ? resolve19(root, ...member.pathHint.split("/")) : void 0;
|
|
28100
|
-
if (!memberWorktree || !
|
|
28167
|
+
if (!memberWorktree || !existsSync25(memberWorktree)) {
|
|
28101
28168
|
if (member.required === false) continue;
|
|
28102
28169
|
throw new KxmConfigError([{
|
|
28103
28170
|
phase: "discovery",
|
|
@@ -28186,7 +28253,7 @@ function portableMemberPathHint(pathHint) {
|
|
|
28186
28253
|
return pathHint !== "." && kxmPortablePath(pathHint);
|
|
28187
28254
|
}
|
|
28188
28255
|
function loadBaseProjectDeclarations(projectFile) {
|
|
28189
|
-
if (!
|
|
28256
|
+
if (!existsSync25(projectFile)) return [];
|
|
28190
28257
|
const value = parseRestrictedYaml2(readFileSync24(projectFile, "utf8"), ".kxm/project.yaml");
|
|
28191
28258
|
const repositories = Array.isArray(value?.repositories) ? value.repositories : [];
|
|
28192
28259
|
const members = [];
|
|
@@ -28263,7 +28330,7 @@ import { randomUUID as randomUUID11 } from "node:crypto";
|
|
|
28263
28330
|
import {
|
|
28264
28331
|
chmodSync as chmodSync5,
|
|
28265
28332
|
closeSync as closeSync3,
|
|
28266
|
-
existsSync as
|
|
28333
|
+
existsSync as existsSync26,
|
|
28267
28334
|
fsyncSync as fsyncSync2,
|
|
28268
28335
|
linkSync,
|
|
28269
28336
|
lstatSync as lstatSync7,
|
|
@@ -28372,7 +28439,7 @@ function readRegularBounded(file, label) {
|
|
|
28372
28439
|
}
|
|
28373
28440
|
function readOptionalManaged(projectRoot, path4) {
|
|
28374
28441
|
const file = join29(projectRoot, ...path4.split("/"));
|
|
28375
|
-
if (!
|
|
28442
|
+
if (!existsSync26(file)) return void 0;
|
|
28376
28443
|
let parent = dirname15(file);
|
|
28377
28444
|
const boundary = resolve20(projectRoot);
|
|
28378
28445
|
while (parent !== boundary) {
|
|
@@ -28385,7 +28452,7 @@ function readOptionalManaged(projectRoot, path4) {
|
|
|
28385
28452
|
function readProjectAndProvenance(projectRoot, schemasDir) {
|
|
28386
28453
|
const projectFile = join29(projectRoot, ".kxm", "project.yaml");
|
|
28387
28454
|
const provenanceFile = join29(projectRoot, ...KXM_TEMPLATE_PROVENANCE_PATH.split("/"));
|
|
28388
|
-
if (!
|
|
28455
|
+
if (!existsSync26(projectFile) || !existsSync26(provenanceFile)) return void 0;
|
|
28389
28456
|
const projectBytes = readOptionalManaged(projectRoot, ".kxm/project.yaml");
|
|
28390
28457
|
const provenanceBytes = readOptionalManaged(projectRoot, KXM_TEMPLATE_PROVENANCE_PATH);
|
|
28391
28458
|
if (!projectBytes || !provenanceBytes) return void 0;
|
|
@@ -28566,11 +28633,11 @@ function updatePhase(projectRoot, operation, phase) {
|
|
|
28566
28633
|
}
|
|
28567
28634
|
function readOperation(projectRoot, schemasDir) {
|
|
28568
28635
|
const root = transactionRoot(projectRoot);
|
|
28569
|
-
if (!
|
|
28636
|
+
if (!existsSync26(root)) return void 0;
|
|
28570
28637
|
const stat = lstatSync7(root);
|
|
28571
28638
|
if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("init_transaction_invalid", TRANSACTION_NAME, "initialization transaction must be a regular directory", "path");
|
|
28572
28639
|
const file = operationFile(projectRoot);
|
|
28573
|
-
if (!
|
|
28640
|
+
if (!existsSync26(file)) {
|
|
28574
28641
|
const entries = readdirSync9(root, { withFileTypes: true });
|
|
28575
28642
|
const recoverable = entries.every((entry) => entry.isFile() && /^\.operation-op_[a-f0-9]{32}-[a-f0-9-]{36}\.tmp$/.test(entry.name));
|
|
28576
28643
|
if (recoverable) return void 0;
|
|
@@ -28598,7 +28665,7 @@ function readOperation(projectRoot, schemasDir) {
|
|
|
28598
28665
|
return operation;
|
|
28599
28666
|
}
|
|
28600
28667
|
function hasKxmInitTransaction(projectRoot) {
|
|
28601
|
-
return
|
|
28668
|
+
return existsSync26(transactionRoot(projectRoot));
|
|
28602
28669
|
}
|
|
28603
28670
|
function readTarget(projectRoot, path4) {
|
|
28604
28671
|
return readOptionalManaged(join29(transactionRoot(projectRoot), "targets"), path4);
|
|
@@ -28675,10 +28742,10 @@ function sourceConfigFiles(projectRoot) {
|
|
|
28675
28742
|
".kxm/repo/repo.yaml",
|
|
28676
28743
|
".kxm/repo/env.yaml"
|
|
28677
28744
|
];
|
|
28678
|
-
const files = fixed.filter((path4) =>
|
|
28745
|
+
const files = fixed.filter((path4) => existsSync26(join29(projectRoot, ...path4.split("/"))));
|
|
28679
28746
|
for (const directory of ["agents", "models", "workflows"]) {
|
|
28680
28747
|
const absolute = join29(projectRoot, ".kxm", directory);
|
|
28681
|
-
if (!
|
|
28748
|
+
if (!existsSync26(absolute)) continue;
|
|
28682
28749
|
const stat = lstatSync7(absolute);
|
|
28683
28750
|
if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_directory_invalid", `.kxm/${directory}`, "configuration directory must be regular", "path");
|
|
28684
28751
|
for (const entry of readdirSync9(absolute, { withFileTypes: true })) {
|
|
@@ -28700,7 +28767,7 @@ function absoluteMemberBindings(projectRoot, project, explicit = {}) {
|
|
|
28700
28767
|
if (repository.role !== "member" || typeof repository.id !== "string" || Object.hasOwn(bindings, repository.id)) continue;
|
|
28701
28768
|
if (typeof repository.pathHint === "string") {
|
|
28702
28769
|
const candidate2 = resolve20(projectRoot, ...repository.pathHint.split("/"));
|
|
28703
|
-
if (
|
|
28770
|
+
if (existsSync26(candidate2)) bindings[repository.id] = candidate2;
|
|
28704
28771
|
}
|
|
28705
28772
|
}
|
|
28706
28773
|
return bindings;
|
|
@@ -28726,7 +28793,7 @@ function prepareShadow(projectRoot, operation, options) {
|
|
|
28726
28793
|
if (!bytes) fail3("init_transaction_target_missing", entry.path, "pinned target artifact is missing");
|
|
28727
28794
|
const destination = join29(shadow, ...entry.path.split("/"));
|
|
28728
28795
|
mkdirSync21(dirname15(destination), { recursive: true, mode: 448 });
|
|
28729
|
-
const mode =
|
|
28796
|
+
const mode = existsSync26(destination) ? lstatSync7(destination).mode & 511 : 420;
|
|
28730
28797
|
writeDurable(destination, bytes, mode);
|
|
28731
28798
|
}
|
|
28732
28799
|
const project = parseRestrictedYaml2(readFileSync25(join29(shadow, ".kxm", "project.yaml")), ".kxm/project.yaml");
|
|
@@ -28767,7 +28834,7 @@ function prepareOperation(projectRoot, operation, rendered, options) {
|
|
|
28767
28834
|
const transaction = transactionRoot(projectRoot);
|
|
28768
28835
|
validateOperationRecord(operation, options.schemasDir);
|
|
28769
28836
|
validateOperationIntent(projectRoot, operation);
|
|
28770
|
-
if (
|
|
28837
|
+
if (existsSync26(transaction)) fail3("init_transaction_exists", TRANSACTION_NAME, "an initialization transaction already exists and must be resumed");
|
|
28771
28838
|
mkdirSync21(transaction, { mode: 448 });
|
|
28772
28839
|
syncDirectory2(projectRoot);
|
|
28773
28840
|
writeOperation(projectRoot, operation);
|
|
@@ -28794,14 +28861,14 @@ function ensureDestinationParents(projectRoot, path4) {
|
|
|
28794
28861
|
let current = boundary;
|
|
28795
28862
|
for (const segment of segments) {
|
|
28796
28863
|
current = join29(current, segment);
|
|
28797
|
-
if (!
|
|
28864
|
+
if (!existsSync26(current)) mkdirSync21(current, { mode: 448 });
|
|
28798
28865
|
const stat = lstatSync7(current);
|
|
28799
28866
|
if (stat.isSymbolicLink() || !stat.isDirectory()) fail3("repair_destination_parent_invalid", path4, "destination parent must be a regular directory", "path");
|
|
28800
28867
|
}
|
|
28801
28868
|
}
|
|
28802
28869
|
function destinationSha(projectRoot, path4) {
|
|
28803
28870
|
const file = join29(projectRoot, ...path4.split("/"));
|
|
28804
|
-
if (!
|
|
28871
|
+
if (!existsSync26(file)) return void 0;
|
|
28805
28872
|
return kxmContentSha256(readRegularBounded(file, path4));
|
|
28806
28873
|
}
|
|
28807
28874
|
function requireConditionalLinkSupport(directory, operationId, path4) {
|
|
@@ -28828,13 +28895,13 @@ function atomicInstallTarget(projectRoot, operation, entry) {
|
|
|
28828
28895
|
const temporaryPrefix = `.kxm-repair-${operation.operationId}-`;
|
|
28829
28896
|
const pathKey = kxmContentSha256(entry.path).slice("sha256:".length, "sha256:".length + 16);
|
|
28830
28897
|
const displaced = join29(directory, `${temporaryPrefix}${pathKey}.preimage`);
|
|
28831
|
-
if (
|
|
28898
|
+
if (existsSync26(displaced)) {
|
|
28832
28899
|
const stat = lstatSync7(displaced);
|
|
28833
28900
|
if (stat.isSymbolicLink() || !stat.isFile()) fail3("repair_preimage_temporary_invalid", entry.path, "stale displaced preimage is not a regular file", "path");
|
|
28834
28901
|
if (entry.observedSha256 === null || kxmContentSha256(readFileSync25(displaced)) !== entry.observedSha256) {
|
|
28835
28902
|
fail3("repair_preimage_temporary_mismatch", entry.path, "stale displaced preimage does not match the pinned operation");
|
|
28836
28903
|
}
|
|
28837
|
-
if (!
|
|
28904
|
+
if (!existsSync26(destination)) {
|
|
28838
28905
|
try {
|
|
28839
28906
|
linkSync(displaced, destination);
|
|
28840
28907
|
} catch {
|
|
@@ -28860,14 +28927,14 @@ function atomicInstallTarget(projectRoot, operation, entry) {
|
|
|
28860
28927
|
const bytes = readTarget(projectRoot, entry.path);
|
|
28861
28928
|
if (!bytes) fail3("init_transaction_target_missing", entry.path, "pinned target artifact is missing");
|
|
28862
28929
|
const temporary = join29(directory, `${temporaryPrefix}${randomUUID11()}.tmp`);
|
|
28863
|
-
const mode =
|
|
28930
|
+
const mode = existsSync26(destination) ? lstatSync7(destination).mode & 511 : 420;
|
|
28864
28931
|
try {
|
|
28865
28932
|
writeDurableNew(temporary, bytes, mode);
|
|
28866
28933
|
if (entry.observedSha256 !== null) {
|
|
28867
28934
|
renameSync7(destination, displaced);
|
|
28868
28935
|
syncDirectory2(directory);
|
|
28869
28936
|
if (kxmContentSha256(readFileSync25(displaced)) !== entry.observedSha256) {
|
|
28870
|
-
if (!
|
|
28937
|
+
if (!existsSync26(destination)) {
|
|
28871
28938
|
try {
|
|
28872
28939
|
linkSync(displaced, destination);
|
|
28873
28940
|
} catch {
|
|
@@ -28883,7 +28950,7 @@ function atomicInstallTarget(projectRoot, operation, entry) {
|
|
|
28883
28950
|
try {
|
|
28884
28951
|
linkSync(temporary, destination);
|
|
28885
28952
|
} catch {
|
|
28886
|
-
if (!
|
|
28953
|
+
if (!existsSync26(destination) && existsSync26(displaced)) {
|
|
28887
28954
|
try {
|
|
28888
28955
|
linkSync(displaced, destination);
|
|
28889
28956
|
} catch {
|
|
@@ -28906,7 +28973,7 @@ function atomicInstallTarget(projectRoot, operation, entry) {
|
|
|
28906
28973
|
}
|
|
28907
28974
|
function installedCreateMatches(projectRoot, operation) {
|
|
28908
28975
|
const configRoot = join29(projectRoot, ".kxm");
|
|
28909
|
-
if (!
|
|
28976
|
+
if (!existsSync26(configRoot)) return false;
|
|
28910
28977
|
const actual = [];
|
|
28911
28978
|
const visit2 = (directory) => {
|
|
28912
28979
|
for (const entry of readdirSync9(directory, { withFileTypes: true })) {
|
|
@@ -28926,7 +28993,7 @@ function relativeConfigPath(projectRoot, file) {
|
|
|
28926
28993
|
return relative5(resolve20(projectRoot), file).replaceAll("\\", "/");
|
|
28927
28994
|
}
|
|
28928
28995
|
function syncTreeDirectories(root) {
|
|
28929
|
-
if (!
|
|
28996
|
+
if (!existsSync26(root)) return;
|
|
28930
28997
|
const directories = [];
|
|
28931
28998
|
const visit2 = (directory) => {
|
|
28932
28999
|
directories.push(directory);
|
|
@@ -28956,7 +29023,7 @@ function applyOperation(projectRoot, original, options, resumed) {
|
|
|
28956
29023
|
operation = updatePhase(projectRoot, operation, "applying");
|
|
28957
29024
|
if (operation.kind === "create") {
|
|
28958
29025
|
const destination = join29(projectRoot, ".kxm");
|
|
28959
|
-
if (!
|
|
29026
|
+
if (!existsSync26(destination)) {
|
|
28960
29027
|
rebuildCreateShadow(projectRoot, operation, effectiveOptions);
|
|
28961
29028
|
renameSync7(join29(shadowRoot(projectRoot), ".kxm"), destination);
|
|
28962
29029
|
syncDirectory2(projectRoot);
|
|
@@ -29119,7 +29186,7 @@ function resumeKxmInitTransaction(projectRoot, options = {}) {
|
|
|
29119
29186
|
let operation = readOperation(projectRoot, options.schemasDir);
|
|
29120
29187
|
if (!operation) {
|
|
29121
29188
|
const root = transactionRoot(projectRoot);
|
|
29122
|
-
if (
|
|
29189
|
+
if (existsSync26(root)) {
|
|
29123
29190
|
rmSync9(root, { recursive: true, force: true });
|
|
29124
29191
|
syncDirectory2(projectRoot);
|
|
29125
29192
|
}
|
|
@@ -29167,7 +29234,7 @@ var init_repair = __esm({
|
|
|
29167
29234
|
|
|
29168
29235
|
// plugins/kxm/src/init.ts
|
|
29169
29236
|
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
29170
|
-
import { existsSync as
|
|
29237
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
29171
29238
|
import { basename as basename6, join as join30 } from "node:path";
|
|
29172
29239
|
function initIssue(code, file, message) {
|
|
29173
29240
|
return { phase: "discovery", code, file, message };
|
|
@@ -29261,7 +29328,7 @@ function validateBindingIdentities(bundle, persisted, explicit) {
|
|
|
29261
29328
|
}
|
|
29262
29329
|
function initializeKxmProjectAtGitRoot(start, gitRoot, options, mutationLock) {
|
|
29263
29330
|
const storeOptions = bindingStoreOptions(options);
|
|
29264
|
-
const persisted =
|
|
29331
|
+
const persisted = existsSync27(join30(gitRoot, ".kxm", "project.yaml")) ? readKxmLocalBindings(gitRoot, storeOptions) : void 0;
|
|
29265
29332
|
const repositoryBindings = Object.fromEntries([
|
|
29266
29333
|
...Object.entries(persisted?.repositories ?? {}),
|
|
29267
29334
|
...Object.entries(options.repositoryBindings ?? {})
|
|
@@ -29449,7 +29516,7 @@ function initializeKxmProjectAtGitRoot(start, gitRoot, options, mutationLock) {
|
|
|
29449
29516
|
const files = [...rendered.files.keys()];
|
|
29450
29517
|
if (options.dryRun) return { action: "planned", plan, projectRoot: gitRoot, files };
|
|
29451
29518
|
if (!mutationLock) throw new Error("project mutation lock is required to create configuration");
|
|
29452
|
-
if (
|
|
29519
|
+
if (existsSync27(join30(gitRoot, ".kxm"))) {
|
|
29453
29520
|
throw new KxmConfigError([initIssue("workspace_changed", ".kxm", "workspace changed after planning; existing .kxm state was not overwritten")]);
|
|
29454
29521
|
}
|
|
29455
29522
|
const created = prepareAndApplyKxmCreate(gitRoot, rendered, transactionOptions);
|
|
@@ -29687,6 +29754,7 @@ __export(project_exports, {
|
|
|
29687
29754
|
cmdRouteCount: () => cmdRouteCount,
|
|
29688
29755
|
cmdRouteList: () => cmdRouteList,
|
|
29689
29756
|
cmdTenantStatus: () => cmdTenantStatus,
|
|
29757
|
+
cmdVisionAssert: () => cmdVisionAssert,
|
|
29690
29758
|
explicitRepositoryBindings: () => explicitRepositoryBindings,
|
|
29691
29759
|
initPlanPayload: () => initPlanPayload,
|
|
29692
29760
|
kxmDriveCliSeams: () => kxmDriveCliSeams,
|
|
@@ -29694,7 +29762,7 @@ __export(project_exports, {
|
|
|
29694
29762
|
runEngineNotice: () => runEngineNotice,
|
|
29695
29763
|
selectInventoryModel: () => selectInventoryModel
|
|
29696
29764
|
});
|
|
29697
|
-
import { existsSync as
|
|
29765
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
29698
29766
|
import { dirname as dirname16, join as join31, resolve as resolve21 } from "node:path";
|
|
29699
29767
|
import { createInterface as createInterface2 } from "node:readline";
|
|
29700
29768
|
function initPlanPayload(plan) {
|
|
@@ -29921,7 +29989,7 @@ async function cmdRestore(runtime, manifestArg, options = {}) {
|
|
|
29921
29989
|
[
|
|
29922
29990
|
...plan.stores.flatMap((store) => [
|
|
29923
29991
|
{ action: "write", target: store.targetPath },
|
|
29924
|
-
...[`${store.targetPath}-wal`, `${store.targetPath}-shm`].filter((file) =>
|
|
29992
|
+
...[`${store.targetPath}-wal`, `${store.targetPath}-shm`].filter((file) => existsSync28(file)).map((file) => ({ action: "delete", target: file }))
|
|
29925
29993
|
]),
|
|
29926
29994
|
...plan.files.map((file) => ({ action: "write", target: file.targetPath }))
|
|
29927
29995
|
],
|
|
@@ -30465,6 +30533,46 @@ async function cmdRouteCount(runtime) {
|
|
|
30465
30533
|
print(runtime.io, runtime.json, { ok: true, command: "routes count", admitted: policy.admitted.length, disabled: policy.disabled.length, summary }, summary);
|
|
30466
30534
|
return 0;
|
|
30467
30535
|
}
|
|
30536
|
+
async function cmdVisionAssert(runtime, options) {
|
|
30537
|
+
const image = options.image?.trim();
|
|
30538
|
+
const question = options.question?.trim();
|
|
30539
|
+
if (!image || !question) {
|
|
30540
|
+
print(runtime.io, runtime.json, { ok: false, command: "vision assert", error: "image_and_question_required" }, "both --image and --question are required");
|
|
30541
|
+
return 2;
|
|
30542
|
+
}
|
|
30543
|
+
let expect;
|
|
30544
|
+
if (options.expect !== void 0) {
|
|
30545
|
+
if (options.expect !== "true" && options.expect !== "false") {
|
|
30546
|
+
print(runtime.io, runtime.json, { ok: false, command: "vision assert", error: "expect_invalid" }, "--expect must be true or false");
|
|
30547
|
+
return 2;
|
|
30548
|
+
}
|
|
30549
|
+
expect = options.expect === "true";
|
|
30550
|
+
}
|
|
30551
|
+
let timeoutMs;
|
|
30552
|
+
if (options.timeoutMs !== void 0) {
|
|
30553
|
+
const parsed = Number(options.timeoutMs);
|
|
30554
|
+
if (!Number.isInteger(parsed)) {
|
|
30555
|
+
print(runtime.io, runtime.json, { ok: false, command: "vision assert", error: "timeout_invalid" }, "--timeout-ms must be an integer");
|
|
30556
|
+
return 2;
|
|
30557
|
+
}
|
|
30558
|
+
timeoutMs = parsed;
|
|
30559
|
+
}
|
|
30560
|
+
const result = await runVisionGate({
|
|
30561
|
+
projectRoot: runtime.dirs.workdir,
|
|
30562
|
+
imagePath: resolve21(runtime.cwd, image),
|
|
30563
|
+
question,
|
|
30564
|
+
...options.route !== void 0 ? { route: options.route } : {},
|
|
30565
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
30566
|
+
});
|
|
30567
|
+
const pass = result.verdict !== null && (expect === void 0 || result.verdict === expect);
|
|
30568
|
+
print(
|
|
30569
|
+
runtime.io,
|
|
30570
|
+
runtime.json,
|
|
30571
|
+
{ ok: pass, command: "vision assert", ...expect !== void 0 ? { expect } : {}, ...result },
|
|
30572
|
+
result.verdict === null ? `vision gate fail-closed: ${result.divergence}` : `verdict ${result.verdict}${expect !== void 0 ? ` (expected ${expect})` : ""}`
|
|
30573
|
+
);
|
|
30574
|
+
return pass ? 0 : 1;
|
|
30575
|
+
}
|
|
30468
30576
|
async function cmdModelInventoryRefresh(runtime) {
|
|
30469
30577
|
if (runtime.dryRun) {
|
|
30470
30578
|
print(runtime.io, runtime.json, { ok: true, command: "models inventory refresh", dryRun: true }, "would refresh .kxm/models/inventory.yaml");
|
|
@@ -30619,6 +30727,7 @@ var init_project = __esm({
|
|
|
30619
30727
|
init_hub_autostart();
|
|
30620
30728
|
init_model_inventory();
|
|
30621
30729
|
init_routes();
|
|
30730
|
+
init_vision_gate();
|
|
30622
30731
|
init_project_config();
|
|
30623
30732
|
init_init();
|
|
30624
30733
|
init_permission();
|
|
@@ -37033,7 +37142,7 @@ async function cmdRetrospectiveExport(runtime, runId, options) {
|
|
|
37033
37142
|
// plugins/kxm/src/cli/tasks.ts
|
|
37034
37143
|
var import_yaml15 = __toESM(require_dist(), 1);
|
|
37035
37144
|
init_harness();
|
|
37036
|
-
import { existsSync as
|
|
37145
|
+
import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync10 } from "node:fs";
|
|
37037
37146
|
import { basename as basename7, join as join32, resolve as resolve22 } from "node:path";
|
|
37038
37147
|
|
|
37039
37148
|
// plugins/kxm/src/suggest.ts
|
|
@@ -38093,7 +38202,7 @@ async function cmdSuggest(runtime, promptParts, probeHarnesses2 = probeHarnesses
|
|
|
38093
38202
|
}
|
|
38094
38203
|
const inventory = runtime.dryRun ? void 0 : await probeHarnesses2({ env: runtime.env });
|
|
38095
38204
|
const suggestion = suggestWorkflowAndRoles(prompt, { availableHarnesses: inventory?.harnesses });
|
|
38096
|
-
if (suggestion.execution.supported && [".yaml", ".yml"].some((extension) =>
|
|
38205
|
+
if (suggestion.execution.supported && [".yaml", ".yml"].some((extension) => existsSync29(join32(runtime.cwd, ".kxm", "workflows", `${suggestion.workflowId}${extension}`)))) {
|
|
38097
38206
|
suggestion.roles = [];
|
|
38098
38207
|
suggestion.execution = {
|
|
38099
38208
|
supported: false,
|
|
@@ -38313,7 +38422,7 @@ async function cmdStudioLayout(runtime, workflowPath) {
|
|
|
38313
38422
|
}
|
|
38314
38423
|
let yamlContent;
|
|
38315
38424
|
let workflowId = "default";
|
|
38316
|
-
if (
|
|
38425
|
+
if (existsSync29(filePath)) {
|
|
38317
38426
|
yamlContent = readFileSync27(filePath, "utf8");
|
|
38318
38427
|
} else {
|
|
38319
38428
|
yamlContent = `schema: kxm.workflow.v1
|
|
@@ -38389,12 +38498,12 @@ async function cmdStudioServe(runtime, options) {
|
|
|
38389
38498
|
try {
|
|
38390
38499
|
const wfDir = join32(runtime.cwd, ".kxm", "workflows");
|
|
38391
38500
|
const defaultPath = join32(wfDir, "default.yaml");
|
|
38392
|
-
let targetPath =
|
|
38393
|
-
if (!targetPath &&
|
|
38501
|
+
let targetPath = existsSync29(defaultPath) ? defaultPath : void 0;
|
|
38502
|
+
if (!targetPath && existsSync29(wfDir)) {
|
|
38394
38503
|
const yml = readdirSync10(wfDir).find((f2) => f2.endsWith(".yaml") || f2.endsWith(".yml"));
|
|
38395
38504
|
if (yml) targetPath = join32(wfDir, yml);
|
|
38396
38505
|
}
|
|
38397
|
-
if (targetPath &&
|
|
38506
|
+
if (targetPath && existsSync29(targetPath)) {
|
|
38398
38507
|
const raw = readFileSync27(targetPath, "utf8");
|
|
38399
38508
|
const parsed = (0, import_yaml15.parse)(raw);
|
|
38400
38509
|
const wfId = basename7(targetPath).replace(/\.(yaml|yml)$/, "");
|
|
@@ -39017,7 +39126,7 @@ async function cmdPluginInstall(runtime, options) {
|
|
|
39017
39126
|
init_project_config();
|
|
39018
39127
|
init_types();
|
|
39019
39128
|
import { spawn as spawn4 } from "node:child_process";
|
|
39020
|
-
import { existsSync as
|
|
39129
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
39021
39130
|
import { join as join35 } from "node:path";
|
|
39022
39131
|
var GENERATOR = "plans/kxm-roadmap/update-dashboard.mjs";
|
|
39023
39132
|
var SERVER = "ops/docs-site/serve.py";
|
|
@@ -39036,7 +39145,7 @@ function requireProject(runtime, command) {
|
|
|
39036
39145
|
}
|
|
39037
39146
|
function requireFile(runtime, command, root, relative8, error) {
|
|
39038
39147
|
const file = join35(root, relative8);
|
|
39039
|
-
if (
|
|
39148
|
+
if (existsSync30(file)) return file;
|
|
39040
39149
|
print(
|
|
39041
39150
|
runtime.io,
|
|
39042
39151
|
runtime.json,
|
|
@@ -39090,7 +39199,7 @@ init_runtime_store();
|
|
|
39090
39199
|
init_runtime_supervisor();
|
|
39091
39200
|
init_types();
|
|
39092
39201
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
39093
|
-
import { chmodSync as chmodSync6, existsSync as
|
|
39202
|
+
import { chmodSync as chmodSync6, existsSync as existsSync31, mkdirSync as mkdirSync24, readFileSync as readFileSync29, writeFileSync as writeFileSync22 } from "node:fs";
|
|
39094
39203
|
import { basename as basename8, dirname as dirname17, join as join36, resolve as resolve24 } from "node:path";
|
|
39095
39204
|
var LANE_SCHEMA = "kxm.lanes.v1";
|
|
39096
39205
|
var SHA_RE = /^[a-f0-9]{40,64}$/;
|
|
@@ -39146,7 +39255,7 @@ function isLaneFile(value) {
|
|
|
39146
39255
|
}
|
|
39147
39256
|
function loadLanes(runtime, command) {
|
|
39148
39257
|
const file = lanesFile(runtime);
|
|
39149
|
-
if (!
|
|
39258
|
+
if (!existsSync31(file)) return { schema: LANE_SCHEMA, lanes: {} };
|
|
39150
39259
|
try {
|
|
39151
39260
|
const parsed = JSON.parse(readFileSync29(file, "utf8"));
|
|
39152
39261
|
if (!isLaneFile(parsed)) return refuse(runtime, command, "lanes_unreadable", `lane record ${file} is malformed`);
|
|
@@ -39205,7 +39314,7 @@ function withLaneCwd(runtime, unit, command = "lane") {
|
|
|
39205
39314
|
const loaded = loadLanes(runtime, command);
|
|
39206
39315
|
if (typeof loaded === "number") return loaded;
|
|
39207
39316
|
const lane = loaded.lanes[unit];
|
|
39208
|
-
if (!lane || !
|
|
39317
|
+
if (!lane || !existsSync31(lane.path)) {
|
|
39209
39318
|
return refuse(runtime, command, "lane_missing", `lane ${unit} is not recorded or its worktree is absent`, { unit });
|
|
39210
39319
|
}
|
|
39211
39320
|
return {
|
|
@@ -39276,7 +39385,7 @@ function aheadBehind(path4, baseSha) {
|
|
|
39276
39385
|
return { behind: Number(match[1]), ahead: Number(match[2]) };
|
|
39277
39386
|
}
|
|
39278
39387
|
function laneView(unit, record) {
|
|
39279
|
-
const exists =
|
|
39388
|
+
const exists = existsSync31(record.path);
|
|
39280
39389
|
const counts = exists ? aheadBehind(record.path, record.baseSha) : { ahead: null, behind: null };
|
|
39281
39390
|
return {
|
|
39282
39391
|
unit,
|
|
@@ -39353,7 +39462,7 @@ async function laneStoreRuns(runtime, lanePath) {
|
|
|
39353
39462
|
}
|
|
39354
39463
|
}
|
|
39355
39464
|
const eventsPath = kxmProjectRunEventsPath(lanePath, runtime.env);
|
|
39356
|
-
if (!
|
|
39465
|
+
if (!existsSync31(eventsPath)) return void 0;
|
|
39357
39466
|
const store = new KxmRunEventStore(eventsPath);
|
|
39358
39467
|
try {
|
|
39359
39468
|
return store.runsForProject(projectId(), LANE_RUN_LIST_LIMIT).map((run) => ({ runId: run.runId, status: run.status }));
|
|
@@ -39422,7 +39531,7 @@ async function cmdLaneCreate(runtime, unit, options = {}) {
|
|
|
39422
39531
|
const loaded = loadLanes(runtime, command);
|
|
39423
39532
|
if (typeof loaded === "number") return loaded;
|
|
39424
39533
|
const path4 = laneWorktreePath(root, unit);
|
|
39425
|
-
if (loaded.lanes[unit] ||
|
|
39534
|
+
if (loaded.lanes[unit] || existsSync31(path4) || branchExists(root, unit)) {
|
|
39426
39535
|
return refuse(runtime, command, "lane_exists", `lane ${unit} already exists`, { unit });
|
|
39427
39536
|
}
|
|
39428
39537
|
const baseSha = resolveBase(root, base);
|
|
@@ -39447,7 +39556,7 @@ async function cmdLaneCreate(runtime, unit, options = {}) {
|
|
|
39447
39556
|
return refuse(runtime, command, "lane_git_failed", `git worktree add failed: ${added.stderr.trim().slice(0, 300)}`, { unit });
|
|
39448
39557
|
}
|
|
39449
39558
|
const recordedPath = discoverKxmProjectRoot(path4) ?? path4;
|
|
39450
|
-
if (!
|
|
39559
|
+
if (!existsSync31(join36(recordedPath, ".kxm", "project.yaml"))) {
|
|
39451
39560
|
git2(root, ["worktree", "remove", "--force", path4]);
|
|
39452
39561
|
return refuse(runtime, command, "lane_not_project", `lane ${recordedPath} has no .kxm/project.yaml; the worktree was removed`, { unit, path: recordedPath });
|
|
39453
39562
|
}
|
|
@@ -39513,7 +39622,7 @@ async function unregisterLaneRoot(runtime, projectRoot, force) {
|
|
|
39513
39622
|
return void 0;
|
|
39514
39623
|
}
|
|
39515
39624
|
const paths = kxmRuntimePaths({ env: runtime.env });
|
|
39516
|
-
if (!
|
|
39625
|
+
if (!existsSync31(paths.registryDb)) return void 0;
|
|
39517
39626
|
const registry = new KxmRuntimeRegistry(paths.registryDb);
|
|
39518
39627
|
let outcome;
|
|
39519
39628
|
try {
|
|
@@ -39543,7 +39652,7 @@ async function cmdLaneDrop(runtime, unit, options = {}) {
|
|
|
39543
39652
|
if (!record) return refuse(runtime, command, "lane_missing", `lane ${unit} is not recorded`, { unit });
|
|
39544
39653
|
const force = options.force === true;
|
|
39545
39654
|
if (!force) {
|
|
39546
|
-
if (
|
|
39655
|
+
if (existsSync31(record.path)) {
|
|
39547
39656
|
const dirty = porcelainCount(record.path);
|
|
39548
39657
|
if (dirty === null) {
|
|
39549
39658
|
return refuse(runtime, command, "lane_git_failed", `lane ${unit} worktree status could not be read`, { unit });
|
|
@@ -39569,7 +39678,7 @@ async function cmdLaneDrop(runtime, unit, options = {}) {
|
|
|
39569
39678
|
if (unregistered !== void 0) {
|
|
39570
39679
|
return refuse(runtime, command, "lane_unregister_failed", `lane ${unit} could not be unregistered: ${unregistered.slice(0, 300)}`, { unit });
|
|
39571
39680
|
}
|
|
39572
|
-
if (
|
|
39681
|
+
if (existsSync31(record.path)) {
|
|
39573
39682
|
const removed = git2(root, ["worktree", "remove", ...force ? ["--force"] : [], record.path]);
|
|
39574
39683
|
if (removed.status !== 0) {
|
|
39575
39684
|
return refuse(runtime, command, "lane_git_failed", `git worktree remove failed: ${removed.stderr.trim().slice(0, 300)}`, { unit });
|
|
@@ -39680,7 +39789,7 @@ function cmdLand(runtime, options = {}) {
|
|
|
39680
39789
|
init_project_config();
|
|
39681
39790
|
init_types();
|
|
39682
39791
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
39683
|
-
import { existsSync as
|
|
39792
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
39684
39793
|
import { join as join38 } from "node:path";
|
|
39685
39794
|
var kxmAssignCliSeams = {};
|
|
39686
39795
|
var RUNNER_REL = "scripts/assignment-run.mjs";
|
|
@@ -39753,7 +39862,7 @@ async function cmdAssign(runtime, subcommand, args) {
|
|
|
39753
39862
|
const command = `assign ${subcommand}`;
|
|
39754
39863
|
const root = discoverKxmProjectRoot(runtime.cwd);
|
|
39755
39864
|
if (!root) return refuse3(runtime, command, "project_required", `${command} requires a KXM project (run kxm init first)`);
|
|
39756
|
-
if (!
|
|
39865
|
+
if (!existsSync32(join38(root, "scripts", "assignment-run.mjs"))) {
|
|
39757
39866
|
return refuse3(runtime, command, "assign_runner_missing", `${command} requires scripts/assignment-run.mjs in the project root`);
|
|
39758
39867
|
}
|
|
39759
39868
|
const argv = ["node", RUNNER_REL, ...runnerArgv(subcommand, args)];
|
|
@@ -39784,7 +39893,7 @@ async function cmdAssign(runtime, subcommand, args) {
|
|
|
39784
39893
|
// plugins/kxm/src/cli/hub.ts
|
|
39785
39894
|
init_redact();
|
|
39786
39895
|
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
39787
|
-
import { existsSync as
|
|
39896
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync28, readFileSync as readFileSync35, readdirSync as readdirSync12, rmSync as rmSync11, writeFileSync as writeFileSync25 } from "node:fs";
|
|
39788
39897
|
import { join as join48, resolve as resolve27 } from "node:path";
|
|
39789
39898
|
init_hub_env();
|
|
39790
39899
|
init_envelope();
|
|
@@ -48370,7 +48479,7 @@ var SHARED_EFFECT_KINDS = Object.freeze([
|
|
|
48370
48479
|
// plugins/kxm/src/local-snapshot.ts
|
|
48371
48480
|
init_sqlite();
|
|
48372
48481
|
init_telemetry();
|
|
48373
|
-
import { existsSync as
|
|
48482
|
+
import { existsSync as existsSync33, readdirSync as readdirSync11, readFileSync as readFileSync30 } from "node:fs";
|
|
48374
48483
|
import { homedir as homedir6 } from "node:os";
|
|
48375
48484
|
import { isAbsolute as isAbsolute10, join as join42, resolve as resolve25 } from "node:path";
|
|
48376
48485
|
var DEFAULT_BUSY_TIMEOUT_MS = 5e3;
|
|
@@ -48503,15 +48612,15 @@ function resolveKxmSnapshotPaths(cwd, env = process.env) {
|
|
|
48503
48612
|
return { dataPath, stateDir };
|
|
48504
48613
|
}
|
|
48505
48614
|
function resolveKxmStateRoot(stateDir, options) {
|
|
48506
|
-
if (options?.kxmStateRoot &&
|
|
48615
|
+
if (options?.kxmStateRoot && existsSync33(options.kxmStateRoot)) {
|
|
48507
48616
|
return resolve25(options.kxmStateRoot);
|
|
48508
48617
|
}
|
|
48509
|
-
if (
|
|
48618
|
+
if (existsSync33(join42(stateDir, "runtime", "registry.db")) || existsSync33(join42(stateDir, "runtime", "projects"))) {
|
|
48510
48619
|
return stateDir;
|
|
48511
48620
|
}
|
|
48512
48621
|
const env = options?.env ?? process.env;
|
|
48513
48622
|
const explicit = env.KXM_STATE_HOME?.trim() || env.KXM_USER_STATE_DIR?.trim() || env.KXM_STATE_ROOT?.trim();
|
|
48514
|
-
if (explicit && isAbsolute10(explicit) &&
|
|
48623
|
+
if (explicit && isAbsolute10(explicit) && existsSync33(resolve25(explicit))) {
|
|
48515
48624
|
return resolve25(explicit);
|
|
48516
48625
|
}
|
|
48517
48626
|
let base;
|
|
@@ -48526,7 +48635,7 @@ function resolveKxmStateRoot(stateDir, options) {
|
|
|
48526
48635
|
base = xdgState && isAbsolute10(xdgState) ? xdgState : join42(homedir6(), ".local", "state");
|
|
48527
48636
|
base = resolve25(base, "kxm");
|
|
48528
48637
|
}
|
|
48529
|
-
if (
|
|
48638
|
+
if (existsSync33(base)) return base;
|
|
48530
48639
|
return void 0;
|
|
48531
48640
|
}
|
|
48532
48641
|
function readRuntimeRuns(eventDb, projectId) {
|
|
@@ -48559,7 +48668,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
48559
48668
|
let legacyRuns = [];
|
|
48560
48669
|
let legacyRunTotal = 0;
|
|
48561
48670
|
let plans = [];
|
|
48562
|
-
if (
|
|
48671
|
+
if (existsSync33(dataPath)) {
|
|
48563
48672
|
hasLegacy = true;
|
|
48564
48673
|
const database = openReadOnlyDatabase(dataPath);
|
|
48565
48674
|
try {
|
|
@@ -48594,7 +48703,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
48594
48703
|
const registryDbPath = join42(runtimeDir, "registry.db");
|
|
48595
48704
|
const projectsDir = join42(runtimeDir, "projects");
|
|
48596
48705
|
const projectKeys = /* @__PURE__ */ new Set();
|
|
48597
|
-
if (
|
|
48706
|
+
if (existsSync33(registryDbPath)) {
|
|
48598
48707
|
hasKxm = true;
|
|
48599
48708
|
try {
|
|
48600
48709
|
const regDb = openReadOnlyDatabase(registryDbPath);
|
|
@@ -48610,7 +48719,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
48610
48719
|
} catch {
|
|
48611
48720
|
}
|
|
48612
48721
|
}
|
|
48613
|
-
if (!scope &&
|
|
48722
|
+
if (!scope && existsSync33(projectsDir)) {
|
|
48614
48723
|
try {
|
|
48615
48724
|
for (const entry of readdirSync11(projectsDir, { withFileTypes: true })) {
|
|
48616
48725
|
if (entry.isDirectory()) {
|
|
@@ -48623,7 +48732,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
48623
48732
|
for (const key of projectKeys) {
|
|
48624
48733
|
if (scope && !RUNTIME_PROJECT_KEY.test(key)) continue;
|
|
48625
48734
|
const eventDbPath = join42(projectsDir, key, "run-events.db");
|
|
48626
|
-
if (
|
|
48735
|
+
if (existsSync33(eventDbPath)) {
|
|
48627
48736
|
hasKxm = true;
|
|
48628
48737
|
try {
|
|
48629
48738
|
const eventDb = openReadOnlyDatabase(eventDbPath);
|
|
@@ -48641,7 +48750,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
48641
48750
|
}
|
|
48642
48751
|
}
|
|
48643
48752
|
const pids = [];
|
|
48644
|
-
if (
|
|
48753
|
+
if (existsSync33(stateDir)) {
|
|
48645
48754
|
for (const file of readdirSync11(stateDir).filter((name) => name.endsWith(".pid"))) {
|
|
48646
48755
|
try {
|
|
48647
48756
|
const record = JSON.parse(readFileSync30(join42(stateDir, file), "utf8"));
|
|
@@ -48684,7 +48793,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
48684
48793
|
source = "legacy";
|
|
48685
48794
|
}
|
|
48686
48795
|
const telemetryFile = join42(stateDir, "telemetry.jsonl");
|
|
48687
|
-
const spend =
|
|
48796
|
+
const spend = existsSync33(telemetryFile) ? readRoutingRecords(telemetryFile) : [];
|
|
48688
48797
|
return {
|
|
48689
48798
|
source,
|
|
48690
48799
|
agents: agents.sort((a, b2) => Number(b2.online) - Number(a.online) || a.name.localeCompare(b2.name)),
|
|
@@ -49587,7 +49696,7 @@ async function runMeshTui(input) {
|
|
|
49587
49696
|
// plugins/kxm/src/session-work.ts
|
|
49588
49697
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
49589
49698
|
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
49590
|
-
import { existsSync as
|
|
49699
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync26, readFileSync as readFileSync31, renameSync as renameSync8, writeFileSync as writeFileSync23 } from "node:fs";
|
|
49591
49700
|
import { join as join44 } from "node:path";
|
|
49592
49701
|
init_hub_binding();
|
|
49593
49702
|
init_telemetry();
|
|
@@ -49749,7 +49858,7 @@ function buildSessionBrief(snapshot, current, hub, ship, updateLatest, cost, ses
|
|
|
49749
49858
|
function readCachedSessionBrief(stateDir) {
|
|
49750
49859
|
const file = join44(stateDir, "session-brief.json");
|
|
49751
49860
|
try {
|
|
49752
|
-
if (!
|
|
49861
|
+
if (!existsSync34(file)) return void 0;
|
|
49753
49862
|
const raw = readFileSync31(file, "utf8");
|
|
49754
49863
|
const parsed = JSON.parse(raw);
|
|
49755
49864
|
if (parsed && parsed.schema === "kxm.session-brief.v1" && typeof parsed.generatedAt === "string") {
|
|
@@ -49776,7 +49885,7 @@ function writeCachedSessionBrief(stateDir, brief) {
|
|
|
49776
49885
|
function estimateSessionCost(stateDir) {
|
|
49777
49886
|
try {
|
|
49778
49887
|
const telemetryFile = join44(stateDir, "telemetry.jsonl");
|
|
49779
|
-
if (!
|
|
49888
|
+
if (!existsSync34(telemetryFile)) return void 0;
|
|
49780
49889
|
const records = readRoutingRecords(telemetryFile);
|
|
49781
49890
|
if (records.length === 0) return void 0;
|
|
49782
49891
|
let totalCost = 0;
|
|
@@ -49948,7 +50057,7 @@ init_hub_binding();
|
|
|
49948
50057
|
|
|
49949
50058
|
// plugins/kxm/src/kxm-install-kind.ts
|
|
49950
50059
|
init_bindings();
|
|
49951
|
-
import { existsSync as
|
|
50060
|
+
import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
|
|
49952
50061
|
import { dirname as dirname20, join as join45 } from "node:path";
|
|
49953
50062
|
function packageIsKxm(root) {
|
|
49954
50063
|
try {
|
|
@@ -50006,7 +50115,7 @@ function classifyInstallRoot(probe) {
|
|
|
50006
50115
|
const claudeHome = probe.env.CLAUDE_CONFIG_DIR?.trim() || join45(probe.homeDir, ".claude");
|
|
50007
50116
|
const pluginsRoot = join45(claudeHome, "plugins");
|
|
50008
50117
|
if (isUnder(probe.moduleDir, pluginsRoot, probe.platform)) return report("claude-marketplace", probe.moduleDir);
|
|
50009
|
-
const hasGit =
|
|
50118
|
+
const hasGit = existsSync35(join45(probe.repoRoot, ".git"));
|
|
50010
50119
|
if (hasGit && packageIsKxm(probe.repoRoot)) return report("source", probe.repoRoot);
|
|
50011
50120
|
if (isNpmPackageLayout(probe.repoRoot, probe.platform) && packageIsKxm(probe.repoRoot) && !hasGit) {
|
|
50012
50121
|
return report("npm-package", probe.repoRoot);
|
|
@@ -50030,12 +50139,12 @@ init_bindings();
|
|
|
50030
50139
|
|
|
50031
50140
|
// plugins/kxm/src/kxm-update-config.ts
|
|
50032
50141
|
var import_yaml16 = __toESM(require_dist(), 1);
|
|
50033
|
-
import { existsSync as
|
|
50142
|
+
import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
|
|
50034
50143
|
import { join as join46 } from "node:path";
|
|
50035
50144
|
init_bindings();
|
|
50036
50145
|
function loadKxmUpdateConfig(env = process.env) {
|
|
50037
50146
|
const path4 = join46(kxmUserStateRoot({ env }), "update.yaml");
|
|
50038
|
-
if (!
|
|
50147
|
+
if (!existsSync36(path4)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "github" };
|
|
50039
50148
|
let parsed;
|
|
50040
50149
|
try {
|
|
50041
50150
|
parsed = (0, import_yaml16.parse)(readFileSync33(path4, "utf8"));
|
|
@@ -50066,7 +50175,7 @@ function loadKxmUpdateConfig(env = process.env) {
|
|
|
50066
50175
|
// plugins/kxm/src/session.ts
|
|
50067
50176
|
init_protocol();
|
|
50068
50177
|
init_envelope();
|
|
50069
|
-
import { existsSync as
|
|
50178
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync27, readFileSync as readFileSync34, writeFileSync as writeFileSync24 } from "node:fs";
|
|
50070
50179
|
import { join as join47 } from "node:path";
|
|
50071
50180
|
var SESSION_SCHEMA = "kxm.session.v1";
|
|
50072
50181
|
var SessionConfigError = class extends Error {
|
|
@@ -50146,7 +50255,7 @@ function loadRosterMap(configDir) {
|
|
|
50146
50255
|
return byName;
|
|
50147
50256
|
}
|
|
50148
50257
|
function loadRoster(path4, key) {
|
|
50149
|
-
if (!
|
|
50258
|
+
if (!existsSync37(path4)) return [];
|
|
50150
50259
|
let parsed;
|
|
50151
50260
|
try {
|
|
50152
50261
|
parsed = JSON.parse(readFileSync34(path4, "utf8"));
|
|
@@ -50245,7 +50354,7 @@ function moduleInstallRoot() {
|
|
|
50245
50354
|
}
|
|
50246
50355
|
function warnIgnoredProjectUpdateYaml(runtime) {
|
|
50247
50356
|
const projectFile = join48(runtime.dirs.workspace, "update.yaml");
|
|
50248
|
-
if (!
|
|
50357
|
+
if (!existsSync38(projectFile)) return;
|
|
50249
50358
|
const userFile = join48(kxmUserStateRoot({ env: runtime.env }), "update.yaml");
|
|
50250
50359
|
runtime.io.stderr(`kxm: ignoring .kxm/update.yaml in ${runtime.dirs.workdir}; update settings are read only from ${userFile}
|
|
50251
50360
|
`);
|
|
@@ -50527,7 +50636,7 @@ async function cmdWorker(runtime, options) {
|
|
|
50527
50636
|
return await (runtime.io.spawnWorker ?? ((launchEnv) => spawnScript("kxm-worker.mjs", launchEnv)))(extraEnv);
|
|
50528
50637
|
}
|
|
50529
50638
|
async function cmdStop(runtime, waitMsFlag) {
|
|
50530
|
-
const pids =
|
|
50639
|
+
const pids = existsSync38(runtime.dirs.state) ? readdirSync12(runtime.dirs.state).filter((name) => name.endsWith(".pid")) : [];
|
|
50531
50640
|
if (runtime.dryRun) {
|
|
50532
50641
|
print(runtime.io, runtime.json, { ok: true, command: "stop", dryRun: true, pidFiles: pids }, "would signal pid files");
|
|
50533
50642
|
return 0;
|
|
@@ -50619,7 +50728,7 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
50619
50728
|
}
|
|
50620
50729
|
async function cmdSessionStatus(runtime) {
|
|
50621
50730
|
const stateDir = runtime.dirs.state;
|
|
50622
|
-
const names3 =
|
|
50731
|
+
const names3 = existsSync38(stateDir) ? readdirSync12(stateDir) : [];
|
|
50623
50732
|
const claims = [];
|
|
50624
50733
|
for (const file of names3.filter((name) => name.endsWith(".pid"))) {
|
|
50625
50734
|
try {
|
|
@@ -50664,7 +50773,7 @@ async function cmdSessionStatus(runtime) {
|
|
|
50664
50773
|
async function cmdAuthToken(runtime, options = {}) {
|
|
50665
50774
|
const tokenFile = sessionTokenPath(runtime.env.KXM_USER_CONFIG_DIR);
|
|
50666
50775
|
if (options.clear && runtime.dryRun) {
|
|
50667
|
-
const present =
|
|
50776
|
+
const present = existsSync38(tokenFile);
|
|
50668
50777
|
printPlan(runtime, { command: "auth token", cleared: present }, present ? [{ action: "delete", target: tokenFile }] : [], present ? "clear the session token from disk" : "no session token file to clear");
|
|
50669
50778
|
return 0;
|
|
50670
50779
|
}
|
|
@@ -50863,7 +50972,7 @@ init_routing();
|
|
|
50863
50972
|
init_prices();
|
|
50864
50973
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
50865
50974
|
import { createHash as createHash19 } from "node:crypto";
|
|
50866
|
-
import { existsSync as
|
|
50975
|
+
import { existsSync as existsSync45, mkdtempSync as mkdtempSync3, readFileSync as readFileSync40, rmSync as rmSync13 } from "node:fs";
|
|
50867
50976
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
50868
50977
|
import { basename as basename11, extname as extname3, join as join54, resolve as resolve32 } from "node:path";
|
|
50869
50978
|
import { createInterface as createInterface3 } from "node:readline";
|
|
@@ -50873,7 +50982,7 @@ init_routing();
|
|
|
50873
50982
|
init_protocol();
|
|
50874
50983
|
init_relevance();
|
|
50875
50984
|
import { createHash as createHash18 } from "node:crypto";
|
|
50876
|
-
import { existsSync as
|
|
50985
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
|
|
50877
50986
|
import { join as join49, relative as relative7, resolve as resolve28 } from "node:path";
|
|
50878
50987
|
var CANDIDATE_SCHEMA = "kxm.candidate.v1";
|
|
50879
50988
|
var IMPROVEMENT_REPORT_SCHEMA = "kxm.improvement-report.v2";
|
|
@@ -51166,7 +51275,7 @@ function buildImprovementReport(records, options = {}) {
|
|
|
51166
51275
|
createdAt: nowIso()
|
|
51167
51276
|
};
|
|
51168
51277
|
if (!options.dryRun) {
|
|
51169
|
-
if (!
|
|
51278
|
+
if (!existsSync39(candidatesDir)) {
|
|
51170
51279
|
mkdirSync29(candidatesDir, { recursive: true });
|
|
51171
51280
|
}
|
|
51172
51281
|
writeFileSync26(diffFilePath, diff, "utf8");
|
|
@@ -51283,7 +51392,7 @@ init_project_config();
|
|
|
51283
51392
|
init_runtime_store();
|
|
51284
51393
|
init_routing();
|
|
51285
51394
|
init_telemetry();
|
|
51286
|
-
import { existsSync as
|
|
51395
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
51287
51396
|
import { resolve as resolve29 } from "node:path";
|
|
51288
51397
|
var ENGINE_EVENTS_SQL = "SELECT run_id, sequence, event_type, payload FROM events WHERE event_type IN ('routing.attempt.recorded','step.entered','run.status_changed') ORDER BY run_id, sequence";
|
|
51289
51398
|
var SIMULATED_HARNESS = "driver-simulated";
|
|
@@ -51306,7 +51415,7 @@ function readEngineRoutingRecords(path4) {
|
|
|
51306
51415
|
undecided: 0,
|
|
51307
51416
|
duplicatesDropped: 0
|
|
51308
51417
|
};
|
|
51309
|
-
if (!
|
|
51418
|
+
if (!existsSync40(path4)) return { records: [], source };
|
|
51310
51419
|
source.exists = true;
|
|
51311
51420
|
let rows;
|
|
51312
51421
|
let database;
|
|
@@ -51382,7 +51491,7 @@ function readEngineRoutingRecords(path4) {
|
|
|
51382
51491
|
};
|
|
51383
51492
|
}
|
|
51384
51493
|
function readJsonlSource(kind, path4) {
|
|
51385
|
-
const exists =
|
|
51494
|
+
const exists = existsSync40(path4);
|
|
51386
51495
|
const records = exists ? readRoutingRecords(path4).map((entry) => entry.routing) : [];
|
|
51387
51496
|
return { records, source: { kind, path: path4, exists, records: records.length, duplicatesDropped: 0 } };
|
|
51388
51497
|
}
|
|
@@ -51427,7 +51536,7 @@ function loadRoutingSources(options) {
|
|
|
51427
51536
|
// plugins/kxm/src/modes.ts
|
|
51428
51537
|
var import_yaml17 = __toESM(require_dist(), 1);
|
|
51429
51538
|
init_prices();
|
|
51430
|
-
import { existsSync as
|
|
51539
|
+
import { existsSync as existsSync41, readFileSync as readFileSync37 } from "node:fs";
|
|
51431
51540
|
import { join as join50 } from "node:path";
|
|
51432
51541
|
var DEFAULT_MODES_CONFIG = Object.freeze({
|
|
51433
51542
|
schema: "kxm.modes.v1",
|
|
@@ -51492,7 +51601,7 @@ function loadModesConfig(projectRoot) {
|
|
|
51492
51601
|
return DEFAULT_MODES_CONFIG;
|
|
51493
51602
|
}
|
|
51494
51603
|
const modesPath = join50(projectRoot, ".kxm", "modes.yaml");
|
|
51495
|
-
if (!
|
|
51604
|
+
if (!existsSync41(modesPath)) {
|
|
51496
51605
|
return DEFAULT_MODES_CONFIG;
|
|
51497
51606
|
}
|
|
51498
51607
|
try {
|
|
@@ -51584,7 +51693,7 @@ function calculatePromptFootprint(resolved, projectRoot = process.cwd(), catalog
|
|
|
51584
51693
|
for (const relPath of resolved.contextFiles) {
|
|
51585
51694
|
const fullPath = join50(projectRoot, relPath);
|
|
51586
51695
|
let chars = 0;
|
|
51587
|
-
if (
|
|
51696
|
+
if (existsSync41(fullPath)) {
|
|
51588
51697
|
try {
|
|
51589
51698
|
chars = readFileSync37(fullPath, "utf8").length;
|
|
51590
51699
|
} catch {
|
|
@@ -51719,7 +51828,7 @@ ${divider}
|
|
|
51719
51828
|
// plugins/kxm/src/ssh-remote.ts
|
|
51720
51829
|
init_safety_integrity();
|
|
51721
51830
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
51722
|
-
import { existsSync as
|
|
51831
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync13, rmSync as rmSync12, statSync as statSync7 } from "node:fs";
|
|
51723
51832
|
import { homedir as homedir8 } from "node:os";
|
|
51724
51833
|
import { join as join51, resolve as resolve30 } from "node:path";
|
|
51725
51834
|
var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
|
|
@@ -51748,7 +51857,7 @@ function truncateSshOutput(raw) {
|
|
|
51748
51857
|
}
|
|
51749
51858
|
function parseSshConfig(configPath) {
|
|
51750
51859
|
const targetPath = configPath ?? join51(homedir8(), ".ssh", "config");
|
|
51751
|
-
if (!
|
|
51860
|
+
if (!existsSync42(targetPath)) {
|
|
51752
51861
|
return [];
|
|
51753
51862
|
}
|
|
51754
51863
|
try {
|
|
@@ -51830,7 +51939,7 @@ function resolveSshHostG(host, execFn = spawnSync11) {
|
|
|
51830
51939
|
}
|
|
51831
51940
|
function ensureSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
|
|
51832
51941
|
const resolved = resolve30(socketDir);
|
|
51833
|
-
if (!
|
|
51942
|
+
if (!existsSync42(resolved)) {
|
|
51834
51943
|
mkdirSync30(resolved, { recursive: true, mode: 448 });
|
|
51835
51944
|
}
|
|
51836
51945
|
return resolved;
|
|
@@ -52325,7 +52434,7 @@ complete -c kxm -n "__fish_seen_subcommand_from goal" -a "create list get"
|
|
|
52325
52434
|
}
|
|
52326
52435
|
|
|
52327
52436
|
// plugins/kxm/src/completion-install.ts
|
|
52328
|
-
import { existsSync as
|
|
52437
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync39, writeFileSync as writeFileSync27 } from "node:fs";
|
|
52329
52438
|
import { homedir as homedir9 } from "node:os";
|
|
52330
52439
|
import { basename as basename10, delimiter, dirname as dirname23, isAbsolute as isAbsolute11, join as join52, resolve as resolve31 } from "node:path";
|
|
52331
52440
|
var COMPLETION_MARKER = "# kxm completion";
|
|
@@ -52354,7 +52463,7 @@ function completionScriptPath(shell, options = {}) {
|
|
|
52354
52463
|
function bashRcCandidate(options) {
|
|
52355
52464
|
const home = effectiveHome(options);
|
|
52356
52465
|
const candidates = [join52(home, ".bashrc"), join52(home, ".bash_profile")];
|
|
52357
|
-
const existing = candidates.find((candidate) =>
|
|
52466
|
+
const existing = candidates.find((candidate) => existsSync43(candidate));
|
|
52358
52467
|
return existing ?? candidates[0];
|
|
52359
52468
|
}
|
|
52360
52469
|
function zshRcCandidate(options) {
|
|
@@ -52399,13 +52508,13 @@ function installShellCompletion(shellInput, options = {}) {
|
|
|
52399
52508
|
const scriptPath = completionScriptPath(shell, options);
|
|
52400
52509
|
const { rcFile } = completionRcTarget(shell, options);
|
|
52401
52510
|
const script = generateShellCompletion(shell);
|
|
52402
|
-
const existingScript =
|
|
52511
|
+
const existingScript = existsSync43(scriptPath);
|
|
52403
52512
|
const existingScriptMatches = existingScript && readFileSync39(scriptPath, "utf8") === script;
|
|
52404
52513
|
let rcModified = false;
|
|
52405
52514
|
let alreadyInstalled = false;
|
|
52406
52515
|
if (shell === "fish") {
|
|
52407
52516
|
const fishTarget = fishCompletionTarget(shell, options);
|
|
52408
|
-
const fishInstalled =
|
|
52517
|
+
const fishInstalled = existsSync43(fishTarget) && readFileSync39(fishTarget, "utf8") === script;
|
|
52409
52518
|
alreadyInstalled = fishInstalled;
|
|
52410
52519
|
if (!options.dryRun && (!fishInstalled || options.overwrite)) {
|
|
52411
52520
|
mkdirSync31(resolve31(fishTarget, ".."), { recursive: true });
|
|
@@ -52422,7 +52531,7 @@ function installShellCompletion(shellInput, options = {}) {
|
|
|
52422
52531
|
}
|
|
52423
52532
|
if (rcFile) {
|
|
52424
52533
|
const line = sourceLine(shell, scriptPath);
|
|
52425
|
-
const rcContent =
|
|
52534
|
+
const rcContent = existsSync43(rcFile) ? readFileSync39(rcFile, "utf8") : "";
|
|
52426
52535
|
alreadyInstalled = rcContent.includes(line) || rcContent.includes(`${COMPLETION_MARKER}`);
|
|
52427
52536
|
}
|
|
52428
52537
|
if (!options.dryRun) {
|
|
@@ -52438,7 +52547,7 @@ ${marker}
|
|
|
52438
52547
|
${line}
|
|
52439
52548
|
`;
|
|
52440
52549
|
mkdirSync31(resolve31(rcFile, ".."), { recursive: true });
|
|
52441
|
-
writeFileSync27(rcFile, (
|
|
52550
|
+
writeFileSync27(rcFile, (existsSync43(rcFile) ? readFileSync39(rcFile, "utf8") : "") + stanza, { encoding: "utf8" });
|
|
52442
52551
|
rcModified = true;
|
|
52443
52552
|
}
|
|
52444
52553
|
}
|
|
@@ -52455,13 +52564,13 @@ function kxmBinDir(env = process.env) {
|
|
|
52455
52564
|
const argv1 = env.KXM_ENTRY ?? process.argv[1];
|
|
52456
52565
|
if (argv1 && isAbsolute11(argv1)) {
|
|
52457
52566
|
const dir = dirname23(argv1);
|
|
52458
|
-
if (
|
|
52567
|
+
if (existsSync43(join52(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
|
|
52459
52568
|
}
|
|
52460
52569
|
const pathEnv = env.PATH ?? "";
|
|
52461
52570
|
for (const part of pathEnv.split(delimiter)) {
|
|
52462
52571
|
if (!part) continue;
|
|
52463
52572
|
const candidate = resolve31(part, process.platform === "win32" ? "kxm.cmd" : "kxm");
|
|
52464
|
-
if (
|
|
52573
|
+
if (existsSync43(candidate)) return resolve31(part);
|
|
52465
52574
|
}
|
|
52466
52575
|
return void 0;
|
|
52467
52576
|
}
|
|
@@ -52479,7 +52588,7 @@ function installPathEntry(shellInput, options = {}) {
|
|
|
52479
52588
|
}
|
|
52480
52589
|
const { rcFile } = completionRcTarget(shell, options);
|
|
52481
52590
|
const line = pathLine(binDir);
|
|
52482
|
-
const rcContent =
|
|
52591
|
+
const rcContent = existsSync43(rcFile ?? "") ? readFileSync39(rcFile, "utf8") : "";
|
|
52483
52592
|
const alreadyInstalled = rcContent.includes(binDir) || (options.env?.PATH ?? process.env.PATH ?? "").split(delimiter).includes(binDir);
|
|
52484
52593
|
if (!options.dryRun && rcFile && !alreadyInstalled) {
|
|
52485
52594
|
mkdirSync31(dirname23(rcFile), { recursive: true });
|
|
@@ -52494,7 +52603,7 @@ ${line}
|
|
|
52494
52603
|
// plugins/kxm/src/init-guide-setup.ts
|
|
52495
52604
|
var import_yaml18 = __toESM(require_dist(), 1);
|
|
52496
52605
|
init_routes();
|
|
52497
|
-
import { existsSync as
|
|
52606
|
+
import { existsSync as existsSync44, mkdirSync as mkdirSync32, writeFileSync as writeFileSync28 } from "node:fs";
|
|
52498
52607
|
import { dirname as dirname24, join as join53 } from "node:path";
|
|
52499
52608
|
var ADMITTED_GUIDE_BINDINGS = Object.freeze({
|
|
52500
52609
|
"anthropic/claude-fable-5.1": { harness: "claude", provider: "anthropic", model: "fable" },
|
|
@@ -52998,7 +53107,7 @@ function writeGuideSetupFiles(files) {
|
|
|
52998
53107
|
const written = [];
|
|
52999
53108
|
const existed = [];
|
|
53000
53109
|
for (const file of files) {
|
|
53001
|
-
if (
|
|
53110
|
+
if (existsSync44(file.path)) {
|
|
53002
53111
|
existed.push(file.path);
|
|
53003
53112
|
continue;
|
|
53004
53113
|
}
|
|
@@ -53078,7 +53187,7 @@ function applyKxmPackageUpdate(runtime, notice) {
|
|
|
53078
53187
|
for (const step of planned) {
|
|
53079
53188
|
if (step.kind === "verify") {
|
|
53080
53189
|
if (!verifyReleaseAssetDigest(step.path, step.sha256)) {
|
|
53081
|
-
const actual =
|
|
53190
|
+
const actual = existsSync45(step.path) ? createHash19("sha256").update(readFileSync40(step.path)).digest("hex") : "missing";
|
|
53082
53191
|
return {
|
|
53083
53192
|
ok: false,
|
|
53084
53193
|
error: "release_digest_mismatch",
|
|
@@ -53356,7 +53465,7 @@ async function cmdValidate(runtime, fileFlag) {
|
|
|
53356
53465
|
}
|
|
53357
53466
|
const selectedFile = explicitFile || configuredFile;
|
|
53358
53467
|
const file = selectedFile ? resolve32(runtime.cwd, selectedFile) : void 0;
|
|
53359
|
-
if (file && !
|
|
53468
|
+
if (file && !existsSync45(file)) {
|
|
53360
53469
|
printWorker(runtime, worker, { ok: false, command: "validate", error: "file_not_found", file }, `workflow file not found: ${file}`);
|
|
53361
53470
|
return 1;
|
|
53362
53471
|
}
|
|
@@ -53609,7 +53718,7 @@ async function maybeOfferCompletionInstall(runtime) {
|
|
|
53609
53718
|
if (!pathNeeded) {
|
|
53610
53719
|
const scriptPath = completionScriptPath(shell, { env: runtime.env, configDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
53611
53720
|
const { rcFile } = completionRcTarget(shell, { env: runtime.env });
|
|
53612
|
-
if (rcFile &&
|
|
53721
|
+
if (rcFile && existsSync45(rcFile)) {
|
|
53613
53722
|
try {
|
|
53614
53723
|
if (readFileSync40(rcFile, "utf8").includes(scriptPath)) return;
|
|
53615
53724
|
} catch {
|
|
@@ -54319,6 +54428,11 @@ function createProgram(ctx, result, argv) {
|
|
|
54319
54428
|
result.code = await cmdRouteChange(runtimeFrom(ctx, this), status === "admit" ? "admitted" : "disabled", options.model);
|
|
54320
54429
|
});
|
|
54321
54430
|
}
|
|
54431
|
+
const visionCmd = addGlobalOptions(program2.command("vision").description("Judge captured UI screenshots through the admitted vision gate"));
|
|
54432
|
+
visionCmd.helpCommand("help", "Show vision help");
|
|
54433
|
+
addGlobalOptions(visionCmd.command("assert").description("Assert a UI state on a screenshot (deterministic vision gate)")).requiredOption("--image <path>", "Path to the captured screenshot").requiredOption("--question <text>", "Yes/no assertion to verify on the screenshot").option("--route <id>", `Vision route (default ${"zai-coding-cn/glm-5.3-flash"})`).option("--timeout-ms <n>", "Transport timeout in milliseconds (5000-300000)").option("--expect <bool>", "Exit 0 only when the verdict matches true|false").action(async function visionAssertAction(options) {
|
|
54434
|
+
result.code = await cmdVisionAssert(runtimeFrom(ctx, this), options ?? {});
|
|
54435
|
+
});
|
|
54322
54436
|
const harnessCmd = addGlobalOptions(program2.command("harness").description("Detect coding-agent harnesses and authentication"));
|
|
54323
54437
|
harnessCmd.helpCommand("help", "Show harness help");
|
|
54324
54438
|
addGlobalOptions(harnessCmd.command("list").description("Show installed harnesses, auth, and native updaters")).action(bind(cmdHarnessList));
|
|
@@ -17313,7 +17313,7 @@ function sessionTokenFixHint(policy) {
|
|
|
17313
17313
|
}
|
|
17314
17314
|
|
|
17315
17315
|
// plugins/kxm/src/mcp-server.ts
|
|
17316
|
-
var VERSION = "0.7.
|
|
17316
|
+
var VERSION = "0.7.138";
|
|
17317
17317
|
var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
17318
17318
|
var inbox = /* @__PURE__ */ new Map();
|
|
17319
17319
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
package/plugins/kxm/package.json
CHANGED
|
@@ -94,3 +94,17 @@ const browser = await connectBrowserOverCdp(
|
|
|
94
94
|
- Save test traces to `.kxm/artifacts/browser/trace-<runId>.zip`.
|
|
95
95
|
- Sanitize recorded traces and screenshots: mask password fields, authorization headers, and personal data.
|
|
96
96
|
- Keep permanent regression tests under `test/e2e/`. Scratch reproduction scripts stay out of that directory.
|
|
97
|
+
|
|
98
|
+
## Vision Gate (screenshot assertion)
|
|
99
|
+
|
|
100
|
+
After capturing a screenshot, a deterministic vision gate can assert UI state through an admitted vision route — bounded prompt, strict `{"verdict": true|false}` verdict, fail-closed (no free-form chat):
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
kxm vision assert --image .kxm/artifacts/browser/shot.png \
|
|
104
|
+
--question "Does the page show the error banner?" \
|
|
105
|
+
--expect false
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- Exit 0 only when the verdict resolves and matches `--expect`; fail-closed divergences (`route_not_admitted`, `image unreadable`, `verdict unreadable`) exit 1.
|
|
109
|
+
- The route must be admitted in `.kxm/routes.yaml` (default: `zai-coding-cn/glm-5.3-flash`, the 12/12-accuracy verified vision route).
|
|
110
|
+
- The gate is a supplement to deterministic Playwright assertions, never a replacement.
|
|
@@ -7,6 +7,7 @@ import { applyRestorePlan, createBackup, databaseError, planBackup, planRestore,
|
|
|
7
7
|
import { readLiveHubClaim } from "../hub-autostart.ts";
|
|
8
8
|
import { refreshModelInventory } from "../model-inventory.ts";
|
|
9
9
|
import { listInventoryModels, listRoleBindings, loadRoutePolicy, rolesForInventoryModel, setRouteState, updateRouteState } from "../routes.ts";
|
|
10
|
+
import { runVisionGate } from "../vision-gate.ts";
|
|
10
11
|
import {
|
|
11
12
|
KxmConfigError,
|
|
12
13
|
discoverKxmProjectRoot,
|
|
@@ -921,6 +922,58 @@ export async function cmdRouteCount(runtime: Runtime): Promise<number> {
|
|
|
921
922
|
return 0;
|
|
922
923
|
}
|
|
923
924
|
|
|
925
|
+
export interface VisionAssertOptions {
|
|
926
|
+
image?: string | undefined;
|
|
927
|
+
question?: string | undefined;
|
|
928
|
+
route?: string | undefined;
|
|
929
|
+
timeoutMs?: string | undefined;
|
|
930
|
+
expect?: string | undefined;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/** Judge a captured UI screenshot through the admitted vision gate. Exit 0
|
|
934
|
+
* when the verdict resolves and matches --expect (or when no expectation was
|
|
935
|
+
* given and the verdict resolved); fail-closed divergences exit 1. */
|
|
936
|
+
export async function cmdVisionAssert(runtime: Runtime, options: VisionAssertOptions): Promise<number> {
|
|
937
|
+
const image = options.image?.trim();
|
|
938
|
+
const question = options.question?.trim();
|
|
939
|
+
if (!image || !question) {
|
|
940
|
+
print(runtime.io, runtime.json, { ok: false, command: "vision assert", error: "image_and_question_required" }, "both --image and --question are required");
|
|
941
|
+
return 2;
|
|
942
|
+
}
|
|
943
|
+
let expect: boolean | undefined;
|
|
944
|
+
if (options.expect !== undefined) {
|
|
945
|
+
if (options.expect !== "true" && options.expect !== "false") {
|
|
946
|
+
print(runtime.io, runtime.json, { ok: false, command: "vision assert", error: "expect_invalid" }, "--expect must be true or false");
|
|
947
|
+
return 2;
|
|
948
|
+
}
|
|
949
|
+
expect = options.expect === "true";
|
|
950
|
+
}
|
|
951
|
+
let timeoutMs: number | undefined;
|
|
952
|
+
if (options.timeoutMs !== undefined) {
|
|
953
|
+
const parsed = Number(options.timeoutMs);
|
|
954
|
+
if (!Number.isInteger(parsed)) {
|
|
955
|
+
print(runtime.io, runtime.json, { ok: false, command: "vision assert", error: "timeout_invalid" }, "--timeout-ms must be an integer");
|
|
956
|
+
return 2;
|
|
957
|
+
}
|
|
958
|
+
timeoutMs = parsed;
|
|
959
|
+
}
|
|
960
|
+
const result = await runVisionGate({
|
|
961
|
+
projectRoot: runtime.dirs.workdir,
|
|
962
|
+
imagePath: resolve(runtime.cwd, image),
|
|
963
|
+
question,
|
|
964
|
+
...(options.route !== undefined ? { route: options.route } : {}),
|
|
965
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
966
|
+
});
|
|
967
|
+
const pass = result.verdict !== null && (expect === undefined || result.verdict === expect);
|
|
968
|
+
print(
|
|
969
|
+
runtime.io,
|
|
970
|
+
runtime.json,
|
|
971
|
+
{ ok: pass, command: "vision assert", ...(expect !== undefined ? { expect } : {}), ...result },
|
|
972
|
+
result.verdict === null ? `vision gate fail-closed: ${result.divergence}` : `verdict ${result.verdict}${expect !== undefined ? ` (expected ${expect})` : ""}`,
|
|
973
|
+
);
|
|
974
|
+
return pass ? 0 : 1;
|
|
975
|
+
}
|
|
976
|
+
|
|
924
977
|
export async function cmdModelInventoryRefresh(runtime: Runtime): Promise<number> {
|
|
925
978
|
if (runtime.dryRun) {
|
|
926
979
|
print(runtime.io, runtime.json, { ok: true, command: "models inventory refresh", dryRun: true }, "would refresh .kxm/models/inventory.yaml");
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -114,6 +114,7 @@ cmdBackup,
|
|
|
114
114
|
cmdRouteChange,
|
|
115
115
|
cmdModelsScreen,
|
|
116
116
|
cmdRouteList,
|
|
117
|
+
cmdVisionAssert,
|
|
117
118
|
cmdRouteCount,
|
|
118
119
|
cmdModelInventoryRefresh,
|
|
119
120
|
cmdKxmRuntime,
|
|
@@ -675,6 +676,16 @@ function createProgram(ctx: CliContext, result: { code: number }, argv: readonly
|
|
|
675
676
|
addGlobalOptions(routesCmd.command(status).description(`${status === "admit" ? "Admit" : "Disable"} a model route from the inventory`)).option("--model <id>", "Exact model id; omit to choose interactively").action(async function routeChangeAction(this: Command, options: { model?: string }) { result.code = await cmdRouteChange(runtimeFrom(ctx, this), status === "admit" ? "admitted" : "disabled", options.model); });
|
|
676
677
|
}
|
|
677
678
|
|
|
679
|
+
const visionCmd = addGlobalOptions(program.command("vision").description("Judge captured UI screenshots through the admitted vision gate"));
|
|
680
|
+
visionCmd.helpCommand("help", "Show vision help");
|
|
681
|
+
addGlobalOptions(visionCmd.command("assert").description("Assert a UI state on a screenshot (deterministic vision gate)"))
|
|
682
|
+
.requiredOption("--image <path>", "Path to the captured screenshot")
|
|
683
|
+
.requiredOption("--question <text>", "Yes/no assertion to verify on the screenshot")
|
|
684
|
+
.option("--route <id>", `Vision route (default ${"zai-coding-cn/glm-5.3-flash"})`)
|
|
685
|
+
.option("--timeout-ms <n>", "Transport timeout in milliseconds (5000-300000)")
|
|
686
|
+
.option("--expect <bool>", "Exit 0 only when the verdict matches true|false")
|
|
687
|
+
.action(async function visionAssertAction(this: Command, options: { image?: string; question?: string; route?: string; timeoutMs?: string; expect?: string }) { result.code = await cmdVisionAssert(runtimeFrom(ctx, this), options ?? {}); });
|
|
688
|
+
|
|
678
689
|
const harnessCmd = addGlobalOptions(program.command("harness").description("Detect coding-agent harnesses and authentication"));
|
|
679
690
|
harnessCmd.helpCommand("help", "Show harness help");
|
|
680
691
|
addGlobalOptions(harnessCmd.command("list").description("Show installed harnesses, auth, and native updaters")).action(bind(cmdHarnessList));
|
|
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
|
|
|
11
11
|
import type { HubEvent, MessageRecord } from "./protocol.ts";
|
|
12
12
|
import { sessionTokenFixHint } from "./session-token-hint.ts";
|
|
13
13
|
|
|
14
|
-
const VERSION = "0.7.
|
|
14
|
+
const VERSION = "0.7.138";
|
|
15
15
|
const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
16
16
|
const inbox = new Map<string, MessageRecord>();
|
|
17
17
|
const notifiedInbox = new Set<string>();
|