switchroom 0.21.16 → 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.
Files changed (43) hide show
  1. package/dist/agent-scheduler/index.js +5 -0
  2. package/dist/auth-broker/index.js +5 -0
  3. package/dist/cli/notion-write-pretool.mjs +5 -0
  4. package/dist/cli/switchroom.js +1492 -1136
  5. package/dist/host-control/main.js +6 -1
  6. package/dist/vault/approvals/kernel-server.js +5 -0
  7. package/dist/vault/broker/server.js +5 -0
  8. package/package.json +1 -1
  9. package/profiles/_base/start.sh.hbs +11 -0
  10. package/telegram-plugin/dist/gateway/gateway.js +9 -4
  11. package/telegram-plugin/scripts/bun-test-ci.sh +6 -0
  12. package/telegram-plugin/uat/flip/allowlist.test.ts +229 -0
  13. package/telegram-plugin/uat/flip/allowlist.ts +349 -0
  14. package/telegram-plugin/uat/flip/gate.test.ts +153 -0
  15. package/telegram-plugin/uat/flip/gate.ts +232 -0
  16. package/telegram-plugin/uat/flip/probe-scoring.test.ts +210 -0
  17. package/telegram-plugin/uat/flip/probe-scoring.ts +200 -0
  18. package/telegram-plugin/uat/flip/probe-suite.test.ts +95 -0
  19. package/telegram-plugin/uat/flip/probe-suite.ts +155 -0
  20. package/telegram-plugin/uat/flip/probes/kdogg.probes.json +36 -0
  21. package/telegram-plugin/uat/flip/probes/test-harness.probes.json +15 -0
  22. package/telegram-plugin/uat/flip/recall-log.test.ts +131 -0
  23. package/telegram-plugin/uat/flip/recall-log.ts +178 -0
  24. package/telegram-plugin/uat/flip/report.ts +95 -0
  25. package/telegram-plugin/uat/flip/tier1-equivalence.test.ts +470 -0
  26. package/telegram-plugin/uat/flip/tier1-equivalence.ts +697 -0
  27. package/telegram-plugin/uat/flip/tier2-probe-runner.ts +327 -0
  28. package/telegram-plugin/uat/runners/scorer.ts +1 -1
  29. package/vendor/hindsight-memory/hooks/hooks.json +9 -0
  30. package/vendor/hindsight-memory/scripts/directive_verify.py +43 -1
  31. package/vendor/hindsight-memory/scripts/lib/client.py +35 -0
  32. package/vendor/hindsight-memory/scripts/lib/config.py +47 -0
  33. package/vendor/hindsight-memory/scripts/lib/orientation.py +248 -0
  34. package/vendor/hindsight-memory/scripts/lib/recall_buffer.py +29 -0
  35. package/vendor/hindsight-memory/scripts/orientation.py +195 -0
  36. package/vendor/hindsight-memory/scripts/prefetch.py +10 -0
  37. package/vendor/hindsight-memory/scripts/recall.py +144 -11
  38. package/vendor/hindsight-memory/scripts/setup_hooks.py +10 -1
  39. package/vendor/hindsight-memory/scripts/tests/fixtures/rules-block.golden.md +9 -0
  40. package/vendor/hindsight-memory/scripts/tests/test_orientation_hook.py +283 -0
  41. package/vendor/hindsight-memory/scripts/tests/test_orientation_logic.py +176 -0
  42. package/vendor/hindsight-memory/scripts/tests/test_prefetch_invalidation.py +329 -0
  43. package/vendor/hindsight-memory/scripts/tests/test_recall_directive_suppression.py +328 -0
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.21.16", COMMIT_SHA = "d6c1267a", COMMIT_DATE = "2026-08-17T09:16:43Z";
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";
@@ -13745,6 +13745,10 @@ var init_schema = __esm(() => {
13745
13745
  }).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry \u2014 e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced \u2014 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded \u2014 each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
13746
13746
  observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
13747
13747
  rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded \u2014 the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false \u21d2 byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded \u2014 each agent's flip is a deliberate M3 rollout step)."),
13748
+ orientation: exports_external.boolean().optional().describe("Memory v2 M5 go-live flag (Surface B: orientation-at-boot), " + "effective default false \u2014 dark build (unset \u21d2 OFF, applied by the " + "scaffold resolver, NOT a Zod .default() \u2014 a hard default here would " + "shadow the per-agent value in the cascade the way it does every other " + "memory knob). When true, the orientation SessionStart " + "hook injects this agent's cron-refreshed `orientation` mental model " + "into context at boot AND re-seats it after every compaction (E-88), " + "deterministically and with zero tool call. Unset/false \u21d2 byte-" + "identical to pre-M5 behaviour: the hook no-ops before any bank " + "resolve or network call. Per-agent ONLY (never fleet-seeded \u2014 a " + "flip is a deliberate canary/rollout step gated on the agent's " + "m2-residue measurement + operator-created model, carve-M5 \u00a77). " + "Cascade: override (per-agent wins over default)."),
13749
+ orientation_reinject_turns: exports_external.number().int().min(0).optional().describe("Memory v2 M5 optional per-turn re-inject cadence, effective default " + "0 = off (unset \u21d2 0, applied by the scaffold resolver, not a Zod " + ".default() \u2014 same cascade-shadowing reason as `orientation`). At 0 the " + "briefing is injected only at SessionStart + compaction (the cheap " + "path). N>0 " + "would additionally re-inject the orientation briefing every N turns; " + "priced at ~55M tok/30d at every-turn, which is WHY it defaults off " + "(carve-M5 \u00a72/\u00a74). Only late-session context loss should ever set it, " + "per-agent, measured. A stripped/absent value fails to 0 (fail-safe " + "AND fail-cheap). Per-agent ONLY. Cascade: override."),
13750
+ orientation_cadence_hours: exports_external.number().int().min(1).optional().describe("Memory v2 M5 per-agent refresh cadence tier (hours), effective " + "default 48 (unset \u21d2 48, applied by the scaffold resolver, not a Zod " + ".default() \u2014 a hard default would shadow a `defaults.memory." + "orientation_cadence_hours` fleet value in the cascade). " + "The fast-churning agents (klanker, overlord) set 24; everyone else " + "inherits 48 (carve-M5 \u00a76, Ken's locked tiered cadence). Drives BOTH " + "the out-of-session refresh scheduler AND the staleness guard's " + "per-tier thresholds (stale prefix at 1.5\u00d7 cadence, cold degrade at " + "3\u00d7) \u2014 so a fixed 36h can never mislabel a 48h-tier agent as stale a " + "day early. Modelled as an explicit int (not a hard-coded name list) " + "so the tiering is data-driven and auditable. A cost knob, not an " + "enablement knob: a stripped/absent value failing to 48 is safe " + "(the cheaper tier). Cascade: override (per-agent wins over default); " + "unlike `orientation`/`_reinject_turns` it IS accepted at the " + "defaults/profile tier."),
13751
+ inject_directives: exports_external.boolean().optional().describe("Memory v2 M3 Surface-A directive-injection switch (default TRUE \u2014 " + "on-by-default via the plugin settings.json stamp + config.py " + "DEFAULTS). When left unset the agent keeps injecting the " + "`<active_directives>` block on every turn, byte-identical to pre-M3. " + "Set FALSE to SUPPRESS that injection for this agent \u2014 its standing " + "rules are then served from the live rules block (memory.rules_block) " + "instead of re-injected directives, which is the change that collapses " + "the always-on directive-token spend (E-41). The suppression is " + "fail-safe and re-checked every turn in recall.py: it fires ONLY when " + "a non-empty rules block is physically present in the agent's " + "CLAUDE.md \u2014 a false flag with an empty/absent block keeps injecting " + "AND emits a degraded-canary notice, so a zero-standing-rules turn is " + "unreachable. Requires memory.rules_block=true first (the block must " + "be live before injection is turned off \u2014 enforce the ordering with " + "`switchroom memory flip`, never a bare flag flip). Coordinates with " + "M4's memory.recall prefetch (memoryPrefetchEnabled): the same guard " + "gates all three recall.py emit sites so a flipped agent leaks no " + "directive on a cache/prefetch hit. Cascade: override, per-agent ONLY " + "(never fleet-seeded from the defaults/profile tier \u2014 each flip is a " + "deliberate staggered M3 rollout step, same darkness safeguard as " + "rules_block). Default-true is the fail-safe direction under the " + "Zod-strip footgun: a stripped/misread key keeps the agent injecting."),
13748
13752
  disposition: exports_external.object({
13749
13753
  skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
13750
13754
  literalism: exports_external.number().int().min(1).max(5).optional().describe("How literally the bank reads statements vs inferring intent (1-5; engine default 3)."),
@@ -14190,6 +14194,7 @@ var init_schema = __esm(() => {
14190
14194
  profile: exports_external.string().optional(),
14191
14195
  directive_capture_nudge: exports_external.boolean().optional(),
14192
14196
  profile_capture_nudge: exports_external.boolean().optional(),
14197
+ orientation_cadence_hours: exports_external.number().int().min(1).optional(),
14193
14198
  anti_confabulation_directive: AntiConfabulationDirectiveSchema,
14194
14199
  observation_scopes: ObservationScopesSchema,
14195
14200
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -19562,6 +19567,9 @@ function generateHindsightMcpConfig(collection, memoryConfig, opts = {}) {
19562
19567
  }
19563
19568
  };
19564
19569
  }
19570
+ const grantedBanks = [
19571
+ ...new Set((opts.knowledgeExtraBanks ?? []).filter((b) => b && b !== collection))
19572
+ ];
19565
19573
  return {
19566
19574
  type: "stdio",
19567
19575
  command: HINDSIGHT_SHIM_CLI_PATH,
@@ -19569,6 +19577,7 @@ function generateHindsightMcpConfig(collection, memoryConfig, opts = {}) {
19569
19577
  env: {
19570
19578
  HINDSIGHT_MCP_URL: url,
19571
19579
  HINDSIGHT_BANK_ID: collection,
19580
+ ...grantedBanks.length > 0 ? { HINDSIGHT_KNOWLEDGE_EXTRA_BANKS: grantedBanks.join(",") } : {},
19572
19581
  HINDSIGHT_SHIM_CACHE_DIR: `${HINDSIGHT_SHIM_AGENT_HOME}/.hindsight-shim`,
19573
19582
  HOME: HINDSIGHT_SHIM_AGENT_HOME,
19574
19583
  ...opts.reflectBudget ? { HINDSIGHT_SHIM_REFLECT_BUDGET: opts.reflectBudget } : {},
@@ -28007,9 +28016,11 @@ function getHindsightSettingsEntry(agentName, config) {
28007
28016
  }
28008
28017
  const collection = getCollectionForAgent(agentName, config);
28009
28018
  const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[agentName] ?? {});
28019
+ const { additionalBanks } = resolveUsers(config, agentName);
28010
28020
  const mcpConfig = generateHindsightMcpConfig(collection, memoryConfig, {
28011
28021
  reflectBudget: resolved.memory?.reflect_budget,
28012
- reflectMaxTokens: resolved.memory?.reflect_max_tokens
28022
+ reflectMaxTokens: resolved.memory?.reflect_max_tokens,
28023
+ knowledgeExtraBanks: additionalBanks
28013
28024
  });
28014
28025
  return { key: "hindsight", value: { ...mcpConfig, alwaysLoad: false } };
28015
28026
  }
@@ -28102,6 +28113,7 @@ function getBuiltinDefaultSkillEntries() {
28102
28113
  var GOOGLE_WORKSPACE_MCP_PINNED_SHA = "db6212992738d4380a94fb6940140bcebf5d861d", MICROSOFT_WORKSPACE_MCP_PINNED_VERSION = "0.113.0", MICROSOFT_WORKSPACE_MCP_PACKAGE = "@softeria/ms-365-mcp-server", NOTION_MCP_PINNED_VERSION = "1.8.1", NOTION_MCP_PACKAGE = "@notionhq/notion-mcp-server", NOTION_TOKEN_ENV = "NOTION_TOKEN";
28103
28114
  var init_scaffold_integration = __esm(() => {
28104
28115
  init_merge();
28116
+ init_users();
28105
28117
  init_hindsight2();
28106
28118
  });
28107
28119
 
@@ -30493,6 +30505,7 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig, resolvedA
30493
30505
  const additionalBanks = resolveUsers(switchroomConfig, agentName).additionalBanks;
30494
30506
  const retainConfig = resolveHindsightRetainConfig(switchroomConfig, agentName, resolvedAgentConfig);
30495
30507
  const observationScopeSettings = resolveHindsightObservationScopeSettings(switchroomConfig, agentName, resolvedAgentConfig);
30508
+ const orientationSettings = resolveHindsightOrientationSettings(switchroomConfig, agentName, resolvedAgentConfig);
30496
30509
  const recallConfig = resolveHindsightRecallConfig(switchroomConfig, agentName, resolvedAgentConfig);
30497
30510
  const recallTunables = resolveHindsightRecallTunables(recallConfig);
30498
30511
  const recallCaps = resolveHindsightRecallCaps(recallConfig);
@@ -30500,7 +30513,7 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig, resolvedA
30500
30513
  try {
30501
30514
  const vendorSettingsPath = join15(sourcePath, "settings.json");
30502
30515
  if (existsSync20(vendorSettingsPath)) {
30503
- const expectedSettings = renderHindsightSettingsOverrides(readFileSync17(vendorSettingsPath, "utf-8"), additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings);
30516
+ const expectedSettings = renderHindsightSettingsOverrides(readFileSync17(vendorSettingsPath, "utf-8"), additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings);
30504
30517
  if (expectedSettings != null)
30505
30518
  contentOverrides["settings.json"] = expectedSettings;
30506
30519
  }
@@ -30518,7 +30531,7 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig, resolvedA
30518
30531
  }
30519
30532
  copyDirRecursive2(sourcePath, destPath);
30520
30533
  }
30521
- applyHindsightSettingsOverrides(destPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings);
30534
+ applyHindsightSettingsOverrides(destPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings);
30522
30535
  applyHindsightHooksOverrides(destPath, recallTunables);
30523
30536
  const bankId = agentMemory?.collection ?? agentName;
30524
30537
  const mcpUrl = memory.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
@@ -30547,6 +30560,15 @@ function resolveHindsightObservationScopeSettings(switchroomConfig, agentName, r
30547
30560
  observationScopeStrategy: memory?.observation_scope_strategy
30548
30561
  };
30549
30562
  }
30563
+ function resolveHindsightOrientationSettings(switchroomConfig, agentName, resolvedAgentConfig) {
30564
+ const memory = resolvedAgentConfig ? resolvedAgentConfig.memory : switchroomConfig ? resolveAgentConfig(switchroomConfig.defaults, switchroomConfig.profiles, switchroomConfig.agents[agentName] ?? {})?.memory : undefined;
30565
+ return {
30566
+ enabled: memory?.orientation === true,
30567
+ model: "orientation",
30568
+ cadenceHours: typeof memory?.orientation_cadence_hours === "number" ? memory.orientation_cadence_hours : 48,
30569
+ reinjectTurns: typeof memory?.orientation_reinject_turns === "number" ? memory.orientation_reinject_turns : 0
30570
+ };
30571
+ }
30550
30572
  function resolveHindsightRecallConfig(switchroomConfig, agentName, resolvedAgentConfig) {
30551
30573
  if (resolvedAgentConfig) {
30552
30574
  return resolvedAgentConfig.memory?.recall;
@@ -30581,7 +30603,7 @@ function defaultRecallOwnBankMinSlots(maxMemories) {
30581
30603
  function defaultRecallAdditionalBankMinSlots(maxMemories) {
30582
30604
  return recallSlotFloorFromCap(maxMemories, RECALL_ADDITIONAL_BANK_SLOT_DIVISOR);
30583
30605
  }
30584
- function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings) {
30606
+ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings) {
30585
30607
  const settingsPath = join15(pluginDestPath, "settings.json");
30586
30608
  if (!existsSync20(settingsPath))
30587
30609
  return;
@@ -30591,12 +30613,12 @@ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retain
30591
30613
  } catch {
30592
30614
  return;
30593
30615
  }
30594
- const next = renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings);
30616
+ const next = renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings);
30595
30617
  if (next == null)
30596
30618
  return;
30597
30619
  writeFileSyncIfChanged(settingsPath, next);
30598
30620
  }
30599
- function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings) {
30621
+ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings) {
30600
30622
  let settings;
30601
30623
  try {
30602
30624
  settings = JSON.parse(raw);
@@ -30618,6 +30640,7 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, re
30618
30640
  settings.recallSkipTrivial = true;
30619
30641
  settings.directiveCaptureNudge = true;
30620
30642
  settings.profileCaptureNudge = true;
30643
+ settings.injectDirectives = true;
30621
30644
  settings.recallTagWeights = { ...HINDSIGHT_RECALL_TAG_WEIGHT_SEED };
30622
30645
  if (additionalBanks.length > 0) {
30623
30646
  settings.recallAdditionalBanks = [...additionalBanks];
@@ -30630,6 +30653,10 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, re
30630
30653
  if (typeof observationScopeStrategy === "string" && observationScopeStrategy.length > 0) {
30631
30654
  settings.observationScopeStrategy = observationScopeStrategy;
30632
30655
  }
30656
+ settings.memoryOrientationEnabled = orientationSettings.enabled;
30657
+ settings.memoryOrientationModel = orientationSettings.model;
30658
+ settings.memoryOrientationCadenceHours = orientationSettings.cadenceHours;
30659
+ settings.memoryOrientationReinjectTurns = orientationSettings.reinjectTurns;
30633
30660
  return JSON.stringify(settings, null, 2) + `
30634
30661
  `;
30635
30662
  }
@@ -30672,6 +30699,7 @@ function buildWorkspaceContext(args) {
30672
30699
  hindsightRecallSkipTrivial,
30673
30700
  hindsightDirectiveCaptureNudge,
30674
30701
  hindsightProfileCaptureNudge,
30702
+ hindsightInjectDirectives,
30675
30703
  hindsightObservationScopes,
30676
30704
  hindsightObservationScopeStrategy,
30677
30705
  hindsightTopicAliasesJson,
@@ -30728,6 +30756,7 @@ function buildWorkspaceContext(args) {
30728
30756
  hindsightRecallSkipTrivial,
30729
30757
  hindsightDirectiveCaptureNudge,
30730
30758
  hindsightProfileCaptureNudge,
30759
+ hindsightInjectDirectives,
30731
30760
  hindsightObservationScopesQ: hindsightObservationScopes ? shellSingleQuote(hindsightObservationScopes) : undefined,
30732
30761
  hindsightObservationScopeStrategyQ: hindsightObservationScopeStrategy ? shellSingleQuote(hindsightObservationScopeStrategy) : undefined,
30733
30762
  hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
@@ -31004,6 +31033,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
31004
31033
  const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
31005
31034
  const hindsightDirectiveCaptureNudge = agentConfig.memory?.directive_capture_nudge === undefined ? undefined : String(agentConfig.memory.directive_capture_nudge);
31006
31035
  const hindsightProfileCaptureNudge = agentConfig.memory?.profile_capture_nudge === undefined ? undefined : String(agentConfig.memory.profile_capture_nudge);
31036
+ const hindsightInjectDirectives = agentConfig.memory?.inject_directives === undefined ? undefined : String(agentConfig.memory.inject_directives);
31007
31037
  const hindsightObservationScopes = agentConfig.memory?.observation_scopes;
31008
31038
  const hindsightObservationScopeStrategy = agentConfig.memory?.observation_scope_strategy;
31009
31039
  const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
@@ -31042,6 +31072,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
31042
31072
  hindsightRecallSkipTrivial,
31043
31073
  hindsightDirectiveCaptureNudge,
31044
31074
  hindsightProfileCaptureNudge,
31075
+ hindsightInjectDirectives,
31045
31076
  hindsightObservationScopes,
31046
31077
  hindsightObservationScopeStrategy,
31047
31078
  hindsightTopicAliasesJson,
@@ -32031,6 +32062,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
32031
32062
  const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
32032
32063
  const hindsightDirectiveCaptureNudge = agentConfig.memory?.directive_capture_nudge === undefined ? undefined : String(agentConfig.memory.directive_capture_nudge);
32033
32064
  const hindsightProfileCaptureNudge = agentConfig.memory?.profile_capture_nudge === undefined ? undefined : String(agentConfig.memory.profile_capture_nudge);
32065
+ const hindsightInjectDirectives = agentConfig.memory?.inject_directives === undefined ? undefined : String(agentConfig.memory.inject_directives);
32034
32066
  const hindsightObservationScopes = agentConfig.memory?.observation_scopes;
32035
32067
  const hindsightObservationScopeStrategy = agentConfig.memory?.observation_scope_strategy;
32036
32068
  const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
@@ -32081,6 +32113,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
32081
32113
  hindsightRecallSkipTrivial,
32082
32114
  hindsightDirectiveCaptureNudge,
32083
32115
  hindsightProfileCaptureNudge,
32116
+ hindsightInjectDirectives,
32084
32117
  hindsightObservationScopesQ: hindsightObservationScopes ? shellSingleQuote(hindsightObservationScopes) : undefined,
32085
32118
  hindsightObservationScopeStrategyQ: hindsightObservationScopeStrategy ? shellSingleQuote(hindsightObservationScopeStrategy) : undefined,
32086
32119
  hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
@@ -32519,6 +32552,7 @@ ${body}
32519
32552
  hindsightRecallSkipTrivial,
32520
32553
  hindsightDirectiveCaptureNudge,
32521
32554
  hindsightProfileCaptureNudge,
32555
+ hindsightInjectDirectives,
32522
32556
  hindsightObservationScopes,
32523
32557
  hindsightObservationScopeStrategy,
32524
32558
  hindsightTopicAliasesJson,
@@ -66507,6 +66541,51 @@ function readClaudeMdSafe(agentsDir, agentName, readFileFn) {
66507
66541
  return null;
66508
66542
  }
66509
66543
  }
66544
+ function runDirectiveFlipChecks(config, deps = {}) {
66545
+ const readFileFn = deps.readFileFn ?? ((p) => readFileSync65(p, "utf-8"));
66546
+ let agentsDir = deps.agentsDir;
66547
+ if (agentsDir === undefined) {
66548
+ try {
66549
+ agentsDir = resolveAgentsDir(config);
66550
+ } catch {
66551
+ agentsDir = undefined;
66552
+ }
66553
+ }
66554
+ const results = [];
66555
+ for (const [agentName, agentConfig] of Object.entries(config.agents)) {
66556
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
66557
+ if (resolved.memory?.inject_directives !== false)
66558
+ continue;
66559
+ const name = `agent ${agentName} directive flip (inject_directives=false)`;
66560
+ if (resolved.memory?.rules_block !== true) {
66561
+ results.push({
66562
+ name,
66563
+ status: "fail",
66564
+ detail: "inject_directives is false but memory.rules_block is not true \u2014 the " + "rules block that must carry the migrated directives is not even " + "enabled. recall.py fails safe (keeps injecting), but fix the config: " + "enable rules_block and migrate, or re-enable inject_directives.",
66565
+ fix: `Set memory.rules_block: true for ${agentName} (and migrate its directives) before flipping.`
66566
+ });
66567
+ continue;
66568
+ }
66569
+ const claudeMd = readClaudeMdSafe(agentsDir, agentName, readFileFn);
66570
+ const parsed = claudeMd ? parseRulesBlock(claudeMd) : null;
66571
+ const ruleCount = parsed?.rules.length ?? 0;
66572
+ if (ruleCount === 0) {
66573
+ results.push({
66574
+ name,
66575
+ status: "fail",
66576
+ detail: claudeMd === null ? "inject_directives is false but the agent's CLAUDE.md could not be " + "read to confirm a rules block carries the migrated directives." : "inject_directives is false but the CLAUDE.md rules block is empty " + "(no `- **R-\u2026` rule lines). Every directive would be suppressed with " + "nothing carrying its guarantee. recall.py fails safe at runtime, but " + "fix the config: migrate the directives into the rules block, or " + "re-enable inject_directives.",
66577
+ fix: `Migrate ${agentName}'s directives into the rules block (memory rule add) or re-enable inject_directives.`
66578
+ });
66579
+ } else {
66580
+ results.push({
66581
+ name,
66582
+ status: "ok",
66583
+ detail: `flipped, and the rules block carries ${ruleCount} rule(s) \u2014 directive injection safely suppressed.`
66584
+ });
66585
+ }
66586
+ }
66587
+ return results;
66588
+ }
66510
66589
  async function runRulesBlockChecks(config, engineUrl, deps = {}) {
66511
66590
  const readFileFn = deps.readFileFn ?? ((p) => readFileSync65(p, "utf-8"));
66512
66591
  let agentsDir = deps.agentsDir;
@@ -68797,6 +68876,7 @@ async function checkHindsight(config) {
68797
68876
  results.push(...await checkBankIngestHealth(config, url, { includeConsolidationBacklog: true }));
68798
68877
  results.push(...await checkBankObservationsMissions(config, url));
68799
68878
  results.push(...await runRulesBlockChecks(config, url));
68879
+ results.push(...runDirectiveFlipChecks(config));
68800
68880
  results.push(...await checkBankConfigApiReachable(config, url));
68801
68881
  results.push(await checkObservationScopeSaturation(config, url));
68802
68882
  const memoryAgentsDir = resolveAgentsDir(config);
@@ -71656,11 +71736,11 @@ var init_apply = __esm(() => {
71656
71736
  });
71657
71737
 
71658
71738
  // src/host-control/audit-reader.ts
71659
- 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";
71660
71740
  import { homedir as homedir43 } from "node:os";
71661
- import { join as join90 } from "node:path";
71741
+ import { join as join91 } from "node:path";
71662
71742
  function defaultAuditLogPath2(home2 = homedir43()) {
71663
- return join90(home2, ".switchroom", "host-control-audit.log");
71743
+ return join91(home2, ".switchroom", "host-control-audit.log");
71664
71744
  }
71665
71745
  function auditRotation() {
71666
71746
  return resolveRotationConfig({
@@ -71677,14 +71757,14 @@ function auditReadFullWindowBytes() {
71677
71757
  function auditReadMaxGenerations() {
71678
71758
  return auditRotation().maxFiles;
71679
71759
  }
71680
- function tailBytes(path7, n, strict = false) {
71760
+ function tailBytes(path9, n, strict = false) {
71681
71761
  if (n <= 0)
71682
71762
  return "";
71683
71763
  let fd;
71684
71764
  let size;
71685
71765
  try {
71686
- size = statSync49(path7).size;
71687
- fd = openSync15(path7, "r");
71766
+ size = statSync50(path9).size;
71767
+ fd = openSync15(path9, "r");
71688
71768
  } catch (err) {
71689
71769
  if (strict && err.code !== "ENOENT")
71690
71770
  throw err;
@@ -71729,7 +71809,7 @@ function readAuditRaw(logPath, opts = {}) {
71729
71809
  budget -= Buffer.byteLength(active, "utf-8");
71730
71810
  for (let n = 1;n <= maxFiles && budget > 0; n++) {
71731
71811
  const gen = `${logPath}.${n}`;
71732
- if (!existsSync83(gen))
71812
+ if (!existsSync84(gen))
71733
71813
  break;
71734
71814
  const chunk2 = tailBytes(gen, budget, strict);
71735
71815
  if (chunk2.length === 0)
@@ -71907,16 +71987,16 @@ var init_audit_reader = __esm(() => {
71907
71987
  });
71908
71988
 
71909
71989
  // src/web/fleet-health-read.ts
71910
- import { readFileSync as readFileSync78 } from "node:fs";
71911
- import { resolve as resolve45 } from "node:path";
71990
+ import { readFileSync as readFileSync79 } from "node:fs";
71991
+ import { resolve as resolve46 } from "node:path";
71912
71992
  import { homedir as homedir46 } from "node:os";
71913
71993
  function fleetHealthLedgerPath(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir46()) {
71914
- return resolve45(home2, ".switchroom", "fleet-health", "ledger.json");
71994
+ return resolve46(home2, ".switchroom", "fleet-health", "ledger.json");
71915
71995
  }
71916
- function readFleetHealth(path7) {
71996
+ function readFleetHealth(path9) {
71917
71997
  let ledger;
71918
71998
  try {
71919
- ledger = JSON.parse(readFileSync78(path7, "utf-8"));
71999
+ ledger = JSON.parse(readFileSync79(path9, "utf-8"));
71920
72000
  } catch {
71921
72001
  return {
71922
72002
  owner_agent: null,
@@ -71942,8 +72022,8 @@ function readFleetHealth(path7) {
71942
72022
  ...ranked.length === 0 ? { reason: EMPTY_REASON } : {}
71943
72023
  };
71944
72024
  }
71945
- function handleGetFleetHealth(path7 = fleetHealthLedgerPath()) {
71946
- return readFleetHealth(path7);
72025
+ function handleGetFleetHealth(path9 = fleetHealthLedgerPath()) {
72026
+ return readFleetHealth(path9);
71947
72027
  }
71948
72028
  var EMPTY_REASON;
71949
72029
  var init_fleet_health_read = __esm(() => {
@@ -110178,8 +110258,8 @@ init_loader();
110178
110258
  init_hindsight();
110179
110259
  var import_yaml16 = __toESM(require_dist(), 1);
110180
110260
  import { spawn as spawn5 } from "node:child_process";
110181
- import { existsSync as existsSync82, readFileSync as readFileSync74, statSync as statSync48 } from "node:fs";
110182
- 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";
110183
110263
 
110184
110264
  // src/cli/memory-rules.ts
110185
110265
  init_source();
@@ -110446,11 +110526,933 @@ function registerMemoryDirectiveCommand(memory, program3) {
110446
110526
  }));
110447
110527
  }
110448
110528
 
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
+ }
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" };
110569
+ }
110570
+ return {
110571
+ category: override.category,
110572
+ signal: isActive ? overrideSignal : `${overrideSignal} \u2014 already inactive, nothing to retire`,
110573
+ action: isActive ? "retire" : "keep"
110574
+ };
110575
+ }
110576
+ return {
110577
+ category: "reflect-directive",
110578
+ signal: "no deterministic signal \u2014 defaults to KEEP",
110579
+ action: "keep"
110580
+ };
110581
+ }
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
+ });
110593
+ }
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
+ }
110619
+ }
110620
+ function normalizePositiveLimit(value) {
110621
+ if (!Number.isFinite(value) || value <= 0) {
110622
+ return 1;
110623
+ }
110624
+ return Math.floor(value);
110625
+ }
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) {
110631
+ return [];
110632
+ }
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);
110642
+ }
110643
+ return result;
110644
+ }
110645
+ function appendSeenSignature(signatures, signature) {
110646
+ if (!signature.trim()) {
110647
+ return signatures;
110648
+ }
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);
110657
+ }
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;
110665
+ }
110666
+ if (!injectedByPath.has(pathValue)) {
110667
+ injectedByPath.set(pathValue, file.content);
110668
+ }
110669
+ const normalizedPath = pathValue.replace(/\\/g, "/");
110670
+ const baseName = path7.posix.basename(normalizedPath);
110671
+ if (!injectedByBaseName.has(baseName)) {
110672
+ injectedByBaseName.set(baseName, file.content);
110673
+ }
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: [] };
110703
+ }
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");
110710
+ }
110711
+ if (totalOverLimit) {
110712
+ causes.push("total-limit");
110713
+ }
110714
+ }
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
110732
+ }
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;
110748
+ }
110749
+ if (a.rawChars !== b.rawChars) {
110750
+ return a.rawChars - b.rawChars;
110751
+ }
110752
+ if (a.injectedChars !== b.injectedChars) {
110753
+ return a.injectedChars - b.injectedChars;
110754
+ }
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")) {
110843
+ return;
110844
+ }
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
+
110449
111451
  // src/cli/memory.ts
110450
111452
  function persistMemoryConfigUrl(configPath, provider, url) {
110451
- if (!existsSync82(configPath))
111453
+ if (!existsSync83(configPath))
110452
111454
  return false;
110453
- const raw = readFileSync74(configPath, "utf-8");
111455
+ const raw = readFileSync75(configPath, "utf-8");
110454
111456
  const doc = import_yaml16.default.parseDocument(raw);
110455
111457
  if (!doc.has("memory")) {
110456
111458
  doc.set("memory", { backend: "hindsight", shared_collection: "shared", config: { provider, url } });
@@ -110468,7 +111470,7 @@ function persistMemoryConfigUrl(configPath, provider, url) {
110468
111470
  }
110469
111471
  let mode = 420;
110470
111472
  try {
110471
- mode = statSync48(configPath).mode & 511;
111473
+ mode = statSync49(configPath).mode & 511;
110472
111474
  } catch {}
110473
111475
  writeConfigFileSync(configPath, doc.toString(), mode);
110474
111476
  return true;
@@ -110517,12 +111519,12 @@ async function resolveLiteLLMForHindsight(config) {
110517
111519
  return resolveHindsightLiteLlm(config);
110518
111520
  }
110519
111521
  function readRecallLog(agentDir, limit) {
110520
- const path7 = join88(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
110521
- if (!existsSync82(path7))
111522
+ const path9 = join89(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
111523
+ if (!existsSync83(path9))
110522
111524
  return [];
110523
111525
  let raw;
110524
111526
  try {
110525
- raw = readFileSync74(path7, "utf-8");
111527
+ raw = readFileSync75(path9, "utf-8");
110526
111528
  } catch {
110527
111529
  return [];
110528
111530
  }
@@ -111286,9 +112288,9 @@ Repairing vector index coverage for ${target}${opts.dryRun ? source_default.yell
111286
112288
  captured += chunk2.toString();
111287
112289
  process.stdout.write(chunk2);
111288
112290
  });
111289
- const childExit = await new Promise((resolve43) => {
111290
- child.on("error", () => resolve43(127));
111291
- 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));
111292
112294
  });
111293
112295
  const outcome = classifyRepairOutcome({
111294
112296
  childExit,
@@ -111313,7 +112315,7 @@ Repairing vector index coverage for ${target}${opts.dryRun ? source_default.yell
111313
112315
  process.exit(1);
111314
112316
  })() : Object.keys(config.agents);
111315
112317
  for (const name of targets) {
111316
- const agentDir = join88(agentsDir, name);
112318
+ const agentDir = join89(agentsDir, name);
111317
112319
  const entries = readRecallLog(agentDir, limit);
111318
112320
  if (opts.json) {
111319
112321
  for (const e of entries) {
@@ -111506,11 +112508,12 @@ Observation-scope cleanup \u2014 DRY RUN (no writes)
111506
112508
  }));
111507
112509
  registerMemoryRuleCommand(memory, program3);
111508
112510
  registerMemoryDirectiveCommand(memory, program3);
112511
+ registerMemoryFlipCommand(memory, program3);
111509
112512
  }
111510
112513
 
111511
112514
  // src/cli/web.ts
111512
112515
  init_source();
111513
- import { join as join100 } from "node:path";
112516
+ import { join as join101 } from "node:path";
111514
112517
  import { homedir as homedir50 } from "node:os";
111515
112518
 
111516
112519
  // src/web/server.ts
@@ -111518,8 +112521,8 @@ init_merge();
111518
112521
  init_loader();
111519
112522
  init_client();
111520
112523
  import {
111521
- readFileSync as readFileSync82,
111522
- existsSync as existsSync90,
112524
+ readFileSync as readFileSync83,
112525
+ existsSync as existsSync91,
111523
112526
  realpathSync as realpathSync7,
111524
112527
  mkdirSync as mkdirSync50,
111525
112528
  openSync as openSync17,
@@ -111527,7 +112530,7 @@ import {
111527
112530
  writeSync as writeSync8,
111528
112531
  constants as fsConstants6
111529
112532
  } from "node:fs";
111530
- 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";
111531
112534
  import { homedir as homedir49 } from "node:os";
111532
112535
  import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes15 } from "node:crypto";
111533
112536
 
@@ -111536,8 +112539,8 @@ init_lifecycle();
111536
112539
  init_manager();
111537
112540
  init_hindsight2();
111538
112541
  import { spawnSync as spawnSync17 } from "node:child_process";
111539
- import { existsSync as existsSync86, readFileSync as readFileSync77, statSync as statSync51 } from "node:fs";
111540
- 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";
111541
112544
 
111542
112545
  // src/web/memory-remediation.ts
111543
112546
  init_hindsight2();
@@ -111605,15 +112608,15 @@ async function buildUserProfile(mcpUrl, bank, opts) {
111605
112608
  init_hindsight();
111606
112609
 
111607
112610
  // src/web/blocked-approvals-read.ts
111608
- import { readFileSync as readFileSync75, readdirSync as readdirSync30 } from "node:fs";
111609
- 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";
111610
112613
  import { homedir as homedir42 } from "node:os";
111611
112614
  function blockedApprovalsDir(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir42()) {
111612
- return resolve43(home2, ".switchroom", "blocked-approvals");
112615
+ return resolve44(home2, ".switchroom", "blocked-approvals");
111613
112616
  }
111614
112617
  var FALLBACK_RECORD_NAME = "blocked-approval.json";
111615
112618
  function agentStateRoot(sharedDir) {
111616
- return resolve43(sharedDir, "..", "agents");
112619
+ return resolve44(sharedDir, "..", "agents");
111617
112620
  }
111618
112621
  function finiteOrNull(v) {
111619
112622
  const n = Number(v);
@@ -111652,7 +112655,7 @@ function readBlockedApprovalsWithErrors(dir, agentsRoot = agentStateRoot(dir)) {
111652
112655
  function readSharedRecords(dir) {
111653
112656
  let entries;
111654
112657
  try {
111655
- entries = readdirSync30(dir);
112658
+ entries = readdirSync31(dir);
111656
112659
  } catch (err) {
111657
112660
  if (err?.code === "ENOENT") {
111658
112661
  return { blocked: [], unreadable: 0 };
@@ -111666,7 +112669,7 @@ function readSharedRecords(dir) {
111666
112669
  continue;
111667
112670
  let text;
111668
112671
  try {
111669
- text = readFileSync75(join89(dir, name), "utf-8");
112672
+ text = readFileSync76(join90(dir, name), "utf-8");
111670
112673
  } catch {
111671
112674
  unreadable++;
111672
112675
  continue;
@@ -111685,7 +112688,7 @@ function readSharedRecords(dir) {
111685
112688
  function readFallbackRecords(agentsRoot, alreadySeen) {
111686
112689
  let agents;
111687
112690
  try {
111688
- 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);
111689
112692
  } catch (err) {
111690
112693
  if (err?.code === "ENOENT") {
111691
112694
  return { blocked: [], unreadable: 0 };
@@ -111699,7 +112702,7 @@ function readFallbackRecords(agentsRoot, alreadySeen) {
111699
112702
  continue;
111700
112703
  let text;
111701
112704
  try {
111702
- text = readFileSync75(join89(agentsRoot, agent, FALLBACK_RECORD_NAME), "utf-8");
112705
+ text = readFileSync76(join90(agentsRoot, agent, FALLBACK_RECORD_NAME), "utf-8");
111703
112706
  } catch (err) {
111704
112707
  if (err?.code !== "ENOENT")
111705
112708
  unreadable++;
@@ -111746,7 +112749,7 @@ init_audit_reader();
111746
112749
  // src/scheduler/dispatch.ts
111747
112750
  init_merge();
111748
112751
  init_overlay_loader();
111749
- import { createHash as createHash15 } from "node:crypto";
112752
+ import { createHash as createHash16 } from "node:crypto";
111750
112753
  function collectScheduleEntries(config) {
111751
112754
  const out = [];
111752
112755
  const agentNames = Object.keys(config.agents).sort();
@@ -111766,7 +112769,7 @@ function collectScheduleEntries(config) {
111766
112769
  cron: entry.cron,
111767
112770
  ...typeof title === "string" && title.length > 0 ? { name: title } : {},
111768
112771
  ...entry.prompt !== undefined ? { prompt: entry.prompt } : {},
111769
- promptKey: createHash15("sha256").update(auditMaterial).digest("hex").slice(0, 12),
112772
+ promptKey: createHash16("sha256").update(auditMaterial).digest("hex").slice(0, 12),
111770
112773
  ...entry.topic !== undefined ? { topic: entry.topic } : {},
111771
112774
  ...entry.kind !== undefined ? { kind: entry.kind } : {},
111772
112775
  ...entry.model !== undefined ? { model: entry.model } : {},
@@ -111815,7 +112818,7 @@ import { homedir as homedir45 } from "node:os";
111815
112818
  // src/web/config-edit-plan.ts
111816
112819
  init_schema();
111817
112820
  var import_yaml17 = __toESM(require_dist(), 1);
111818
- import { readFileSync as readFileSync76 } from "node:fs";
112821
+ import { readFileSync as readFileSync77 } from "node:fs";
111819
112822
 
111820
112823
  class ConfigPlanError extends Error {
111821
112824
  }
@@ -111825,7 +112828,7 @@ function composeTransforms(...transforms) {
111825
112828
  function planConfigEdit(configPath, transform) {
111826
112829
  let before;
111827
112830
  try {
111828
- before = readFileSync76(configPath, "utf-8");
112831
+ before = readFileSync77(configPath, "utf-8");
111829
112832
  } catch (err) {
111830
112833
  throw new ConfigPlanError(`could not read config: ${err.message}`);
111831
112834
  }
@@ -111860,19 +112863,19 @@ import {
111860
112863
  } from "node:fs";
111861
112864
  import { spawnSync as spawnSync16 } from "node:child_process";
111862
112865
  import { tmpdir as tmpdir5 } from "node:os";
111863
- import { join as join91 } from "node:path";
112866
+ import { join as join92 } from "node:path";
111864
112867
 
111865
112868
  class ConfigDiffError extends Error {
111866
112869
  }
111867
112870
  function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "git") {
111868
112871
  if (before === after)
111869
112872
  return "";
111870
- const dir = mkdtemp(join91(tmpdir5(), "switchroom-config-diff-"));
112873
+ const dir = mkdtemp(join92(tmpdir5(), "switchroom-config-diff-"));
111871
112874
  try {
111872
- mkdirSync47(join91(dir, "cur"), { recursive: true });
111873
- mkdirSync47(join91(dir, "new"), { recursive: true });
111874
- writeFileSync32(join91(dir, "cur", name), before);
111875
- 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);
111876
112879
  const r = spawnSync16(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
111877
112880
  if (r.status === 0)
111878
112881
  return "";
@@ -111904,9 +112907,9 @@ function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "
111904
112907
 
111905
112908
  // src/web/hostd-config-propose.ts
111906
112909
  init_client5();
111907
- import { existsSync as existsSync84 } from "node:fs";
112910
+ import { existsSync as existsSync85 } from "node:fs";
111908
112911
  import { homedir as homedir44 } from "node:os";
111909
- import { join as join92 } from "node:path";
112912
+ import { join as join93 } from "node:path";
111910
112913
  var PROPOSE_TIMEOUT_MS = 61 * 60 * 1000;
111911
112914
  function resolveHostdOperatorSocket(env2 = process.env) {
111912
112915
  const override = env2.SWITCHROOM_HOSTD_OPERATOR_SOCKET;
@@ -111914,10 +112917,10 @@ function resolveHostdOperatorSocket(env2 = process.env) {
111914
112917
  return override;
111915
112918
  const candidates = [
111916
112919
  "/host-home/.switchroom/hostd/operator/sock",
111917
- join92(homedir44(), ".switchroom", "hostd", "operator", "sock")
112920
+ join93(homedir44(), ".switchroom", "hostd", "operator", "sock")
111918
112921
  ];
111919
112922
  for (const c of candidates) {
111920
- if (existsSync84(c))
112923
+ if (existsSync85(c))
111921
112924
  return c;
111922
112925
  }
111923
112926
  return null;
@@ -112125,7 +113128,7 @@ init_client2();
112125
113128
 
112126
113129
  // telegram-plugin/registry/turns-schema.ts
112127
113130
  import { chmodSync as chmodSync15 } from "fs";
112128
- import { join as join94 } from "path";
113131
+ import { join as join95 } from "path";
112129
113132
 
112130
113133
  // src/util/state-owner.ts
112131
113134
  init_atomic();
@@ -112133,28 +113136,28 @@ import {
112133
113136
  appendFileSync as appendFileSync4,
112134
113137
  closeSync as closeSync16,
112135
113138
  constants as constants2,
112136
- existsSync as existsSync85,
113139
+ existsSync as existsSync86,
112137
113140
  fchownSync as fchownSync2,
112138
113141
  fstatSync as fstatSync5,
112139
113142
  mkdirSync as mkdirSync48,
112140
113143
  openSync as openSync16,
112141
- readdirSync as readdirSync31,
112142
- statSync as statSync50,
113144
+ readdirSync as readdirSync32,
113145
+ statSync as statSync51,
112143
113146
  writeFileSync as writeFileSync33
112144
113147
  } from "node:fs";
112145
- 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";
112146
113149
  var NOFOLLOW = constants2.O_NOFOLLOW ?? 0;
112147
113150
  var ownerCache = new Map;
112148
113151
  var loggedFailure = false;
112149
113152
  function defaultLog(line) {
112150
113153
  process.stderr.write(line);
112151
113154
  }
112152
- function noteFailure(op, path7, err, log) {
113155
+ function noteFailure(op, path9, err, log) {
112153
113156
  if (loggedFailure)
112154
113157
  return;
112155
113158
  loggedFailure = true;
112156
113159
  const code = err.code ?? "";
112157
- 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.
112158
113161
  `);
112159
113162
  }
112160
113163
  function isMultiplyLinked(st) {
@@ -112168,7 +113171,7 @@ function resolveStateOwner(dir) {
112168
113171
  return cached;
112169
113172
  let owner = null;
112170
113173
  try {
112171
- const st = statSync50(dir);
113174
+ const st = statSync51(dir);
112172
113175
  owner = st.uid === 0 && st.gid === 0 ? null : { uid: st.uid, gid: st.gid };
112173
113176
  } catch {
112174
113177
  owner = null;
@@ -112176,13 +113179,13 @@ function resolveStateOwner(dir) {
112176
113179
  ownerCache.set(dir, owner);
112177
113180
  return owner;
112178
113181
  }
112179
- function adoptStateOwnership(path7, owner, log = defaultLog) {
112180
- const target = owner === undefined ? resolveStateOwner(dirname35(path7)) : owner;
113182
+ function adoptStateOwnership(path9, owner, log = defaultLog) {
113183
+ const target = owner === undefined ? resolveStateOwner(dirname35(path9)) : owner;
112181
113184
  if (!target)
112182
113185
  return;
112183
113186
  let fd = null;
112184
113187
  try {
112185
- fd = openSync16(path7, constants2.O_RDONLY | NOFOLLOW);
113188
+ fd = openSync16(path9, constants2.O_RDONLY | NOFOLLOW);
112186
113189
  const st = fstatSync5(fd);
112187
113190
  if (st.uid === target.uid && st.gid === target.gid)
112188
113191
  return;
@@ -112191,7 +113194,7 @@ function adoptStateOwnership(path7, owner, log = defaultLog) {
112191
113194
  fchownSync2(fd, target.uid, target.gid);
112192
113195
  } catch (err) {
112193
113196
  if (err.code !== "ENOENT") {
112194
- noteFailure("adopt", path7, err, log);
113197
+ noteFailure("adopt", path9, err, log);
112195
113198
  }
112196
113199
  } finally {
112197
113200
  if (fd !== null) {
@@ -112209,8 +113212,8 @@ function adoptSqliteOwnership(dbPath, log = defaultLog) {
112209
113212
  adoptStateOwnership(dbPath + suffix, owner, log);
112210
113213
  }
112211
113214
  }
112212
- function mkdirStateSync(path7, options = { recursive: true }, log = defaultLog) {
112213
- const first = mkdirSync48(path7, { ...options, recursive: true });
113215
+ function mkdirStateSync(path9, options = { recursive: true }, log = defaultLog) {
113216
+ const first = mkdirSync48(path9, { ...options, recursive: true });
112214
113217
  if (first === undefined)
112215
113218
  return;
112216
113219
  const owner = resolveStateOwner(dirname35(first));
@@ -112218,13 +113221,13 @@ function mkdirStateSync(path7, options = { recursive: true }, log = defaultLog)
112218
113221
  return;
112219
113222
  let current = first;
112220
113223
  adoptStateOwnership(current, owner, log);
112221
- const tail = relative4(first, path7);
113224
+ const tail = relative4(first, path9);
112222
113225
  if (tail === "" || tail.startsWith(".."))
112223
113226
  return;
112224
113227
  for (const part of tail.split(sep6)) {
112225
113228
  if (part === "")
112226
113229
  continue;
112227
- current = join93(current, part);
113230
+ current = join94(current, part);
112228
113231
  adoptStateOwnership(current, owner, log);
112229
113232
  }
112230
113233
  }
@@ -112301,20 +113304,20 @@ function applySchema(db) {
112301
113304
  }
112302
113305
  function openTurnsDb(agentDir) {
112303
113306
  const Database2 = loadDatabaseClass();
112304
- const dir = join94(agentDir, "telegram");
113307
+ const dir = join95(agentDir, "telegram");
112305
113308
  mkdirStateSync(dir, { recursive: true, mode: 448 });
112306
- const path7 = join94(dir, "registry.db");
112307
- const db = new Database2(path7, { create: true });
113309
+ const path9 = join95(dir, "registry.db");
113310
+ const db = new Database2(path9, { create: true });
112308
113311
  applySchema(db);
112309
113312
  try {
112310
- chmodSync15(path7, 420);
113313
+ chmodSync15(path9, 420);
112311
113314
  for (const suffix of ["-shm", "-wal"]) {
112312
113315
  try {
112313
- chmodSync15(path7 + suffix, 420);
113316
+ chmodSync15(path9 + suffix, 420);
112314
113317
  } catch {}
112315
113318
  }
112316
113319
  } catch {}
112317
- adoptSqliteOwnership(path7);
113320
+ adoptSqliteOwnership(path9);
112318
113321
  return db;
112319
113322
  }
112320
113323
  function mapRow(row) {
@@ -112516,7 +113519,7 @@ var DASHBOARD_CACHE_TTL = {
112516
113519
  };
112517
113520
  function readContextOccupancy(agentsDir, name) {
112518
113521
  try {
112519
- const raw = readFileSync77(resolve44(agentsDir, name, "context-occupancy.json"), "utf-8");
113522
+ const raw = readFileSync78(resolve45(agentsDir, name, "context-occupancy.json"), "utf-8");
112520
113523
  const s = JSON.parse(raw);
112521
113524
  if (typeof s?.occupancy !== "number")
112522
113525
  return null;
@@ -112527,7 +113530,7 @@ function readContextOccupancy(agentsDir, name) {
112527
113530
  }
112528
113531
  function readLastTurnAt(agentsDir, name) {
112529
113532
  try {
112530
- const db = openTurnsDb(resolve44(agentsDir, name));
113533
+ const db = openTurnsDb(resolve45(agentsDir, name));
112531
113534
  try {
112532
113535
  const turns = listTurnsForAgent(db, { limit: 1 });
112533
113536
  return turns.length > 0 ? turns[0].started_at : null;
@@ -112540,8 +113543,8 @@ function readLastTurnAt(agentsDir, name) {
112540
113543
  }
112541
113544
  function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now2 = Date.now()) {
112542
113545
  try {
112543
- const f = resolve44(agentsDir, name, "telegram", ".bridge-alive");
112544
- return now2 - statSync51(f).mtimeMs <= maxAgeMs;
113546
+ const f = resolve45(agentsDir, name, "telegram", ".bridge-alive");
113547
+ return now2 - statSync52(f).mtimeMs <= maxAgeMs;
112545
113548
  } catch {
112546
113549
  return false;
112547
113550
  }
@@ -112639,7 +113642,7 @@ function handleGetTurns(config, sessionId, limit) {
112639
113642
  const agentName = parsed.agentName;
112640
113643
  const hasThread = "threadId" in parsed;
112641
113644
  const agentsDir = resolveAgentsDir(config);
112642
- const agentDir = resolve44(agentsDir, agentName);
113645
+ const agentDir = resolve45(agentsDir, agentName);
112643
113646
  const chatId = config.agents?.[agentName]?.channels?.telegram?.chat_id ?? undefined;
112644
113647
  const db = openTurnsDb(agentDir);
112645
113648
  try {
@@ -112662,7 +113665,7 @@ function handleListThreadIds(config, agentName) {
112662
113665
  if (!chatId)
112663
113666
  return [];
112664
113667
  const agentsDir = resolveAgentsDir(config);
112665
- const db = openTurnsDb(resolve44(agentsDir, agentName));
113668
+ const db = openTurnsDb(resolve45(agentsDir, agentName));
112666
113669
  try {
112667
113670
  return listDistinctThreadIds(db, chatId);
112668
113671
  } finally {
@@ -112675,7 +113678,7 @@ function handleListThreadIds(config, agentName) {
112675
113678
  function handleGetSubagents(config, agentName, status) {
112676
113679
  try {
112677
113680
  const agentsDir = resolveAgentsDir(config);
112678
- const agentDir = resolve44(agentsDir, agentName);
113681
+ const agentDir = resolve45(agentsDir, agentName);
112679
113682
  const db = openTurnsDb(agentDir);
112680
113683
  try {
112681
113684
  applySubagentsSchema(db);
@@ -112932,7 +113935,7 @@ async function handleGetSystemHealth(config, home2) {
112932
113935
  };
112933
113936
  try {
112934
113937
  const logPath = defaultAuditLogPath2(home2);
112935
- if (existsSync86(logPath)) {
113938
+ if (existsSync87(logPath)) {
112936
113939
  hostd.auditLogPresent = true;
112937
113940
  const raw = readAuditRaw(logPath);
112938
113941
  hostd.recent = readAndFilter(raw, {}, 1000).filter((e) => !HOSTD_NOISE_OPS.has(e.op)).slice(-10);
@@ -113241,7 +114244,7 @@ async function handleGetSchedule(config, deps = {}) {
113241
114244
  const recentByAgent = {};
113242
114245
  const agents = new Set(entries.map((e) => e.agent));
113243
114246
  for (const agent of agents) {
113244
- const rows = readRecentFires(resolve44(agentsDir, agent, "scheduler.jsonl"));
114247
+ const rows = readRecentFires(resolve45(agentsDir, agent, "scheduler.jsonl"));
113245
114248
  if (rows.length > 0)
113246
114249
  recentByAgent[agent] = rows.slice(-10);
113247
114250
  }
@@ -113273,11 +114276,11 @@ async function handleGetApprovals() {
113273
114276
  return { reachable: true, decisions: sorted };
113274
114277
  }
113275
114278
  function brokerOperatorSocketPath(home2 = process.env.HOME || homedir45()) {
113276
- return join95(home2, ".switchroom", "broker-operator", "sock");
114279
+ return join96(home2, ".switchroom", "broker-operator", "sock");
113277
114280
  }
113278
114281
  function resolveBrokerOperatorSocket(home2 = process.env.HOME || homedir45()) {
113279
114282
  const p = brokerOperatorSocketPath(home2);
113280
- return existsSync86(p) ? p : null;
114283
+ return existsSync87(p) ? p : null;
113281
114284
  }
113282
114285
  async function handleGetGrants(deps = {}) {
113283
114286
  const socket = "socketPath" in deps ? deps.socketPath : resolveBrokerOperatorSocket();
@@ -113668,8 +114671,8 @@ async function handleGetSummary(deps, now2 = Date.now()) {
113668
114671
  init_fleet_health_read();
113669
114672
 
113670
114673
  // src/web/webhook-handler.ts
113671
- import { appendFileSync as appendFileSync5, existsSync as existsSync88, mkdirSync as mkdirSync49, readFileSync as readFileSync80, writeFileSync as writeFileSync34 } from "fs";
113672
- 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";
113673
114676
  import { homedir as homedir48 } from "os";
113674
114677
 
113675
114678
  // src/web/webhook-verify.ts
@@ -113837,7 +114840,7 @@ init_log_rotation();
113837
114840
  import net4 from "node:net";
113838
114841
  function forwardToGateway(socketPath, req, opts = {}) {
113839
114842
  const timeoutMs = opts.timeoutMs ?? 5000;
113840
- return new Promise((resolve46) => {
114843
+ return new Promise((resolve47) => {
113841
114844
  let settled = false;
113842
114845
  let buf = "";
113843
114846
  const done = (value) => {
@@ -113847,7 +114850,7 @@ function forwardToGateway(socketPath, req, opts = {}) {
113847
114850
  try {
113848
114851
  conn.destroy();
113849
114852
  } catch {}
113850
- resolve46(value);
114853
+ resolve47(value);
113851
114854
  };
113852
114855
  const conn = net4.createConnection(socketPath);
113853
114856
  conn.setEncoding("utf8");
@@ -113878,20 +114881,20 @@ function forwardToGateway(socketPath, req, opts = {}) {
113878
114881
  }
113879
114882
 
113880
114883
  // src/web/webhook-edge.ts
113881
- import { existsSync as existsSync87, readFileSync as readFileSync79 } from "fs";
113882
- import { join as join96 } from "path";
114884
+ import { existsSync as existsSync88, readFileSync as readFileSync80 } from "fs";
114885
+ import { join as join97 } from "path";
113883
114886
  import { homedir as homedir47 } from "os";
113884
114887
  import { timingSafeEqual as timingSafeEqual2 } from "crypto";
113885
114888
  var EDGE_HEADER = "x-switchroom-edge";
113886
114889
  function edgeSecretPath() {
113887
- return join96(homedir47(), ".switchroom", "webhook-edge-secret");
114890
+ return join97(homedir47(), ".switchroom", "webhook-edge-secret");
113888
114891
  }
113889
- function loadEdgeSecret(path7) {
113890
- const p = path7 ?? edgeSecretPath();
114892
+ function loadEdgeSecret(path9) {
114893
+ const p = path9 ?? edgeSecretPath();
113891
114894
  try {
113892
- if (!existsSync87(p))
114895
+ if (!existsSync88(p))
113893
114896
  return null;
113894
- const raw = readFileSync79(p, "utf-8").trim();
114897
+ const raw = readFileSync80(p, "utf-8").trim();
113895
114898
  return raw.length > 0 ? raw : null;
113896
114899
  } catch {
113897
114900
  return null;
@@ -113940,17 +114943,17 @@ function jsonReply(status, body, extraHeaders) {
113940
114943
  }
113941
114944
  var DEDUP_MAX = 1000;
113942
114945
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
113943
- function loadDedupFile(path7) {
114946
+ function loadDedupFile(path9) {
113944
114947
  try {
113945
- if (!existsSync88(path7))
114948
+ if (!existsSync89(path9))
113946
114949
  return {};
113947
- const raw = JSON.parse(readFileSync80(path7, "utf-8"));
114950
+ const raw = JSON.parse(readFileSync81(path9, "utf-8"));
113948
114951
  return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
113949
114952
  } catch {
113950
114953
  return {};
113951
114954
  }
113952
114955
  }
113953
- function saveDedupFile(path7, deliveries, now2) {
114956
+ function saveDedupFile(path9, deliveries, now2) {
113954
114957
  const pruned = {};
113955
114958
  for (const [id, ts] of Object.entries(deliveries)) {
113956
114959
  if (now2 - ts < DEDUP_TTL_MS)
@@ -113958,7 +114961,7 @@ function saveDedupFile(path7, deliveries, now2) {
113958
114961
  }
113959
114962
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
113960
114963
  const final = Object.fromEntries(sorted);
113961
- writeFileSync34(path7, JSON.stringify({ deliveries: final }), {
114964
+ writeFileSync34(path9, JSON.stringify({ deliveries: final }), {
113962
114965
  mode: 384
113963
114966
  });
113964
114967
  }
@@ -113966,8 +114969,8 @@ var agentDedupCache = new Map;
113966
114969
  function createFileDedupStore(resolveAgentDir2) {
113967
114970
  return {
113968
114971
  check(agent, deliveryId, now2) {
113969
- const telegramDir = join97(resolveAgentDir2(agent), "telegram");
113970
- const filePath = join97(telegramDir, "webhook-dedup.json");
114972
+ const telegramDir = join98(resolveAgentDir2(agent), "telegram");
114973
+ const filePath = join98(telegramDir, "webhook-dedup.json");
113971
114974
  if (!agentDedupCache.has(agent)) {
113972
114975
  agentDedupCache.set(agent, loadDedupFile(filePath));
113973
114976
  }
@@ -114019,7 +115022,7 @@ function shouldWriteThrottleIssue(agent, source, now2, windowMap) {
114019
115022
  return true;
114020
115023
  }
114021
115024
  function writeThrottleIssue(agent, source, now2, telegramDir, log) {
114022
- const issuesPath = join97(telegramDir, "issues.jsonl");
115025
+ const issuesPath = join98(telegramDir, "issues.jsonl");
114023
115026
  try {
114024
115027
  mkdirSync49(telegramDir, { recursive: true });
114025
115028
  const record = {
@@ -114044,7 +115047,7 @@ function writeThrottleIssue(agent, source, now2, telegramDir, log) {
114044
115047
  async function handleWebhookIngest(args, deps = {}) {
114045
115048
  const log = deps.log ?? ((s) => process.stderr.write(s));
114046
115049
  const now2 = (deps.now ?? Date.now)();
114047
- const resolveAgentDir2 = deps.resolveAgentDir ?? ((a) => join97(homedir48(), ".switchroom", "agents", a));
115050
+ const resolveAgentDir2 = deps.resolveAgentDir ?? ((a) => join98(homedir48(), ".switchroom", "agents", a));
114048
115051
  const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
114049
115052
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir2);
114050
115053
  if (!args.agentExists) {
@@ -114111,7 +115114,7 @@ async function handleWebhookIngest(args, deps = {}) {
114111
115114
  if (retryAfter !== null) {
114112
115115
  if (!args.viaGateway) {
114113
115116
  const agentDir2 = resolveAgentDir2(args.agent);
114114
- const telegramDir2 = join97(agentDir2, "telegram");
115117
+ const telegramDir2 = join98(agentDir2, "telegram");
114115
115118
  if (shouldWriteThrottleIssue(args.agent, source, now2)) {
114116
115119
  writeThrottleIssue(args.agent, source, now2, telegramDir2, log);
114117
115120
  }
@@ -114131,7 +115134,7 @@ async function handleWebhookIngest(args, deps = {}) {
114131
115134
  const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : source === "linear" ? String(payload.type ?? "unknown").toLowerCase() : args.source;
114132
115135
  const rendered = source === "github" ? renderGithubEvent(eventType, payload) : source === "linear" ? renderLinearEvent(eventType, payload) : renderGenericEvent(args.source, payload);
114133
115136
  if (args.viaGateway) {
114134
- const socketPath = join97(resolveAgentDir2(args.agent), "telegram", "webhook.sock");
115137
+ const socketPath = join98(resolveAgentDir2(args.agent), "telegram", "webhook.sock");
114135
115138
  const forward = deps.forwardFn ?? forwardToGateway;
114136
115139
  const deliveryId = source === "github" ? args.headers.get("x-github-delivery") ?? undefined : undefined;
114137
115140
  let resp;
@@ -114170,8 +115173,8 @@ async function handleWebhookIngest(args, deps = {}) {
114170
115173
  return jsonReply(202, { ok: true, recorded: true, ts: resp.ts });
114171
115174
  }
114172
115175
  const agentDir = resolveAgentDir2(args.agent);
114173
- const telegramDir = join97(agentDir, "telegram");
114174
- const logPath = join97(telegramDir, "webhook-events.jsonl");
115176
+ const telegramDir = join98(agentDir, "telegram");
115177
+ const logPath = join98(telegramDir, "webhook-events.jsonl");
114175
115178
  try {
114176
115179
  mkdirSync49(telegramDir, { recursive: true });
114177
115180
  rotateWebhookLogIfNeeded(logPath);
@@ -114200,9 +115203,9 @@ init_shipped_assets();
114200
115203
  // src/web/hermes-adapter.ts
114201
115204
  init_loader();
114202
115205
  import { createConnection as createConnection3 } from "node:net";
114203
- import { createHash as createHash16 } from "node:crypto";
114204
- import { join as join98, resolve as resolve46 } from "node:path";
114205
- 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";
114206
115209
 
114207
115210
  // src/agent-scheduler/channel-target.ts
114208
115211
  init_merge();
@@ -114246,8 +115249,8 @@ function agentLiveness(config, agentName) {
114246
115249
  const agentsDir = resolveAgentsDir(config);
114247
115250
  if (agentBridgeAlive(agentsDir, agentName))
114248
115251
  return "active";
114249
- const alive = join98(agentsDir, agentName, "telegram", ".bridge-alive");
114250
- if (existsSync89(alive))
115252
+ const alive = join99(agentsDir, agentName, "telegram", ".bridge-alive");
115253
+ if (existsSync90(alive))
114251
115254
  return "idle";
114252
115255
  return "offline";
114253
115256
  }
@@ -114355,11 +115358,11 @@ function resolveAgentChat(config, sessionId, agentsDir) {
114355
115358
  const threadId = topicId !== undefined ? topicId === null ? undefined : parseInt(topicId, 10) || undefined : channel.threadId;
114356
115359
  return { chatId: channel.chatId, threadId };
114357
115360
  }
114358
- const accessPath = resolve46(agentsDir, agentName, "telegram", "access.json");
114359
- if (!existsSync89(accessPath))
115361
+ const accessPath = resolve47(agentsDir, agentName, "telegram", "access.json");
115362
+ if (!existsSync90(accessPath))
114360
115363
  return null;
114361
115364
  try {
114362
- const raw = readFileSync81(accessPath, "utf-8");
115365
+ const raw = readFileSync82(accessPath, "utf-8");
114363
115366
  const access = JSON.parse(raw);
114364
115367
  const chatId = access.allowFrom?.[0];
114365
115368
  if (!chatId)
@@ -114370,8 +115373,8 @@ function resolveAgentChat(config, sessionId, agentsDir) {
114370
115373
  }
114371
115374
  }
114372
115375
  async function injectInbound(agentsDir, agentName, chatId, threadId, text, promptKey) {
114373
- const socketPath = resolve46(agentsDir, agentName, "telegram", "gateway.sock");
114374
- if (!existsSync89(socketPath)) {
115376
+ const socketPath = resolve47(agentsDir, agentName, "telegram", "gateway.sock");
115377
+ if (!existsSync90(socketPath)) {
114375
115378
  return { ok: false, error: `agent ${agentName} gateway socket not found \u2014 is it running?` };
114376
115379
  }
114377
115380
  const ts = Date.now();
@@ -114418,8 +115421,8 @@ async function injectInbound(agentsDir, agentName, chatId, threadId, text, promp
114418
115421
  });
114419
115422
  }
114420
115423
  function readHistoryDb(agentsDir, agentName, limit) {
114421
- const dbPath = resolve46(agentsDir, agentName, "telegram", "history.db");
114422
- if (!existsSync89(dbPath))
115424
+ const dbPath = resolve47(agentsDir, agentName, "telegram", "history.db");
115425
+ if (!existsSync90(dbPath))
114423
115426
  return null;
114424
115427
  try {
114425
115428
  let SqliteDatabase;
@@ -114448,7 +115451,7 @@ function readHistoryDb(agentsDir, agentName, limit) {
114448
115451
  }
114449
115452
  }
114450
115453
  function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs = 2000) {
114451
- const dbPath = resolve46(agentsDir, agentName, "telegram", "history.db");
115454
+ const dbPath = resolve47(agentsDir, agentName, "telegram", "history.db");
114452
115455
  let SqliteDatabase = null;
114453
115456
  try {
114454
115457
  const meta = import.meta;
@@ -114458,7 +115461,7 @@ function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs =
114458
115461
  }
114459
115462
  } catch {}
114460
115463
  let cursor = 0;
114461
- if (SqliteDatabase && existsSync89(dbPath)) {
115464
+ if (SqliteDatabase && existsSync90(dbPath)) {
114462
115465
  try {
114463
115466
  const db = new SqliteDatabase(dbPath, { readonly: true });
114464
115467
  try {
@@ -114470,7 +115473,7 @@ function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs =
114470
115473
  } catch {}
114471
115474
  }
114472
115475
  const timer = setInterval(() => {
114473
- if (!SqliteDatabase || !existsSync89(dbPath))
115476
+ if (!SqliteDatabase || !existsSync90(dbPath))
114474
115477
  return;
114475
115478
  try {
114476
115479
  const db = new SqliteDatabase(dbPath, { readonly: true });
@@ -115133,7 +116136,7 @@ async function onHermesMessage(ctx, raw) {
115133
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.`));
115134
116137
  break;
115135
116138
  }
115136
- 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);
115137
116140
  sendEvent(ctx, "message.start", sessionId, { prompt_key: promptKey });
115138
116141
  pendingPromptKeys(ctx).set(sessionId, promptKey);
115139
116142
  const result = await injectInbound(agentsDir, submitAgent, chat.chatId, chat.threadId, content, promptKey);
@@ -115329,9 +116332,9 @@ function resolveWebToken() {
115329
116332
  if (fromEnv && fromEnv.length > 0)
115330
116333
  return fromEnv;
115331
116334
  const home2 = process.env.HOME ?? homedir49();
115332
- const tokenPath = join99(home2, ".switchroom", "web-token");
115333
- if (existsSync90(tokenPath)) {
115334
- 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();
115335
116338
  if (existing.length > 0)
115336
116339
  return existing;
115337
116340
  }
@@ -115348,7 +116351,7 @@ function resolveWebToken() {
115348
116351
  return token;
115349
116352
  } catch (err) {
115350
116353
  if (err.code === "EEXIST") {
115351
- const existing = readFileSync82(tokenPath, "utf8").trim();
116354
+ const existing = readFileSync83(tokenPath, "utf8").trim();
115352
116355
  if (existing.length > 0)
115353
116356
  return existing;
115354
116357
  }
@@ -115421,14 +116424,14 @@ function checkWsAuth(req, token, server) {
115421
116424
  return presented !== null && constantTimeEqual(presented, token);
115422
116425
  }
115423
116426
  function loadWebhookSecrets() {
115424
- const path7 = join99(homedir49(), ".switchroom", "webhook-secrets.json");
115425
- if (!existsSync90(path7))
116427
+ const path9 = join100(homedir49(), ".switchroom", "webhook-secrets.json");
116428
+ if (!existsSync91(path9))
115426
116429
  return {};
115427
116430
  try {
115428
- const parsed = JSON.parse(readFileSync82(path7, "utf-8"));
116431
+ const parsed = JSON.parse(readFileSync83(path9, "utf-8"));
115429
116432
  return parsed && typeof parsed === "object" ? parsed : {};
115430
116433
  } catch (err) {
115431
- 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
115432
116435
  `);
115433
116436
  return {};
115434
116437
  }
@@ -115615,11 +116618,11 @@ function resolveUiDir(probe2 = {
115615
116618
  bundleDir: import.meta.dirname,
115616
116619
  execPath: process.execPath
115617
116620
  }) {
115618
- 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);
115619
116622
  }
115620
116623
  function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
115621
116624
  const uiDirRaw = resolveUiDir();
115622
- const uiDir = existsSync90(uiDirRaw) ? realpathSync7(uiDirRaw) : uiDirRaw;
116625
+ const uiDir = existsSync91(uiDirRaw) ? realpathSync7(uiDirRaw) : uiDirRaw;
115623
116626
  const token = resolveWebToken();
115624
116627
  const freshConfig = () => {
115625
116628
  if (!configPath)
@@ -115919,8 +116922,8 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
115919
116922
  })();
115920
116923
  }
115921
116924
  const filePath = resolveDashboardFilePath(pathname);
115922
- const fullPath = join99(uiDir, filePath);
115923
- if (!existsSync90(fullPath)) {
116925
+ const fullPath = join100(uiDir, filePath);
116926
+ if (!existsSync91(fullPath)) {
115924
116927
  return new Response("Not Found", { status: 404 });
115925
116928
  }
115926
116929
  let realFullPath;
@@ -115930,12 +116933,12 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
115930
116933
  return new Response("Not Found", { status: 404 });
115931
116934
  }
115932
116935
  const rel = relative5(uiDir, realFullPath);
115933
- if (rel.startsWith("..") || resolve47(uiDir, rel) !== realFullPath) {
116936
+ if (rel.startsWith("..") || resolve48(uiDir, rel) !== realFullPath) {
115934
116937
  return new Response("Forbidden", { status: 403 });
115935
116938
  }
115936
116939
  const ext = extname(realFullPath);
115937
116940
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
115938
- const content = readFileSync82(realFullPath);
116941
+ const content = readFileSync83(realFullPath);
115939
116942
  const headers = { "Content-Type": contentType };
115940
116943
  const cacheControl = dashboardCacheControl(ext);
115941
116944
  if (cacheControl)
@@ -116058,13 +117061,13 @@ init_helpers();
116058
117061
  init_loader();
116059
117062
 
116060
117063
  // src/web/startup-guard.ts
116061
- 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";
116062
117065
  import { dirname as dirname37 } from "node:path";
116063
117066
  function detectConfigMountFault(configPath, deps = {}) {
116064
- const stat = deps.stat ?? ((p) => statSync52(p));
117067
+ const stat2 = deps.stat ?? ((p) => statSync53(p));
116065
117068
  let st;
116066
117069
  try {
116067
- st = stat(configPath);
117070
+ st = stat2(configPath);
116068
117071
  } catch {
116069
117072
  return null;
116070
117073
  }
@@ -116094,9 +117097,9 @@ function nextCrashBackoff(prior, now2, options = {}) {
116094
117097
  }
116095
117098
  function readCrashState(statePath) {
116096
117099
  try {
116097
- if (!existsSync91(statePath))
117100
+ if (!existsSync92(statePath))
116098
117101
  return null;
116099
- const raw = JSON.parse(readFileSync83(statePath, "utf-8"));
117102
+ const raw = JSON.parse(readFileSync84(statePath, "utf-8"));
116100
117103
  if (typeof raw.count !== "number" || typeof raw.lastTs !== "number")
116101
117104
  return null;
116102
117105
  return { count: raw.count, lastTs: raw.lastTs };
@@ -116112,7 +117115,7 @@ function writeCrashState(statePath, state) {
116112
117115
  }
116113
117116
  function clearCrashState(statePath) {
116114
117117
  try {
116115
- if (existsSync91(statePath))
117118
+ if (existsSync92(statePath))
116116
117119
  unlinkSync18(statePath);
116117
117120
  } catch {}
116118
117121
  }
@@ -116135,7 +117138,7 @@ async function handleWebStartupFailure(err, configPath, stateFile) {
116135
117138
  throw err;
116136
117139
  }
116137
117140
  function webCrashStatePath() {
116138
- return join100(homedir50(), ".switchroom", "web", ".crashloop-state.json");
117141
+ return join101(homedir50(), ".switchroom", "web", ".crashloop-state.json");
116139
117142
  }
116140
117143
  function registerWebCommand(program3) {
116141
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) => {
@@ -116202,9 +117205,9 @@ init_embedded_examples();
116202
117205
  init_atomic();
116203
117206
  init_loader();
116204
117207
  init_scaffold();
116205
- 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";
116206
117209
  import { execFileSync as execFileSync29 } from "node:child_process";
116207
- import { resolve as resolve48, dirname as dirname38 } from "node:path";
117210
+ import { resolve as resolve49, dirname as dirname38 } from "node:path";
116208
117211
  init_state();
116209
117212
  init_vault();
116210
117213
  init_manager();
@@ -116619,7 +117622,7 @@ ${status} ${source_default.bold(`Step ${num3}:`)} ${title}`);
116619
117622
  init_source();
116620
117623
  init_loader();
116621
117624
  init_hindsight();
116622
- import { existsSync as existsSync92 } from "node:fs";
117625
+ import { existsSync as existsSync93 } from "node:fs";
116623
117626
  var HINDSIGHT_READY_RETRIES = 5;
116624
117627
  var HINDSIGHT_READY_INTERVAL_MS = 1000;
116625
117628
  async function resolveLiteLLMForHindsight2(config, deps = {}) {
@@ -116656,7 +117659,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, d
116656
117659
  try {
116657
117660
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
116658
117661
  const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
116659
- if (passphrase && existsSync92(vaultPath)) {
117662
+ if (passphrase && existsSync93(vaultPath)) {
116660
117663
  const { getStringSecret: getStringSecret2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
116661
117664
  const existing = getStringSecret2(passphrase, vaultPath, "hindsight-api-key");
116662
117665
  if (existing) {
@@ -116926,7 +117929,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, d
116926
117929
  function writeSwitchroomYaml(configPath, text) {
116927
117930
  let mode = 420;
116928
117931
  try {
116929
- mode = statSync53(configPath).mode & 511;
117932
+ mode = statSync54(configPath).mode & 511;
116930
117933
  } catch {}
116931
117934
  writeConfigFileSync(configPath, text, mode);
116932
117935
  }
@@ -117025,7 +118028,7 @@ async function stepConfigFile(configPath, nonInteractive) {
117025
118028
  existingConfig = null;
117026
118029
  }
117027
118030
  }
117028
- if (existingConfig && existsSync93(existingConfig)) {
118031
+ if (existingConfig && existsSync94(existingConfig)) {
117029
118032
  if (!nonInteractive) {
117030
118033
  const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
117031
118034
  if (!useExisting) {
@@ -117037,9 +118040,9 @@ async function stepConfigFile(configPath, nonInteractive) {
117037
118040
  console.log(source_default.green(` ${STEP_DONE} Config loaded`) + source_default.gray(` (${Object.keys(config.agents).length} agents)`));
117038
118041
  const hasExplicitTimezone = config.switchroom?.timezone !== undefined || config.defaults?.timezone !== undefined;
117039
118042
  if (!hasExplicitTimezone) {
117040
- await writeDetectedTimezone(resolve48(existingConfig), nonInteractive);
118043
+ await writeDetectedTimezone(resolve49(existingConfig), nonInteractive);
117041
118044
  }
117042
- return { config, configPath: resolve48(existingConfig) };
118045
+ return { config, configPath: resolve49(existingConfig) };
117043
118046
  }
117044
118047
  return await copyExampleConfig2(nonInteractive);
117045
118048
  }
@@ -117066,12 +118069,12 @@ async function copyExampleConfig2(nonInteractive) {
117066
118069
  await writeDetectedTimezone(destFile, nonInteractive);
117067
118070
  const config = loadConfig(destFile);
117068
118071
  console.log(source_default.green(` ${STEP_DONE} Config loaded`) + source_default.gray(` (${Object.keys(config.agents).length} agents)`));
117069
- return { config, configPath: resolve48(destFile) };
118072
+ return { config, configPath: resolve49(destFile) };
117070
118073
  }
117071
118074
  async function writeDetectedTimezone(destFile, nonInteractive, detect = detectServerTimezone, prompt = ask) {
117072
118075
  const detected = detect();
117073
118076
  if (detected !== undefined && detected !== "UTC" && isValidTimezone(detected)) {
117074
- const before2 = readFileSync84(destFile, "utf-8");
118077
+ const before2 = readFileSync85(destFile, "utf-8");
117075
118078
  const after2 = setSwitchroomTimezone(before2, detected);
117076
118079
  if (after2 !== before2)
117077
118080
  writeSwitchroomYaml(destFile, after2);
@@ -117092,7 +118095,7 @@ async function writeDetectedTimezone(destFile, nonInteractive, detect = detectSe
117092
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."));
117093
118096
  return;
117094
118097
  }
117095
- const before = readFileSync84(destFile, "utf-8");
118098
+ const before = readFileSync85(destFile, "utf-8");
117096
118099
  const after = setSwitchroomTimezone(before, zone);
117097
118100
  if (after !== before)
117098
118101
  writeSwitchroomYaml(destFile, after);
@@ -117171,7 +118174,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
117171
118174
  try {
117172
118175
  const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
117173
118176
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
117174
- if (existsSync93(vaultPath)) {
118177
+ if (existsSync94(vaultPath)) {
117175
118178
  const secrets = openVault2(passphrase, vaultPath);
117176
118179
  const key = rawToken.replace("vault:", "");
117177
118180
  const entry = secrets[key];
@@ -117199,7 +118202,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
117199
118202
  async function storeTokenInVault(config, vaultRef, token) {
117200
118203
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
117201
118204
  const key = vaultRef.replace("vault:", "");
117202
- if (!existsSync93(vaultPath)) {
118205
+ if (!existsSync94(vaultPath)) {
117203
118206
  console.log(source_default.gray(" Creating encrypted vault..."));
117204
118207
  let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
117205
118208
  if (!passphrase) {
@@ -117482,13 +118485,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
117482
118485
  return;
117483
118486
  }
117484
118487
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
117485
- if (!existsSync93(vaultPath)) {
118488
+ if (!existsSync94(vaultPath)) {
117486
118489
  console.log(source_default.gray(" Skipping (vault not created yet)."));
117487
118490
  return;
117488
118491
  }
117489
118492
  const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
117490
118493
  const credPath = resolvePath(credPathRaw);
117491
- if (config.vault?.broker?.autoUnlock === true && existsSync93(credPath)) {
118494
+ if (config.vault?.broker?.autoUnlock === true && existsSync94(credPath)) {
117492
118495
  console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
117493
118496
  return;
117494
118497
  }
@@ -117566,10 +118569,10 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
117566
118569
  }
117567
118570
  }
117568
118571
  function persistApprovalAuthTelegramId(configPath) {
117569
- if (!existsSync93(configPath)) {
118572
+ if (!existsSync94(configPath)) {
117570
118573
  throw new ConfigError(`Cannot persist vault.broker.approvalAuth: config not found at ${configPath}`);
117571
118574
  }
117572
- const content = readFileSync84(configPath, "utf-8");
118575
+ const content = readFileSync85(configPath, "utf-8");
117573
118576
  const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
117574
118577
  if (result.kind === "rewritten") {
117575
118578
  writeSwitchroomYaml(configPath, result.content);
@@ -117577,10 +118580,10 @@ function persistApprovalAuthTelegramId(configPath) {
117577
118580
  return result.kind;
117578
118581
  }
117579
118582
  function persistDangerousMode(config, configPath) {
117580
- if (!existsSync93(configPath)) {
118583
+ if (!existsSync94(configPath)) {
117581
118584
  throw new ConfigError(`Cannot persist dangerous_mode: config not found at ${configPath}`);
117582
118585
  }
117583
- let content = readFileSync84(configPath, "utf-8");
118586
+ let content = readFileSync85(configPath, "utf-8");
117584
118587
  const agentNames = Object.keys(config.agents);
117585
118588
  for (const name of agentNames) {
117586
118589
  const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
@@ -117620,7 +118623,7 @@ async function stepOnboardingGuidance(config, nonInteractive) {
117620
118623
  const agentNames = Object.keys(config.agents);
117621
118624
  let allAuthenticated = true;
117622
118625
  for (const name of agentNames) {
117623
- const agentDir = resolve48(agentsDir, name);
118626
+ const agentDir = resolve49(agentsDir, name);
117624
118627
  const status = getAuthStatus(name, agentDir);
117625
118628
  if (status.authenticated) {
117626
118629
  console.log(` ${source_default.green("OK")} ${source_default.bold(name)}` + source_default.gray(` - authenticated (expires: ${status.timeUntilExpiry ?? "unknown"})`));
@@ -117716,7 +118719,7 @@ async function stepVerification(config, nonInteractive, deps = {}, expect = {})
117716
118719
  const findings = [];
117717
118720
  let applyPending = false;
117718
118721
  if (!nonInteractive && agentNames.length > 0) {
117719
- const composeExists = (deps.composeFileExists ?? (() => existsSync93(composeFilePath())))();
118722
+ const composeExists = (deps.composeFileExists ?? (() => existsSync94(composeFilePath())))();
117720
118723
  if (!composeExists) {
117721
118724
  applyPending = true;
117722
118725
  log(source_default.gray(" Not started: `switchroom apply` has not generated the compose file yet."));
@@ -117813,9 +118816,9 @@ init_source();
117813
118816
  init_loader();
117814
118817
  init_lifecycle();
117815
118818
  init_compose_env();
117816
- 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";
117817
118820
  import { spawnSync as spawnSync20 } from "node:child_process";
117818
- import { join as join101, dirname as dirname42 } from "node:path";
118821
+ import { join as join102, dirname as dirname42 } from "node:path";
117819
118822
  import { homedir as homedir51 } from "node:os";
117820
118823
 
117821
118824
  // src/cli/release-yaml.ts
@@ -117876,7 +118879,7 @@ init_operator_uid();
117876
118879
  init_atomic();
117877
118880
 
117878
118881
  // src/cli/preflight-mounts.ts
117879
- import { statSync as statSync54 } from "node:fs";
118882
+ import { statSync as statSync55 } from "node:fs";
117880
118883
  function parseHostBindSources(composeText) {
117881
118884
  const out = [];
117882
118885
  const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
@@ -117903,7 +118906,7 @@ function parseHostBindSources(composeText) {
117903
118906
  return out;
117904
118907
  }
117905
118908
  function validateBindSources(composeText, deps = {}) {
117906
- const stat = deps.stat ?? ((p) => statSync54(p));
118909
+ const stat2 = deps.stat ?? ((p) => statSync55(p));
117907
118910
  const sources = parseHostBindSources(composeText);
117908
118911
  const issues = [];
117909
118912
  const seen = new Set;
@@ -117915,7 +118918,7 @@ function validateBindSources(composeText, deps = {}) {
117915
118918
  checked++;
117916
118919
  let st = null;
117917
118920
  try {
117918
- st = stat(source);
118921
+ st = stat2(source);
117919
118922
  } catch {
117920
118923
  st = null;
117921
118924
  }
@@ -117949,18 +118952,18 @@ init_self_update();
117949
118952
  init_shipped_assets();
117950
118953
 
117951
118954
  // src/cli/self-update-io.ts
117952
- import { createHash as createHash17 } from "node:crypto";
118955
+ import { createHash as createHash18 } from "node:crypto";
117953
118956
  import {
117954
118957
  chmodSync as chmodSync16,
117955
118958
  closeSync as closeSync18,
117956
118959
  copyFileSync as copyFileSync12,
117957
118960
  createWriteStream,
117958
- existsSync as existsSync94,
118961
+ existsSync as existsSync95,
117959
118962
  lstatSync as lstatSync13,
117960
118963
  mkdirSync as mkdirSync53,
117961
118964
  openSync as openSync18,
117962
- readdirSync as readdirSync32,
117963
- readFileSync as readFileSync85,
118965
+ readdirSync as readdirSync33,
118966
+ readFileSync as readFileSync86,
117964
118967
  readSync as readSync6,
117965
118968
  renameSync as renameSync23,
117966
118969
  rmSync as rmSync18,
@@ -117999,9 +119002,9 @@ function defaultSelfUpdateIO() {
117999
119002
  throw new Error(`empty response body for ${url}`);
118000
119003
  await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
118001
119004
  },
118002
- sha256File(path7) {
118003
- const hash2 = createHash17("sha256");
118004
- const fd = openSync18(path7, "r");
119005
+ sha256File(path9) {
119006
+ const hash2 = createHash18("sha256");
119007
+ const fd = openSync18(path9, "r");
118005
119008
  try {
118006
119009
  const buf = Buffer.allocUnsafe(1 << 20);
118007
119010
  for (;; ) {
@@ -118015,8 +119018,8 @@ function defaultSelfUpdateIO() {
118015
119018
  }
118016
119019
  return hash2.digest("hex");
118017
119020
  },
118018
- probeBinary(path7) {
118019
- const r = spawnSync18(path7, ["--version"], {
119021
+ probeBinary(path9) {
119022
+ const r = spawnSync18(path9, ["--version"], {
118020
119023
  encoding: "utf-8",
118021
119024
  timeout: 30000,
118022
119025
  env: { ...process.env, SWITCHROOM_SELF_UPDATED: "" }
@@ -118056,24 +119059,24 @@ function defaultSelfUpdateIO() {
118056
119059
  copyFile(src, dest) {
118057
119060
  copyFileSync12(src, dest);
118058
119061
  },
118059
- chmodExec(path7) {
118060
- chmodSync16(path7, 493);
119062
+ chmodExec(path9) {
119063
+ chmodSync16(path9, 493);
118061
119064
  },
118062
119065
  rename(src, dest) {
118063
119066
  renameSync23(src, dest);
118064
119067
  },
118065
- remove(path7) {
118066
- rmSync18(path7, { force: true });
119068
+ remove(path9) {
119069
+ rmSync18(path9, { force: true });
118067
119070
  },
118068
- exists(path7) {
118069
- return existsSync94(path7);
119071
+ exists(path9) {
119072
+ return existsSync95(path9);
118070
119073
  },
118071
- dirname(path7) {
118072
- return dirname39(path7);
119074
+ dirname(path9) {
119075
+ return dirname39(path9);
118073
119076
  },
118074
- readText(path7) {
119077
+ readText(path9) {
118075
119078
  try {
118076
- return readFileSync85(path7, "utf-8");
119079
+ return readFileSync86(path9, "utf-8");
118077
119080
  } catch {
118078
119081
  return null;
118079
119082
  }
@@ -118093,19 +119096,19 @@ function defaultSelfUpdateIO() {
118093
119096
  symlink(target, linkPath) {
118094
119097
  symlinkSync5(target, linkPath);
118095
119098
  },
118096
- isSymlink(path7) {
119099
+ isSymlink(path9) {
118097
119100
  try {
118098
- return lstatSync13(path7).isSymbolicLink();
119101
+ return lstatSync13(path9).isSymbolicLink();
118099
119102
  } catch {
118100
119103
  return false;
118101
119104
  }
118102
119105
  },
118103
- removeTree(path7) {
118104
- rmSync18(path7, { recursive: true, force: true });
119106
+ removeTree(path9) {
119107
+ rmSync18(path9, { recursive: true, force: true });
118105
119108
  },
118106
119109
  listDir(dir) {
118107
119110
  try {
118108
- return readdirSync32(dir);
119111
+ return readdirSync33(dir);
118109
119112
  } catch {
118110
119113
  return [];
118111
119114
  }
@@ -118120,7 +119123,7 @@ init_install_cron();
118120
119123
 
118121
119124
  // src/memory/mm-refresh-cron.ts
118122
119125
  import { execFileSync as execFileSync30 } from "node:child_process";
118123
- 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";
118124
119127
  import { dirname as dirname40 } from "node:path";
118125
119128
  var CRON_PATH3 = "/etc/cron.d/mental-model-refresh";
118126
119129
  var CRON_SCHEDULE2 = "27 4 * * *";
@@ -118137,20 +119140,20 @@ function renderCron3(opts) {
118137
119140
  `;
118138
119141
  }
118139
119142
  function installCron3(opts) {
118140
- const path7 = opts.path ?? CRON_PATH3;
119143
+ const path9 = opts.path ?? CRON_PATH3;
118141
119144
  const content = renderCron3(opts);
118142
- if (existsSync95(path7)) {
119145
+ if (existsSync96(path9)) {
118143
119146
  try {
118144
- if (readFileSync86(path7, "utf8") === content) {
118145
- return { status: "unchanged", path: path7, content };
119147
+ if (readFileSync87(path9, "utf8") === content) {
119148
+ return { status: "unchanged", path: path9, content };
118146
119149
  }
118147
119150
  } catch {}
118148
119151
  }
118149
- mkdirSync54(dirname40(path7), { recursive: true });
118150
- const tmp = `${path7}.${process.pid}.tmp`;
119152
+ mkdirSync54(dirname40(path9), { recursive: true });
119153
+ const tmp = `${path9}.${process.pid}.tmp`;
118151
119154
  writeFileSync37(tmp, content, { mode: 420 });
118152
- renameSync24(tmp, path7);
118153
- return { status: "installed", path: path7, content };
119155
+ renameSync24(tmp, path9);
119156
+ return { status: "installed", path: path9, content };
118154
119157
  }
118155
119158
  var defaultIdResolver3 = (user) => {
118156
119159
  const uid = Number(execFileSync30("id", ["-u", user], { encoding: "utf8" }).trim());
@@ -118161,27 +119164,27 @@ var defaultIdResolver3 = (user) => {
118161
119164
  return { uid, gid };
118162
119165
  };
118163
119166
  function ensureLogFile3(opts) {
118164
- const path7 = opts.path ?? CRON_LOG_PATH3;
118165
- if (existsSync95(path7))
118166
- return { status: "unchanged", path: path7 };
119167
+ const path9 = opts.path ?? CRON_LOG_PATH3;
119168
+ if (existsSync96(path9))
119169
+ return { status: "unchanged", path: path9 };
118167
119170
  const { uid, gid } = (opts.resolveIds ?? defaultIdResolver3)(opts.user);
118168
- mkdirSync54(dirname40(path7), { recursive: true });
118169
- writeFileSync37(path7, "", { mode: 420, flag: "a" });
119171
+ mkdirSync54(dirname40(path9), { recursive: true });
119172
+ writeFileSync37(path9, "", { mode: 420, flag: "a" });
118170
119173
  const isRoot = process.getuid?.() === 0;
118171
119174
  let alreadyOwned = false;
118172
119175
  try {
118173
- const st = statSync55(path7);
119176
+ const st = statSync56(path9);
118174
119177
  alreadyOwned = st.uid === uid && st.gid === gid;
118175
119178
  } catch {}
118176
119179
  if (isRoot && !alreadyOwned) {
118177
119180
  try {
118178
- chownSync12(path7, uid, gid);
119181
+ chownSync12(path9, uid, gid);
118179
119182
  } catch (e) {
118180
119183
  if (e.code !== "EPERM")
118181
119184
  throw e;
118182
119185
  }
118183
119186
  }
118184
- return { status: "created", path: path7 };
119187
+ return { status: "created", path: path9 };
118185
119188
  }
118186
119189
  function renderLogrotate3(opts) {
118187
119190
  return `${opts.logPath} {
@@ -118197,20 +119200,20 @@ function renderLogrotate3(opts) {
118197
119200
  `;
118198
119201
  }
118199
119202
  function installLogrotate3(opts) {
118200
- const path7 = opts.path ?? LOGROTATE_PATH3;
119203
+ const path9 = opts.path ?? LOGROTATE_PATH3;
118201
119204
  const content = renderLogrotate3({ logPath: opts.logPath ?? CRON_LOG_PATH3, user: opts.user });
118202
- if (existsSync95(path7)) {
119205
+ if (existsSync96(path9)) {
118203
119206
  try {
118204
- if (readFileSync86(path7, "utf8") === content) {
118205
- return { status: "unchanged", path: path7, content };
119207
+ if (readFileSync87(path9, "utf8") === content) {
119208
+ return { status: "unchanged", path: path9, content };
118206
119209
  }
118207
119210
  } catch {}
118208
119211
  }
118209
- mkdirSync54(dirname40(path7), { recursive: true });
118210
- const tmp = `${path7}.${process.pid}.tmp`;
119212
+ mkdirSync54(dirname40(path9), { recursive: true });
119213
+ const tmp = `${path9}.${process.pid}.tmp`;
118211
119214
  writeFileSync37(tmp, content, { mode: 420 });
118212
- renameSync24(tmp, path7);
118213
- return { status: "installed", path: path7, content };
119215
+ renameSync24(tmp, path9);
119216
+ return { status: "installed", path: path9, content };
118214
119217
  }
118215
119218
  function parseCronUser3(content) {
118216
119219
  for (const line of content.split(`
@@ -118227,20 +119230,20 @@ function parseCronUser3(content) {
118227
119230
  return null;
118228
119231
  }
118229
119232
  function reconcileCron3(opts) {
118230
- const path7 = opts.path ?? CRON_PATH3;
118231
- if (!existsSync95(path7))
118232
- return { status: "absent", path: path7 };
119233
+ const path9 = opts.path ?? CRON_PATH3;
119234
+ if (!existsSync96(path9))
119235
+ return { status: "absent", path: path9 };
118233
119236
  let user = opts.fallbackUser;
118234
119237
  try {
118235
- user = parseCronUser3(readFileSync86(path7, "utf8")) ?? opts.fallbackUser;
119238
+ user = parseCronUser3(readFileSync87(path9, "utf8")) ?? opts.fallbackUser;
118236
119239
  } catch {}
118237
119240
  if (!user) {
118238
- 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`);
118239
119242
  }
118240
- const r = installCron3({ user, binary: opts.binary, path: path7 });
119243
+ const r = installCron3({ user, binary: opts.binary, path: path9 });
118241
119244
  return {
118242
119245
  status: r.status === "installed" ? "reconciled" : "unchanged",
118243
- path: path7,
119246
+ path: path9,
118244
119247
  content: r.content
118245
119248
  };
118246
119249
  }
@@ -118250,7 +119253,7 @@ init_install_cron2();
118250
119253
 
118251
119254
  // src/host-cli-self-heal/install-timer.ts
118252
119255
  import { spawnSync as spawnSync19 } from "node:child_process";
118253
- 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";
118254
119257
  import { dirname as dirname41 } from "node:path";
118255
119258
  var SERVICE_PATH = "/etc/systemd/system/switchroom-self-heal.service";
118256
119259
  var TIMER_PATH = "/etc/systemd/system/switchroom-self-heal.timer";
@@ -118296,22 +119299,22 @@ function renderTimer() {
118296
119299
  ` + `WantedBy=timers.target
118297
119300
  `;
118298
119301
  }
118299
- function writeUnitIdempotent(path7, content) {
118300
- if (existsSync96(path7)) {
119302
+ function writeUnitIdempotent(path9, content) {
119303
+ if (existsSync97(path9)) {
118301
119304
  try {
118302
- if (readFileSync87(path7, "utf8") === content)
119305
+ if (readFileSync88(path9, "utf8") === content)
118303
119306
  return "unchanged";
118304
119307
  } catch {}
118305
119308
  }
118306
- mkdirSync55(dirname41(path7), { recursive: true });
118307
- const tmp = `${path7}.${process.pid}.tmp`;
119309
+ mkdirSync55(dirname41(path9), { recursive: true });
119310
+ const tmp = `${path9}.${process.pid}.tmp`;
118308
119311
  writeFileSync38(tmp, content, { mode: 420 });
118309
- renameSync25(tmp, path7);
119312
+ renameSync25(tmp, path9);
118310
119313
  return "written";
118311
119314
  }
118312
119315
  function homeFromPasswd2(user) {
118313
119316
  try {
118314
- for (const line of readFileSync87("/etc/passwd", "utf8").split(`
119317
+ for (const line of readFileSync88("/etc/passwd", "utf8").split(`
118315
119318
  `)) {
118316
119319
  const f = line.split(":");
118317
119320
  if (f[0] === user && f[5])
@@ -118328,7 +119331,7 @@ function resolveOperatorUser(env2) {
118328
119331
  }
118329
119332
  function installSelfHealTimer(deps = {}) {
118330
119333
  const env2 = deps.env ?? process.env;
118331
- const systemdBooted = deps.systemdBooted ?? (() => existsSync96(SYSTEMD_MARKER));
119334
+ const systemdBooted = deps.systemdBooted ?? (() => existsSync97(SYSTEMD_MARKER));
118332
119335
  const geteuid = deps.geteuid ?? (() => typeof process.geteuid === "function" ? process.geteuid() : undefined);
118333
119336
  const homeForUser = deps.homeForUser ?? homeFromPasswd2;
118334
119337
  const servicePath = deps.servicePath ?? SERVICE_PATH;
@@ -118389,22 +119392,22 @@ function installSelfHealTimer(deps = {}) {
118389
119392
  // src/cli/update.ts
118390
119393
  function defaultPersistPin(configPath) {
118391
119394
  return (pin) => {
118392
- const path7 = configPath ?? findConfigFile();
118393
- const before = readFileSync88(path7, "utf8");
119395
+ const path9 = configPath ?? findConfigFile();
119396
+ const before = readFileSync89(path9, "utf8");
118394
119397
  const after = setReleasePinInConfig(before, pin);
118395
119398
  if (after === before)
118396
119399
  return;
118397
- writeConfigFileSync(path7, after, statSync56(path7).mode & 511);
119400
+ writeConfigFileSync(path9, after, statSync57(path9).mode & 511);
118398
119401
  try {
118399
119402
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
118400
119403
  const uid = resolveOperatorUid();
118401
119404
  if (uid !== undefined)
118402
- chownSync13(path7, uid, uid);
119405
+ chownSync13(path9, uid, uid);
118403
119406
  }
118404
119407
  } catch {}
118405
119408
  };
118406
119409
  }
118407
- var DEFAULT_COMPOSE_PATH2 = join101(homedir51(), ".switchroom", "compose", "docker-compose.yml");
119410
+ var DEFAULT_COMPOSE_PATH2 = join102(homedir51(), ".switchroom", "compose", "docker-compose.yml");
118408
119411
  var PACKAGED_INSTALL_KINDS = new Set([
118409
119412
  "static-binary",
118410
119413
  "npm-global",
@@ -118413,9 +119416,9 @@ var PACKAGED_INSTALL_KINDS = new Set([
118413
119416
  function runningFromSwitchroomCheckout(scriptPath) {
118414
119417
  let dir = dirname42(scriptPath);
118415
119418
  for (let i2 = 0;i2 < 12; i2++) {
118416
- if (existsSync97(join101(dir, ".git"))) {
119419
+ if (existsSync98(join102(dir, ".git"))) {
118417
119420
  try {
118418
- const pkg = JSON.parse(readFileSync88(join101(dir, "package.json"), "utf-8"));
119421
+ const pkg = JSON.parse(readFileSync89(join102(dir, "package.json"), "utf-8"));
118419
119422
  if (pkg.name === "switchroom")
118420
119423
  return true;
118421
119424
  } catch {}
@@ -118482,7 +119485,7 @@ function planUpdate(opts) {
118482
119485
  bundleDir: import.meta.dirname,
118483
119486
  execPath: process.execPath,
118484
119487
  scriptPath,
118485
- inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync97("/.dockerenv")
119488
+ inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync98("/.dockerenv")
118486
119489
  });
118487
119490
  if (detection.kind !== "static-binary") {
118488
119491
  say(source_default.gray(` host CLI not self-updated: ${detection.reason}
@@ -118565,7 +119568,7 @@ function planUpdate(opts) {
118565
119568
  steps.push({
118566
119569
  name: "pull-images",
118567
119570
  description: "Pull broker / kernel / agent images from GHCR",
118568
- 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,
118569
119572
  run: () => {
118570
119573
  const r = runner("docker", [
118571
119574
  "compose",
@@ -118822,13 +119825,13 @@ function planUpdate(opts) {
118822
119825
  bundleDir: import.meta.dirname,
118823
119826
  execPath: process.execPath
118824
119827
  });
118825
- const dest = join101(homedir51(), ".switchroom", "skills", "_bundled");
119828
+ const dest = join102(homedir51(), ".switchroom", "skills", "_bundled");
118826
119829
  if (resolution.path === null) {
118827
119830
  const detection = detectInstallKind(opts.installProbe ?? {
118828
119831
  bundleDir: import.meta.dirname,
118829
119832
  execPath: process.execPath,
118830
119833
  scriptPath,
118831
- inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync97("/.dockerenv")
119834
+ inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync98("/.dockerenv")
118832
119835
  });
118833
119836
  const detail = `no shipped skills/ payload found for this install ` + `(${detection.kind}; ${describeShippedAssetSearch(resolution)})`;
118834
119837
  if (PACKAGED_INSTALL_KINDS.has(detection.kind)) {
@@ -118867,11 +119870,11 @@ function planUpdate(opts) {
118867
119870
  run: () => {
118868
119871
  if (opts.syncBundledSkillsFn)
118869
119872
  return;
118870
- const dest = join101(homedir51(), ".switchroom", "skills", "_bundled");
118871
- if (!existsSync97(dest)) {
119873
+ const dest = join102(homedir51(), ".switchroom", "skills", "_bundled");
119874
+ if (!existsSync98(dest)) {
118872
119875
  return;
118873
119876
  }
118874
- 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)));
118875
119878
  if (missing.length > 0) {
118876
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.`);
118877
119880
  }
@@ -118906,7 +119909,7 @@ function planUpdate(opts) {
118906
119909
  description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
118907
119910
  run: () => {
118908
119911
  try {
118909
- const composeText = readFileSync88(composePath, "utf8");
119912
+ const composeText = readFileSync89(composePath, "utf8");
118910
119913
  const pf = validateBindSources(composeText);
118911
119914
  if (!pf.ok)
118912
119915
  throw new Error(formatPreflightError(pf));
@@ -118981,14 +119984,14 @@ function defaultStatusProbe(composePath) {
118981
119984
  } catch {}
118982
119985
  if (scriptPath) {
118983
119986
  try {
118984
- cliBuiltAt = new Date(statSync56(scriptPath).mtimeMs).toISOString();
119987
+ cliBuiltAt = new Date(statSync57(scriptPath).mtimeMs).toISOString();
118985
119988
  } catch {}
118986
119989
  let dir = dirname42(scriptPath);
118987
119990
  for (let i2 = 0;i2 < 8; i2++) {
118988
- const pkgPath = join101(dir, "package.json");
118989
- if (existsSync97(pkgPath)) {
119991
+ const pkgPath = join102(dir, "package.json");
119992
+ if (existsSync98(pkgPath)) {
118990
119993
  try {
118991
- const pkg = JSON.parse(readFileSync88(pkgPath, "utf-8"));
119994
+ const pkg = JSON.parse(readFileSync89(pkgPath, "utf-8"));
118992
119995
  if (typeof pkg.version === "string")
118993
119996
  cliVersion = pkg.version;
118994
119997
  } catch (err) {
@@ -119009,7 +120012,7 @@ function defaultStatusProbe(composePath) {
119009
120012
  warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
119010
120013
  }
119011
120014
  const services = [];
119012
- if (!existsSync97(composePath)) {
120015
+ if (!existsSync98(composePath)) {
119013
120016
  warnings.push(`compose file not found at ${composePath}; service status unknown`);
119014
120017
  return { cliVersion, cliBuiltAt, services, warnings };
119015
120018
  }
@@ -119272,22 +120275,22 @@ function registerUpdateCommand(program3) {
119272
120275
  // src/cli/rollout.ts
119273
120276
  init_helpers();
119274
120277
  import { spawnSync as spawnSync22 } from "node:child_process";
119275
- 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";
119276
120279
  import { homedir as homedir53 } from "node:os";
119277
120280
 
119278
120281
  // src/cli/rollout-pin-journal.ts
119279
120282
  import {
119280
- existsSync as existsSync98,
119281
- readFileSync as readFileSync89,
120283
+ existsSync as existsSync99,
120284
+ readFileSync as readFileSync90,
119282
120285
  writeFileSync as writeFileSync39,
119283
120286
  renameSync as renameSync26,
119284
120287
  unlinkSync as unlinkSync19,
119285
- statSync as statSync57,
120288
+ statSync as statSync58,
119286
120289
  mkdirSync as mkdirSync57
119287
120290
  } from "node:fs";
119288
120291
  import { homedir as homedir52 } from "node:os";
119289
- import { createHash as createHash18 } from "node:crypto";
119290
- 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";
119291
120294
  init_flock();
119292
120295
  var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
119293
120296
  var STATE_DIR_NAME = ".switchroom";
@@ -119296,16 +120299,16 @@ function pinJournalDir(configPath) {
119296
120299
  if (override && override.trim().length > 0)
119297
120300
  return override.trim();
119298
120301
  if (configPath) {
119299
- const dir = dirname43(resolve50(configPath));
120302
+ const dir = dirname43(resolve51(configPath));
119300
120303
  if (basename9(dir) === STATE_DIR_NAME)
119301
120304
  return dir;
119302
120305
  }
119303
- return join102(homedir52(), STATE_DIR_NAME);
120306
+ return join103(homedir52(), STATE_DIR_NAME);
119304
120307
  }
119305
120308
  function pinJournalPath(configPath) {
119306
- const abs = resolve50(configPath);
119307
- const key = createHash18("sha256").update(abs).digest("hex").slice(0, 12);
119308
- 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`);
119309
120312
  }
119310
120313
  function isPidAlive(pid) {
119311
120314
  if (!Number.isInteger(pid) || pid <= 0)
@@ -119339,9 +120342,9 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
119339
120342
  const p = pinJournalPath(configPath);
119340
120343
  let raw;
119341
120344
  try {
119342
- raw = readFileSync89(p, "utf8");
120345
+ raw = readFileSync90(p, "utf8");
119343
120346
  } catch (e) {
119344
- if (existsSync98(p)) {
120347
+ if (existsSync99(p)) {
119345
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.
119346
120349
  `);
119347
120350
  }
@@ -119392,7 +120395,7 @@ function beginPinPersist(configPath, pin, opts = {}) {
119392
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.
119393
120396
  `);
119394
120397
  }
119395
- const priorPin = getReleasePinFromConfig(readFileSync89(configPath, "utf8"));
120398
+ const priorPin = getReleasePinFromConfig(readFileSync90(configPath, "utf8"));
119396
120399
  const journal = {
119397
120400
  v: 1,
119398
120401
  configPath,
@@ -119413,7 +120416,7 @@ function commitPinPersist(configPath) {
119413
120416
  unlinkSync19(p);
119414
120417
  return null;
119415
120418
  } catch (e) {
119416
- if (!existsSync98(p))
120419
+ if (!existsSync99(p))
119417
120420
  return null;
119418
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\`.`;
119419
120422
  }
@@ -119435,11 +120438,11 @@ function rollbackPinPersist(configPath, opts = {}) {
119435
120438
  }
119436
120439
  }
119437
120440
  try {
119438
- const current = readFileSync89(configPath, "utf8");
120441
+ const current = readFileSync90(configPath, "utf8");
119439
120442
  const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
119440
120443
  let mode = 384;
119441
120444
  try {
119442
- mode = statSync57(configPath).mode & 511;
120445
+ mode = statSync58(configPath).mode & 511;
119443
120446
  } catch {}
119444
120447
  if (opts.writeConfig) {
119445
120448
  opts.writeConfig(configPath, next, mode);
@@ -119489,8 +120492,8 @@ init_host_cli_stamp();
119489
120492
 
119490
120493
  // src/cli/host-cli-upgrade.ts
119491
120494
  init_self_update();
119492
- import { lchownSync, lstatSync as lstatSync14, readdirSync as readdirSync33, statSync as statSync58 } from "node:fs";
119493
- 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";
119494
120497
  init_shipped_assets();
119495
120498
  var HOST_CLI_UPGRADE_SENTINEL = "SWITCHROOM_HOST_CLI_UPGRADE:";
119496
120499
  function encodeHostCliUpgradeResult(r) {
@@ -119526,7 +120529,7 @@ function defaultIo() {
119526
120529
  return {
119527
120530
  isFile: (p) => {
119528
120531
  try {
119529
- return statSync58(p).isFile();
120532
+ return statSync59(p).isFile();
119530
120533
  } catch {
119531
120534
  return false;
119532
120535
  }
@@ -119556,7 +120559,7 @@ function defaultIo() {
119556
120559
  chown: (p, uid, gid) => lchownSync(p, uid, gid),
119557
120560
  list: (d) => {
119558
120561
  try {
119559
- return readdirSync33(d);
120562
+ return readdirSync34(d);
119560
120563
  } catch {
119561
120564
  return [];
119562
120565
  }
@@ -119587,7 +120590,7 @@ function handBackOwnership(paths, owner, io) {
119587
120590
  if (kind !== "dir")
119588
120591
  continue;
119589
120592
  for (const child of io.list(p))
119590
- stack.push(join103(p, child));
120593
+ stack.push(join104(p, child));
119591
120594
  }
119592
120595
  return failures;
119593
120596
  }
@@ -119626,7 +120629,7 @@ async function runHostCliUpgrade(opts, io = defaultIo(), log = () => {}) {
119626
120629
  const installRoot = payloadInstallRoot(binary);
119627
120630
  const failures = handBackOwnership([
119628
120631
  binary,
119629
- join103(dirname44(binary), ".switchroom-versions"),
120632
+ join104(dirname44(binary), ".switchroom-versions"),
119630
120633
  dirname44(installRoot),
119631
120634
  installRoot,
119632
120635
  payloadVersionDir(installRoot, pin)
@@ -119722,21 +120725,21 @@ function planHostCliHeal(opts) {
119722
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`
119723
120726
  };
119724
120727
  }
119725
- const path7 = stamp.path;
119726
- if (!path7.startsWith("/") || path7.includes("/..")) {
119727
- 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` };
119728
120731
  }
119729
- const installDir = path7.slice(0, path7.lastIndexOf("/"));
119730
- const name = path7.slice(path7.lastIndexOf("/") + 1);
120732
+ const installDir = path9.slice(0, path9.lastIndexOf("/"));
120733
+ const name = path9.slice(path9.lastIndexOf("/") + 1);
119731
120734
  if (name !== "switchroom") {
119732
- 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\`` };
119733
120736
  }
119734
120737
  const prefixHostPath = installDir.slice(0, installDir.lastIndexOf("/"));
119735
120738
  const dirName = installDir.slice(installDir.lastIndexOf("/") + 1);
119736
120739
  if (prefixHostPath.length === 0 || dirName.length === 0) {
119737
120740
  return {
119738
120741
  action: "skip",
119739
- 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`
119740
120743
  };
119741
120744
  }
119742
120745
  return {
@@ -120421,13 +121424,13 @@ function createRolloutDeps(params) {
120421
121424
  }
120422
121425
  return { ok: r.status === 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
120423
121426
  };
120424
- const writeConfigPreservingOwnership = (path7, text, mode) => {
120425
- writeConfigFileSync(path7, text, mode);
121427
+ const writeConfigPreservingOwnership = (path9, text, mode) => {
121428
+ writeConfigFileSync(path9, text, mode);
120426
121429
  try {
120427
121430
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
120428
121431
  const uid = resolveOperatorUid();
120429
121432
  if (uid !== undefined) {
120430
- chownSync14(path7, uid, uid);
121433
+ chownSync14(path9, uid, uid);
120431
121434
  } else if (hostdCtx) {
120432
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.
120433
121436
  `);
@@ -120486,12 +121489,12 @@ function createRolloutDeps(params) {
120486
121489
  return stamp ? { hostCli: stamp } : {};
120487
121490
  })(),
120488
121491
  persistPin: (pin) => {
120489
- const before = readFileSync90(configPath, "utf8");
121492
+ const before = readFileSync91(configPath, "utf8");
120490
121493
  const after = setReleasePinInConfig(before, pin);
120491
121494
  if (after === before)
120492
121495
  return false;
120493
121496
  beginPinPersist(configPath, pin);
120494
- writeConfigPreservingOwnership(configPath, after, statSync59(configPath).mode & 511);
121497
+ writeConfigPreservingOwnership(configPath, after, statSync60(configPath).mode & 511);
120495
121498
  return true;
120496
121499
  },
120497
121500
  commitPin: () => {
@@ -120767,7 +121770,7 @@ init_source();
120767
121770
  init_helpers();
120768
121771
  init_loader();
120769
121772
  init_lifecycle();
120770
- import { resolve as resolve51 } from "node:path";
121773
+ import { resolve as resolve52 } from "node:path";
120771
121774
 
120772
121775
  // src/cli/version.ts
120773
121776
  init_source();
@@ -120775,8 +121778,8 @@ init_helpers();
120775
121778
  init_lifecycle();
120776
121779
  init_resolve_version();
120777
121780
  import { execSync as execSync3 } from "node:child_process";
120778
- import { existsSync as existsSync99, readFileSync as readFileSync91 } from "node:fs";
120779
- 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";
120780
121783
  function getClaudeCodeVersion() {
120781
121784
  try {
120782
121785
  const out = execSync3("claude --version 2>/dev/null", {
@@ -120826,11 +121829,11 @@ function formatUptime3(timestamp) {
120826
121829
  function locateSwitchroomInstallDir() {
120827
121830
  let dir = import.meta.dirname;
120828
121831
  for (let i2 = 0;i2 < 10 && dir && dir !== "/"; i2++) {
120829
- const pkgPath = join104(dir, "package.json");
120830
- if (existsSync99(pkgPath)) {
121832
+ const pkgPath = join105(dir, "package.json");
121833
+ if (existsSync100(pkgPath)) {
120831
121834
  try {
120832
- const pkg = JSON.parse(readFileSync91(pkgPath, "utf-8"));
120833
- 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"))) {
120834
121837
  return dir;
120835
121838
  }
120836
121839
  } catch {}
@@ -120923,7 +121926,7 @@ function registerRestartCommand(program3) {
120923
121926
  }
120924
121927
  const didRestart = res.restarted || !graceful;
120925
121928
  if (didRestart) {
120926
- const agentDir = resolve51(agentsDir, name);
121929
+ const agentDir = resolve52(agentsDir, name);
120927
121930
  const converged = waitForAuthConverge(name, agentDir);
120928
121931
  if (!converged) {
120929
121932
  console.log(source_default.yellow(` ${name}: agent is up but auth status didn't converge in 30s \u2014 check logs`));
@@ -121006,37 +122009,37 @@ Dependency manifest`));
121006
122009
  // src/cli/handoff.ts
121007
122010
  init_helpers();
121008
122011
  init_loader();
121009
- import { resolve as resolve52 } from "node:path";
122012
+ import { resolve as resolve53 } from "node:path";
121010
122013
  init_merge();
121011
122014
 
121012
122015
  // src/agents/session-retention.ts
121013
122016
  import {
121014
- existsSync as existsSync100,
121015
- readdirSync as readdirSync34,
121016
- statSync as statSync60,
122017
+ existsSync as existsSync101,
122018
+ readdirSync as readdirSync35,
122019
+ statSync as statSync61,
121017
122020
  unlinkSync as unlinkSync20
121018
122021
  } from "node:fs";
121019
- import { join as join105 } from "node:path";
122022
+ import { join as join106 } from "node:path";
121020
122023
  var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
121021
122024
  var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
121022
122025
  var MIN_KEEP = 2;
121023
122026
  function collectSessionJsonl(claudeConfigDir) {
121024
- const projects = join105(claudeConfigDir, "projects");
121025
- if (!existsSync100(projects))
122027
+ const projects = join106(claudeConfigDir, "projects");
122028
+ if (!existsSync101(projects))
121026
122029
  return [];
121027
122030
  const found = [];
121028
122031
  const walk2 = (dir) => {
121029
122032
  let entries;
121030
122033
  try {
121031
- entries = readdirSync34(dir);
122034
+ entries = readdirSync35(dir);
121032
122035
  } catch {
121033
122036
  return;
121034
122037
  }
121035
122038
  for (const name of entries) {
121036
- const full = join105(dir, name);
122039
+ const full = join106(dir, name);
121037
122040
  let st;
121038
122041
  try {
121039
- st = statSync60(full);
122042
+ st = statSync61(full);
121040
122043
  } catch {
121041
122044
  continue;
121042
122045
  }
@@ -121107,7 +122110,7 @@ function registerHandoffCommand(program3) {
121107
122110
  }
121108
122111
  agentConfig = resolveAgentConfig(config.defaults, config.profiles, rawAgent);
121109
122112
  const agentsDir = resolveAgentsDir(config);
121110
- agentDir = resolve52(agentsDir, agentName);
122113
+ agentDir = resolve53(agentsDir, agentName);
121111
122114
  } catch (err) {
121112
122115
  if (!(err instanceof ConfigError))
121113
122116
  throw err;
@@ -121122,7 +122125,7 @@ function registerHandoffCommand(program3) {
121122
122125
  `);
121123
122126
  return;
121124
122127
  }
121125
- const claudeConfigDir = resolve52(agentDir, ".claude");
122128
+ const claudeConfigDir = resolve53(agentDir, ".claude");
121126
122129
  const jsonl = findLatestSessionJsonl(claudeConfigDir);
121127
122130
  if (!jsonl) {
121128
122131
  process.stderr.write(`handoff: no session JSONL under ${claudeConfigDir}/projects; skipping
@@ -121154,18 +122157,18 @@ function registerHandoffCommand(program3) {
121154
122157
  // src/issues/store.ts
121155
122158
  import {
121156
122159
  closeSync as closeSync19,
121157
- existsSync as existsSync101,
122160
+ existsSync as existsSync102,
121158
122161
  mkdirSync as mkdirSync58,
121159
122162
  openSync as openSync19,
121160
- readdirSync as readdirSync35,
121161
- readFileSync as readFileSync92,
122163
+ readdirSync as readdirSync36,
122164
+ readFileSync as readFileSync93,
121162
122165
  renameSync as renameSync27,
121163
- statSync as statSync61,
122166
+ statSync as statSync62,
121164
122167
  unlinkSync as unlinkSync21,
121165
122168
  writeFileSync as writeFileSync40,
121166
122169
  writeSync as writeSync9
121167
122170
  } from "node:fs";
121168
- import { join as join106 } from "node:path";
122171
+ import { join as join107 } from "node:path";
121169
122172
  import { randomBytes as randomBytes16 } from "node:crypto";
121170
122173
  import { execSync as execSync4 } from "node:child_process";
121171
122174
 
@@ -121192,12 +122195,12 @@ function computeFingerprint(source, code) {
121192
122195
  var ISSUES_FILE = "issues.jsonl";
121193
122196
  var ISSUES_LOCK = "issues.lock";
121194
122197
  function readAll(stateDir) {
121195
- const path7 = join106(stateDir, ISSUES_FILE);
121196
- if (!existsSync101(path7))
122198
+ const path9 = join107(stateDir, ISSUES_FILE);
122199
+ if (!existsSync102(path9))
121197
122200
  return [];
121198
122201
  let raw;
121199
122202
  try {
121200
- raw = readFileSync92(path7, "utf-8");
122203
+ raw = readFileSync93(path9, "utf-8");
121201
122204
  } catch {
121202
122205
  return [];
121203
122206
  }
@@ -121269,8 +122272,8 @@ function record(stateDir, input, nowFn = Date.now) {
121269
122272
  return result;
121270
122273
  });
121271
122274
  }
121272
- function resolve53(stateDir, fingerprint, nowFn = Date.now) {
121273
- if (!existsSync101(join106(stateDir, ISSUES_FILE)))
122275
+ function resolve54(stateDir, fingerprint, nowFn = Date.now) {
122276
+ if (!existsSync102(join107(stateDir, ISSUES_FILE)))
121274
122277
  return 0;
121275
122278
  return withLock(stateDir, () => {
121276
122279
  const all = readAll(stateDir);
@@ -121288,7 +122291,7 @@ function resolve53(stateDir, fingerprint, nowFn = Date.now) {
121288
122291
  });
121289
122292
  }
121290
122293
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
121291
- if (!existsSync101(join106(stateDir, ISSUES_FILE)))
122294
+ if (!existsSync102(join107(stateDir, ISSUES_FILE)))
121292
122295
  return 0;
121293
122296
  return withLock(stateDir, () => {
121294
122297
  const all = readAll(stateDir);
@@ -121306,7 +122309,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
121306
122309
  });
121307
122310
  }
121308
122311
  function prune(stateDir, opts = {}) {
121309
- if (!existsSync101(join106(stateDir, ISSUES_FILE)))
122312
+ if (!existsSync102(join107(stateDir, ISSUES_FILE)))
121310
122313
  return 0;
121311
122314
  return withLock(stateDir, () => {
121312
122315
  const all = readAll(stateDir);
@@ -121339,21 +122342,21 @@ function ensureDir2(stateDir) {
121339
122342
  mkdirSync58(stateDir, { recursive: true });
121340
122343
  }
121341
122344
  function writeAll(stateDir, events) {
121342
- const path7 = join106(stateDir, ISSUES_FILE);
122345
+ const path9 = join107(stateDir, ISSUES_FILE);
121343
122346
  sweepOrphanTmpFiles(stateDir);
121344
- const tmp = `${path7}.tmp-${process.pid}-${randomBytes16(4).toString("hex")}`;
122347
+ const tmp = `${path9}.tmp-${process.pid}-${randomBytes16(4).toString("hex")}`;
121345
122348
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
121346
122349
  `) + `
121347
122350
  `;
121348
122351
  writeFileSync40(tmp, body, "utf-8");
121349
- renameSync27(tmp, path7);
122352
+ renameSync27(tmp, path9);
121350
122353
  }
121351
122354
  var ORPHAN_TMP_TTL_MS = 60000;
121352
122355
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
121353
122356
  function sweepOrphanTmpFiles(stateDir) {
121354
122357
  let entries;
121355
122358
  try {
121356
- entries = readdirSync35(stateDir);
122359
+ entries = readdirSync36(stateDir);
121357
122360
  } catch {
121358
122361
  return;
121359
122362
  }
@@ -121361,10 +122364,10 @@ function sweepOrphanTmpFiles(stateDir) {
121361
122364
  for (const entry of entries) {
121362
122365
  if (!entry.startsWith(TMP_PREFIX))
121363
122366
  continue;
121364
- const tmpPath = join106(stateDir, entry);
122367
+ const tmpPath = join107(stateDir, entry);
121365
122368
  try {
121366
- const stat = statSync61(tmpPath);
121367
- if (stat.mtimeMs < cutoff) {
122369
+ const stat2 = statSync62(tmpPath);
122370
+ if (stat2.mtimeMs < cutoff) {
121368
122371
  unlinkSync21(tmpPath);
121369
122372
  }
121370
122373
  } catch {}
@@ -121373,7 +122376,7 @@ function sweepOrphanTmpFiles(stateDir) {
121373
122376
  var LOCK_RETRY_MS = 25;
121374
122377
  var LOCK_TIMEOUT_MS = 1e4;
121375
122378
  function withLock(stateDir, fn) {
121376
- const lockPath = join106(stateDir, ISSUES_LOCK);
122379
+ const lockPath = join107(stateDir, ISSUES_LOCK);
121377
122380
  const startedAt = Date.now();
121378
122381
  let fd = null;
121379
122382
  while (fd === null) {
@@ -121408,7 +122411,7 @@ function withLock(stateDir, fn) {
121408
122411
  function tryStealStaleLock(lockPath) {
121409
122412
  let pidStr;
121410
122413
  try {
121411
- pidStr = readFileSync92(lockPath, "utf-8").trim();
122414
+ pidStr = readFileSync93(lockPath, "utf-8").trim();
121412
122415
  } catch {
121413
122416
  return true;
121414
122417
  }
@@ -121557,11 +122560,11 @@ function registerIssuesCommand(program3) {
121557
122560
  const stateDir = resolveStateDir2(opts);
121558
122561
  let flipped;
121559
122562
  if (opts.source && opts.code) {
121560
- flipped = resolve53(stateDir, computeFingerprint(opts.source, opts.code));
122563
+ flipped = resolve54(stateDir, computeFingerprint(opts.source, opts.code));
121561
122564
  } else if (opts.source && !fingerprint) {
121562
122565
  flipped = resolveAllBySource(stateDir, opts.source);
121563
122566
  } else if (fingerprint) {
121564
- flipped = resolve53(stateDir, fingerprint);
122567
+ flipped = resolve54(stateDir, fingerprint);
121565
122568
  } else {
121566
122569
  process.stderr.write(`issues resolve: need either <fingerprint>, --source, or --source + --code
121567
122570
  `);
@@ -121656,20 +122659,20 @@ function relTime(deltaMs) {
121656
122659
 
121657
122660
  // src/cli/deps.ts
121658
122661
  init_source();
121659
- import { existsSync as existsSync104 } from "node:fs";
122662
+ import { existsSync as existsSync105 } from "node:fs";
121660
122663
  import { homedir as homedir56 } from "node:os";
121661
- import { join as join109, resolve as resolve54 } from "node:path";
122664
+ import { join as join110, resolve as resolve55 } from "node:path";
121662
122665
 
121663
122666
  // src/deps/python.ts
121664
- import { createHash as createHash19 } from "node:crypto";
122667
+ import { createHash as createHash20 } from "node:crypto";
121665
122668
  import {
121666
- existsSync as existsSync102,
122669
+ existsSync as existsSync103,
121667
122670
  mkdirSync as mkdirSync59,
121668
- readFileSync as readFileSync93,
122671
+ readFileSync as readFileSync94,
121669
122672
  rmSync as rmSync19,
121670
122673
  writeFileSync as writeFileSync41
121671
122674
  } from "node:fs";
121672
- import { dirname as dirname46, join as join107 } from "node:path";
122675
+ import { dirname as dirname46, join as join108 } from "node:path";
121673
122676
  import { homedir as homedir54 } from "node:os";
121674
122677
  import { execFileSync as execFileSync31 } from "node:child_process";
121675
122678
 
@@ -121682,26 +122685,26 @@ class PythonEnvError extends Error {
121682
122685
  }
121683
122686
  }
121684
122687
  function defaultPythonCacheRoot() {
121685
- return join107(homedir54(), ".switchroom", "deps", "python");
122688
+ return join108(homedir54(), ".switchroom", "deps", "python");
121686
122689
  }
121687
- function hashFile(path7) {
121688
- return createHash19("sha256").update(readFileSync93(path7)).digest("hex");
122690
+ function hashFile(path9) {
122691
+ return createHash20("sha256").update(readFileSync94(path9)).digest("hex");
121689
122692
  }
121690
122693
  function ensurePythonEnv(opts) {
121691
122694
  const { skillName, requirementsPath, force = false } = opts;
121692
122695
  const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
121693
122696
  const hostPython = opts.pythonBin ?? "python3";
121694
- if (!existsSync102(requirementsPath)) {
122697
+ if (!existsSync103(requirementsPath)) {
121695
122698
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
121696
122699
  }
121697
- const venvDir = join107(cacheRoot, skillName);
121698
- const stampPath = join107(venvDir, ".requirements.sha256");
121699
- const binDir = join107(venvDir, "bin");
121700
- const pythonBin = join107(binDir, "python");
121701
- 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");
121702
122705
  const targetHash = hashFile(requirementsPath);
121703
- if (!force && existsSync102(stampPath) && existsSync102(pythonBin)) {
121704
- const existingHash = readFileSync93(stampPath, "utf8").trim();
122706
+ if (!force && existsSync103(stampPath) && existsSync103(pythonBin)) {
122707
+ const existingHash = readFileSync94(stampPath, "utf8").trim();
121705
122708
  if (existingHash === targetHash) {
121706
122709
  return {
121707
122710
  skillName,
@@ -121713,7 +122716,7 @@ function ensurePythonEnv(opts) {
121713
122716
  };
121714
122717
  }
121715
122718
  }
121716
- if (existsSync102(venvDir)) {
122719
+ if (existsSync103(venvDir)) {
121717
122720
  rmSync19(venvDir, { recursive: true, force: true });
121718
122721
  }
121719
122722
  mkdirSync59(dirname46(venvDir), { recursive: true });
@@ -121748,16 +122751,16 @@ function ensurePythonEnv(opts) {
121748
122751
  }
121749
122752
 
121750
122753
  // src/deps/node.ts
121751
- import { createHash as createHash20 } from "node:crypto";
122754
+ import { createHash as createHash21 } from "node:crypto";
121752
122755
  import {
121753
122756
  copyFileSync as copyFileSync13,
121754
- existsSync as existsSync103,
122757
+ existsSync as existsSync104,
121755
122758
  mkdirSync as mkdirSync60,
121756
- readFileSync as readFileSync94,
122759
+ readFileSync as readFileSync95,
121757
122760
  rmSync as rmSync20,
121758
122761
  writeFileSync as writeFileSync42
121759
122762
  } from "node:fs";
121760
- import { dirname as dirname47, join as join108 } from "node:path";
122763
+ import { dirname as dirname47, join as join109 } from "node:path";
121761
122764
  import { homedir as homedir55 } from "node:os";
121762
122765
  import { execFileSync as execFileSync32 } from "node:child_process";
121763
122766
 
@@ -121781,23 +122784,23 @@ var LOCKFILES_FOR = {
121781
122784
  npm: ["package-lock.json"]
121782
122785
  };
121783
122786
  function defaultNodeCacheRoot() {
121784
- return join108(homedir55(), ".switchroom", "deps", "node");
122787
+ return join109(homedir55(), ".switchroom", "deps", "node");
121785
122788
  }
121786
122789
  function hashDepInputs(packageJsonPath) {
121787
122790
  const sourceDir = dirname47(packageJsonPath);
121788
- const hasher = createHash20("sha256");
122791
+ const hasher = createHash21("sha256");
121789
122792
  hasher.update(`package.json
121790
122793
  `);
121791
- hasher.update(readFileSync94(packageJsonPath));
122794
+ hasher.update(readFileSync95(packageJsonPath));
121792
122795
  for (const lockName of ALL_LOCKFILES) {
121793
- const lockPath = join108(sourceDir, lockName);
121794
- if (existsSync103(lockPath)) {
122796
+ const lockPath = join109(sourceDir, lockName);
122797
+ if (existsSync104(lockPath)) {
121795
122798
  hasher.update(`
121796
122799
  `);
121797
122800
  hasher.update(lockName);
121798
122801
  hasher.update(`
121799
122802
  `);
121800
- hasher.update(readFileSync94(lockPath));
122803
+ hasher.update(readFileSync95(lockPath));
121801
122804
  }
121802
122805
  }
121803
122806
  return hasher.digest("hex");
@@ -121806,17 +122809,17 @@ function ensureNodeEnv(opts) {
121806
122809
  const { skillName, packageJsonPath, force = false } = opts;
121807
122810
  const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
121808
122811
  const installer = opts.installer ?? "bun";
121809
- if (!existsSync103(packageJsonPath)) {
122812
+ if (!existsSync104(packageJsonPath)) {
121810
122813
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
121811
122814
  }
121812
122815
  const sourceDir = dirname47(packageJsonPath);
121813
- const envDir = join108(cacheRoot, skillName);
121814
- const stampPath = join108(envDir, ".package.sha256");
121815
- const nodeModulesDir = join108(envDir, "node_modules");
121816
- const binDir = join108(nodeModulesDir, ".bin");
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");
121817
122820
  const targetHash = hashDepInputs(packageJsonPath);
121818
- if (!force && existsSync103(stampPath) && existsSync103(nodeModulesDir)) {
121819
- const existingHash = readFileSync94(stampPath, "utf8").trim();
122821
+ if (!force && existsSync104(stampPath) && existsSync104(nodeModulesDir)) {
122822
+ const existingHash = readFileSync95(stampPath, "utf8").trim();
121820
122823
  if (existingHash === targetHash) {
121821
122824
  return {
121822
122825
  skillName,
@@ -121827,16 +122830,16 @@ function ensureNodeEnv(opts) {
121827
122830
  };
121828
122831
  }
121829
122832
  }
121830
- if (existsSync103(envDir)) {
122833
+ if (existsSync104(envDir)) {
121831
122834
  rmSync20(envDir, { recursive: true, force: true });
121832
122835
  }
121833
122836
  mkdirSync60(envDir, { recursive: true });
121834
- copyFileSync13(packageJsonPath, join108(envDir, "package.json"));
122837
+ copyFileSync13(packageJsonPath, join109(envDir, "package.json"));
121835
122838
  let copiedLockfile = false;
121836
122839
  for (const lockName of LOCKFILES_FOR[installer]) {
121837
- const lockPath = join108(sourceDir, lockName);
121838
- if (existsSync103(lockPath)) {
121839
- copyFileSync13(lockPath, join108(envDir, lockName));
122840
+ const lockPath = join109(sourceDir, lockName);
122841
+ if (existsSync104(lockPath)) {
122842
+ copyFileSync13(lockPath, join109(envDir, lockName));
121840
122843
  copiedLockfile = true;
121841
122844
  }
121842
122845
  }
@@ -121865,28 +122868,28 @@ function ensureNodeEnv(opts) {
121865
122868
 
121866
122869
  // src/cli/deps.ts
121867
122870
  function builtinSkillsRoot() {
121868
- return resolve54(homedir56(), ".switchroom/skills/_bundled");
122871
+ return resolve55(homedir56(), ".switchroom/skills/_bundled");
121869
122872
  }
121870
122873
  function registerDepsCommand(program3) {
121871
122874
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
121872
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) => {
121873
122876
  const skillsRoot = builtinSkillsRoot();
121874
- if (!existsSync104(skillsRoot)) {
122877
+ if (!existsSync105(skillsRoot)) {
121875
122878
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
121876
122879
  process.exit(1);
121877
122880
  }
121878
- const skillDir = join109(skillsRoot, skill);
121879
- if (!existsSync104(skillDir)) {
122881
+ const skillDir = join110(skillsRoot, skill);
122882
+ if (!existsSync105(skillDir)) {
121880
122883
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
121881
122884
  process.exit(1);
121882
122885
  }
121883
- const requirementsPath = join109(skillDir, "requirements.txt");
121884
- const packageJsonPath = join109(skillDir, "package.json");
121885
- const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync104(requirementsPath));
121886
- const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync104(packageJsonPath));
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));
121887
122890
  let did = 0;
121888
122891
  if (wantPython) {
121889
- if (!existsSync104(requirementsPath)) {
122892
+ if (!existsSync105(requirementsPath)) {
121890
122893
  console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
121891
122894
  process.exit(1);
121892
122895
  }
@@ -121910,7 +122913,7 @@ function registerDepsCommand(program3) {
121910
122913
  }
121911
122914
  }
121912
122915
  if (wantNode) {
121913
- if (!existsSync104(packageJsonPath)) {
122916
+ if (!existsSync105(packageJsonPath)) {
121914
122917
  console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
121915
122918
  process.exit(1);
121916
122919
  }
@@ -121943,436 +122946,10 @@ function registerDepsCommand(program3) {
121943
122946
  // src/cli/workspace.ts
121944
122947
  init_helpers();
121945
122948
  init_loader();
121946
- import { existsSync as existsSync105 } from "node:fs";
121947
- import { resolve as resolve55, sep as sep7 } from "node:path";
122949
+ import { existsSync as existsSync106 } from "node:fs";
122950
+ import { resolve as resolve56, sep as sep7 } from "node:path";
121948
122951
  import { spawnSync as spawnSync23 } from "node:child_process";
121949
122952
 
121950
- // src/agents/workspace.ts
121951
- import { readFile as readFile2, stat } from "node:fs/promises";
121952
- import path8 from "node:path";
121953
-
121954
- // src/agents/bootstrap-budget.ts
121955
- import path7 from "node:path";
121956
- var DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO = 0.85;
121957
- var DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES = 3;
121958
- var DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX = 32;
121959
-
121960
- class BootstrapBudgetExceededError extends Error {
121961
- analysis;
121962
- constructor(analysis, message) {
121963
- super(message);
121964
- this.analysis = analysis;
121965
- this.name = "BootstrapBudgetExceededError";
121966
- }
121967
- }
121968
- function normalizePositiveLimit(value) {
121969
- if (!Number.isFinite(value) || value <= 0) {
121970
- return 1;
121971
- }
121972
- return Math.floor(value);
121973
- }
121974
- function formatWarningCause(cause) {
121975
- return cause === "per-file-limit" ? "max/file" : "max/total";
121976
- }
121977
- function normalizeSeenSignatures(signatures) {
121978
- if (!Array.isArray(signatures) || signatures.length === 0) {
121979
- return [];
121980
- }
121981
- const seen = new Set;
121982
- const result = [];
121983
- for (const signature of signatures) {
121984
- const value = typeof signature === "string" ? signature.trim() : "";
121985
- if (!value || seen.has(value)) {
121986
- continue;
121987
- }
121988
- seen.add(value);
121989
- result.push(value);
121990
- }
121991
- return result;
121992
- }
121993
- function appendSeenSignature(signatures, signature) {
121994
- if (!signature.trim()) {
121995
- return signatures;
121996
- }
121997
- if (signatures.includes(signature)) {
121998
- return signatures;
121999
- }
122000
- const next = [...signatures, signature];
122001
- if (next.length <= DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX) {
122002
- return next;
122003
- }
122004
- return next.slice(-DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX);
122005
- }
122006
- function buildBootstrapInjectionStats(params) {
122007
- const injectedByPath = new Map;
122008
- const injectedByBaseName = new Map;
122009
- for (const file of params.injectedFiles) {
122010
- const pathValue = typeof file.path === "string" ? file.path.trim() : "";
122011
- if (!pathValue) {
122012
- continue;
122013
- }
122014
- if (!injectedByPath.has(pathValue)) {
122015
- injectedByPath.set(pathValue, file.content);
122016
- }
122017
- const normalizedPath = pathValue.replace(/\\/g, "/");
122018
- const baseName = path7.posix.basename(normalizedPath);
122019
- if (!injectedByBaseName.has(baseName)) {
122020
- injectedByBaseName.set(baseName, file.content);
122021
- }
122022
- }
122023
- return params.bootstrapFiles.map((file) => {
122024
- const pathValue = typeof file.path === "string" ? file.path.trim() : "";
122025
- const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
122026
- const injected = (pathValue ? injectedByPath.get(pathValue) : undefined) ?? injectedByPath.get(file.name) ?? injectedByBaseName.get(file.name);
122027
- const injectedChars = injected ? injected.length : 0;
122028
- const truncated = !file.missing && injectedChars < rawChars;
122029
- return {
122030
- name: file.name,
122031
- path: pathValue || file.name,
122032
- missing: file.missing,
122033
- rawChars,
122034
- injectedChars,
122035
- truncated
122036
- };
122037
- });
122038
- }
122039
- function analyzeBootstrapBudget(params) {
122040
- const bootstrapMaxChars = normalizePositiveLimit(params.bootstrapMaxChars);
122041
- const bootstrapTotalMaxChars = normalizePositiveLimit(params.bootstrapTotalMaxChars);
122042
- const nearLimitRatio = typeof params.nearLimitRatio === "number" && Number.isFinite(params.nearLimitRatio) && params.nearLimitRatio > 0 && params.nearLimitRatio < 1 ? params.nearLimitRatio : DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO;
122043
- const nonMissing = params.files.filter((file) => !file.missing);
122044
- const rawChars = nonMissing.reduce((sum, file) => sum + file.rawChars, 0);
122045
- const injectedChars = nonMissing.reduce((sum, file) => sum + file.injectedChars, 0);
122046
- const totalNearLimit = injectedChars >= Math.ceil(bootstrapTotalMaxChars * nearLimitRatio);
122047
- const totalOverLimit = injectedChars >= bootstrapTotalMaxChars;
122048
- const files = params.files.map((file) => {
122049
- if (file.missing) {
122050
- return { ...file, nearLimit: false, causes: [] };
122051
- }
122052
- const perFileOverLimit = file.rawChars > bootstrapMaxChars;
122053
- const nearLimit = file.rawChars >= Math.ceil(bootstrapMaxChars * nearLimitRatio);
122054
- const causes = [];
122055
- if (file.truncated) {
122056
- if (perFileOverLimit) {
122057
- causes.push("per-file-limit");
122058
- }
122059
- if (totalOverLimit) {
122060
- causes.push("total-limit");
122061
- }
122062
- }
122063
- return { ...file, nearLimit, causes };
122064
- });
122065
- const truncatedFiles = files.filter((file) => file.truncated);
122066
- const nearLimitFiles = files.filter((file) => file.nearLimit);
122067
- return {
122068
- files,
122069
- truncatedFiles,
122070
- nearLimitFiles,
122071
- totalNearLimit,
122072
- hasTruncation: truncatedFiles.length > 0,
122073
- totals: {
122074
- rawChars,
122075
- injectedChars,
122076
- truncatedChars: Math.max(0, rawChars - injectedChars),
122077
- bootstrapMaxChars,
122078
- bootstrapTotalMaxChars,
122079
- nearLimitRatio
122080
- }
122081
- };
122082
- }
122083
- function buildBootstrapTruncationSignature(analysis) {
122084
- if (!analysis.hasTruncation) {
122085
- return;
122086
- }
122087
- const files = analysis.truncatedFiles.map((file) => ({
122088
- path: file.path || file.name,
122089
- rawChars: file.rawChars,
122090
- injectedChars: file.injectedChars,
122091
- causes: [...file.causes].sort()
122092
- })).sort((a, b) => {
122093
- const pathCmp = a.path.localeCompare(b.path);
122094
- if (pathCmp !== 0) {
122095
- return pathCmp;
122096
- }
122097
- if (a.rawChars !== b.rawChars) {
122098
- return a.rawChars - b.rawChars;
122099
- }
122100
- if (a.injectedChars !== b.injectedChars) {
122101
- return a.injectedChars - b.injectedChars;
122102
- }
122103
- return a.causes.join("+").localeCompare(b.causes.join("+"));
122104
- });
122105
- return JSON.stringify({
122106
- bootstrapMaxChars: analysis.totals.bootstrapMaxChars,
122107
- bootstrapTotalMaxChars: analysis.totals.bootstrapTotalMaxChars,
122108
- files
122109
- });
122110
- }
122111
- function formatBootstrapTruncationWarningLines(params) {
122112
- if (!params.analysis.hasTruncation) {
122113
- return [];
122114
- }
122115
- const maxFiles = typeof params.maxFiles === "number" && Number.isFinite(params.maxFiles) && params.maxFiles > 0 ? Math.floor(params.maxFiles) : DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES;
122116
- const lines = [];
122117
- const duplicateNameCounts = params.analysis.truncatedFiles.reduce((acc, file) => {
122118
- acc.set(file.name, (acc.get(file.name) ?? 0) + 1);
122119
- return acc;
122120
- }, new Map);
122121
- const topFiles = params.analysis.truncatedFiles.slice(0, maxFiles);
122122
- for (const file of topFiles) {
122123
- const pct = file.rawChars > 0 ? Math.round((file.rawChars - file.injectedChars) / file.rawChars * 100) : 0;
122124
- const causeText = file.causes.length > 0 ? file.causes.map((cause) => formatWarningCause(cause)).join(", ") : "";
122125
- const nameLabel = (duplicateNameCounts.get(file.name) ?? 0) > 1 && file.path.trim().length > 0 ? `${file.name} (${file.path})` : file.name;
122126
- lines.push(`${nameLabel}: ${file.rawChars} raw -> ${file.injectedChars} injected (~${Math.max(0, pct)}% removed${causeText ? `; ${causeText}` : ""}).`);
122127
- }
122128
- if (params.analysis.truncatedFiles.length > topFiles.length) {
122129
- lines.push(`+${params.analysis.truncatedFiles.length - topFiles.length} more truncated file(s).`);
122130
- }
122131
- lines.push("If unintentional, raise switchroom workspace bootstrapMaxChars and/or bootstrapTotalMaxChars config.");
122132
- return lines;
122133
- }
122134
- function buildBootstrapPromptWarning(params) {
122135
- const signature = buildBootstrapTruncationSignature(params.analysis);
122136
- let seenSignatures = normalizeSeenSignatures(params.seenSignatures);
122137
- if (params.previousSignature && !seenSignatures.includes(params.previousSignature)) {
122138
- seenSignatures = appendSeenSignature(seenSignatures, params.previousSignature);
122139
- }
122140
- const hasSeenSignature = Boolean(signature && seenSignatures.includes(signature));
122141
- const warningShown = params.mode !== "off" && Boolean(signature) && (params.mode === "always" || !hasSeenSignature);
122142
- const warningSignaturesSeen = signature && params.mode !== "off" ? appendSeenSignature(seenSignatures, signature) : seenSignatures;
122143
- return {
122144
- signature,
122145
- warningShown,
122146
- lines: warningShown ? formatBootstrapTruncationWarningLines({
122147
- analysis: params.analysis,
122148
- maxFiles: params.maxFiles
122149
- }) : [],
122150
- warningSignaturesSeen
122151
- };
122152
- }
122153
-
122154
- // src/agents/bootstrap-types.ts
122155
- var DEFAULT_AGENTS_FILENAME = "AGENTS.md";
122156
- var DEFAULT_SOUL_DEFAULT_FILENAME = "SOUL.default.md";
122157
- var DEFAULT_SOUL_FILENAME = "SOUL.md";
122158
- var DEFAULT_TOOLS_FILENAME = "TOOLS.md";
122159
- var DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
122160
- var DEFAULT_USER_FILENAME = "USER.md";
122161
- var DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
122162
- var DEFAULT_MEMORY_FILENAME = "MEMORY.md";
122163
-
122164
- // src/agents/workspace.ts
122165
- var DEFAULT_WORKSPACE_DIR_NAME = "workspace";
122166
- var DEFAULT_MEMORY_SUBDIR = "memory";
122167
- var STABLE_BOOTSTRAP_FILENAMES = [
122168
- DEFAULT_AGENTS_FILENAME,
122169
- DEFAULT_SOUL_DEFAULT_FILENAME,
122170
- DEFAULT_SOUL_FILENAME,
122171
- DEFAULT_IDENTITY_FILENAME,
122172
- DEFAULT_USER_FILENAME,
122173
- DEFAULT_TOOLS_FILENAME,
122174
- DEFAULT_BOOTSTRAP_FILENAME
122175
- ];
122176
- var DYNAMIC_BOOTSTRAP_FILENAMES = [
122177
- DEFAULT_MEMORY_FILENAME
122178
- ];
122179
- var DEFAULT_BOOTSTRAP_MAX_CHARS = 12000;
122180
- var DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS = 64000;
122181
- var DEFAULT_DYNAMIC_TOTAL_MAX_CHARS = 24000;
122182
- function resolveAgentWorkspaceDir(agentDir) {
122183
- return path8.join(agentDir, DEFAULT_WORKSPACE_DIR_NAME);
122184
- }
122185
- async function readOptionalFile(filePath) {
122186
- try {
122187
- const content = await readFile2(filePath, "utf8");
122188
- return content;
122189
- } catch (err) {
122190
- if (isErrnoException(err) && (err.code === "ENOENT" || err.code === "EISDIR")) {
122191
- return;
122192
- }
122193
- throw err;
122194
- }
122195
- }
122196
- function isErrnoException(value) {
122197
- return value instanceof Error && typeof value.code === "string";
122198
- }
122199
- async function loadNamedFile(workspaceDir, name, relativePath) {
122200
- const filePath = path8.join(workspaceDir, relativePath ?? name);
122201
- const content = await readOptionalFile(filePath);
122202
- if (content === undefined) {
122203
- return { name, path: filePath, missing: true };
122204
- }
122205
- return { name, path: filePath, content, missing: false };
122206
- }
122207
- async function loadStableBootstrapFiles(workspaceDir, options) {
122208
- const defaultFiles = await Promise.all(STABLE_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
122209
- const extras = options?.extraStableFiles ?? [];
122210
- if (extras.length === 0) {
122211
- return defaultFiles;
122212
- }
122213
- const extraFiles = await Promise.all(extras.map((name) => loadNamedFile(workspaceDir, name)));
122214
- return [...defaultFiles, ...extraFiles];
122215
- }
122216
- async function loadDynamicBootstrapFiles(workspaceDir, options) {
122217
- const now2 = options?.now ?? new Date;
122218
- const includeYesterday = options?.includeYesterday ?? true;
122219
- const files = await Promise.all(DYNAMIC_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
122220
- const todayRelative = dailyMemoryRelativePath(now2);
122221
- const todayName = DEFAULT_MEMORY_FILENAME;
122222
- const today = await loadNamedFile(workspaceDir, todayName, todayRelative);
122223
- today.path = path8.join(workspaceDir, todayRelative);
122224
- files.push(today);
122225
- if (includeYesterday) {
122226
- const yesterdayRelative = dailyMemoryRelativePath(addDays(now2, -1));
122227
- const yesterday = await loadNamedFile(workspaceDir, todayName, yesterdayRelative);
122228
- yesterday.path = path8.join(workspaceDir, yesterdayRelative);
122229
- files.push(yesterday);
122230
- }
122231
- return files;
122232
- }
122233
- function pad22(n) {
122234
- return n < 10 ? `0${n}` : String(n);
122235
- }
122236
- function dailyMemoryRelativePath(date) {
122237
- const y = date.getFullYear();
122238
- const m = pad22(date.getMonth() + 1);
122239
- const d = pad22(date.getDate());
122240
- return path8.join(DEFAULT_MEMORY_SUBDIR, `${y}-${m}-${d}.md`);
122241
- }
122242
- function addDays(date, days) {
122243
- const out = new Date(date.getTime());
122244
- out.setDate(out.getDate() + days);
122245
- return out;
122246
- }
122247
- function truncateContent(name, content, maxChars) {
122248
- if (content.length <= maxChars) {
122249
- return content;
122250
- }
122251
- if (maxChars < 200) {
122252
- return content.slice(0, Math.max(0, maxChars));
122253
- }
122254
- const headRoomInitial = Math.floor(maxChars * 0.7);
122255
- const sampleMarker = `
122256
- \u2026(truncated ${name}: kept ${headRoomInitial}+${Math.max(0, maxChars - headRoomInitial - 96)} chars of ${content.length})\u2026
122257
- `;
122258
- const markerReserve = Math.max(64, sampleMarker.length + 8);
122259
- const headRoom = Math.min(headRoomInitial, Math.max(0, maxChars - markerReserve - 8));
122260
- const tailRoom = Math.max(0, maxChars - headRoom - markerReserve);
122261
- const head = content.slice(0, Math.max(0, headRoom));
122262
- const tail = tailRoom > 0 ? content.slice(content.length - tailRoom) : "";
122263
- const result = `${head}
122264
- \u2026(truncated ${name}: kept ${headRoom}+${tailRoom} chars of ${content.length})\u2026
122265
- ${tail}`;
122266
- return result.length <= maxChars ? result : result.slice(0, maxChars);
122267
- }
122268
- function projectBootstrapFiles(params) {
122269
- const {
122270
- files,
122271
- heading,
122272
- budget,
122273
- seenSignatures,
122274
- warningMode = "error",
122275
- warningMaxFiles = DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES
122276
- } = params;
122277
- const perFileCap = budget.bootstrapMaxChars;
122278
- let remainingTotal = budget.bootstrapTotalMaxChars;
122279
- const injectedFiles = [];
122280
- const parts = [];
122281
- for (const file of files) {
122282
- if (file.missing || typeof file.content !== "string") {
122283
- continue;
122284
- }
122285
- const raw = file.content.trimEnd();
122286
- if (raw.length === 0) {
122287
- continue;
122288
- }
122289
- const perFileAllowed = Math.min(perFileCap, remainingTotal);
122290
- if (perFileAllowed <= 0) {
122291
- break;
122292
- }
122293
- const injected = truncateContent(file.name, raw, perFileAllowed);
122294
- const relativePath = file.path;
122295
- injectedFiles.push({ path: relativePath, content: injected });
122296
- parts.push(`## ${relativePath}`);
122297
- parts.push(injected);
122298
- remainingTotal -= injected.length;
122299
- if (remainingTotal <= 0) {
122300
- break;
122301
- }
122302
- }
122303
- const concatenated = parts.length > 0 ? [heading.trim().length > 0 ? `# ${heading}` : null, ...parts].filter((p) => p !== null).join(`
122304
-
122305
- `) : "";
122306
- const stats = buildBootstrapInjectionStats({
122307
- bootstrapFiles: files,
122308
- injectedFiles
122309
- });
122310
- const analysis = analyzeBootstrapBudget({
122311
- files: stats,
122312
- bootstrapMaxChars: budget.bootstrapMaxChars,
122313
- bootstrapTotalMaxChars: budget.bootstrapTotalMaxChars,
122314
- nearLimitRatio: budget.nearLimitRatio ?? DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO
122315
- });
122316
- const warning = buildBootstrapPromptWarning({
122317
- analysis,
122318
- mode: warningMode,
122319
- seenSignatures,
122320
- maxFiles: warningMaxFiles
122321
- });
122322
- if (warningMode === "error" && analysis.hasTruncation) {
122323
- const errorLines = [
122324
- "Bootstrap budget exceeded. The following files exceed limits:",
122325
- ""
122326
- ];
122327
- for (const file of analysis.truncatedFiles) {
122328
- const excessChars = file.rawChars - file.injectedChars;
122329
- 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)`);
122330
- errorLines.push(` ${file.name}: ${file.rawChars.toLocaleString()} bytes (exceeds ${causes.join(", ")})`);
122331
- errorLines.push(` Trim ${excessChars.toLocaleString()} bytes, or pass --warning-mode warn to proceed with truncation.`);
122332
- }
122333
- throw new BootstrapBudgetExceededError(analysis, errorLines.join(`
122334
- `));
122335
- }
122336
- return { files, injectedFiles, concatenated, analysis, warning };
122337
- }
122338
- async function buildStableBootstrapPrompt(params) {
122339
- const files = await loadStableBootstrapFiles(params.workspaceDir, {
122340
- extraStableFiles: params.extraStableFiles
122341
- });
122342
- const budget = {
122343
- bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
122344
- bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS,
122345
- nearLimitRatio: params.budget?.nearLimitRatio
122346
- };
122347
- return projectBootstrapFiles({
122348
- files,
122349
- heading: "Project Context (stable workspace files)",
122350
- budget,
122351
- seenSignatures: params.seenSignatures,
122352
- warningMode: params.budget?.warningMode,
122353
- warningMaxFiles: params.budget?.warningMaxFiles
122354
- });
122355
- }
122356
- async function buildDynamicBootstrapPrompt(params) {
122357
- const files = await loadDynamicBootstrapFiles(params.workspaceDir, {
122358
- now: params.now,
122359
- includeYesterday: params.includeYesterday
122360
- });
122361
- const budget = {
122362
- bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
122363
- bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_DYNAMIC_TOTAL_MAX_CHARS,
122364
- nearLimitRatio: params.budget?.nearLimitRatio
122365
- };
122366
- return projectBootstrapFiles({
122367
- files,
122368
- heading: "Project Context (dynamic workspace files)",
122369
- budget,
122370
- seenSignatures: params.seenSignatures,
122371
- warningMode: params.budget?.warningMode,
122372
- warningMaxFiles: params.budget?.warningMaxFiles
122373
- });
122374
- }
122375
-
122376
122953
  // src/agents/memory-search.ts
122377
122954
  import { readFile as readFile3, readdir, realpath, stat as stat2 } from "node:fs/promises";
122378
122955
  import path9 from "node:path";
@@ -122650,8 +123227,8 @@ function registerWorkspaceCommand(program3) {
122650
123227
  const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
122651
123228
  if (!dir)
122652
123229
  return;
122653
- const resolvedWorkspace = resolve55(dir);
122654
- const target = resolve55(resolvedWorkspace, file ?? "AGENTS.md");
123230
+ const resolvedWorkspace = resolve56(dir);
123231
+ const target = resolve56(resolvedWorkspace, file ?? "AGENTS.md");
122655
123232
  if (!isInsideWorkspace(resolvedWorkspace, target)) {
122656
123233
  process.stderr.write(`workspace edit: refusing path traversal outside workspace dir (${target})
122657
123234
  `);
@@ -122719,8 +123296,8 @@ function registerWorkspaceCommand(program3) {
122719
123296
  const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
122720
123297
  if (!dir)
122721
123298
  return;
122722
- const gitDir = resolve55(dir, ".git");
122723
- if (!existsSync105(gitDir)) {
123299
+ const gitDir = resolve56(dir, ".git");
123300
+ if (!existsSync106(gitDir)) {
122724
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.
122725
123302
  `);
122726
123303
  return;
@@ -122773,8 +123350,8 @@ function registerWorkspaceCommand(program3) {
122773
123350
  const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
122774
123351
  if (!dir)
122775
123352
  return;
122776
- const gitDir = resolve55(dir, ".git");
122777
- if (!existsSync105(gitDir)) {
123353
+ const gitDir = resolve56(dir, ".git");
123354
+ if (!existsSync106(gitDir)) {
122778
123355
  process.stdout.write(`Workspace is not a git repository.
122779
123356
  `);
122780
123357
  return;
@@ -122797,9 +123374,9 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
122797
123374
  return;
122798
123375
  }
122799
123376
  const agentsDir = resolveAgentsDir(config);
122800
- const agentDir = resolve55(agentsDir, agentName);
123377
+ const agentDir = resolve56(agentsDir, agentName);
122801
123378
  const dir = resolveAgentWorkspaceDir(agentDir);
122802
- if (!existsSync105(dir)) {
123379
+ if (!existsSync106(dir)) {
122803
123380
  process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
122804
123381
  `);
122805
123382
  return;
@@ -122835,8 +123412,8 @@ function safeParseInt(value, fallback) {
122835
123412
  init_helpers();
122836
123413
  init_loader();
122837
123414
  init_merge();
122838
- import { copyFileSync as copyFileSync14, existsSync as existsSync106, readFileSync as readFileSync95, writeFileSync as writeFileSync43 } from "node:fs";
122839
- 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";
122840
123417
  init_scaffold();
122841
123418
  init_profiles();
122842
123419
  init_schema();
@@ -122851,9 +123428,9 @@ function resolveSoulTargetOrExit(program3, agentName) {
122851
123428
  const profileName = merged.extends ?? DEFAULT_PROFILE;
122852
123429
  const profilePath = getProfilePath(profileName);
122853
123430
  const agentsDir = resolveAgentsDir(config);
122854
- const agentDir = resolve56(agentsDir, agentName);
123431
+ const agentDir = resolve57(agentsDir, agentName);
122855
123432
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
122856
- if (!existsSync106(workspaceDir)) {
123433
+ if (!existsSync107(workspaceDir)) {
122857
123434
  console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
122858
123435
  process.exit(1);
122859
123436
  }
@@ -122862,7 +123439,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
122862
123439
  profileName,
122863
123440
  profilePath,
122864
123441
  workspaceDir,
122865
- soulPath: join110(workspaceDir, "SOUL.md"),
123442
+ soulPath: join111(workspaceDir, "SOUL.md"),
122866
123443
  soul: merged.soul
122867
123444
  };
122868
123445
  }
@@ -122879,11 +123456,11 @@ function registerSoulCommand(program3) {
122879
123456
  const t = resolveSoulTargetOrExit(program3, agentName);
122880
123457
  if (!t)
122881
123458
  return;
122882
- if (!existsSync106(t.soulPath)) {
123459
+ if (!existsSync107(t.soulPath)) {
122883
123460
  console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
122884
123461
  process.exit(1);
122885
123462
  }
122886
- process.stdout.write(readFileSync95(t.soulPath, "utf-8"));
123463
+ process.stdout.write(readFileSync96(t.soulPath, "utf-8"));
122887
123464
  }));
122888
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) => {
122889
123466
  const t = resolveSoulTargetOrExit(program3, agentName);
@@ -122894,7 +123471,7 @@ function registerSoulCommand(program3) {
122894
123471
  console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
122895
123472
  process.exit(1);
122896
123473
  }
122897
- const exists = existsSync106(t.soulPath);
123474
+ const exists = existsSync107(t.soulPath);
122898
123475
  if (exists && !opts.yes) {
122899
123476
  if (!isInteractive()) {
122900
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).`);
@@ -122909,7 +123486,7 @@ function registerSoulCommand(program3) {
122909
123486
  let backupPath;
122910
123487
  if (exists) {
122911
123488
  backupPath = `${t.soulPath}.bak`;
122912
- if (existsSync106(backupPath)) {
123489
+ if (existsSync107(backupPath)) {
122913
123490
  backupPath = `${t.soulPath}.bak.${Date.now()}`;
122914
123491
  }
122915
123492
  copyFileSync14(t.soulPath, backupPath);
@@ -122925,279 +123502,6 @@ function registerSoulCommand(program3) {
122925
123502
  }));
122926
123503
  }
122927
123504
 
122928
- // src/cli/debug.ts
122929
- init_helpers();
122930
- init_loader();
122931
- import { existsSync as existsSync107, readFileSync as readFileSync96, readdirSync as readdirSync36, statSync as statSync62 } from "node:fs";
122932
- import { resolve as resolve57, join as join111 } from "node:path";
122933
- import { createHash as createHash21 } from "node:crypto";
122934
- init_merge();
122935
- init_hindsight2();
122936
- function formatBytes(bytes) {
122937
- return `${bytes.toLocaleString()} bytes`;
122938
- }
122939
- function estimateTokens(bytes) {
122940
- return Math.round(bytes / 3.7);
122941
- }
122942
- function readMcpServerNames(agentDir) {
122943
- const mcpPath = join111(agentDir, ".mcp.json");
122944
- if (!existsSync107(mcpPath))
122945
- return [];
122946
- try {
122947
- const parsed = JSON.parse(readFileSync96(mcpPath, "utf-8"));
122948
- return Object.keys(parsed.mcpServers ?? {});
122949
- } catch {
122950
- return null;
122951
- }
122952
- }
122953
- function sha2562(content) {
122954
- return createHash21("sha256").update(content).digest("hex").slice(0, 16);
122955
- }
122956
- function findLatestTranscriptJsonl(claudeConfigDir) {
122957
- const projectsDir = join111(claudeConfigDir, "projects");
122958
- if (!existsSync107(projectsDir))
122959
- return;
122960
- try {
122961
- const entries = readdirSync36(projectsDir, { withFileTypes: true });
122962
- let latest;
122963
- for (const entry of entries) {
122964
- if (!entry.isDirectory())
122965
- continue;
122966
- const projectPath = join111(projectsDir, entry.name);
122967
- const transcriptPath = join111(projectPath, "transcript.jsonl");
122968
- if (!existsSync107(transcriptPath))
122969
- continue;
122970
- const stat3 = statSync62(transcriptPath);
122971
- if (!latest || stat3.mtimeMs > latest.mtime) {
122972
- latest = { path: transcriptPath, mtime: stat3.mtimeMs };
122973
- }
122974
- }
122975
- return latest?.path;
122976
- } catch {
122977
- return;
122978
- }
122979
- }
122980
- function extractLatestUserMessage(transcriptPath) {
122981
- try {
122982
- const content = readFileSync96(transcriptPath, "utf-8");
122983
- const lines = content.trim().split(`
122984
- `).filter(Boolean);
122985
- for (let i2 = lines.length - 1;i2 >= 0; i2--) {
122986
- const line = lines[i2];
122987
- try {
122988
- const event = JSON.parse(line);
122989
- if (event.type === "message" && event.role === "user" && typeof event.content === "string") {
122990
- const timestamp = event.timestamp ? new Date(event.timestamp).toLocaleString() : "unknown";
122991
- return { text: event.content, timestamp };
122992
- }
122993
- } catch {
122994
- continue;
122995
- }
122996
- }
122997
- } catch {
122998
- return;
122999
- }
123000
- }
123001
- function buildProgressUpdateGuidance() {
123002
- return `## Progress updates (human-style check-ins)
123003
-
123004
- You're talking to a human colleague on Telegram. Alongside the emoji status
123005
- ladder, send a short \`progress_update\` at inflection points, the moments a
123006
- senior colleague would ping the person who asked them to do something:
123007
-
123008
- - **Plan formed:** "Got it. Going to do X first, then Y, then Z."
123009
- - **Pivot or blocker:** "First approach didn't work because <reason>. Trying
123010
- <alternative> instead."
123011
- - **Chunk finished:** "Done with X. Starting Y now."
123012
-
123013
- Keep them short (one or two sentences). Don't narrate every step, the pinned
123014
- progress card shows that for free. Don't send an update on a trivial one-shot
123015
- task. Send them when a colleague would genuinely want to know what's happening.
123016
-
123017
- Final answers still go through \`reply\` as usual,
123018
- \`progress_update\` is only for mid-turn check-ins.`;
123019
- }
123020
- function registerDebugCommand(program3) {
123021
- const cmd = program3.command("debug", { hidden: true }).description("Observability tools for inspecting agent prompt layering [advanced]");
123022
- 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) => {
123023
- const config = getConfig(program3);
123024
- const agentConfig = config.agents[agentName];
123025
- if (!agentConfig) {
123026
- console.error(`Agent '${agentName}' not found in switchroom.yaml`);
123027
- process.exit(1);
123028
- }
123029
- const agentsDir = resolveAgentsDir(config);
123030
- const agentDir = resolve57(agentsDir, agentName);
123031
- if (!existsSync107(agentDir)) {
123032
- console.error(`Agent directory not found: ${agentDir}`);
123033
- process.exit(1);
123034
- }
123035
- const workspaceDir = resolveAgentWorkspaceDir(agentDir);
123036
- const claudeConfigDir = join111(agentDir, ".claude");
123037
- const claudeMdPath2 = join111(agentDir, "CLAUDE.md");
123038
- const soulMdPath = join111(agentDir, "SOUL.md");
123039
- const workspaceSoulMdPath = join111(workspaceDir, "SOUL.md");
123040
- const handoffPath = join111(agentDir, ".handoff.md");
123041
- const lastN = parseInt(opts.last, 10);
123042
- if (isNaN(lastN) || lastN < 1) {
123043
- console.error("--last must be a positive integer");
123044
- process.exit(1);
123045
- }
123046
- if (lastN > 1) {
123047
- console.error("Note: --last N where N > 1 not yet implemented (only latest turn supported)");
123048
- process.exit(1);
123049
- }
123050
- console.log(`=== Debug Turn Dump: ${agentName} ===
123051
- `);
123052
- console.log(`=== Append System Prompt (stable) ===
123053
- `);
123054
- const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
123055
- const useHotReloadStable = resolved.channels?.telegram?.hotReloadStable === true;
123056
- const stableResult = await buildStableBootstrapPrompt({
123057
- workspaceDir,
123058
- budget: { warningMode: "off" }
123059
- });
123060
- if (useHotReloadStable) {
123061
- console.log(`-- Workspace Stable Render (not used \u2014 stable content is in per-turn hook) --`);
123062
- console.log();
123063
- } else if (stableResult.concatenated.trim().length > 0) {
123064
- console.log(`-- Workspace Stable Render (${formatBytes(stableResult.concatenated.length)}) --`);
123065
- console.log(stableResult.concatenated);
123066
- console.log();
123067
- } else {
123068
- console.log("-- Workspace Stable Render (0 bytes, not set up) --");
123069
- console.log();
123070
- }
123071
- const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(resolved);
123072
- const progressGuidance = useSwitchroomPlugin ? buildProgressUpdateGuidance() : "";
123073
- if (progressGuidance.length > 0) {
123074
- console.log(`-- Progress Updates Guidance (${formatBytes(progressGuidance.length)}) --`);
123075
- console.log(progressGuidance);
123076
- console.log();
123077
- }
123078
- const baseSystemPromptAppend = resolved.system_prompt_append ?? "";
123079
- if (baseSystemPromptAppend.trim().length > 0) {
123080
- console.log(`-- User System Prompt Append (${formatBytes(baseSystemPromptAppend.length)}) --`);
123081
- console.log(baseSystemPromptAppend);
123082
- console.log();
123083
- }
123084
- console.log(`=== Append System Prompt (per-session) ===
123085
- `);
123086
- const handoffContent = existsSync107(handoffPath) ? readFileSync96(handoffPath, "utf-8") : "";
123087
- if (handoffContent.trim().length > 0) {
123088
- console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
123089
- console.log(handoffContent);
123090
- console.log();
123091
- } else {
123092
- console.log("-- Handoff Briefing (0 bytes, no prior session) --");
123093
- console.log();
123094
- }
123095
- console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
123096
- `);
123097
- const claudeMdContent = existsSync107(claudeMdPath2) ? readFileSync96(claudeMdPath2, "utf-8") : "";
123098
- if (claudeMdContent.trim().length > 0) {
123099
- console.log(`(${formatBytes(claudeMdContent.length)})`);
123100
- console.log(claudeMdContent);
123101
- console.log();
123102
- } else {
123103
- console.log("(0 bytes, not present)");
123104
- console.log();
123105
- }
123106
- console.log(`=== Persona (SOUL.md) ===
123107
- `);
123108
- const soulMdContent = existsSync107(soulMdPath) ? readFileSync96(soulMdPath, "utf-8") : existsSync107(workspaceSoulMdPath) ? readFileSync96(workspaceSoulMdPath, "utf-8") : "";
123109
- if (soulMdContent.trim().length > 0) {
123110
- console.log(`(${formatBytes(soulMdContent.length)})`);
123111
- console.log(soulMdContent);
123112
- console.log();
123113
- } else {
123114
- console.log("(0 bytes, stale placeholder \u2014 Phase 2 item: single source of truth for persona)");
123115
- console.log();
123116
- }
123117
- console.log(`=== Per-Turn Injections (UserPromptSubmit) ===
123118
- `);
123119
- if (useHotReloadStable) {
123120
- if (stableResult.concatenated.trim().length > 0) {
123121
- console.log(`-- Workspace Stable (hot-reload hook): ${formatBytes(stableResult.concatenated.length)} --`);
123122
- console.log(stableResult.concatenated);
123123
- console.log();
123124
- } else {
123125
- console.log("-- Workspace Stable (hot-reload hook): 0 bytes, not set up --");
123126
- console.log();
123127
- }
123128
- }
123129
- const dynamicResult = await buildDynamicBootstrapPrompt({
123130
- workspaceDir,
123131
- budget: { warningMode: "off" }
123132
- });
123133
- if (dynamicResult.concatenated.trim().length > 0) {
123134
- console.log(`-- Workspace Dynamic: fired, ${formatBytes(dynamicResult.concatenated.length)} --`);
123135
- console.log(dynamicResult.concatenated);
123136
- console.log();
123137
- } else {
123138
- console.log("-- Workspace Dynamic: no content (MEMORY.md and daily notes empty or missing) --");
123139
- console.log();
123140
- }
123141
- const hindsightEnabled = isHindsightEnabled(config) && agentConfig.memory?.auto_recall !== false;
123142
- if (hindsightEnabled) {
123143
- console.log("-- Hindsight Recall: enabled (exact content unavailable, check hindsight logs) --");
123144
- console.log();
123145
- } else {
123146
- console.log("-- Hindsight Recall: disabled --");
123147
- console.log();
123148
- }
123149
- console.log(`=== User Message (latest turn) ===
123150
- `);
123151
- const transcriptPath = findLatestTranscriptJsonl(claudeConfigDir);
123152
- const userMessage = transcriptPath ? extractLatestUserMessage(transcriptPath) : undefined;
123153
- if (userMessage) {
123154
- console.log(`(Turn timestamp: ${userMessage.timestamp})`);
123155
- console.log(userMessage.text);
123156
- console.log();
123157
- } else {
123158
- console.log("(unavailable: no transcript found or transcript empty)");
123159
- console.log();
123160
- }
123161
- console.log(`=== Totals ===
123162
- `);
123163
- const stableBytes = stableResult.concatenated.length + progressGuidance.length + baseSystemPromptAppend.length;
123164
- const perSessionBytes = handoffContent.length;
123165
- const claudeMdBytes = claudeMdContent.length;
123166
- const soulMdBytes = soulMdContent.length;
123167
- const perTurnBytes = dynamicResult.concatenated.length;
123168
- const userBytes = userMessage?.text.length ?? 0;
123169
- const fleetDir = join111(agentsDir, "..", "fleet");
123170
- const fleetInvPath = join111(fleetDir, "switchroom-invariants.md");
123171
- const fleetClaudePath = join111(fleetDir, "CLAUDE.md");
123172
- const fleetInvBytes = existsSync107(fleetInvPath) ? readFileSync96(fleetInvPath, "utf-8").length : 0;
123173
- const fleetClaudeBytes = existsSync107(fleetClaudePath) ? readFileSync96(fleetClaudePath, "utf-8").length : 0;
123174
- const fleetBytes = fleetInvBytes + fleetClaudeBytes;
123175
- const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
123176
- console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
123177
- console.log(`Per-session: ${formatBytes(perSessionBytes).padEnd(20)} (cache-warm until next session)`);
123178
- console.log(`CLAUDE.md (cwd): ${formatBytes(claudeMdBytes).padEnd(20)} (cache-hot)`);
123179
- console.log(`Fleet invariants: ${formatBytes(fleetBytes).padEnd(20)} (--add-dir, cache-hot)`);
123180
- console.log(`Per-turn: ${formatBytes(perTurnBytes).padEnd(20)} (never cached \u2014 the real per-turn $ cost)`);
123181
- console.log(`User message: ${formatBytes(userBytes).padEnd(20)}`);
123182
- console.log(`Authored text: ${formatBytes(totalBytes).padEnd(20)} (~${estimateTokens(totalBytes).toLocaleString()} tokens est.)`);
123183
- const mcpServers = readMcpServerNames(agentDir);
123184
- const mcpCount = mcpServers?.length ?? null;
123185
- const mcpEstK = mcpCount != null ? mcpCount * 3 : null;
123186
- const mcpLabel = mcpCount != null ? `${mcpCount} servers` : "(unreadable \u2014 agent-private .mcp.json)";
123187
- const mcpEstLabel = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k tok` : "~30k tok (audit est.)";
123188
- console.log(`MCP tool surface: ${mcpLabel.padEnd(20)} (NOT counted above; ~3k tok/server \u2248 ${mcpEstLabel} \u2014 deferred under tool search)`);
123189
- if (mcpServers && mcpServers.length > 0) {
123190
- console.log(` [${mcpServers.join(", ")}]`);
123191
- }
123192
- const floorMcp = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k` : "~30k";
123193
- 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)`);
123194
- const stableCacheInput = stableResult.concatenated + progressGuidance + baseSystemPromptAppend;
123195
- const stableHash = sha2562(stableCacheInput);
123196
- console.log(`Cache stable hash: sha256:${stableHash}`);
123197
- console.log();
123198
- }));
123199
- }
123200
-
123201
123505
  // src/cli/worktree.ts
123202
123506
  init_source();
123203
123507
  init_claim();
@@ -126158,6 +126462,19 @@ function clampKnowledgeSearchLimit(limit) {
126158
126462
  }
126159
126463
  return Math.min(KNOWLEDGE_SEARCH_LIMIT_MAX, Math.max(KNOWLEDGE_SEARCH_LIMIT_MIN, Math.trunc(limit)));
126160
126464
  }
126465
+
126466
+ class KnowledgeBankNotGrantedError extends Error {
126467
+ requested;
126468
+ ownBankId;
126469
+ grantedBanks;
126470
+ constructor(requested, ownBankId, grantedBanks) {
126471
+ super(grantedBanks.length > 0 ? `bank_id '${requested}' is not a bank you can read. You may read your ` + `own bank ('${ownBankId}') or an operator-granted bank ` + `(${grantedBanks.map((b) => `'${b}'`).join(", ")}); '${requested}' ` + `is none of these. Omit bank_id to read your own memory bank.` : `bank_id '${requested}' is not your own bank ('${ownBankId}'), and ` + `this agent has not been granted any other bank to read. Omit ` + `bank_id to read your own memory bank.`);
126472
+ this.requested = requested;
126473
+ this.ownBankId = ownBankId;
126474
+ this.grantedBanks = grantedBanks;
126475
+ this.name = "KnowledgeBankNotGrantedError";
126476
+ }
126477
+ }
126161
126478
  var KNOWLEDGE_ADMIN_TIMEOUT_MS = 15000;
126162
126479
  var KNOWLEDGE_PAGE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
126163
126480
 
@@ -126169,11 +126486,22 @@ class KnowledgeAdmin {
126169
126486
  get fetchImpl() {
126170
126487
  return this.opts.fetchImpl ?? fetch;
126171
126488
  }
126172
- knowledgeBasePath() {
126489
+ resolveBank(requested) {
126490
+ const own = this.opts.bankId;
126491
+ if (requested === undefined || requested === "" || requested === own) {
126492
+ return own;
126493
+ }
126494
+ if ((this.opts.extraBanks ?? []).includes(requested))
126495
+ return requested;
126496
+ throw new KnowledgeBankNotGrantedError(requested, own, [
126497
+ ...this.opts.extraBanks ?? []
126498
+ ]);
126499
+ }
126500
+ knowledgeBasePath(bankId) {
126173
126501
  const base = this.opts.apiBaseUrl.replace(/\/+$/, "");
126174
- return `${base}/v1/default/banks/${encodeURIComponent(this.opts.bankId)}/knowledge-base`;
126502
+ return `${base}/v1/default/banks/${encodeURIComponent(bankId)}/knowledge-base`;
126175
126503
  }
126176
- async get(url) {
126504
+ async get(url, bankId) {
126177
126505
  const ctl = new AbortController;
126178
126506
  const timer = setTimeout(() => ctl.abort(), this.opts.timeoutMs ?? KNOWLEDGE_ADMIN_TIMEOUT_MS);
126179
126507
  try {
@@ -126181,7 +126509,7 @@ class KnowledgeAdmin {
126181
126509
  method: "GET",
126182
126510
  headers: {
126183
126511
  accept: "application/json",
126184
- ...this.opts.bankId ? { "X-Bank-Id": this.opts.bankId } : {}
126512
+ ...bankId ? { "X-Bank-Id": bankId } : {}
126185
126513
  },
126186
126514
  signal: ctl.signal
126187
126515
  });
@@ -126190,13 +126518,14 @@ class KnowledgeAdmin {
126190
126518
  }
126191
126519
  }
126192
126520
  async search(args) {
126521
+ const bank = this.resolveBank(args.bankId);
126193
126522
  const params = new URLSearchParams({
126194
126523
  q: args.query,
126195
126524
  limit: String(clampKnowledgeSearchLimit(args.limit))
126196
126525
  });
126197
- const res = await this.get(`${this.knowledgeBasePath()}/search?${params}`);
126526
+ const res = await this.get(`${this.knowledgeBasePath(bank)}/search?${params}`, bank);
126198
126527
  if (!res.ok) {
126199
- throw new Error(`searching knowledge pages in bank '${this.opts.bankId}' failed: HTTP ${res.status}`);
126528
+ throw new Error(`searching knowledge pages in bank '${bank}' failed: HTTP ${res.status}`);
126200
126529
  }
126201
126530
  const parsed = await res.json();
126202
126531
  const results = Array.isArray(parsed.results) ? parsed.results : [];
@@ -126209,23 +126538,25 @@ class KnowledgeAdmin {
126209
126538
  if (!KNOWLEDGE_PAGE_ID_PATTERN.test(args.page_id)) {
126210
126539
  throw new Error(`'${args.page_id}' is not a knowledge page id. Ids look like ` + `'kp-<hex>' (letters, digits, '-' and '_' only) and come from ` + `search_knowledge_pages or get_knowledge_tree.`);
126211
126540
  }
126212
- const res = await this.get(`${this.knowledgeBasePath()}/pages/${encodeURIComponent(args.page_id)}`);
126541
+ const bank = this.resolveBank(args.bankId);
126542
+ const res = await this.get(`${this.knowledgeBasePath(bank)}/pages/${encodeURIComponent(args.page_id)}`, bank);
126213
126543
  if (res.status === 404) {
126214
- throw new Error(`no knowledge page '${args.page_id}' in bank '${this.opts.bankId}'. ` + `Page ids come from search_knowledge_pages or get_knowledge_tree.`);
126544
+ throw new Error(`no knowledge page '${args.page_id}' in bank '${bank}'. ` + `Page ids come from search_knowledge_pages or get_knowledge_tree.`);
126215
126545
  }
126216
126546
  if (!res.ok) {
126217
- throw new Error(`reading knowledge page '${args.page_id}' in bank ` + `'${this.opts.bankId}' failed: HTTP ${res.status}`);
126547
+ throw new Error(`reading knowledge page '${args.page_id}' in bank ` + `'${bank}' failed: HTTP ${res.status}`);
126218
126548
  }
126219
126549
  const page = await res.json();
126220
126550
  if (typeof page.markdown !== "string") {
126221
- throw new Error(`knowledge page '${args.page_id}' in bank '${this.opts.bankId}' came ` + `back with no markdown body \u2014 the response had no string 'markdown' ` + `field, so there is nothing to read.`);
126551
+ throw new Error(`knowledge page '${args.page_id}' in bank '${bank}' came ` + `back with no markdown body \u2014 the response had no string 'markdown' ` + `field, so there is nothing to read.`);
126222
126552
  }
126223
126553
  return page;
126224
126554
  }
126225
- async tree() {
126226
- const res = await this.get(`${this.knowledgeBasePath()}/tree`);
126555
+ async tree(args = {}) {
126556
+ const bank = this.resolveBank(args.bankId);
126557
+ const res = await this.get(`${this.knowledgeBasePath(bank)}/tree`, bank);
126227
126558
  if (!res.ok) {
126228
- throw new Error(`listing the knowledge tree in bank '${this.opts.bankId}' failed: HTTP ${res.status}`);
126559
+ throw new Error(`listing the knowledge tree in bank '${bank}' failed: HTTP ${res.status}`);
126229
126560
  }
126230
126561
  const parsed = await res.json();
126231
126562
  return { roots: Array.isArray(parsed.roots) ? parsed.roots : [] };
@@ -126278,6 +126609,10 @@ var FALLBACK_TOOL_TABLE = {
126278
126609
  update_memory: [["memory_id"], ["bank_id", "context", "entities", "fact_type", "memory_id", "occurred_end", "occurred_start", "text"]],
126279
126610
  update_mental_model: [["mental_model_id"], ["bank_id", "max_tokens", "mental_model_id", "name", "source_query", "tags", "trigger_refresh_after_consolidation"]]
126280
126611
  };
126612
+ var KNOWLEDGE_BANK_SELECTOR_PROP = {
126613
+ type: "string",
126614
+ description: "Optional. Which memory bank to read. Omit to read YOUR OWN bank (the " + "default). You may name another bank ONLY if your operator has granted " + "it to you (e.g. a shared repo knowledge bank); naming any other bank is " + "rejected."
126615
+ };
126281
126616
  var SYNTHESIZED_TOOL_TABLE = {
126282
126617
  deactivate_directive: {
126283
126618
  description: "Retire one of YOUR OWN standing directives by setting is_active=false, " + "so it stops being injected as a hard rule. Reversible with " + "reactivate_directive; the directive and its text are preserved. Pass " + "superseded_by when another directive replaces this one \u2014 both get " + "provenance tags recording the supersession. Operates only on your own " + "memory bank and changes nothing but the active flag and those tags.",
@@ -126304,7 +126639,7 @@ var SYNTHESIZED_TOOL_TABLE = {
126304
126639
  }
126305
126640
  },
126306
126641
  search_knowledge_pages: {
126307
- description: "Search YOUR OWN knowledge pages \u2014 the curated, continuously-refreshed " + "summaries of what this bank knows \u2014 with hybrid full-text + semantic " + "search. Reach for this before re-deriving a standing answer from raw " + "recall: a page is already synthesized, where recall returns fragments. " + "Returns ranked hits with a relevance snippet and a page id; read the " + "whole page with get_knowledge_page. Read-only, and only ever your own " + "memory bank. An empty knowledge base returns no results, not an error.",
126642
+ description: "Search knowledge pages \u2014 the curated, continuously-refreshed " + "summaries of what a bank knows \u2014 with hybrid full-text + semantic " + "search. Reach for this before re-deriving a standing answer from raw " + "recall: a page is already synthesized, where recall returns fragments. " + "Returns ranked hits with a relevance snippet and a page id; read the " + "whole page with get_knowledge_page. Read-only. Defaults to your own " + "bank; pass bank_id to search an operator-granted bank. An empty " + "knowledge base returns no results, not an error.",
126308
126643
  required: ["query"],
126309
126644
  props: {
126310
126645
  query: {
@@ -126316,23 +126651,27 @@ var SYNTHESIZED_TOOL_TABLE = {
126316
126651
  minimum: KNOWLEDGE_SEARCH_LIMIT_MIN,
126317
126652
  maximum: KNOWLEDGE_SEARCH_LIMIT_MAX,
126318
126653
  description: `Maximum hits to return (${KNOWLEDGE_SEARCH_LIMIT_MIN}-` + `${KNOWLEDGE_SEARCH_LIMIT_MAX}, default ${KNOWLEDGE_SEARCH_LIMIT_DEFAULT}).`
126319
- }
126654
+ },
126655
+ bank_id: KNOWLEDGE_BANK_SELECTOR_PROP
126320
126656
  }
126321
126657
  },
126322
126658
  get_knowledge_page: {
126323
- description: "Read one of YOUR OWN knowledge pages in full, by the page id returned " + "by search_knowledge_pages or get_knowledge_tree. Returns the complete " + "markdown document (YAML frontmatter + synthesized body). Prefer this " + "over rebuilding the same understanding from scratch. Read-only: it " + "returns the page as it currently stands and never triggers a refresh.",
126659
+ description: "Read one knowledge page in full, by the page id returned by " + "search_knowledge_pages or get_knowledge_tree. Returns the complete " + "markdown document (YAML frontmatter + synthesized body). Prefer this " + "over rebuilding the same understanding from scratch. Read-only: it " + "returns the page as it currently stands and never triggers a refresh. " + "Defaults to your own bank; pass bank_id (matching the tree/search call " + "the id came from) to read from an operator-granted bank.",
126324
126660
  required: ["page_id"],
126325
126661
  props: {
126326
126662
  page_id: {
126327
126663
  type: "string",
126328
126664
  description: "Id of the page to read, from search_knowledge_pages or " + "get_knowledge_tree."
126329
- }
126665
+ },
126666
+ bank_id: KNOWLEDGE_BANK_SELECTOR_PROP
126330
126667
  }
126331
126668
  },
126332
126669
  get_knowledge_tree: {
126333
- description: "List YOUR OWN knowledge base as a folder/page tree \u2014 every page's id, " + "name, source query, tags and staleness flag. Call this to see what " + "this bank already knows before reading anything else; is_stale=true " + "means the page MAY be behind newer memories, not that it is wrong. " + "Read-only, and only ever your own memory bank.",
126670
+ description: "List a knowledge base as a folder/page tree \u2014 every page's id, " + "name, source query, tags and staleness flag. Call this to see what " + "a bank already knows before reading anything else; is_stale=true " + "means the page MAY be behind newer memories, not that it is wrong. " + "Read-only. Defaults to your own bank; pass bank_id to list an " + "operator-granted bank.",
126334
126671
  required: [],
126335
- props: {}
126672
+ props: {
126673
+ bank_id: KNOWLEDGE_BANK_SELECTOR_PROP
126674
+ }
126336
126675
  }
126337
126676
  };
126338
126677
  var SYNTHESIZED_TOOL_NAMES = Object.keys(SYNTHESIZED_TOOL_TABLE);
@@ -126936,6 +127275,7 @@ class HindsightShim {
126936
127275
  this.knowledgeAdminCache = new KnowledgeAdmin({
126937
127276
  apiBaseUrl: this.opts.apiBaseUrl ?? this.opts.url.replace(/\/mcp\/?$/, ""),
126938
127277
  bankId: this.opts.bankId,
127278
+ ...this.opts.extraBanks ? { extraBanks: this.opts.extraBanks } : {},
126939
127279
  ...this.opts.fetchImpl ? { fetchImpl: this.opts.fetchImpl } : {}
126940
127280
  });
126941
127281
  }
@@ -126979,6 +127319,15 @@ class HindsightShim {
126979
127319
  if (!this.opts.bankId) {
126980
127320
  return fail4(`${name} is unavailable: this agent has no HINDSIGHT_BANK_ID, so ` + "there is no bank to pin it to.");
126981
127321
  }
127322
+ if (typeof clean3.bank_id === "string") {
127323
+ const requested = clean3.bank_id;
127324
+ const granted = requested === this.opts.bankId || (this.opts.extraBanks ?? []).includes(requested);
127325
+ if (!granted) {
127326
+ return fail4(new KnowledgeBankNotGrantedError(requested, this.opts.bankId, [
127327
+ ...this.opts.extraBanks ?? []
127328
+ ]).message);
127329
+ }
127330
+ }
126982
127331
  const preflight = await this.contractPin.preflight(name);
126983
127332
  if (!preflight.ok)
126984
127333
  return fail4(preflight.text);
@@ -126986,6 +127335,9 @@ class HindsightShim {
126986
127335
  const text = await this.runSynthesized(name, clean3);
126987
127336
  return { content: [{ type: "text", text }], isError: false };
126988
127337
  } catch (err) {
127338
+ if (err instanceof KnowledgeBankNotGrantedError) {
127339
+ return fail4(err.message);
127340
+ }
126989
127341
  if (err instanceof DirectivePairInconsistentError) {
126990
127342
  this.log(`[hindsight-shim] ${err.message}`);
126991
127343
  }
@@ -127004,23 +127356,25 @@ class HindsightShim {
127004
127356
  case "search_knowledge_pages": {
127005
127357
  const res = await this.knowledgeAdmin.search({
127006
127358
  query: args.query,
127007
- ...typeof args.limit === "number" ? { limit: args.limit } : {}
127359
+ ...typeof args.limit === "number" ? { limit: args.limit } : {},
127360
+ ...typeof args.bank_id === "string" ? { bankId: args.bank_id } : {}
127008
127361
  });
127009
127362
  if (res.results.length === 0) {
127010
- return "No knowledge pages matched that query in your memory bank. " + "The bank may have no pages yet \u2014 get_knowledge_tree lists what " + "exists.";
127363
+ return "No knowledge pages matched that query in " + (typeof args.bank_id === "string" ? `bank '${args.bank_id}'. ` : "your memory bank. ") + "The bank may have no pages yet \u2014 get_knowledge_tree lists what " + "exists.";
127011
127364
  }
127012
127365
  return capKnowledgeResponse(JSON.stringify({ total: res.total, results: res.results }, null, 2));
127013
127366
  }
127014
127367
  case "get_knowledge_page": {
127015
127368
  const page = await this.knowledgeAdmin.getPage({
127016
- page_id: args.page_id
127369
+ page_id: args.page_id,
127370
+ ...typeof args.bank_id === "string" ? { bankId: args.bank_id } : {}
127017
127371
  });
127018
127372
  return capKnowledgeResponse(page.markdown);
127019
127373
  }
127020
127374
  case "get_knowledge_tree": {
127021
- const tree = await this.knowledgeAdmin.tree();
127375
+ const tree = await this.knowledgeAdmin.tree(typeof args.bank_id === "string" ? { bankId: args.bank_id } : {});
127022
127376
  if (tree.roots.length === 0) {
127023
- return "Your knowledge base has no pages yet. Pages are synthesized from " + "mental models \u2014 propose one to start it.";
127377
+ return typeof args.bank_id === "string" ? `Bank '${args.bank_id}' has no knowledge pages yet.` : "Your knowledge base has no pages yet. Pages are synthesized " + "from mental models \u2014 propose one to start it.";
127024
127378
  }
127025
127379
  return renderKnowledgeTree(tree.roots);
127026
127380
  }
@@ -127134,9 +127488,11 @@ class HindsightShim {
127134
127488
  }
127135
127489
  function resolveShimOptionsFromEnv(env2) {
127136
127490
  const home2 = env2.HOME && env2.HOME !== "/" ? env2.HOME : tmpdir6();
127491
+ const extraBanks = (env2.HINDSIGHT_KNOWLEDGE_EXTRA_BANKS ?? "").split(",").map((b) => b.trim()).filter((b) => b.length > 0 && b !== (env2.HINDSIGHT_BANK_ID || ""));
127137
127492
  return {
127138
127493
  url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
127139
127494
  bankId: env2.HINDSIGHT_BANK_ID || "",
127495
+ ...extraBanks.length > 0 ? { extraBanks } : {},
127140
127496
  cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join117(home2, ".hindsight-shim")
127141
127497
  };
127142
127498
  }