switchroom 0.21.17 → 0.21.18

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.
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.21.17", COMMIT_SHA = "ac61272c", COMMIT_DATE = "2026-08-17T16:42:58Z";
2123
+ var VERSION = "0.21.18", COMMIT_SHA = "ae063418", COMMIT_DATE = "2026-08-18T03:14:53Z";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -71736,11 +71736,11 @@ var init_apply = __esm(() => {
71736
71736
  });
71737
71737
 
71738
71738
  // src/host-control/audit-reader.ts
71739
- import { closeSync as closeSync15, existsSync as existsSync83, openSync as openSync15, readSync as readSync5, statSync as statSync49 } from "node:fs";
71739
+ import { closeSync as closeSync15, existsSync as existsSync84, openSync as openSync15, readSync as readSync5, statSync as statSync50 } from "node:fs";
71740
71740
  import { homedir as homedir43 } from "node:os";
71741
- import { join as join90 } from "node:path";
71741
+ import { join as join91 } from "node:path";
71742
71742
  function defaultAuditLogPath2(home2 = homedir43()) {
71743
- return join90(home2, ".switchroom", "host-control-audit.log");
71743
+ return join91(home2, ".switchroom", "host-control-audit.log");
71744
71744
  }
71745
71745
  function auditRotation() {
71746
71746
  return resolveRotationConfig({
@@ -71757,14 +71757,14 @@ function auditReadFullWindowBytes() {
71757
71757
  function auditReadMaxGenerations() {
71758
71758
  return auditRotation().maxFiles;
71759
71759
  }
71760
- function tailBytes(path7, n, strict = false) {
71760
+ function tailBytes(path9, n, strict = false) {
71761
71761
  if (n <= 0)
71762
71762
  return "";
71763
71763
  let fd;
71764
71764
  let size;
71765
71765
  try {
71766
- size = statSync49(path7).size;
71767
- fd = openSync15(path7, "r");
71766
+ size = statSync50(path9).size;
71767
+ fd = openSync15(path9, "r");
71768
71768
  } catch (err) {
71769
71769
  if (strict && err.code !== "ENOENT")
71770
71770
  throw err;
@@ -71809,7 +71809,7 @@ function readAuditRaw(logPath, opts = {}) {
71809
71809
  budget -= Buffer.byteLength(active, "utf-8");
71810
71810
  for (let n = 1;n <= maxFiles && budget > 0; n++) {
71811
71811
  const gen = `${logPath}.${n}`;
71812
- if (!existsSync83(gen))
71812
+ if (!existsSync84(gen))
71813
71813
  break;
71814
71814
  const chunk2 = tailBytes(gen, budget, strict);
71815
71815
  if (chunk2.length === 0)
@@ -71987,16 +71987,16 @@ var init_audit_reader = __esm(() => {
71987
71987
  });
71988
71988
 
71989
71989
  // src/web/fleet-health-read.ts
71990
- import { readFileSync as readFileSync78 } from "node:fs";
71991
- import { resolve as resolve45 } from "node:path";
71990
+ import { readFileSync as readFileSync79 } from "node:fs";
71991
+ import { resolve as resolve46 } from "node:path";
71992
71992
  import { homedir as homedir46 } from "node:os";
71993
71993
  function fleetHealthLedgerPath(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir46()) {
71994
- return resolve45(home2, ".switchroom", "fleet-health", "ledger.json");
71994
+ return resolve46(home2, ".switchroom", "fleet-health", "ledger.json");
71995
71995
  }
71996
- function readFleetHealth(path7) {
71996
+ function readFleetHealth(path9) {
71997
71997
  let ledger;
71998
71998
  try {
71999
- ledger = JSON.parse(readFileSync78(path7, "utf-8"));
71999
+ ledger = JSON.parse(readFileSync79(path9, "utf-8"));
72000
72000
  } catch {
72001
72001
  return {
72002
72002
  owner_agent: null,
@@ -72022,8 +72022,8 @@ function readFleetHealth(path7) {
72022
72022
  ...ranked.length === 0 ? { reason: EMPTY_REASON } : {}
72023
72023
  };
72024
72024
  }
72025
- function handleGetFleetHealth(path7 = fleetHealthLedgerPath()) {
72026
- return readFleetHealth(path7);
72025
+ function handleGetFleetHealth(path9 = fleetHealthLedgerPath()) {
72026
+ return readFleetHealth(path9);
72027
72027
  }
72028
72028
  var EMPTY_REASON;
72029
72029
  var init_fleet_health_read = __esm(() => {
@@ -110258,8 +110258,8 @@ init_loader();
110258
110258
  init_hindsight();
110259
110259
  var import_yaml16 = __toESM(require_dist(), 1);
110260
110260
  import { spawn as spawn5 } from "node:child_process";
110261
- import { existsSync as existsSync82, readFileSync as readFileSync74, statSync as statSync48 } from "node:fs";
110262
- import { join as join88 } from "node:path";
110261
+ import { existsSync as existsSync83, readFileSync as readFileSync75, statSync as statSync49 } from "node:fs";
110262
+ import { join as join89 } from "node:path";
110263
110263
 
110264
110264
  // src/cli/memory-rules.ts
110265
110265
  init_source();
@@ -110526,301 +110526,1223 @@ function registerMemoryDirectiveCommand(memory, program3) {
110526
110526
  }));
110527
110527
  }
110528
110528
 
110529
- // src/cli/memory.ts
110530
- function persistMemoryConfigUrl(configPath, provider, url) {
110531
- if (!existsSync82(configPath))
110532
- return false;
110533
- const raw = readFileSync74(configPath, "utf-8");
110534
- const doc = import_yaml16.default.parseDocument(raw);
110535
- if (!doc.has("memory")) {
110536
- doc.set("memory", { backend: "hindsight", shared_collection: "shared", config: { provider, url } });
110537
- } else {
110538
- const memNode = doc.get("memory");
110539
- if (!memNode.has("config")) {
110540
- memNode.set("config", { provider, url });
110541
- } else {
110542
- const configNode = memNode.get("config");
110543
- configNode.set("url", url);
110544
- if (!configNode.has("provider")) {
110545
- configNode.set("provider", provider);
110546
- }
110547
- }
110529
+ // src/cli/memory-flip.ts
110530
+ init_source();
110531
+ init_helpers();
110532
+ init_hindsight2();
110533
+ init_hindsight();
110534
+ init_hindsight_directive_admin();
110535
+
110536
+ // src/memory/directive-triage.ts
110537
+ init_hindsight_directive_admin();
110538
+ var SUPERSEDED_BY_TAG_RE = /^superseded-by:(.+)$/;
110539
+ function classifyDirective(directive, override) {
110540
+ const isActive = directive.is_active !== false;
110541
+ const overrideSignal = override?.signal.trim();
110542
+ if (hasRulesBlockMarker(directive.tags)) {
110543
+ return {
110544
+ category: "rules-block",
110545
+ signal: "carries the rules-block marker tag \u2014 staged for M3, never retired",
110546
+ action: "stage-for-m3"
110547
+ };
110548
110548
  }
110549
- let mode = 420;
110550
- try {
110551
- mode = statSync48(configPath).mode & 511;
110552
- } catch {}
110553
- writeConfigFileSync(configPath, doc.toString(), mode);
110554
- return true;
110555
- }
110556
- function resolveMemorySetupTag(input) {
110557
- const explicit = input.explicitTag?.trim();
110558
- if (explicit) {
110559
- if (explicit.toLowerCase() === "latest") {
110560
- return { tag: undefined, reason: "explicit-latest" };
110549
+ if (override && override.category === "rules-block") {
110550
+ return {
110551
+ category: "rules-block",
110552
+ signal: overrideSignal || "rules-block override with no signal text \u2014 staged for M3, never retired",
110553
+ action: "stage-for-m3"
110554
+ };
110555
+ }
110556
+ const supersessionTag = (directive.tags ?? []).map((t) => SUPERSEDED_BY_TAG_RE.exec(t)).find((m) => m !== null);
110557
+ if (supersessionTag) {
110558
+ const winner = supersessionTag[1];
110559
+ return {
110560
+ category: "retire",
110561
+ signal: isActive ? `superseded by '${winner}' (superseded-by tag)` : `superseded by '${winner}' (superseded-by tag) \u2014 already inactive, nothing to retire`,
110562
+ action: isActive ? "retire" : "keep",
110563
+ supersededBy: winner
110564
+ };
110565
+ }
110566
+ if (override && overrideSignal) {
110567
+ if (override.category === "reflect-directive") {
110568
+ return { category: "reflect-directive", signal: overrideSignal, action: "keep" };
110561
110569
  }
110562
- const normalized = normalizeHindsightVersionTag(explicit);
110563
- if (!normalized)
110564
- return { tag: explicit, reason: "invalid" };
110565
- return { tag: normalized, reason: "explicit" };
110570
+ return {
110571
+ category: override.category,
110572
+ signal: isActive ? overrideSignal : `${overrideSignal} \u2014 already inactive, nothing to retire`,
110573
+ action: isActive ? "retire" : "keep"
110574
+ };
110566
110575
  }
110567
- const pin = normalizeHindsightVersionTag(input.releasePin);
110568
- if (pin)
110569
- return { tag: pin, reason: "pin" };
110570
- return { tag: undefined, reason: "latest" };
110571
- }
110572
- function hindsightSecretValues(input) {
110573
- const out = [];
110574
- const push = (v) => {
110575
- if (typeof v === "string" && v.trim().length > 0)
110576
- out.push(v.trim());
110576
+ return {
110577
+ category: "reflect-directive",
110578
+ signal: "no deterministic signal \u2014 defaults to KEEP",
110579
+ action: "keep"
110577
110580
  };
110578
- push(input.cpAccessKey);
110579
- push(input.llm?.api_key);
110580
- for (const op of ["retain", "reflect", "consolidation"]) {
110581
- push(input.llm?.[op]?.api_key);
110582
- }
110583
- return out;
110584
110581
  }
110585
- function hasUnresolvedHindsightVaultRef(config) {
110586
- const hindsight = config.hindsight;
110587
- return hindsightSecretValues({
110588
- llm: hindsight?.llm,
110589
- cpAccessKey: hindsight?.cp_access_key
110590
- }).some((v) => isVaultReference(v));
110582
+ function buildDirectiveTriageRows(directives, overrides) {
110583
+ return directives.map((d) => {
110584
+ const classified = classifyDirective(d, overrides?.get(d.id));
110585
+ return {
110586
+ id: d.id,
110587
+ name: d.name,
110588
+ priority: d.priority ?? 0,
110589
+ isActive: d.is_active !== false,
110590
+ ...classified
110591
+ };
110592
+ });
110591
110593
  }
110592
- function emitsCleartextHindsightSecret(input) {
110593
- return hindsightSecretValues(input).some((v) => !isVaultReference(v));
110594
+
110595
+ // src/cli/debug.ts
110596
+ init_helpers();
110597
+ init_loader();
110598
+ import { existsSync as existsSync82, readFileSync as readFileSync74, readdirSync as readdirSync30, statSync as statSync48 } from "node:fs";
110599
+ import { resolve as resolve43, join as join88 } from "node:path";
110600
+ import { createHash as createHash15 } from "node:crypto";
110601
+
110602
+ // src/agents/workspace.ts
110603
+ import { readFile as readFile2, stat } from "node:fs/promises";
110604
+ import path8 from "node:path";
110605
+
110606
+ // src/agents/bootstrap-budget.ts
110607
+ import path7 from "node:path";
110608
+ var DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO = 0.85;
110609
+ var DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES = 3;
110610
+ var DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX = 32;
110611
+
110612
+ class BootstrapBudgetExceededError extends Error {
110613
+ analysis;
110614
+ constructor(analysis, message) {
110615
+ super(message);
110616
+ this.analysis = analysis;
110617
+ this.name = "BootstrapBudgetExceededError";
110618
+ }
110594
110619
  }
110595
- var HINDSIGHT_COMPOSE_CLEARTEXT_SECRETS_WARNING = "This snippet embeds secrets in CLEARTEXT (LLM api_key and/or cp_access_key)." + " Treat the output as sensitive \u2014 do not paste it into shared channels or" + " commit it unredacted.";
110596
- async function resolveLiteLLMForHindsight(config) {
110597
- return resolveHindsightLiteLlm(config);
110620
+ function normalizePositiveLimit(value) {
110621
+ if (!Number.isFinite(value) || value <= 0) {
110622
+ return 1;
110623
+ }
110624
+ return Math.floor(value);
110598
110625
  }
110599
- function readRecallLog(agentDir, limit) {
110600
- const path7 = join88(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
110601
- if (!existsSync82(path7))
110602
- return [];
110603
- let raw;
110604
- try {
110605
- raw = readFileSync74(path7, "utf-8");
110606
- } catch {
110626
+ function formatWarningCause(cause) {
110627
+ return cause === "per-file-limit" ? "max/file" : "max/total";
110628
+ }
110629
+ function normalizeSeenSignatures(signatures) {
110630
+ if (!Array.isArray(signatures) || signatures.length === 0) {
110607
110631
  return [];
110608
110632
  }
110609
- const lines = raw.split(`
110610
- `).filter((l) => l.trim().length > 0);
110611
- const tail = lines.slice(-limit);
110612
- const out = [];
110613
- for (const line of tail) {
110614
- try {
110615
- out.push(JSON.parse(line));
110616
- } catch {}
110633
+ const seen = new Set;
110634
+ const result = [];
110635
+ for (const signature of signatures) {
110636
+ const value = typeof signature === "string" ? signature.trim() : "";
110637
+ if (!value || seen.has(value)) {
110638
+ continue;
110639
+ }
110640
+ seen.add(value);
110641
+ result.push(value);
110617
110642
  }
110618
- return out;
110643
+ return result;
110619
110644
  }
110620
- function formatRecallLogView(name, entries) {
110621
- const lines = [source_default.bold(`
110622
- ${name}:`)];
110623
- const total = entries.length;
110624
- const hits = entries.filter((e) => e.cache_hit).length;
110625
- const cappedTurns = entries.filter((e) => e.capped).length;
110626
- const omittedTurns = entries.filter((e) => typeof e.directives_omitted === "number" && e.directives_omitted > 0).length;
110627
- const memCounts = entries.map((e) => e.result_count).filter((n) => typeof n === "number");
110628
- const avg = memCounts.length > 0 ? Math.round(memCounts.reduce((s, n) => s + n, 0) / memCounts.length * 10) / 10 : null;
110629
- const max = memCounts.length > 0 ? Math.max(...memCounts) : null;
110630
- const medians = entries.map((e) => e.injected_score_median).filter((n) => typeof n === "number");
110631
- const avgScore = medians.length > 0 ? Math.round(medians.reduce((s, n) => s + n, 0) / medians.length * 100) / 100 : null;
110632
- const collapseTurns = entries.filter((e) => typeof e.injected_own_bank_count === "number" && e.injected_own_bank_count === 0 && typeof e.injected_additional_bank_count === "number" && e.injected_additional_bank_count > 0).length;
110633
- lines.push(source_default.gray(` last ${total} turn${total === 1 ? "" : "s"}: ` + `avg=${avg ?? "\u2014"} max=${max ?? "\u2014"} ` + `avg_score=${avgScore ?? "\u2014"} ` + `cache_hits=${hits} capped=${cappedTurns}` + (omittedTurns > 0 ? ` directives_omitted_turns=${omittedTurns}` : "") + (collapseTurns > 0 ? source_default.red(` own_bank_collapse_turns=${collapseTurns}`) : "")));
110634
- for (const e of entries) {
110635
- const flag = e.cache_hit ? source_default.cyan("CACHE") : e.capped ? source_default.yellow("CAP") : source_default.green("OK");
110636
- const dem = e.demoted_count && e.demoted_count > 0 ? source_default.dim(` -${e.demoted_count}d`) : "";
110637
- const omitted = typeof e.directives_omitted === "number" && e.directives_omitted > 0 ? source_default.yellow(` +${e.directives_omitted}omitted`) : "";
110638
- const ids = e.memory_ids && e.memory_ids.length > 0 ? source_default.dim(` ids=${e.memory_ids.slice(0, 3).join(",")}${e.memory_ids.length > 3 ? `\u2026+${e.memory_ids.length - 3}` : ""}`) : "";
110639
- const own = e.injected_own_bank_count;
110640
- const additional = e.injected_additional_bank_count;
110641
- const composition = typeof own === "number" || typeof additional === "number" ? (() => {
110642
- const text = ` own/add=${own ?? "\u2014"}/${additional ?? "\u2014"}`;
110643
- return own === 0 && typeof additional === "number" && additional > 0 ? source_default.red(text) : source_default.dim(text);
110644
- })() : "";
110645
- const score = typeof e.injected_score_min === "number" && typeof e.injected_score_median === "number" && typeof e.injected_score_max === "number" ? source_default.dim(` score=${e.injected_score_min.toFixed(2)}/` + `${e.injected_score_median.toFixed(2)}/` + `${e.injected_score_max.toFixed(2)}`) : "";
110646
- lines.push(` ${source_default.gray(e.ts)} ${flag} ` + `n=${e.result_count ?? "\u2014"}${e.pre_cap_count != null && e.pre_cap_count !== e.result_count ? `/${e.pre_cap_count}` : ""}` + `${dem}${omitted}${score}${composition}${ids}`);
110645
+ function appendSeenSignature(signatures, signature) {
110646
+ if (!signature.trim()) {
110647
+ return signatures;
110647
110648
  }
110648
- return lines;
110649
+ if (signatures.includes(signature)) {
110650
+ return signatures;
110651
+ }
110652
+ const next = [...signatures, signature];
110653
+ if (next.length <= DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX) {
110654
+ return next;
110655
+ }
110656
+ return next.slice(-DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX);
110649
110657
  }
110650
- function registerMemoryCommand(program3) {
110651
- const memory = program3.command("memory").description("Hindsight memory operations");
110652
- memory.command("search <query>").description("Search agent memories via Hindsight").option("-a, --agent <name>", "Search a specific agent's collection").action(withConfigError(async (query, opts) => {
110653
- const config = getConfig(program3);
110654
- if (opts.agent) {
110655
- if (!config.agents[opts.agent]) {
110656
- console.error(source_default.red(`Agent "${opts.agent}" is not defined in switchroom.yaml`));
110657
- process.exit(1);
110658
- }
110659
- const collection = getCollectionForAgent(opts.agent, config);
110660
- console.log(source_default.bold(`
110661
- Search: ${opts.agent} (collection: ${collection})
110662
- `));
110663
- console.log(source_default.gray(` $ ${searchMemory(query, collection)}`));
110664
- console.log();
110665
- return;
110658
+ function buildBootstrapInjectionStats(params) {
110659
+ const injectedByPath = new Map;
110660
+ const injectedByBaseName = new Map;
110661
+ for (const file of params.injectedFiles) {
110662
+ const pathValue = typeof file.path === "string" ? file.path.trim() : "";
110663
+ if (!pathValue) {
110664
+ continue;
110666
110665
  }
110667
- const agentNames = Object.keys(config.agents);
110668
- console.log(source_default.bold(`
110669
- Searching all eligible collections:
110670
- `));
110671
- for (const name of agentNames) {
110672
- const collection = getCollectionForAgent(name, config);
110673
- if (isStrictIsolation(name, config)) {
110674
- console.log(source_default.gray(` ${name} (${collection}) \u2014 skipped (strict isolation)`));
110675
- continue;
110676
- }
110677
- console.log(source_default.cyan(` ${name} (${collection}):`));
110678
- console.log(source_default.gray(` $ ${searchMemory(query, collection)}`));
110666
+ if (!injectedByPath.has(pathValue)) {
110667
+ injectedByPath.set(pathValue, file.content);
110679
110668
  }
110680
- console.log();
110681
- }));
110682
- memory.command("stats").description("List agents with their collection names and isolation mode").action(withConfigError(async () => {
110683
- const config = getConfig(program3);
110684
- const agentNames = Object.keys(config.agents);
110685
- if (agentNames.length === 0) {
110686
- console.log(source_default.yellow("No agents defined in switchroom.yaml"));
110687
- return;
110669
+ const normalizedPath = pathValue.replace(/\\/g, "/");
110670
+ const baseName = path7.posix.basename(normalizedPath);
110671
+ if (!injectedByBaseName.has(baseName)) {
110672
+ injectedByBaseName.set(baseName, file.content);
110688
110673
  }
110689
- const headers = ["Agent", "Collection", "Isolation", "Auto-recall"];
110690
- const widths = [20, 20, 12, 12];
110691
- const headerLine = headers.map((h, i2) => source_default.bold(h.padEnd(widths[i2]))).join(" ");
110692
- console.log(`
110693
- ${headerLine}`);
110694
- for (const name of agentNames) {
110695
- const collection = getCollectionForAgent(name, config);
110696
- const isolation = isStrictIsolation(name, config) ? "strict" : "default";
110697
- const autoRecall = config.agents[name].memory?.auto_recall ?? true;
110698
- const row = [
110699
- name.padEnd(widths[0]),
110700
- collection.padEnd(widths[1]),
110701
- isolation.padEnd(widths[2]),
110702
- (autoRecall ? "yes" : "no").padEnd(widths[3])
110703
- ].join(" ");
110704
- console.log(` ${row}`);
110674
+ }
110675
+ return params.bootstrapFiles.map((file) => {
110676
+ const pathValue = typeof file.path === "string" ? file.path.trim() : "";
110677
+ const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
110678
+ const injected = (pathValue ? injectedByPath.get(pathValue) : undefined) ?? injectedByPath.get(file.name) ?? injectedByBaseName.get(file.name);
110679
+ const injectedChars = injected ? injected.length : 0;
110680
+ const truncated = !file.missing && injectedChars < rawChars;
110681
+ return {
110682
+ name: file.name,
110683
+ path: pathValue || file.name,
110684
+ missing: file.missing,
110685
+ rawChars,
110686
+ injectedChars,
110687
+ truncated
110688
+ };
110689
+ });
110690
+ }
110691
+ function analyzeBootstrapBudget(params) {
110692
+ const bootstrapMaxChars = normalizePositiveLimit(params.bootstrapMaxChars);
110693
+ const bootstrapTotalMaxChars = normalizePositiveLimit(params.bootstrapTotalMaxChars);
110694
+ const nearLimitRatio = typeof params.nearLimitRatio === "number" && Number.isFinite(params.nearLimitRatio) && params.nearLimitRatio > 0 && params.nearLimitRatio < 1 ? params.nearLimitRatio : DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO;
110695
+ const nonMissing = params.files.filter((file) => !file.missing);
110696
+ const rawChars = nonMissing.reduce((sum, file) => sum + file.rawChars, 0);
110697
+ const injectedChars = nonMissing.reduce((sum, file) => sum + file.injectedChars, 0);
110698
+ const totalNearLimit = injectedChars >= Math.ceil(bootstrapTotalMaxChars * nearLimitRatio);
110699
+ const totalOverLimit = injectedChars >= bootstrapTotalMaxChars;
110700
+ const files = params.files.map((file) => {
110701
+ if (file.missing) {
110702
+ return { ...file, nearLimit: false, causes: [] };
110705
110703
  }
110706
- console.log();
110707
- const profileBanks = collectProfileBanks(config);
110708
- if (profileBanks.size > 0) {
110709
- const owners = new Map;
110710
- for (const [uname, u] of Object.entries(config.users ?? {})) {
110711
- if (u.profile_bank) {
110712
- owners.set(u.profile_bank, [
110713
- ...owners.get(u.profile_bank) ?? [],
110714
- uname
110715
- ]);
110716
- }
110704
+ const perFileOverLimit = file.rawChars > bootstrapMaxChars;
110705
+ const nearLimit = file.rawChars >= Math.ceil(bootstrapMaxChars * nearLimitRatio);
110706
+ const causes = [];
110707
+ if (file.truncated) {
110708
+ if (perFileOverLimit) {
110709
+ causes.push("per-file-limit");
110717
110710
  }
110718
- console.log(source_default.bold(` Profile banks (per-user / shared):
110719
- `));
110720
- for (const bank of [...profileBanks].sort()) {
110721
- const who = owners.get(bank);
110722
- const label = who ? `user: ${who.join(", ")}` : "shared";
110723
- console.log(` ${bank.padEnd(widths[1])} ${source_default.gray(label)}`);
110711
+ if (totalOverLimit) {
110712
+ causes.push("total-limit");
110724
110713
  }
110725
- console.log();
110726
- }
110727
- console.log(source_default.bold(` Hindsight CLI commands:
110728
- `));
110729
- for (const name of agentNames) {
110730
- const collection = getCollectionForAgent(name, config);
110731
- console.log(source_default.gray(` $ ${getMemoryStats(collection)}`));
110732
110714
  }
110733
- console.log();
110734
- }));
110735
- memory.command("reflect").description("Show cross-agent reflection plan").action(withConfigError(async () => {
110736
- const config = getConfig(program3);
110737
- const { eligible, excluded, commands } = reflectAcrossAgents(config);
110738
- console.log(source_default.bold(`
110739
- Cross-agent reflection plan
110740
- `));
110741
- if (eligible.length > 0) {
110742
- console.log(source_default.green(" Eligible collections:"));
110743
- for (const { agent, collection } of eligible) {
110744
- console.log(source_default.white(` ${agent} -> ${collection}`));
110745
- }
110715
+ return { ...file, nearLimit, causes };
110716
+ });
110717
+ const truncatedFiles = files.filter((file) => file.truncated);
110718
+ const nearLimitFiles = files.filter((file) => file.nearLimit);
110719
+ return {
110720
+ files,
110721
+ truncatedFiles,
110722
+ nearLimitFiles,
110723
+ totalNearLimit,
110724
+ hasTruncation: truncatedFiles.length > 0,
110725
+ totals: {
110726
+ rawChars,
110727
+ injectedChars,
110728
+ truncatedChars: Math.max(0, rawChars - injectedChars),
110729
+ bootstrapMaxChars,
110730
+ bootstrapTotalMaxChars,
110731
+ nearLimitRatio
110746
110732
  }
110747
- if (excluded.length > 0) {
110748
- console.log(source_default.red(`
110749
- Excluded (strict isolation):`));
110750
- for (const { agent, collection } of excluded) {
110751
- console.log(source_default.gray(` ${agent} -> ${collection}`));
110752
- }
110733
+ };
110734
+ }
110735
+ function buildBootstrapTruncationSignature(analysis) {
110736
+ if (!analysis.hasTruncation) {
110737
+ return;
110738
+ }
110739
+ const files = analysis.truncatedFiles.map((file) => ({
110740
+ path: file.path || file.name,
110741
+ rawChars: file.rawChars,
110742
+ injectedChars: file.injectedChars,
110743
+ causes: [...file.causes].sort()
110744
+ })).sort((a, b) => {
110745
+ const pathCmp = a.path.localeCompare(b.path);
110746
+ if (pathCmp !== 0) {
110747
+ return pathCmp;
110753
110748
  }
110754
- if (commands.length > 0) {
110755
- console.log(source_default.bold(`
110756
- Hindsight CLI commands:
110757
- `));
110758
- for (const cmd of commands) {
110759
- console.log(source_default.gray(` $ ${cmd}`));
110760
- }
110761
- } else {
110762
- console.log(source_default.yellow(`
110763
- No eligible collections for reflection.`));
110749
+ if (a.rawChars !== b.rawChars) {
110750
+ return a.rawChars - b.rawChars;
110764
110751
  }
110765
- console.log();
110766
- }));
110767
- memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current. Also required after any `hindsight.llm` model/provider config change \u2014 env is only re-derived on recreate, a plain docker restart is not enough (see docs/operators/hindsight-model-change.md).").option("--tag <version>", "Pin the hindsight image tag to pull + run (e.g. v0.15.18), overriding the " + "default floating `:latest`. Threaded through by `switchroom rollout` so a " + "version-pinned roll recreates hindsight on the SAME tag as the rest of the " + "fleet. Omit for `:latest` (the standalone default).").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").option("--strict-env", "Refuse the recreate (before the running container is touched) if it would drop " + "env vars that are live on the container but declared in neither `hindsight.env` " + "nor switchroom's managed defaults. Default is to warn loudly and proceed, so a " + "rollout is never wedged half-way with memory down.").option("--gpu", "Force GPU passthrough on for this launch, overriding host autodetection. " + "Use when `~/.switchroom/host-capabilities.json` is wrong or unreadable and " + "you know the host has a working nvidia container toolkit (`docker run --gpus " + "all` hard-fails container create without one). Beats `hindsight.gpu`.").option("--no-gpu", "Force GPU passthrough off for this launch, overriding host autodetection. " + "Also the explicit opt-out for the recreate GPU drop guard.").option("--allow-gpu-drop", "Proceed with a --recreate that would take GPU passthrough away from the " + "running container. Without this the recreate refuses, because that drop " + "moves the embedding model and cross-encoder reranker to CPU on the " + "interactive recall path with no other signal.").action(async (opts) => {
110768
- if (opts.status) {
110769
- if (!isDockerAvailable()) {
110770
- console.log(source_default.red(" Docker is not available."));
110771
- process.exit(1);
110772
- }
110773
- const status = getHindsightStatus();
110774
- if (status) {
110775
- console.log(source_default.bold(`
110776
- Hindsight container status:`));
110777
- console.log(` ${source_default.cyan("switchroom-hindsight")}: ${status}
110778
- `);
110779
- } else {
110780
- console.log(source_default.yellow(`
110781
- Hindsight container not found.
110782
- `));
110783
- console.log(source_default.gray(" Run 'switchroom memory setup' to start it."));
110784
- }
110785
- return;
110752
+ if (a.injectedChars !== b.injectedChars) {
110753
+ return a.injectedChars - b.injectedChars;
110786
110754
  }
110787
- if (opts.stop) {
110788
- if (!isDockerAvailable()) {
110789
- console.log(source_default.red(" Docker is not available."));
110790
- process.exit(1);
110791
- }
110792
- if (!isHindsightContainerExists()) {
110793
- console.log(source_default.yellow(" No switchroom-hindsight container found."));
110794
- return;
110795
- }
110796
- console.log(source_default.gray(" Stopping switchroom-hindsight..."));
110797
- stopHindsightRecallPool();
110798
- stopHindsight();
110799
- console.log(source_default.green(" Hindsight container stopped and removed."));
110755
+ return a.causes.join("+").localeCompare(b.causes.join("+"));
110756
+ });
110757
+ return JSON.stringify({
110758
+ bootstrapMaxChars: analysis.totals.bootstrapMaxChars,
110759
+ bootstrapTotalMaxChars: analysis.totals.bootstrapTotalMaxChars,
110760
+ files
110761
+ });
110762
+ }
110763
+ function formatBootstrapTruncationWarningLines(params) {
110764
+ if (!params.analysis.hasTruncation) {
110765
+ return [];
110766
+ }
110767
+ const maxFiles = typeof params.maxFiles === "number" && Number.isFinite(params.maxFiles) && params.maxFiles > 0 ? Math.floor(params.maxFiles) : DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES;
110768
+ const lines = [];
110769
+ const duplicateNameCounts = params.analysis.truncatedFiles.reduce((acc, file) => {
110770
+ acc.set(file.name, (acc.get(file.name) ?? 0) + 1);
110771
+ return acc;
110772
+ }, new Map);
110773
+ const topFiles = params.analysis.truncatedFiles.slice(0, maxFiles);
110774
+ for (const file of topFiles) {
110775
+ const pct = file.rawChars > 0 ? Math.round((file.rawChars - file.injectedChars) / file.rawChars * 100) : 0;
110776
+ const causeText = file.causes.length > 0 ? file.causes.map((cause) => formatWarningCause(cause)).join(", ") : "";
110777
+ const nameLabel = (duplicateNameCounts.get(file.name) ?? 0) > 1 && file.path.trim().length > 0 ? `${file.name} (${file.path})` : file.name;
110778
+ lines.push(`${nameLabel}: ${file.rawChars} raw -> ${file.injectedChars} injected (~${Math.max(0, pct)}% removed${causeText ? `; ${causeText}` : ""}).`);
110779
+ }
110780
+ if (params.analysis.truncatedFiles.length > topFiles.length) {
110781
+ lines.push(`+${params.analysis.truncatedFiles.length - topFiles.length} more truncated file(s).`);
110782
+ }
110783
+ lines.push("If unintentional, raise switchroom workspace bootstrapMaxChars and/or bootstrapTotalMaxChars config.");
110784
+ return lines;
110785
+ }
110786
+ function buildBootstrapPromptWarning(params) {
110787
+ const signature = buildBootstrapTruncationSignature(params.analysis);
110788
+ let seenSignatures = normalizeSeenSignatures(params.seenSignatures);
110789
+ if (params.previousSignature && !seenSignatures.includes(params.previousSignature)) {
110790
+ seenSignatures = appendSeenSignature(seenSignatures, params.previousSignature);
110791
+ }
110792
+ const hasSeenSignature = Boolean(signature && seenSignatures.includes(signature));
110793
+ const warningShown = params.mode !== "off" && Boolean(signature) && (params.mode === "always" || !hasSeenSignature);
110794
+ const warningSignaturesSeen = signature && params.mode !== "off" ? appendSeenSignature(seenSignatures, signature) : seenSignatures;
110795
+ return {
110796
+ signature,
110797
+ warningShown,
110798
+ lines: warningShown ? formatBootstrapTruncationWarningLines({
110799
+ analysis: params.analysis,
110800
+ maxFiles: params.maxFiles
110801
+ }) : [],
110802
+ warningSignaturesSeen
110803
+ };
110804
+ }
110805
+
110806
+ // src/agents/bootstrap-types.ts
110807
+ var DEFAULT_AGENTS_FILENAME = "AGENTS.md";
110808
+ var DEFAULT_SOUL_DEFAULT_FILENAME = "SOUL.default.md";
110809
+ var DEFAULT_SOUL_FILENAME = "SOUL.md";
110810
+ var DEFAULT_TOOLS_FILENAME = "TOOLS.md";
110811
+ var DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
110812
+ var DEFAULT_USER_FILENAME = "USER.md";
110813
+ var DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
110814
+ var DEFAULT_MEMORY_FILENAME = "MEMORY.md";
110815
+
110816
+ // src/agents/workspace.ts
110817
+ var DEFAULT_WORKSPACE_DIR_NAME = "workspace";
110818
+ var DEFAULT_MEMORY_SUBDIR = "memory";
110819
+ var STABLE_BOOTSTRAP_FILENAMES = [
110820
+ DEFAULT_AGENTS_FILENAME,
110821
+ DEFAULT_SOUL_DEFAULT_FILENAME,
110822
+ DEFAULT_SOUL_FILENAME,
110823
+ DEFAULT_IDENTITY_FILENAME,
110824
+ DEFAULT_USER_FILENAME,
110825
+ DEFAULT_TOOLS_FILENAME,
110826
+ DEFAULT_BOOTSTRAP_FILENAME
110827
+ ];
110828
+ var DYNAMIC_BOOTSTRAP_FILENAMES = [
110829
+ DEFAULT_MEMORY_FILENAME
110830
+ ];
110831
+ var DEFAULT_BOOTSTRAP_MAX_CHARS = 12000;
110832
+ var DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS = 64000;
110833
+ var DEFAULT_DYNAMIC_TOTAL_MAX_CHARS = 24000;
110834
+ function resolveAgentWorkspaceDir(agentDir) {
110835
+ return path8.join(agentDir, DEFAULT_WORKSPACE_DIR_NAME);
110836
+ }
110837
+ async function readOptionalFile(filePath) {
110838
+ try {
110839
+ const content = await readFile2(filePath, "utf8");
110840
+ return content;
110841
+ } catch (err) {
110842
+ if (isErrnoException(err) && (err.code === "ENOENT" || err.code === "EISDIR")) {
110800
110843
  return;
110801
110844
  }
110802
- if (!isDockerAvailable()) {
110803
- console.log(source_default.red(`
110804
- Docker is not available.`));
110805
- console.log(source_default.gray(` Install Docker: https://docs.docker.com/get-docker/
110806
- `));
110807
- process.exit(1);
110808
- }
110809
- const recreate = opts.recreate === true;
110810
- let resolvedLlmPromise;
110811
- const getResolvedLlm = () => {
110812
- if (!resolvedLlmPromise) {
110813
- resolvedLlmPromise = resolveHindsightLlmSecrets(getConfig(program3).hindsight?.llm);
110814
- }
110815
- return resolvedLlmPromise;
110816
- };
110817
- let releasePin;
110818
- try {
110819
- releasePin = getConfig(program3).release?.pin ?? undefined;
110820
- } catch {
110821
- releasePin = undefined;
110822
- }
110823
- const { tag: effectiveTag, reason: tagReason } = resolveMemorySetupTag({
110845
+ throw err;
110846
+ }
110847
+ }
110848
+ function isErrnoException(value) {
110849
+ return value instanceof Error && typeof value.code === "string";
110850
+ }
110851
+ async function loadNamedFile(workspaceDir, name, relativePath) {
110852
+ const filePath = path8.join(workspaceDir, relativePath ?? name);
110853
+ const content = await readOptionalFile(filePath);
110854
+ if (content === undefined) {
110855
+ return { name, path: filePath, missing: true };
110856
+ }
110857
+ return { name, path: filePath, content, missing: false };
110858
+ }
110859
+ async function loadStableBootstrapFiles(workspaceDir, options) {
110860
+ const defaultFiles = await Promise.all(STABLE_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
110861
+ const extras = options?.extraStableFiles ?? [];
110862
+ if (extras.length === 0) {
110863
+ return defaultFiles;
110864
+ }
110865
+ const extraFiles = await Promise.all(extras.map((name) => loadNamedFile(workspaceDir, name)));
110866
+ return [...defaultFiles, ...extraFiles];
110867
+ }
110868
+ async function loadDynamicBootstrapFiles(workspaceDir, options) {
110869
+ const now2 = options?.now ?? new Date;
110870
+ const includeYesterday = options?.includeYesterday ?? true;
110871
+ const files = await Promise.all(DYNAMIC_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
110872
+ const todayRelative = dailyMemoryRelativePath(now2);
110873
+ const todayName = DEFAULT_MEMORY_FILENAME;
110874
+ const today = await loadNamedFile(workspaceDir, todayName, todayRelative);
110875
+ today.path = path8.join(workspaceDir, todayRelative);
110876
+ files.push(today);
110877
+ if (includeYesterday) {
110878
+ const yesterdayRelative = dailyMemoryRelativePath(addDays(now2, -1));
110879
+ const yesterday = await loadNamedFile(workspaceDir, todayName, yesterdayRelative);
110880
+ yesterday.path = path8.join(workspaceDir, yesterdayRelative);
110881
+ files.push(yesterday);
110882
+ }
110883
+ return files;
110884
+ }
110885
+ function pad22(n) {
110886
+ return n < 10 ? `0${n}` : String(n);
110887
+ }
110888
+ function dailyMemoryRelativePath(date) {
110889
+ const y = date.getFullYear();
110890
+ const m = pad22(date.getMonth() + 1);
110891
+ const d = pad22(date.getDate());
110892
+ return path8.join(DEFAULT_MEMORY_SUBDIR, `${y}-${m}-${d}.md`);
110893
+ }
110894
+ function addDays(date, days) {
110895
+ const out = new Date(date.getTime());
110896
+ out.setDate(out.getDate() + days);
110897
+ return out;
110898
+ }
110899
+ function truncateContent(name, content, maxChars) {
110900
+ if (content.length <= maxChars) {
110901
+ return content;
110902
+ }
110903
+ if (maxChars < 200) {
110904
+ return content.slice(0, Math.max(0, maxChars));
110905
+ }
110906
+ const headRoomInitial = Math.floor(maxChars * 0.7);
110907
+ const sampleMarker = `
110908
+ \u2026(truncated ${name}: kept ${headRoomInitial}+${Math.max(0, maxChars - headRoomInitial - 96)} chars of ${content.length})\u2026
110909
+ `;
110910
+ const markerReserve = Math.max(64, sampleMarker.length + 8);
110911
+ const headRoom = Math.min(headRoomInitial, Math.max(0, maxChars - markerReserve - 8));
110912
+ const tailRoom = Math.max(0, maxChars - headRoom - markerReserve);
110913
+ const head = content.slice(0, Math.max(0, headRoom));
110914
+ const tail = tailRoom > 0 ? content.slice(content.length - tailRoom) : "";
110915
+ const result = `${head}
110916
+ \u2026(truncated ${name}: kept ${headRoom}+${tailRoom} chars of ${content.length})\u2026
110917
+ ${tail}`;
110918
+ return result.length <= maxChars ? result : result.slice(0, maxChars);
110919
+ }
110920
+ function projectBootstrapFiles(params) {
110921
+ const {
110922
+ files,
110923
+ heading,
110924
+ budget,
110925
+ seenSignatures,
110926
+ warningMode = "error",
110927
+ warningMaxFiles = DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES
110928
+ } = params;
110929
+ const perFileCap = budget.bootstrapMaxChars;
110930
+ let remainingTotal = budget.bootstrapTotalMaxChars;
110931
+ const injectedFiles = [];
110932
+ const parts = [];
110933
+ for (const file of files) {
110934
+ if (file.missing || typeof file.content !== "string") {
110935
+ continue;
110936
+ }
110937
+ const raw = file.content.trimEnd();
110938
+ if (raw.length === 0) {
110939
+ continue;
110940
+ }
110941
+ const perFileAllowed = Math.min(perFileCap, remainingTotal);
110942
+ if (perFileAllowed <= 0) {
110943
+ break;
110944
+ }
110945
+ const injected = truncateContent(file.name, raw, perFileAllowed);
110946
+ const relativePath = file.path;
110947
+ injectedFiles.push({ path: relativePath, content: injected });
110948
+ parts.push(`## ${relativePath}`);
110949
+ parts.push(injected);
110950
+ remainingTotal -= injected.length;
110951
+ if (remainingTotal <= 0) {
110952
+ break;
110953
+ }
110954
+ }
110955
+ const concatenated = parts.length > 0 ? [heading.trim().length > 0 ? `# ${heading}` : null, ...parts].filter((p) => p !== null).join(`
110956
+
110957
+ `) : "";
110958
+ const stats = buildBootstrapInjectionStats({
110959
+ bootstrapFiles: files,
110960
+ injectedFiles
110961
+ });
110962
+ const analysis = analyzeBootstrapBudget({
110963
+ files: stats,
110964
+ bootstrapMaxChars: budget.bootstrapMaxChars,
110965
+ bootstrapTotalMaxChars: budget.bootstrapTotalMaxChars,
110966
+ nearLimitRatio: budget.nearLimitRatio ?? DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO
110967
+ });
110968
+ const warning = buildBootstrapPromptWarning({
110969
+ analysis,
110970
+ mode: warningMode,
110971
+ seenSignatures,
110972
+ maxFiles: warningMaxFiles
110973
+ });
110974
+ if (warningMode === "error" && analysis.hasTruncation) {
110975
+ const errorLines = [
110976
+ "Bootstrap budget exceeded. The following files exceed limits:",
110977
+ ""
110978
+ ];
110979
+ for (const file of analysis.truncatedFiles) {
110980
+ const excessChars = file.rawChars - file.injectedChars;
110981
+ const causes = file.causes.map((c) => c === "per-file-limit" ? `per-file limit (${analysis.totals.bootstrapMaxChars.toLocaleString()} chars)` : `total limit (${analysis.totals.bootstrapTotalMaxChars.toLocaleString()} chars)`);
110982
+ errorLines.push(` ${file.name}: ${file.rawChars.toLocaleString()} bytes (exceeds ${causes.join(", ")})`);
110983
+ errorLines.push(` Trim ${excessChars.toLocaleString()} bytes, or pass --warning-mode warn to proceed with truncation.`);
110984
+ }
110985
+ throw new BootstrapBudgetExceededError(analysis, errorLines.join(`
110986
+ `));
110987
+ }
110988
+ return { files, injectedFiles, concatenated, analysis, warning };
110989
+ }
110990
+ async function buildStableBootstrapPrompt(params) {
110991
+ const files = await loadStableBootstrapFiles(params.workspaceDir, {
110992
+ extraStableFiles: params.extraStableFiles
110993
+ });
110994
+ const budget = {
110995
+ bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
110996
+ bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS,
110997
+ nearLimitRatio: params.budget?.nearLimitRatio
110998
+ };
110999
+ return projectBootstrapFiles({
111000
+ files,
111001
+ heading: "Project Context (stable workspace files)",
111002
+ budget,
111003
+ seenSignatures: params.seenSignatures,
111004
+ warningMode: params.budget?.warningMode,
111005
+ warningMaxFiles: params.budget?.warningMaxFiles
111006
+ });
111007
+ }
111008
+ async function buildDynamicBootstrapPrompt(params) {
111009
+ const files = await loadDynamicBootstrapFiles(params.workspaceDir, {
111010
+ now: params.now,
111011
+ includeYesterday: params.includeYesterday
111012
+ });
111013
+ const budget = {
111014
+ bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
111015
+ bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_DYNAMIC_TOTAL_MAX_CHARS,
111016
+ nearLimitRatio: params.budget?.nearLimitRatio
111017
+ };
111018
+ return projectBootstrapFiles({
111019
+ files,
111020
+ heading: "Project Context (dynamic workspace files)",
111021
+ budget,
111022
+ seenSignatures: params.seenSignatures,
111023
+ warningMode: params.budget?.warningMode,
111024
+ warningMaxFiles: params.budget?.warningMaxFiles
111025
+ });
111026
+ }
111027
+
111028
+ // src/cli/debug.ts
111029
+ init_merge();
111030
+ init_hindsight2();
111031
+ function formatBytes(bytes) {
111032
+ return `${bytes.toLocaleString()} bytes`;
111033
+ }
111034
+ function estimateTokens(bytes) {
111035
+ return Math.round(bytes / 3.7);
111036
+ }
111037
+ function readMcpServerNames(agentDir) {
111038
+ const mcpPath = join88(agentDir, ".mcp.json");
111039
+ if (!existsSync82(mcpPath))
111040
+ return [];
111041
+ try {
111042
+ const parsed = JSON.parse(readFileSync74(mcpPath, "utf-8"));
111043
+ return Object.keys(parsed.mcpServers ?? {});
111044
+ } catch {
111045
+ return null;
111046
+ }
111047
+ }
111048
+ function sha2562(content) {
111049
+ return createHash15("sha256").update(content).digest("hex").slice(0, 16);
111050
+ }
111051
+ function findLatestTranscriptJsonl(claudeConfigDir) {
111052
+ const projectsDir = join88(claudeConfigDir, "projects");
111053
+ if (!existsSync82(projectsDir))
111054
+ return;
111055
+ try {
111056
+ const entries = readdirSync30(projectsDir, { withFileTypes: true });
111057
+ let latest;
111058
+ for (const entry of entries) {
111059
+ if (!entry.isDirectory())
111060
+ continue;
111061
+ const projectPath = join88(projectsDir, entry.name);
111062
+ const transcriptPath = join88(projectPath, "transcript.jsonl");
111063
+ if (!existsSync82(transcriptPath))
111064
+ continue;
111065
+ const stat2 = statSync48(transcriptPath);
111066
+ if (!latest || stat2.mtimeMs > latest.mtime) {
111067
+ latest = { path: transcriptPath, mtime: stat2.mtimeMs };
111068
+ }
111069
+ }
111070
+ return latest?.path;
111071
+ } catch {
111072
+ return;
111073
+ }
111074
+ }
111075
+ function extractLatestUserMessage(transcriptPath) {
111076
+ try {
111077
+ const content = readFileSync74(transcriptPath, "utf-8");
111078
+ const lines = content.trim().split(`
111079
+ `).filter(Boolean);
111080
+ for (let i2 = lines.length - 1;i2 >= 0; i2--) {
111081
+ const line = lines[i2];
111082
+ try {
111083
+ const event = JSON.parse(line);
111084
+ if (event.type === "message" && event.role === "user" && typeof event.content === "string") {
111085
+ const timestamp = event.timestamp ? new Date(event.timestamp).toLocaleString() : "unknown";
111086
+ return { text: event.content, timestamp };
111087
+ }
111088
+ } catch {
111089
+ continue;
111090
+ }
111091
+ }
111092
+ } catch {
111093
+ return;
111094
+ }
111095
+ }
111096
+ function buildProgressUpdateGuidance() {
111097
+ return `## Progress updates (human-style check-ins)
111098
+
111099
+ You're talking to a human colleague on Telegram. Alongside the emoji status
111100
+ ladder, send a short \`progress_update\` at inflection points, the moments a
111101
+ senior colleague would ping the person who asked them to do something:
111102
+
111103
+ - **Plan formed:** "Got it. Going to do X first, then Y, then Z."
111104
+ - **Pivot or blocker:** "First approach didn't work because <reason>. Trying
111105
+ <alternative> instead."
111106
+ - **Chunk finished:** "Done with X. Starting Y now."
111107
+
111108
+ Keep them short (one or two sentences). Don't narrate every step, the pinned
111109
+ progress card shows that for free. Don't send an update on a trivial one-shot
111110
+ task. Send them when a colleague would genuinely want to know what's happening.
111111
+
111112
+ Final answers still go through \`reply\` as usual,
111113
+ \`progress_update\` is only for mid-turn check-ins.`;
111114
+ }
111115
+ function registerDebugCommand(program3) {
111116
+ const cmd = program3.command("debug", { hidden: true }).description("Observability tools for inspecting agent prompt layering [advanced]");
111117
+ cmd.command("turn <agent>").description("Dump the exact prompt layering the model saw on the most recent turn").option("--last <n>", "Show N-th most recent turn instead of latest", "1").action(withConfigError(async (agentName, opts) => {
111118
+ const config = getConfig(program3);
111119
+ const agentConfig = config.agents[agentName];
111120
+ if (!agentConfig) {
111121
+ console.error(`Agent '${agentName}' not found in switchroom.yaml`);
111122
+ process.exit(1);
111123
+ }
111124
+ const agentsDir = resolveAgentsDir(config);
111125
+ const agentDir = resolve43(agentsDir, agentName);
111126
+ if (!existsSync82(agentDir)) {
111127
+ console.error(`Agent directory not found: ${agentDir}`);
111128
+ process.exit(1);
111129
+ }
111130
+ const workspaceDir = resolveAgentWorkspaceDir(agentDir);
111131
+ const claudeConfigDir = join88(agentDir, ".claude");
111132
+ const claudeMdPath2 = join88(agentDir, "CLAUDE.md");
111133
+ const soulMdPath = join88(agentDir, "SOUL.md");
111134
+ const workspaceSoulMdPath = join88(workspaceDir, "SOUL.md");
111135
+ const handoffPath = join88(agentDir, ".handoff.md");
111136
+ const lastN = parseInt(opts.last, 10);
111137
+ if (isNaN(lastN) || lastN < 1) {
111138
+ console.error("--last must be a positive integer");
111139
+ process.exit(1);
111140
+ }
111141
+ if (lastN > 1) {
111142
+ console.error("Note: --last N where N > 1 not yet implemented (only latest turn supported)");
111143
+ process.exit(1);
111144
+ }
111145
+ console.log(`=== Debug Turn Dump: ${agentName} ===
111146
+ `);
111147
+ console.log(`=== Append System Prompt (stable) ===
111148
+ `);
111149
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
111150
+ const useHotReloadStable = resolved.channels?.telegram?.hotReloadStable === true;
111151
+ const stableResult = await buildStableBootstrapPrompt({
111152
+ workspaceDir,
111153
+ budget: { warningMode: "off" }
111154
+ });
111155
+ if (useHotReloadStable) {
111156
+ console.log(`-- Workspace Stable Render (not used \u2014 stable content is in per-turn hook) --`);
111157
+ console.log();
111158
+ } else if (stableResult.concatenated.trim().length > 0) {
111159
+ console.log(`-- Workspace Stable Render (${formatBytes(stableResult.concatenated.length)}) --`);
111160
+ console.log(stableResult.concatenated);
111161
+ console.log();
111162
+ } else {
111163
+ console.log("-- Workspace Stable Render (0 bytes, not set up) --");
111164
+ console.log();
111165
+ }
111166
+ const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(resolved);
111167
+ const progressGuidance = useSwitchroomPlugin ? buildProgressUpdateGuidance() : "";
111168
+ if (progressGuidance.length > 0) {
111169
+ console.log(`-- Progress Updates Guidance (${formatBytes(progressGuidance.length)}) --`);
111170
+ console.log(progressGuidance);
111171
+ console.log();
111172
+ }
111173
+ const baseSystemPromptAppend = resolved.system_prompt_append ?? "";
111174
+ if (baseSystemPromptAppend.trim().length > 0) {
111175
+ console.log(`-- User System Prompt Append (${formatBytes(baseSystemPromptAppend.length)}) --`);
111176
+ console.log(baseSystemPromptAppend);
111177
+ console.log();
111178
+ }
111179
+ console.log(`=== Append System Prompt (per-session) ===
111180
+ `);
111181
+ const handoffContent = existsSync82(handoffPath) ? readFileSync74(handoffPath, "utf-8") : "";
111182
+ if (handoffContent.trim().length > 0) {
111183
+ console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
111184
+ console.log(handoffContent);
111185
+ console.log();
111186
+ } else {
111187
+ console.log("-- Handoff Briefing (0 bytes, no prior session) --");
111188
+ console.log();
111189
+ }
111190
+ console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
111191
+ `);
111192
+ const claudeMdContent = existsSync82(claudeMdPath2) ? readFileSync74(claudeMdPath2, "utf-8") : "";
111193
+ if (claudeMdContent.trim().length > 0) {
111194
+ console.log(`(${formatBytes(claudeMdContent.length)})`);
111195
+ console.log(claudeMdContent);
111196
+ console.log();
111197
+ } else {
111198
+ console.log("(0 bytes, not present)");
111199
+ console.log();
111200
+ }
111201
+ console.log(`=== Persona (SOUL.md) ===
111202
+ `);
111203
+ const soulMdContent = existsSync82(soulMdPath) ? readFileSync74(soulMdPath, "utf-8") : existsSync82(workspaceSoulMdPath) ? readFileSync74(workspaceSoulMdPath, "utf-8") : "";
111204
+ if (soulMdContent.trim().length > 0) {
111205
+ console.log(`(${formatBytes(soulMdContent.length)})`);
111206
+ console.log(soulMdContent);
111207
+ console.log();
111208
+ } else {
111209
+ console.log("(0 bytes, stale placeholder \u2014 Phase 2 item: single source of truth for persona)");
111210
+ console.log();
111211
+ }
111212
+ console.log(`=== Per-Turn Injections (UserPromptSubmit) ===
111213
+ `);
111214
+ if (useHotReloadStable) {
111215
+ if (stableResult.concatenated.trim().length > 0) {
111216
+ console.log(`-- Workspace Stable (hot-reload hook): ${formatBytes(stableResult.concatenated.length)} --`);
111217
+ console.log(stableResult.concatenated);
111218
+ console.log();
111219
+ } else {
111220
+ console.log("-- Workspace Stable (hot-reload hook): 0 bytes, not set up --");
111221
+ console.log();
111222
+ }
111223
+ }
111224
+ const dynamicResult = await buildDynamicBootstrapPrompt({
111225
+ workspaceDir,
111226
+ budget: { warningMode: "off" }
111227
+ });
111228
+ if (dynamicResult.concatenated.trim().length > 0) {
111229
+ console.log(`-- Workspace Dynamic: fired, ${formatBytes(dynamicResult.concatenated.length)} --`);
111230
+ console.log(dynamicResult.concatenated);
111231
+ console.log();
111232
+ } else {
111233
+ console.log("-- Workspace Dynamic: no content (MEMORY.md and daily notes empty or missing) --");
111234
+ console.log();
111235
+ }
111236
+ const hindsightEnabled = isHindsightEnabled(config) && agentConfig.memory?.auto_recall !== false;
111237
+ if (hindsightEnabled) {
111238
+ console.log("-- Hindsight Recall: enabled (exact content unavailable, check hindsight logs) --");
111239
+ console.log();
111240
+ } else {
111241
+ console.log("-- Hindsight Recall: disabled --");
111242
+ console.log();
111243
+ }
111244
+ console.log(`=== User Message (latest turn) ===
111245
+ `);
111246
+ const transcriptPath = findLatestTranscriptJsonl(claudeConfigDir);
111247
+ const userMessage = transcriptPath ? extractLatestUserMessage(transcriptPath) : undefined;
111248
+ if (userMessage) {
111249
+ console.log(`(Turn timestamp: ${userMessage.timestamp})`);
111250
+ console.log(userMessage.text);
111251
+ console.log();
111252
+ } else {
111253
+ console.log("(unavailable: no transcript found or transcript empty)");
111254
+ console.log();
111255
+ }
111256
+ console.log(`=== Totals ===
111257
+ `);
111258
+ const stableBytes = stableResult.concatenated.length + progressGuidance.length + baseSystemPromptAppend.length;
111259
+ const perSessionBytes = handoffContent.length;
111260
+ const claudeMdBytes = claudeMdContent.length;
111261
+ const soulMdBytes = soulMdContent.length;
111262
+ const perTurnBytes = dynamicResult.concatenated.length;
111263
+ const userBytes = userMessage?.text.length ?? 0;
111264
+ const fleetDir = join88(agentsDir, "..", "fleet");
111265
+ const fleetInvPath = join88(fleetDir, "switchroom-invariants.md");
111266
+ const fleetClaudePath = join88(fleetDir, "CLAUDE.md");
111267
+ const fleetInvBytes = existsSync82(fleetInvPath) ? readFileSync74(fleetInvPath, "utf-8").length : 0;
111268
+ const fleetClaudeBytes = existsSync82(fleetClaudePath) ? readFileSync74(fleetClaudePath, "utf-8").length : 0;
111269
+ const fleetBytes = fleetInvBytes + fleetClaudeBytes;
111270
+ const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
111271
+ console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
111272
+ console.log(`Per-session: ${formatBytes(perSessionBytes).padEnd(20)} (cache-warm until next session)`);
111273
+ console.log(`CLAUDE.md (cwd): ${formatBytes(claudeMdBytes).padEnd(20)} (cache-hot)`);
111274
+ console.log(`Fleet invariants: ${formatBytes(fleetBytes).padEnd(20)} (--add-dir, cache-hot)`);
111275
+ console.log(`Per-turn: ${formatBytes(perTurnBytes).padEnd(20)} (never cached \u2014 the real per-turn $ cost)`);
111276
+ console.log(`User message: ${formatBytes(userBytes).padEnd(20)}`);
111277
+ console.log(`Authored text: ${formatBytes(totalBytes).padEnd(20)} (~${estimateTokens(totalBytes).toLocaleString()} tokens est.)`);
111278
+ const mcpServers = readMcpServerNames(agentDir);
111279
+ const mcpCount = mcpServers?.length ?? null;
111280
+ const mcpEstK = mcpCount != null ? mcpCount * 3 : null;
111281
+ const mcpLabel = mcpCount != null ? `${mcpCount} servers` : "(unreadable \u2014 agent-private .mcp.json)";
111282
+ const mcpEstLabel = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k tok` : "~30k tok (audit est.)";
111283
+ console.log(`MCP tool surface: ${mcpLabel.padEnd(20)} (NOT counted above; ~3k tok/server \u2248 ${mcpEstLabel} \u2014 deferred under tool search)`);
111284
+ if (mcpServers && mcpServers.length > 0) {
111285
+ console.log(` [${mcpServers.join(", ")}]`);
111286
+ }
111287
+ const floorMcp = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k` : "~30k";
111288
+ console.log(`Per-turn FLOOR: ${`~${estimateTokens(totalBytes).toLocaleString()} + ${floorMcp} MCP + ~13k CLI-base`.padEnd(20)} tokens est. (before the user msg, recall, or tool results)`);
111289
+ const stableCacheInput = stableResult.concatenated + progressGuidance + baseSystemPromptAppend;
111290
+ const stableHash = sha2562(stableCacheInput);
111291
+ console.log(`Cache stable hash: sha256:${stableHash}`);
111292
+ console.log();
111293
+ }));
111294
+ }
111295
+
111296
+ // src/memory/directive-residue.ts
111297
+ var RESIDUE_CATEGORIES = new Set([
111298
+ "rules-block",
111299
+ "reflect-directive"
111300
+ ]);
111301
+ function renderInjectedLine(index, directive) {
111302
+ const priority = directive.priority ?? 0;
111303
+ const name = (directive.name ?? "").trim() || "(unnamed)";
111304
+ const content = (directive.content ?? "").trim();
111305
+ return `${index}. [P${priority}] ${name}: ${content}`;
111306
+ }
111307
+ function measureDirectiveResidue(agent, rows, directivesById) {
111308
+ const residueRows = rows.filter((r) => r.isActive && RESIDUE_CATEGORIES.has(r.category));
111309
+ const lines = residueRows.map((row, i2) => {
111310
+ const directive = directivesById.get(row.id);
111311
+ if (!directive) {
111312
+ throw new Error(`measureDirectiveResidue: row '${row.name}' (id ${row.id}) has no ` + "matching directive in directivesById \u2014 pass the SAME list the " + "rows were built from.");
111313
+ }
111314
+ return renderInjectedLine(i2 + 1, directive);
111315
+ });
111316
+ const residueBytes = lines.reduce((sum, line) => sum + Buffer.byteLength(line, "utf8") + 1, 0);
111317
+ return {
111318
+ agent,
111319
+ residueDirectiveCount: residueRows.length,
111320
+ totalDirectiveCount: rows.length,
111321
+ residueBytes,
111322
+ residueTokensEstimate: estimateTokens(residueBytes)
111323
+ };
111324
+ }
111325
+
111326
+ // src/memory/directive-flip.ts
111327
+ init_rules_block();
111328
+ function evaluateFlipReadiness(measurement, opts) {
111329
+ const reasons = [];
111330
+ if (!opts.rulesBlockEnabled) {
111331
+ reasons.push("memory.rules_block (M1) is not enabled \u2014 enable the rules-block " + "toolchain and migrate the directives into the block BEFORE flipping " + "inject_directives off (two-flag ordering; flipping first strips every " + "guardrail).");
111332
+ }
111333
+ if (measurement.residueBytes > RULES_BLOCK_BUDGET_BYTES) {
111334
+ reasons.push(`directive residue is ${measurement.residueBytes}B, over the ` + `${RULES_BLOCK_BUDGET_BYTES}B rules-block budget (` + `${measurement.residueDirectiveCount} active residue directive(s)). ` + "Triage the directive set down \u2014 retire stale rules or shorten bodies \u2014 " + "until the residue fits, then re-check. The budget, not MAX_DIRECTIVES, " + "is the binding gate.");
111335
+ }
111336
+ return {
111337
+ agent: measurement.agent,
111338
+ ready: reasons.length === 0,
111339
+ reasons,
111340
+ residueBytes: measurement.residueBytes,
111341
+ budgetBytes: RULES_BLOCK_BUDGET_BYTES,
111342
+ residueDirectiveCount: measurement.residueDirectiveCount
111343
+ };
111344
+ }
111345
+ async function measureLiveResidue(admin, agent) {
111346
+ const directives = await admin.list();
111347
+ const rows = buildDirectiveTriageRows(directives);
111348
+ const byId = new Map(directives.map((d) => [d.id, d]));
111349
+ return measureDirectiveResidue(agent, rows, byId);
111350
+ }
111351
+ function flipConfigStanza(agent) {
111352
+ return [
111353
+ `# ${agent} \u2014 Memory v2 M3 Surface-A flip (suppress <active_directives>)`,
111354
+ "memory:",
111355
+ " rules_block: true # M1 must already be on (checked)",
111356
+ " inject_directives: false # M3: stop re-injecting migrated directives"
111357
+ ].join(`
111358
+ `);
111359
+ }
111360
+
111361
+ // src/cli/memory-flip.ts
111362
+ function resolveFlipAdmin(program3, agent) {
111363
+ const config = getConfig(program3);
111364
+ if (!config.agents[agent]) {
111365
+ console.error(source_default.red(`Agent "${agent}" is not defined in switchroom.yaml`));
111366
+ process.exit(1);
111367
+ }
111368
+ if (!isHindsightEnabled(config)) {
111369
+ console.error(source_default.red("Hindsight memory is not enabled in this switchroom.yaml."));
111370
+ process.exit(1);
111371
+ }
111372
+ const agentConfig = config.agents[agent];
111373
+ const mcpBaseUrl = config.memory?.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
111374
+ const apiBaseUrl = mcpBaseUrl.replace(/\/mcp\/?$/, "").replace(/\/+$/, "");
111375
+ const bankId = agentConfig?.memory?.collection ?? agent;
111376
+ return new DirectiveAdmin({ apiBaseUrl, bankId });
111377
+ }
111378
+ function readRulesBlockEnabled(program3, agent) {
111379
+ const config = getConfig(program3);
111380
+ return config.agents[agent]?.memory?.rules_block === true;
111381
+ }
111382
+ function buildFlipPreflightJson(readiness, rulesBlockEnabled) {
111383
+ return {
111384
+ ok: true,
111385
+ agent: readiness.agent,
111386
+ ready: readiness.ready,
111387
+ rules_block: rulesBlockEnabled,
111388
+ residue_bytes: readiness.residueBytes,
111389
+ budget_bytes: readiness.budgetBytes,
111390
+ fits_budget: readiness.residueBytes <= readiness.budgetBytes,
111391
+ residue_directive_count: readiness.residueDirectiveCount,
111392
+ reasons: readiness.reasons
111393
+ };
111394
+ }
111395
+ function renderFlipPreflight(readiness, rulesBlockEnabled) {
111396
+ const fits = readiness.residueBytes <= readiness.budgetBytes;
111397
+ const lines = [];
111398
+ const verdict = readiness.ready ? source_default.green("READY") : source_default.red("NOT-READY");
111399
+ lines.push(`${verdict} \u2014 ${readiness.agent} M3 flip preflight`);
111400
+ lines.push("");
111401
+ lines.push(` residue: ${readiness.residueBytes}B / ${readiness.budgetBytes}B budget ` + `(${readiness.residueDirectiveCount} active residue directive(s)) ` + (fits ? source_default.green("fits") : source_default.red("OVER budget")));
111402
+ lines.push(` rules_block: ${rulesBlockEnabled ? source_default.green("on") : source_default.red("off")}`);
111403
+ if (readiness.ready) {
111404
+ lines.push("");
111405
+ lines.push(" Apply this per-agent stanza to switchroom.yaml, then restart:");
111406
+ lines.push("");
111407
+ for (const l of flipConfigStanza(readiness.agent).split(`
111408
+ `)) {
111409
+ lines.push(` ${l}`);
111410
+ }
111411
+ } else {
111412
+ lines.push("");
111413
+ lines.push(source_default.red(" Blocked:"));
111414
+ for (const reason of readiness.reasons) {
111415
+ lines.push(` - ${reason}`);
111416
+ }
111417
+ }
111418
+ return lines.join(`
111419
+ `);
111420
+ }
111421
+ function registerMemoryFlipCommand(memory, program3, deps = {}) {
111422
+ const makeAdmin = deps.makeAdmin ?? resolveFlipAdmin;
111423
+ memory.command("flip-preflight <agent>").description("Deterministic M3 (Surface-A) flip-readiness gate for <agent>: measure " + "the live post-triage directive residue, check it fits the 6144B " + "rules-block budget AND that memory.rules_block is already on, and " + "print READY + the exact switchroom.yaml stanza, or NOT-READY + why. " + "Never mutates config \u2014 the flip itself is an operator-gated edit.").option("--json", "Machine-readable output").action(withConfigError(async (agent, opts) => {
111424
+ const admin = makeAdmin(program3, agent);
111425
+ const rulesBlockEnabled = readRulesBlockEnabled(program3, agent);
111426
+ let ready;
111427
+ try {
111428
+ const measurement = await measureLiveResidue(admin, agent);
111429
+ const readiness = evaluateFlipReadiness(measurement, { rulesBlockEnabled });
111430
+ ready = readiness.ready;
111431
+ if (opts.json) {
111432
+ console.log(JSON.stringify(buildFlipPreflightJson(readiness, rulesBlockEnabled)));
111433
+ } else {
111434
+ console.log(renderFlipPreflight(readiness, rulesBlockEnabled));
111435
+ }
111436
+ } catch (e) {
111437
+ const msg = e instanceof Error ? e.message : String(e);
111438
+ if (opts.json) {
111439
+ console.log(JSON.stringify({ ok: false, error: msg }));
111440
+ } else {
111441
+ console.error(source_default.red(`\u2717 flip preflight for ${agent} failed: ${msg}`));
111442
+ }
111443
+ process.exit(1);
111444
+ return;
111445
+ }
111446
+ if (!ready)
111447
+ process.exit(2);
111448
+ }));
111449
+ }
111450
+
111451
+ // src/cli/memory.ts
111452
+ function persistMemoryConfigUrl(configPath, provider, url) {
111453
+ if (!existsSync83(configPath))
111454
+ return false;
111455
+ const raw = readFileSync75(configPath, "utf-8");
111456
+ const doc = import_yaml16.default.parseDocument(raw);
111457
+ if (!doc.has("memory")) {
111458
+ doc.set("memory", { backend: "hindsight", shared_collection: "shared", config: { provider, url } });
111459
+ } else {
111460
+ const memNode = doc.get("memory");
111461
+ if (!memNode.has("config")) {
111462
+ memNode.set("config", { provider, url });
111463
+ } else {
111464
+ const configNode = memNode.get("config");
111465
+ configNode.set("url", url);
111466
+ if (!configNode.has("provider")) {
111467
+ configNode.set("provider", provider);
111468
+ }
111469
+ }
111470
+ }
111471
+ let mode = 420;
111472
+ try {
111473
+ mode = statSync49(configPath).mode & 511;
111474
+ } catch {}
111475
+ writeConfigFileSync(configPath, doc.toString(), mode);
111476
+ return true;
111477
+ }
111478
+ function resolveMemorySetupTag(input) {
111479
+ const explicit = input.explicitTag?.trim();
111480
+ if (explicit) {
111481
+ if (explicit.toLowerCase() === "latest") {
111482
+ return { tag: undefined, reason: "explicit-latest" };
111483
+ }
111484
+ const normalized = normalizeHindsightVersionTag(explicit);
111485
+ if (!normalized)
111486
+ return { tag: explicit, reason: "invalid" };
111487
+ return { tag: normalized, reason: "explicit" };
111488
+ }
111489
+ const pin = normalizeHindsightVersionTag(input.releasePin);
111490
+ if (pin)
111491
+ return { tag: pin, reason: "pin" };
111492
+ return { tag: undefined, reason: "latest" };
111493
+ }
111494
+ function hindsightSecretValues(input) {
111495
+ const out = [];
111496
+ const push = (v) => {
111497
+ if (typeof v === "string" && v.trim().length > 0)
111498
+ out.push(v.trim());
111499
+ };
111500
+ push(input.cpAccessKey);
111501
+ push(input.llm?.api_key);
111502
+ for (const op of ["retain", "reflect", "consolidation"]) {
111503
+ push(input.llm?.[op]?.api_key);
111504
+ }
111505
+ return out;
111506
+ }
111507
+ function hasUnresolvedHindsightVaultRef(config) {
111508
+ const hindsight = config.hindsight;
111509
+ return hindsightSecretValues({
111510
+ llm: hindsight?.llm,
111511
+ cpAccessKey: hindsight?.cp_access_key
111512
+ }).some((v) => isVaultReference(v));
111513
+ }
111514
+ function emitsCleartextHindsightSecret(input) {
111515
+ return hindsightSecretValues(input).some((v) => !isVaultReference(v));
111516
+ }
111517
+ var HINDSIGHT_COMPOSE_CLEARTEXT_SECRETS_WARNING = "This snippet embeds secrets in CLEARTEXT (LLM api_key and/or cp_access_key)." + " Treat the output as sensitive \u2014 do not paste it into shared channels or" + " commit it unredacted.";
111518
+ async function resolveLiteLLMForHindsight(config) {
111519
+ return resolveHindsightLiteLlm(config);
111520
+ }
111521
+ function readRecallLog(agentDir, limit) {
111522
+ const path9 = join89(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
111523
+ if (!existsSync83(path9))
111524
+ return [];
111525
+ let raw;
111526
+ try {
111527
+ raw = readFileSync75(path9, "utf-8");
111528
+ } catch {
111529
+ return [];
111530
+ }
111531
+ const lines = raw.split(`
111532
+ `).filter((l) => l.trim().length > 0);
111533
+ const tail = lines.slice(-limit);
111534
+ const out = [];
111535
+ for (const line of tail) {
111536
+ try {
111537
+ out.push(JSON.parse(line));
111538
+ } catch {}
111539
+ }
111540
+ return out;
111541
+ }
111542
+ function formatRecallLogView(name, entries) {
111543
+ const lines = [source_default.bold(`
111544
+ ${name}:`)];
111545
+ const total = entries.length;
111546
+ const hits = entries.filter((e) => e.cache_hit).length;
111547
+ const cappedTurns = entries.filter((e) => e.capped).length;
111548
+ const omittedTurns = entries.filter((e) => typeof e.directives_omitted === "number" && e.directives_omitted > 0).length;
111549
+ const memCounts = entries.map((e) => e.result_count).filter((n) => typeof n === "number");
111550
+ const avg = memCounts.length > 0 ? Math.round(memCounts.reduce((s, n) => s + n, 0) / memCounts.length * 10) / 10 : null;
111551
+ const max = memCounts.length > 0 ? Math.max(...memCounts) : null;
111552
+ const medians = entries.map((e) => e.injected_score_median).filter((n) => typeof n === "number");
111553
+ const avgScore = medians.length > 0 ? Math.round(medians.reduce((s, n) => s + n, 0) / medians.length * 100) / 100 : null;
111554
+ const collapseTurns = entries.filter((e) => typeof e.injected_own_bank_count === "number" && e.injected_own_bank_count === 0 && typeof e.injected_additional_bank_count === "number" && e.injected_additional_bank_count > 0).length;
111555
+ lines.push(source_default.gray(` last ${total} turn${total === 1 ? "" : "s"}: ` + `avg=${avg ?? "\u2014"} max=${max ?? "\u2014"} ` + `avg_score=${avgScore ?? "\u2014"} ` + `cache_hits=${hits} capped=${cappedTurns}` + (omittedTurns > 0 ? ` directives_omitted_turns=${omittedTurns}` : "") + (collapseTurns > 0 ? source_default.red(` own_bank_collapse_turns=${collapseTurns}`) : "")));
111556
+ for (const e of entries) {
111557
+ const flag = e.cache_hit ? source_default.cyan("CACHE") : e.capped ? source_default.yellow("CAP") : source_default.green("OK");
111558
+ const dem = e.demoted_count && e.demoted_count > 0 ? source_default.dim(` -${e.demoted_count}d`) : "";
111559
+ const omitted = typeof e.directives_omitted === "number" && e.directives_omitted > 0 ? source_default.yellow(` +${e.directives_omitted}omitted`) : "";
111560
+ const ids = e.memory_ids && e.memory_ids.length > 0 ? source_default.dim(` ids=${e.memory_ids.slice(0, 3).join(",")}${e.memory_ids.length > 3 ? `\u2026+${e.memory_ids.length - 3}` : ""}`) : "";
111561
+ const own = e.injected_own_bank_count;
111562
+ const additional = e.injected_additional_bank_count;
111563
+ const composition = typeof own === "number" || typeof additional === "number" ? (() => {
111564
+ const text = ` own/add=${own ?? "\u2014"}/${additional ?? "\u2014"}`;
111565
+ return own === 0 && typeof additional === "number" && additional > 0 ? source_default.red(text) : source_default.dim(text);
111566
+ })() : "";
111567
+ const score = typeof e.injected_score_min === "number" && typeof e.injected_score_median === "number" && typeof e.injected_score_max === "number" ? source_default.dim(` score=${e.injected_score_min.toFixed(2)}/` + `${e.injected_score_median.toFixed(2)}/` + `${e.injected_score_max.toFixed(2)}`) : "";
111568
+ lines.push(` ${source_default.gray(e.ts)} ${flag} ` + `n=${e.result_count ?? "\u2014"}${e.pre_cap_count != null && e.pre_cap_count !== e.result_count ? `/${e.pre_cap_count}` : ""}` + `${dem}${omitted}${score}${composition}${ids}`);
111569
+ }
111570
+ return lines;
111571
+ }
111572
+ function registerMemoryCommand(program3) {
111573
+ const memory = program3.command("memory").description("Hindsight memory operations");
111574
+ memory.command("search <query>").description("Search agent memories via Hindsight").option("-a, --agent <name>", "Search a specific agent's collection").action(withConfigError(async (query, opts) => {
111575
+ const config = getConfig(program3);
111576
+ if (opts.agent) {
111577
+ if (!config.agents[opts.agent]) {
111578
+ console.error(source_default.red(`Agent "${opts.agent}" is not defined in switchroom.yaml`));
111579
+ process.exit(1);
111580
+ }
111581
+ const collection = getCollectionForAgent(opts.agent, config);
111582
+ console.log(source_default.bold(`
111583
+ Search: ${opts.agent} (collection: ${collection})
111584
+ `));
111585
+ console.log(source_default.gray(` $ ${searchMemory(query, collection)}`));
111586
+ console.log();
111587
+ return;
111588
+ }
111589
+ const agentNames = Object.keys(config.agents);
111590
+ console.log(source_default.bold(`
111591
+ Searching all eligible collections:
111592
+ `));
111593
+ for (const name of agentNames) {
111594
+ const collection = getCollectionForAgent(name, config);
111595
+ if (isStrictIsolation(name, config)) {
111596
+ console.log(source_default.gray(` ${name} (${collection}) \u2014 skipped (strict isolation)`));
111597
+ continue;
111598
+ }
111599
+ console.log(source_default.cyan(` ${name} (${collection}):`));
111600
+ console.log(source_default.gray(` $ ${searchMemory(query, collection)}`));
111601
+ }
111602
+ console.log();
111603
+ }));
111604
+ memory.command("stats").description("List agents with their collection names and isolation mode").action(withConfigError(async () => {
111605
+ const config = getConfig(program3);
111606
+ const agentNames = Object.keys(config.agents);
111607
+ if (agentNames.length === 0) {
111608
+ console.log(source_default.yellow("No agents defined in switchroom.yaml"));
111609
+ return;
111610
+ }
111611
+ const headers = ["Agent", "Collection", "Isolation", "Auto-recall"];
111612
+ const widths = [20, 20, 12, 12];
111613
+ const headerLine = headers.map((h, i2) => source_default.bold(h.padEnd(widths[i2]))).join(" ");
111614
+ console.log(`
111615
+ ${headerLine}`);
111616
+ for (const name of agentNames) {
111617
+ const collection = getCollectionForAgent(name, config);
111618
+ const isolation = isStrictIsolation(name, config) ? "strict" : "default";
111619
+ const autoRecall = config.agents[name].memory?.auto_recall ?? true;
111620
+ const row = [
111621
+ name.padEnd(widths[0]),
111622
+ collection.padEnd(widths[1]),
111623
+ isolation.padEnd(widths[2]),
111624
+ (autoRecall ? "yes" : "no").padEnd(widths[3])
111625
+ ].join(" ");
111626
+ console.log(` ${row}`);
111627
+ }
111628
+ console.log();
111629
+ const profileBanks = collectProfileBanks(config);
111630
+ if (profileBanks.size > 0) {
111631
+ const owners = new Map;
111632
+ for (const [uname, u] of Object.entries(config.users ?? {})) {
111633
+ if (u.profile_bank) {
111634
+ owners.set(u.profile_bank, [
111635
+ ...owners.get(u.profile_bank) ?? [],
111636
+ uname
111637
+ ]);
111638
+ }
111639
+ }
111640
+ console.log(source_default.bold(` Profile banks (per-user / shared):
111641
+ `));
111642
+ for (const bank of [...profileBanks].sort()) {
111643
+ const who = owners.get(bank);
111644
+ const label = who ? `user: ${who.join(", ")}` : "shared";
111645
+ console.log(` ${bank.padEnd(widths[1])} ${source_default.gray(label)}`);
111646
+ }
111647
+ console.log();
111648
+ }
111649
+ console.log(source_default.bold(` Hindsight CLI commands:
111650
+ `));
111651
+ for (const name of agentNames) {
111652
+ const collection = getCollectionForAgent(name, config);
111653
+ console.log(source_default.gray(` $ ${getMemoryStats(collection)}`));
111654
+ }
111655
+ console.log();
111656
+ }));
111657
+ memory.command("reflect").description("Show cross-agent reflection plan").action(withConfigError(async () => {
111658
+ const config = getConfig(program3);
111659
+ const { eligible, excluded, commands } = reflectAcrossAgents(config);
111660
+ console.log(source_default.bold(`
111661
+ Cross-agent reflection plan
111662
+ `));
111663
+ if (eligible.length > 0) {
111664
+ console.log(source_default.green(" Eligible collections:"));
111665
+ for (const { agent, collection } of eligible) {
111666
+ console.log(source_default.white(` ${agent} -> ${collection}`));
111667
+ }
111668
+ }
111669
+ if (excluded.length > 0) {
111670
+ console.log(source_default.red(`
111671
+ Excluded (strict isolation):`));
111672
+ for (const { agent, collection } of excluded) {
111673
+ console.log(source_default.gray(` ${agent} -> ${collection}`));
111674
+ }
111675
+ }
111676
+ if (commands.length > 0) {
111677
+ console.log(source_default.bold(`
111678
+ Hindsight CLI commands:
111679
+ `));
111680
+ for (const cmd of commands) {
111681
+ console.log(source_default.gray(` $ ${cmd}`));
111682
+ }
111683
+ } else {
111684
+ console.log(source_default.yellow(`
111685
+ No eligible collections for reflection.`));
111686
+ }
111687
+ console.log();
111688
+ }));
111689
+ memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current. Also required after any `hindsight.llm` model/provider config change \u2014 env is only re-derived on recreate, a plain docker restart is not enough (see docs/operators/hindsight-model-change.md).").option("--tag <version>", "Pin the hindsight image tag to pull + run (e.g. v0.15.18), overriding the " + "default floating `:latest`. Threaded through by `switchroom rollout` so a " + "version-pinned roll recreates hindsight on the SAME tag as the rest of the " + "fleet. Omit for `:latest` (the standalone default).").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").option("--strict-env", "Refuse the recreate (before the running container is touched) if it would drop " + "env vars that are live on the container but declared in neither `hindsight.env` " + "nor switchroom's managed defaults. Default is to warn loudly and proceed, so a " + "rollout is never wedged half-way with memory down.").option("--gpu", "Force GPU passthrough on for this launch, overriding host autodetection. " + "Use when `~/.switchroom/host-capabilities.json` is wrong or unreadable and " + "you know the host has a working nvidia container toolkit (`docker run --gpus " + "all` hard-fails container create without one). Beats `hindsight.gpu`.").option("--no-gpu", "Force GPU passthrough off for this launch, overriding host autodetection. " + "Also the explicit opt-out for the recreate GPU drop guard.").option("--allow-gpu-drop", "Proceed with a --recreate that would take GPU passthrough away from the " + "running container. Without this the recreate refuses, because that drop " + "moves the embedding model and cross-encoder reranker to CPU on the " + "interactive recall path with no other signal.").action(async (opts) => {
111690
+ if (opts.status) {
111691
+ if (!isDockerAvailable()) {
111692
+ console.log(source_default.red(" Docker is not available."));
111693
+ process.exit(1);
111694
+ }
111695
+ const status = getHindsightStatus();
111696
+ if (status) {
111697
+ console.log(source_default.bold(`
111698
+ Hindsight container status:`));
111699
+ console.log(` ${source_default.cyan("switchroom-hindsight")}: ${status}
111700
+ `);
111701
+ } else {
111702
+ console.log(source_default.yellow(`
111703
+ Hindsight container not found.
111704
+ `));
111705
+ console.log(source_default.gray(" Run 'switchroom memory setup' to start it."));
111706
+ }
111707
+ return;
111708
+ }
111709
+ if (opts.stop) {
111710
+ if (!isDockerAvailable()) {
111711
+ console.log(source_default.red(" Docker is not available."));
111712
+ process.exit(1);
111713
+ }
111714
+ if (!isHindsightContainerExists()) {
111715
+ console.log(source_default.yellow(" No switchroom-hindsight container found."));
111716
+ return;
111717
+ }
111718
+ console.log(source_default.gray(" Stopping switchroom-hindsight..."));
111719
+ stopHindsightRecallPool();
111720
+ stopHindsight();
111721
+ console.log(source_default.green(" Hindsight container stopped and removed."));
111722
+ return;
111723
+ }
111724
+ if (!isDockerAvailable()) {
111725
+ console.log(source_default.red(`
111726
+ Docker is not available.`));
111727
+ console.log(source_default.gray(` Install Docker: https://docs.docker.com/get-docker/
111728
+ `));
111729
+ process.exit(1);
111730
+ }
111731
+ const recreate = opts.recreate === true;
111732
+ let resolvedLlmPromise;
111733
+ const getResolvedLlm = () => {
111734
+ if (!resolvedLlmPromise) {
111735
+ resolvedLlmPromise = resolveHindsightLlmSecrets(getConfig(program3).hindsight?.llm);
111736
+ }
111737
+ return resolvedLlmPromise;
111738
+ };
111739
+ let releasePin;
111740
+ try {
111741
+ releasePin = getConfig(program3).release?.pin ?? undefined;
111742
+ } catch {
111743
+ releasePin = undefined;
111744
+ }
111745
+ const { tag: effectiveTag, reason: tagReason } = resolveMemorySetupTag({
110824
111746
  explicitTag: opts.tag,
110825
111747
  releasePin
110826
111748
  });
@@ -111366,9 +112288,9 @@ Repairing vector index coverage for ${target}${opts.dryRun ? source_default.yell
111366
112288
  captured += chunk2.toString();
111367
112289
  process.stdout.write(chunk2);
111368
112290
  });
111369
- const childExit = await new Promise((resolve43) => {
111370
- child.on("error", () => resolve43(127));
111371
- child.on("close", (c) => resolve43(c ?? 1));
112291
+ const childExit = await new Promise((resolve44) => {
112292
+ child.on("error", () => resolve44(127));
112293
+ child.on("close", (c) => resolve44(c ?? 1));
111372
112294
  });
111373
112295
  const outcome = classifyRepairOutcome({
111374
112296
  childExit,
@@ -111393,7 +112315,7 @@ Repairing vector index coverage for ${target}${opts.dryRun ? source_default.yell
111393
112315
  process.exit(1);
111394
112316
  })() : Object.keys(config.agents);
111395
112317
  for (const name of targets) {
111396
- const agentDir = join88(agentsDir, name);
112318
+ const agentDir = join89(agentsDir, name);
111397
112319
  const entries = readRecallLog(agentDir, limit);
111398
112320
  if (opts.json) {
111399
112321
  for (const e of entries) {
@@ -111586,11 +112508,12 @@ Observation-scope cleanup \u2014 DRY RUN (no writes)
111586
112508
  }));
111587
112509
  registerMemoryRuleCommand(memory, program3);
111588
112510
  registerMemoryDirectiveCommand(memory, program3);
112511
+ registerMemoryFlipCommand(memory, program3);
111589
112512
  }
111590
112513
 
111591
112514
  // src/cli/web.ts
111592
112515
  init_source();
111593
- import { join as join100 } from "node:path";
112516
+ import { join as join101 } from "node:path";
111594
112517
  import { homedir as homedir50 } from "node:os";
111595
112518
 
111596
112519
  // src/web/server.ts
@@ -111598,8 +112521,8 @@ init_merge();
111598
112521
  init_loader();
111599
112522
  init_client();
111600
112523
  import {
111601
- readFileSync as readFileSync82,
111602
- existsSync as existsSync90,
112524
+ readFileSync as readFileSync83,
112525
+ existsSync as existsSync91,
111603
112526
  realpathSync as realpathSync7,
111604
112527
  mkdirSync as mkdirSync50,
111605
112528
  openSync as openSync17,
@@ -111607,7 +112530,7 @@ import {
111607
112530
  writeSync as writeSync8,
111608
112531
  constants as fsConstants6
111609
112532
  } from "node:fs";
111610
- import { resolve as resolve47, extname, join as join99, relative as relative5, dirname as dirname36 } from "node:path";
112533
+ import { resolve as resolve48, extname, join as join100, relative as relative5, dirname as dirname36 } from "node:path";
111611
112534
  import { homedir as homedir49 } from "node:os";
111612
112535
  import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes15 } from "node:crypto";
111613
112536
 
@@ -111616,8 +112539,8 @@ init_lifecycle();
111616
112539
  init_manager();
111617
112540
  init_hindsight2();
111618
112541
  import { spawnSync as spawnSync17 } from "node:child_process";
111619
- import { existsSync as existsSync86, readFileSync as readFileSync77, statSync as statSync51 } from "node:fs";
111620
- import { resolve as resolve44, join as join95 } from "node:path";
112542
+ import { existsSync as existsSync87, readFileSync as readFileSync78, statSync as statSync52 } from "node:fs";
112543
+ import { resolve as resolve45, join as join96 } from "node:path";
111621
112544
 
111622
112545
  // src/web/memory-remediation.ts
111623
112546
  init_hindsight2();
@@ -111685,15 +112608,15 @@ async function buildUserProfile(mcpUrl, bank, opts) {
111685
112608
  init_hindsight();
111686
112609
 
111687
112610
  // src/web/blocked-approvals-read.ts
111688
- import { readFileSync as readFileSync75, readdirSync as readdirSync30 } from "node:fs";
111689
- import { resolve as resolve43, join as join89 } from "node:path";
112611
+ import { readFileSync as readFileSync76, readdirSync as readdirSync31 } from "node:fs";
112612
+ import { resolve as resolve44, join as join90 } from "node:path";
111690
112613
  import { homedir as homedir42 } from "node:os";
111691
112614
  function blockedApprovalsDir(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir42()) {
111692
- return resolve43(home2, ".switchroom", "blocked-approvals");
112615
+ return resolve44(home2, ".switchroom", "blocked-approvals");
111693
112616
  }
111694
112617
  var FALLBACK_RECORD_NAME = "blocked-approval.json";
111695
112618
  function agentStateRoot(sharedDir) {
111696
- return resolve43(sharedDir, "..", "agents");
112619
+ return resolve44(sharedDir, "..", "agents");
111697
112620
  }
111698
112621
  function finiteOrNull(v) {
111699
112622
  const n = Number(v);
@@ -111732,7 +112655,7 @@ function readBlockedApprovalsWithErrors(dir, agentsRoot = agentStateRoot(dir)) {
111732
112655
  function readSharedRecords(dir) {
111733
112656
  let entries;
111734
112657
  try {
111735
- entries = readdirSync30(dir);
112658
+ entries = readdirSync31(dir);
111736
112659
  } catch (err) {
111737
112660
  if (err?.code === "ENOENT") {
111738
112661
  return { blocked: [], unreadable: 0 };
@@ -111746,7 +112669,7 @@ function readSharedRecords(dir) {
111746
112669
  continue;
111747
112670
  let text;
111748
112671
  try {
111749
- text = readFileSync75(join89(dir, name), "utf-8");
112672
+ text = readFileSync76(join90(dir, name), "utf-8");
111750
112673
  } catch {
111751
112674
  unreadable++;
111752
112675
  continue;
@@ -111765,7 +112688,7 @@ function readSharedRecords(dir) {
111765
112688
  function readFallbackRecords(agentsRoot, alreadySeen) {
111766
112689
  let agents;
111767
112690
  try {
111768
- agents = readdirSync30(agentsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
112691
+ agents = readdirSync31(agentsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
111769
112692
  } catch (err) {
111770
112693
  if (err?.code === "ENOENT") {
111771
112694
  return { blocked: [], unreadable: 0 };
@@ -111779,7 +112702,7 @@ function readFallbackRecords(agentsRoot, alreadySeen) {
111779
112702
  continue;
111780
112703
  let text;
111781
112704
  try {
111782
- text = readFileSync75(join89(agentsRoot, agent, FALLBACK_RECORD_NAME), "utf-8");
112705
+ text = readFileSync76(join90(agentsRoot, agent, FALLBACK_RECORD_NAME), "utf-8");
111783
112706
  } catch (err) {
111784
112707
  if (err?.code !== "ENOENT")
111785
112708
  unreadable++;
@@ -111826,7 +112749,7 @@ init_audit_reader();
111826
112749
  // src/scheduler/dispatch.ts
111827
112750
  init_merge();
111828
112751
  init_overlay_loader();
111829
- import { createHash as createHash15 } from "node:crypto";
112752
+ import { createHash as createHash16 } from "node:crypto";
111830
112753
  function collectScheduleEntries(config) {
111831
112754
  const out = [];
111832
112755
  const agentNames = Object.keys(config.agents).sort();
@@ -111846,7 +112769,7 @@ function collectScheduleEntries(config) {
111846
112769
  cron: entry.cron,
111847
112770
  ...typeof title === "string" && title.length > 0 ? { name: title } : {},
111848
112771
  ...entry.prompt !== undefined ? { prompt: entry.prompt } : {},
111849
- promptKey: createHash15("sha256").update(auditMaterial).digest("hex").slice(0, 12),
112772
+ promptKey: createHash16("sha256").update(auditMaterial).digest("hex").slice(0, 12),
111850
112773
  ...entry.topic !== undefined ? { topic: entry.topic } : {},
111851
112774
  ...entry.kind !== undefined ? { kind: entry.kind } : {},
111852
112775
  ...entry.model !== undefined ? { model: entry.model } : {},
@@ -111895,7 +112818,7 @@ import { homedir as homedir45 } from "node:os";
111895
112818
  // src/web/config-edit-plan.ts
111896
112819
  init_schema();
111897
112820
  var import_yaml17 = __toESM(require_dist(), 1);
111898
- import { readFileSync as readFileSync76 } from "node:fs";
112821
+ import { readFileSync as readFileSync77 } from "node:fs";
111899
112822
 
111900
112823
  class ConfigPlanError extends Error {
111901
112824
  }
@@ -111905,7 +112828,7 @@ function composeTransforms(...transforms) {
111905
112828
  function planConfigEdit(configPath, transform) {
111906
112829
  let before;
111907
112830
  try {
111908
- before = readFileSync76(configPath, "utf-8");
112831
+ before = readFileSync77(configPath, "utf-8");
111909
112832
  } catch (err) {
111910
112833
  throw new ConfigPlanError(`could not read config: ${err.message}`);
111911
112834
  }
@@ -111940,19 +112863,19 @@ import {
111940
112863
  } from "node:fs";
111941
112864
  import { spawnSync as spawnSync16 } from "node:child_process";
111942
112865
  import { tmpdir as tmpdir5 } from "node:os";
111943
- import { join as join91 } from "node:path";
112866
+ import { join as join92 } from "node:path";
111944
112867
 
111945
112868
  class ConfigDiffError extends Error {
111946
112869
  }
111947
112870
  function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "git") {
111948
112871
  if (before === after)
111949
112872
  return "";
111950
- const dir = mkdtemp(join91(tmpdir5(), "switchroom-config-diff-"));
112873
+ const dir = mkdtemp(join92(tmpdir5(), "switchroom-config-diff-"));
111951
112874
  try {
111952
- mkdirSync47(join91(dir, "cur"), { recursive: true });
111953
- mkdirSync47(join91(dir, "new"), { recursive: true });
111954
- writeFileSync32(join91(dir, "cur", name), before);
111955
- writeFileSync32(join91(dir, "new", name), after);
112875
+ mkdirSync47(join92(dir, "cur"), { recursive: true });
112876
+ mkdirSync47(join92(dir, "new"), { recursive: true });
112877
+ writeFileSync32(join92(dir, "cur", name), before);
112878
+ writeFileSync32(join92(dir, "new", name), after);
111956
112879
  const r = spawnSync16(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
111957
112880
  if (r.status === 0)
111958
112881
  return "";
@@ -111984,9 +112907,9 @@ function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "
111984
112907
 
111985
112908
  // src/web/hostd-config-propose.ts
111986
112909
  init_client5();
111987
- import { existsSync as existsSync84 } from "node:fs";
112910
+ import { existsSync as existsSync85 } from "node:fs";
111988
112911
  import { homedir as homedir44 } from "node:os";
111989
- import { join as join92 } from "node:path";
112912
+ import { join as join93 } from "node:path";
111990
112913
  var PROPOSE_TIMEOUT_MS = 61 * 60 * 1000;
111991
112914
  function resolveHostdOperatorSocket(env2 = process.env) {
111992
112915
  const override = env2.SWITCHROOM_HOSTD_OPERATOR_SOCKET;
@@ -111994,10 +112917,10 @@ function resolveHostdOperatorSocket(env2 = process.env) {
111994
112917
  return override;
111995
112918
  const candidates = [
111996
112919
  "/host-home/.switchroom/hostd/operator/sock",
111997
- join92(homedir44(), ".switchroom", "hostd", "operator", "sock")
112920
+ join93(homedir44(), ".switchroom", "hostd", "operator", "sock")
111998
112921
  ];
111999
112922
  for (const c of candidates) {
112000
- if (existsSync84(c))
112923
+ if (existsSync85(c))
112001
112924
  return c;
112002
112925
  }
112003
112926
  return null;
@@ -112205,7 +113128,7 @@ init_client2();
112205
113128
 
112206
113129
  // telegram-plugin/registry/turns-schema.ts
112207
113130
  import { chmodSync as chmodSync15 } from "fs";
112208
- import { join as join94 } from "path";
113131
+ import { join as join95 } from "path";
112209
113132
 
112210
113133
  // src/util/state-owner.ts
112211
113134
  init_atomic();
@@ -112213,28 +113136,28 @@ import {
112213
113136
  appendFileSync as appendFileSync4,
112214
113137
  closeSync as closeSync16,
112215
113138
  constants as constants2,
112216
- existsSync as existsSync85,
113139
+ existsSync as existsSync86,
112217
113140
  fchownSync as fchownSync2,
112218
113141
  fstatSync as fstatSync5,
112219
113142
  mkdirSync as mkdirSync48,
112220
113143
  openSync as openSync16,
112221
- readdirSync as readdirSync31,
112222
- statSync as statSync50,
113144
+ readdirSync as readdirSync32,
113145
+ statSync as statSync51,
112223
113146
  writeFileSync as writeFileSync33
112224
113147
  } from "node:fs";
112225
- import { dirname as dirname35, join as join93, relative as relative4, sep as sep6 } from "node:path";
113148
+ import { dirname as dirname35, join as join94, relative as relative4, sep as sep6 } from "node:path";
112226
113149
  var NOFOLLOW = constants2.O_NOFOLLOW ?? 0;
112227
113150
  var ownerCache = new Map;
112228
113151
  var loggedFailure = false;
112229
113152
  function defaultLog(line) {
112230
113153
  process.stderr.write(line);
112231
113154
  }
112232
- function noteFailure(op, path7, err, log) {
113155
+ function noteFailure(op, path9, err, log) {
112233
113156
  if (loggedFailure)
112234
113157
  return;
112235
113158
  loggedFailure = true;
112236
113159
  const code = err.code ?? "";
112237
- log(`switchroom state-owner: ${op} could not adopt directory ownership for ${path7}` + `${code ? ` (${code})` : ""}: ${err.message}. ` + `Continuing; state files may stay owned by the writing uid. ` + `Further ownership warnings this process are suppressed.
113160
+ log(`switchroom state-owner: ${op} could not adopt directory ownership for ${path9}` + `${code ? ` (${code})` : ""}: ${err.message}. ` + `Continuing; state files may stay owned by the writing uid. ` + `Further ownership warnings this process are suppressed.
112238
113161
  `);
112239
113162
  }
112240
113163
  function isMultiplyLinked(st) {
@@ -112248,7 +113171,7 @@ function resolveStateOwner(dir) {
112248
113171
  return cached;
112249
113172
  let owner = null;
112250
113173
  try {
112251
- const st = statSync50(dir);
113174
+ const st = statSync51(dir);
112252
113175
  owner = st.uid === 0 && st.gid === 0 ? null : { uid: st.uid, gid: st.gid };
112253
113176
  } catch {
112254
113177
  owner = null;
@@ -112256,13 +113179,13 @@ function resolveStateOwner(dir) {
112256
113179
  ownerCache.set(dir, owner);
112257
113180
  return owner;
112258
113181
  }
112259
- function adoptStateOwnership(path7, owner, log = defaultLog) {
112260
- const target = owner === undefined ? resolveStateOwner(dirname35(path7)) : owner;
113182
+ function adoptStateOwnership(path9, owner, log = defaultLog) {
113183
+ const target = owner === undefined ? resolveStateOwner(dirname35(path9)) : owner;
112261
113184
  if (!target)
112262
113185
  return;
112263
113186
  let fd = null;
112264
113187
  try {
112265
- fd = openSync16(path7, constants2.O_RDONLY | NOFOLLOW);
113188
+ fd = openSync16(path9, constants2.O_RDONLY | NOFOLLOW);
112266
113189
  const st = fstatSync5(fd);
112267
113190
  if (st.uid === target.uid && st.gid === target.gid)
112268
113191
  return;
@@ -112271,7 +113194,7 @@ function adoptStateOwnership(path7, owner, log = defaultLog) {
112271
113194
  fchownSync2(fd, target.uid, target.gid);
112272
113195
  } catch (err) {
112273
113196
  if (err.code !== "ENOENT") {
112274
- noteFailure("adopt", path7, err, log);
113197
+ noteFailure("adopt", path9, err, log);
112275
113198
  }
112276
113199
  } finally {
112277
113200
  if (fd !== null) {
@@ -112289,8 +113212,8 @@ function adoptSqliteOwnership(dbPath, log = defaultLog) {
112289
113212
  adoptStateOwnership(dbPath + suffix, owner, log);
112290
113213
  }
112291
113214
  }
112292
- function mkdirStateSync(path7, options = { recursive: true }, log = defaultLog) {
112293
- const first = mkdirSync48(path7, { ...options, recursive: true });
113215
+ function mkdirStateSync(path9, options = { recursive: true }, log = defaultLog) {
113216
+ const first = mkdirSync48(path9, { ...options, recursive: true });
112294
113217
  if (first === undefined)
112295
113218
  return;
112296
113219
  const owner = resolveStateOwner(dirname35(first));
@@ -112298,13 +113221,13 @@ function mkdirStateSync(path7, options = { recursive: true }, log = defaultLog)
112298
113221
  return;
112299
113222
  let current = first;
112300
113223
  adoptStateOwnership(current, owner, log);
112301
- const tail = relative4(first, path7);
113224
+ const tail = relative4(first, path9);
112302
113225
  if (tail === "" || tail.startsWith(".."))
112303
113226
  return;
112304
113227
  for (const part of tail.split(sep6)) {
112305
113228
  if (part === "")
112306
113229
  continue;
112307
- current = join93(current, part);
113230
+ current = join94(current, part);
112308
113231
  adoptStateOwnership(current, owner, log);
112309
113232
  }
112310
113233
  }
@@ -112381,20 +113304,20 @@ function applySchema(db) {
112381
113304
  }
112382
113305
  function openTurnsDb(agentDir) {
112383
113306
  const Database2 = loadDatabaseClass();
112384
- const dir = join94(agentDir, "telegram");
113307
+ const dir = join95(agentDir, "telegram");
112385
113308
  mkdirStateSync(dir, { recursive: true, mode: 448 });
112386
- const path7 = join94(dir, "registry.db");
112387
- const db = new Database2(path7, { create: true });
113309
+ const path9 = join95(dir, "registry.db");
113310
+ const db = new Database2(path9, { create: true });
112388
113311
  applySchema(db);
112389
113312
  try {
112390
- chmodSync15(path7, 420);
113313
+ chmodSync15(path9, 420);
112391
113314
  for (const suffix of ["-shm", "-wal"]) {
112392
113315
  try {
112393
- chmodSync15(path7 + suffix, 420);
113316
+ chmodSync15(path9 + suffix, 420);
112394
113317
  } catch {}
112395
113318
  }
112396
113319
  } catch {}
112397
- adoptSqliteOwnership(path7);
113320
+ adoptSqliteOwnership(path9);
112398
113321
  return db;
112399
113322
  }
112400
113323
  function mapRow(row) {
@@ -112596,7 +113519,7 @@ var DASHBOARD_CACHE_TTL = {
112596
113519
  };
112597
113520
  function readContextOccupancy(agentsDir, name) {
112598
113521
  try {
112599
- const raw = readFileSync77(resolve44(agentsDir, name, "context-occupancy.json"), "utf-8");
113522
+ const raw = readFileSync78(resolve45(agentsDir, name, "context-occupancy.json"), "utf-8");
112600
113523
  const s = JSON.parse(raw);
112601
113524
  if (typeof s?.occupancy !== "number")
112602
113525
  return null;
@@ -112607,7 +113530,7 @@ function readContextOccupancy(agentsDir, name) {
112607
113530
  }
112608
113531
  function readLastTurnAt(agentsDir, name) {
112609
113532
  try {
112610
- const db = openTurnsDb(resolve44(agentsDir, name));
113533
+ const db = openTurnsDb(resolve45(agentsDir, name));
112611
113534
  try {
112612
113535
  const turns = listTurnsForAgent(db, { limit: 1 });
112613
113536
  return turns.length > 0 ? turns[0].started_at : null;
@@ -112620,8 +113543,8 @@ function readLastTurnAt(agentsDir, name) {
112620
113543
  }
112621
113544
  function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now2 = Date.now()) {
112622
113545
  try {
112623
- const f = resolve44(agentsDir, name, "telegram", ".bridge-alive");
112624
- return now2 - statSync51(f).mtimeMs <= maxAgeMs;
113546
+ const f = resolve45(agentsDir, name, "telegram", ".bridge-alive");
113547
+ return now2 - statSync52(f).mtimeMs <= maxAgeMs;
112625
113548
  } catch {
112626
113549
  return false;
112627
113550
  }
@@ -112719,7 +113642,7 @@ function handleGetTurns(config, sessionId, limit) {
112719
113642
  const agentName = parsed.agentName;
112720
113643
  const hasThread = "threadId" in parsed;
112721
113644
  const agentsDir = resolveAgentsDir(config);
112722
- const agentDir = resolve44(agentsDir, agentName);
113645
+ const agentDir = resolve45(agentsDir, agentName);
112723
113646
  const chatId = config.agents?.[agentName]?.channels?.telegram?.chat_id ?? undefined;
112724
113647
  const db = openTurnsDb(agentDir);
112725
113648
  try {
@@ -112742,7 +113665,7 @@ function handleListThreadIds(config, agentName) {
112742
113665
  if (!chatId)
112743
113666
  return [];
112744
113667
  const agentsDir = resolveAgentsDir(config);
112745
- const db = openTurnsDb(resolve44(agentsDir, agentName));
113668
+ const db = openTurnsDb(resolve45(agentsDir, agentName));
112746
113669
  try {
112747
113670
  return listDistinctThreadIds(db, chatId);
112748
113671
  } finally {
@@ -112755,7 +113678,7 @@ function handleListThreadIds(config, agentName) {
112755
113678
  function handleGetSubagents(config, agentName, status) {
112756
113679
  try {
112757
113680
  const agentsDir = resolveAgentsDir(config);
112758
- const agentDir = resolve44(agentsDir, agentName);
113681
+ const agentDir = resolve45(agentsDir, agentName);
112759
113682
  const db = openTurnsDb(agentDir);
112760
113683
  try {
112761
113684
  applySubagentsSchema(db);
@@ -113012,7 +113935,7 @@ async function handleGetSystemHealth(config, home2) {
113012
113935
  };
113013
113936
  try {
113014
113937
  const logPath = defaultAuditLogPath2(home2);
113015
- if (existsSync86(logPath)) {
113938
+ if (existsSync87(logPath)) {
113016
113939
  hostd.auditLogPresent = true;
113017
113940
  const raw = readAuditRaw(logPath);
113018
113941
  hostd.recent = readAndFilter(raw, {}, 1000).filter((e) => !HOSTD_NOISE_OPS.has(e.op)).slice(-10);
@@ -113321,7 +114244,7 @@ async function handleGetSchedule(config, deps = {}) {
113321
114244
  const recentByAgent = {};
113322
114245
  const agents = new Set(entries.map((e) => e.agent));
113323
114246
  for (const agent of agents) {
113324
- const rows = readRecentFires(resolve44(agentsDir, agent, "scheduler.jsonl"));
114247
+ const rows = readRecentFires(resolve45(agentsDir, agent, "scheduler.jsonl"));
113325
114248
  if (rows.length > 0)
113326
114249
  recentByAgent[agent] = rows.slice(-10);
113327
114250
  }
@@ -113353,11 +114276,11 @@ async function handleGetApprovals() {
113353
114276
  return { reachable: true, decisions: sorted };
113354
114277
  }
113355
114278
  function brokerOperatorSocketPath(home2 = process.env.HOME || homedir45()) {
113356
- return join95(home2, ".switchroom", "broker-operator", "sock");
114279
+ return join96(home2, ".switchroom", "broker-operator", "sock");
113357
114280
  }
113358
114281
  function resolveBrokerOperatorSocket(home2 = process.env.HOME || homedir45()) {
113359
114282
  const p = brokerOperatorSocketPath(home2);
113360
- return existsSync86(p) ? p : null;
114283
+ return existsSync87(p) ? p : null;
113361
114284
  }
113362
114285
  async function handleGetGrants(deps = {}) {
113363
114286
  const socket = "socketPath" in deps ? deps.socketPath : resolveBrokerOperatorSocket();
@@ -113748,8 +114671,8 @@ async function handleGetSummary(deps, now2 = Date.now()) {
113748
114671
  init_fleet_health_read();
113749
114672
 
113750
114673
  // src/web/webhook-handler.ts
113751
- import { appendFileSync as appendFileSync5, existsSync as existsSync88, mkdirSync as mkdirSync49, readFileSync as readFileSync80, writeFileSync as writeFileSync34 } from "fs";
113752
- import { join as join97 } from "path";
114674
+ import { appendFileSync as appendFileSync5, existsSync as existsSync89, mkdirSync as mkdirSync49, readFileSync as readFileSync81, writeFileSync as writeFileSync34 } from "fs";
114675
+ import { join as join98 } from "path";
113753
114676
  import { homedir as homedir48 } from "os";
113754
114677
 
113755
114678
  // src/web/webhook-verify.ts
@@ -113917,7 +114840,7 @@ init_log_rotation();
113917
114840
  import net4 from "node:net";
113918
114841
  function forwardToGateway(socketPath, req, opts = {}) {
113919
114842
  const timeoutMs = opts.timeoutMs ?? 5000;
113920
- return new Promise((resolve46) => {
114843
+ return new Promise((resolve47) => {
113921
114844
  let settled = false;
113922
114845
  let buf = "";
113923
114846
  const done = (value) => {
@@ -113927,7 +114850,7 @@ function forwardToGateway(socketPath, req, opts = {}) {
113927
114850
  try {
113928
114851
  conn.destroy();
113929
114852
  } catch {}
113930
- resolve46(value);
114853
+ resolve47(value);
113931
114854
  };
113932
114855
  const conn = net4.createConnection(socketPath);
113933
114856
  conn.setEncoding("utf8");
@@ -113958,20 +114881,20 @@ function forwardToGateway(socketPath, req, opts = {}) {
113958
114881
  }
113959
114882
 
113960
114883
  // src/web/webhook-edge.ts
113961
- import { existsSync as existsSync87, readFileSync as readFileSync79 } from "fs";
113962
- import { join as join96 } from "path";
114884
+ import { existsSync as existsSync88, readFileSync as readFileSync80 } from "fs";
114885
+ import { join as join97 } from "path";
113963
114886
  import { homedir as homedir47 } from "os";
113964
114887
  import { timingSafeEqual as timingSafeEqual2 } from "crypto";
113965
114888
  var EDGE_HEADER = "x-switchroom-edge";
113966
114889
  function edgeSecretPath() {
113967
- return join96(homedir47(), ".switchroom", "webhook-edge-secret");
114890
+ return join97(homedir47(), ".switchroom", "webhook-edge-secret");
113968
114891
  }
113969
- function loadEdgeSecret(path7) {
113970
- const p = path7 ?? edgeSecretPath();
114892
+ function loadEdgeSecret(path9) {
114893
+ const p = path9 ?? edgeSecretPath();
113971
114894
  try {
113972
- if (!existsSync87(p))
114895
+ if (!existsSync88(p))
113973
114896
  return null;
113974
- const raw = readFileSync79(p, "utf-8").trim();
114897
+ const raw = readFileSync80(p, "utf-8").trim();
113975
114898
  return raw.length > 0 ? raw : null;
113976
114899
  } catch {
113977
114900
  return null;
@@ -114020,17 +114943,17 @@ function jsonReply(status, body, extraHeaders) {
114020
114943
  }
114021
114944
  var DEDUP_MAX = 1000;
114022
114945
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
114023
- function loadDedupFile(path7) {
114946
+ function loadDedupFile(path9) {
114024
114947
  try {
114025
- if (!existsSync88(path7))
114948
+ if (!existsSync89(path9))
114026
114949
  return {};
114027
- const raw = JSON.parse(readFileSync80(path7, "utf-8"));
114950
+ const raw = JSON.parse(readFileSync81(path9, "utf-8"));
114028
114951
  return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
114029
114952
  } catch {
114030
114953
  return {};
114031
114954
  }
114032
114955
  }
114033
- function saveDedupFile(path7, deliveries, now2) {
114956
+ function saveDedupFile(path9, deliveries, now2) {
114034
114957
  const pruned = {};
114035
114958
  for (const [id, ts] of Object.entries(deliveries)) {
114036
114959
  if (now2 - ts < DEDUP_TTL_MS)
@@ -114038,7 +114961,7 @@ function saveDedupFile(path7, deliveries, now2) {
114038
114961
  }
114039
114962
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
114040
114963
  const final = Object.fromEntries(sorted);
114041
- writeFileSync34(path7, JSON.stringify({ deliveries: final }), {
114964
+ writeFileSync34(path9, JSON.stringify({ deliveries: final }), {
114042
114965
  mode: 384
114043
114966
  });
114044
114967
  }
@@ -114046,8 +114969,8 @@ var agentDedupCache = new Map;
114046
114969
  function createFileDedupStore(resolveAgentDir2) {
114047
114970
  return {
114048
114971
  check(agent, deliveryId, now2) {
114049
- const telegramDir = join97(resolveAgentDir2(agent), "telegram");
114050
- const filePath = join97(telegramDir, "webhook-dedup.json");
114972
+ const telegramDir = join98(resolveAgentDir2(agent), "telegram");
114973
+ const filePath = join98(telegramDir, "webhook-dedup.json");
114051
114974
  if (!agentDedupCache.has(agent)) {
114052
114975
  agentDedupCache.set(agent, loadDedupFile(filePath));
114053
114976
  }
@@ -114099,7 +115022,7 @@ function shouldWriteThrottleIssue(agent, source, now2, windowMap) {
114099
115022
  return true;
114100
115023
  }
114101
115024
  function writeThrottleIssue(agent, source, now2, telegramDir, log) {
114102
- const issuesPath = join97(telegramDir, "issues.jsonl");
115025
+ const issuesPath = join98(telegramDir, "issues.jsonl");
114103
115026
  try {
114104
115027
  mkdirSync49(telegramDir, { recursive: true });
114105
115028
  const record = {
@@ -114124,7 +115047,7 @@ function writeThrottleIssue(agent, source, now2, telegramDir, log) {
114124
115047
  async function handleWebhookIngest(args, deps = {}) {
114125
115048
  const log = deps.log ?? ((s) => process.stderr.write(s));
114126
115049
  const now2 = (deps.now ?? Date.now)();
114127
- const resolveAgentDir2 = deps.resolveAgentDir ?? ((a) => join97(homedir48(), ".switchroom", "agents", a));
115050
+ const resolveAgentDir2 = deps.resolveAgentDir ?? ((a) => join98(homedir48(), ".switchroom", "agents", a));
114128
115051
  const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
114129
115052
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir2);
114130
115053
  if (!args.agentExists) {
@@ -114191,7 +115114,7 @@ async function handleWebhookIngest(args, deps = {}) {
114191
115114
  if (retryAfter !== null) {
114192
115115
  if (!args.viaGateway) {
114193
115116
  const agentDir2 = resolveAgentDir2(args.agent);
114194
- const telegramDir2 = join97(agentDir2, "telegram");
115117
+ const telegramDir2 = join98(agentDir2, "telegram");
114195
115118
  if (shouldWriteThrottleIssue(args.agent, source, now2)) {
114196
115119
  writeThrottleIssue(args.agent, source, now2, telegramDir2, log);
114197
115120
  }
@@ -114211,7 +115134,7 @@ async function handleWebhookIngest(args, deps = {}) {
114211
115134
  const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : source === "linear" ? String(payload.type ?? "unknown").toLowerCase() : args.source;
114212
115135
  const rendered = source === "github" ? renderGithubEvent(eventType, payload) : source === "linear" ? renderLinearEvent(eventType, payload) : renderGenericEvent(args.source, payload);
114213
115136
  if (args.viaGateway) {
114214
- const socketPath = join97(resolveAgentDir2(args.agent), "telegram", "webhook.sock");
115137
+ const socketPath = join98(resolveAgentDir2(args.agent), "telegram", "webhook.sock");
114215
115138
  const forward = deps.forwardFn ?? forwardToGateway;
114216
115139
  const deliveryId = source === "github" ? args.headers.get("x-github-delivery") ?? undefined : undefined;
114217
115140
  let resp;
@@ -114250,8 +115173,8 @@ async function handleWebhookIngest(args, deps = {}) {
114250
115173
  return jsonReply(202, { ok: true, recorded: true, ts: resp.ts });
114251
115174
  }
114252
115175
  const agentDir = resolveAgentDir2(args.agent);
114253
- const telegramDir = join97(agentDir, "telegram");
114254
- const logPath = join97(telegramDir, "webhook-events.jsonl");
115176
+ const telegramDir = join98(agentDir, "telegram");
115177
+ const logPath = join98(telegramDir, "webhook-events.jsonl");
114255
115178
  try {
114256
115179
  mkdirSync49(telegramDir, { recursive: true });
114257
115180
  rotateWebhookLogIfNeeded(logPath);
@@ -114280,9 +115203,9 @@ init_shipped_assets();
114280
115203
  // src/web/hermes-adapter.ts
114281
115204
  init_loader();
114282
115205
  import { createConnection as createConnection3 } from "node:net";
114283
- import { createHash as createHash16 } from "node:crypto";
114284
- import { join as join98, resolve as resolve46 } from "node:path";
114285
- import { existsSync as existsSync89, readFileSync as readFileSync81 } from "node:fs";
115206
+ import { createHash as createHash17 } from "node:crypto";
115207
+ import { join as join99, resolve as resolve47 } from "node:path";
115208
+ import { existsSync as existsSync90, readFileSync as readFileSync82 } from "node:fs";
114286
115209
 
114287
115210
  // src/agent-scheduler/channel-target.ts
114288
115211
  init_merge();
@@ -114326,8 +115249,8 @@ function agentLiveness(config, agentName) {
114326
115249
  const agentsDir = resolveAgentsDir(config);
114327
115250
  if (agentBridgeAlive(agentsDir, agentName))
114328
115251
  return "active";
114329
- const alive = join98(agentsDir, agentName, "telegram", ".bridge-alive");
114330
- if (existsSync89(alive))
115252
+ const alive = join99(agentsDir, agentName, "telegram", ".bridge-alive");
115253
+ if (existsSync90(alive))
114331
115254
  return "idle";
114332
115255
  return "offline";
114333
115256
  }
@@ -114435,11 +115358,11 @@ function resolveAgentChat(config, sessionId, agentsDir) {
114435
115358
  const threadId = topicId !== undefined ? topicId === null ? undefined : parseInt(topicId, 10) || undefined : channel.threadId;
114436
115359
  return { chatId: channel.chatId, threadId };
114437
115360
  }
114438
- const accessPath = resolve46(agentsDir, agentName, "telegram", "access.json");
114439
- if (!existsSync89(accessPath))
115361
+ const accessPath = resolve47(agentsDir, agentName, "telegram", "access.json");
115362
+ if (!existsSync90(accessPath))
114440
115363
  return null;
114441
115364
  try {
114442
- const raw = readFileSync81(accessPath, "utf-8");
115365
+ const raw = readFileSync82(accessPath, "utf-8");
114443
115366
  const access = JSON.parse(raw);
114444
115367
  const chatId = access.allowFrom?.[0];
114445
115368
  if (!chatId)
@@ -114450,8 +115373,8 @@ function resolveAgentChat(config, sessionId, agentsDir) {
114450
115373
  }
114451
115374
  }
114452
115375
  async function injectInbound(agentsDir, agentName, chatId, threadId, text, promptKey) {
114453
- const socketPath = resolve46(agentsDir, agentName, "telegram", "gateway.sock");
114454
- if (!existsSync89(socketPath)) {
115376
+ const socketPath = resolve47(agentsDir, agentName, "telegram", "gateway.sock");
115377
+ if (!existsSync90(socketPath)) {
114455
115378
  return { ok: false, error: `agent ${agentName} gateway socket not found \u2014 is it running?` };
114456
115379
  }
114457
115380
  const ts = Date.now();
@@ -114498,8 +115421,8 @@ async function injectInbound(agentsDir, agentName, chatId, threadId, text, promp
114498
115421
  });
114499
115422
  }
114500
115423
  function readHistoryDb(agentsDir, agentName, limit) {
114501
- const dbPath = resolve46(agentsDir, agentName, "telegram", "history.db");
114502
- if (!existsSync89(dbPath))
115424
+ const dbPath = resolve47(agentsDir, agentName, "telegram", "history.db");
115425
+ if (!existsSync90(dbPath))
114503
115426
  return null;
114504
115427
  try {
114505
115428
  let SqliteDatabase;
@@ -114528,7 +115451,7 @@ function readHistoryDb(agentsDir, agentName, limit) {
114528
115451
  }
114529
115452
  }
114530
115453
  function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs = 2000) {
114531
- const dbPath = resolve46(agentsDir, agentName, "telegram", "history.db");
115454
+ const dbPath = resolve47(agentsDir, agentName, "telegram", "history.db");
114532
115455
  let SqliteDatabase = null;
114533
115456
  try {
114534
115457
  const meta = import.meta;
@@ -114538,7 +115461,7 @@ function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs =
114538
115461
  }
114539
115462
  } catch {}
114540
115463
  let cursor = 0;
114541
- if (SqliteDatabase && existsSync89(dbPath)) {
115464
+ if (SqliteDatabase && existsSync90(dbPath)) {
114542
115465
  try {
114543
115466
  const db = new SqliteDatabase(dbPath, { readonly: true });
114544
115467
  try {
@@ -114550,7 +115473,7 @@ function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs =
114550
115473
  } catch {}
114551
115474
  }
114552
115475
  const timer = setInterval(() => {
114553
- if (!SqliteDatabase || !existsSync89(dbPath))
115476
+ if (!SqliteDatabase || !existsSync90(dbPath))
114554
115477
  return;
114555
115478
  try {
114556
115479
  const db = new SqliteDatabase(dbPath, { readonly: true });
@@ -115213,7 +116136,7 @@ async function onHermesMessage(ctx, raw) {
115213
116136
  sendResponse(ctx, rpcErr(id, -32603, `Could not resolve chat for ${sessionId} \u2014 ensure the agent has telegram.forum_chat_id or channels.telegram.chat_id configured.`));
115214
116137
  break;
115215
116138
  }
115216
- const promptKey = createHash16("sha256").update(`${sessionId}:${Date.now()}:${content}`).digest("hex").slice(0, 12);
116139
+ const promptKey = createHash17("sha256").update(`${sessionId}:${Date.now()}:${content}`).digest("hex").slice(0, 12);
115217
116140
  sendEvent(ctx, "message.start", sessionId, { prompt_key: promptKey });
115218
116141
  pendingPromptKeys(ctx).set(sessionId, promptKey);
115219
116142
  const result = await injectInbound(agentsDir, submitAgent, chat.chatId, chat.threadId, content, promptKey);
@@ -115409,9 +116332,9 @@ function resolveWebToken() {
115409
116332
  if (fromEnv && fromEnv.length > 0)
115410
116333
  return fromEnv;
115411
116334
  const home2 = process.env.HOME ?? homedir49();
115412
- const tokenPath = join99(home2, ".switchroom", "web-token");
115413
- if (existsSync90(tokenPath)) {
115414
- const existing = readFileSync82(tokenPath, "utf8").trim();
116335
+ const tokenPath = join100(home2, ".switchroom", "web-token");
116336
+ if (existsSync91(tokenPath)) {
116337
+ const existing = readFileSync83(tokenPath, "utf8").trim();
115415
116338
  if (existing.length > 0)
115416
116339
  return existing;
115417
116340
  }
@@ -115428,7 +116351,7 @@ function resolveWebToken() {
115428
116351
  return token;
115429
116352
  } catch (err) {
115430
116353
  if (err.code === "EEXIST") {
115431
- const existing = readFileSync82(tokenPath, "utf8").trim();
116354
+ const existing = readFileSync83(tokenPath, "utf8").trim();
115432
116355
  if (existing.length > 0)
115433
116356
  return existing;
115434
116357
  }
@@ -115501,14 +116424,14 @@ function checkWsAuth(req, token, server) {
115501
116424
  return presented !== null && constantTimeEqual(presented, token);
115502
116425
  }
115503
116426
  function loadWebhookSecrets() {
115504
- const path7 = join99(homedir49(), ".switchroom", "webhook-secrets.json");
115505
- if (!existsSync90(path7))
116427
+ const path9 = join100(homedir49(), ".switchroom", "webhook-secrets.json");
116428
+ if (!existsSync91(path9))
115506
116429
  return {};
115507
116430
  try {
115508
- const parsed = JSON.parse(readFileSync82(path7, "utf-8"));
116431
+ const parsed = JSON.parse(readFileSync83(path9, "utf-8"));
115509
116432
  return parsed && typeof parsed === "object" ? parsed : {};
115510
116433
  } catch (err) {
115511
- process.stderr.write(`webhook-ingest: failed to parse ${path7}: ${err.message} \u2014 webhooks will return 401 until fixed
116434
+ process.stderr.write(`webhook-ingest: failed to parse ${path9}: ${err.message} \u2014 webhooks will return 401 until fixed
115512
116435
  `);
115513
116436
  return {};
115514
116437
  }
@@ -115695,11 +116618,11 @@ function resolveUiDir(probe2 = {
115695
116618
  bundleDir: import.meta.dirname,
115696
116619
  execPath: process.execPath
115697
116620
  }) {
115698
- return resolveShippedAsset(WEB_UI_ASSET, probe2).path ?? resolve47(probe2.bundleDir, WEB_UI_ASSET.asset);
116621
+ return resolveShippedAsset(WEB_UI_ASSET, probe2).path ?? resolve48(probe2.bundleDir, WEB_UI_ASSET.asset);
115699
116622
  }
115700
116623
  function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
115701
116624
  const uiDirRaw = resolveUiDir();
115702
- const uiDir = existsSync90(uiDirRaw) ? realpathSync7(uiDirRaw) : uiDirRaw;
116625
+ const uiDir = existsSync91(uiDirRaw) ? realpathSync7(uiDirRaw) : uiDirRaw;
115703
116626
  const token = resolveWebToken();
115704
116627
  const freshConfig = () => {
115705
116628
  if (!configPath)
@@ -115999,8 +116922,8 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
115999
116922
  })();
116000
116923
  }
116001
116924
  const filePath = resolveDashboardFilePath(pathname);
116002
- const fullPath = join99(uiDir, filePath);
116003
- if (!existsSync90(fullPath)) {
116925
+ const fullPath = join100(uiDir, filePath);
116926
+ if (!existsSync91(fullPath)) {
116004
116927
  return new Response("Not Found", { status: 404 });
116005
116928
  }
116006
116929
  let realFullPath;
@@ -116010,12 +116933,12 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
116010
116933
  return new Response("Not Found", { status: 404 });
116011
116934
  }
116012
116935
  const rel = relative5(uiDir, realFullPath);
116013
- if (rel.startsWith("..") || resolve47(uiDir, rel) !== realFullPath) {
116936
+ if (rel.startsWith("..") || resolve48(uiDir, rel) !== realFullPath) {
116014
116937
  return new Response("Forbidden", { status: 403 });
116015
116938
  }
116016
116939
  const ext = extname(realFullPath);
116017
116940
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
116018
- const content = readFileSync82(realFullPath);
116941
+ const content = readFileSync83(realFullPath);
116019
116942
  const headers = { "Content-Type": contentType };
116020
116943
  const cacheControl = dashboardCacheControl(ext);
116021
116944
  if (cacheControl)
@@ -116138,13 +117061,13 @@ init_helpers();
116138
117061
  init_loader();
116139
117062
 
116140
117063
  // src/web/startup-guard.ts
116141
- import { existsSync as existsSync91, readFileSync as readFileSync83, writeFileSync as writeFileSync35, mkdirSync as mkdirSync51, statSync as statSync52, unlinkSync as unlinkSync18 } from "node:fs";
117064
+ import { existsSync as existsSync92, readFileSync as readFileSync84, writeFileSync as writeFileSync35, mkdirSync as mkdirSync51, statSync as statSync53, unlinkSync as unlinkSync18 } from "node:fs";
116142
117065
  import { dirname as dirname37 } from "node:path";
116143
117066
  function detectConfigMountFault(configPath, deps = {}) {
116144
- const stat = deps.stat ?? ((p) => statSync52(p));
117067
+ const stat2 = deps.stat ?? ((p) => statSync53(p));
116145
117068
  let st;
116146
117069
  try {
116147
- st = stat(configPath);
117070
+ st = stat2(configPath);
116148
117071
  } catch {
116149
117072
  return null;
116150
117073
  }
@@ -116174,9 +117097,9 @@ function nextCrashBackoff(prior, now2, options = {}) {
116174
117097
  }
116175
117098
  function readCrashState(statePath) {
116176
117099
  try {
116177
- if (!existsSync91(statePath))
117100
+ if (!existsSync92(statePath))
116178
117101
  return null;
116179
- const raw = JSON.parse(readFileSync83(statePath, "utf-8"));
117102
+ const raw = JSON.parse(readFileSync84(statePath, "utf-8"));
116180
117103
  if (typeof raw.count !== "number" || typeof raw.lastTs !== "number")
116181
117104
  return null;
116182
117105
  return { count: raw.count, lastTs: raw.lastTs };
@@ -116192,7 +117115,7 @@ function writeCrashState(statePath, state) {
116192
117115
  }
116193
117116
  function clearCrashState(statePath) {
116194
117117
  try {
116195
- if (existsSync91(statePath))
117118
+ if (existsSync92(statePath))
116196
117119
  unlinkSync18(statePath);
116197
117120
  } catch {}
116198
117121
  }
@@ -116215,7 +117138,7 @@ async function handleWebStartupFailure(err, configPath, stateFile) {
116215
117138
  throw err;
116216
117139
  }
116217
117140
  function webCrashStatePath() {
116218
- return join100(homedir50(), ".switchroom", "web", ".crashloop-state.json");
117141
+ return join101(homedir50(), ".switchroom", "web", ".crashloop-state.json");
116219
117142
  }
116220
117143
  function registerWebCommand(program3) {
116221
117144
  program3.command("web").description("Start the web dashboard for monitoring agents").option("-p, --port <port>", "Port to listen on", "8080").option("-b, --bind <host>", "Host/IP to bind to (default: 127.0.0.1, localhost-only)", "127.0.0.1").action(withConfigError(async (opts) => {
@@ -116282,9 +117205,9 @@ init_embedded_examples();
116282
117205
  init_atomic();
116283
117206
  init_loader();
116284
117207
  init_scaffold();
116285
- import { existsSync as existsSync93, readFileSync as readFileSync84, mkdirSync as mkdirSync52, statSync as statSync53, writeFileSync as writeFileSync36 } from "node:fs";
117208
+ import { existsSync as existsSync94, readFileSync as readFileSync85, mkdirSync as mkdirSync52, statSync as statSync54, writeFileSync as writeFileSync36 } from "node:fs";
116286
117209
  import { execFileSync as execFileSync29 } from "node:child_process";
116287
- import { resolve as resolve48, dirname as dirname38 } from "node:path";
117210
+ import { resolve as resolve49, dirname as dirname38 } from "node:path";
116288
117211
  init_state();
116289
117212
  init_vault();
116290
117213
  init_manager();
@@ -116699,7 +117622,7 @@ ${status} ${source_default.bold(`Step ${num3}:`)} ${title}`);
116699
117622
  init_source();
116700
117623
  init_loader();
116701
117624
  init_hindsight();
116702
- import { existsSync as existsSync92 } from "node:fs";
117625
+ import { existsSync as existsSync93 } from "node:fs";
116703
117626
  var HINDSIGHT_READY_RETRIES = 5;
116704
117627
  var HINDSIGHT_READY_INTERVAL_MS = 1000;
116705
117628
  async function resolveLiteLLMForHindsight2(config, deps = {}) {
@@ -116736,7 +117659,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, d
116736
117659
  try {
116737
117660
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
116738
117661
  const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
116739
- if (passphrase && existsSync92(vaultPath)) {
117662
+ if (passphrase && existsSync93(vaultPath)) {
116740
117663
  const { getStringSecret: getStringSecret2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
116741
117664
  const existing = getStringSecret2(passphrase, vaultPath, "hindsight-api-key");
116742
117665
  if (existing) {
@@ -117006,7 +117929,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, d
117006
117929
  function writeSwitchroomYaml(configPath, text) {
117007
117930
  let mode = 420;
117008
117931
  try {
117009
- mode = statSync53(configPath).mode & 511;
117932
+ mode = statSync54(configPath).mode & 511;
117010
117933
  } catch {}
117011
117934
  writeConfigFileSync(configPath, text, mode);
117012
117935
  }
@@ -117105,7 +118028,7 @@ async function stepConfigFile(configPath, nonInteractive) {
117105
118028
  existingConfig = null;
117106
118029
  }
117107
118030
  }
117108
- if (existingConfig && existsSync93(existingConfig)) {
118031
+ if (existingConfig && existsSync94(existingConfig)) {
117109
118032
  if (!nonInteractive) {
117110
118033
  const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
117111
118034
  if (!useExisting) {
@@ -117117,9 +118040,9 @@ async function stepConfigFile(configPath, nonInteractive) {
117117
118040
  console.log(source_default.green(` ${STEP_DONE} Config loaded`) + source_default.gray(` (${Object.keys(config.agents).length} agents)`));
117118
118041
  const hasExplicitTimezone = config.switchroom?.timezone !== undefined || config.defaults?.timezone !== undefined;
117119
118042
  if (!hasExplicitTimezone) {
117120
- await writeDetectedTimezone(resolve48(existingConfig), nonInteractive);
118043
+ await writeDetectedTimezone(resolve49(existingConfig), nonInteractive);
117121
118044
  }
117122
- return { config, configPath: resolve48(existingConfig) };
118045
+ return { config, configPath: resolve49(existingConfig) };
117123
118046
  }
117124
118047
  return await copyExampleConfig2(nonInteractive);
117125
118048
  }
@@ -117146,12 +118069,12 @@ async function copyExampleConfig2(nonInteractive) {
117146
118069
  await writeDetectedTimezone(destFile, nonInteractive);
117147
118070
  const config = loadConfig(destFile);
117148
118071
  console.log(source_default.green(` ${STEP_DONE} Config loaded`) + source_default.gray(` (${Object.keys(config.agents).length} agents)`));
117149
- return { config, configPath: resolve48(destFile) };
118072
+ return { config, configPath: resolve49(destFile) };
117150
118073
  }
117151
118074
  async function writeDetectedTimezone(destFile, nonInteractive, detect = detectServerTimezone, prompt = ask) {
117152
118075
  const detected = detect();
117153
118076
  if (detected !== undefined && detected !== "UTC" && isValidTimezone(detected)) {
117154
- const before2 = readFileSync84(destFile, "utf-8");
118077
+ const before2 = readFileSync85(destFile, "utf-8");
117155
118078
  const after2 = setSwitchroomTimezone(before2, detected);
117156
118079
  if (after2 !== before2)
117157
118080
  writeSwitchroomYaml(destFile, after2);
@@ -117172,7 +118095,7 @@ async function writeDetectedTimezone(destFile, nonInteractive, detect = detectSe
117172
118095
  console.error(source_default.yellow(` \u26a0 "${zone}" is not a valid IANA zone (expected Region/City like ` + '"Australia/Melbourne"). Skipping \u2014 agents will use UTC. ' + "Edit switchroom.timezone in switchroom.yaml by hand."));
117173
118096
  return;
117174
118097
  }
117175
- const before = readFileSync84(destFile, "utf-8");
118098
+ const before = readFileSync85(destFile, "utf-8");
117176
118099
  const after = setSwitchroomTimezone(before, zone);
117177
118100
  if (after !== before)
117178
118101
  writeSwitchroomYaml(destFile, after);
@@ -117251,7 +118174,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
117251
118174
  try {
117252
118175
  const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
117253
118176
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
117254
- if (existsSync93(vaultPath)) {
118177
+ if (existsSync94(vaultPath)) {
117255
118178
  const secrets = openVault2(passphrase, vaultPath);
117256
118179
  const key = rawToken.replace("vault:", "");
117257
118180
  const entry = secrets[key];
@@ -117279,7 +118202,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
117279
118202
  async function storeTokenInVault(config, vaultRef, token) {
117280
118203
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
117281
118204
  const key = vaultRef.replace("vault:", "");
117282
- if (!existsSync93(vaultPath)) {
118205
+ if (!existsSync94(vaultPath)) {
117283
118206
  console.log(source_default.gray(" Creating encrypted vault..."));
117284
118207
  let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
117285
118208
  if (!passphrase) {
@@ -117562,13 +118485,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
117562
118485
  return;
117563
118486
  }
117564
118487
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
117565
- if (!existsSync93(vaultPath)) {
118488
+ if (!existsSync94(vaultPath)) {
117566
118489
  console.log(source_default.gray(" Skipping (vault not created yet)."));
117567
118490
  return;
117568
118491
  }
117569
118492
  const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
117570
118493
  const credPath = resolvePath(credPathRaw);
117571
- if (config.vault?.broker?.autoUnlock === true && existsSync93(credPath)) {
118494
+ if (config.vault?.broker?.autoUnlock === true && existsSync94(credPath)) {
117572
118495
  console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
117573
118496
  return;
117574
118497
  }
@@ -117646,10 +118569,10 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
117646
118569
  }
117647
118570
  }
117648
118571
  function persistApprovalAuthTelegramId(configPath) {
117649
- if (!existsSync93(configPath)) {
118572
+ if (!existsSync94(configPath)) {
117650
118573
  throw new ConfigError(`Cannot persist vault.broker.approvalAuth: config not found at ${configPath}`);
117651
118574
  }
117652
- const content = readFileSync84(configPath, "utf-8");
118575
+ const content = readFileSync85(configPath, "utf-8");
117653
118576
  const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
117654
118577
  if (result.kind === "rewritten") {
117655
118578
  writeSwitchroomYaml(configPath, result.content);
@@ -117657,10 +118580,10 @@ function persistApprovalAuthTelegramId(configPath) {
117657
118580
  return result.kind;
117658
118581
  }
117659
118582
  function persistDangerousMode(config, configPath) {
117660
- if (!existsSync93(configPath)) {
118583
+ if (!existsSync94(configPath)) {
117661
118584
  throw new ConfigError(`Cannot persist dangerous_mode: config not found at ${configPath}`);
117662
118585
  }
117663
- let content = readFileSync84(configPath, "utf-8");
118586
+ let content = readFileSync85(configPath, "utf-8");
117664
118587
  const agentNames = Object.keys(config.agents);
117665
118588
  for (const name of agentNames) {
117666
118589
  const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
@@ -117700,7 +118623,7 @@ async function stepOnboardingGuidance(config, nonInteractive) {
117700
118623
  const agentNames = Object.keys(config.agents);
117701
118624
  let allAuthenticated = true;
117702
118625
  for (const name of agentNames) {
117703
- const agentDir = resolve48(agentsDir, name);
118626
+ const agentDir = resolve49(agentsDir, name);
117704
118627
  const status = getAuthStatus(name, agentDir);
117705
118628
  if (status.authenticated) {
117706
118629
  console.log(` ${source_default.green("OK")} ${source_default.bold(name)}` + source_default.gray(` - authenticated (expires: ${status.timeUntilExpiry ?? "unknown"})`));
@@ -117796,7 +118719,7 @@ async function stepVerification(config, nonInteractive, deps = {}, expect = {})
117796
118719
  const findings = [];
117797
118720
  let applyPending = false;
117798
118721
  if (!nonInteractive && agentNames.length > 0) {
117799
- const composeExists = (deps.composeFileExists ?? (() => existsSync93(composeFilePath())))();
118722
+ const composeExists = (deps.composeFileExists ?? (() => existsSync94(composeFilePath())))();
117800
118723
  if (!composeExists) {
117801
118724
  applyPending = true;
117802
118725
  log(source_default.gray(" Not started: `switchroom apply` has not generated the compose file yet."));
@@ -117893,9 +118816,9 @@ init_source();
117893
118816
  init_loader();
117894
118817
  init_lifecycle();
117895
118818
  init_compose_env();
117896
- import { existsSync as existsSync97, mkdirSync as mkdirSync56, readFileSync as readFileSync88, realpathSync as realpathSync8, statSync as statSync56, chownSync as chownSync13 } from "node:fs";
118819
+ import { existsSync as existsSync98, mkdirSync as mkdirSync56, readFileSync as readFileSync89, realpathSync as realpathSync8, statSync as statSync57, chownSync as chownSync13 } from "node:fs";
117897
118820
  import { spawnSync as spawnSync20 } from "node:child_process";
117898
- import { join as join101, dirname as dirname42 } from "node:path";
118821
+ import { join as join102, dirname as dirname42 } from "node:path";
117899
118822
  import { homedir as homedir51 } from "node:os";
117900
118823
 
117901
118824
  // src/cli/release-yaml.ts
@@ -117956,7 +118879,7 @@ init_operator_uid();
117956
118879
  init_atomic();
117957
118880
 
117958
118881
  // src/cli/preflight-mounts.ts
117959
- import { statSync as statSync54 } from "node:fs";
118882
+ import { statSync as statSync55 } from "node:fs";
117960
118883
  function parseHostBindSources(composeText) {
117961
118884
  const out = [];
117962
118885
  const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
@@ -117983,7 +118906,7 @@ function parseHostBindSources(composeText) {
117983
118906
  return out;
117984
118907
  }
117985
118908
  function validateBindSources(composeText, deps = {}) {
117986
- const stat = deps.stat ?? ((p) => statSync54(p));
118909
+ const stat2 = deps.stat ?? ((p) => statSync55(p));
117987
118910
  const sources = parseHostBindSources(composeText);
117988
118911
  const issues = [];
117989
118912
  const seen = new Set;
@@ -117995,7 +118918,7 @@ function validateBindSources(composeText, deps = {}) {
117995
118918
  checked++;
117996
118919
  let st = null;
117997
118920
  try {
117998
- st = stat(source);
118921
+ st = stat2(source);
117999
118922
  } catch {
118000
118923
  st = null;
118001
118924
  }
@@ -118029,18 +118952,18 @@ init_self_update();
118029
118952
  init_shipped_assets();
118030
118953
 
118031
118954
  // src/cli/self-update-io.ts
118032
- import { createHash as createHash17 } from "node:crypto";
118955
+ import { createHash as createHash18 } from "node:crypto";
118033
118956
  import {
118034
118957
  chmodSync as chmodSync16,
118035
118958
  closeSync as closeSync18,
118036
118959
  copyFileSync as copyFileSync12,
118037
118960
  createWriteStream,
118038
- existsSync as existsSync94,
118961
+ existsSync as existsSync95,
118039
118962
  lstatSync as lstatSync13,
118040
118963
  mkdirSync as mkdirSync53,
118041
118964
  openSync as openSync18,
118042
- readdirSync as readdirSync32,
118043
- readFileSync as readFileSync85,
118965
+ readdirSync as readdirSync33,
118966
+ readFileSync as readFileSync86,
118044
118967
  readSync as readSync6,
118045
118968
  renameSync as renameSync23,
118046
118969
  rmSync as rmSync18,
@@ -118079,9 +119002,9 @@ function defaultSelfUpdateIO() {
118079
119002
  throw new Error(`empty response body for ${url}`);
118080
119003
  await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
118081
119004
  },
118082
- sha256File(path7) {
118083
- const hash2 = createHash17("sha256");
118084
- const fd = openSync18(path7, "r");
119005
+ sha256File(path9) {
119006
+ const hash2 = createHash18("sha256");
119007
+ const fd = openSync18(path9, "r");
118085
119008
  try {
118086
119009
  const buf = Buffer.allocUnsafe(1 << 20);
118087
119010
  for (;; ) {
@@ -118095,8 +119018,8 @@ function defaultSelfUpdateIO() {
118095
119018
  }
118096
119019
  return hash2.digest("hex");
118097
119020
  },
118098
- probeBinary(path7) {
118099
- const r = spawnSync18(path7, ["--version"], {
119021
+ probeBinary(path9) {
119022
+ const r = spawnSync18(path9, ["--version"], {
118100
119023
  encoding: "utf-8",
118101
119024
  timeout: 30000,
118102
119025
  env: { ...process.env, SWITCHROOM_SELF_UPDATED: "" }
@@ -118136,24 +119059,24 @@ function defaultSelfUpdateIO() {
118136
119059
  copyFile(src, dest) {
118137
119060
  copyFileSync12(src, dest);
118138
119061
  },
118139
- chmodExec(path7) {
118140
- chmodSync16(path7, 493);
119062
+ chmodExec(path9) {
119063
+ chmodSync16(path9, 493);
118141
119064
  },
118142
119065
  rename(src, dest) {
118143
119066
  renameSync23(src, dest);
118144
119067
  },
118145
- remove(path7) {
118146
- rmSync18(path7, { force: true });
119068
+ remove(path9) {
119069
+ rmSync18(path9, { force: true });
118147
119070
  },
118148
- exists(path7) {
118149
- return existsSync94(path7);
119071
+ exists(path9) {
119072
+ return existsSync95(path9);
118150
119073
  },
118151
- dirname(path7) {
118152
- return dirname39(path7);
119074
+ dirname(path9) {
119075
+ return dirname39(path9);
118153
119076
  },
118154
- readText(path7) {
119077
+ readText(path9) {
118155
119078
  try {
118156
- return readFileSync85(path7, "utf-8");
119079
+ return readFileSync86(path9, "utf-8");
118157
119080
  } catch {
118158
119081
  return null;
118159
119082
  }
@@ -118173,19 +119096,19 @@ function defaultSelfUpdateIO() {
118173
119096
  symlink(target, linkPath) {
118174
119097
  symlinkSync5(target, linkPath);
118175
119098
  },
118176
- isSymlink(path7) {
119099
+ isSymlink(path9) {
118177
119100
  try {
118178
- return lstatSync13(path7).isSymbolicLink();
119101
+ return lstatSync13(path9).isSymbolicLink();
118179
119102
  } catch {
118180
119103
  return false;
118181
119104
  }
118182
119105
  },
118183
- removeTree(path7) {
118184
- rmSync18(path7, { recursive: true, force: true });
119106
+ removeTree(path9) {
119107
+ rmSync18(path9, { recursive: true, force: true });
118185
119108
  },
118186
119109
  listDir(dir) {
118187
119110
  try {
118188
- return readdirSync32(dir);
119111
+ return readdirSync33(dir);
118189
119112
  } catch {
118190
119113
  return [];
118191
119114
  }
@@ -118200,7 +119123,7 @@ init_install_cron();
118200
119123
 
118201
119124
  // src/memory/mm-refresh-cron.ts
118202
119125
  import { execFileSync as execFileSync30 } from "node:child_process";
118203
- import { chownSync as chownSync12, existsSync as existsSync95, mkdirSync as mkdirSync54, readFileSync as readFileSync86, renameSync as renameSync24, statSync as statSync55, writeFileSync as writeFileSync37 } from "node:fs";
119126
+ import { chownSync as chownSync12, existsSync as existsSync96, mkdirSync as mkdirSync54, readFileSync as readFileSync87, renameSync as renameSync24, statSync as statSync56, writeFileSync as writeFileSync37 } from "node:fs";
118204
119127
  import { dirname as dirname40 } from "node:path";
118205
119128
  var CRON_PATH3 = "/etc/cron.d/mental-model-refresh";
118206
119129
  var CRON_SCHEDULE2 = "27 4 * * *";
@@ -118217,20 +119140,20 @@ function renderCron3(opts) {
118217
119140
  `;
118218
119141
  }
118219
119142
  function installCron3(opts) {
118220
- const path7 = opts.path ?? CRON_PATH3;
119143
+ const path9 = opts.path ?? CRON_PATH3;
118221
119144
  const content = renderCron3(opts);
118222
- if (existsSync95(path7)) {
119145
+ if (existsSync96(path9)) {
118223
119146
  try {
118224
- if (readFileSync86(path7, "utf8") === content) {
118225
- return { status: "unchanged", path: path7, content };
119147
+ if (readFileSync87(path9, "utf8") === content) {
119148
+ return { status: "unchanged", path: path9, content };
118226
119149
  }
118227
119150
  } catch {}
118228
119151
  }
118229
- mkdirSync54(dirname40(path7), { recursive: true });
118230
- const tmp = `${path7}.${process.pid}.tmp`;
119152
+ mkdirSync54(dirname40(path9), { recursive: true });
119153
+ const tmp = `${path9}.${process.pid}.tmp`;
118231
119154
  writeFileSync37(tmp, content, { mode: 420 });
118232
- renameSync24(tmp, path7);
118233
- return { status: "installed", path: path7, content };
119155
+ renameSync24(tmp, path9);
119156
+ return { status: "installed", path: path9, content };
118234
119157
  }
118235
119158
  var defaultIdResolver3 = (user) => {
118236
119159
  const uid = Number(execFileSync30("id", ["-u", user], { encoding: "utf8" }).trim());
@@ -118241,27 +119164,27 @@ var defaultIdResolver3 = (user) => {
118241
119164
  return { uid, gid };
118242
119165
  };
118243
119166
  function ensureLogFile3(opts) {
118244
- const path7 = opts.path ?? CRON_LOG_PATH3;
118245
- if (existsSync95(path7))
118246
- return { status: "unchanged", path: path7 };
119167
+ const path9 = opts.path ?? CRON_LOG_PATH3;
119168
+ if (existsSync96(path9))
119169
+ return { status: "unchanged", path: path9 };
118247
119170
  const { uid, gid } = (opts.resolveIds ?? defaultIdResolver3)(opts.user);
118248
- mkdirSync54(dirname40(path7), { recursive: true });
118249
- writeFileSync37(path7, "", { mode: 420, flag: "a" });
119171
+ mkdirSync54(dirname40(path9), { recursive: true });
119172
+ writeFileSync37(path9, "", { mode: 420, flag: "a" });
118250
119173
  const isRoot = process.getuid?.() === 0;
118251
119174
  let alreadyOwned = false;
118252
119175
  try {
118253
- const st = statSync55(path7);
119176
+ const st = statSync56(path9);
118254
119177
  alreadyOwned = st.uid === uid && st.gid === gid;
118255
119178
  } catch {}
118256
119179
  if (isRoot && !alreadyOwned) {
118257
119180
  try {
118258
- chownSync12(path7, uid, gid);
119181
+ chownSync12(path9, uid, gid);
118259
119182
  } catch (e) {
118260
119183
  if (e.code !== "EPERM")
118261
119184
  throw e;
118262
119185
  }
118263
119186
  }
118264
- return { status: "created", path: path7 };
119187
+ return { status: "created", path: path9 };
118265
119188
  }
118266
119189
  function renderLogrotate3(opts) {
118267
119190
  return `${opts.logPath} {
@@ -118277,20 +119200,20 @@ function renderLogrotate3(opts) {
118277
119200
  `;
118278
119201
  }
118279
119202
  function installLogrotate3(opts) {
118280
- const path7 = opts.path ?? LOGROTATE_PATH3;
119203
+ const path9 = opts.path ?? LOGROTATE_PATH3;
118281
119204
  const content = renderLogrotate3({ logPath: opts.logPath ?? CRON_LOG_PATH3, user: opts.user });
118282
- if (existsSync95(path7)) {
119205
+ if (existsSync96(path9)) {
118283
119206
  try {
118284
- if (readFileSync86(path7, "utf8") === content) {
118285
- return { status: "unchanged", path: path7, content };
119207
+ if (readFileSync87(path9, "utf8") === content) {
119208
+ return { status: "unchanged", path: path9, content };
118286
119209
  }
118287
119210
  } catch {}
118288
119211
  }
118289
- mkdirSync54(dirname40(path7), { recursive: true });
118290
- const tmp = `${path7}.${process.pid}.tmp`;
119212
+ mkdirSync54(dirname40(path9), { recursive: true });
119213
+ const tmp = `${path9}.${process.pid}.tmp`;
118291
119214
  writeFileSync37(tmp, content, { mode: 420 });
118292
- renameSync24(tmp, path7);
118293
- return { status: "installed", path: path7, content };
119215
+ renameSync24(tmp, path9);
119216
+ return { status: "installed", path: path9, content };
118294
119217
  }
118295
119218
  function parseCronUser3(content) {
118296
119219
  for (const line of content.split(`
@@ -118307,20 +119230,20 @@ function parseCronUser3(content) {
118307
119230
  return null;
118308
119231
  }
118309
119232
  function reconcileCron3(opts) {
118310
- const path7 = opts.path ?? CRON_PATH3;
118311
- if (!existsSync95(path7))
118312
- return { status: "absent", path: path7 };
119233
+ const path9 = opts.path ?? CRON_PATH3;
119234
+ if (!existsSync96(path9))
119235
+ return { status: "absent", path: path9 };
118313
119236
  let user = opts.fallbackUser;
118314
119237
  try {
118315
- user = parseCronUser3(readFileSync86(path7, "utf8")) ?? opts.fallbackUser;
119238
+ user = parseCronUser3(readFileSync87(path9, "utf8")) ?? opts.fallbackUser;
118316
119239
  } catch {}
118317
119240
  if (!user) {
118318
- throw new Error(`cannot reconcile ${path7}: no cron-user parseable from the existing fragment and no fallback user available`);
119241
+ throw new Error(`cannot reconcile ${path9}: no cron-user parseable from the existing fragment and no fallback user available`);
118319
119242
  }
118320
- const r = installCron3({ user, binary: opts.binary, path: path7 });
119243
+ const r = installCron3({ user, binary: opts.binary, path: path9 });
118321
119244
  return {
118322
119245
  status: r.status === "installed" ? "reconciled" : "unchanged",
118323
- path: path7,
119246
+ path: path9,
118324
119247
  content: r.content
118325
119248
  };
118326
119249
  }
@@ -118330,7 +119253,7 @@ init_install_cron2();
118330
119253
 
118331
119254
  // src/host-cli-self-heal/install-timer.ts
118332
119255
  import { spawnSync as spawnSync19 } from "node:child_process";
118333
- import { existsSync as existsSync96, mkdirSync as mkdirSync55, readFileSync as readFileSync87, renameSync as renameSync25, writeFileSync as writeFileSync38 } from "node:fs";
119256
+ import { existsSync as existsSync97, mkdirSync as mkdirSync55, readFileSync as readFileSync88, renameSync as renameSync25, writeFileSync as writeFileSync38 } from "node:fs";
118334
119257
  import { dirname as dirname41 } from "node:path";
118335
119258
  var SERVICE_PATH = "/etc/systemd/system/switchroom-self-heal.service";
118336
119259
  var TIMER_PATH = "/etc/systemd/system/switchroom-self-heal.timer";
@@ -118376,22 +119299,22 @@ function renderTimer() {
118376
119299
  ` + `WantedBy=timers.target
118377
119300
  `;
118378
119301
  }
118379
- function writeUnitIdempotent(path7, content) {
118380
- if (existsSync96(path7)) {
119302
+ function writeUnitIdempotent(path9, content) {
119303
+ if (existsSync97(path9)) {
118381
119304
  try {
118382
- if (readFileSync87(path7, "utf8") === content)
119305
+ if (readFileSync88(path9, "utf8") === content)
118383
119306
  return "unchanged";
118384
119307
  } catch {}
118385
119308
  }
118386
- mkdirSync55(dirname41(path7), { recursive: true });
118387
- const tmp = `${path7}.${process.pid}.tmp`;
119309
+ mkdirSync55(dirname41(path9), { recursive: true });
119310
+ const tmp = `${path9}.${process.pid}.tmp`;
118388
119311
  writeFileSync38(tmp, content, { mode: 420 });
118389
- renameSync25(tmp, path7);
119312
+ renameSync25(tmp, path9);
118390
119313
  return "written";
118391
119314
  }
118392
119315
  function homeFromPasswd2(user) {
118393
119316
  try {
118394
- for (const line of readFileSync87("/etc/passwd", "utf8").split(`
119317
+ for (const line of readFileSync88("/etc/passwd", "utf8").split(`
118395
119318
  `)) {
118396
119319
  const f = line.split(":");
118397
119320
  if (f[0] === user && f[5])
@@ -118408,7 +119331,7 @@ function resolveOperatorUser(env2) {
118408
119331
  }
118409
119332
  function installSelfHealTimer(deps = {}) {
118410
119333
  const env2 = deps.env ?? process.env;
118411
- const systemdBooted = deps.systemdBooted ?? (() => existsSync96(SYSTEMD_MARKER));
119334
+ const systemdBooted = deps.systemdBooted ?? (() => existsSync97(SYSTEMD_MARKER));
118412
119335
  const geteuid = deps.geteuid ?? (() => typeof process.geteuid === "function" ? process.geteuid() : undefined);
118413
119336
  const homeForUser = deps.homeForUser ?? homeFromPasswd2;
118414
119337
  const servicePath = deps.servicePath ?? SERVICE_PATH;
@@ -118469,22 +119392,22 @@ function installSelfHealTimer(deps = {}) {
118469
119392
  // src/cli/update.ts
118470
119393
  function defaultPersistPin(configPath) {
118471
119394
  return (pin) => {
118472
- const path7 = configPath ?? findConfigFile();
118473
- const before = readFileSync88(path7, "utf8");
119395
+ const path9 = configPath ?? findConfigFile();
119396
+ const before = readFileSync89(path9, "utf8");
118474
119397
  const after = setReleasePinInConfig(before, pin);
118475
119398
  if (after === before)
118476
119399
  return;
118477
- writeConfigFileSync(path7, after, statSync56(path7).mode & 511);
119400
+ writeConfigFileSync(path9, after, statSync57(path9).mode & 511);
118478
119401
  try {
118479
119402
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
118480
119403
  const uid = resolveOperatorUid();
118481
119404
  if (uid !== undefined)
118482
- chownSync13(path7, uid, uid);
119405
+ chownSync13(path9, uid, uid);
118483
119406
  }
118484
119407
  } catch {}
118485
119408
  };
118486
119409
  }
118487
- var DEFAULT_COMPOSE_PATH2 = join101(homedir51(), ".switchroom", "compose", "docker-compose.yml");
119410
+ var DEFAULT_COMPOSE_PATH2 = join102(homedir51(), ".switchroom", "compose", "docker-compose.yml");
118488
119411
  var PACKAGED_INSTALL_KINDS = new Set([
118489
119412
  "static-binary",
118490
119413
  "npm-global",
@@ -118493,9 +119416,9 @@ var PACKAGED_INSTALL_KINDS = new Set([
118493
119416
  function runningFromSwitchroomCheckout(scriptPath) {
118494
119417
  let dir = dirname42(scriptPath);
118495
119418
  for (let i2 = 0;i2 < 12; i2++) {
118496
- if (existsSync97(join101(dir, ".git"))) {
119419
+ if (existsSync98(join102(dir, ".git"))) {
118497
119420
  try {
118498
- const pkg = JSON.parse(readFileSync88(join101(dir, "package.json"), "utf-8"));
119421
+ const pkg = JSON.parse(readFileSync89(join102(dir, "package.json"), "utf-8"));
118499
119422
  if (pkg.name === "switchroom")
118500
119423
  return true;
118501
119424
  } catch {}
@@ -118562,7 +119485,7 @@ function planUpdate(opts) {
118562
119485
  bundleDir: import.meta.dirname,
118563
119486
  execPath: process.execPath,
118564
119487
  scriptPath,
118565
- inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync97("/.dockerenv")
119488
+ inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync98("/.dockerenv")
118566
119489
  });
118567
119490
  if (detection.kind !== "static-binary") {
118568
119491
  say(source_default.gray(` host CLI not self-updated: ${detection.reason}
@@ -118645,7 +119568,7 @@ function planUpdate(opts) {
118645
119568
  steps.push({
118646
119569
  name: "pull-images",
118647
119570
  description: "Pull broker / kernel / agent images from GHCR",
118648
- skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync97(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
119571
+ skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync98(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
118649
119572
  run: () => {
118650
119573
  const r = runner("docker", [
118651
119574
  "compose",
@@ -118902,13 +119825,13 @@ function planUpdate(opts) {
118902
119825
  bundleDir: import.meta.dirname,
118903
119826
  execPath: process.execPath
118904
119827
  });
118905
- const dest = join101(homedir51(), ".switchroom", "skills", "_bundled");
119828
+ const dest = join102(homedir51(), ".switchroom", "skills", "_bundled");
118906
119829
  if (resolution.path === null) {
118907
119830
  const detection = detectInstallKind(opts.installProbe ?? {
118908
119831
  bundleDir: import.meta.dirname,
118909
119832
  execPath: process.execPath,
118910
119833
  scriptPath,
118911
- inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync97("/.dockerenv")
119834
+ inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync98("/.dockerenv")
118912
119835
  });
118913
119836
  const detail = `no shipped skills/ payload found for this install ` + `(${detection.kind}; ${describeShippedAssetSearch(resolution)})`;
118914
119837
  if (PACKAGED_INSTALL_KINDS.has(detection.kind)) {
@@ -118947,11 +119870,11 @@ function planUpdate(opts) {
118947
119870
  run: () => {
118948
119871
  if (opts.syncBundledSkillsFn)
118949
119872
  return;
118950
- const dest = join101(homedir51(), ".switchroom", "skills", "_bundled");
118951
- if (!existsSync97(dest)) {
119873
+ const dest = join102(homedir51(), ".switchroom", "skills", "_bundled");
119874
+ if (!existsSync98(dest)) {
118952
119875
  return;
118953
119876
  }
118954
- const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync97(join101(dest, key)));
119877
+ const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync98(join102(dest, key)));
118955
119878
  if (missing.length > 0) {
118956
119879
  throw new Error(`verify-bundled-skills: builtin default skill(s) missing from the pool after sync: ` + `${missing.join(", ")}. These ship in the CLI package and must exist in ${dest}. ` + `This is a broken sync or a packaging regression \u2014 the pool is not converged.`);
118957
119880
  }
@@ -118986,7 +119909,7 @@ function planUpdate(opts) {
118986
119909
  description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
118987
119910
  run: () => {
118988
119911
  try {
118989
- const composeText = readFileSync88(composePath, "utf8");
119912
+ const composeText = readFileSync89(composePath, "utf8");
118990
119913
  const pf = validateBindSources(composeText);
118991
119914
  if (!pf.ok)
118992
119915
  throw new Error(formatPreflightError(pf));
@@ -119061,14 +119984,14 @@ function defaultStatusProbe(composePath) {
119061
119984
  } catch {}
119062
119985
  if (scriptPath) {
119063
119986
  try {
119064
- cliBuiltAt = new Date(statSync56(scriptPath).mtimeMs).toISOString();
119987
+ cliBuiltAt = new Date(statSync57(scriptPath).mtimeMs).toISOString();
119065
119988
  } catch {}
119066
119989
  let dir = dirname42(scriptPath);
119067
119990
  for (let i2 = 0;i2 < 8; i2++) {
119068
- const pkgPath = join101(dir, "package.json");
119069
- if (existsSync97(pkgPath)) {
119991
+ const pkgPath = join102(dir, "package.json");
119992
+ if (existsSync98(pkgPath)) {
119070
119993
  try {
119071
- const pkg = JSON.parse(readFileSync88(pkgPath, "utf-8"));
119994
+ const pkg = JSON.parse(readFileSync89(pkgPath, "utf-8"));
119072
119995
  if (typeof pkg.version === "string")
119073
119996
  cliVersion = pkg.version;
119074
119997
  } catch (err) {
@@ -119089,7 +120012,7 @@ function defaultStatusProbe(composePath) {
119089
120012
  warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
119090
120013
  }
119091
120014
  const services = [];
119092
- if (!existsSync97(composePath)) {
120015
+ if (!existsSync98(composePath)) {
119093
120016
  warnings.push(`compose file not found at ${composePath}; service status unknown`);
119094
120017
  return { cliVersion, cliBuiltAt, services, warnings };
119095
120018
  }
@@ -119352,22 +120275,22 @@ function registerUpdateCommand(program3) {
119352
120275
  // src/cli/rollout.ts
119353
120276
  init_helpers();
119354
120277
  import { spawnSync as spawnSync22 } from "node:child_process";
119355
- import { readFileSync as readFileSync90, chownSync as chownSync14, statSync as statSync59 } from "node:fs";
120278
+ import { readFileSync as readFileSync91, chownSync as chownSync14, statSync as statSync60 } from "node:fs";
119356
120279
  import { homedir as homedir53 } from "node:os";
119357
120280
 
119358
120281
  // src/cli/rollout-pin-journal.ts
119359
120282
  import {
119360
- existsSync as existsSync98,
119361
- readFileSync as readFileSync89,
120283
+ existsSync as existsSync99,
120284
+ readFileSync as readFileSync90,
119362
120285
  writeFileSync as writeFileSync39,
119363
120286
  renameSync as renameSync26,
119364
120287
  unlinkSync as unlinkSync19,
119365
- statSync as statSync57,
120288
+ statSync as statSync58,
119366
120289
  mkdirSync as mkdirSync57
119367
120290
  } from "node:fs";
119368
120291
  import { homedir as homedir52 } from "node:os";
119369
- import { createHash as createHash18 } from "node:crypto";
119370
- import { join as join102, basename as basename9, resolve as resolve50, dirname as dirname43 } from "node:path";
120292
+ import { createHash as createHash19 } from "node:crypto";
120293
+ import { join as join103, basename as basename9, resolve as resolve51, dirname as dirname43 } from "node:path";
119371
120294
  init_flock();
119372
120295
  var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
119373
120296
  var STATE_DIR_NAME = ".switchroom";
@@ -119376,16 +120299,16 @@ function pinJournalDir(configPath) {
119376
120299
  if (override && override.trim().length > 0)
119377
120300
  return override.trim();
119378
120301
  if (configPath) {
119379
- const dir = dirname43(resolve50(configPath));
120302
+ const dir = dirname43(resolve51(configPath));
119380
120303
  if (basename9(dir) === STATE_DIR_NAME)
119381
120304
  return dir;
119382
120305
  }
119383
- return join102(homedir52(), STATE_DIR_NAME);
120306
+ return join103(homedir52(), STATE_DIR_NAME);
119384
120307
  }
119385
120308
  function pinJournalPath(configPath) {
119386
- const abs = resolve50(configPath);
119387
- const key = createHash18("sha256").update(abs).digest("hex").slice(0, 12);
119388
- return join102(pinJournalDir(abs), `.rollout-pin-journal.${basename9(abs)}.${key}.json`);
120309
+ const abs = resolve51(configPath);
120310
+ const key = createHash19("sha256").update(abs).digest("hex").slice(0, 12);
120311
+ return join103(pinJournalDir(abs), `.rollout-pin-journal.${basename9(abs)}.${key}.json`);
119389
120312
  }
119390
120313
  function isPidAlive(pid) {
119391
120314
  if (!Number.isInteger(pid) || pid <= 0)
@@ -119419,9 +120342,9 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
119419
120342
  const p = pinJournalPath(configPath);
119420
120343
  let raw;
119421
120344
  try {
119422
- raw = readFileSync89(p, "utf8");
120345
+ raw = readFileSync90(p, "utf8");
119423
120346
  } catch (e) {
119424
- if (existsSync98(p)) {
120347
+ if (existsSync99(p)) {
119425
120348
  warn(`\u26a0\ufe0f rollout pin journal: ${p} exists but could not be read ` + `(${e.message}). A provisional \`release.pin\` may be ` + `uncommitted \u2014 verify it host-side before the next reconcile.
119426
120349
  `);
119427
120350
  }
@@ -119472,7 +120395,7 @@ function beginPinPersist(configPath, pin, opts = {}) {
119472
120395
  warn(`\u26a0\ufe0f rollout pin journal: overwriting an ABANDONED journal at ${p} ` + `(pid ${existing.pid} recorded ${existing.at}, provisional pin ` + `${existing.pin}). Its roll never committed or reverted; \`release.pin\` ` + `in ${configPath} may still name that unproven build \u2014 verify it.
119473
120396
  `);
119474
120397
  }
119475
- const priorPin = getReleasePinFromConfig(readFileSync89(configPath, "utf8"));
120398
+ const priorPin = getReleasePinFromConfig(readFileSync90(configPath, "utf8"));
119476
120399
  const journal = {
119477
120400
  v: 1,
119478
120401
  configPath,
@@ -119493,7 +120416,7 @@ function commitPinPersist(configPath) {
119493
120416
  unlinkSync19(p);
119494
120417
  return null;
119495
120418
  } catch (e) {
119496
- if (!existsSync98(p))
120419
+ if (!existsSync99(p))
119497
120420
  return null;
119498
120421
  return `rollout pin journal: FAILED to clear ${p} after a SUCCESSFUL roll ` + `(${e.message}). Delete it host-side \u2014 while it exists, ` + `recovery may revert a proven \`release.pin\`.`;
119499
120422
  }
@@ -119515,11 +120438,11 @@ function rollbackPinPersist(configPath, opts = {}) {
119515
120438
  }
119516
120439
  }
119517
120440
  try {
119518
- const current = readFileSync89(configPath, "utf8");
120441
+ const current = readFileSync90(configPath, "utf8");
119519
120442
  const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
119520
120443
  let mode = 384;
119521
120444
  try {
119522
- mode = statSync57(configPath).mode & 511;
120445
+ mode = statSync58(configPath).mode & 511;
119523
120446
  } catch {}
119524
120447
  if (opts.writeConfig) {
119525
120448
  opts.writeConfig(configPath, next, mode);
@@ -119569,8 +120492,8 @@ init_host_cli_stamp();
119569
120492
 
119570
120493
  // src/cli/host-cli-upgrade.ts
119571
120494
  init_self_update();
119572
- import { lchownSync, lstatSync as lstatSync14, readdirSync as readdirSync33, statSync as statSync58 } from "node:fs";
119573
- import { basename as basename10, dirname as dirname44, join as join103 } from "node:path";
120495
+ import { lchownSync, lstatSync as lstatSync14, readdirSync as readdirSync34, statSync as statSync59 } from "node:fs";
120496
+ import { basename as basename10, dirname as dirname44, join as join104 } from "node:path";
119574
120497
  init_shipped_assets();
119575
120498
  var HOST_CLI_UPGRADE_SENTINEL = "SWITCHROOM_HOST_CLI_UPGRADE:";
119576
120499
  function encodeHostCliUpgradeResult(r) {
@@ -119606,7 +120529,7 @@ function defaultIo() {
119606
120529
  return {
119607
120530
  isFile: (p) => {
119608
120531
  try {
119609
- return statSync58(p).isFile();
120532
+ return statSync59(p).isFile();
119610
120533
  } catch {
119611
120534
  return false;
119612
120535
  }
@@ -119636,7 +120559,7 @@ function defaultIo() {
119636
120559
  chown: (p, uid, gid) => lchownSync(p, uid, gid),
119637
120560
  list: (d) => {
119638
120561
  try {
119639
- return readdirSync33(d);
120562
+ return readdirSync34(d);
119640
120563
  } catch {
119641
120564
  return [];
119642
120565
  }
@@ -119667,7 +120590,7 @@ function handBackOwnership(paths, owner, io) {
119667
120590
  if (kind !== "dir")
119668
120591
  continue;
119669
120592
  for (const child of io.list(p))
119670
- stack.push(join103(p, child));
120593
+ stack.push(join104(p, child));
119671
120594
  }
119672
120595
  return failures;
119673
120596
  }
@@ -119706,7 +120629,7 @@ async function runHostCliUpgrade(opts, io = defaultIo(), log = () => {}) {
119706
120629
  const installRoot = payloadInstallRoot(binary);
119707
120630
  const failures = handBackOwnership([
119708
120631
  binary,
119709
- join103(dirname44(binary), ".switchroom-versions"),
120632
+ join104(dirname44(binary), ".switchroom-versions"),
119710
120633
  dirname44(installRoot),
119711
120634
  installRoot,
119712
120635
  payloadVersionDir(installRoot, pin)
@@ -119802,21 +120725,21 @@ function planHostCliHeal(opts) {
119802
120725
  reason: `the host CLI is a ${stamp.installKind} install \u2014 only a static-binary ` + `install can be replaced by swapping one file, so an operator must run ` + `the upgrade host-side`
119803
120726
  };
119804
120727
  }
119805
- const path7 = stamp.path;
119806
- if (!path7.startsWith("/") || path7.includes("/..")) {
119807
- return { action: "skip", reason: `recorded host CLI path "${path7}" is not a plain absolute path` };
120728
+ const path9 = stamp.path;
120729
+ if (!path9.startsWith("/") || path9.includes("/..")) {
120730
+ return { action: "skip", reason: `recorded host CLI path "${path9}" is not a plain absolute path` };
119808
120731
  }
119809
- const installDir = path7.slice(0, path7.lastIndexOf("/"));
119810
- const name = path7.slice(path7.lastIndexOf("/") + 1);
120732
+ const installDir = path9.slice(0, path9.lastIndexOf("/"));
120733
+ const name = path9.slice(path9.lastIndexOf("/") + 1);
119811
120734
  if (name !== "switchroom") {
119812
- return { action: "skip", reason: `recorded host CLI path "${path7}" is not named \`switchroom\`` };
120735
+ return { action: "skip", reason: `recorded host CLI path "${path9}" is not named \`switchroom\`` };
119813
120736
  }
119814
120737
  const prefixHostPath = installDir.slice(0, installDir.lastIndexOf("/"));
119815
120738
  const dirName = installDir.slice(installDir.lastIndexOf("/") + 1);
119816
120739
  if (prefixHostPath.length === 0 || dirName.length === 0) {
119817
120740
  return {
119818
120741
  action: "skip",
119819
- reason: `recorded host CLI path "${path7}" has no install prefix to bind \u2014 ` + `refusing to mount the host root`
120742
+ reason: `recorded host CLI path "${path9}" has no install prefix to bind \u2014 ` + `refusing to mount the host root`
119820
120743
  };
119821
120744
  }
119822
120745
  return {
@@ -120501,13 +121424,13 @@ function createRolloutDeps(params) {
120501
121424
  }
120502
121425
  return { ok: r.status === 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
120503
121426
  };
120504
- const writeConfigPreservingOwnership = (path7, text, mode) => {
120505
- writeConfigFileSync(path7, text, mode);
121427
+ const writeConfigPreservingOwnership = (path9, text, mode) => {
121428
+ writeConfigFileSync(path9, text, mode);
120506
121429
  try {
120507
121430
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
120508
121431
  const uid = resolveOperatorUid();
120509
121432
  if (uid !== undefined) {
120510
- chownSync14(path7, uid, uid);
121433
+ chownSync14(path9, uid, uid);
120511
121434
  } else if (hostdCtx) {
120512
121435
  warn("\u26a0\ufe0f rollout (hostd path): could not resolve operator uid; " + "skipping config chown-back to avoid leaving switchroom.yaml " + "root-owned. Verify ownership host-side if other yaml verbs " + `EACCES.
120513
121436
  `);
@@ -120566,12 +121489,12 @@ function createRolloutDeps(params) {
120566
121489
  return stamp ? { hostCli: stamp } : {};
120567
121490
  })(),
120568
121491
  persistPin: (pin) => {
120569
- const before = readFileSync90(configPath, "utf8");
121492
+ const before = readFileSync91(configPath, "utf8");
120570
121493
  const after = setReleasePinInConfig(before, pin);
120571
121494
  if (after === before)
120572
121495
  return false;
120573
121496
  beginPinPersist(configPath, pin);
120574
- writeConfigPreservingOwnership(configPath, after, statSync59(configPath).mode & 511);
121497
+ writeConfigPreservingOwnership(configPath, after, statSync60(configPath).mode & 511);
120575
121498
  return true;
120576
121499
  },
120577
121500
  commitPin: () => {
@@ -120847,7 +121770,7 @@ init_source();
120847
121770
  init_helpers();
120848
121771
  init_loader();
120849
121772
  init_lifecycle();
120850
- import { resolve as resolve51 } from "node:path";
121773
+ import { resolve as resolve52 } from "node:path";
120851
121774
 
120852
121775
  // src/cli/version.ts
120853
121776
  init_source();
@@ -120855,8 +121778,8 @@ init_helpers();
120855
121778
  init_lifecycle();
120856
121779
  init_resolve_version();
120857
121780
  import { execSync as execSync3 } from "node:child_process";
120858
- import { existsSync as existsSync99, readFileSync as readFileSync91 } from "node:fs";
120859
- import { dirname as dirname45, join as join104 } from "node:path";
121781
+ import { existsSync as existsSync100, readFileSync as readFileSync92 } from "node:fs";
121782
+ import { dirname as dirname45, join as join105 } from "node:path";
120860
121783
  function getClaudeCodeVersion() {
120861
121784
  try {
120862
121785
  const out = execSync3("claude --version 2>/dev/null", {
@@ -120906,11 +121829,11 @@ function formatUptime3(timestamp) {
120906
121829
  function locateSwitchroomInstallDir() {
120907
121830
  let dir = import.meta.dirname;
120908
121831
  for (let i2 = 0;i2 < 10 && dir && dir !== "/"; i2++) {
120909
- const pkgPath = join104(dir, "package.json");
120910
- if (existsSync99(pkgPath)) {
121832
+ const pkgPath = join105(dir, "package.json");
121833
+ if (existsSync100(pkgPath)) {
120911
121834
  try {
120912
- const pkg = JSON.parse(readFileSync91(pkgPath, "utf-8"));
120913
- if (pkg.name === "switchroom" && existsSync99(join104(dir, ".git"))) {
121835
+ const pkg = JSON.parse(readFileSync92(pkgPath, "utf-8"));
121836
+ if (pkg.name === "switchroom" && existsSync100(join105(dir, ".git"))) {
120914
121837
  return dir;
120915
121838
  }
120916
121839
  } catch {}
@@ -121003,7 +121926,7 @@ function registerRestartCommand(program3) {
121003
121926
  }
121004
121927
  const didRestart = res.restarted || !graceful;
121005
121928
  if (didRestart) {
121006
- const agentDir = resolve51(agentsDir, name);
121929
+ const agentDir = resolve52(agentsDir, name);
121007
121930
  const converged = waitForAuthConverge(name, agentDir);
121008
121931
  if (!converged) {
121009
121932
  console.log(source_default.yellow(` ${name}: agent is up but auth status didn't converge in 30s \u2014 check logs`));
@@ -121086,37 +122009,37 @@ Dependency manifest`));
121086
122009
  // src/cli/handoff.ts
121087
122010
  init_helpers();
121088
122011
  init_loader();
121089
- import { resolve as resolve52 } from "node:path";
122012
+ import { resolve as resolve53 } from "node:path";
121090
122013
  init_merge();
121091
122014
 
121092
122015
  // src/agents/session-retention.ts
121093
122016
  import {
121094
- existsSync as existsSync100,
121095
- readdirSync as readdirSync34,
121096
- statSync as statSync60,
122017
+ existsSync as existsSync101,
122018
+ readdirSync as readdirSync35,
122019
+ statSync as statSync61,
121097
122020
  unlinkSync as unlinkSync20
121098
122021
  } from "node:fs";
121099
- import { join as join105 } from "node:path";
122022
+ import { join as join106 } from "node:path";
121100
122023
  var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
121101
122024
  var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
121102
122025
  var MIN_KEEP = 2;
121103
122026
  function collectSessionJsonl(claudeConfigDir) {
121104
- const projects = join105(claudeConfigDir, "projects");
121105
- if (!existsSync100(projects))
122027
+ const projects = join106(claudeConfigDir, "projects");
122028
+ if (!existsSync101(projects))
121106
122029
  return [];
121107
122030
  const found = [];
121108
122031
  const walk2 = (dir) => {
121109
122032
  let entries;
121110
122033
  try {
121111
- entries = readdirSync34(dir);
122034
+ entries = readdirSync35(dir);
121112
122035
  } catch {
121113
122036
  return;
121114
122037
  }
121115
122038
  for (const name of entries) {
121116
- const full = join105(dir, name);
122039
+ const full = join106(dir, name);
121117
122040
  let st;
121118
122041
  try {
121119
- st = statSync60(full);
122042
+ st = statSync61(full);
121120
122043
  } catch {
121121
122044
  continue;
121122
122045
  }
@@ -121187,7 +122110,7 @@ function registerHandoffCommand(program3) {
121187
122110
  }
121188
122111
  agentConfig = resolveAgentConfig(config.defaults, config.profiles, rawAgent);
121189
122112
  const agentsDir = resolveAgentsDir(config);
121190
- agentDir = resolve52(agentsDir, agentName);
122113
+ agentDir = resolve53(agentsDir, agentName);
121191
122114
  } catch (err) {
121192
122115
  if (!(err instanceof ConfigError))
121193
122116
  throw err;
@@ -121202,7 +122125,7 @@ function registerHandoffCommand(program3) {
121202
122125
  `);
121203
122126
  return;
121204
122127
  }
121205
- const claudeConfigDir = resolve52(agentDir, ".claude");
122128
+ const claudeConfigDir = resolve53(agentDir, ".claude");
121206
122129
  const jsonl = findLatestSessionJsonl(claudeConfigDir);
121207
122130
  if (!jsonl) {
121208
122131
  process.stderr.write(`handoff: no session JSONL under ${claudeConfigDir}/projects; skipping
@@ -121234,18 +122157,18 @@ function registerHandoffCommand(program3) {
121234
122157
  // src/issues/store.ts
121235
122158
  import {
121236
122159
  closeSync as closeSync19,
121237
- existsSync as existsSync101,
122160
+ existsSync as existsSync102,
121238
122161
  mkdirSync as mkdirSync58,
121239
122162
  openSync as openSync19,
121240
- readdirSync as readdirSync35,
121241
- readFileSync as readFileSync92,
122163
+ readdirSync as readdirSync36,
122164
+ readFileSync as readFileSync93,
121242
122165
  renameSync as renameSync27,
121243
- statSync as statSync61,
122166
+ statSync as statSync62,
121244
122167
  unlinkSync as unlinkSync21,
121245
122168
  writeFileSync as writeFileSync40,
121246
122169
  writeSync as writeSync9
121247
122170
  } from "node:fs";
121248
- import { join as join106 } from "node:path";
122171
+ import { join as join107 } from "node:path";
121249
122172
  import { randomBytes as randomBytes16 } from "node:crypto";
121250
122173
  import { execSync as execSync4 } from "node:child_process";
121251
122174
 
@@ -121272,12 +122195,12 @@ function computeFingerprint(source, code) {
121272
122195
  var ISSUES_FILE = "issues.jsonl";
121273
122196
  var ISSUES_LOCK = "issues.lock";
121274
122197
  function readAll(stateDir) {
121275
- const path7 = join106(stateDir, ISSUES_FILE);
121276
- if (!existsSync101(path7))
122198
+ const path9 = join107(stateDir, ISSUES_FILE);
122199
+ if (!existsSync102(path9))
121277
122200
  return [];
121278
122201
  let raw;
121279
122202
  try {
121280
- raw = readFileSync92(path7, "utf-8");
122203
+ raw = readFileSync93(path9, "utf-8");
121281
122204
  } catch {
121282
122205
  return [];
121283
122206
  }
@@ -121349,8 +122272,8 @@ function record(stateDir, input, nowFn = Date.now) {
121349
122272
  return result;
121350
122273
  });
121351
122274
  }
121352
- function resolve53(stateDir, fingerprint, nowFn = Date.now) {
121353
- if (!existsSync101(join106(stateDir, ISSUES_FILE)))
122275
+ function resolve54(stateDir, fingerprint, nowFn = Date.now) {
122276
+ if (!existsSync102(join107(stateDir, ISSUES_FILE)))
121354
122277
  return 0;
121355
122278
  return withLock(stateDir, () => {
121356
122279
  const all = readAll(stateDir);
@@ -121368,7 +122291,7 @@ function resolve53(stateDir, fingerprint, nowFn = Date.now) {
121368
122291
  });
121369
122292
  }
121370
122293
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
121371
- if (!existsSync101(join106(stateDir, ISSUES_FILE)))
122294
+ if (!existsSync102(join107(stateDir, ISSUES_FILE)))
121372
122295
  return 0;
121373
122296
  return withLock(stateDir, () => {
121374
122297
  const all = readAll(stateDir);
@@ -121386,7 +122309,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
121386
122309
  });
121387
122310
  }
121388
122311
  function prune(stateDir, opts = {}) {
121389
- if (!existsSync101(join106(stateDir, ISSUES_FILE)))
122312
+ if (!existsSync102(join107(stateDir, ISSUES_FILE)))
121390
122313
  return 0;
121391
122314
  return withLock(stateDir, () => {
121392
122315
  const all = readAll(stateDir);
@@ -121419,21 +122342,21 @@ function ensureDir2(stateDir) {
121419
122342
  mkdirSync58(stateDir, { recursive: true });
121420
122343
  }
121421
122344
  function writeAll(stateDir, events) {
121422
- const path7 = join106(stateDir, ISSUES_FILE);
122345
+ const path9 = join107(stateDir, ISSUES_FILE);
121423
122346
  sweepOrphanTmpFiles(stateDir);
121424
- const tmp = `${path7}.tmp-${process.pid}-${randomBytes16(4).toString("hex")}`;
122347
+ const tmp = `${path9}.tmp-${process.pid}-${randomBytes16(4).toString("hex")}`;
121425
122348
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
121426
122349
  `) + `
121427
122350
  `;
121428
122351
  writeFileSync40(tmp, body, "utf-8");
121429
- renameSync27(tmp, path7);
122352
+ renameSync27(tmp, path9);
121430
122353
  }
121431
122354
  var ORPHAN_TMP_TTL_MS = 60000;
121432
122355
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
121433
122356
  function sweepOrphanTmpFiles(stateDir) {
121434
122357
  let entries;
121435
122358
  try {
121436
- entries = readdirSync35(stateDir);
122359
+ entries = readdirSync36(stateDir);
121437
122360
  } catch {
121438
122361
  return;
121439
122362
  }
@@ -121441,10 +122364,10 @@ function sweepOrphanTmpFiles(stateDir) {
121441
122364
  for (const entry of entries) {
121442
122365
  if (!entry.startsWith(TMP_PREFIX))
121443
122366
  continue;
121444
- const tmpPath = join106(stateDir, entry);
122367
+ const tmpPath = join107(stateDir, entry);
121445
122368
  try {
121446
- const stat = statSync61(tmpPath);
121447
- if (stat.mtimeMs < cutoff) {
122369
+ const stat2 = statSync62(tmpPath);
122370
+ if (stat2.mtimeMs < cutoff) {
121448
122371
  unlinkSync21(tmpPath);
121449
122372
  }
121450
122373
  } catch {}
@@ -121453,7 +122376,7 @@ function sweepOrphanTmpFiles(stateDir) {
121453
122376
  var LOCK_RETRY_MS = 25;
121454
122377
  var LOCK_TIMEOUT_MS = 1e4;
121455
122378
  function withLock(stateDir, fn) {
121456
- const lockPath = join106(stateDir, ISSUES_LOCK);
122379
+ const lockPath = join107(stateDir, ISSUES_LOCK);
121457
122380
  const startedAt = Date.now();
121458
122381
  let fd = null;
121459
122382
  while (fd === null) {
@@ -121488,7 +122411,7 @@ function withLock(stateDir, fn) {
121488
122411
  function tryStealStaleLock(lockPath) {
121489
122412
  let pidStr;
121490
122413
  try {
121491
- pidStr = readFileSync92(lockPath, "utf-8").trim();
122414
+ pidStr = readFileSync93(lockPath, "utf-8").trim();
121492
122415
  } catch {
121493
122416
  return true;
121494
122417
  }
@@ -121637,11 +122560,11 @@ function registerIssuesCommand(program3) {
121637
122560
  const stateDir = resolveStateDir2(opts);
121638
122561
  let flipped;
121639
122562
  if (opts.source && opts.code) {
121640
- flipped = resolve53(stateDir, computeFingerprint(opts.source, opts.code));
122563
+ flipped = resolve54(stateDir, computeFingerprint(opts.source, opts.code));
121641
122564
  } else if (opts.source && !fingerprint) {
121642
122565
  flipped = resolveAllBySource(stateDir, opts.source);
121643
122566
  } else if (fingerprint) {
121644
- flipped = resolve53(stateDir, fingerprint);
122567
+ flipped = resolve54(stateDir, fingerprint);
121645
122568
  } else {
121646
122569
  process.stderr.write(`issues resolve: need either <fingerprint>, --source, or --source + --code
121647
122570
  `);
@@ -121736,20 +122659,20 @@ function relTime(deltaMs) {
121736
122659
 
121737
122660
  // src/cli/deps.ts
121738
122661
  init_source();
121739
- import { existsSync as existsSync104 } from "node:fs";
122662
+ import { existsSync as existsSync105 } from "node:fs";
121740
122663
  import { homedir as homedir56 } from "node:os";
121741
- import { join as join109, resolve as resolve54 } from "node:path";
122664
+ import { join as join110, resolve as resolve55 } from "node:path";
121742
122665
 
121743
122666
  // src/deps/python.ts
121744
- import { createHash as createHash19 } from "node:crypto";
122667
+ import { createHash as createHash20 } from "node:crypto";
121745
122668
  import {
121746
- existsSync as existsSync102,
122669
+ existsSync as existsSync103,
121747
122670
  mkdirSync as mkdirSync59,
121748
- readFileSync as readFileSync93,
122671
+ readFileSync as readFileSync94,
121749
122672
  rmSync as rmSync19,
121750
122673
  writeFileSync as writeFileSync41
121751
122674
  } from "node:fs";
121752
- import { dirname as dirname46, join as join107 } from "node:path";
122675
+ import { dirname as dirname46, join as join108 } from "node:path";
121753
122676
  import { homedir as homedir54 } from "node:os";
121754
122677
  import { execFileSync as execFileSync31 } from "node:child_process";
121755
122678
 
@@ -121762,26 +122685,26 @@ class PythonEnvError extends Error {
121762
122685
  }
121763
122686
  }
121764
122687
  function defaultPythonCacheRoot() {
121765
- return join107(homedir54(), ".switchroom", "deps", "python");
122688
+ return join108(homedir54(), ".switchroom", "deps", "python");
121766
122689
  }
121767
- function hashFile(path7) {
121768
- return createHash19("sha256").update(readFileSync93(path7)).digest("hex");
122690
+ function hashFile(path9) {
122691
+ return createHash20("sha256").update(readFileSync94(path9)).digest("hex");
121769
122692
  }
121770
122693
  function ensurePythonEnv(opts) {
121771
122694
  const { skillName, requirementsPath, force = false } = opts;
121772
122695
  const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
121773
122696
  const hostPython = opts.pythonBin ?? "python3";
121774
- if (!existsSync102(requirementsPath)) {
122697
+ if (!existsSync103(requirementsPath)) {
121775
122698
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
121776
122699
  }
121777
- const venvDir = join107(cacheRoot, skillName);
121778
- const stampPath = join107(venvDir, ".requirements.sha256");
121779
- const binDir = join107(venvDir, "bin");
121780
- const pythonBin = join107(binDir, "python");
121781
- const pipBin = join107(binDir, "pip");
122700
+ const venvDir = join108(cacheRoot, skillName);
122701
+ const stampPath = join108(venvDir, ".requirements.sha256");
122702
+ const binDir = join108(venvDir, "bin");
122703
+ const pythonBin = join108(binDir, "python");
122704
+ const pipBin = join108(binDir, "pip");
121782
122705
  const targetHash = hashFile(requirementsPath);
121783
- if (!force && existsSync102(stampPath) && existsSync102(pythonBin)) {
121784
- const existingHash = readFileSync93(stampPath, "utf8").trim();
122706
+ if (!force && existsSync103(stampPath) && existsSync103(pythonBin)) {
122707
+ const existingHash = readFileSync94(stampPath, "utf8").trim();
121785
122708
  if (existingHash === targetHash) {
121786
122709
  return {
121787
122710
  skillName,
@@ -121793,7 +122716,7 @@ function ensurePythonEnv(opts) {
121793
122716
  };
121794
122717
  }
121795
122718
  }
121796
- if (existsSync102(venvDir)) {
122719
+ if (existsSync103(venvDir)) {
121797
122720
  rmSync19(venvDir, { recursive: true, force: true });
121798
122721
  }
121799
122722
  mkdirSync59(dirname46(venvDir), { recursive: true });
@@ -121828,16 +122751,16 @@ function ensurePythonEnv(opts) {
121828
122751
  }
121829
122752
 
121830
122753
  // src/deps/node.ts
121831
- import { createHash as createHash20 } from "node:crypto";
122754
+ import { createHash as createHash21 } from "node:crypto";
121832
122755
  import {
121833
122756
  copyFileSync as copyFileSync13,
121834
- existsSync as existsSync103,
122757
+ existsSync as existsSync104,
121835
122758
  mkdirSync as mkdirSync60,
121836
- readFileSync as readFileSync94,
122759
+ readFileSync as readFileSync95,
121837
122760
  rmSync as rmSync20,
121838
122761
  writeFileSync as writeFileSync42
121839
122762
  } from "node:fs";
121840
- import { dirname as dirname47, join as join108 } from "node:path";
122763
+ import { dirname as dirname47, join as join109 } from "node:path";
121841
122764
  import { homedir as homedir55 } from "node:os";
121842
122765
  import { execFileSync as execFileSync32 } from "node:child_process";
121843
122766
 
@@ -121845,614 +122768,188 @@ class NodeEnvError extends Error {
121845
122768
  stderr;
121846
122769
  constructor(message, stderr) {
121847
122770
  super(message);
121848
- this.name = "NodeEnvError";
121849
- this.stderr = stderr;
121850
- }
121851
- }
121852
- var ALL_LOCKFILES = [
121853
- "bun.lock",
121854
- "bun.lockb",
121855
- "package-lock.json",
121856
- "pnpm-lock.yaml",
121857
- "yarn.lock"
121858
- ];
121859
- var LOCKFILES_FOR = {
121860
- bun: ["bun.lock", "bun.lockb"],
121861
- npm: ["package-lock.json"]
121862
- };
121863
- function defaultNodeCacheRoot() {
121864
- return join108(homedir55(), ".switchroom", "deps", "node");
121865
- }
121866
- function hashDepInputs(packageJsonPath) {
121867
- const sourceDir = dirname47(packageJsonPath);
121868
- const hasher = createHash20("sha256");
121869
- hasher.update(`package.json
121870
- `);
121871
- hasher.update(readFileSync94(packageJsonPath));
121872
- for (const lockName of ALL_LOCKFILES) {
121873
- const lockPath = join108(sourceDir, lockName);
121874
- if (existsSync103(lockPath)) {
121875
- hasher.update(`
121876
- `);
121877
- hasher.update(lockName);
121878
- hasher.update(`
121879
- `);
121880
- hasher.update(readFileSync94(lockPath));
121881
- }
121882
- }
121883
- return hasher.digest("hex");
121884
- }
121885
- function ensureNodeEnv(opts) {
121886
- const { skillName, packageJsonPath, force = false } = opts;
121887
- const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
121888
- const installer = opts.installer ?? "bun";
121889
- if (!existsSync103(packageJsonPath)) {
121890
- throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
121891
- }
121892
- const sourceDir = dirname47(packageJsonPath);
121893
- const envDir = join108(cacheRoot, skillName);
121894
- const stampPath = join108(envDir, ".package.sha256");
121895
- const nodeModulesDir = join108(envDir, "node_modules");
121896
- const binDir = join108(nodeModulesDir, ".bin");
121897
- const targetHash = hashDepInputs(packageJsonPath);
121898
- if (!force && existsSync103(stampPath) && existsSync103(nodeModulesDir)) {
121899
- const existingHash = readFileSync94(stampPath, "utf8").trim();
121900
- if (existingHash === targetHash) {
121901
- return {
121902
- skillName,
121903
- dir: envDir,
121904
- nodeModulesDir,
121905
- binDir,
121906
- rebuilt: false
121907
- };
121908
- }
121909
- }
121910
- if (existsSync103(envDir)) {
121911
- rmSync20(envDir, { recursive: true, force: true });
121912
- }
121913
- mkdirSync60(envDir, { recursive: true });
121914
- copyFileSync13(packageJsonPath, join108(envDir, "package.json"));
121915
- let copiedLockfile = false;
121916
- for (const lockName of LOCKFILES_FOR[installer]) {
121917
- const lockPath = join108(sourceDir, lockName);
121918
- if (existsSync103(lockPath)) {
121919
- copyFileSync13(lockPath, join108(envDir, lockName));
121920
- copiedLockfile = true;
121921
- }
121922
- }
121923
- try {
121924
- if (installer === "bun") {
121925
- const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
121926
- execFileSync32("bun", args, { cwd: envDir, stdio: "pipe" });
121927
- } else {
121928
- const args = copiedLockfile ? ["ci"] : ["install"];
121929
- execFileSync32("npm", args, { cwd: envDir, stdio: "pipe" });
121930
- }
121931
- } catch (err) {
121932
- const e = err;
121933
- throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
121934
- }
121935
- writeFileSync42(stampPath, targetHash + `
121936
- `);
121937
- return {
121938
- skillName,
121939
- dir: envDir,
121940
- nodeModulesDir,
121941
- binDir,
121942
- rebuilt: true
121943
- };
121944
- }
121945
-
121946
- // src/cli/deps.ts
121947
- function builtinSkillsRoot() {
121948
- return resolve54(homedir56(), ".switchroom/skills/_bundled");
121949
- }
121950
- function registerDepsCommand(program3) {
121951
- const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
121952
- deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
121953
- const skillsRoot = builtinSkillsRoot();
121954
- if (!existsSync104(skillsRoot)) {
121955
- console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
121956
- process.exit(1);
121957
- }
121958
- const skillDir = join109(skillsRoot, skill);
121959
- if (!existsSync104(skillDir)) {
121960
- console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
121961
- process.exit(1);
121962
- }
121963
- const requirementsPath = join109(skillDir, "requirements.txt");
121964
- const packageJsonPath = join109(skillDir, "package.json");
121965
- const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync104(requirementsPath));
121966
- const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync104(packageJsonPath));
121967
- let did = 0;
121968
- if (wantPython) {
121969
- if (!existsSync104(requirementsPath)) {
121970
- console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
121971
- process.exit(1);
121972
- }
121973
- try {
121974
- console.log(source_default.gray(`Rebuilding Python env for ${skill}...`));
121975
- const env2 = ensurePythonEnv({
121976
- skillName: skill,
121977
- requirementsPath,
121978
- force: true
121979
- });
121980
- console.log(source_default.green(` \u2713 Python env at ${env2.venvDir} (python: ${env2.pythonBin})`));
121981
- did++;
121982
- } catch (err) {
121983
- if (err instanceof PythonEnvError) {
121984
- console.error(source_default.red(` Python env failed: ${err.message}`));
121985
- if (err.stderr)
121986
- console.error(source_default.dim(err.stderr));
121987
- process.exit(1);
121988
- }
121989
- throw err;
121990
- }
121991
- }
121992
- if (wantNode) {
121993
- if (!existsSync104(packageJsonPath)) {
121994
- console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
121995
- process.exit(1);
121996
- }
121997
- try {
121998
- console.log(source_default.gray(`Rebuilding Node env for ${skill}...`));
121999
- const env2 = ensureNodeEnv({
122000
- skillName: skill,
122001
- packageJsonPath,
122002
- force: true
122003
- });
122004
- console.log(source_default.green(` \u2713 Node env at ${env2.dir} (node_modules: ${env2.nodeModulesDir})`));
122005
- did++;
122006
- } catch (err) {
122007
- if (err instanceof NodeEnvError) {
122008
- console.error(source_default.red(` Node env failed: ${err.message}`));
122009
- if (err.stderr)
122010
- console.error(source_default.dim(err.stderr));
122011
- process.exit(1);
122012
- }
122013
- throw err;
122014
- }
122015
- }
122016
- if (did === 0) {
122017
- console.error(source_default.yellow(`Skill "${skill}" has neither requirements.txt nor package.json \u2014 nothing to rebuild.`));
122018
- process.exit(1);
122019
- }
122020
- });
122021
- }
122022
-
122023
- // src/cli/workspace.ts
122024
- init_helpers();
122025
- init_loader();
122026
- import { existsSync as existsSync105 } from "node:fs";
122027
- import { resolve as resolve55, sep as sep7 } from "node:path";
122028
- import { spawnSync as spawnSync23 } from "node:child_process";
122029
-
122030
- // src/agents/workspace.ts
122031
- import { readFile as readFile2, stat } from "node:fs/promises";
122032
- import path8 from "node:path";
122033
-
122034
- // src/agents/bootstrap-budget.ts
122035
- import path7 from "node:path";
122036
- var DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO = 0.85;
122037
- var DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES = 3;
122038
- var DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX = 32;
122039
-
122040
- class BootstrapBudgetExceededError extends Error {
122041
- analysis;
122042
- constructor(analysis, message) {
122043
- super(message);
122044
- this.analysis = analysis;
122045
- this.name = "BootstrapBudgetExceededError";
122046
- }
122047
- }
122048
- function normalizePositiveLimit(value) {
122049
- if (!Number.isFinite(value) || value <= 0) {
122050
- return 1;
122051
- }
122052
- return Math.floor(value);
122053
- }
122054
- function formatWarningCause(cause) {
122055
- return cause === "per-file-limit" ? "max/file" : "max/total";
122056
- }
122057
- function normalizeSeenSignatures(signatures) {
122058
- if (!Array.isArray(signatures) || signatures.length === 0) {
122059
- return [];
122060
- }
122061
- const seen = new Set;
122062
- const result = [];
122063
- for (const signature of signatures) {
122064
- const value = typeof signature === "string" ? signature.trim() : "";
122065
- if (!value || seen.has(value)) {
122066
- continue;
122067
- }
122068
- seen.add(value);
122069
- result.push(value);
122070
- }
122071
- return result;
122072
- }
122073
- function appendSeenSignature(signatures, signature) {
122074
- if (!signature.trim()) {
122075
- return signatures;
122076
- }
122077
- if (signatures.includes(signature)) {
122078
- return signatures;
122079
- }
122080
- const next = [...signatures, signature];
122081
- if (next.length <= DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX) {
122082
- return next;
122083
- }
122084
- return next.slice(-DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX);
122085
- }
122086
- function buildBootstrapInjectionStats(params) {
122087
- const injectedByPath = new Map;
122088
- const injectedByBaseName = new Map;
122089
- for (const file of params.injectedFiles) {
122090
- const pathValue = typeof file.path === "string" ? file.path.trim() : "";
122091
- if (!pathValue) {
122092
- continue;
122093
- }
122094
- if (!injectedByPath.has(pathValue)) {
122095
- injectedByPath.set(pathValue, file.content);
122096
- }
122097
- const normalizedPath = pathValue.replace(/\\/g, "/");
122098
- const baseName = path7.posix.basename(normalizedPath);
122099
- if (!injectedByBaseName.has(baseName)) {
122100
- injectedByBaseName.set(baseName, file.content);
122101
- }
122102
- }
122103
- return params.bootstrapFiles.map((file) => {
122104
- const pathValue = typeof file.path === "string" ? file.path.trim() : "";
122105
- const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
122106
- const injected = (pathValue ? injectedByPath.get(pathValue) : undefined) ?? injectedByPath.get(file.name) ?? injectedByBaseName.get(file.name);
122107
- const injectedChars = injected ? injected.length : 0;
122108
- const truncated = !file.missing && injectedChars < rawChars;
122109
- return {
122110
- name: file.name,
122111
- path: pathValue || file.name,
122112
- missing: file.missing,
122113
- rawChars,
122114
- injectedChars,
122115
- truncated
122116
- };
122117
- });
122118
- }
122119
- function analyzeBootstrapBudget(params) {
122120
- const bootstrapMaxChars = normalizePositiveLimit(params.bootstrapMaxChars);
122121
- const bootstrapTotalMaxChars = normalizePositiveLimit(params.bootstrapTotalMaxChars);
122122
- const nearLimitRatio = typeof params.nearLimitRatio === "number" && Number.isFinite(params.nearLimitRatio) && params.nearLimitRatio > 0 && params.nearLimitRatio < 1 ? params.nearLimitRatio : DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO;
122123
- const nonMissing = params.files.filter((file) => !file.missing);
122124
- const rawChars = nonMissing.reduce((sum, file) => sum + file.rawChars, 0);
122125
- const injectedChars = nonMissing.reduce((sum, file) => sum + file.injectedChars, 0);
122126
- const totalNearLimit = injectedChars >= Math.ceil(bootstrapTotalMaxChars * nearLimitRatio);
122127
- const totalOverLimit = injectedChars >= bootstrapTotalMaxChars;
122128
- const files = params.files.map((file) => {
122129
- if (file.missing) {
122130
- return { ...file, nearLimit: false, causes: [] };
122131
- }
122132
- const perFileOverLimit = file.rawChars > bootstrapMaxChars;
122133
- const nearLimit = file.rawChars >= Math.ceil(bootstrapMaxChars * nearLimitRatio);
122134
- const causes = [];
122135
- if (file.truncated) {
122136
- if (perFileOverLimit) {
122137
- causes.push("per-file-limit");
122138
- }
122139
- if (totalOverLimit) {
122140
- causes.push("total-limit");
122141
- }
122142
- }
122143
- return { ...file, nearLimit, causes };
122144
- });
122145
- const truncatedFiles = files.filter((file) => file.truncated);
122146
- const nearLimitFiles = files.filter((file) => file.nearLimit);
122147
- return {
122148
- files,
122149
- truncatedFiles,
122150
- nearLimitFiles,
122151
- totalNearLimit,
122152
- hasTruncation: truncatedFiles.length > 0,
122153
- totals: {
122154
- rawChars,
122155
- injectedChars,
122156
- truncatedChars: Math.max(0, rawChars - injectedChars),
122157
- bootstrapMaxChars,
122158
- bootstrapTotalMaxChars,
122159
- nearLimitRatio
122160
- }
122161
- };
122162
- }
122163
- function buildBootstrapTruncationSignature(analysis) {
122164
- if (!analysis.hasTruncation) {
122165
- return;
122166
- }
122167
- const files = analysis.truncatedFiles.map((file) => ({
122168
- path: file.path || file.name,
122169
- rawChars: file.rawChars,
122170
- injectedChars: file.injectedChars,
122171
- causes: [...file.causes].sort()
122172
- })).sort((a, b) => {
122173
- const pathCmp = a.path.localeCompare(b.path);
122174
- if (pathCmp !== 0) {
122175
- return pathCmp;
122176
- }
122177
- if (a.rawChars !== b.rawChars) {
122178
- return a.rawChars - b.rawChars;
122179
- }
122180
- if (a.injectedChars !== b.injectedChars) {
122181
- return a.injectedChars - b.injectedChars;
122182
- }
122183
- return a.causes.join("+").localeCompare(b.causes.join("+"));
122184
- });
122185
- return JSON.stringify({
122186
- bootstrapMaxChars: analysis.totals.bootstrapMaxChars,
122187
- bootstrapTotalMaxChars: analysis.totals.bootstrapTotalMaxChars,
122188
- files
122189
- });
122190
- }
122191
- function formatBootstrapTruncationWarningLines(params) {
122192
- if (!params.analysis.hasTruncation) {
122193
- return [];
122194
- }
122195
- const maxFiles = typeof params.maxFiles === "number" && Number.isFinite(params.maxFiles) && params.maxFiles > 0 ? Math.floor(params.maxFiles) : DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES;
122196
- const lines = [];
122197
- const duplicateNameCounts = params.analysis.truncatedFiles.reduce((acc, file) => {
122198
- acc.set(file.name, (acc.get(file.name) ?? 0) + 1);
122199
- return acc;
122200
- }, new Map);
122201
- const topFiles = params.analysis.truncatedFiles.slice(0, maxFiles);
122202
- for (const file of topFiles) {
122203
- const pct = file.rawChars > 0 ? Math.round((file.rawChars - file.injectedChars) / file.rawChars * 100) : 0;
122204
- const causeText = file.causes.length > 0 ? file.causes.map((cause) => formatWarningCause(cause)).join(", ") : "";
122205
- const nameLabel = (duplicateNameCounts.get(file.name) ?? 0) > 1 && file.path.trim().length > 0 ? `${file.name} (${file.path})` : file.name;
122206
- lines.push(`${nameLabel}: ${file.rawChars} raw -> ${file.injectedChars} injected (~${Math.max(0, pct)}% removed${causeText ? `; ${causeText}` : ""}).`);
122207
- }
122208
- if (params.analysis.truncatedFiles.length > topFiles.length) {
122209
- lines.push(`+${params.analysis.truncatedFiles.length - topFiles.length} more truncated file(s).`);
122210
- }
122211
- lines.push("If unintentional, raise switchroom workspace bootstrapMaxChars and/or bootstrapTotalMaxChars config.");
122212
- return lines;
122213
- }
122214
- function buildBootstrapPromptWarning(params) {
122215
- const signature = buildBootstrapTruncationSignature(params.analysis);
122216
- let seenSignatures = normalizeSeenSignatures(params.seenSignatures);
122217
- if (params.previousSignature && !seenSignatures.includes(params.previousSignature)) {
122218
- seenSignatures = appendSeenSignature(seenSignatures, params.previousSignature);
122219
- }
122220
- const hasSeenSignature = Boolean(signature && seenSignatures.includes(signature));
122221
- const warningShown = params.mode !== "off" && Boolean(signature) && (params.mode === "always" || !hasSeenSignature);
122222
- const warningSignaturesSeen = signature && params.mode !== "off" ? appendSeenSignature(seenSignatures, signature) : seenSignatures;
122223
- return {
122224
- signature,
122225
- warningShown,
122226
- lines: warningShown ? formatBootstrapTruncationWarningLines({
122227
- analysis: params.analysis,
122228
- maxFiles: params.maxFiles
122229
- }) : [],
122230
- warningSignaturesSeen
122231
- };
122232
- }
122233
-
122234
- // src/agents/bootstrap-types.ts
122235
- var DEFAULT_AGENTS_FILENAME = "AGENTS.md";
122236
- var DEFAULT_SOUL_DEFAULT_FILENAME = "SOUL.default.md";
122237
- var DEFAULT_SOUL_FILENAME = "SOUL.md";
122238
- var DEFAULT_TOOLS_FILENAME = "TOOLS.md";
122239
- var DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
122240
- var DEFAULT_USER_FILENAME = "USER.md";
122241
- var DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
122242
- var DEFAULT_MEMORY_FILENAME = "MEMORY.md";
122243
-
122244
- // src/agents/workspace.ts
122245
- var DEFAULT_WORKSPACE_DIR_NAME = "workspace";
122246
- var DEFAULT_MEMORY_SUBDIR = "memory";
122247
- var STABLE_BOOTSTRAP_FILENAMES = [
122248
- DEFAULT_AGENTS_FILENAME,
122249
- DEFAULT_SOUL_DEFAULT_FILENAME,
122250
- DEFAULT_SOUL_FILENAME,
122251
- DEFAULT_IDENTITY_FILENAME,
122252
- DEFAULT_USER_FILENAME,
122253
- DEFAULT_TOOLS_FILENAME,
122254
- DEFAULT_BOOTSTRAP_FILENAME
122255
- ];
122256
- var DYNAMIC_BOOTSTRAP_FILENAMES = [
122257
- DEFAULT_MEMORY_FILENAME
122258
- ];
122259
- var DEFAULT_BOOTSTRAP_MAX_CHARS = 12000;
122260
- var DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS = 64000;
122261
- var DEFAULT_DYNAMIC_TOTAL_MAX_CHARS = 24000;
122262
- function resolveAgentWorkspaceDir(agentDir) {
122263
- return path8.join(agentDir, DEFAULT_WORKSPACE_DIR_NAME);
122264
- }
122265
- async function readOptionalFile(filePath) {
122266
- try {
122267
- const content = await readFile2(filePath, "utf8");
122268
- return content;
122269
- } catch (err) {
122270
- if (isErrnoException(err) && (err.code === "ENOENT" || err.code === "EISDIR")) {
122271
- return;
122272
- }
122273
- throw err;
122274
- }
122275
- }
122276
- function isErrnoException(value) {
122277
- return value instanceof Error && typeof value.code === "string";
122278
- }
122279
- async function loadNamedFile(workspaceDir, name, relativePath) {
122280
- const filePath = path8.join(workspaceDir, relativePath ?? name);
122281
- const content = await readOptionalFile(filePath);
122282
- if (content === undefined) {
122283
- return { name, path: filePath, missing: true };
122284
- }
122285
- return { name, path: filePath, content, missing: false };
122286
- }
122287
- async function loadStableBootstrapFiles(workspaceDir, options) {
122288
- const defaultFiles = await Promise.all(STABLE_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
122289
- const extras = options?.extraStableFiles ?? [];
122290
- if (extras.length === 0) {
122291
- return defaultFiles;
122292
- }
122293
- const extraFiles = await Promise.all(extras.map((name) => loadNamedFile(workspaceDir, name)));
122294
- return [...defaultFiles, ...extraFiles];
122295
- }
122296
- async function loadDynamicBootstrapFiles(workspaceDir, options) {
122297
- const now2 = options?.now ?? new Date;
122298
- const includeYesterday = options?.includeYesterday ?? true;
122299
- const files = await Promise.all(DYNAMIC_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
122300
- const todayRelative = dailyMemoryRelativePath(now2);
122301
- const todayName = DEFAULT_MEMORY_FILENAME;
122302
- const today = await loadNamedFile(workspaceDir, todayName, todayRelative);
122303
- today.path = path8.join(workspaceDir, todayRelative);
122304
- files.push(today);
122305
- if (includeYesterday) {
122306
- const yesterdayRelative = dailyMemoryRelativePath(addDays(now2, -1));
122307
- const yesterday = await loadNamedFile(workspaceDir, todayName, yesterdayRelative);
122308
- yesterday.path = path8.join(workspaceDir, yesterdayRelative);
122309
- files.push(yesterday);
122771
+ this.name = "NodeEnvError";
122772
+ this.stderr = stderr;
122310
122773
  }
122311
- return files;
122312
- }
122313
- function pad22(n) {
122314
- return n < 10 ? `0${n}` : String(n);
122315
- }
122316
- function dailyMemoryRelativePath(date) {
122317
- const y = date.getFullYear();
122318
- const m = pad22(date.getMonth() + 1);
122319
- const d = pad22(date.getDate());
122320
- return path8.join(DEFAULT_MEMORY_SUBDIR, `${y}-${m}-${d}.md`);
122321
122774
  }
122322
- function addDays(date, days) {
122323
- const out = new Date(date.getTime());
122324
- out.setDate(out.getDate() + days);
122325
- return out;
122775
+ var ALL_LOCKFILES = [
122776
+ "bun.lock",
122777
+ "bun.lockb",
122778
+ "package-lock.json",
122779
+ "pnpm-lock.yaml",
122780
+ "yarn.lock"
122781
+ ];
122782
+ var LOCKFILES_FOR = {
122783
+ bun: ["bun.lock", "bun.lockb"],
122784
+ npm: ["package-lock.json"]
122785
+ };
122786
+ function defaultNodeCacheRoot() {
122787
+ return join109(homedir55(), ".switchroom", "deps", "node");
122326
122788
  }
122327
- function truncateContent(name, content, maxChars) {
122328
- if (content.length <= maxChars) {
122329
- return content;
122330
- }
122331
- if (maxChars < 200) {
122332
- return content.slice(0, Math.max(0, maxChars));
122789
+ function hashDepInputs(packageJsonPath) {
122790
+ const sourceDir = dirname47(packageJsonPath);
122791
+ const hasher = createHash21("sha256");
122792
+ hasher.update(`package.json
122793
+ `);
122794
+ hasher.update(readFileSync95(packageJsonPath));
122795
+ for (const lockName of ALL_LOCKFILES) {
122796
+ const lockPath = join109(sourceDir, lockName);
122797
+ if (existsSync104(lockPath)) {
122798
+ hasher.update(`
122799
+ `);
122800
+ hasher.update(lockName);
122801
+ hasher.update(`
122802
+ `);
122803
+ hasher.update(readFileSync95(lockPath));
122804
+ }
122333
122805
  }
122334
- const headRoomInitial = Math.floor(maxChars * 0.7);
122335
- const sampleMarker = `
122336
- \u2026(truncated ${name}: kept ${headRoomInitial}+${Math.max(0, maxChars - headRoomInitial - 96)} chars of ${content.length})\u2026
122337
- `;
122338
- const markerReserve = Math.max(64, sampleMarker.length + 8);
122339
- const headRoom = Math.min(headRoomInitial, Math.max(0, maxChars - markerReserve - 8));
122340
- const tailRoom = Math.max(0, maxChars - headRoom - markerReserve);
122341
- const head = content.slice(0, Math.max(0, headRoom));
122342
- const tail = tailRoom > 0 ? content.slice(content.length - tailRoom) : "";
122343
- const result = `${head}
122344
- \u2026(truncated ${name}: kept ${headRoom}+${tailRoom} chars of ${content.length})\u2026
122345
- ${tail}`;
122346
- return result.length <= maxChars ? result : result.slice(0, maxChars);
122806
+ return hasher.digest("hex");
122347
122807
  }
122348
- function projectBootstrapFiles(params) {
122349
- const {
122350
- files,
122351
- heading,
122352
- budget,
122353
- seenSignatures,
122354
- warningMode = "error",
122355
- warningMaxFiles = DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES
122356
- } = params;
122357
- const perFileCap = budget.bootstrapMaxChars;
122358
- let remainingTotal = budget.bootstrapTotalMaxChars;
122359
- const injectedFiles = [];
122360
- const parts = [];
122361
- for (const file of files) {
122362
- if (file.missing || typeof file.content !== "string") {
122363
- continue;
122364
- }
122365
- const raw = file.content.trimEnd();
122366
- if (raw.length === 0) {
122367
- continue;
122368
- }
122369
- const perFileAllowed = Math.min(perFileCap, remainingTotal);
122370
- if (perFileAllowed <= 0) {
122371
- break;
122808
+ function ensureNodeEnv(opts) {
122809
+ const { skillName, packageJsonPath, force = false } = opts;
122810
+ const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
122811
+ const installer = opts.installer ?? "bun";
122812
+ if (!existsSync104(packageJsonPath)) {
122813
+ throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
122814
+ }
122815
+ const sourceDir = dirname47(packageJsonPath);
122816
+ const envDir = join109(cacheRoot, skillName);
122817
+ const stampPath = join109(envDir, ".package.sha256");
122818
+ const nodeModulesDir = join109(envDir, "node_modules");
122819
+ const binDir = join109(nodeModulesDir, ".bin");
122820
+ const targetHash = hashDepInputs(packageJsonPath);
122821
+ if (!force && existsSync104(stampPath) && existsSync104(nodeModulesDir)) {
122822
+ const existingHash = readFileSync95(stampPath, "utf8").trim();
122823
+ if (existingHash === targetHash) {
122824
+ return {
122825
+ skillName,
122826
+ dir: envDir,
122827
+ nodeModulesDir,
122828
+ binDir,
122829
+ rebuilt: false
122830
+ };
122372
122831
  }
122373
- const injected = truncateContent(file.name, raw, perFileAllowed);
122374
- const relativePath = file.path;
122375
- injectedFiles.push({ path: relativePath, content: injected });
122376
- parts.push(`## ${relativePath}`);
122377
- parts.push(injected);
122378
- remainingTotal -= injected.length;
122379
- if (remainingTotal <= 0) {
122380
- break;
122832
+ }
122833
+ if (existsSync104(envDir)) {
122834
+ rmSync20(envDir, { recursive: true, force: true });
122835
+ }
122836
+ mkdirSync60(envDir, { recursive: true });
122837
+ copyFileSync13(packageJsonPath, join109(envDir, "package.json"));
122838
+ let copiedLockfile = false;
122839
+ for (const lockName of LOCKFILES_FOR[installer]) {
122840
+ const lockPath = join109(sourceDir, lockName);
122841
+ if (existsSync104(lockPath)) {
122842
+ copyFileSync13(lockPath, join109(envDir, lockName));
122843
+ copiedLockfile = true;
122381
122844
  }
122382
122845
  }
122383
- const concatenated = parts.length > 0 ? [heading.trim().length > 0 ? `# ${heading}` : null, ...parts].filter((p) => p !== null).join(`
122384
-
122385
- `) : "";
122386
- const stats = buildBootstrapInjectionStats({
122387
- bootstrapFiles: files,
122388
- injectedFiles
122389
- });
122390
- const analysis = analyzeBootstrapBudget({
122391
- files: stats,
122392
- bootstrapMaxChars: budget.bootstrapMaxChars,
122393
- bootstrapTotalMaxChars: budget.bootstrapTotalMaxChars,
122394
- nearLimitRatio: budget.nearLimitRatio ?? DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO
122395
- });
122396
- const warning = buildBootstrapPromptWarning({
122397
- analysis,
122398
- mode: warningMode,
122399
- seenSignatures,
122400
- maxFiles: warningMaxFiles
122401
- });
122402
- if (warningMode === "error" && analysis.hasTruncation) {
122403
- const errorLines = [
122404
- "Bootstrap budget exceeded. The following files exceed limits:",
122405
- ""
122406
- ];
122407
- for (const file of analysis.truncatedFiles) {
122408
- const excessChars = file.rawChars - file.injectedChars;
122409
- const causes = file.causes.map((c) => c === "per-file-limit" ? `per-file limit (${analysis.totals.bootstrapMaxChars.toLocaleString()} chars)` : `total limit (${analysis.totals.bootstrapTotalMaxChars.toLocaleString()} chars)`);
122410
- errorLines.push(` ${file.name}: ${file.rawChars.toLocaleString()} bytes (exceeds ${causes.join(", ")})`);
122411
- errorLines.push(` Trim ${excessChars.toLocaleString()} bytes, or pass --warning-mode warn to proceed with truncation.`);
122846
+ try {
122847
+ if (installer === "bun") {
122848
+ const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
122849
+ execFileSync32("bun", args, { cwd: envDir, stdio: "pipe" });
122850
+ } else {
122851
+ const args = copiedLockfile ? ["ci"] : ["install"];
122852
+ execFileSync32("npm", args, { cwd: envDir, stdio: "pipe" });
122412
122853
  }
122413
- throw new BootstrapBudgetExceededError(analysis, errorLines.join(`
122414
- `));
122854
+ } catch (err) {
122855
+ const e = err;
122856
+ throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
122415
122857
  }
122416
- return { files, injectedFiles, concatenated, analysis, warning };
122417
- }
122418
- async function buildStableBootstrapPrompt(params) {
122419
- const files = await loadStableBootstrapFiles(params.workspaceDir, {
122420
- extraStableFiles: params.extraStableFiles
122421
- });
122422
- const budget = {
122423
- bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
122424
- bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS,
122425
- nearLimitRatio: params.budget?.nearLimitRatio
122858
+ writeFileSync42(stampPath, targetHash + `
122859
+ `);
122860
+ return {
122861
+ skillName,
122862
+ dir: envDir,
122863
+ nodeModulesDir,
122864
+ binDir,
122865
+ rebuilt: true
122426
122866
  };
122427
- return projectBootstrapFiles({
122428
- files,
122429
- heading: "Project Context (stable workspace files)",
122430
- budget,
122431
- seenSignatures: params.seenSignatures,
122432
- warningMode: params.budget?.warningMode,
122433
- warningMaxFiles: params.budget?.warningMaxFiles
122434
- });
122435
122867
  }
122436
- async function buildDynamicBootstrapPrompt(params) {
122437
- const files = await loadDynamicBootstrapFiles(params.workspaceDir, {
122438
- now: params.now,
122439
- includeYesterday: params.includeYesterday
122440
- });
122441
- const budget = {
122442
- bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
122443
- bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_DYNAMIC_TOTAL_MAX_CHARS,
122444
- nearLimitRatio: params.budget?.nearLimitRatio
122445
- };
122446
- return projectBootstrapFiles({
122447
- files,
122448
- heading: "Project Context (dynamic workspace files)",
122449
- budget,
122450
- seenSignatures: params.seenSignatures,
122451
- warningMode: params.budget?.warningMode,
122452
- warningMaxFiles: params.budget?.warningMaxFiles
122868
+
122869
+ // src/cli/deps.ts
122870
+ function builtinSkillsRoot() {
122871
+ return resolve55(homedir56(), ".switchroom/skills/_bundled");
122872
+ }
122873
+ function registerDepsCommand(program3) {
122874
+ const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
122875
+ deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
122876
+ const skillsRoot = builtinSkillsRoot();
122877
+ if (!existsSync105(skillsRoot)) {
122878
+ console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
122879
+ process.exit(1);
122880
+ }
122881
+ const skillDir = join110(skillsRoot, skill);
122882
+ if (!existsSync105(skillDir)) {
122883
+ console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
122884
+ process.exit(1);
122885
+ }
122886
+ const requirementsPath = join110(skillDir, "requirements.txt");
122887
+ const packageJsonPath = join110(skillDir, "package.json");
122888
+ const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync105(requirementsPath));
122889
+ const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync105(packageJsonPath));
122890
+ let did = 0;
122891
+ if (wantPython) {
122892
+ if (!existsSync105(requirementsPath)) {
122893
+ console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
122894
+ process.exit(1);
122895
+ }
122896
+ try {
122897
+ console.log(source_default.gray(`Rebuilding Python env for ${skill}...`));
122898
+ const env2 = ensurePythonEnv({
122899
+ skillName: skill,
122900
+ requirementsPath,
122901
+ force: true
122902
+ });
122903
+ console.log(source_default.green(` \u2713 Python env at ${env2.venvDir} (python: ${env2.pythonBin})`));
122904
+ did++;
122905
+ } catch (err) {
122906
+ if (err instanceof PythonEnvError) {
122907
+ console.error(source_default.red(` Python env failed: ${err.message}`));
122908
+ if (err.stderr)
122909
+ console.error(source_default.dim(err.stderr));
122910
+ process.exit(1);
122911
+ }
122912
+ throw err;
122913
+ }
122914
+ }
122915
+ if (wantNode) {
122916
+ if (!existsSync105(packageJsonPath)) {
122917
+ console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
122918
+ process.exit(1);
122919
+ }
122920
+ try {
122921
+ console.log(source_default.gray(`Rebuilding Node env for ${skill}...`));
122922
+ const env2 = ensureNodeEnv({
122923
+ skillName: skill,
122924
+ packageJsonPath,
122925
+ force: true
122926
+ });
122927
+ console.log(source_default.green(` \u2713 Node env at ${env2.dir} (node_modules: ${env2.nodeModulesDir})`));
122928
+ did++;
122929
+ } catch (err) {
122930
+ if (err instanceof NodeEnvError) {
122931
+ console.error(source_default.red(` Node env failed: ${err.message}`));
122932
+ if (err.stderr)
122933
+ console.error(source_default.dim(err.stderr));
122934
+ process.exit(1);
122935
+ }
122936
+ throw err;
122937
+ }
122938
+ }
122939
+ if (did === 0) {
122940
+ console.error(source_default.yellow(`Skill "${skill}" has neither requirements.txt nor package.json \u2014 nothing to rebuild.`));
122941
+ process.exit(1);
122942
+ }
122453
122943
  });
122454
122944
  }
122455
122945
 
122946
+ // src/cli/workspace.ts
122947
+ init_helpers();
122948
+ init_loader();
122949
+ import { existsSync as existsSync106 } from "node:fs";
122950
+ import { resolve as resolve56, sep as sep7 } from "node:path";
122951
+ import { spawnSync as spawnSync23 } from "node:child_process";
122952
+
122456
122953
  // src/agents/memory-search.ts
122457
122954
  import { readFile as readFile3, readdir, realpath, stat as stat2 } from "node:fs/promises";
122458
122955
  import path9 from "node:path";
@@ -122730,8 +123227,8 @@ function registerWorkspaceCommand(program3) {
122730
123227
  const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
122731
123228
  if (!dir)
122732
123229
  return;
122733
- const resolvedWorkspace = resolve55(dir);
122734
- const target = resolve55(resolvedWorkspace, file ?? "AGENTS.md");
123230
+ const resolvedWorkspace = resolve56(dir);
123231
+ const target = resolve56(resolvedWorkspace, file ?? "AGENTS.md");
122735
123232
  if (!isInsideWorkspace(resolvedWorkspace, target)) {
122736
123233
  process.stderr.write(`workspace edit: refusing path traversal outside workspace dir (${target})
122737
123234
  `);
@@ -122799,8 +123296,8 @@ function registerWorkspaceCommand(program3) {
122799
123296
  const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
122800
123297
  if (!dir)
122801
123298
  return;
122802
- const gitDir = resolve55(dir, ".git");
122803
- if (!existsSync105(gitDir)) {
123299
+ const gitDir = resolve56(dir, ".git");
123300
+ if (!existsSync106(gitDir)) {
122804
123301
  process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
122805
123302
  `);
122806
123303
  return;
@@ -122853,8 +123350,8 @@ function registerWorkspaceCommand(program3) {
122853
123350
  const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
122854
123351
  if (!dir)
122855
123352
  return;
122856
- const gitDir = resolve55(dir, ".git");
122857
- if (!existsSync105(gitDir)) {
123353
+ const gitDir = resolve56(dir, ".git");
123354
+ if (!existsSync106(gitDir)) {
122858
123355
  process.stdout.write(`Workspace is not a git repository.
122859
123356
  `);
122860
123357
  return;
@@ -122877,9 +123374,9 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
122877
123374
  return;
122878
123375
  }
122879
123376
  const agentsDir = resolveAgentsDir(config);
122880
- const agentDir = resolve55(agentsDir, agentName);
123377
+ const agentDir = resolve56(agentsDir, agentName);
122881
123378
  const dir = resolveAgentWorkspaceDir(agentDir);
122882
- if (!existsSync105(dir)) {
123379
+ if (!existsSync106(dir)) {
122883
123380
  process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
122884
123381
  `);
122885
123382
  return;
@@ -122915,8 +123412,8 @@ function safeParseInt(value, fallback) {
122915
123412
  init_helpers();
122916
123413
  init_loader();
122917
123414
  init_merge();
122918
- import { copyFileSync as copyFileSync14, existsSync as existsSync106, readFileSync as readFileSync95, writeFileSync as writeFileSync43 } from "node:fs";
122919
- import { join as join110, resolve as resolve56 } from "node:path";
123415
+ import { copyFileSync as copyFileSync14, existsSync as existsSync107, readFileSync as readFileSync96, writeFileSync as writeFileSync43 } from "node:fs";
123416
+ import { join as join111, resolve as resolve57 } from "node:path";
122920
123417
  init_scaffold();
122921
123418
  init_profiles();
122922
123419
  init_schema();
@@ -122931,9 +123428,9 @@ function resolveSoulTargetOrExit(program3, agentName) {
122931
123428
  const profileName = merged.extends ?? DEFAULT_PROFILE;
122932
123429
  const profilePath = getProfilePath(profileName);
122933
123430
  const agentsDir = resolveAgentsDir(config);
122934
- const agentDir = resolve56(agentsDir, agentName);
123431
+ const agentDir = resolve57(agentsDir, agentName);
122935
123432
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
122936
- if (!existsSync106(workspaceDir)) {
123433
+ if (!existsSync107(workspaceDir)) {
122937
123434
  console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
122938
123435
  process.exit(1);
122939
123436
  }
@@ -122942,7 +123439,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
122942
123439
  profileName,
122943
123440
  profilePath,
122944
123441
  workspaceDir,
122945
- soulPath: join110(workspaceDir, "SOUL.md"),
123442
+ soulPath: join111(workspaceDir, "SOUL.md"),
122946
123443
  soul: merged.soul
122947
123444
  };
122948
123445
  }
@@ -122959,11 +123456,11 @@ function registerSoulCommand(program3) {
122959
123456
  const t = resolveSoulTargetOrExit(program3, agentName);
122960
123457
  if (!t)
122961
123458
  return;
122962
- if (!existsSync106(t.soulPath)) {
123459
+ if (!existsSync107(t.soulPath)) {
122963
123460
  console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
122964
123461
  process.exit(1);
122965
123462
  }
122966
- process.stdout.write(readFileSync95(t.soulPath, "utf-8"));
123463
+ process.stdout.write(readFileSync96(t.soulPath, "utf-8"));
122967
123464
  }));
122968
123465
  cmd.command("reset <agent>").description("Re-seed SOUL.md from the agent's current profile " + "(backs the existing file up to SOUL.md.bak first)").option("-y, --yes", "Skip the confirmation prompt").action(withConfigError(async (agentName, opts) => {
122969
123466
  const t = resolveSoulTargetOrExit(program3, agentName);
@@ -122974,7 +123471,7 @@ function registerSoulCommand(program3) {
122974
123471
  console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
122975
123472
  process.exit(1);
122976
123473
  }
122977
- const exists = existsSync106(t.soulPath);
123474
+ const exists = existsSync107(t.soulPath);
122978
123475
  if (exists && !opts.yes) {
122979
123476
  if (!isInteractive()) {
122980
123477
  console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
@@ -122989,7 +123486,7 @@ function registerSoulCommand(program3) {
122989
123486
  let backupPath;
122990
123487
  if (exists) {
122991
123488
  backupPath = `${t.soulPath}.bak`;
122992
- if (existsSync106(backupPath)) {
123489
+ if (existsSync107(backupPath)) {
122993
123490
  backupPath = `${t.soulPath}.bak.${Date.now()}`;
122994
123491
  }
122995
123492
  copyFileSync14(t.soulPath, backupPath);
@@ -123005,279 +123502,6 @@ function registerSoulCommand(program3) {
123005
123502
  }));
123006
123503
  }
123007
123504
 
123008
- // src/cli/debug.ts
123009
- init_helpers();
123010
- init_loader();
123011
- import { existsSync as existsSync107, readFileSync as readFileSync96, readdirSync as readdirSync36, statSync as statSync62 } from "node:fs";
123012
- import { resolve as resolve57, join as join111 } from "node:path";
123013
- import { createHash as createHash21 } from "node:crypto";
123014
- init_merge();
123015
- init_hindsight2();
123016
- function formatBytes(bytes) {
123017
- return `${bytes.toLocaleString()} bytes`;
123018
- }
123019
- function estimateTokens(bytes) {
123020
- return Math.round(bytes / 3.7);
123021
- }
123022
- function readMcpServerNames(agentDir) {
123023
- const mcpPath = join111(agentDir, ".mcp.json");
123024
- if (!existsSync107(mcpPath))
123025
- return [];
123026
- try {
123027
- const parsed = JSON.parse(readFileSync96(mcpPath, "utf-8"));
123028
- return Object.keys(parsed.mcpServers ?? {});
123029
- } catch {
123030
- return null;
123031
- }
123032
- }
123033
- function sha2562(content) {
123034
- return createHash21("sha256").update(content).digest("hex").slice(0, 16);
123035
- }
123036
- function findLatestTranscriptJsonl(claudeConfigDir) {
123037
- const projectsDir = join111(claudeConfigDir, "projects");
123038
- if (!existsSync107(projectsDir))
123039
- return;
123040
- try {
123041
- const entries = readdirSync36(projectsDir, { withFileTypes: true });
123042
- let latest;
123043
- for (const entry of entries) {
123044
- if (!entry.isDirectory())
123045
- continue;
123046
- const projectPath = join111(projectsDir, entry.name);
123047
- const transcriptPath = join111(projectPath, "transcript.jsonl");
123048
- if (!existsSync107(transcriptPath))
123049
- continue;
123050
- const stat3 = statSync62(transcriptPath);
123051
- if (!latest || stat3.mtimeMs > latest.mtime) {
123052
- latest = { path: transcriptPath, mtime: stat3.mtimeMs };
123053
- }
123054
- }
123055
- return latest?.path;
123056
- } catch {
123057
- return;
123058
- }
123059
- }
123060
- function extractLatestUserMessage(transcriptPath) {
123061
- try {
123062
- const content = readFileSync96(transcriptPath, "utf-8");
123063
- const lines = content.trim().split(`
123064
- `).filter(Boolean);
123065
- for (let i2 = lines.length - 1;i2 >= 0; i2--) {
123066
- const line = lines[i2];
123067
- try {
123068
- const event = JSON.parse(line);
123069
- if (event.type === "message" && event.role === "user" && typeof event.content === "string") {
123070
- const timestamp = event.timestamp ? new Date(event.timestamp).toLocaleString() : "unknown";
123071
- return { text: event.content, timestamp };
123072
- }
123073
- } catch {
123074
- continue;
123075
- }
123076
- }
123077
- } catch {
123078
- return;
123079
- }
123080
- }
123081
- function buildProgressUpdateGuidance() {
123082
- return `## Progress updates (human-style check-ins)
123083
-
123084
- You're talking to a human colleague on Telegram. Alongside the emoji status
123085
- ladder, send a short \`progress_update\` at inflection points, the moments a
123086
- senior colleague would ping the person who asked them to do something:
123087
-
123088
- - **Plan formed:** "Got it. Going to do X first, then Y, then Z."
123089
- - **Pivot or blocker:** "First approach didn't work because <reason>. Trying
123090
- <alternative> instead."
123091
- - **Chunk finished:** "Done with X. Starting Y now."
123092
-
123093
- Keep them short (one or two sentences). Don't narrate every step, the pinned
123094
- progress card shows that for free. Don't send an update on a trivial one-shot
123095
- task. Send them when a colleague would genuinely want to know what's happening.
123096
-
123097
- Final answers still go through \`reply\` as usual,
123098
- \`progress_update\` is only for mid-turn check-ins.`;
123099
- }
123100
- function registerDebugCommand(program3) {
123101
- const cmd = program3.command("debug", { hidden: true }).description("Observability tools for inspecting agent prompt layering [advanced]");
123102
- cmd.command("turn <agent>").description("Dump the exact prompt layering the model saw on the most recent turn").option("--last <n>", "Show N-th most recent turn instead of latest", "1").action(withConfigError(async (agentName, opts) => {
123103
- const config = getConfig(program3);
123104
- const agentConfig = config.agents[agentName];
123105
- if (!agentConfig) {
123106
- console.error(`Agent '${agentName}' not found in switchroom.yaml`);
123107
- process.exit(1);
123108
- }
123109
- const agentsDir = resolveAgentsDir(config);
123110
- const agentDir = resolve57(agentsDir, agentName);
123111
- if (!existsSync107(agentDir)) {
123112
- console.error(`Agent directory not found: ${agentDir}`);
123113
- process.exit(1);
123114
- }
123115
- const workspaceDir = resolveAgentWorkspaceDir(agentDir);
123116
- const claudeConfigDir = join111(agentDir, ".claude");
123117
- const claudeMdPath2 = join111(agentDir, "CLAUDE.md");
123118
- const soulMdPath = join111(agentDir, "SOUL.md");
123119
- const workspaceSoulMdPath = join111(workspaceDir, "SOUL.md");
123120
- const handoffPath = join111(agentDir, ".handoff.md");
123121
- const lastN = parseInt(opts.last, 10);
123122
- if (isNaN(lastN) || lastN < 1) {
123123
- console.error("--last must be a positive integer");
123124
- process.exit(1);
123125
- }
123126
- if (lastN > 1) {
123127
- console.error("Note: --last N where N > 1 not yet implemented (only latest turn supported)");
123128
- process.exit(1);
123129
- }
123130
- console.log(`=== Debug Turn Dump: ${agentName} ===
123131
- `);
123132
- console.log(`=== Append System Prompt (stable) ===
123133
- `);
123134
- const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
123135
- const useHotReloadStable = resolved.channels?.telegram?.hotReloadStable === true;
123136
- const stableResult = await buildStableBootstrapPrompt({
123137
- workspaceDir,
123138
- budget: { warningMode: "off" }
123139
- });
123140
- if (useHotReloadStable) {
123141
- console.log(`-- Workspace Stable Render (not used \u2014 stable content is in per-turn hook) --`);
123142
- console.log();
123143
- } else if (stableResult.concatenated.trim().length > 0) {
123144
- console.log(`-- Workspace Stable Render (${formatBytes(stableResult.concatenated.length)}) --`);
123145
- console.log(stableResult.concatenated);
123146
- console.log();
123147
- } else {
123148
- console.log("-- Workspace Stable Render (0 bytes, not set up) --");
123149
- console.log();
123150
- }
123151
- const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(resolved);
123152
- const progressGuidance = useSwitchroomPlugin ? buildProgressUpdateGuidance() : "";
123153
- if (progressGuidance.length > 0) {
123154
- console.log(`-- Progress Updates Guidance (${formatBytes(progressGuidance.length)}) --`);
123155
- console.log(progressGuidance);
123156
- console.log();
123157
- }
123158
- const baseSystemPromptAppend = resolved.system_prompt_append ?? "";
123159
- if (baseSystemPromptAppend.trim().length > 0) {
123160
- console.log(`-- User System Prompt Append (${formatBytes(baseSystemPromptAppend.length)}) --`);
123161
- console.log(baseSystemPromptAppend);
123162
- console.log();
123163
- }
123164
- console.log(`=== Append System Prompt (per-session) ===
123165
- `);
123166
- const handoffContent = existsSync107(handoffPath) ? readFileSync96(handoffPath, "utf-8") : "";
123167
- if (handoffContent.trim().length > 0) {
123168
- console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
123169
- console.log(handoffContent);
123170
- console.log();
123171
- } else {
123172
- console.log("-- Handoff Briefing (0 bytes, no prior session) --");
123173
- console.log();
123174
- }
123175
- console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
123176
- `);
123177
- const claudeMdContent = existsSync107(claudeMdPath2) ? readFileSync96(claudeMdPath2, "utf-8") : "";
123178
- if (claudeMdContent.trim().length > 0) {
123179
- console.log(`(${formatBytes(claudeMdContent.length)})`);
123180
- console.log(claudeMdContent);
123181
- console.log();
123182
- } else {
123183
- console.log("(0 bytes, not present)");
123184
- console.log();
123185
- }
123186
- console.log(`=== Persona (SOUL.md) ===
123187
- `);
123188
- const soulMdContent = existsSync107(soulMdPath) ? readFileSync96(soulMdPath, "utf-8") : existsSync107(workspaceSoulMdPath) ? readFileSync96(workspaceSoulMdPath, "utf-8") : "";
123189
- if (soulMdContent.trim().length > 0) {
123190
- console.log(`(${formatBytes(soulMdContent.length)})`);
123191
- console.log(soulMdContent);
123192
- console.log();
123193
- } else {
123194
- console.log("(0 bytes, stale placeholder \u2014 Phase 2 item: single source of truth for persona)");
123195
- console.log();
123196
- }
123197
- console.log(`=== Per-Turn Injections (UserPromptSubmit) ===
123198
- `);
123199
- if (useHotReloadStable) {
123200
- if (stableResult.concatenated.trim().length > 0) {
123201
- console.log(`-- Workspace Stable (hot-reload hook): ${formatBytes(stableResult.concatenated.length)} --`);
123202
- console.log(stableResult.concatenated);
123203
- console.log();
123204
- } else {
123205
- console.log("-- Workspace Stable (hot-reload hook): 0 bytes, not set up --");
123206
- console.log();
123207
- }
123208
- }
123209
- const dynamicResult = await buildDynamicBootstrapPrompt({
123210
- workspaceDir,
123211
- budget: { warningMode: "off" }
123212
- });
123213
- if (dynamicResult.concatenated.trim().length > 0) {
123214
- console.log(`-- Workspace Dynamic: fired, ${formatBytes(dynamicResult.concatenated.length)} --`);
123215
- console.log(dynamicResult.concatenated);
123216
- console.log();
123217
- } else {
123218
- console.log("-- Workspace Dynamic: no content (MEMORY.md and daily notes empty or missing) --");
123219
- console.log();
123220
- }
123221
- const hindsightEnabled = isHindsightEnabled(config) && agentConfig.memory?.auto_recall !== false;
123222
- if (hindsightEnabled) {
123223
- console.log("-- Hindsight Recall: enabled (exact content unavailable, check hindsight logs) --");
123224
- console.log();
123225
- } else {
123226
- console.log("-- Hindsight Recall: disabled --");
123227
- console.log();
123228
- }
123229
- console.log(`=== User Message (latest turn) ===
123230
- `);
123231
- const transcriptPath = findLatestTranscriptJsonl(claudeConfigDir);
123232
- const userMessage = transcriptPath ? extractLatestUserMessage(transcriptPath) : undefined;
123233
- if (userMessage) {
123234
- console.log(`(Turn timestamp: ${userMessage.timestamp})`);
123235
- console.log(userMessage.text);
123236
- console.log();
123237
- } else {
123238
- console.log("(unavailable: no transcript found or transcript empty)");
123239
- console.log();
123240
- }
123241
- console.log(`=== Totals ===
123242
- `);
123243
- const stableBytes = stableResult.concatenated.length + progressGuidance.length + baseSystemPromptAppend.length;
123244
- const perSessionBytes = handoffContent.length;
123245
- const claudeMdBytes = claudeMdContent.length;
123246
- const soulMdBytes = soulMdContent.length;
123247
- const perTurnBytes = dynamicResult.concatenated.length;
123248
- const userBytes = userMessage?.text.length ?? 0;
123249
- const fleetDir = join111(agentsDir, "..", "fleet");
123250
- const fleetInvPath = join111(fleetDir, "switchroom-invariants.md");
123251
- const fleetClaudePath = join111(fleetDir, "CLAUDE.md");
123252
- const fleetInvBytes = existsSync107(fleetInvPath) ? readFileSync96(fleetInvPath, "utf-8").length : 0;
123253
- const fleetClaudeBytes = existsSync107(fleetClaudePath) ? readFileSync96(fleetClaudePath, "utf-8").length : 0;
123254
- const fleetBytes = fleetInvBytes + fleetClaudeBytes;
123255
- const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
123256
- console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
123257
- console.log(`Per-session: ${formatBytes(perSessionBytes).padEnd(20)} (cache-warm until next session)`);
123258
- console.log(`CLAUDE.md (cwd): ${formatBytes(claudeMdBytes).padEnd(20)} (cache-hot)`);
123259
- console.log(`Fleet invariants: ${formatBytes(fleetBytes).padEnd(20)} (--add-dir, cache-hot)`);
123260
- console.log(`Per-turn: ${formatBytes(perTurnBytes).padEnd(20)} (never cached \u2014 the real per-turn $ cost)`);
123261
- console.log(`User message: ${formatBytes(userBytes).padEnd(20)}`);
123262
- console.log(`Authored text: ${formatBytes(totalBytes).padEnd(20)} (~${estimateTokens(totalBytes).toLocaleString()} tokens est.)`);
123263
- const mcpServers = readMcpServerNames(agentDir);
123264
- const mcpCount = mcpServers?.length ?? null;
123265
- const mcpEstK = mcpCount != null ? mcpCount * 3 : null;
123266
- const mcpLabel = mcpCount != null ? `${mcpCount} servers` : "(unreadable \u2014 agent-private .mcp.json)";
123267
- const mcpEstLabel = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k tok` : "~30k tok (audit est.)";
123268
- console.log(`MCP tool surface: ${mcpLabel.padEnd(20)} (NOT counted above; ~3k tok/server \u2248 ${mcpEstLabel} \u2014 deferred under tool search)`);
123269
- if (mcpServers && mcpServers.length > 0) {
123270
- console.log(` [${mcpServers.join(", ")}]`);
123271
- }
123272
- const floorMcp = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k` : "~30k";
123273
- console.log(`Per-turn FLOOR: ${`~${estimateTokens(totalBytes).toLocaleString()} + ${floorMcp} MCP + ~13k CLI-base`.padEnd(20)} tokens est. (before the user msg, recall, or tool results)`);
123274
- const stableCacheInput = stableResult.concatenated + progressGuidance + baseSystemPromptAppend;
123275
- const stableHash = sha2562(stableCacheInput);
123276
- console.log(`Cache stable hash: sha256:${stableHash}`);
123277
- console.log();
123278
- }));
123279
- }
123280
-
123281
123505
  // src/cli/worktree.ts
123282
123506
  init_source();
123283
123507
  init_claim();