@kontextmind/kxm 0.7.127 → 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.
@@ -18414,9 +18414,10 @@ function legacyConfigFilesAt(root) {
18414
18414
  }
18415
18415
 
18416
18416
  // plugins/kxm/src/runtime-store.ts
18417
+ import { spawnSync as spawnSync3 } from "node:child_process";
18417
18418
  import { createHash as createHash7, randomUUID } from "node:crypto";
18418
- import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
18419
- import { dirname as dirname5, resolve as resolve6 } from "node:path";
18419
+ import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync3, statSync, writeFileSync as writeFileSync2 } from "node:fs";
18420
+ import { dirname as dirname5, isAbsolute as isAbsolute4, resolve as resolve6 } from "node:path";
18420
18421
 
18421
18422
  // plugins/kxm/src/sqlite.ts
18422
18423
  import { existsSync as existsSync4 } from "node:fs";
@@ -18934,22 +18935,25 @@ function ensureWalJournalMode(database, file, description, timeoutMs = 5e3) {
18934
18935
  Atomics.wait(sleeper, 0, 0, 10);
18935
18936
  }
18936
18937
  }
18938
+ function assertDatabaseFile(file, description) {
18939
+ checkedParent(file, description);
18940
+ const stat = lstatSync2(file, { throwIfNoEntry: false });
18941
+ if (stat) {
18942
+ if (stat.isSymbolicLink() || !stat.isFile()) {
18943
+ throw databaseError("runtime_path_invalid", description, `${description} must be a regular file, not a link or directory`);
18944
+ }
18945
+ }
18946
+ for (const sidecar of [`${file}-wal`, `${file}-shm`]) {
18947
+ const info = lstatSync2(sidecar, { throwIfNoEntry: false });
18948
+ if (info?.isSymbolicLink()) {
18949
+ throw databaseError("runtime_path_invalid", description, `${description} sidecar must not be a link`);
18950
+ }
18951
+ }
18952
+ }
18937
18953
  function openDatabase(file, description, spec) {
18938
18954
  const isMemory = file === ":memory:";
18939
18955
  if (!isMemory) {
18940
- checkedParent(file, description);
18941
- const stat = lstatSync2(file, { throwIfNoEntry: false });
18942
- if (stat) {
18943
- if (stat.isSymbolicLink() || !stat.isFile()) {
18944
- throw databaseError("runtime_path_invalid", description, `${description} must be a regular file, not a link or directory`);
18945
- }
18946
- }
18947
- for (const sidecar of [`${file}-wal`, `${file}-shm`]) {
18948
- const info = lstatSync2(sidecar, { throwIfNoEntry: false });
18949
- if (info?.isSymbolicLink()) {
18950
- throw databaseError("runtime_path_invalid", description, `${description} sidecar must not be a link`);
18951
- }
18952
- }
18956
+ assertDatabaseFile(file, description);
18953
18957
  }
18954
18958
  const database = new DatabaseSync(file);
18955
18959
  let transaction = false;
@@ -18978,7 +18982,7 @@ function openDatabase(file, description, spec) {
18978
18982
  throw databaseError(
18979
18983
  "runtime_schema_outdated",
18980
18984
  file,
18981
- `${description} is schema version ${version}; this build requires ${spec.version}. Delete the state file to start fresh and let its owning process recreate it (\`kxm hub start\` for hub state, the Runtime for registry/event stores); \`kxm init\` is project-only and rebuilds no database \u2014 upgrading old state in place is deliberately unsupported`
18985
+ `${description} is schema version ${version}; this build requires ${spec.version}. Delete the state file to start fresh and let its owning process recreate it (\`kxm hub start\` for hub state, the Runtime for event stores). The Runtime registry copy from schema 1 to 2 is the one exception. \`kxm init\` is project-only and rebuilds no database. Upgrading any other old state in place is deliberately unsupported`
18982
18986
  );
18983
18987
  }
18984
18988
  if (spec.tables) {
@@ -19216,7 +19220,7 @@ function restoreDatabaseFile(backupPath, targetPath, storeId, expectedSchemaVers
19216
19220
  }
19217
19221
  var KXM_BACKUP_CEILINGS = {
19218
19222
  "hub-store": 5,
19219
- registry: 1,
19223
+ registry: 2,
19220
19224
  "binding-store": 1,
19221
19225
  events: 7
19222
19226
  };
@@ -19582,10 +19586,10 @@ function projectRuntimeOwnsRun(projectRoot, runId, env) {
19582
19586
  database.close();
19583
19587
  }
19584
19588
  }
19585
- var KXM_REGISTRY_SCHEMA_VERSION = 1;
19589
+ var KXM_REGISTRY_SCHEMA_VERSION = 2;
19586
19590
  var REGISTRY_TABLES = {
19587
19591
  supervisor: ["singleton_id", "runtime_id", "pid", "port", "token_hash", "started_at", "heartbeat_at", "state"],
19588
- projects: ["project_id", "project_root", "project_key", "home_runtime_id", "config_revision", "registered_at"]
19592
+ projects: ["project_id", "project_root", "project_key", "home_runtime_id", "config_revision", "registered_at", "lane_of"]
19589
19593
  };
19590
19594
  var REGISTRY_SCHEMA = `
19591
19595
  CREATE TABLE supervisor (
@@ -19599,14 +19603,117 @@ CREATE TABLE supervisor (
19599
19603
  state TEXT NOT NULL
19600
19604
  ) STRICT;
19601
19605
  CREATE TABLE projects (
19602
- project_id TEXT PRIMARY KEY,
19606
+ project_key TEXT PRIMARY KEY,
19607
+ project_id TEXT NOT NULL,
19603
19608
  project_root TEXT NOT NULL,
19604
- project_key TEXT NOT NULL UNIQUE,
19605
19609
  home_runtime_id TEXT NOT NULL,
19606
19610
  config_revision TEXT,
19607
- registered_at TEXT NOT NULL
19611
+ registered_at TEXT NOT NULL,
19612
+ lane_of TEXT
19608
19613
  ) STRICT;
19614
+ CREATE INDEX projects_by_project_id ON projects (project_id);
19609
19615
  `;
19616
+ var PROJECT_COLUMNS = "project_id, project_root, project_key, home_runtime_id, config_revision, registered_at, lane_of";
19617
+ function registrationFromRow(row) {
19618
+ return {
19619
+ projectId: row.project_id,
19620
+ projectRoot: row.project_root,
19621
+ projectKey: row.project_key,
19622
+ homeRuntimeId: row.home_runtime_id,
19623
+ ...row.config_revision !== null ? { configRevision: row.config_revision } : {},
19624
+ ...row.lane_of !== null ? { laneOf: row.lane_of } : {},
19625
+ registeredAt: row.registered_at
19626
+ };
19627
+ }
19628
+ function controlRootDirectoryExists(projectRoot) {
19629
+ const stat = statSync(projectRoot, { throwIfNoEntry: false });
19630
+ return Boolean(stat?.isDirectory());
19631
+ }
19632
+ function gitEnv() {
19633
+ return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
19634
+ }
19635
+ function gitCommonDirectory(projectRoot) {
19636
+ const result = spawnSync3("git", ["-C", projectRoot, "rev-parse", "--git-common-dir"], {
19637
+ encoding: "utf8",
19638
+ env: gitEnv(),
19639
+ timeout: 3e4,
19640
+ windowsHide: true
19641
+ });
19642
+ if (result.status !== 0) return void 0;
19643
+ const text = (result.stdout ?? "").trim();
19644
+ if (!text) return void 0;
19645
+ const absolute = isAbsolute4(text) ? text : resolve6(projectRoot, text);
19646
+ let canonical;
19647
+ try {
19648
+ canonical = realpathSync3.native(absolute);
19649
+ } catch {
19650
+ canonical = resolve6(absolute);
19651
+ }
19652
+ return process.platform === "win32" ? canonical.toLocaleLowerCase("en-US") : canonical;
19653
+ }
19654
+ function isPrimaryWorktree(projectRoot, commonDir) {
19655
+ if (commonDir === void 0) return false;
19656
+ const dotGit = resolve6(projectRoot, ".git");
19657
+ let canonical;
19658
+ try {
19659
+ canonical = realpathSync3.native(dotGit);
19660
+ } catch {
19661
+ canonical = resolve6(dotGit);
19662
+ }
19663
+ if (process.platform === "win32") canonical = canonical.toLocaleLowerCase("en-US");
19664
+ return canonical === commonDir;
19665
+ }
19666
+ function migrateRegistryLanes(file) {
19667
+ const stat = lstatSync3(file, { throwIfNoEntry: false });
19668
+ if (!stat) return;
19669
+ assertDatabaseFile(file, "runtime registry");
19670
+ const database = new DatabaseSync(file);
19671
+ let transaction = false;
19672
+ try {
19673
+ database.exec("PRAGMA busy_timeout = 5000");
19674
+ const current = database.prepare("PRAGMA user_version").get();
19675
+ if ((current?.user_version ?? 0) !== 1) return;
19676
+ database.exec("BEGIN IMMEDIATE");
19677
+ transaction = true;
19678
+ const locked = database.prepare("PRAGMA user_version").get();
19679
+ if ((locked?.user_version ?? 0) !== 1) {
19680
+ database.exec("ROLLBACK");
19681
+ transaction = false;
19682
+ return;
19683
+ }
19684
+ if (!tableColumns(database, "projects").includes("lane_of")) {
19685
+ database.exec(`
19686
+ CREATE TABLE projects_v2 (
19687
+ project_key TEXT PRIMARY KEY,
19688
+ project_id TEXT NOT NULL,
19689
+ project_root TEXT NOT NULL,
19690
+ home_runtime_id TEXT NOT NULL,
19691
+ config_revision TEXT,
19692
+ registered_at TEXT NOT NULL,
19693
+ lane_of TEXT
19694
+ ) STRICT;
19695
+ INSERT INTO projects_v2 (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
19696
+ SELECT project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, NULL FROM projects;
19697
+ DROP TABLE projects;
19698
+ ALTER TABLE projects_v2 RENAME TO projects;
19699
+ CREATE INDEX projects_by_project_id ON projects (project_id);
19700
+ `);
19701
+ }
19702
+ database.exec("PRAGMA user_version = 2");
19703
+ database.exec("COMMIT");
19704
+ transaction = false;
19705
+ } catch (error) {
19706
+ if (transaction) {
19707
+ try {
19708
+ database.exec("ROLLBACK");
19709
+ } catch {
19710
+ }
19711
+ }
19712
+ throw error;
19713
+ } finally {
19714
+ database.close();
19715
+ }
19716
+ }
19610
19717
  function readKxmSupervisorRecord(database) {
19611
19718
  const row = database.prepare("SELECT runtime_id, pid, port, token_hash, started_at, heartbeat_at, state FROM supervisor WHERE singleton_id = 1").get();
19612
19719
  return row ? {
@@ -19624,6 +19731,7 @@ var KxmRuntimeRegistry = class {
19624
19731
  database;
19625
19732
  constructor(path) {
19626
19733
  this.path = resolve6(path);
19734
+ migrateRegistryLanes(this.path);
19627
19735
  this.database = openDatabase(this.path, "runtime registry", {
19628
19736
  schema: REGISTRY_SCHEMA,
19629
19737
  version: KXM_REGISTRY_SCHEMA_VERSION,
@@ -19702,57 +19810,188 @@ var KxmRuntimeRegistry = class {
19702
19810
  supervisor() {
19703
19811
  return this.readSupervisorRow();
19704
19812
  }
19705
- /** Register or revalidate a project's home binding. Home Runtime is immutable. */
19706
- /** All projects registered to this Runtime, for restart recovery: the
19707
- * supervisor needs to reopen their contexts so pending outbox rows resume
19708
- * syncing and presence keeps beating. */
19813
+ /** All projects registered to this Runtime, including lane roots, for restart
19814
+ * recovery: the supervisor reopens their contexts so pending outbox rows
19815
+ * resume syncing and presence keeps beating. */
19709
19816
  projectsForRuntime(homeRuntimeId) {
19710
19817
  const rows = this.database.prepare(
19711
19818
  "SELECT project_root, project_id FROM projects WHERE home_runtime_id = ? ORDER BY registered_at"
19712
19819
  ).all(homeRuntimeId);
19713
19820
  return rows.map((row) => ({ projectRoot: row.project_root, projectId: row.project_id }));
19714
19821
  }
19822
+ /**
19823
+ * Register or revalidate a control root. The home runtime of a live row is
19824
+ * immutable. A second root whose git common directory matches a live row
19825
+ * for the same project is a lane, unless the registering root is the primary
19826
+ * worktree and the live home row is not: then the primary becomes the home
19827
+ * row (same home runtime id) and every other live row of that repository
19828
+ * becomes its lane. A primary never becomes a lane. A root whose directory
19829
+ * is gone is replaced. A live root that is a different repository is
19830
+ * `project_home_conflict`.
19831
+ */
19715
19832
  registerProject(registration) {
19716
19833
  const projectRoot = resolve6(registration.projectRoot);
19717
19834
  const projectKey = projectRuntimeKey(projectRoot);
19835
+ const candidateCommon = gitCommonDirectory(projectRoot);
19836
+ const commonByRoot = /* @__PURE__ */ new Map();
19837
+ for (let attempt = 0; attempt < 8; attempt += 1) {
19838
+ this.database.exec("BEGIN IMMEDIATE");
19839
+ let rootsToMeasure;
19840
+ try {
19841
+ const byKey = this.projectRow("SELECT " + PROJECT_COLUMNS + " FROM projects WHERE project_key = ?", projectKey);
19842
+ if (byKey) {
19843
+ const problems = [];
19844
+ if (byKey.project_id !== registration.projectId) problems.push(`control root is already bound to a different project id ${byKey.project_id}`);
19845
+ if (byKey.home_runtime_id !== registration.homeRuntimeId) problems.push(`project ${registration.projectId} home runtime is immutable and cannot be rebound`);
19846
+ if (problems.length > 0) {
19847
+ this.database.exec("ROLLBACK");
19848
+ throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
19849
+ }
19850
+ const result = registrationFromRow(byKey);
19851
+ this.database.exec("COMMIT");
19852
+ return result;
19853
+ }
19854
+ const rows = this.database.prepare(
19855
+ `SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_id = ? ORDER BY registered_at`
19856
+ ).all(registration.projectId);
19857
+ const live = rows.filter((row) => controlRootDirectoryExists(row.project_root));
19858
+ const dead = rows.filter((row) => !controlRootDirectoryExists(row.project_root));
19859
+ const unknown = live.filter((row) => !commonByRoot.has(row.project_root));
19860
+ if (unknown.length > 0) {
19861
+ rootsToMeasure = unknown.map((row) => row.project_root);
19862
+ this.database.exec("ROLLBACK");
19863
+ } else if (live.length > 0) {
19864
+ const anchor = live.find((row) => {
19865
+ const dir = commonByRoot.get(row.project_root);
19866
+ return candidateCommon !== void 0 && dir === candidateCommon;
19867
+ });
19868
+ if (!anchor) {
19869
+ this.database.exec("ROLLBACK");
19870
+ throw runtimeError(
19871
+ "project_home_conflict",
19872
+ ".kxm/project.yaml",
19873
+ `project ${registration.projectId} is already bound to a different control root`
19874
+ );
19875
+ }
19876
+ if (live.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
19877
+ this.database.exec("ROLLBACK");
19878
+ throw runtimeError(
19879
+ "project_home_conflict",
19880
+ ".kxm/project.yaml",
19881
+ `project ${registration.projectId} home runtime is immutable and cannot be rebound`
19882
+ );
19883
+ }
19884
+ const sameRepo = live.filter((row) => commonByRoot.get(row.project_root) === candidateCommon);
19885
+ const home = sameRepo.find((row) => row.lane_of === null);
19886
+ const registeringIsPrimary = isPrimaryWorktree(projectRoot, candidateCommon);
19887
+ const homeIsPrimary = home !== void 0 && isPrimaryWorktree(home.project_root, commonByRoot.get(home.project_root));
19888
+ if (registeringIsPrimary && !homeIsPrimary) {
19889
+ this.insertHome(registration, projectRoot, projectKey);
19890
+ for (const row of sameRepo) {
19891
+ this.database.prepare("UPDATE projects SET lane_of = ? WHERE project_key = ?").run(projectKey, row.project_key);
19892
+ }
19893
+ this.database.exec("COMMIT");
19894
+ registration.logger?.({
19895
+ event: "project_home_promoted",
19896
+ projectId: registration.projectId,
19897
+ projectRoot,
19898
+ homeRuntimeId: registration.homeRuntimeId,
19899
+ previousHomeRoot: home?.project_root ?? null,
19900
+ previousHomeKey: home?.project_key ?? null
19901
+ });
19902
+ return this.insertedRegistration(registration, projectRoot, projectKey);
19903
+ }
19904
+ if (registeringIsPrimary) {
19905
+ this.database.exec("ROLLBACK");
19906
+ throw runtimeError(
19907
+ "project_home_conflict",
19908
+ ".kxm/project.yaml",
19909
+ `project ${registration.projectId} primary checkout is already the home row`
19910
+ );
19911
+ }
19912
+ const laneOf = anchor.lane_of ?? anchor.project_key;
19913
+ this.database.prepare(`
19914
+ INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
19915
+ VALUES (?, ?, ?, ?, ?, ?, ?)
19916
+ `).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now, laneOf);
19917
+ const result = this.insertedRegistration(registration, projectRoot, projectKey, laneOf);
19918
+ this.database.exec("COMMIT");
19919
+ return result;
19920
+ } else if (dead.length > 0) {
19921
+ const replaced = dead[0];
19922
+ if (dead.some((row) => row.home_runtime_id !== registration.homeRuntimeId)) {
19923
+ this.database.exec("ROLLBACK");
19924
+ throw runtimeError(
19925
+ "project_home_conflict",
19926
+ ".kxm/project.yaml",
19927
+ `project ${registration.projectId} home runtime is immutable and cannot be rebound`
19928
+ );
19929
+ }
19930
+ this.database.prepare("DELETE FROM projects WHERE project_id = ?").run(registration.projectId);
19931
+ this.insertHome(registration, projectRoot, projectKey);
19932
+ this.database.exec("COMMIT");
19933
+ registration.logger?.({
19934
+ event: "project_registration_replaced",
19935
+ projectId: registration.projectId,
19936
+ projectRoot,
19937
+ replacedRoot: replaced.project_root,
19938
+ replacedCount: dead.length,
19939
+ replacedHomeRuntimeId: replaced.home_runtime_id,
19940
+ replacedProjectKey: replaced.project_key
19941
+ });
19942
+ return this.insertedRegistration(registration, projectRoot, projectKey);
19943
+ } else {
19944
+ this.insertHome(registration, projectRoot, projectKey);
19945
+ this.database.exec("COMMIT");
19946
+ return this.insertedRegistration(registration, projectRoot, projectKey);
19947
+ }
19948
+ } catch (error) {
19949
+ try {
19950
+ this.database.exec("ROLLBACK");
19951
+ } catch {
19952
+ }
19953
+ throw error;
19954
+ }
19955
+ for (const root of rootsToMeasure ?? []) {
19956
+ commonByRoot.set(root, gitCommonDirectory(root));
19957
+ }
19958
+ }
19959
+ throw runtimeError(
19960
+ "runtime_registry_busy",
19961
+ projectRoot,
19962
+ `project ${registration.projectId} registration could not be rechecked`
19963
+ );
19964
+ }
19965
+ /** Remove one control root. Returns false when that root was not registered. */
19966
+ unregisterProject(projectRoot) {
19718
19967
  this.database.exec("BEGIN IMMEDIATE");
19719
19968
  try {
19720
- const byId = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_id = ?").get(registration.projectId);
19721
- const byKey = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_key = ?").get(projectKey);
19722
- const existing = byId ?? byKey;
19723
- if (existing) {
19724
- const problems = [];
19725
- if (byId && byId.project_key !== projectKey) problems.push(`project ${registration.projectId} is already bound to a different control root`);
19726
- if (byKey && byKey.project_id !== registration.projectId) problems.push(`control root is already bound to a different project id ${byKey.project_id}`);
19727
- if (existing.home_runtime_id !== registration.homeRuntimeId) problems.push(`project ${registration.projectId} home runtime is immutable and cannot be rebound`);
19728
- if (problems.length > 0) {
19729
- this.database.exec("ROLLBACK");
19730
- throw runtimeError("project_home_conflict", ".kxm/project.yaml", problems.join("; "));
19731
- }
19732
- const result = {
19733
- projectId: existing.project_id,
19734
- projectRoot: existing.project_root,
19735
- projectKey: existing.project_key,
19736
- homeRuntimeId: existing.home_runtime_id,
19737
- ...existing.config_revision !== null ? { configRevision: existing.config_revision } : {},
19738
- registeredAt: existing.registered_at
19739
- };
19740
- this.database.exec("COMMIT");
19741
- return result;
19969
+ const removed = this.deleteRegisteredRoot(projectRoot);
19970
+ this.database.exec("COMMIT");
19971
+ return removed;
19972
+ } catch (error) {
19973
+ try {
19974
+ this.database.exec("ROLLBACK");
19975
+ } catch {
19742
19976
  }
19743
- this.database.prepare(`
19744
- INSERT INTO projects (project_id, project_root, project_key, home_runtime_id, config_revision, registered_at)
19745
- VALUES (?, ?, ?, ?, ?, ?)
19746
- `).run(registration.projectId, projectRoot, projectKey, registration.homeRuntimeId, registration.configRevision ?? null, registration.now);
19977
+ throw error;
19978
+ }
19979
+ }
19980
+ /**
19981
+ * Delete the row only when `supervisorIsLive` is false for the supervisor
19982
+ * row read inside this write transaction. A live supervisor keeps the row;
19983
+ * the caller uses the supervisor route instead of editing the file.
19984
+ */
19985
+ unregisterProjectIfIdle(projectRoot, supervisorIsLive) {
19986
+ this.database.exec("BEGIN IMMEDIATE");
19987
+ try {
19988
+ if (supervisorIsLive(this.readSupervisorRow())) {
19989
+ this.database.exec("ROLLBACK");
19990
+ return "supervisor_live";
19991
+ }
19992
+ const removed = this.deleteRegisteredRoot(projectRoot);
19747
19993
  this.database.exec("COMMIT");
19748
- return {
19749
- projectId: registration.projectId,
19750
- projectRoot,
19751
- projectKey,
19752
- homeRuntimeId: registration.homeRuntimeId,
19753
- ...registration.configRevision !== void 0 ? { configRevision: registration.configRevision } : {},
19754
- registeredAt: registration.now
19755
- };
19994
+ return removed ? "removed" : "absent";
19756
19995
  } catch (error) {
19757
19996
  try {
19758
19997
  this.database.exec("ROLLBACK");
@@ -19761,28 +20000,65 @@ var KxmRuntimeRegistry = class {
19761
20000
  throw error;
19762
20001
  }
19763
20002
  }
20003
+ /** How many other rows share this home root's project. Zero when this root is a lane or missing. */
20004
+ homeLaneCount(projectRoot) {
20005
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(resolve6(projectRoot)));
20006
+ if (!row || row.lane_of !== null) return 0;
20007
+ const count = this.database.prepare(
20008
+ "SELECT COUNT(*) AS total FROM projects WHERE project_id = ? AND project_key != ?"
20009
+ ).get(row.project_id, row.project_key);
20010
+ return Number(count?.total ?? 0);
20011
+ }
20012
+ /** Caller holds the write transaction. Does not commit. */
20013
+ deleteRegisteredRoot(projectRoot) {
20014
+ const projectKey = projectRuntimeKey(resolve6(projectRoot));
20015
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectKey);
20016
+ if (!row) return false;
20017
+ this.database.prepare("DELETE FROM projects WHERE project_key = ?").run(projectKey);
20018
+ if (row.lane_of === null) {
20019
+ const successor = this.database.prepare(
20020
+ "SELECT project_key FROM projects WHERE project_id = ? ORDER BY registered_at LIMIT 1"
20021
+ ).get(row.project_id);
20022
+ if (successor) {
20023
+ this.database.prepare("UPDATE projects SET lane_of = NULL WHERE project_key = ?").run(successor.project_key);
20024
+ this.database.prepare(
20025
+ "UPDATE projects SET lane_of = ? WHERE project_id = ? AND project_key != ? AND (lane_of IS NULL OR lane_of = ?)"
20026
+ ).run(successor.project_key, row.project_id, successor.project_key, projectKey);
20027
+ }
20028
+ }
20029
+ return true;
20030
+ }
20031
+ /** The home row for a project id, or the earliest row when every row is a lane. */
19764
20032
  project(projectId) {
19765
- const row = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_id = ?").get(projectId);
19766
- return row ? {
19767
- projectId: row.project_id,
19768
- projectRoot: row.project_root,
19769
- projectKey: row.project_key,
19770
- homeRuntimeId: row.home_runtime_id,
19771
- ...row.config_revision !== null ? { configRevision: row.config_revision } : {},
19772
- registeredAt: row.registered_at
19773
- } : void 0;
20033
+ const row = this.projectRow(
20034
+ `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`,
20035
+ projectId
20036
+ );
20037
+ return row ? registrationFromRow(row) : void 0;
19774
20038
  }
19775
20039
  projectByRoot(projectRoot) {
19776
- const key = projectRuntimeKey(projectRoot);
19777
- const row = this.database.prepare("SELECT project_id, project_root, project_key, home_runtime_id, config_revision, registered_at FROM projects WHERE project_key = ?").get(key);
19778
- return row ? {
19779
- projectId: row.project_id,
19780
- projectRoot: row.project_root,
19781
- projectKey: row.project_key,
19782
- homeRuntimeId: row.home_runtime_id,
19783
- ...row.config_revision !== null ? { configRevision: row.config_revision } : {},
19784
- registeredAt: row.registered_at
19785
- } : void 0;
20040
+ const row = this.projectRow(`SELECT ${PROJECT_COLUMNS} FROM projects WHERE project_key = ?`, projectRuntimeKey(projectRoot));
20041
+ return row ? registrationFromRow(row) : void 0;
20042
+ }
20043
+ projectRow(sql, parameter) {
20044
+ return this.database.prepare(sql).get(parameter);
20045
+ }
20046
+ insertHome(registration, projectRoot, projectKey) {
20047
+ this.database.prepare(`
20048
+ INSERT INTO projects (project_key, project_id, project_root, home_runtime_id, config_revision, registered_at, lane_of)
20049
+ VALUES (?, ?, ?, ?, ?, ?, NULL)
20050
+ `).run(projectKey, registration.projectId, projectRoot, registration.homeRuntimeId, registration.configRevision ?? null, registration.now);
20051
+ }
20052
+ insertedRegistration(registration, projectRoot, projectKey, laneOf) {
20053
+ return {
20054
+ projectId: registration.projectId,
20055
+ projectRoot,
20056
+ projectKey,
20057
+ homeRuntimeId: registration.homeRuntimeId,
20058
+ ...registration.configRevision !== void 0 ? { configRevision: registration.configRevision } : {},
20059
+ ...laneOf !== void 0 ? { laneOf } : {},
20060
+ registeredAt: registration.now
20061
+ };
19786
20062
  }
19787
20063
  };
19788
20064
  var KXM_RUN_EVENT_SCHEMA = "kxm.run-event.v1";
@@ -24228,7 +24504,8 @@ function openKxmRuntimeContext(projectRoot, options) {
24228
24504
  projectRoot: bundle.projectRoot,
24229
24505
  homeRuntimeId: options.homeRuntimeId,
24230
24506
  configRevision: bundle.configRevision,
24231
- now: options.now ?? (/* @__PURE__ */ new Date()).toISOString()
24507
+ now: options.now ?? (/* @__PURE__ */ new Date()).toISOString(),
24508
+ ...options.logger ? { logger: options.logger } : {}
24232
24509
  });
24233
24510
  const eventStore = new KxmRunEventStore(join7(paths.projectsDir, registration.projectKey, "run-events.db"));
24234
24511
  registerKxmRuntimeHandle(eventStore.path);
@@ -24582,7 +24859,7 @@ import { spawn as spawn3 } from "node:child_process";
24582
24859
  import { createHash as createHash16, createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
24583
24860
  import { chmodSync as chmodSync2, existsSync as existsSync16, lstatSync as lstatSync6, mkdirSync as mkdirSync9, readFileSync as readFileSync13, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
24584
24861
  import { createServer } from "node:http";
24585
- import { dirname as dirname11, isAbsolute as isAbsolute7, join as join18 } from "node:path";
24862
+ import { dirname as dirname11, isAbsolute as isAbsolute8, join as join18 } from "node:path";
24586
24863
 
24587
24864
  // plugins/kxm/src/oneshot-evidence.ts
24588
24865
  import { createHash as createHash10, randomUUID as randomUUID3 } from "node:crypto";
@@ -24737,7 +25014,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
24737
25014
  // plugins/kxm/src/prices.ts
24738
25015
  var import_yaml3 = __toESM(require_dist(), 1);
24739
25016
  import { createHash as createHash11 } from "node:crypto";
24740
- import { existsSync as existsSync8, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync3 } from "node:fs";
25017
+ import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
24741
25018
  import { join as join9 } from "node:path";
24742
25019
 
24743
25020
  // plugins/kxm/src/price-calc.ts
@@ -24890,7 +25167,7 @@ function parsePriceCatalog(text) {
24890
25167
  return catalog;
24891
25168
  }
24892
25169
  function loadPriceCatalog(rootOrPath) {
24893
- const candidatePath = existsSync8(join9(rootOrPath, ".kxm", "prices.yaml")) ? join9(rootOrPath, ".kxm", "prices.yaml") : existsSync8(join9(rootOrPath, "prices.yaml")) ? join9(rootOrPath, "prices.yaml") : existsSync8(rootOrPath) && statSync(rootOrPath).isFile() ? rootOrPath : void 0;
25170
+ const candidatePath = existsSync8(join9(rootOrPath, ".kxm", "prices.yaml")) ? join9(rootOrPath, ".kxm", "prices.yaml") : existsSync8(join9(rootOrPath, "prices.yaml")) ? join9(rootOrPath, "prices.yaml") : existsSync8(rootOrPath) && statSync2(rootOrPath).isFile() ? rootOrPath : void 0;
24894
25171
  if (!candidatePath || !existsSync8(candidatePath)) {
24895
25172
  return void 0;
24896
25173
  }
@@ -24955,9 +25232,9 @@ function isRouteAdmitted(root, modelId) {
24955
25232
  }
24956
25233
 
24957
25234
  // plugins/kxm/src/worktree-witness.ts
24958
- import { spawnSync as spawnSync3 } from "node:child_process";
25235
+ import { spawnSync as spawnSync4 } from "node:child_process";
24959
25236
  function gitText(cwd, args) {
24960
- const result = spawnSync3("git", ["-C", cwd, ...args], {
25237
+ const result = spawnSync4("git", ["-C", cwd, ...args], {
24961
25238
  encoding: "utf8",
24962
25239
  windowsHide: true,
24963
25240
  timeout: 15e3
@@ -25270,7 +25547,7 @@ function pruneContextPacket(packet, tokenBudget) {
25270
25547
  }
25271
25548
 
25272
25549
  // plugins/kxm/src/dispatch-context.ts
25273
- import { spawnSync as spawnSync4 } from "node:child_process";
25550
+ import { spawnSync as spawnSync5 } from "node:child_process";
25274
25551
  import { readdirSync as readdirSync8 } from "node:fs";
25275
25552
  import { extname as extname3, join as join13 } from "node:path";
25276
25553
 
@@ -26033,7 +26310,7 @@ function loadAuthoredMemory(repoRoot) {
26033
26310
  // plugins/kxm/src/skills.ts
26034
26311
  var import_yaml8 = __toESM(require_dist(), 1);
26035
26312
  import { createHash as createHash12 } from "node:crypto";
26036
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, readFileSync as readFileSync9, renameSync, rmSync, statSync as statSync2, writeFileSync as writeFileSync6 } from "node:fs";
26313
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, readFileSync as readFileSync9, renameSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync6 } from "node:fs";
26037
26314
  import { dirname as dirname7, join as join12 } from "node:path";
26038
26315
  var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
26039
26316
  var SKILL_EVALUATION_SCHEMA = "kxm.skill-evaluation.v1";
@@ -26413,7 +26690,7 @@ function boundedList(value, field) {
26413
26690
  return [...new Set(refs)];
26414
26691
  }
26415
26692
  function readdirSorted(dir) {
26416
- return readdirSync7(dir).filter((entry) => statSync2(join12(dir, entry)).isDirectory()).sort();
26693
+ return readdirSync7(dir).filter((entry) => statSync3(join12(dir, entry)).isDirectory()).sort();
26417
26694
  }
26418
26695
 
26419
26696
  // plugins/kxm/src/dispatch-context.ts
@@ -26474,7 +26751,7 @@ function gitEnvironment2() {
26474
26751
  return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
26475
26752
  }
26476
26753
  function committedGateGap(projectRoot) {
26477
- const result = spawnSync4("git", [
26754
+ const result = spawnSync5("git", [
26478
26755
  "--no-optional-locks",
26479
26756
  "-C",
26480
26757
  projectRoot,
@@ -26616,11 +26893,11 @@ function assembleDispatchContext(sources, input) {
26616
26893
  import { join as join14 } from "node:path";
26617
26894
 
26618
26895
  // plugins/kxm/src/artifacts-exist.ts
26619
- import { lstatSync as lstatSync5, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
26620
- import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve8 } from "node:path";
26896
+ import { lstatSync as lstatSync5, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
26897
+ import { isAbsolute as isAbsolute5, relative as relative4, resolve as resolve8 } from "node:path";
26621
26898
  function staysUnder(root, candidate) {
26622
26899
  const child = relative4(root, candidate);
26623
- return child === "" || !isAbsolute4(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
26900
+ return child === "" || !isAbsolute5(child) && child !== ".." && !child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
26624
26901
  }
26625
26902
  function verifyArtifactExists(rootInput, pathInput) {
26626
26903
  const lexicalRoot = resolve8(rootInput);
@@ -26631,12 +26908,12 @@ function verifyArtifactExists(rootInput, pathInput) {
26631
26908
  let realRoot;
26632
26909
  let realPath;
26633
26910
  try {
26634
- realRoot = realpathSync3(lexicalRoot);
26911
+ realRoot = realpathSync4(lexicalRoot);
26635
26912
  } catch {
26636
26913
  return { ok: false, error: "artifact_unreadable", path: lexicalPath };
26637
26914
  }
26638
26915
  try {
26639
- realPath = realpathSync3(lexicalPath);
26916
+ realPath = realpathSync4(lexicalPath);
26640
26917
  } catch {
26641
26918
  return { ok: false, error: "artifact_missing", path: lexicalPath };
26642
26919
  }
@@ -26648,7 +26925,7 @@ function verifyArtifactExists(rootInput, pathInput) {
26648
26925
  if (link.isSymbolicLink()) {
26649
26926
  return { ok: false, error: "artifact_outside_workspace_assets", path: lexicalPath };
26650
26927
  }
26651
- const artifact = statSync3(realPath);
26928
+ const artifact = statSync4(realPath);
26652
26929
  if (!artifact.isFile()) return { ok: false, error: "artifact_not_file", path: lexicalPath };
26653
26930
  if (artifact.size < 1) return { ok: false, error: "artifact_empty", path: lexicalPath };
26654
26931
  return { ok: true, path: realPath, bytes: artifact.size };
@@ -30789,7 +31066,7 @@ var RuntimeHubClient = class {
30789
31066
  // plugins/kxm/src/hub-binding.ts
30790
31067
  import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
30791
31068
  import { homedir as homedir2 } from "node:os";
30792
- import { dirname as dirname8, isAbsolute as isAbsolute5, join as join16, resolve as resolve9 } from "node:path";
31069
+ import { dirname as dirname8, isAbsolute as isAbsolute6, join as join16, resolve as resolve9 } from "node:path";
30793
31070
  var HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
30794
31071
  var HubBindingError = class extends Error {
30795
31072
  constructor(message) {
@@ -30800,17 +31077,17 @@ var HubBindingError = class extends Error {
30800
31077
  function resolveUserStateRoot(env) {
30801
31078
  const explicit = env.KXM_STATE_HOME?.trim();
30802
31079
  if (explicit) {
30803
- if (!isAbsolute5(explicit)) throw new HubBindingError("local_state_root_not_absolute");
31080
+ if (!isAbsolute6(explicit)) throw new HubBindingError("local_state_root_not_absolute");
30804
31081
  return resolve9(explicit);
30805
31082
  }
30806
31083
  if (process.platform === "win32") {
30807
31084
  const localAppData = env.LOCALAPPDATA?.trim();
30808
- const base2 = localAppData && isAbsolute5(localAppData) ? localAppData : join16(homedir2(), "AppData", "Local");
31085
+ const base2 = localAppData && isAbsolute6(localAppData) ? localAppData : join16(homedir2(), "AppData", "Local");
30809
31086
  return resolve9(base2, "KXM");
30810
31087
  }
30811
31088
  if (process.platform === "darwin") return resolve9(homedir2(), "Library", "Application Support", "KXM");
30812
31089
  const xdgState = env.XDG_STATE_HOME?.trim();
30813
- const base = xdgState && isAbsolute5(xdgState) ? xdgState : join16(homedir2(), ".local", "state");
31090
+ const base = xdgState && isAbsolute6(xdgState) ? xdgState : join16(homedir2(), ".local", "state");
30814
31091
  return resolve9(base, "kxm");
30815
31092
  }
30816
31093
  function hubBindingFile(env = process.env) {
@@ -30861,7 +31138,7 @@ function readHubBinding(env = process.env) {
30861
31138
  // plugins/kxm/src/hub-env.ts
30862
31139
  import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync12, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
30863
31140
  import { homedir as homedir3 } from "node:os";
30864
- import { dirname as dirname9, isAbsolute as isAbsolute6, join as join17, resolve as resolve10 } from "node:path";
31141
+ import { dirname as dirname9, isAbsolute as isAbsolute7, join as join17, resolve as resolve10 } from "node:path";
30865
31142
  var HUB_ENV_SCHEMA = "kxm.hub-env.v1";
30866
31143
  var HubEnvError = class extends Error {
30867
31144
  constructor(message) {
@@ -30872,17 +31149,17 @@ var HubEnvError = class extends Error {
30872
31149
  function resolveUserStateRoot2(env) {
30873
31150
  const explicit = env.KXM_STATE_HOME?.trim();
30874
31151
  if (explicit) {
30875
- if (!isAbsolute6(explicit)) throw new HubEnvError("local_state_root_not_absolute");
31152
+ if (!isAbsolute7(explicit)) throw new HubEnvError("local_state_root_not_absolute");
30876
31153
  return resolve10(explicit);
30877
31154
  }
30878
31155
  if (process.platform === "win32") {
30879
31156
  const localAppData = env.LOCALAPPDATA?.trim();
30880
- const base2 = localAppData && isAbsolute6(localAppData) ? localAppData : join17(homedir3(), "AppData", "Local");
31157
+ const base2 = localAppData && isAbsolute7(localAppData) ? localAppData : join17(homedir3(), "AppData", "Local");
30881
31158
  return resolve10(base2, "KXM");
30882
31159
  }
30883
31160
  if (process.platform === "darwin") return resolve10(homedir3(), "Library", "Application Support", "KXM");
30884
31161
  const xdgState = env.XDG_STATE_HOME?.trim();
30885
- const base = xdgState && isAbsolute6(xdgState) ? xdgState : join17(homedir3(), ".local", "state");
31162
+ const base = xdgState && isAbsolute7(xdgState) ? xdgState : join17(homedir3(), ".local", "state");
30886
31163
  return resolve10(base, "kxm");
30887
31164
  }
30888
31165
  function hubEnvFile(env = process.env) {
@@ -30933,7 +31210,7 @@ function resolveClientHubAuthToken(env, project) {
30933
31210
  }
30934
31211
 
30935
31212
  // plugins/kxm/src/logger.ts
30936
- import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync as statSync4, unlinkSync as unlinkSync2 } from "node:fs";
31213
+ import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync as statSync5, unlinkSync as unlinkSync2 } from "node:fs";
30937
31214
  import { dirname as dirname10 } from "node:path";
30938
31215
  var LOG_LEVEL_PRIORITY = {
30939
31216
  debug: 10,
@@ -31004,7 +31281,7 @@ function createLogger(options) {
31004
31281
  let currentSize = 0;
31005
31282
  if (filePath && existsSync15(filePath)) {
31006
31283
  try {
31007
- currentSize = statSync4(filePath).size;
31284
+ currentSize = statSync5(filePath).size;
31008
31285
  } catch {
31009
31286
  currentSize = 0;
31010
31287
  }
@@ -31151,6 +31428,24 @@ function readRecentSupervisorError(paths) {
31151
31428
  return void 0;
31152
31429
  }
31153
31430
  }
31431
+ var SETTLED_RUN_STATUS = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
31432
+ function kxmSupervisorRecordIsLive(record2) {
31433
+ return supervisorStatusOf(record2).running;
31434
+ }
31435
+ function unsettledRunIds(context) {
31436
+ const ids = [];
31437
+ for (const stored of context.eventStore.runsForProject(context.projectId, 1e4)) {
31438
+ let status = stored.status;
31439
+ try {
31440
+ status = projectKxmRunReadOnly(context, stored.runId).status;
31441
+ } catch {
31442
+ ids.push(stored.runId);
31443
+ continue;
31444
+ }
31445
+ if (!SETTLED_RUN_STATUS.has(status)) ids.push(stored.runId);
31446
+ }
31447
+ return ids;
31448
+ }
31154
31449
  function supervisorStatusOf(record2) {
31155
31450
  if (!record2) return { running: false };
31156
31451
  const heartbeatAgeMs = Date.now() - Date.parse(record2.heartbeatAt);
@@ -31499,7 +31794,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
31499
31794
  }
31500
31795
  };
31501
31796
  const contextFor = (projectRoot) => {
31502
- if (!isAbsolute7(projectRoot)) {
31797
+ if (!isAbsolute8(projectRoot)) {
31503
31798
  throw runtimeError("runtime_request_invalid", "projectRoot", "projectRoot must be an absolute path");
31504
31799
  }
31505
31800
  const key = projectRuntimeKey(projectRoot);
@@ -31565,6 +31860,62 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
31565
31860
  sendJson(response, 200, { ok: true, projectId: context.projectId, retried, outbox: context.eventStore.outboxStatus() });
31566
31861
  return;
31567
31862
  }
31863
+ if (request.method === "POST" && url.pathname === "/v1/projects/unregister") {
31864
+ const body = await readJsonBody(request);
31865
+ const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
31866
+ const force = body.force === true;
31867
+ if (!projectRoot || !isAbsolute8(projectRoot)) {
31868
+ sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: "projectRoot must be an absolute path" });
31869
+ return;
31870
+ }
31871
+ if (!force) {
31872
+ const key2 = projectRuntimeKey(projectRoot);
31873
+ const existing2 = contexts.get(key2);
31874
+ let openRunIds = [];
31875
+ if (existing2) {
31876
+ openRunIds = unsettledRunIds(existing2);
31877
+ } else {
31878
+ const registration = registry.projectByRoot(projectRoot);
31879
+ const eventsPath = join18(paths.projectsDir, registration?.projectKey ?? projectRuntimeKey(projectRoot), "run-events.db");
31880
+ if (registration && existsSync16(eventsPath)) {
31881
+ const store = new KxmRunEventStore(eventsPath);
31882
+ try {
31883
+ openRunIds = store.runsForProject(registration.projectId, 1e4).filter((run) => !SETTLED_RUN_STATUS.has(run.status)).map((run) => run.runId);
31884
+ } finally {
31885
+ store.close();
31886
+ }
31887
+ }
31888
+ }
31889
+ if (openRunIds.length > 0) {
31890
+ sendJson(response, 409, {
31891
+ ok: false,
31892
+ error: "runtime_project_busy",
31893
+ message: `unsettled run ${openRunIds.join(", ")}`,
31894
+ runIds: openRunIds
31895
+ });
31896
+ return;
31897
+ }
31898
+ const laneCount = registry.homeLaneCount(projectRoot);
31899
+ if (laneCount > 0) {
31900
+ sendJson(response, 409, {
31901
+ ok: false,
31902
+ error: "runtime_project_has_lanes",
31903
+ message: `home root still has ${laneCount} lane(s)`
31904
+ });
31905
+ return;
31906
+ }
31907
+ }
31908
+ const key = projectRuntimeKey(projectRoot);
31909
+ const existing = contexts.get(key);
31910
+ const unregistered = registry.unregisterProject(projectRoot);
31911
+ if (existing) {
31912
+ contexts.delete(key);
31913
+ syncStatuses.delete(key);
31914
+ closeKxmRuntimeContext(existing);
31915
+ }
31916
+ sendJson(response, 200, { ok: true, unregistered });
31917
+ return;
31918
+ }
31568
31919
  if (request.method === "POST" && url.pathname === "/v1/runs") {
31569
31920
  const body = await readJsonBody(request);
31570
31921
  const projectRoot = typeof body.projectRoot === "string" ? body.projectRoot : "";
@@ -31788,7 +32139,10 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
31788
32139
  sendJson(response, 400, { ok: false, error: "runtime_request_invalid", message: `project ${requestedProjectId} is not the bound project ${context.projectId}` });
31789
32140
  return;
31790
32141
  }
31791
- const runs = context.eventStore.runsForProject(requestedProjectId, 50).map((stored) => {
32142
+ const limitText = url.searchParams.get("limit");
32143
+ const parsedLimit = limitText === null ? 50 : Number(limitText);
32144
+ const limit = Number.isInteger(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 1e4) : 50;
32145
+ const runs = context.eventStore.runsForProject(requestedProjectId, limit).map((stored) => {
31792
32146
  try {
31793
32147
  return projectKxmRunReadOnly(context, stored.runId);
31794
32148
  } catch (error) {
@@ -33679,8 +34033,8 @@ ${divider}
33679
34033
  }
33680
34034
 
33681
34035
  // plugins/kxm/src/ssh-remote.ts
33682
- import { spawnSync as spawnSync5 } from "node:child_process";
33683
- import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync16, readdirSync as readdirSync9, rmSync as rmSync5, statSync as statSync5 } from "node:fs";
34036
+ import { spawnSync as spawnSync6 } from "node:child_process";
34037
+ import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync16, readdirSync as readdirSync9, rmSync as rmSync5, statSync as statSync6 } from "node:fs";
33684
34038
  import { homedir as homedir4 } from "node:os";
33685
34039
  import { join as join22, resolve as resolve12 } from "node:path";
33686
34040
  var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
@@ -33755,7 +34109,7 @@ function parseSshConfig(configPath) {
33755
34109
  return [];
33756
34110
  }
33757
34111
  }
33758
- function resolveSshHostG(host, execFn = spawnSync5) {
34112
+ function resolveSshHostG(host, execFn = spawnSync6) {
33759
34113
  try {
33760
34114
  const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
33761
34115
  if (result.status !== 0 || !result.stdout) {
@@ -33821,7 +34175,7 @@ function buildSshArgs(options) {
33821
34175
  args.push(options.host);
33822
34176
  return args;
33823
34177
  }
33824
- function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync5) {
34178
+ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync6) {
33825
34179
  const resolvedDir = ensureSocketDir(socketDir);
33826
34180
  const controlPath = join22(resolvedDir, "%C");
33827
34181
  try {
@@ -33833,7 +34187,7 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
33833
34187
  return false;
33834
34188
  }
33835
34189
  }
33836
- function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync5) {
34190
+ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync6) {
33837
34191
  const resolvedDir = ensureSocketDir(socketDir);
33838
34192
  const controlPath = join22(resolvedDir, "%C");
33839
34193
  try {
@@ -33853,7 +34207,7 @@ function pruneSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
33853
34207
  for (const entry of entries) {
33854
34208
  const fullPath = join22(resolvedDir, entry);
33855
34209
  try {
33856
- const stat = statSync5(fullPath);
34210
+ const stat = statSync6(fullPath);
33857
34211
  if (stat.isSocket()) {
33858
34212
  rmSync5(fullPath, { force: true });
33859
34213
  removed++;
@@ -33867,7 +34221,7 @@ function pruneSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
33867
34221
  }
33868
34222
  function executeSshRun(params) {
33869
34223
  const startTime = Date.now();
33870
- const execSyncFn = params.execFn ?? spawnSync5;
34224
+ const execSyncFn = params.execFn ?? spawnSync6;
33871
34225
  if (params.action === "info") {
33872
34226
  if (params.host) {
33873
34227
  const hostInfo = resolveSshHostG(params.host, execSyncFn);
@@ -34314,6 +34668,7 @@ export {
34314
34668
  acceptKxmRun,
34315
34669
  applyRestorePlan,
34316
34670
  assertClosedGateObservation,
34671
+ assertDatabaseFile,
34317
34672
  assertKxmConfigError,
34318
34673
  attachKxmSupervisor,
34319
34674
  backupDatabaseFile,
@@ -34381,6 +34736,7 @@ export {
34381
34736
  kxmRunRevisionDrift,
34382
34737
  kxmRuntimePaths,
34383
34738
  kxmRuntimeRequest,
34739
+ kxmSupervisorRecordIsLive,
34384
34740
  kxmSupervisorStatus,
34385
34741
  kxmSupervisorTokenFile,
34386
34742
  kxmToolPolicyRevision,