@kontextmind/kxm 0.7.128 → 0.7.130
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/CHANGELOG.md +20 -0
- package/docs/architecture/inventory.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/contributing/development.md +3 -1
- package/docs/operations/backup-and-restore.md +3 -3
- package/docs/operations/upgrade.md +2 -2
- package/docs/reference/cli-reference.md +11 -6
- package/docs/reference/config-reference.md +5 -2
- package/package.json +8 -8
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +2284 -222
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +461 -106
- package/plugins/kxm/dist/runtime.js +470 -114
- package/plugins/kxm/dist/server.js +17 -14
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/lanes.ts +109 -4
- package/plugins/kxm/src/cli/project.ts +2 -2
- package/plugins/kxm/src/database.ts +25 -19
- package/plugins/kxm/src/mcp-server.ts +1 -1
- 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 -3
- 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";
|
|
@@ -18244,9 +18244,10 @@ function legacyConfigFilesAt(root) {
|
|
|
18244
18244
|
}
|
|
18245
18245
|
|
|
18246
18246
|
// plugins/kxm/src/runtime-store.ts
|
|
18247
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
18247
18248
|
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";
|
|
18249
|
+
import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync3, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18250
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, resolve as resolve6 } from "node:path";
|
|
18250
18251
|
|
|
18251
18252
|
// plugins/kxm/src/sqlite.ts
|
|
18252
18253
|
import { existsSync as existsSync4 } from "node:fs";
|
|
@@ -18760,22 +18761,25 @@ function ensureWalJournalMode(database, file, description, timeoutMs = 5e3) {
|
|
|
18760
18761
|
Atomics.wait(sleeper, 0, 0, 10);
|
|
18761
18762
|
}
|
|
18762
18763
|
}
|
|
18764
|
+
function assertDatabaseFile(file, description) {
|
|
18765
|
+
checkedParent(file, description);
|
|
18766
|
+
const stat = lstatSync2(file, { throwIfNoEntry: false });
|
|
18767
|
+
if (stat) {
|
|
18768
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
18769
|
+
throw databaseError("runtime_path_invalid", description, `${description} must be a regular file, not a link or directory`);
|
|
18770
|
+
}
|
|
18771
|
+
}
|
|
18772
|
+
for (const sidecar of [`${file}-wal`, `${file}-shm`]) {
|
|
18773
|
+
const info = lstatSync2(sidecar, { throwIfNoEntry: false });
|
|
18774
|
+
if (info?.isSymbolicLink()) {
|
|
18775
|
+
throw databaseError("runtime_path_invalid", description, `${description} sidecar must not be a link`);
|
|
18776
|
+
}
|
|
18777
|
+
}
|
|
18778
|
+
}
|
|
18763
18779
|
function openDatabase(file, description, spec) {
|
|
18764
18780
|
const isMemory = file === ":memory:";
|
|
18765
18781
|
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
|
-
}
|
|
18782
|
+
assertDatabaseFile(file, description);
|
|
18779
18783
|
}
|
|
18780
18784
|
const database = new DatabaseSync(file);
|
|
18781
18785
|
let transaction = false;
|
|
@@ -18804,7 +18808,7 @@ function openDatabase(file, description, spec) {
|
|
|
18804
18808
|
throw databaseError(
|
|
18805
18809
|
"runtime_schema_outdated",
|
|
18806
18810
|
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
|
|
18811
|
+
`${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
18812
|
);
|
|
18809
18813
|
}
|
|
18810
18814
|
if (spec.tables) {
|
|
@@ -18917,10 +18921,10 @@ function runtimeIssue(phase, code, file, message) {
|
|
|
18917
18921
|
function runtimeError(code, file, message) {
|
|
18918
18922
|
return new KxmConfigError([runtimeIssue("semantic", code, file, message)]);
|
|
18919
18923
|
}
|
|
18920
|
-
var KXM_REGISTRY_SCHEMA_VERSION =
|
|
18924
|
+
var KXM_REGISTRY_SCHEMA_VERSION = 2;
|
|
18921
18925
|
var REGISTRY_TABLES = {
|
|
18922
18926
|
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"]
|
|
18927
|
+
projects: ["project_id", "project_root", "project_key", "home_runtime_id", "config_revision", "registered_at", "lane_of"]
|
|
18924
18928
|
};
|
|
18925
18929
|
var REGISTRY_SCHEMA = `
|
|
18926
18930
|
CREATE TABLE supervisor (
|
|
@@ -18934,14 +18938,117 @@ CREATE TABLE supervisor (
|
|
|
18934
18938
|
state TEXT NOT NULL
|
|
18935
18939
|
) STRICT;
|
|
18936
18940
|
CREATE TABLE projects (
|
|
18937
|
-
|
|
18941
|
+
project_key TEXT PRIMARY KEY,
|
|
18942
|
+
project_id TEXT NOT NULL,
|
|
18938
18943
|
project_root TEXT NOT NULL,
|
|
18939
|
-
project_key TEXT NOT NULL UNIQUE,
|
|
18940
18944
|
home_runtime_id TEXT NOT NULL,
|
|
18941
18945
|
config_revision TEXT,
|
|
18942
|
-
registered_at TEXT NOT NULL
|
|
18946
|
+
registered_at TEXT NOT NULL,
|
|
18947
|
+
lane_of TEXT
|
|
18943
18948
|
) STRICT;
|
|
18949
|
+
CREATE INDEX projects_by_project_id ON projects (project_id);
|
|
18944
18950
|
`;
|
|
18951
|
+
var PROJECT_COLUMNS = "project_id, project_root, project_key, home_runtime_id, config_revision, registered_at, lane_of";
|
|
18952
|
+
function registrationFromRow(row) {
|
|
18953
|
+
return {
|
|
18954
|
+
projectId: row.project_id,
|
|
18955
|
+
projectRoot: row.project_root,
|
|
18956
|
+
projectKey: row.project_key,
|
|
18957
|
+
homeRuntimeId: row.home_runtime_id,
|
|
18958
|
+
...row.config_revision !== null ? { configRevision: row.config_revision } : {},
|
|
18959
|
+
...row.lane_of !== null ? { laneOf: row.lane_of } : {},
|
|
18960
|
+
registeredAt: row.registered_at
|
|
18961
|
+
};
|
|
18962
|
+
}
|
|
18963
|
+
function controlRootDirectoryExists(projectRoot) {
|
|
18964
|
+
const stat = statSync(projectRoot, { throwIfNoEntry: false });
|
|
18965
|
+
return Boolean(stat?.isDirectory());
|
|
18966
|
+
}
|
|
18967
|
+
function gitEnv() {
|
|
18968
|
+
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
|
|
18969
|
+
}
|
|
18970
|
+
function gitCommonDirectory(projectRoot) {
|
|
18971
|
+
const result = spawnSync3("git", ["-C", projectRoot, "rev-parse", "--git-common-dir"], {
|
|
18972
|
+
encoding: "utf8",
|
|
18973
|
+
env: gitEnv(),
|
|
18974
|
+
timeout: 3e4,
|
|
18975
|
+
windowsHide: true
|
|
18976
|
+
});
|
|
18977
|
+
if (result.status !== 0) return void 0;
|
|
18978
|
+
const text = (result.stdout ?? "").trim();
|
|
18979
|
+
if (!text) return void 0;
|
|
18980
|
+
const absolute = isAbsolute4(text) ? text : resolve6(projectRoot, text);
|
|
18981
|
+
let canonical;
|
|
18982
|
+
try {
|
|
18983
|
+
canonical = realpathSync3.native(absolute);
|
|
18984
|
+
} catch {
|
|
18985
|
+
canonical = resolve6(absolute);
|
|
18986
|
+
}
|
|
18987
|
+
return process.platform === "win32" ? canonical.toLocaleLowerCase("en-US") : canonical;
|
|
18988
|
+
}
|
|
18989
|
+
function isPrimaryWorktree(projectRoot, commonDir) {
|
|
18990
|
+
if (commonDir === void 0) return false;
|
|
18991
|
+
const dotGit = resolve6(projectRoot, ".git");
|
|
18992
|
+
let canonical;
|
|
18993
|
+
try {
|
|
18994
|
+
canonical = realpathSync3.native(dotGit);
|
|
18995
|
+
} catch {
|
|
18996
|
+
canonical = resolve6(dotGit);
|
|
18997
|
+
}
|
|
18998
|
+
if (process.platform === "win32") canonical = canonical.toLocaleLowerCase("en-US");
|
|
18999
|
+
return canonical === commonDir;
|
|
19000
|
+
}
|
|
19001
|
+
function migrateRegistryLanes(file) {
|
|
19002
|
+
const stat = lstatSync3(file, { throwIfNoEntry: false });
|
|
19003
|
+
if (!stat) return;
|
|
19004
|
+
assertDatabaseFile(file, "runtime registry");
|
|
19005
|
+
const database = new DatabaseSync(file);
|
|
19006
|
+
let transaction = false;
|
|
19007
|
+
try {
|
|
19008
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
19009
|
+
const current = database.prepare("PRAGMA user_version").get();
|
|
19010
|
+
if ((current?.user_version ?? 0) !== 1) return;
|
|
19011
|
+
database.exec("BEGIN IMMEDIATE");
|
|
19012
|
+
transaction = true;
|
|
19013
|
+
const locked = database.prepare("PRAGMA user_version").get();
|
|
19014
|
+
if ((locked?.user_version ?? 0) !== 1) {
|
|
19015
|
+
database.exec("ROLLBACK");
|
|
19016
|
+
transaction = false;
|
|
19017
|
+
return;
|
|
19018
|
+
}
|
|
19019
|
+
if (!tableColumns(database, "projects").includes("lane_of")) {
|
|
19020
|
+
database.exec(`
|
|
19021
|
+
CREATE TABLE projects_v2 (
|
|
19022
|
+
project_key TEXT PRIMARY KEY,
|
|
19023
|
+
project_id TEXT NOT NULL,
|
|
19024
|
+
project_root TEXT NOT NULL,
|
|
19025
|
+
home_runtime_id TEXT NOT NULL,
|
|
19026
|
+
config_revision TEXT,
|
|
19027
|
+
registered_at TEXT NOT NULL,
|
|
19028
|
+
lane_of TEXT
|
|
19029
|
+
) STRICT;
|
|
19030
|
+
INSERT INTO projects_v2 (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
|
|
19031
|
+
SELECT project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, NULL FROM projects;
|
|
19032
|
+
DROP TABLE projects;
|
|
19033
|
+
ALTER TABLE projects_v2 RENAME TO projects;
|
|
19034
|
+
CREATE INDEX projects_by_project_id ON projects (project_id);
|
|
19035
|
+
`);
|
|
19036
|
+
}
|
|
19037
|
+
database.exec("PRAGMA user_version = 2");
|
|
19038
|
+
database.exec("COMMIT");
|
|
19039
|
+
transaction = false;
|
|
19040
|
+
} catch (error) {
|
|
19041
|
+
if (transaction) {
|
|
19042
|
+
try {
|
|
19043
|
+
database.exec("ROLLBACK");
|
|
19044
|
+
} catch {
|
|
19045
|
+
}
|
|
19046
|
+
}
|
|
19047
|
+
throw error;
|
|
19048
|
+
} finally {
|
|
19049
|
+
database.close();
|
|
19050
|
+
}
|
|
19051
|
+
}
|
|
18945
19052
|
function readKxmSupervisorRecord(database) {
|
|
18946
19053
|
const row = database.prepare("SELECT runtime_id, pid, port, token_hash, started_at, heartbeat_at, state FROM supervisor WHERE singleton_id = 1").get();
|
|
18947
19054
|
return row ? {
|
|
@@ -18959,6 +19066,7 @@ var KxmRuntimeRegistry = class {
|
|
|
18959
19066
|
database;
|
|
18960
19067
|
constructor(path) {
|
|
18961
19068
|
this.path = resolve6(path);
|
|
19069
|
+
migrateRegistryLanes(this.path);
|
|
18962
19070
|
this.database = openDatabase(this.path, "runtime registry", {
|
|
18963
19071
|
schema: REGISTRY_SCHEMA,
|
|
18964
19072
|
version: KXM_REGISTRY_SCHEMA_VERSION,
|
|
@@ -19037,57 +19145,188 @@ var KxmRuntimeRegistry = class {
|
|
|
19037
19145
|
supervisor() {
|
|
19038
19146
|
return this.readSupervisorRow();
|
|
19039
19147
|
}
|
|
19040
|
-
/**
|
|
19041
|
-
|
|
19042
|
-
*
|
|
19043
|
-
* syncing and presence keeps beating. */
|
|
19148
|
+
/** All projects registered to this Runtime, including lane roots, for restart
|
|
19149
|
+
* recovery: the supervisor reopens their contexts so pending outbox rows
|
|
19150
|
+
* resume syncing and presence keeps beating. */
|
|
19044
19151
|
projectsForRuntime(homeRuntimeId) {
|
|
19045
19152
|
const rows = this.database.prepare(
|
|
19046
19153
|
"SELECT project_root, project_id FROM projects WHERE home_runtime_id = ? ORDER BY registered_at"
|
|
19047
19154
|
).all(homeRuntimeId);
|
|
19048
19155
|
return rows.map((row) => ({ projectRoot: row.project_root, projectId: row.project_id }));
|
|
19049
19156
|
}
|
|
19157
|
+
/**
|
|
19158
|
+
* Register or revalidate a control root. The home runtime of a live row is
|
|
19159
|
+
* immutable. A second root whose git common directory matches a live row
|
|
19160
|
+
* for the same project is a lane, unless the registering root is the primary
|
|
19161
|
+
* worktree and the live home row is not: then the primary becomes the home
|
|
19162
|
+
* row (same home runtime id) and every other live row of that repository
|
|
19163
|
+
* becomes its lane. A primary never becomes a lane. A root whose directory
|
|
19164
|
+
* is gone is replaced. A live root that is a different repository is
|
|
19165
|
+
* `project_home_conflict`.
|
|
19166
|
+
*/
|
|
19050
19167
|
registerProject(registration) {
|
|
19051
19168
|
const projectRoot = resolve6(registration.projectRoot);
|
|
19052
19169
|
const projectKey = projectRuntimeKey(projectRoot);
|
|
19170
|
+
const candidateCommon = gitCommonDirectory(projectRoot);
|
|
19171
|
+
const commonByRoot = /* @__PURE__ */ new Map();
|
|
19172
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
19173
|
+
this.database.exec("BEGIN IMMEDIATE");
|
|
19174
|
+
let rootsToMeasure;
|
|
19175
|
+
try {
|
|
19176
|
+
const byKey = this.projectRow("SELECT " + PROJECT_COLUMNS + " FROM projects WHERE project_key = ?", projectKey);
|
|
19177
|
+
if (byKey) {
|
|
19178
|
+
const problems = [];
|
|
19179
|
+
if (byKey.project_id !== registration.projectId) problems.push(`control root is already bound to a different project id ${byKey.project_id}`);
|
|
19180
|
+
if (byKey.home_runtime_id !== registration.homeRuntimeId) problems.push(`project ${registration.projectId} home runtime is immutable and cannot be rebound`);
|
|
19181
|
+
if (problems.length > 0) {
|
|
19182
|
+
this.database.exec("ROLLBACK");
|
|
19183
|
+
throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
|
|
19184
|
+
}
|
|
19185
|
+
const result = registrationFromRow(byKey);
|
|
19186
|
+
this.database.exec("COMMIT");
|
|
19187
|
+
return result;
|
|
19188
|
+
}
|
|
19189
|
+
const rows = this.database.prepare(
|
|
19190
|
+
`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_id = ? ORDER BY registered_at`
|
|
19191
|
+
).all(registration.projectId);
|
|
19192
|
+
const live = rows.filter((row) => controlRootDirectoryExists(row.project_root));
|
|
19193
|
+
const dead = rows.filter((row) => !controlRootDirectoryExists(row.project_root));
|
|
19194
|
+
const unknown = live.filter((row) => !commonByRoot.has(row.project_root));
|
|
19195
|
+
if (unknown.length > 0) {
|
|
19196
|
+
rootsToMeasure = unknown.map((row) => row.project_root);
|
|
19197
|
+
this.database.exec("ROLLBACK");
|
|
19198
|
+
} else if (live.length > 0) {
|
|
19199
|
+
const anchor = live.find((row) => {
|
|
19200
|
+
const dir = commonByRoot.get(row.project_root);
|
|
19201
|
+
return candidateCommon !== void 0 && dir === candidateCommon;
|
|
19202
|
+
});
|
|
19203
|
+
if (!anchor) {
|
|
19204
|
+
this.database.exec("ROLLBACK");
|
|
19205
|
+
throw runtimeError(
|
|
19206
|
+
"project_home_conflict",
|
|
19207
|
+
".kxm/project.yaml",
|
|
19208
|
+
`project ${registration.projectId} is already bound to a different control root`
|
|
19209
|
+
);
|
|
19210
|
+
}
|
|
19211
|
+
if (live.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
|
|
19212
|
+
this.database.exec("ROLLBACK");
|
|
19213
|
+
throw runtimeError(
|
|
19214
|
+
"project_home_conflict",
|
|
19215
|
+
".kxm/project.yaml",
|
|
19216
|
+
`project ${registration.projectId} home runtime is immutable and cannot be rebound`
|
|
19217
|
+
);
|
|
19218
|
+
}
|
|
19219
|
+
const sameRepo = live.filter((row) => commonByRoot.get(row.project_root) === candidateCommon);
|
|
19220
|
+
const home = sameRepo.find((row) => row.lane_of === null);
|
|
19221
|
+
const registeringIsPrimary = isPrimaryWorktree(projectRoot, candidateCommon);
|
|
19222
|
+
const homeIsPrimary = home !== void 0 && isPrimaryWorktree(home.project_root, commonByRoot.get(home.project_root));
|
|
19223
|
+
if (registeringIsPrimary && !homeIsPrimary) {
|
|
19224
|
+
this.insertHome(registration, projectRoot, projectKey);
|
|
19225
|
+
for (const row of sameRepo) {
|
|
19226
|
+
this.database.prepare("UPDATE projects SET lane_of = ? WHERE project_key = ?").run(projectKey, row.project_key);
|
|
19227
|
+
}
|
|
19228
|
+
this.database.exec("COMMIT");
|
|
19229
|
+
registration.logger?.({
|
|
19230
|
+
event: "project_home_promoted",
|
|
19231
|
+
projectId: registration.projectId,
|
|
19232
|
+
projectRoot,
|
|
19233
|
+
homeRuntimeId: registration.homeRuntimeId,
|
|
19234
|
+
previousHomeRoot: home?.project_root ?? null,
|
|
19235
|
+
previousHomeKey: home?.project_key ?? null
|
|
19236
|
+
});
|
|
19237
|
+
return this.insertedRegistration(registration, projectRoot, projectKey);
|
|
19238
|
+
}
|
|
19239
|
+
if (registeringIsPrimary) {
|
|
19240
|
+
this.database.exec("ROLLBACK");
|
|
19241
|
+
throw runtimeError(
|
|
19242
|
+
"project_home_conflict",
|
|
19243
|
+
".kxm/project.yaml",
|
|
19244
|
+
`project ${registration.projectId} primary checkout is already the home row`
|
|
19245
|
+
);
|
|
19246
|
+
}
|
|
19247
|
+
const laneOf = anchor.lane_of ?? anchor.project_key;
|
|
19248
|
+
this.database.prepare(`
|
|
19249
|
+
INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
|
|
19250
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
19251
|
+
`).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now, laneOf);
|
|
19252
|
+
const result = this.insertedRegistration(registration, projectRoot, projectKey, laneOf);
|
|
19253
|
+
this.database.exec("COMMIT");
|
|
19254
|
+
return result;
|
|
19255
|
+
} else if (dead.length > 0) {
|
|
19256
|
+
const replaced = dead[0];
|
|
19257
|
+
if (dead.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
|
|
19258
|
+
this.database.exec("ROLLBACK");
|
|
19259
|
+
throw runtimeError(
|
|
19260
|
+
"project_home_conflict",
|
|
19261
|
+
".kxm/project.yaml",
|
|
19262
|
+
`project ${registration.projectId} home runtime is immutable and cannot be rebound`
|
|
19263
|
+
);
|
|
19264
|
+
}
|
|
19265
|
+
this.database.prepare("DELETE FROM projects WHERE project_id = ?").run(registration.projectId);
|
|
19266
|
+
this.insertHome(registration, projectRoot, projectKey);
|
|
19267
|
+
this.database.exec("COMMIT");
|
|
19268
|
+
registration.logger?.({
|
|
19269
|
+
event: "project_registration_replaced",
|
|
19270
|
+
projectId: registration.projectId,
|
|
19271
|
+
projectRoot,
|
|
19272
|
+
replacedRoot: replaced.project_root,
|
|
19273
|
+
replacedCount: dead.length,
|
|
19274
|
+
replacedHomeRuntimeId: replaced.home_runtime_id,
|
|
19275
|
+
replacedProjectKey: replaced.project_key
|
|
19276
|
+
});
|
|
19277
|
+
return this.insertedRegistration(registration, projectRoot, projectKey);
|
|
19278
|
+
} else {
|
|
19279
|
+
this.insertHome(registration, projectRoot, projectKey);
|
|
19280
|
+
this.database.exec("COMMIT");
|
|
19281
|
+
return this.insertedRegistration(registration, projectRoot, projectKey);
|
|
19282
|
+
}
|
|
19283
|
+
} catch (error) {
|
|
19284
|
+
try {
|
|
19285
|
+
this.database.exec("ROLLBACK");
|
|
19286
|
+
} catch {
|
|
19287
|
+
}
|
|
19288
|
+
throw error;
|
|
19289
|
+
}
|
|
19290
|
+
for (const root of rootsToMeasure ?? []) {
|
|
19291
|
+
commonByRoot.set(root, gitCommonDirectory(root));
|
|
19292
|
+
}
|
|
19293
|
+
}
|
|
19294
|
+
throw runtimeError(
|
|
19295
|
+
"runtime_registry_busy",
|
|
19296
|
+
projectRoot,
|
|
19297
|
+
`project ${registration.projectId} registration could not be rechecked`
|
|
19298
|
+
);
|
|
19299
|
+
}
|
|
19300
|
+
/** Remove one control root. Returns false when that root was not registered. */
|
|
19301
|
+
unregisterProject(projectRoot) {
|
|
19053
19302
|
this.database.exec("BEGIN IMMEDIATE");
|
|
19054
19303
|
try {
|
|
19055
|
-
const
|
|
19056
|
-
|
|
19057
|
-
|
|
19058
|
-
|
|
19059
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
|
|
19067
|
-
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19072
|
-
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
this.database.exec("
|
|
19076
|
-
return
|
|
19304
|
+
const removed = this.deleteRegisteredRoot(projectRoot);
|
|
19305
|
+
this.database.exec("COMMIT");
|
|
19306
|
+
return removed;
|
|
19307
|
+
} catch (error) {
|
|
19308
|
+
try {
|
|
19309
|
+
this.database.exec("ROLLBACK");
|
|
19310
|
+
} catch {
|
|
19311
|
+
}
|
|
19312
|
+
throw error;
|
|
19313
|
+
}
|
|
19314
|
+
}
|
|
19315
|
+
/**
|
|
19316
|
+
* Delete the row only when `supervisorIsLive` is false for the supervisor
|
|
19317
|
+
* row read inside this write transaction. A live supervisor keeps the row;
|
|
19318
|
+
* the caller uses the supervisor route instead of editing the file.
|
|
19319
|
+
*/
|
|
19320
|
+
unregisterProjectIfIdle(projectRoot, supervisorIsLive) {
|
|
19321
|
+
this.database.exec("BEGIN IMMEDIATE");
|
|
19322
|
+
try {
|
|
19323
|
+
if (supervisorIsLive(this.readSupervisorRow())) {
|
|
19324
|
+
this.database.exec("ROLLBACK");
|
|
19325
|
+
return "supervisor_live";
|
|
19077
19326
|
}
|
|
19078
|
-
this.
|
|
19079
|
-
INSERT INTO projects (project_id, project_root, project_key, home_runtime_id, config_revision, registered_at)
|
|
19080
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
19081
|
-
`).run(registration.projectId, projectRoot, projectKey, registration.homeRuntimeId, registration.configRevision ?? null, registration.now);
|
|
19327
|
+
const removed = this.deleteRegisteredRoot(projectRoot);
|
|
19082
19328
|
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
|
-
};
|
|
19329
|
+
return removed ? "removed" : "absent";
|
|
19091
19330
|
} catch (error) {
|
|
19092
19331
|
try {
|
|
19093
19332
|
this.database.exec("ROLLBACK");
|
|
@@ -19096,28 +19335,65 @@ var KxmRuntimeRegistry = class {
|
|
|
19096
19335
|
throw error;
|
|
19097
19336
|
}
|
|
19098
19337
|
}
|
|
19338
|
+
/** How many other rows share this home root's project. Zero when this root is a lane or missing. */
|
|
19339
|
+
homeLaneCount(projectRoot) {
|
|
19340
|
+
const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(resolve6(projectRoot)));
|
|
19341
|
+
if (!row || row.lane_of !== null) return 0;
|
|
19342
|
+
const count = this.database.prepare(
|
|
19343
|
+
"SELECT COUNT(*) AS total FROM projects WHERE project_id = ? AND project_key != ?"
|
|
19344
|
+
).get(row.project_id, row.project_key);
|
|
19345
|
+
return Number(count?.total ?? 0);
|
|
19346
|
+
}
|
|
19347
|
+
/** Caller holds the write transaction. Does not commit. */
|
|
19348
|
+
deleteRegisteredRoot(projectRoot) {
|
|
19349
|
+
const projectKey = projectRuntimeKey(resolve6(projectRoot));
|
|
19350
|
+
const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectKey);
|
|
19351
|
+
if (!row) return false;
|
|
19352
|
+
this.database.prepare("DELETE FROM projects WHERE project_key = ?").run(projectKey);
|
|
19353
|
+
if (row.lane_of === null) {
|
|
19354
|
+
const successor = this.database.prepare(
|
|
19355
|
+
"SELECT project_key FROM projects WHERE project_id = ? ORDER BY registered_at LIMIT 1"
|
|
19356
|
+
).get(row.project_id);
|
|
19357
|
+
if (successor) {
|
|
19358
|
+
this.database.prepare("UPDATE projects SET lane_of = NULL WHERE project_key = ?").run(successor.project_key);
|
|
19359
|
+
this.database.prepare(
|
|
19360
|
+
"UPDATE projects SET lane_of = ? WHERE project_id = ? AND project_key != ? AND (lane_of IS NULL OR lane_of = ?)"
|
|
19361
|
+
).run(successor.project_key, row.project_id, successor.project_key, projectKey);
|
|
19362
|
+
}
|
|
19363
|
+
}
|
|
19364
|
+
return true;
|
|
19365
|
+
}
|
|
19366
|
+
/** The home row for a project id, or the earliest row when every row is a lane. */
|
|
19099
19367
|
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;
|
|
19368
|
+
const row = this.projectRow(
|
|
19369
|
+
`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`,
|
|
19370
|
+
projectId
|
|
19371
|
+
);
|
|
19372
|
+
return row ? registrationFromRow(row) : void 0;
|
|
19109
19373
|
}
|
|
19110
19374
|
projectByRoot(projectRoot) {
|
|
19111
|
-
const
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
|
|
19116
|
-
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
|
|
19375
|
+
const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(projectRoot));
|
|
19376
|
+
return row ? registrationFromRow(row) : void 0;
|
|
19377
|
+
}
|
|
19378
|
+
projectRow(sql, parameter) {
|
|
19379
|
+
return this.database.prepare(sql).get(parameter);
|
|
19380
|
+
}
|
|
19381
|
+
insertHome(registration, projectRoot, projectKey) {
|
|
19382
|
+
this.database.prepare(`
|
|
19383
|
+
INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
|
|
19384
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL)
|
|
19385
|
+
`).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now);
|
|
19386
|
+
}
|
|
19387
|
+
insertedRegistration(registration, projectRoot, projectKey, laneOf) {
|
|
19388
|
+
return {
|
|
19389
|
+
projectId: registration.projectId,
|
|
19390
|
+
projectRoot,
|
|
19391
|
+
projectKey,
|
|
19392
|
+
homeRuntimeId: registration.homeRuntimeId,
|
|
19393
|
+
...registration.configRevision !== void 0 ? { configRevision: registration.configRevision } : {},
|
|
19394
|
+
...laneOf !== void 0 ? { laneOf } : {},
|
|
19395
|
+
registeredAt: registration.now
|
|
19396
|
+
};
|
|
19121
19397
|
}
|
|
19122
19398
|
};
|
|
19123
19399
|
var KXM_RUN_EVENT_SCHEMA = "kxm.run-event.v1";
|
|
@@ -23559,7 +23835,8 @@ function openKxmRuntimeContext(projectRoot, options) {
|
|
|
23559
23835
|
projectRoot: bundle.projectRoot,
|
|
23560
23836
|
homeRuntimeId: options.homeRuntimeId,
|
|
23561
23837
|
configRevision: bundle.configRevision,
|
|
23562
|
-
now: options.now ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
23838
|
+
now: options.now ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
23839
|
+
...options.logger ? { logger: options.logger } : {}
|
|
23563
23840
|
});
|
|
23564
23841
|
const eventStore = new KxmRunEventStore(join7(paths.projectsDir, registration.projectKey, "run-events.db"));
|
|
23565
23842
|
registerKxmRuntimeHandle(eventStore.path);
|
|
@@ -24022,7 +24299,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
24022
24299
|
// plugins/kxm/src/prices.ts
|
|
24023
24300
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
24024
24301
|
import { createHash as createHash10 } from "node:crypto";
|
|
24025
|
-
import { existsSync as existsSync8, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
24302
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
24026
24303
|
import { join as join9 } from "node:path";
|
|
24027
24304
|
|
|
24028
24305
|
// plugins/kxm/src/price-calc.ts
|
|
@@ -24175,7 +24452,7 @@ function parsePriceCatalog(text) {
|
|
|
24175
24452
|
return catalog;
|
|
24176
24453
|
}
|
|
24177
24454
|
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) &&
|
|
24455
|
+
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
24456
|
if (!candidatePath || !existsSync8(candidatePath)) {
|
|
24180
24457
|
return void 0;
|
|
24181
24458
|
}
|
|
@@ -24228,9 +24505,9 @@ function isRouteAdmitted(root, modelId) {
|
|
|
24228
24505
|
}
|
|
24229
24506
|
|
|
24230
24507
|
// plugins/kxm/src/worktree-witness.ts
|
|
24231
|
-
import { spawnSync as
|
|
24508
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
24232
24509
|
function gitText(cwd, args) {
|
|
24233
|
-
const result =
|
|
24510
|
+
const result = spawnSync4("git", ["-C", cwd, ...args], {
|
|
24234
24511
|
encoding: "utf8",
|
|
24235
24512
|
windowsHide: true,
|
|
24236
24513
|
timeout: 15e3
|
|
@@ -24543,7 +24820,7 @@ function pruneContextPacket(packet, tokenBudget) {
|
|
|
24543
24820
|
}
|
|
24544
24821
|
|
|
24545
24822
|
// plugins/kxm/src/dispatch-context.ts
|
|
24546
|
-
import { spawnSync as
|
|
24823
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
24547
24824
|
import { readdirSync as readdirSync8 } from "node:fs";
|
|
24548
24825
|
import { extname as extname3, join as join13 } from "node:path";
|
|
24549
24826
|
|
|
@@ -25303,7 +25580,7 @@ function loadAuthoredMemory(repoRoot) {
|
|
|
25303
25580
|
// plugins/kxm/src/skills.ts
|
|
25304
25581
|
var import_yaml8 = __toESM(require_dist(), 1);
|
|
25305
25582
|
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
|
|
25583
|
+
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
25584
|
import { dirname as dirname7, join as join12 } from "node:path";
|
|
25308
25585
|
var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
|
|
25309
25586
|
var SKILL_EVALUATION_SCHEMA = "kxm.skill-evaluation.v1";
|
|
@@ -25683,7 +25960,7 @@ function boundedList(value, field) {
|
|
|
25683
25960
|
return [...new Set(refs)];
|
|
25684
25961
|
}
|
|
25685
25962
|
function readdirSorted(dir) {
|
|
25686
|
-
return readdirSync7(dir).filter((entry) =>
|
|
25963
|
+
return readdirSync7(dir).filter((entry) => statSync3(join12(dir, entry)).isDirectory()).sort();
|
|
25687
25964
|
}
|
|
25688
25965
|
|
|
25689
25966
|
// plugins/kxm/src/dispatch-context.ts
|
|
@@ -25744,7 +26021,7 @@ function gitEnvironment2() {
|
|
|
25744
26021
|
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
|
|
25745
26022
|
}
|
|
25746
26023
|
function committedGateGap(projectRoot) {
|
|
25747
|
-
const result =
|
|
26024
|
+
const result = spawnSync5("git", [
|
|
25748
26025
|
"--no-optional-locks",
|
|
25749
26026
|
"-C",
|
|
25750
26027
|
projectRoot,
|
|
@@ -25886,11 +26163,11 @@ function assembleDispatchContext(sources, input) {
|
|
|
25886
26163
|
import { join as join14 } from "node:path";
|
|
25887
26164
|
|
|
25888
26165
|
// plugins/kxm/src/artifacts-exist.ts
|
|
25889
|
-
import { lstatSync as lstatSync5, realpathSync as
|
|
25890
|
-
import { isAbsolute as
|
|
26166
|
+
import { lstatSync as lstatSync5, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
|
|
26167
|
+
import { isAbsolute as isAbsolute5, relative as relative4, resolve as resolve8 } from "node:path";
|
|
25891
26168
|
function staysUnder(root, candidate) {
|
|
25892
26169
|
const child = relative4(root, candidate);
|
|
25893
|
-
return child === "" || !
|
|
26170
|
+
return child === "" || !isAbsolute5(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
|
|
25894
26171
|
}
|
|
25895
26172
|
function verifyArtifactExists(rootInput, pathInput) {
|
|
25896
26173
|
const lexicalRoot = resolve8(rootInput);
|
|
@@ -25901,12 +26178,12 @@ function verifyArtifactExists(rootInput, pathInput) {
|
|
|
25901
26178
|
let realRoot;
|
|
25902
26179
|
let realPath;
|
|
25903
26180
|
try {
|
|
25904
|
-
realRoot =
|
|
26181
|
+
realRoot = realpathSync4(lexicalRoot);
|
|
25905
26182
|
} catch {
|
|
25906
26183
|
return { ok: false, error: "artifact_unreadable", path: lexicalPath };
|
|
25907
26184
|
}
|
|
25908
26185
|
try {
|
|
25909
|
-
realPath =
|
|
26186
|
+
realPath = realpathSync4(lexicalPath);
|
|
25910
26187
|
} catch {
|
|
25911
26188
|
return { ok: false, error: "artifact_missing", path: lexicalPath };
|
|
25912
26189
|
}
|
|
@@ -25918,7 +26195,7 @@ function verifyArtifactExists(rootInput, pathInput) {
|
|
|
25918
26195
|
if (link.isSymbolicLink()) {
|
|
25919
26196
|
return { ok: false, error: "artifact_outside_workspace_assets", path: lexicalPath };
|
|
25920
26197
|
}
|
|
25921
|
-
const artifact =
|
|
26198
|
+
const artifact = statSync4(realPath);
|
|
25922
26199
|
if (!artifact.isFile()) return { ok: false, error: "artifact_not_file", path: lexicalPath };
|
|
25923
26200
|
if (artifact.size < 1) return { ok: false, error: "artifact_empty", path: lexicalPath };
|
|
25924
26201
|
return { ok: true, path: realPath, bytes: artifact.size };
|
|
@@ -30041,7 +30318,7 @@ var RuntimeHubClient = class {
|
|
|
30041
30318
|
// plugins/kxm/src/hub-binding.ts
|
|
30042
30319
|
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
30043
30320
|
import { homedir as homedir2 } from "node:os";
|
|
30044
|
-
import { dirname as dirname8, isAbsolute as
|
|
30321
|
+
import { dirname as dirname8, isAbsolute as isAbsolute6, join as join16, resolve as resolve9 } from "node:path";
|
|
30045
30322
|
var HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
|
|
30046
30323
|
var HubBindingError = class extends Error {
|
|
30047
30324
|
constructor(message) {
|
|
@@ -30052,17 +30329,17 @@ var HubBindingError = class extends Error {
|
|
|
30052
30329
|
function resolveUserStateRoot(env) {
|
|
30053
30330
|
const explicit = env.KXM_STATE_HOME?.trim();
|
|
30054
30331
|
if (explicit) {
|
|
30055
|
-
if (!
|
|
30332
|
+
if (!isAbsolute6(explicit)) throw new HubBindingError("local_state_root_not_absolute");
|
|
30056
30333
|
return resolve9(explicit);
|
|
30057
30334
|
}
|
|
30058
30335
|
if (process.platform === "win32") {
|
|
30059
30336
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
30060
|
-
const base2 = localAppData &&
|
|
30337
|
+
const base2 = localAppData && isAbsolute6(localAppData) ? localAppData : join16(homedir2(), "AppData", "Local");
|
|
30061
30338
|
return resolve9(base2, "KXM");
|
|
30062
30339
|
}
|
|
30063
30340
|
if (process.platform === "darwin") return resolve9(homedir2(), "Library", "Application Support", "KXM");
|
|
30064
30341
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
30065
|
-
const base = xdgState &&
|
|
30342
|
+
const base = xdgState && isAbsolute6(xdgState) ? xdgState : join16(homedir2(), ".local", "state");
|
|
30066
30343
|
return resolve9(base, "kxm");
|
|
30067
30344
|
}
|
|
30068
30345
|
function hubBindingFile(env = process.env) {
|
|
@@ -30113,7 +30390,7 @@ function readHubBinding(env = process.env) {
|
|
|
30113
30390
|
// plugins/kxm/src/hub-env.ts
|
|
30114
30391
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync12, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
|
|
30115
30392
|
import { homedir as homedir3 } from "node:os";
|
|
30116
|
-
import { dirname as dirname9, isAbsolute as
|
|
30393
|
+
import { dirname as dirname9, isAbsolute as isAbsolute7, join as join17, resolve as resolve10 } from "node:path";
|
|
30117
30394
|
var HUB_ENV_SCHEMA = "kxm.hub-env.v1";
|
|
30118
30395
|
var HubEnvError = class extends Error {
|
|
30119
30396
|
constructor(message) {
|
|
@@ -30124,17 +30401,17 @@ var HubEnvError = class extends Error {
|
|
|
30124
30401
|
function resolveUserStateRoot2(env) {
|
|
30125
30402
|
const explicit = env.KXM_STATE_HOME?.trim();
|
|
30126
30403
|
if (explicit) {
|
|
30127
|
-
if (!
|
|
30404
|
+
if (!isAbsolute7(explicit)) throw new HubEnvError("local_state_root_not_absolute");
|
|
30128
30405
|
return resolve10(explicit);
|
|
30129
30406
|
}
|
|
30130
30407
|
if (process.platform === "win32") {
|
|
30131
30408
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
30132
|
-
const base2 = localAppData &&
|
|
30409
|
+
const base2 = localAppData && isAbsolute7(localAppData) ? localAppData : join17(homedir3(), "AppData", "Local");
|
|
30133
30410
|
return resolve10(base2, "KXM");
|
|
30134
30411
|
}
|
|
30135
30412
|
if (process.platform === "darwin") return resolve10(homedir3(), "Library", "Application Support", "KXM");
|
|
30136
30413
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
30137
|
-
const base = xdgState &&
|
|
30414
|
+
const base = xdgState && isAbsolute7(xdgState) ? xdgState : join17(homedir3(), ".local", "state");
|
|
30138
30415
|
return resolve10(base, "kxm");
|
|
30139
30416
|
}
|
|
30140
30417
|
function hubEnvFile(env = process.env) {
|
|
@@ -30185,7 +30462,7 @@ function resolveClientHubAuthToken(env, project) {
|
|
|
30185
30462
|
}
|
|
30186
30463
|
|
|
30187
30464
|
// plugins/kxm/src/logger.ts
|
|
30188
|
-
import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync as
|
|
30465
|
+
import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync as statSync5, unlinkSync as unlinkSync2 } from "node:fs";
|
|
30189
30466
|
import { dirname as dirname10 } from "node:path";
|
|
30190
30467
|
var LOG_LEVEL_PRIORITY = {
|
|
30191
30468
|
debug: 10,
|
|
@@ -30256,7 +30533,7 @@ function createLogger(options) {
|
|
|
30256
30533
|
let currentSize = 0;
|
|
30257
30534
|
if (filePath && existsSync15(filePath)) {
|
|
30258
30535
|
try {
|
|
30259
|
-
currentSize =
|
|
30536
|
+
currentSize = statSync5(filePath).size;
|
|
30260
30537
|
} catch {
|
|
30261
30538
|
currentSize = 0;
|
|
30262
30539
|
}
|
|
@@ -30403,6 +30680,24 @@ function readRecentSupervisorError(paths) {
|
|
|
30403
30680
|
return void 0;
|
|
30404
30681
|
}
|
|
30405
30682
|
}
|
|
30683
|
+
var SETTLED_RUN_STATUS = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
30684
|
+
function kxmSupervisorRecordIsLive(record2) {
|
|
30685
|
+
return supervisorStatusOf(record2).running;
|
|
30686
|
+
}
|
|
30687
|
+
function unsettledRunIds(context) {
|
|
30688
|
+
const ids = [];
|
|
30689
|
+
for (const stored of context.eventStore.runsForProject(context.projectId, 1e4)) {
|
|
30690
|
+
let status = stored.status;
|
|
30691
|
+
try {
|
|
30692
|
+
status = projectKxmRunReadOnly(context, stored.runId).status;
|
|
30693
|
+
} catch {
|
|
30694
|
+
ids.push(stored.runId);
|
|
30695
|
+
continue;
|
|
30696
|
+
}
|
|
30697
|
+
if (!SETTLED_RUN_STATUS.has(status)) ids.push(stored.runId);
|
|
30698
|
+
}
|
|
30699
|
+
return ids;
|
|
30700
|
+
}
|
|
30406
30701
|
function supervisorStatusOf(record2) {
|
|
30407
30702
|
if (!record2) return { running: false };
|
|
30408
30703
|
const heartbeatAgeMs = Date.now() - Date.parse(record2.heartbeatAt);
|
|
@@ -30751,7 +31046,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
30751
31046
|
}
|
|
30752
31047
|
};
|
|
30753
31048
|
const contextFor = (projectRoot) => {
|
|
30754
|
-
if (!
|
|
31049
|
+
if (!isAbsolute8(projectRoot)) {
|
|
30755
31050
|
throw runtimeError("runtime_request_invalid", "projectRoot", "projectRoot must be an absolute path");
|
|
30756
31051
|
}
|
|
30757
31052
|
const key = projectRuntimeKey(projectRoot);
|
|
@@ -30817,6 +31112,62 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
30817
31112
|
sendJson(response, 200, { ok: true, projectId: context.projectId, retried, outbox: context.eventStore.outboxStatus() });
|
|
30818
31113
|
return;
|
|
30819
31114
|
}
|
|
31115
|
+
if (request.method === "POST" && url.pathname === "/v1/projects/unregister") {
|
|
31116
|
+
const body = await readJsonBody(request);
|
|
31117
|
+
const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
|
|
31118
|
+
const force = body.force === true;
|
|
31119
|
+
if (!projectRoot || !isAbsolute8(projectRoot)) {
|
|
31120
|
+
sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: "projectRoot must be an absolute path" });
|
|
31121
|
+
return;
|
|
31122
|
+
}
|
|
31123
|
+
if (!force) {
|
|
31124
|
+
const key2 = projectRuntimeKey(projectRoot);
|
|
31125
|
+
const existing2 = contexts.get(key2);
|
|
31126
|
+
let openRunIds = [];
|
|
31127
|
+
if (existing2) {
|
|
31128
|
+
openRunIds = unsettledRunIds(existing2);
|
|
31129
|
+
} else {
|
|
31130
|
+
const registration = registry.projectByRoot(projectRoot);
|
|
31131
|
+
const eventsPath = join18(paths.projectsDir, registration?.projectKey ?? projectRuntimeKey(projectRoot), "run-events.db");
|
|
31132
|
+
if (registration && existsSync16(eventsPath)) {
|
|
31133
|
+
const store = new KxmRunEventStore(eventsPath);
|
|
31134
|
+
try {
|
|
31135
|
+
openRunIds = store.runsForProject(registration.projectId, 1e4).filter((run) => !SETTLED_RUN_STATUS.has(run.status)).map((run) => run.runId);
|
|
31136
|
+
} finally {
|
|
31137
|
+
store.close();
|
|
31138
|
+
}
|
|
31139
|
+
}
|
|
31140
|
+
}
|
|
31141
|
+
if (openRunIds.length > 0) {
|
|
31142
|
+
sendJson(response, 409, {
|
|
31143
|
+
ok: false,
|
|
31144
|
+
error: "runtime_project_busy",
|
|
31145
|
+
message: `unsettled run ${openRunIds.join(", ")}`,
|
|
31146
|
+
runIds: openRunIds
|
|
31147
|
+
});
|
|
31148
|
+
return;
|
|
31149
|
+
}
|
|
31150
|
+
const laneCount = registry.homeLaneCount(projectRoot);
|
|
31151
|
+
if (laneCount > 0) {
|
|
31152
|
+
sendJson(response, 409, {
|
|
31153
|
+
ok: false,
|
|
31154
|
+
error: "runtime_project_has_lanes",
|
|
31155
|
+
message: `home root still has ${laneCount} lane(s)`
|
|
31156
|
+
});
|
|
31157
|
+
return;
|
|
31158
|
+
}
|
|
31159
|
+
}
|
|
31160
|
+
const key = projectRuntimeKey(projectRoot);
|
|
31161
|
+
const existing = contexts.get(key);
|
|
31162
|
+
const unregistered = registry.unregisterProject(projectRoot);
|
|
31163
|
+
if (existing) {
|
|
31164
|
+
contexts.delete(key);
|
|
31165
|
+
syncStatuses.delete(key);
|
|
31166
|
+
closeKxmRuntimeContext(existing);
|
|
31167
|
+
}
|
|
31168
|
+
sendJson(response, 200, { ok: true, unregistered });
|
|
31169
|
+
return;
|
|
31170
|
+
}
|
|
30820
31171
|
if (request.method === "POST" && url.pathname === "/v1/runs") {
|
|
30821
31172
|
const body = await readJsonBody(request);
|
|
30822
31173
|
const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
|
|
@@ -31040,7 +31391,10 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
31040
31391
|
sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: `project ${requestedProjectId} is not the bound project ${context.projectId}` });
|
|
31041
31392
|
return;
|
|
31042
31393
|
}
|
|
31043
|
-
const
|
|
31394
|
+
const limitText = url.searchParams.get("limit");
|
|
31395
|
+
const parsedLimit = limitText === null ? 50 : Number(limitText);
|
|
31396
|
+
const limit = Number.isInteger(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 1e4) : 50;
|
|
31397
|
+
const runs = context.eventStore.runsForProject(requestedProjectId, limit).map((stored) => {
|
|
31044
31398
|
try {
|
|
31045
31399
|
return projectKxmRunReadOnly(context, stored.runId);
|
|
31046
31400
|
} catch (error) {
|
|
@@ -31314,6 +31668,7 @@ export {
|
|
|
31314
31668
|
hashKxmSupervisorToken,
|
|
31315
31669
|
hashKxmTokenProof,
|
|
31316
31670
|
kxmRuntimeRequest,
|
|
31671
|
+
kxmSupervisorRecordIsLive,
|
|
31317
31672
|
kxmSupervisorStatus,
|
|
31318
31673
|
kxmSupervisorTokenFile,
|
|
31319
31674
|
readKxmSupervisorToken,
|