switchroom 0.19.38 → 0.19.40

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 (45) hide show
  1. package/dist/agent-scheduler/index.js +10 -1
  2. package/dist/auth-broker/index.js +15 -6
  3. package/dist/cli/notion-write-pretool.mjs +10 -1
  4. package/dist/cli/switchroom.js +1141 -566
  5. package/dist/host-control/main.js +220 -8
  6. package/dist/vault/approvals/kernel-server.js +15 -6
  7. package/dist/vault/broker/server.js +15 -6
  8. package/package.json +1 -1
  9. package/profiles/_base/start.sh.hbs +10 -0
  10. package/telegram-plugin/dist/bridge/bridge.js +8 -0
  11. package/telegram-plugin/dist/gateway/gateway.js +450 -206
  12. package/telegram-plugin/dist/server.js +8 -0
  13. package/telegram-plugin/gateway/backstop-delivery.ts +48 -0
  14. package/telegram-plugin/gateway/compaction-marker.ts +84 -0
  15. package/telegram-plugin/gateway/gateway.ts +3 -3
  16. package/telegram-plugin/gateway/handback-preturn-signal.ts +16 -0
  17. package/telegram-plugin/gateway/liveness-wiring.ts +15 -0
  18. package/telegram-plugin/gateway/outbound-send-path.ts +20 -0
  19. package/telegram-plugin/gateway/outbox-sweep.ts +116 -18
  20. package/telegram-plugin/gateway/silence-poke-session-event.ts +13 -0
  21. package/telegram-plugin/gateway/stream-render.ts +277 -18
  22. package/telegram-plugin/hooks/compaction-marker-precompact.mjs +70 -0
  23. package/telegram-plugin/hooks/hooks.json +11 -0
  24. package/telegram-plugin/hooks/tool-label-pretool.mjs +119 -1
  25. package/telegram-plugin/session-tail.ts +20 -0
  26. package/telegram-plugin/silence-poke.ts +28 -0
  27. package/telegram-plugin/tests/activity-ever-opened-sticky.test.ts +19 -12
  28. package/telegram-plugin/tests/fixtures/pretool-main-2.1.219.json +15 -0
  29. package/telegram-plugin/tests/fixtures/pretool-subagent-2.1.219.json +16 -0
  30. package/telegram-plugin/tests/outbox-delivery.test.ts +38 -1
  31. package/telegram-plugin/tests/outbox-flush-ack-claim-race.test.ts +213 -0
  32. package/telegram-plugin/tests/outbox-reply-then-recap-e2e.test.ts +1 -1
  33. package/telegram-plugin/tests/outbox-sweep-flood-breaker.test.ts +4 -4
  34. package/telegram-plugin/tests/outbox-sweep-listen-button.test.ts +71 -8
  35. package/telegram-plugin/tests/queued-card-surface.test.ts +262 -0
  36. package/telegram-plugin/tests/send-reply-golden.test.ts +47 -0
  37. package/telegram-plugin/tests/sidechain-label-filter-pretool.test.ts +272 -0
  38. package/telegram-plugin/tests/silence-poke-compaction.test.ts +222 -0
  39. package/vendor/hindsight-memory/scripts/lib/client.py +7 -4
  40. package/vendor/hindsight-memory/scripts/lib/config.py +151 -0
  41. package/vendor/hindsight-memory/scripts/retain.py +19 -15
  42. package/vendor/hindsight-memory/scripts/tests/test_backfill.py +14 -1
  43. package/vendor/hindsight-memory/scripts/tests/test_observation_scopes.py +192 -3
  44. package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +18 -2
  45. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +57 -4
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.19.38", COMMIT_SHA = "9ec88983";
2123
+ var VERSION = "0.19.40", COMMIT_SHA = "89286b22";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -13593,7 +13593,7 @@ function resolveObservationScope(configured, env2 = process.env) {
13593
13593
  return classifyObservationScope(configured);
13594
13594
  return classifyObservationScope(env2[OBSERVATION_SCOPES_ENV]);
13595
13595
  }
13596
- var OBSERVATION_SCOPES, OBSERVATION_SCOPES_ENV = "HINDSIGHT_OBSERVATION_SCOPES";
13596
+ var OBSERVATION_SCOPES, OBSERVATION_SCOPES_ENV = "HINDSIGHT_OBSERVATION_SCOPES", OBSERVATION_SCOPE_STRATEGIES;
13597
13597
  var init_observation_scopes = __esm(() => {
13598
13598
  OBSERVATION_SCOPES = [
13599
13599
  "per_tag",
@@ -13601,13 +13601,19 @@ var init_observation_scopes = __esm(() => {
13601
13601
  "all_combinations",
13602
13602
  "shared"
13603
13603
  ];
13604
+ OBSERVATION_SCOPE_STRATEGIES = [
13605
+ "curated",
13606
+ "shared",
13607
+ "combined",
13608
+ "off"
13609
+ ];
13604
13610
  });
13605
13611
 
13606
13612
  // src/config/schema.ts
13607
13613
  function isValidTimezone(value) {
13608
13614
  return TIMEZONE_REGEX.test(value);
13609
13615
  }
13610
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
13616
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
13611
13617
  var init_schema = __esm(() => {
13612
13618
  init_zod();
13613
13619
  init_observation_scopes();
@@ -13722,7 +13728,8 @@ var init_schema = __esm(() => {
13722
13728
  allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
13723
13729
  deny: exports_external.array(exports_external.string()).default([]).describe("Denied tools (overrides allow)")
13724
13730
  }).optional();
13725
- ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Per-row observation scope stamped on every memory this agent " + `retains. "shared" makes Hindsight's consolidation write the ` + "resulting observations into ONE global untagged scope instead of a " + "scope per tag \u2014 what several agents pooling one bank need so their " + "observations actually merge rather than sitting in parallel " + "per-tag silos. OMITTED BY DEFAULT: unset means the field never goes " + "on the wire and the engine's own default stands, which is the " + "shipped behaviour. Applies to every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
13731
+ ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Operator PIN for the per-row observation scope, overriding the " + 'default `curated` strategy (see docs/configuration.md). "shared" ' + "forces Hindsight's consolidation to write EVERY observation into ONE " + "global untagged scope \u2014 what several agents pooling one bank want so " + "their observations merge rather than sitting in parallel silos. " + '"combined" is the pin form of opting out (restores the pre-#4035 ' + "engine default). UNSET does NOT mean 'nothing on the wire': the " + "`curated` strategy still computes a scope per retain. Applies to " + "every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
13732
+ ObservationScopeStrategySchema = exports_external.enum(OBSERVATION_SCOPE_STRATEGIES).optional().describe("Per-retain strategy that decides how the observation scope is " + 'computed when no `observation_scopes` pin is set. "curated" ' + "(the default since #4035) strips volatile per-session provenance " + "tags from each retain's consolidation scope \u2014 keeping the stable " + "semantic ones on the source fact \u2014 so an agent's observations dedup " + "and merge across sessions instead of pocketing one-per-session. " + '"shared" sends every retain to the one global untagged scope. ' + '"combined" / "off" opt OUT: no per-row scope goes on the wire, ' + "restoring the pre-feature engine default (byte-identical to an " + "unconfigured client). OMITTED BY DEFAULT: unset leaves the plugin's " + "own default (`curated`) in force. A manual `observation_scopes` pin " + "wins over this strategy. Accepted values: " + `${OBSERVATION_SCOPE_STRATEGIES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
13726
13733
  AgentMemorySchema = exports_external.object({
13727
13734
  collection: exports_external.string().describe("Hindsight collection name for this agent"),
13728
13735
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
@@ -13758,6 +13765,7 @@ var init_schema = __esm(() => {
13758
13765
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis \u2014 a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
13759
13766
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true \u2014 Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never \u2026", "from now on \u2026", ' + `"stop doing \u2026", a stated preference, "that's wrong, it's \u2026") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex \u2014 the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
13760
13767
  observation_scopes: ObservationScopesSchema,
13768
+ observation_scope_strategy: ObservationScopeStrategySchema,
13761
13769
  recall: exports_external.object({
13762
13770
  max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
13763
13771
  cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
@@ -14158,6 +14166,7 @@ var init_schema = __esm(() => {
14158
14166
  isolation: exports_external.enum(["default", "strict"]).optional(),
14159
14167
  directive_capture_nudge: exports_external.boolean().optional(),
14160
14168
  observation_scopes: ObservationScopesSchema,
14169
+ observation_scope_strategy: ObservationScopeStrategySchema,
14161
14170
  recall: exports_external.object({
14162
14171
  max_memories: exports_external.number().int().min(0).optional(),
14163
14172
  cache_ttl_secs: exports_external.number().int().min(0).optional(),
@@ -15830,7 +15839,7 @@ function hindsightPgEnv(overrides = new Map) {
15830
15839
  }
15831
15840
  return out;
15832
15841
  }
15833
- var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION, HINDSIGHT_PG_APP_ANON_MIB = 2560, HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048, HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB, HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 3072, HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 7168, HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE", HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS", HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC", HINDSIGHT_PG_DEFAULT_FSYNC = "on", HINDSIGHT_PG_DEFAULTS, HINDSIGHT_PG_ENV_KEYS;
15842
+ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION, HINDSIGHT_PG_APP_ANON_MIB = 2560, HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048, HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB, HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 6144, HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288, HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE", HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS", HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC", HINDSIGHT_PG_DEFAULT_FSYNC = "on", HINDSIGHT_PG_DEFAULTS, HINDSIGHT_PG_ENV_KEYS;
15834
15843
  var init_hindsight_pg_defaults = __esm(() => {
15835
15844
  HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
15836
15845
  HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
@@ -15908,7 +15917,7 @@ function hindsightPerfEnv(caps, overrides = new Map) {
15908
15917
  function findUnmanagedHindsightEnvKeys(configEnv) {
15909
15918
  return Object.keys(configEnv ?? {}).filter((key) => !HINDSIGHT_PERF_ENV_KEYS.has(key) && !HINDSIGHT_PG_ENV_KEYS.has(key)).sort();
15910
15919
  }
15911
- var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150, HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE, HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50, HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2, HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low", HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT, HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true", HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128, HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true", HINDSIGHT_DEFAULT_LLM_MAX_RETRIES = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 500, HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential", HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30, HINDSIGHT_PERF_DEFAULTS_UNGATED, HINDSIGHT_PERF_DEFAULTS_GPU, HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM, HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS, HINDSIGHT_PERF_ENV_KEYS;
15920
+ var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150, HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE, HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50, HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2, HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low", HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT, HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true", HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128, HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true", HINDSIGHT_DEFAULT_LLM_MAX_RETRIES = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250, HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential", HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30, HINDSIGHT_PERF_DEFAULTS_UNGATED, HINDSIGHT_PERF_DEFAULTS_GPU, HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM, HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS, HINDSIGHT_PERF_ENV_KEYS;
15912
15921
  var init_hindsight_perf_defaults = __esm(() => {
15913
15922
  init_hindsight_pg_defaults();
15914
15923
  HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE = Math.ceil(HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION * 0.4);
@@ -29102,6 +29111,7 @@ function buildWorkspaceContext(args) {
29102
29111
  hindsightRecallSkipTrivial,
29103
29112
  hindsightDirectiveCaptureNudge,
29104
29113
  hindsightObservationScopes,
29114
+ hindsightObservationScopeStrategy,
29105
29115
  hindsightTopicAliasesJson,
29106
29116
  hindsightSenderBanksJson,
29107
29117
  hindsightTopicFilterMode
@@ -29156,6 +29166,7 @@ function buildWorkspaceContext(args) {
29156
29166
  hindsightRecallSkipTrivial,
29157
29167
  hindsightDirectiveCaptureNudge,
29158
29168
  hindsightObservationScopesQ: hindsightObservationScopes ? shellSingleQuote(hindsightObservationScopes) : undefined,
29169
+ hindsightObservationScopeStrategyQ: hindsightObservationScopeStrategy ? shellSingleQuote(hindsightObservationScopeStrategy) : undefined,
29159
29170
  hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
29160
29171
  hindsightSenderBanksJsonQ: hindsightSenderBanksJson ? shellSingleQuote(hindsightSenderBanksJson) : undefined,
29161
29172
  hindsightTopicFilterMode,
@@ -29421,6 +29432,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
29421
29432
  const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
29422
29433
  const hindsightDirectiveCaptureNudge = agentConfig.memory?.directive_capture_nudge === undefined ? undefined : String(agentConfig.memory.directive_capture_nudge);
29423
29434
  const hindsightObservationScopes = agentConfig.memory?.observation_scopes;
29435
+ const hindsightObservationScopeStrategy = agentConfig.memory?.observation_scope_strategy;
29424
29436
  const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
29425
29437
  const hindsightTopicAliasesJson = topicAliases && Object.keys(topicAliases).length > 0 ? JSON.stringify(topicAliases) : undefined;
29426
29438
  const senderBanks = switchroomConfig ? resolveUsers(switchroomConfig, name).senderBanks : agentConfig.memory?.recall?.sender_banks ?? {};
@@ -29457,6 +29469,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
29457
29469
  hindsightRecallSkipTrivial,
29458
29470
  hindsightDirectiveCaptureNudge,
29459
29471
  hindsightObservationScopes,
29472
+ hindsightObservationScopeStrategy,
29460
29473
  hindsightTopicAliasesJson,
29461
29474
  hindsightSenderBanksJson,
29462
29475
  hindsightTopicFilterMode
@@ -30402,6 +30415,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
30402
30415
  const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
30403
30416
  const hindsightDirectiveCaptureNudge = agentConfig.memory?.directive_capture_nudge === undefined ? undefined : String(agentConfig.memory.directive_capture_nudge);
30404
30417
  const hindsightObservationScopes = agentConfig.memory?.observation_scopes;
30418
+ const hindsightObservationScopeStrategy = agentConfig.memory?.observation_scope_strategy;
30405
30419
  const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
30406
30420
  const hindsightTopicAliasesJson = topicAliases && Object.keys(topicAliases).length > 0 ? JSON.stringify(topicAliases) : undefined;
30407
30421
  const senderBanks = switchroomConfig ? resolveUsers(switchroomConfig, name).senderBanks : agentConfig.memory?.recall?.sender_banks ?? {};
@@ -30451,6 +30465,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
30451
30465
  hindsightRecallSkipTrivial,
30452
30466
  hindsightDirectiveCaptureNudge,
30453
30467
  hindsightObservationScopesQ: hindsightObservationScopes ? shellSingleQuote(hindsightObservationScopes) : undefined,
30468
+ hindsightObservationScopeStrategyQ: hindsightObservationScopeStrategy ? shellSingleQuote(hindsightObservationScopeStrategy) : undefined,
30454
30469
  hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
30455
30470
  hindsightSenderBanksJsonQ: hindsightSenderBanksJson ? shellSingleQuote(hindsightSenderBanksJson) : undefined,
30456
30471
  hindsightTopicFilterMode,
@@ -30874,6 +30889,7 @@ ${body}
30874
30889
  hindsightRecallSkipTrivial,
30875
30890
  hindsightDirectiveCaptureNudge,
30876
30891
  hindsightObservationScopes,
30892
+ hindsightObservationScopeStrategy,
30877
30893
  hindsightTopicAliasesJson,
30878
30894
  hindsightSenderBanksJson,
30879
30895
  hindsightTopicFilterMode
@@ -39115,168 +39131,6 @@ var init_telegram_yaml = __esm(() => {
39115
39131
  import_yaml12 = __toESM(require_dist(), 1);
39116
39132
  });
39117
39133
 
39118
- // src/memory/hindsight-tools.ts
39119
- var HINDSIGHT_MIN_API_VERSION = "0.8.5", HINDSIGHT_REPAIR_MIN_API_VERSION = "0.8.5", EXPECTED_HINDSIGHT_TOOLS;
39120
- var init_hindsight_tools = __esm(() => {
39121
- EXPECTED_HINDSIGHT_TOOLS = {
39122
- recall: { required: ["query"] },
39123
- reflect: { required: ["query"] },
39124
- retain: { required: ["content"] },
39125
- sync_retain: { required: ["content"] },
39126
- delete_document: { required: ["document_id"] },
39127
- create_directive: { required: ["content", "name"] },
39128
- list_directives: { required: [] },
39129
- delete_directive: { required: ["directive_id"] },
39130
- create_bank: { required: ["bank_id"] },
39131
- update_bank: { required: [] },
39132
- list_banks: { required: [] },
39133
- create_mental_model: { required: ["name", "source_query"] },
39134
- list_mental_models: { required: [] },
39135
- update_mental_model: { required: ["mental_model_id"] },
39136
- refresh_mental_model: { required: ["mental_model_id"] },
39137
- list_memories: { required: [] },
39138
- get_memory: { required: ["memory_id"] },
39139
- update_memory: { required: ["memory_id"] },
39140
- invalidate_memory: { required: ["memory_id"] }
39141
- };
39142
- });
39143
-
39144
- // src/memory/hindsight-repair.ts
39145
- function buildRepairBankArgv(opts) {
39146
- const hasBank = typeof opts.bank === "string" && opts.bank.length > 0;
39147
- if (hasBank === Boolean(opts.all)) {
39148
- throw new Error("pass exactly one of --bank <id> or --all");
39149
- }
39150
- const cmdArgs = ["repair-bank"];
39151
- if (hasBank)
39152
- cmdArgs.push("--bank", opts.bank);
39153
- if (opts.all)
39154
- cmdArgs.push("--all");
39155
- if (opts.schema)
39156
- cmdArgs.push("--schema", opts.schema);
39157
- if (opts.dryRun)
39158
- cmdArgs.push("--dry-run");
39159
- const script = `if command -v hindsight-admin >/dev/null 2>&1; then exec hindsight-admin "$@"; fi; ` + `exec ${HINDSIGHT_ADMIN_BIN} "$@"`;
39160
- return [
39161
- "exec",
39162
- opts.container ?? HINDSIGHT_CONTAINER_NAME,
39163
- "sh",
39164
- "-c",
39165
- script,
39166
- "--",
39167
- ...cmdArgs
39168
- ];
39169
- }
39170
- function parseRepairSummary(stdout) {
39171
- const m = stdout.match(/Done:\s*(\d+)\s+schema\(s\),\s*(\d+)\s+bank\(s\) scanned,\s*(\d+)\s+already present,\s*(\d+)\s+created,\s*(\d+)\s+to-create \(dry-run\),\s*(\d+)\s+failed/);
39172
- if (!m)
39173
- return null;
39174
- return {
39175
- schemas: Number(m[1]),
39176
- banksScanned: Number(m[2]),
39177
- alreadyPresent: Number(m[3]),
39178
- created: Number(m[4]),
39179
- toCreate: Number(m[5]),
39180
- failed: Number(m[6])
39181
- };
39182
- }
39183
- function compareApiVersion(a, b) {
39184
- const part = (v, i) => {
39185
- const raw = v.split(".")[i];
39186
- if (raw === undefined)
39187
- return 0;
39188
- const n = parseInt(raw, 10);
39189
- return Number.isFinite(n) ? n : 0;
39190
- };
39191
- const len = Math.max(a.split(".").length, b.split(".").length);
39192
- for (let i = 0;i < len; i++) {
39193
- const d = part(a, i) - part(b, i);
39194
- if (d !== 0)
39195
- return d;
39196
- }
39197
- return 0;
39198
- }
39199
- async function fetchHindsightApiVersion(mcpUrl, opts = {}) {
39200
- const doFetch = opts.fetchImpl ?? fetch;
39201
- const origin = mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
39202
- const controller = new AbortController;
39203
- const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 4000);
39204
- try {
39205
- const res = await doFetch(`${origin}/version`, { signal: controller.signal });
39206
- if (!res.ok)
39207
- return null;
39208
- const body = await res.json();
39209
- return typeof body?.api_version === "string" ? body.api_version : null;
39210
- } catch {
39211
- return null;
39212
- } finally {
39213
- clearTimeout(timer);
39214
- }
39215
- }
39216
- function classifyRepairPreflight(liveVersion) {
39217
- if (liveVersion === null)
39218
- return { ok: true };
39219
- if (compareApiVersion(liveVersion, HINDSIGHT_REPAIR_MIN_API_VERSION) < 0) {
39220
- return {
39221
- ok: false,
39222
- reason: `hindsight ${liveVersion} has no \`repair-bank\` command \u2014 it lands in ` + `${HINDSIGHT_REPAIR_MIN_API_VERSION} (upstream #2645). Without it there is no ` + `supported way to rebuild a bank's vector-index coverage. Update the ` + `pinned image in docker/Dockerfile.hindsight and recreate the container ` + `with \`switchroom memory --update\`, then re-run this command.`
39223
- };
39224
- }
39225
- return { ok: true };
39226
- }
39227
- function classifyRepairOutcome(run) {
39228
- if (run.childExit !== 0) {
39229
- return {
39230
- exit: REPAIR_FAILED_EXIT,
39231
- level: "fail",
39232
- headline: `repair-bank exited ${run.childExit}.`,
39233
- detail: run.childExit === 2 ? "Exit 2 from the admin CLI is a usage error \u2014 check --bank / --schema spelling. " + "Nothing was repaired and coverage is unknown." : "Failed indexes are dropped, so a re-run is safe and is the documented retry."
39234
- };
39235
- }
39236
- if (run.summary === null) {
39237
- return {
39238
- exit: REPAIR_UNVERIFIED_EXIT,
39239
- level: "fail",
39240
- headline: "repair-bank exited 0 but printed no summary \u2014 coverage was NOT confirmed.",
39241
- detail: "Read its output above. This is not a pass: a backend with a single global " + "index, an aborted scan, or an output-format change upstream all look like " + "this, and none of them means coverage is complete."
39242
- };
39243
- }
39244
- if (run.summary.banksScanned === 0) {
39245
- return {
39246
- exit: REPAIR_UNVERIFIED_EXIT,
39247
- level: "fail",
39248
- headline: "repair-bank scanned 0 bank(s) \u2014 nothing was checked.",
39249
- detail: "A scan that examined no bank proves nothing about coverage. Usual cause is a " + "--bank id or --schema name that matches nothing; `switchroom memory banks` " + "lists the real ids."
39250
- };
39251
- }
39252
- if (run.summary.failed > 0) {
39253
- return {
39254
- exit: REPAIR_FAILED_EXIT,
39255
- level: "fail",
39256
- headline: `${run.summary.failed} index(es) failed to build.`,
39257
- detail: "Those banks still under-return on recall. Failed indexes are dropped, so " + "re-running is safe and is the documented retry; if it repeats, the hindsight " + "container logs carry the postgres error."
39258
- };
39259
- }
39260
- if (run.dryRun && run.summary.toCreate > 0) {
39261
- return {
39262
- exit: REPAIR_COVERAGE_MISSING_EXIT,
39263
- level: "fail",
39264
- headline: `${run.summary.toCreate} vector index(es) missing across ${run.summary.banksScanned} bank(s).`,
39265
- detail: "Recall on those banks silently under-returns. Re-run without --dry-run to fix."
39266
- };
39267
- }
39268
- return {
39269
- exit: 0,
39270
- level: "ok",
39271
- headline: "repair-bank finished.",
39272
- detail: run.dryRun ? `Coverage complete: ${run.summary.alreadyPresent} index(es) present across ` + `${run.summary.banksScanned} bank(s), nothing to create.` : `${run.summary.created} created, ${run.summary.alreadyPresent} already present ` + `across ${run.summary.banksScanned} bank(s). Re-run \`switchroom doctor\` to confirm.`
39273
- };
39274
- }
39275
- var HINDSIGHT_CONTAINER_NAME = "switchroom-hindsight", HINDSIGHT_ADMIN_BIN = "/app/api/.venv/bin/hindsight-admin", REPAIR_FAILED_EXIT = 1, REPAIR_COVERAGE_MISSING_EXIT = 3, REPAIR_UNVERIFIED_EXIT = 4;
39276
- var init_hindsight_repair = __esm(() => {
39277
- init_hindsight_tools();
39278
- });
39279
-
39280
39134
  // src/memory/bank-health.ts
39281
39135
  function hindsightRestBase(mcpUrl) {
39282
39136
  return mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
@@ -39446,6 +39300,168 @@ function recentUnextracted(docs, withinDays = 30, now = new Date, minTextLength
39446
39300
  }
39447
39301
  var DOCUMENTS_PAGE_LIMIT = 500;
39448
39302
 
39303
+ // src/memory/hindsight-tools.ts
39304
+ var HINDSIGHT_MIN_API_VERSION = "0.8.5", HINDSIGHT_REPAIR_MIN_API_VERSION = "0.8.5", EXPECTED_HINDSIGHT_TOOLS;
39305
+ var init_hindsight_tools = __esm(() => {
39306
+ EXPECTED_HINDSIGHT_TOOLS = {
39307
+ recall: { required: ["query"] },
39308
+ reflect: { required: ["query"] },
39309
+ retain: { required: ["content"] },
39310
+ sync_retain: { required: ["content"] },
39311
+ delete_document: { required: ["document_id"] },
39312
+ create_directive: { required: ["content", "name"] },
39313
+ list_directives: { required: [] },
39314
+ delete_directive: { required: ["directive_id"] },
39315
+ create_bank: { required: ["bank_id"] },
39316
+ update_bank: { required: [] },
39317
+ list_banks: { required: [] },
39318
+ create_mental_model: { required: ["name", "source_query"] },
39319
+ list_mental_models: { required: [] },
39320
+ update_mental_model: { required: ["mental_model_id"] },
39321
+ refresh_mental_model: { required: ["mental_model_id"] },
39322
+ list_memories: { required: [] },
39323
+ get_memory: { required: ["memory_id"] },
39324
+ update_memory: { required: ["memory_id"] },
39325
+ invalidate_memory: { required: ["memory_id"] }
39326
+ };
39327
+ });
39328
+
39329
+ // src/memory/hindsight-repair.ts
39330
+ function buildRepairBankArgv(opts) {
39331
+ const hasBank = typeof opts.bank === "string" && opts.bank.length > 0;
39332
+ if (hasBank === Boolean(opts.all)) {
39333
+ throw new Error("pass exactly one of --bank <id> or --all");
39334
+ }
39335
+ const cmdArgs = ["repair-bank"];
39336
+ if (hasBank)
39337
+ cmdArgs.push("--bank", opts.bank);
39338
+ if (opts.all)
39339
+ cmdArgs.push("--all");
39340
+ if (opts.schema)
39341
+ cmdArgs.push("--schema", opts.schema);
39342
+ if (opts.dryRun)
39343
+ cmdArgs.push("--dry-run");
39344
+ const script = `if command -v hindsight-admin >/dev/null 2>&1; then exec hindsight-admin "$@"; fi; ` + `exec ${HINDSIGHT_ADMIN_BIN} "$@"`;
39345
+ return [
39346
+ "exec",
39347
+ opts.container ?? HINDSIGHT_CONTAINER_NAME,
39348
+ "sh",
39349
+ "-c",
39350
+ script,
39351
+ "--",
39352
+ ...cmdArgs
39353
+ ];
39354
+ }
39355
+ function parseRepairSummary(stdout) {
39356
+ const m = stdout.match(/Done:\s*(\d+)\s+schema\(s\),\s*(\d+)\s+bank\(s\) scanned,\s*(\d+)\s+already present,\s*(\d+)\s+created,\s*(\d+)\s+to-create \(dry-run\),\s*(\d+)\s+failed/);
39357
+ if (!m)
39358
+ return null;
39359
+ return {
39360
+ schemas: Number(m[1]),
39361
+ banksScanned: Number(m[2]),
39362
+ alreadyPresent: Number(m[3]),
39363
+ created: Number(m[4]),
39364
+ toCreate: Number(m[5]),
39365
+ failed: Number(m[6])
39366
+ };
39367
+ }
39368
+ function compareApiVersion(a, b) {
39369
+ const part = (v, i) => {
39370
+ const raw = v.split(".")[i];
39371
+ if (raw === undefined)
39372
+ return 0;
39373
+ const n = parseInt(raw, 10);
39374
+ return Number.isFinite(n) ? n : 0;
39375
+ };
39376
+ const len = Math.max(a.split(".").length, b.split(".").length);
39377
+ for (let i = 0;i < len; i++) {
39378
+ const d = part(a, i) - part(b, i);
39379
+ if (d !== 0)
39380
+ return d;
39381
+ }
39382
+ return 0;
39383
+ }
39384
+ async function fetchHindsightApiVersion(mcpUrl, opts = {}) {
39385
+ const doFetch = opts.fetchImpl ?? fetch;
39386
+ const origin = mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
39387
+ const controller = new AbortController;
39388
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 4000);
39389
+ try {
39390
+ const res = await doFetch(`${origin}/version`, { signal: controller.signal });
39391
+ if (!res.ok)
39392
+ return null;
39393
+ const body = await res.json();
39394
+ return typeof body?.api_version === "string" ? body.api_version : null;
39395
+ } catch {
39396
+ return null;
39397
+ } finally {
39398
+ clearTimeout(timer);
39399
+ }
39400
+ }
39401
+ function classifyRepairPreflight(liveVersion) {
39402
+ if (liveVersion === null)
39403
+ return { ok: true };
39404
+ if (compareApiVersion(liveVersion, HINDSIGHT_REPAIR_MIN_API_VERSION) < 0) {
39405
+ return {
39406
+ ok: false,
39407
+ reason: `hindsight ${liveVersion} has no \`repair-bank\` command \u2014 it lands in ` + `${HINDSIGHT_REPAIR_MIN_API_VERSION} (upstream #2645). Without it there is no ` + `supported way to rebuild a bank's vector-index coverage. Update the ` + `pinned image in docker/Dockerfile.hindsight and recreate the container ` + `with \`switchroom memory --update\`, then re-run this command.`
39408
+ };
39409
+ }
39410
+ return { ok: true };
39411
+ }
39412
+ function classifyRepairOutcome(run) {
39413
+ if (run.childExit !== 0) {
39414
+ return {
39415
+ exit: REPAIR_FAILED_EXIT,
39416
+ level: "fail",
39417
+ headline: `repair-bank exited ${run.childExit}.`,
39418
+ detail: run.childExit === 2 ? "Exit 2 from the admin CLI is a usage error \u2014 check --bank / --schema spelling. " + "Nothing was repaired and coverage is unknown." : "Failed indexes are dropped, so a re-run is safe and is the documented retry."
39419
+ };
39420
+ }
39421
+ if (run.summary === null) {
39422
+ return {
39423
+ exit: REPAIR_UNVERIFIED_EXIT,
39424
+ level: "fail",
39425
+ headline: "repair-bank exited 0 but printed no summary \u2014 coverage was NOT confirmed.",
39426
+ detail: "Read its output above. This is not a pass: a backend with a single global " + "index, an aborted scan, or an output-format change upstream all look like " + "this, and none of them means coverage is complete."
39427
+ };
39428
+ }
39429
+ if (run.summary.banksScanned === 0) {
39430
+ return {
39431
+ exit: REPAIR_UNVERIFIED_EXIT,
39432
+ level: "fail",
39433
+ headline: "repair-bank scanned 0 bank(s) \u2014 nothing was checked.",
39434
+ detail: "A scan that examined no bank proves nothing about coverage. Usual cause is a " + "--bank id or --schema name that matches nothing; `switchroom memory banks` " + "lists the real ids."
39435
+ };
39436
+ }
39437
+ if (run.summary.failed > 0) {
39438
+ return {
39439
+ exit: REPAIR_FAILED_EXIT,
39440
+ level: "fail",
39441
+ headline: `${run.summary.failed} index(es) failed to build.`,
39442
+ detail: "Those banks still under-return on recall. Failed indexes are dropped, so " + "re-running is safe and is the documented retry; if it repeats, the hindsight " + "container logs carry the postgres error."
39443
+ };
39444
+ }
39445
+ if (run.dryRun && run.summary.toCreate > 0) {
39446
+ return {
39447
+ exit: REPAIR_COVERAGE_MISSING_EXIT,
39448
+ level: "fail",
39449
+ headline: `${run.summary.toCreate} vector index(es) missing across ${run.summary.banksScanned} bank(s).`,
39450
+ detail: "Recall on those banks silently under-returns. Re-run without --dry-run to fix."
39451
+ };
39452
+ }
39453
+ return {
39454
+ exit: 0,
39455
+ level: "ok",
39456
+ headline: "repair-bank finished.",
39457
+ detail: run.dryRun ? `Coverage complete: ${run.summary.alreadyPresent} index(es) present across ` + `${run.summary.banksScanned} bank(s), nothing to create.` : `${run.summary.created} created, ${run.summary.alreadyPresent} already present ` + `across ${run.summary.banksScanned} bank(s). Re-run \`switchroom doctor\` to confirm.`
39458
+ };
39459
+ }
39460
+ var HINDSIGHT_CONTAINER_NAME = "switchroom-hindsight", HINDSIGHT_ADMIN_BIN = "/app/api/.venv/bin/hindsight-admin", REPAIR_FAILED_EXIT = 1, REPAIR_COVERAGE_MISSING_EXIT = 3, REPAIR_UNVERIFIED_EXIT = 4;
39461
+ var init_hindsight_repair = __esm(() => {
39462
+ init_hindsight_tools();
39463
+ });
39464
+
39449
39465
  // src/host-control/audit-rotation-config.ts
39450
39466
  var DEFAULT_HOSTD_AUDIT_MAX_BYTES, DEFAULT_HOSTD_AUDIT_MAX_FILES = 3;
39451
39467
  var init_audit_rotation_config = __esm(() => {
@@ -45781,6 +45797,8 @@ function classifyObservationScopeSaturation(reports) {
45781
45797
  }
45782
45798
  const unreadable = reports.filter((r) => !r.ok);
45783
45799
  const readable = reports.filter((r) => r.ok);
45800
+ const growing = readable.filter((r) => r.observationsEnabled && r.untaggedCount >= OBSERVATION_SCOPE_UNTAGGED_WARN_COUNT).sort((a, b) => b.untaggedCount - a.untaggedCount);
45801
+ const growthNote = growing.length > 0 ? `${growing.length} bank(s) with an untagged global scope over ` + `${OBSERVATION_SCOPE_UNTAGGED_WARN_COUNT.toLocaleString()} observations ` + `(uncapped curated sink, growth watch): ` + growing.slice(0, OBSERVATION_SCOPE_MAX_LISTED).map((r) => `${r.bankId} ${r.untaggedCount.toLocaleString()}`).join(", ") + (growing.length > OBSERVATION_SCOPE_MAX_LISTED ? `; +${growing.length - OBSERVATION_SCOPE_MAX_LISTED} more` : "") : "";
45784
45802
  if (readable.length === 0) {
45785
45803
  return {
45786
45804
  name,
@@ -45803,7 +45821,11 @@ function classifyObservationScopeSaturation(reports) {
45803
45821
  notes.push(`${disabled.length} bank(s) have observations disabled: ` + disabled.map((r) => r.bankId).join(", "));
45804
45822
  }
45805
45823
  const suffix = notes.length > 0 ? ` \u00b7 ${notes.join(" \u00b7 ")}` : "";
45824
+ const growthSuffix = growthNote ? ` \u00b7 ${growthNote}` : "";
45806
45825
  if (judged.length === 0) {
45826
+ if (growing.length > 0) {
45827
+ return { name, status: "warn", detail: `${growthNote}${suffix}` };
45828
+ }
45807
45829
  return { name, status: "skip", detail: `no bank could be judged${suffix}` };
45808
45830
  }
45809
45831
  const closed = judged.filter((r) => r.cap === 0);
@@ -45835,17 +45857,14 @@ function classifyObservationScopeSaturation(reports) {
45835
45857
  return {
45836
45858
  name,
45837
45859
  status: "fail",
45838
- detail: `${failing.length} observation scope(s) at or over the cap \u2014 consolidation ` + `output is being discarded on them right now: ${list(failing)}` + (warning.length > 0 ? ` \u00b7 ${warning.length} more above ${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}%` : "") + backlog([...failing, ...warning]) + suffix,
45860
+ detail: `${failing.length} observation scope(s) at or over the cap \u2014 consolidation ` + `output is being discarded on them right now: ${list(failing)}` + (warning.length > 0 ? ` \u00b7 ${warning.length} more above ${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}%` : "") + backlog([...failing, ...warning]) + growthSuffix + suffix,
45839
45861
  fix
45840
45862
  };
45841
45863
  }
45842
- if (warning.length > 0) {
45843
- return {
45844
- name,
45845
- status: "warn",
45846
- detail: `${warning.length} observation scope(s) above ` + `${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}% of the cap and still writable: ` + `${list(warning)}` + backlog(warning) + suffix,
45847
- fix
45848
- };
45864
+ if (warning.length > 0 || growing.length > 0) {
45865
+ const scopeClause = warning.length > 0 ? `${warning.length} observation scope(s) above ` + `${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}% of the cap and still writable: ` + `${list(warning)}` + backlog(warning) : "";
45866
+ const detail = warning.length > 0 ? scopeClause + growthSuffix + suffix : `${growthNote}${suffix}`;
45867
+ return { name, status: "warn", detail, ...warning.length > 0 ? { fix } : {} };
45849
45868
  }
45850
45869
  const totalScopes = capped.reduce((n, r) => n + r.scopeCount, 0);
45851
45870
  const detailBase = capped.length === 0 ? `no bank has a positive observation cap` : `${totalScopes} scope(s) across ${capped.length} bank(s) below ` + `${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}% of the cap`;
@@ -45862,7 +45881,7 @@ function classifyObservationScopeSaturation(reports) {
45862
45881
  detail: detailBase + (extra.length > 0 ? ` \u00b7 ${extra.join(" \u00b7 ")}` : "") + suffix
45863
45882
  };
45864
45883
  }
45865
- async function getJson2(url, opts) {
45884
+ async function getJson3(url, opts) {
45866
45885
  const fetchImpl = opts?.fetchImpl ?? fetch;
45867
45886
  const timeoutMs = opts?.timeoutMs ?? 15000;
45868
45887
  const controller = new AbortController;
@@ -45891,10 +45910,11 @@ async function inspectBankScopes(apiUrl, bankId, opts) {
45891
45910
  observationsEnabled: false,
45892
45911
  hasScopeLimitRules: false,
45893
45912
  scopeCount: 0,
45913
+ untaggedCount: 0,
45894
45914
  saturated: [],
45895
45915
  pendingConsolidation: null
45896
45916
  });
45897
- const cfg = await getJson2(`${base}/v1/default/banks/${bank}/config`, opts);
45917
+ const cfg = await getJson3(`${base}/v1/default/banks/${bank}/config`, opts);
45898
45918
  if (!cfg.ok)
45899
45919
  return fail4(cfg.reason);
45900
45920
  const config = cfg.data?.config;
@@ -45907,7 +45927,7 @@ async function inspectBankScopes(apiUrl, bankId, opts) {
45907
45927
  const rules = config["observation_scope_limits"];
45908
45928
  const hasScopeLimitRules = Array.isArray(rules) && rules.length > 0;
45909
45929
  const observationsEnabled = config["enable_observations"] !== false;
45910
- const scopesResp = await getJson2(`${base}/v1/default/banks/${bank}/observations/scopes`, opts);
45930
+ const scopesResp = await getJson3(`${base}/v1/default/banks/${bank}/observations/scopes`, opts);
45911
45931
  if (!scopesResp.ok)
45912
45932
  return fail4(scopesResp.reason);
45913
45933
  const rawScopes = scopesResp.data?.scopes;
@@ -45924,9 +45944,10 @@ async function inspectBankScopes(apiUrl, bankId, opts) {
45924
45944
  scopes.push({ tags, count });
45925
45945
  }
45926
45946
  const saturated = computeScopeSaturation(scopes, rawCap).filter((s) => s.status !== "ok");
45947
+ const untaggedCount = scopes.filter((s) => s.tags.length === 0).reduce((n, s) => n + s.count, 0);
45927
45948
  let pendingConsolidation = null;
45928
45949
  if (saturated.length > 0 && !hasScopeLimitRules && observationsEnabled) {
45929
- const stats = await getJson2(`${base}/v1/default/banks/${bank}/stats`, opts);
45950
+ const stats = await getJson3(`${base}/v1/default/banks/${bank}/stats`, opts);
45930
45951
  if (stats.ok && typeof stats.data?.pending_consolidation === "number") {
45931
45952
  pendingConsolidation = stats.data.pending_consolidation;
45932
45953
  }
@@ -45938,6 +45959,7 @@ async function inspectBankScopes(apiUrl, bankId, opts) {
45938
45959
  observationsEnabled,
45939
45960
  hasScopeLimitRules,
45940
45961
  scopeCount: scopes.length,
45962
+ untaggedCount,
45941
45963
  saturated,
45942
45964
  pendingConsolidation
45943
45965
  };
@@ -45953,7 +45975,7 @@ async function checkObservationScopeSaturation(config, apiUrl, opts) {
45953
45975
  const reports = await Promise.all([...banks].map((bankId) => inspectBankScopes(apiUrl, bankId, opts)));
45954
45976
  return classifyObservationScopeSaturation(reports);
45955
45977
  }
45956
- var OBSERVATION_SCOPE_CHECK_NAME = "hindsight observation scopes", OBSERVATION_SCOPE_WARN_RATIO = 0.8, OBSERVATION_SCOPE_MAX_LISTED = 6;
45978
+ var OBSERVATION_SCOPE_CHECK_NAME = "hindsight observation scopes", OBSERVATION_SCOPE_WARN_RATIO = 0.8, OBSERVATION_SCOPE_MAX_LISTED = 6, OBSERVATION_SCOPE_UNTAGGED_WARN_COUNT = 1e4;
45957
45979
  var init_doctor_observation_scopes = __esm(() => {
45958
45980
  init_merge();
45959
45981
  init_hindsight2();
@@ -45987,6 +46009,38 @@ function installCron(opts) {
45987
46009
  renameSync15(tmp, path6);
45988
46010
  return { status: "installed", path: path6, content };
45989
46011
  }
46012
+ function parseCronUser(content) {
46013
+ for (const line of content.split(`
46014
+ `)) {
46015
+ const trimmed = line.trim();
46016
+ if (!trimmed || trimmed.startsWith("#"))
46017
+ continue;
46018
+ if (!trimmed.includes("hindsight-watch"))
46019
+ continue;
46020
+ const fields = trimmed.split(/\s+/);
46021
+ if (fields.length >= 7)
46022
+ return fields[5];
46023
+ }
46024
+ return null;
46025
+ }
46026
+ function reconcileCron(opts) {
46027
+ const path6 = opts.path ?? CRON_PATH;
46028
+ if (!existsSync63(path6))
46029
+ return { status: "absent", path: path6 };
46030
+ let user = opts.fallbackUser;
46031
+ try {
46032
+ user = parseCronUser(readFileSync60(path6, "utf8")) ?? opts.fallbackUser;
46033
+ } catch {}
46034
+ if (!user) {
46035
+ throw new Error(`cannot reconcile ${path6}: no cron-user parseable from the existing fragment and no fallback user available`);
46036
+ }
46037
+ const r = installCron({ user, binary: opts.binary, path: path6 });
46038
+ return {
46039
+ status: r.status === "installed" ? "reconciled" : "unchanged",
46040
+ path: path6,
46041
+ content: r.content
46042
+ };
46043
+ }
45990
46044
  var CRON_PATH = "/etc/cron.d/hindsight-watch", CRON_SCHEDULE = "*/15 * * * *", CRON_LOG_PATH = "/var/log/hindsight-watch.log", CRON_LOCK_PATH = "/run/lock/hindsight-watch.lock";
45991
46045
  var init_install_cron = () => {};
45992
46046
 
@@ -51689,6 +51743,9 @@ import { spawnSync as spawnSync12 } from "node:child_process";
51689
51743
  function fixFor(c, target) {
51690
51744
  return remediationFor(classifyComponent(c), target);
51691
51745
  }
51746
+ function hostCliBehindStatus(c) {
51747
+ return c.kind === "cli" ? "fail" : "warn";
51748
+ }
51692
51749
  function runComponentVersionChecks(config, opts = {}) {
51693
51750
  const exec = opts.exec ?? defaultExec2;
51694
51751
  const components = collectComponents(opts.cliVersion ?? SWITCHROOM_VERSION, exec);
@@ -51712,10 +51769,11 @@ function runComponentVersionChecks(config, opts = {}) {
51712
51769
  });
51713
51770
  }
51714
51771
  for (const c of report.behind) {
51772
+ const status = hostCliBehindStatus(c);
51715
51773
  results.push({
51716
51774
  name: `component behind: ${c.name}`,
51717
- status: "warn",
51718
- detail: `on ${c.version}, expected ${report.target} (${src})`,
51775
+ status,
51776
+ detail: status === "fail" ? `on ${c.version}, expected ${report.target} (${src}) \u2014 the host CLI must never trail the fleet; it self-heals in-band via \`switchroom update\`` : `on ${c.version}, expected ${report.target} (${src})`,
51719
51777
  fix: fixFor(c, report.target)
51720
51778
  });
51721
51779
  }
@@ -68980,9 +69038,9 @@ __export(exports_server, {
68980
69038
  dispatchTool: () => dispatchTool,
68981
69039
  TOOLS: () => TOOLS
68982
69040
  });
68983
- import { spawnSync as spawnSync22 } from "node:child_process";
69041
+ import { spawnSync as spawnSync23 } from "node:child_process";
68984
69042
  function execCli(args, stdin) {
68985
- const r = spawnSync22(CLI_BIN, args, {
69043
+ const r = spawnSync23(CLI_BIN, args, {
68986
69044
  encoding: "utf-8",
68987
69045
  env: process.env,
68988
69046
  timeout: 15000,
@@ -69491,7 +69549,7 @@ __export(exports_server2, {
69491
69549
  TOOLS: () => TOOLS2
69492
69550
  });
69493
69551
  import { randomBytes as randomBytes16 } from "node:crypto";
69494
- import { existsSync as existsSync105 } from "node:fs";
69552
+ import { existsSync as existsSync106 } from "node:fs";
69495
69553
  function selfSocketPath() {
69496
69554
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
69497
69555
  }
@@ -69518,7 +69576,7 @@ async function dispatchTool2(name, args) {
69518
69576
  return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
69519
69577
  }
69520
69578
  const sockPath = selfSocketPath();
69521
- if (!existsSync105(sockPath)) {
69579
+ if (!existsSync106(sockPath)) {
69522
69580
  return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
69523
69581
  }
69524
69582
  let req;
@@ -69769,13 +69827,13 @@ function resolveAuditLogPath() {
69769
69827
  if (process.env.HOSTD_AUDIT_LOG_PATH)
69770
69828
  return process.env.HOSTD_AUDIT_LOG_PATH;
69771
69829
  const bindMounted = "/host-home/.switchroom/host-control-audit.log";
69772
- if (existsSync105(bindMounted))
69830
+ if (existsSync106(bindMounted))
69773
69831
  return bindMounted;
69774
69832
  return defaultAuditLogPath2();
69775
69833
  }
69776
69834
  function getLastUpdateApplyStatus() {
69777
69835
  const path10 = resolveAuditLogPath();
69778
- if (!existsSync105(path10)) {
69836
+ if (!existsSync106(path10)) {
69779
69837
  return errorText2(`get_status: audit log not found at ${path10}. No update_apply has run yet?`);
69780
69838
  }
69781
69839
  let raw;
@@ -70494,11 +70552,11 @@ var init_test_agents = __esm(() => {
70494
70552
  });
70495
70553
 
70496
70554
  // src/litellm/header-passthrough-guard.ts
70497
- import { existsSync as existsSync108, readdirSync as readdirSync41 } from "node:fs";
70555
+ import { existsSync as existsSync109, readdirSync as readdirSync41 } from "node:fs";
70498
70556
  function discoverLiveLitellmConfigPath(opts) {
70499
70557
  const servicesDir = opts?.servicesDir ?? COOLIFY_SERVICES_DIR;
70500
70558
  const readdir2 = opts?.readdirFn ?? ((p) => readdirSync41(p));
70501
- const exists = opts?.existsFn ?? existsSync108;
70559
+ const exists = opts?.existsFn ?? existsSync109;
70502
70560
  let entries;
70503
70561
  try {
70504
70562
  entries = readdir2(servicesDir);
@@ -70588,7 +70646,7 @@ var init_header_passthrough_guard = __esm(() => {
70588
70646
  });
70589
70647
 
70590
70648
  // src/fleet-health/litellm-config-sensor.ts
70591
- import { readFileSync as readFileSync97, existsSync as existsSync109 } from "node:fs";
70649
+ import { readFileSync as readFileSync98, existsSync as existsSync110 } from "node:fs";
70592
70650
  function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitellmConfigPath()) {
70593
70651
  if (explicit)
70594
70652
  return explicit;
@@ -70599,8 +70657,8 @@ function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitel
70599
70657
  }
70600
70658
  function scanLitellmConfig(opts = {}) {
70601
70659
  const path10 = resolveLitellmConfigPath(opts.path, opts.discoverFn);
70602
- const exists = opts.existsFn ?? existsSync109;
70603
- const read = opts.readFn ?? ((p) => readFileSync97(p, "utf-8"));
70660
+ const exists = opts.existsFn ?? existsSync110;
70661
+ const read = opts.readFn ?? ((p) => readFileSync98(p, "utf-8"));
70604
70662
  const log = opts.log ?? (() => {});
70605
70663
  const nowIso = opts.nowIso ?? new Date().toISOString();
70606
70664
  if (path10 === null) {
@@ -70675,20 +70733,20 @@ __export(exports_scan, {
70675
70733
  ledgerPathForBase: () => ledgerPathForBase
70676
70734
  });
70677
70735
  import {
70678
- readFileSync as readFileSync98,
70736
+ readFileSync as readFileSync99,
70679
70737
  readdirSync as readdirSync42,
70680
- existsSync as existsSync110,
70681
- mkdirSync as mkdirSync63,
70682
- writeFileSync as writeFileSync46
70738
+ existsSync as existsSync111,
70739
+ mkdirSync as mkdirSync64,
70740
+ writeFileSync as writeFileSync47
70683
70741
  } from "node:fs";
70684
- import { resolve as resolve61, dirname as dirname42 } from "node:path";
70742
+ import { resolve as resolve61, dirname as dirname43 } from "node:path";
70685
70743
  import { homedir as homedir61 } from "node:os";
70686
70744
  function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir61()) {
70687
70745
  return resolve61(home2, ".switchroom");
70688
70746
  }
70689
70747
  function listAgents(base) {
70690
70748
  const dir = resolve61(base, "agents");
70691
- if (!existsSync110(dir))
70749
+ if (!existsSync111(dir))
70692
70750
  return [];
70693
70751
  try {
70694
70752
  return readdirSync42(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
@@ -70717,16 +70775,16 @@ function runScan(opts = {}) {
70717
70775
  let gwText = "";
70718
70776
  let sawArtifact = false;
70719
70777
  try {
70720
- if (existsSync110(turnsPath)) {
70721
- turnsText = readFileSync98(turnsPath, "utf-8");
70778
+ if (existsSync111(turnsPath)) {
70779
+ turnsText = readFileSync99(turnsPath, "utf-8");
70722
70780
  sawArtifact = true;
70723
70781
  }
70724
70782
  } catch (e) {
70725
70783
  log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
70726
70784
  }
70727
70785
  try {
70728
- if (existsSync110(gwPath)) {
70729
- gwText = readFileSync98(gwPath, "utf-8");
70786
+ if (existsSync111(gwPath)) {
70787
+ gwText = readFileSync99(gwPath, "utf-8");
70730
70788
  sawArtifact = true;
70731
70789
  }
70732
70790
  } catch (e) {
@@ -70768,20 +70826,20 @@ function runScan(opts = {}) {
70768
70826
  function readLedgerIfPresent(base) {
70769
70827
  const path10 = ledgerPathForBase(base);
70770
70828
  try {
70771
- if (!existsSync110(path10))
70829
+ if (!existsSync111(path10))
70772
70830
  return null;
70773
- return JSON.parse(readFileSync98(path10, "utf-8"));
70831
+ return JSON.parse(readFileSync99(path10, "utf-8"));
70774
70832
  } catch {
70775
70833
  return null;
70776
70834
  }
70777
70835
  }
70778
70836
  function ledgerPathForBase(base) {
70779
- return fleetHealthLedgerPath(dirname42(base));
70837
+ return fleetHealthLedgerPath(dirname43(base));
70780
70838
  }
70781
70839
  function writeLedger(base, ledger) {
70782
70840
  const path10 = ledgerPathForBase(base);
70783
- mkdirSync63(dirname42(path10), { recursive: true });
70784
- writeFileSync46(path10, JSON.stringify(ledger, null, 2) + `
70841
+ mkdirSync64(dirname43(path10), { recursive: true });
70842
+ writeFileSync47(path10, JSON.stringify(ledger, null, 2) + `
70785
70843
  `, "utf-8");
70786
70844
  return path10;
70787
70845
  }
@@ -85438,6 +85496,174 @@ function reflectAcrossAgents(config) {
85438
85496
  return { eligible, excluded, commands };
85439
85497
  }
85440
85498
 
85499
+ // src/memory/observation-scope-cleanup.ts
85500
+ var CLEANUP_VOLATILE_PATTERNS = [
85501
+ /^parent_session:/,
85502
+ /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(-sub-.+)?$/
85503
+ ];
85504
+ var CLEANUP_STALE_PATTERNS = [/^agent-[0-9a-f]{8,}$/];
85505
+ function matchesAny(tag, patterns) {
85506
+ return patterns.some((p) => p.test(tag));
85507
+ }
85508
+ function scopeKey(tags) {
85509
+ return tags.length === 0 ? "shared" : [...tags].sort().join(",");
85510
+ }
85511
+ function planScopeCleanup(bankId, scopes, options = {}) {
85512
+ const volatilePatterns = options.volatilePatterns ?? CLEANUP_VOLATILE_PATTERNS;
85513
+ const stalePatterns = options.stalePatterns ?? CLEANUP_STALE_PATTERNS;
85514
+ const retireStale = options.retireStale ?? false;
85515
+ const totalScopes = scopes.length;
85516
+ const totalObservations = scopes.reduce((n, s) => n + s.count, 0);
85517
+ const reclassified = [];
85518
+ const breakdown = {
85519
+ allVolatileScopes: 0,
85520
+ allVolatileObservations: 0,
85521
+ mixedScopes: 0,
85522
+ mixedObservations: 0,
85523
+ staleScopes: 0,
85524
+ staleObservations: 0
85525
+ };
85526
+ const targets = new Map;
85527
+ for (const s of scopes) {
85528
+ const volatileTags = s.tags.filter((t) => matchesAny(t, volatilePatterns));
85529
+ const staleTags = s.tags.filter((t) => matchesAny(t, stalePatterns));
85530
+ const stripped = s.tags.filter((t) => {
85531
+ if (matchesAny(t, volatilePatterns))
85532
+ return false;
85533
+ if (retireStale && matchesAny(t, stalePatterns))
85534
+ return false;
85535
+ return true;
85536
+ });
85537
+ const toKey = scopeKey(stripped);
85538
+ const changed = scopeKey(s.tags) !== toKey;
85539
+ const hasStable = s.tags.some((t) => !matchesAny(t, volatilePatterns) && !matchesAny(t, stalePatterns));
85540
+ if (staleTags.length > 0) {
85541
+ breakdown.staleScopes += 1;
85542
+ breakdown.staleObservations += s.count;
85543
+ }
85544
+ if (volatileTags.length > 0 && !hasStable && staleTags.length === 0) {
85545
+ breakdown.allVolatileScopes += 1;
85546
+ breakdown.allVolatileObservations += s.count;
85547
+ } else if (volatileTags.length > 0 && hasStable) {
85548
+ breakdown.mixedScopes += 1;
85549
+ breakdown.mixedObservations += s.count;
85550
+ }
85551
+ const entry = targets.get(toKey) ?? { sources: 0, observations: 0, changed: false };
85552
+ entry.sources += 1;
85553
+ entry.observations += s.count;
85554
+ entry.changed = entry.changed || changed;
85555
+ targets.set(toKey, entry);
85556
+ if (changed) {
85557
+ reclassified.push({
85558
+ from: s.tags,
85559
+ to: toKey,
85560
+ count: s.count,
85561
+ strippedVolatile: volatileTags,
85562
+ strippedStale: retireStale ? staleTags : []
85563
+ });
85564
+ }
85565
+ }
85566
+ reclassified.sort((a, b) => b.count - a.count);
85567
+ const merges = [...targets.entries()].filter(([, v]) => v.sources > 1 && v.changed).map(([target, v]) => ({ target, sourceScopes: v.sources, observations: v.observations })).sort((a, b) => b.observations - a.observations);
85568
+ const affectedObservations = reclassified.reduce((n, r) => n + r.count, 0);
85569
+ const resultingScopeCount = targets.size;
85570
+ return {
85571
+ bankId,
85572
+ ok: true,
85573
+ totalScopes,
85574
+ totalObservations,
85575
+ reclassified,
85576
+ merges,
85577
+ affectedScopes: reclassified.length,
85578
+ affectedObservations,
85579
+ resultingScopeCount,
85580
+ scopeReduction: totalScopes - resultingScopeCount,
85581
+ breakdown
85582
+ };
85583
+ }
85584
+ async function getJson2(url, opts) {
85585
+ const fetchImpl = opts?.fetchImpl ?? fetch;
85586
+ const timeoutMs = opts?.timeoutMs ?? 15000;
85587
+ const controller = new AbortController;
85588
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
85589
+ try {
85590
+ const resp = await fetchImpl(url, { signal: controller.signal });
85591
+ clearTimeout(timeout);
85592
+ if (!resp.ok)
85593
+ return { ok: false, reason: `HTTP ${resp.status}` };
85594
+ return { ok: true, data: await resp.json() };
85595
+ } catch (err) {
85596
+ clearTimeout(timeout);
85597
+ if (err.name === "AbortError")
85598
+ return { ok: false, reason: "Timeout" };
85599
+ return { ok: false, reason: String(err.message ?? err) };
85600
+ }
85601
+ }
85602
+ async function planBankCleanup(apiUrl, bankId, options = {}, fetchOpts) {
85603
+ const base = hindsightRestBase(apiUrl);
85604
+ const bank = encodeURIComponent(bankId);
85605
+ const resp = await getJson2(`${base}/v1/default/banks/${bank}/observations/scopes`, fetchOpts);
85606
+ if (!resp.ok) {
85607
+ return {
85608
+ bankId,
85609
+ ok: false,
85610
+ reason: resp.reason,
85611
+ totalScopes: 0,
85612
+ totalObservations: 0,
85613
+ reclassified: [],
85614
+ merges: [],
85615
+ affectedScopes: 0,
85616
+ affectedObservations: 0,
85617
+ resultingScopeCount: 0,
85618
+ scopeReduction: 0,
85619
+ breakdown: {
85620
+ allVolatileScopes: 0,
85621
+ allVolatileObservations: 0,
85622
+ mixedScopes: 0,
85623
+ mixedObservations: 0,
85624
+ staleScopes: 0,
85625
+ staleObservations: 0
85626
+ }
85627
+ };
85628
+ }
85629
+ const raw = resp.data?.scopes;
85630
+ const scopes = [];
85631
+ if (Array.isArray(raw)) {
85632
+ for (const entry of raw) {
85633
+ const tags = entry?.tags;
85634
+ const count = entry?.count;
85635
+ if (!Array.isArray(tags) || typeof count !== "number")
85636
+ continue;
85637
+ if (!tags.every((t) => typeof t === "string"))
85638
+ continue;
85639
+ scopes.push({ tags, count });
85640
+ }
85641
+ }
85642
+ return planScopeCleanup(bankId, scopes, options);
85643
+ }
85644
+ function formatCleanupPlan(plan, maxMerges = 10) {
85645
+ if (!plan.ok) {
85646
+ return `bank ${plan.bankId}: UNREADABLE (${plan.reason ?? "unknown"})`;
85647
+ }
85648
+ const b = plan.breakdown;
85649
+ const lines = [];
85650
+ lines.push(`bank ${plan.bankId}`);
85651
+ lines.push(` today: ${plan.totalScopes} scopes, ${plan.totalObservations.toLocaleString()} observations`);
85652
+ lines.push(` legacy fragmentation:` + ` all-volatile ${b.allVolatileScopes} scopes / ${b.allVolatileObservations.toLocaleString()} obs;` + ` mixed ${b.mixedScopes} scopes / ${b.mixedObservations.toLocaleString()} obs;` + ` stale agent-<hex> ${b.staleScopes} scopes / ${b.staleObservations.toLocaleString()} obs`);
85653
+ lines.push(` WOULD change: ${plan.affectedScopes} scopes` + ` (${plan.affectedObservations.toLocaleString()} obs re-homed),` + ` ${plan.totalScopes} \u2192 ${plan.resultingScopeCount} scopes` + ` (\u2212${plan.scopeReduction})`);
85654
+ if (plan.merges.length > 0) {
85655
+ lines.push(` top merges:`);
85656
+ for (const m of plan.merges.slice(0, maxMerges)) {
85657
+ lines.push(` ${m.sourceScopes} scopes \u2192 ${m.target} (${m.observations.toLocaleString()} obs)`);
85658
+ }
85659
+ if (plan.merges.length > maxMerges) {
85660
+ lines.push(` +${plan.merges.length - maxMerges} more merge targets`);
85661
+ }
85662
+ }
85663
+ return lines.join(`
85664
+ `);
85665
+ }
85666
+
85441
85667
  // src/cli/memory.ts
85442
85668
  init_helpers();
85443
85669
  init_hindsight();
@@ -86164,6 +86390,55 @@ Demoting memory ${source_default.cyan(memoryId)}`));
86164
86390
  ` + " If the ID itself is suspect: `switchroom memory recall-log " + agent + " --json`."));
86165
86391
  process.exit(1);
86166
86392
  }));
86393
+ memory.command("cleanup-scopes [agent]").description("Dry-run audit of legacy pre-#4035 observation-scope fragmentation " + "(per-session UUID islands, stale agent-<hex> scopes). Reads only; " + "prints the blast radius of a curated re-home. Pass an agent, or --all.").option("--all", "Audit every agent bank and profile bank").option("--retire-stale", "Also fold stale agent-<hex> producer scopes into shared (opt-in; not part of the #4035 heal)").option("--execute", "(refused) live mutation is not supported \u2014 see the printed reason").option("--json", "Emit the plans as JSON").action(withConfigError(async (agent, opts) => {
86394
+ const config = getConfig(program3);
86395
+ const apiUrl = config.memory?.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
86396
+ if (opts.execute) {
86397
+ console.error(source_default.red("\u2717 --execute is not supported: legacy-scope healing is not mechanizable"));
86398
+ console.error(source_default.gray(` Hindsight exposes no per-memory tag-write (update_memory / PATCH memories/{id}
86399
+ ` + " silently drop a `tags` argument, #3772) and no per-scope delete (DELETE\n" + ` .../observations clears the WHOLE bank). So an observation's scope cannot be
86400
+ ` + ` edited in place, and this tool stays a read-only audit. A live heal needs an
86401
+ ` + ` upstream tag-write path or a deliberate, operator-approved enumerate-and-
86402
+ ` + " invalidate pass \u2014 neither is run from here."));
86403
+ process.exit(1);
86404
+ }
86405
+ const banks = new Set;
86406
+ if (agent) {
86407
+ if (!config.agents[agent]) {
86408
+ console.error(source_default.red(`Agent "${agent}" is not defined in switchroom.yaml`));
86409
+ process.exit(1);
86410
+ }
86411
+ banks.add(getCollectionForAgent(agent, config));
86412
+ } else if (opts.all) {
86413
+ for (const name of Object.keys(config.agents)) {
86414
+ banks.add(getCollectionForAgent(name, config));
86415
+ }
86416
+ for (const bank of collectProfileBanks(config))
86417
+ banks.add(bank);
86418
+ } else {
86419
+ console.error(source_default.red("Pass an agent name, or --all to audit every bank."));
86420
+ process.exit(1);
86421
+ }
86422
+ const plans = await Promise.all([...banks].map((bankId) => planBankCleanup(apiUrl, bankId, { retireStale: opts.retireStale })));
86423
+ plans.sort((a, b) => b.affectedObservations - a.affectedObservations);
86424
+ if (opts.json) {
86425
+ console.log(JSON.stringify(plans, null, 2));
86426
+ return;
86427
+ }
86428
+ console.log(source_default.bold(`
86429
+ Observation-scope cleanup \u2014 DRY RUN (no writes)
86430
+ `));
86431
+ console.log(source_default.gray(` api=${apiUrl} retireStale=${Boolean(opts.retireStale)}
86432
+ `));
86433
+ for (const plan of plans) {
86434
+ console.log(formatCleanupPlan(plan));
86435
+ console.log();
86436
+ }
86437
+ const totalObs = plans.reduce((n, p) => n + p.affectedObservations, 0);
86438
+ const totalScopes = plans.reduce((n, p) => n + p.scopeReduction, 0);
86439
+ console.log(source_default.cyan(`Fleet total (would change): ${totalObs.toLocaleString()} observations re-homed, ` + `${totalScopes.toLocaleString()} scopes removed.`));
86440
+ console.log(source_default.gray("Nothing was written. Live execution is not supported from this tool " + "(--execute prints why)."));
86441
+ }));
86167
86442
  const restBase = (url) => (url ?? HINDSIGHT_DEFAULT_MCP_URL).replace(/\/mcp\/?$/, "").replace(/\/$/, "");
86168
86443
  const VALID_BANK = /^[a-zA-Z0-9_.-]+$/;
86169
86444
  const profile = memory.command("profile").description("Author + inspect operator profile banks (shared/per-user memory; wire via memory.recall.additional_banks)");
@@ -92108,9 +92383,9 @@ init_source();
92108
92383
  init_loader();
92109
92384
  init_lifecycle();
92110
92385
  init_compose_env();
92111
- import { existsSync as existsSync80, mkdirSync as mkdirSync45, readFileSync as readFileSync74, realpathSync as realpathSync6, statSync as statSync45, chownSync as chownSync8 } from "node:fs";
92112
- import { spawnSync as spawnSync16 } from "node:child_process";
92113
- import { join as join84, dirname as dirname33, resolve as resolve45 } from "node:path";
92386
+ import { existsSync as existsSync81, mkdirSync as mkdirSync46, readFileSync as readFileSync75, realpathSync as realpathSync6, statSync as statSync45, chownSync as chownSync8 } from "node:fs";
92387
+ import { spawnSync as spawnSync17 } from "node:child_process";
92388
+ import { join as join84, dirname as dirname34, resolve as resolve45 } from "node:path";
92114
92389
  import { homedir as homedir44 } from "node:os";
92115
92390
 
92116
92391
  // src/cli/release-yaml.ts
@@ -92639,10 +92914,151 @@ function defaultSelfUpdateIO() {
92639
92914
  // src/cli/update.ts
92640
92915
  init_self_invoke();
92641
92916
  init_component_versions();
92917
+ init_install_cron();
92918
+
92919
+ // src/host-cli-self-heal/install-timer.ts
92920
+ import { spawnSync as spawnSync16 } from "node:child_process";
92921
+ import { existsSync as existsSync80, mkdirSync as mkdirSync45, readFileSync as readFileSync74, renameSync as renameSync20, writeFileSync as writeFileSync31 } from "node:fs";
92922
+ import { dirname as dirname33 } from "node:path";
92923
+ var SERVICE_PATH = "/etc/systemd/system/switchroom-self-heal.service";
92924
+ var TIMER_PATH = "/etc/systemd/system/switchroom-self-heal.timer";
92925
+ var TIMER_UNIT = "switchroom-self-heal.timer";
92926
+ var SYSTEMD_MARKER = "/run/systemd/system";
92927
+ var DOC_URL = "https://github.com/switchroom/switchroom/blob/main/docs/operators/host-cli-self-heal.md";
92928
+ function renderService(opts) {
92929
+ const homeLine = opts.home ? `Environment=HOME=${opts.home}
92930
+ ` : "";
92931
+ return `# switchroom host CLI self-heal \u2014 keeps /usr/local/bin/switchroom from silently
92932
+ ` + `# trailing the fleet. Managed by \`switchroom update\`; edits are overwritten.
92933
+ ` + `[Unit]
92934
+ ` + `Description=Switchroom host CLI self-heal (re-run \`switchroom update\` so the host binary can't silently trail the fleet)
92935
+ ` + `Documentation=${DOC_URL}
92936
+ ` + `After=network-online.target docker.service
92937
+ ` + `Wants=network-online.target
92938
+ ` + `
92939
+ ` + `[Service]
92940
+ ` + `Type=oneshot
92941
+ ` + `# Run as the operator whose ~/.switchroom holds the fleet config \u2014 NOT root.
92942
+ ` + `User=${opts.user}
92943
+ ` + homeLine + `# --skip-images keeps each tick cheap: self-update-cli (the host binary swap)
92944
+ ` + `# still runs; GHCR pulls and container recreation do not.
92945
+ ` + `ExecStart=${opts.binary} update --skip-images
92946
+ ` + `# Self-heal must never wedge the host: bound the run and run at low priority.
92947
+ ` + `TimeoutStartSec=600
92948
+ ` + `Nice=10
92949
+ `;
92950
+ }
92951
+ function renderTimer() {
92952
+ return `# switchroom host CLI self-heal timer. Managed by \`switchroom update\`.
92953
+ ` + `[Unit]
92954
+ ` + `Description=Run the Switchroom host CLI self-heal every 30 minutes
92955
+ ` + `Documentation=${DOC_URL}
92956
+ ` + `
92957
+ ` + `[Timer]
92958
+ ` + `OnBootSec=5min
92959
+ ` + `OnUnitActiveSec=30min
92960
+ ` + `RandomizedDelaySec=2min
92961
+ ` + `Persistent=true
92962
+ ` + `
92963
+ ` + `[Install]
92964
+ ` + `WantedBy=timers.target
92965
+ `;
92966
+ }
92967
+ function writeUnitIdempotent(path7, content) {
92968
+ if (existsSync80(path7)) {
92969
+ try {
92970
+ if (readFileSync74(path7, "utf8") === content)
92971
+ return "unchanged";
92972
+ } catch {}
92973
+ }
92974
+ mkdirSync45(dirname33(path7), { recursive: true });
92975
+ const tmp = `${path7}.${process.pid}.tmp`;
92976
+ writeFileSync31(tmp, content, { mode: 420 });
92977
+ renameSync20(tmp, path7);
92978
+ return "written";
92979
+ }
92980
+ function homeFromPasswd(user) {
92981
+ try {
92982
+ for (const line of readFileSync74("/etc/passwd", "utf8").split(`
92983
+ `)) {
92984
+ const f = line.split(":");
92985
+ if (f[0] === user && f[5])
92986
+ return f[5];
92987
+ }
92988
+ } catch {}
92989
+ return;
92990
+ }
92991
+ function resolveOperatorUser(env2) {
92992
+ const user = env2.SUDO_USER ?? env2.USER ?? env2.LOGNAME;
92993
+ if (!user || user === "root")
92994
+ return null;
92995
+ return user;
92996
+ }
92997
+ function installSelfHealTimer(deps = {}) {
92998
+ const env2 = deps.env ?? process.env;
92999
+ const systemdBooted = deps.systemdBooted ?? (() => existsSync80(SYSTEMD_MARKER));
93000
+ const geteuid = deps.geteuid ?? (() => typeof process.geteuid === "function" ? process.geteuid() : undefined);
93001
+ const homeForUser = deps.homeForUser ?? homeFromPasswd;
93002
+ const servicePath = deps.servicePath ?? SERVICE_PATH;
93003
+ const timerPath = deps.timerPath ?? TIMER_PATH;
93004
+ const binary = env2.SWITCHROOM_BINARY ?? "/usr/local/bin/switchroom";
93005
+ const user = resolveOperatorUser(env2);
93006
+ const renderUser = user ?? "OPERATOR";
93007
+ const home2 = user ? homeForUser(user) : undefined;
93008
+ const serviceContent = renderService({ binary, user: renderUser, ...home2 ? { home: home2 } : {} });
93009
+ const timerContent = renderTimer();
93010
+ const manualCommands = [
93011
+ "sudo systemctl daemon-reload",
93012
+ `sudo systemctl enable --now ${TIMER_UNIT}`
93013
+ ];
93014
+ const base = { servicePath, timerPath, serviceContent, timerContent, manualCommands };
93015
+ if (!binary.startsWith("/")) {
93016
+ return {
93017
+ status: "skipped",
93018
+ reason: `SWITCHROOM_BINARY must be an absolute path (got ${binary})`,
93019
+ ...base
93020
+ };
93021
+ }
93022
+ if (!systemdBooted()) {
93023
+ return {
93024
+ status: "skipped",
93025
+ reason: "host is not systemd-booted (/run/systemd/system absent)",
93026
+ ...base
93027
+ };
93028
+ }
93029
+ if (geteuid() !== 0) {
93030
+ return { status: "skipped", reason: "not privileged (euid != 0)", ...base };
93031
+ }
93032
+ if (!user) {
93033
+ return {
93034
+ status: "skipped",
93035
+ reason: "no non-root operator user resolvable (SUDO_USER / USER / LOGNAME all unset or root)",
93036
+ ...base
93037
+ };
93038
+ }
93039
+ const svc = writeUnitIdempotent(servicePath, serviceContent);
93040
+ const tim = writeUnitIdempotent(timerPath, timerContent);
93041
+ const changed = svc === "written" || tim === "written";
93042
+ const runner = deps.runner ?? ((cmd, args) => {
93043
+ const r = spawnSync16(cmd, args, { stdio: "inherit" });
93044
+ return { status: r.status ?? 1 };
93045
+ });
93046
+ const reload = runner("systemctl", ["daemon-reload"]);
93047
+ if (reload.status !== 0) {
93048
+ throw new Error(`systemctl daemon-reload failed (exit ${reload.status})`);
93049
+ }
93050
+ const enable = runner("systemctl", ["enable", "--now", TIMER_UNIT]);
93051
+ if (enable.status !== 0) {
93052
+ throw new Error(`systemctl enable --now ${TIMER_UNIT} failed (exit ${enable.status})`);
93053
+ }
93054
+ return { status: changed ? "installed" : "unchanged", ...base };
93055
+ }
93056
+
93057
+ // src/cli/update.ts
92642
93058
  function defaultPersistPin(configPath) {
92643
93059
  return (pin) => {
92644
93060
  const path7 = configPath ?? findConfigFile();
92645
- const before = readFileSync74(path7, "utf8");
93061
+ const before = readFileSync75(path7, "utf8");
92646
93062
  const after = setReleasePinInConfig(before, pin);
92647
93063
  if (after === before)
92648
93064
  return;
@@ -92658,16 +93074,16 @@ function defaultPersistPin(configPath) {
92658
93074
  }
92659
93075
  var DEFAULT_COMPOSE_PATH2 = join84(homedir44(), ".switchroom", "compose", "docker-compose.yml");
92660
93076
  function runningFromSwitchroomCheckout(scriptPath) {
92661
- let dir = dirname33(scriptPath);
93077
+ let dir = dirname34(scriptPath);
92662
93078
  for (let i = 0;i < 12; i++) {
92663
- if (existsSync80(join84(dir, ".git"))) {
93079
+ if (existsSync81(join84(dir, ".git"))) {
92664
93080
  try {
92665
- const pkg = JSON.parse(readFileSync74(join84(dir, "package.json"), "utf-8"));
93081
+ const pkg = JSON.parse(readFileSync75(join84(dir, "package.json"), "utf-8"));
92666
93082
  if (pkg.name === "switchroom")
92667
93083
  return true;
92668
93084
  } catch {}
92669
93085
  }
92670
- const parent = dirname33(dir);
93086
+ const parent = dirname34(dir);
92671
93087
  if (parent === dir)
92672
93088
  break;
92673
93089
  dir = parent;
@@ -92728,7 +93144,7 @@ function planUpdate(opts) {
92728
93144
  bundleDir: import.meta.dirname,
92729
93145
  execPath: process.execPath,
92730
93146
  scriptPath,
92731
- inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync80("/.dockerenv")
93147
+ inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync81("/.dockerenv")
92732
93148
  });
92733
93149
  if (detection.kind !== "static-binary") {
92734
93150
  say(source_default.gray(` host CLI not self-updated: ${detection.reason}
@@ -92801,7 +93217,7 @@ function planUpdate(opts) {
92801
93217
  steps.push({
92802
93218
  name: "pull-images",
92803
93219
  description: "Pull broker / kernel / agent images from GHCR",
92804
- skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync80(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
93220
+ skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync81(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
92805
93221
  run: () => {
92806
93222
  const r = runner("docker", [
92807
93223
  "compose",
@@ -92846,6 +93262,69 @@ function planUpdate(opts) {
92846
93262
  throw new Error("switchroom apply failed");
92847
93263
  }
92848
93264
  });
93265
+ steps.push({
93266
+ name: "reconcile-hindsight-watch-cron",
93267
+ description: "Reconcile /etc/cron.d/hindsight-watch (binary path / schedule / flags) on already-armed hosts \u2014 idempotent; no-op when unarmed or already current",
93268
+ skipReason: hostdContext ? "deferred (hostd-context): /etc/cron.d is on the host, not in the hostd container \u2014 finish host-side with `switchroom hindsight-watch --install-cron`" : undefined,
93269
+ isHostdDeferred: hostdContext,
93270
+ run: () => {
93271
+ const say = opts.stdout ?? ((t) => process.stdout.write(t));
93272
+ const binary = process.env.SWITCHROOM_BINARY ?? "/usr/local/bin/switchroom";
93273
+ const fallbackUser = process.env.SUDO_USER ?? process.env.USER ?? process.env.LOGNAME;
93274
+ let res;
93275
+ try {
93276
+ res = reconcileCron({
93277
+ binary,
93278
+ fallbackUser,
93279
+ ...opts.watchCronPath ? { path: opts.watchCronPath } : {}
93280
+ });
93281
+ } catch (e) {
93282
+ say(source_default.yellow(` hindsight-watch cron not reconciled: ${e.message}
93283
+ `));
93284
+ return;
93285
+ }
93286
+ if (res.status === "absent") {
93287
+ say(source_default.gray(" hindsight-watch cron not armed \u2014 skipping reconcile " + "(arm with `switchroom hindsight-watch --install-cron`; doctor FAILs while unarmed)\n"));
93288
+ } else if (res.status === "reconciled") {
93289
+ say(source_default.green(` hindsight-watch cron reconciled at ${res.path}
93290
+ `));
93291
+ } else {
93292
+ say(source_default.gray(` hindsight-watch cron already current at ${res.path}
93293
+ `));
93294
+ }
93295
+ }
93296
+ });
93297
+ steps.push({
93298
+ name: "install-self-heal-timer",
93299
+ description: "Install + enable the switchroom-self-heal systemd timer so a shipped release converges the host binary automatically \u2014 graceful no-op without systemd/privilege",
93300
+ skipReason: hostdContext ? "deferred (hostd-context): /etc/systemd/system is on the host, not in the hostd container \u2014 finish host-side by re-running `switchroom update` on the host" : undefined,
93301
+ isHostdDeferred: hostdContext,
93302
+ run: () => {
93303
+ const say = opts.stdout ?? ((t) => process.stdout.write(t));
93304
+ let res;
93305
+ try {
93306
+ res = installSelfHealTimer(opts.selfHealTimerDeps ?? {});
93307
+ } catch (e) {
93308
+ say(source_default.yellow(` self-heal timer not installed: ${e.message}
93309
+ `));
93310
+ return;
93311
+ }
93312
+ if (res.status === "installed") {
93313
+ say(source_default.green(` self-heal timer installed + enabled (${res.servicePath}, ${res.timerPath})
93314
+ `));
93315
+ } else if (res.status === "unchanged") {
93316
+ say(source_default.gray(` self-heal timer already current + enabled at ${res.timerPath}
93317
+ `));
93318
+ } else {
93319
+ say(source_default.gray(` self-heal timer not installed automatically: ${res.reason}
93320
+ `) + source_default.gray(` Install it by hand:
93321
+ `) + source_default.gray(` # ${res.servicePath}
93322
+ `) + res.serviceContent.replace(/^/gm, source_default.gray(" ")) + source_default.gray(` # ${res.timerPath}
93323
+ `) + res.timerContent.replace(/^/gm, source_default.gray(" ")) + res.manualCommands.map((c) => source_default.gray(` ${c}
93324
+ `)).join(""));
93325
+ }
93326
+ }
93327
+ });
92849
93328
  let hostControlEnabled;
92850
93329
  if (typeof opts.hostControlEnabled === "boolean") {
92851
93330
  hostControlEnabled = opts.hostControlEnabled;
@@ -92922,13 +93401,13 @@ function planUpdate(opts) {
92922
93401
  }
92923
93402
  const source = resolve45(import.meta.dirname, "../../skills");
92924
93403
  const dest = join84(homedir44(), ".switchroom", "skills", "_bundled");
92925
- if (!existsSync80(source)) {
93404
+ if (!existsSync81(source)) {
92926
93405
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
92927
93406
  `);
92928
93407
  return;
92929
93408
  }
92930
93409
  try {
92931
- mkdirSync45(dirname33(dest), { recursive: true });
93410
+ mkdirSync46(dirname34(dest), { recursive: true });
92932
93411
  const r = syncBundledSkills({
92933
93412
  source,
92934
93413
  dest,
@@ -92954,10 +93433,10 @@ function planUpdate(opts) {
92954
93433
  if (opts.syncBundledSkillsFn)
92955
93434
  return;
92956
93435
  const dest = join84(homedir44(), ".switchroom", "skills", "_bundled");
92957
- if (!existsSync80(dest)) {
93436
+ if (!existsSync81(dest)) {
92958
93437
  return;
92959
93438
  }
92960
- const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync80(join84(dest, key)));
93439
+ const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync81(join84(dest, key)));
92961
93440
  if (missing.length > 0) {
92962
93441
  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.`);
92963
93442
  }
@@ -92992,7 +93471,7 @@ function planUpdate(opts) {
92992
93471
  description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
92993
93472
  run: () => {
92994
93473
  try {
92995
- const composeText = readFileSync74(composePath, "utf8");
93474
+ const composeText = readFileSync75(composePath, "utf8");
92996
93475
  const pf = validateBindSources(composeText);
92997
93476
  if (!pf.ok)
92998
93477
  throw new Error(formatPreflightError(pf));
@@ -93026,7 +93505,7 @@ function planUpdate(opts) {
93026
93505
  return steps;
93027
93506
  }
93028
93507
  function defaultRunner2(cmd, args) {
93029
- const r = spawnSync16(cmd, args, { stdio: "inherit" });
93508
+ const r = spawnSync17(cmd, args, { stdio: "inherit" });
93030
93509
  return { status: r.status ?? 1 };
93031
93510
  }
93032
93511
  function writeMarkerInPreferredLocation(agent, reason, runner) {
@@ -93069,12 +93548,12 @@ function defaultStatusProbe(composePath) {
93069
93548
  try {
93070
93549
  cliBuiltAt = new Date(statSync45(scriptPath).mtimeMs).toISOString();
93071
93550
  } catch {}
93072
- let dir = dirname33(scriptPath);
93551
+ let dir = dirname34(scriptPath);
93073
93552
  for (let i = 0;i < 8; i++) {
93074
93553
  const pkgPath = join84(dir, "package.json");
93075
- if (existsSync80(pkgPath)) {
93554
+ if (existsSync81(pkgPath)) {
93076
93555
  try {
93077
- const pkg = JSON.parse(readFileSync74(pkgPath, "utf-8"));
93556
+ const pkg = JSON.parse(readFileSync75(pkgPath, "utf-8"));
93078
93557
  if (typeof pkg.version === "string")
93079
93558
  cliVersion = pkg.version;
93080
93559
  } catch (err) {
@@ -93082,7 +93561,7 @@ function defaultStatusProbe(composePath) {
93082
93561
  }
93083
93562
  break;
93084
93563
  }
93085
- const parent = dirname33(dir);
93564
+ const parent = dirname34(dir);
93086
93565
  if (parent === dir)
93087
93566
  break;
93088
93567
  dir = parent;
@@ -93095,13 +93574,13 @@ function defaultStatusProbe(composePath) {
93095
93574
  warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
93096
93575
  }
93097
93576
  const services = [];
93098
- if (!existsSync80(composePath)) {
93577
+ if (!existsSync81(composePath)) {
93099
93578
  warnings.push(`compose file not found at ${composePath}; service status unknown`);
93100
93579
  return { cliVersion, cliBuiltAt, services, warnings };
93101
93580
  }
93102
93581
  let serviceList = [];
93103
93582
  try {
93104
- const r = spawnSync16("docker", ["compose", "-p", "switchroom", "-f", composePath, "config", "--services"], { encoding: "utf-8", timeout: 1e4 });
93583
+ const r = spawnSync17("docker", ["compose", "-p", "switchroom", "-f", composePath, "config", "--services"], { encoding: "utf-8", timeout: 1e4 });
93105
93584
  if (r.status !== 0) {
93106
93585
  warnings.push(`docker compose config --services failed: ${r.stderr?.trim() ?? r.error?.message ?? "unknown"}`);
93107
93586
  return { cliVersion, cliBuiltAt, services, warnings };
@@ -93118,7 +93597,7 @@ function defaultStatusProbe(composePath) {
93118
93597
  let containerCreatedAt = null;
93119
93598
  let status = "<unknown>";
93120
93599
  try {
93121
- const r = spawnSync16("docker", ["inspect", "-f", "{{.Config.Image}}|{{.Created}}|{{.State.Status}}", containerName2], { encoding: "utf-8", timeout: 5000 });
93600
+ const r = spawnSync17("docker", ["inspect", "-f", "{{.Config.Image}}|{{.Created}}|{{.State.Status}}", containerName2], { encoding: "utf-8", timeout: 5000 });
93122
93601
  if (r.status === 0) {
93123
93602
  const [img, created, st] = r.stdout.trim().split("|");
93124
93603
  image = img ?? null;
@@ -93134,7 +93613,7 @@ function defaultStatusProbe(composePath) {
93134
93613
  let imagePulledAt = null;
93135
93614
  if (image) {
93136
93615
  try {
93137
- const r = spawnSync16("docker", ["image", "inspect", "-f", "{{.Id}}|{{.Created}}|{{.Metadata.LastTagTime}}", image], { encoding: "utf-8", timeout: 5000 });
93616
+ const r = spawnSync17("docker", ["image", "inspect", "-f", "{{.Id}}|{{.Created}}|{{.Metadata.LastTagTime}}", image], { encoding: "utf-8", timeout: 5000 });
93138
93617
  if (r.status === 0) {
93139
93618
  const [id, created, lastTag] = r.stdout.trim().split("|");
93140
93619
  imageDigestShort = id?.replace(/^sha256:/, "").slice(0, 12) ?? null;
@@ -93209,7 +93688,7 @@ function buildReexecArgs(opts) {
93209
93688
  return args;
93210
93689
  }
93211
93690
  function defaultReexec(binaryPath, args) {
93212
- const r = spawnSync16(binaryPath, args, {
93691
+ const r = spawnSync17(binaryPath, args, {
93213
93692
  stdio: "inherit",
93214
93693
  env: { ...process.env, [SELF_UPDATE_ENV_SENTINEL]: "1" }
93215
93694
  });
@@ -93218,7 +93697,7 @@ function defaultReexec(binaryPath, args) {
93218
93697
  function renderDriftPreamble(opts) {
93219
93698
  try {
93220
93699
  const exec = opts.componentExec ?? ((cmd, args) => {
93221
- const r = spawnSync16(cmd, args, { encoding: "utf-8", timeout: 1e4 });
93700
+ const r = spawnSync17(cmd, args, { encoding: "utf-8", timeout: 1e4 });
93222
93701
  return { status: r.status ?? 1, stdout: r.stdout ?? "" };
93223
93702
  });
93224
93703
  let pin;
@@ -93342,23 +93821,23 @@ function registerUpdateCommand(program3) {
93342
93821
 
93343
93822
  // src/cli/rollout.ts
93344
93823
  init_helpers();
93345
- import { spawnSync as spawnSync18 } from "node:child_process";
93346
- import { readFileSync as readFileSync76, chownSync as chownSync9, statSync as statSync47 } from "node:fs";
93824
+ import { spawnSync as spawnSync19 } from "node:child_process";
93825
+ import { readFileSync as readFileSync77, chownSync as chownSync9, statSync as statSync47 } from "node:fs";
93347
93826
  import { homedir as homedir46 } from "node:os";
93348
93827
 
93349
93828
  // src/cli/rollout-pin-journal.ts
93350
93829
  import {
93351
- existsSync as existsSync81,
93352
- readFileSync as readFileSync75,
93353
- writeFileSync as writeFileSync31,
93354
- renameSync as renameSync20,
93830
+ existsSync as existsSync82,
93831
+ readFileSync as readFileSync76,
93832
+ writeFileSync as writeFileSync32,
93833
+ renameSync as renameSync21,
93355
93834
  unlinkSync as unlinkSync15,
93356
93835
  statSync as statSync46,
93357
- mkdirSync as mkdirSync46
93836
+ mkdirSync as mkdirSync47
93358
93837
  } from "node:fs";
93359
93838
  import { homedir as homedir45 } from "node:os";
93360
93839
  import { createHash as createHash17 } from "node:crypto";
93361
- import { join as join85, basename as basename9, resolve as resolve46, dirname as dirname34 } from "node:path";
93840
+ import { join as join85, basename as basename9, resolve as resolve46, dirname as dirname35 } from "node:path";
93362
93841
  init_flock();
93363
93842
  var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
93364
93843
  var STATE_DIR_NAME = ".switchroom";
@@ -93367,7 +93846,7 @@ function pinJournalDir(configPath) {
93367
93846
  if (override && override.trim().length > 0)
93368
93847
  return override.trim();
93369
93848
  if (configPath) {
93370
- const dir = dirname34(resolve46(configPath));
93849
+ const dir = dirname35(resolve46(configPath));
93371
93850
  if (basename9(dir) === STATE_DIR_NAME)
93372
93851
  return dir;
93373
93852
  }
@@ -93410,9 +93889,9 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
93410
93889
  const p = pinJournalPath(configPath);
93411
93890
  let raw;
93412
93891
  try {
93413
- raw = readFileSync75(p, "utf8");
93892
+ raw = readFileSync76(p, "utf8");
93414
93893
  } catch (e) {
93415
- if (existsSync81(p)) {
93894
+ if (existsSync82(p)) {
93416
93895
  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.
93417
93896
  `);
93418
93897
  }
@@ -93463,7 +93942,7 @@ function beginPinPersist(configPath, pin, opts = {}) {
93463
93942
  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.
93464
93943
  `);
93465
93944
  }
93466
- const priorPin = getReleasePinFromConfig(readFileSync75(configPath, "utf8"));
93945
+ const priorPin = getReleasePinFromConfig(readFileSync76(configPath, "utf8"));
93467
93946
  const journal = {
93468
93947
  v: 1,
93469
93948
  configPath,
@@ -93472,10 +93951,10 @@ function beginPinPersist(configPath, pin, opts = {}) {
93472
93951
  pid: process.pid,
93473
93952
  at: new Date().toISOString()
93474
93953
  };
93475
- mkdirSync46(dirname34(p), { recursive: true });
93954
+ mkdirSync47(dirname35(p), { recursive: true });
93476
93955
  const tmp = `${p}.${process.pid}.tmp`;
93477
- writeFileSync31(tmp, JSON.stringify(journal), { encoding: "utf8", mode: 384 });
93478
- renameSync20(tmp, p);
93956
+ writeFileSync32(tmp, JSON.stringify(journal), { encoding: "utf8", mode: 384 });
93957
+ renameSync21(tmp, p);
93479
93958
  return journal;
93480
93959
  }
93481
93960
  function commitPinPersist(configPath) {
@@ -93484,7 +93963,7 @@ function commitPinPersist(configPath) {
93484
93963
  unlinkSync15(p);
93485
93964
  return null;
93486
93965
  } catch (e) {
93487
- if (!existsSync81(p))
93966
+ if (!existsSync82(p))
93488
93967
  return null;
93489
93968
  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\`.`;
93490
93969
  }
@@ -93506,7 +93985,7 @@ function rollbackPinPersist(configPath, opts = {}) {
93506
93985
  }
93507
93986
  }
93508
93987
  try {
93509
- const current = readFileSync75(configPath, "utf8");
93988
+ const current = readFileSync76(configPath, "utf8");
93510
93989
  const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
93511
93990
  let mode = 384;
93512
93991
  try {
@@ -93515,7 +93994,7 @@ function rollbackPinPersist(configPath, opts = {}) {
93515
93994
  if (opts.writeConfig) {
93516
93995
  opts.writeConfig(configPath, next, mode);
93517
93996
  } else {
93518
- writeFileSync31(configPath, next, { encoding: "utf8", mode });
93997
+ writeFileSync32(configPath, next, { encoding: "utf8", mode });
93519
93998
  }
93520
93999
  } catch (e) {
93521
94000
  return {
@@ -93561,12 +94040,13 @@ init_atomic();
93561
94040
  init_resolve_version();
93562
94041
  init_audit_reader();
93563
94042
  init_hindsight();
94043
+ init_hindsight2();
93564
94044
 
93565
94045
  // src/cli/deploy-version-guard.ts
93566
- import { spawnSync as spawnSync17 } from "node:child_process";
94046
+ import { spawnSync as spawnSync18 } from "node:child_process";
93567
94047
  var DOCKER_INSPECT_TIMEOUT_MS = 60 * 1000;
93568
94048
  var defaultRunner3 = (args) => {
93569
- const r = spawnSync17("docker", args, {
94049
+ const r = spawnSync18("docker", args, {
93570
94050
  encoding: "utf8",
93571
94051
  timeout: DOCKER_INSPECT_TIMEOUT_MS,
93572
94052
  killSignal: "SIGKILL"
@@ -93608,22 +94088,31 @@ function checkDowngrade(opts) {
93608
94088
  init_agent_owned_tree();
93609
94089
  init_component_versions();
93610
94090
  var VERIFY_COMPONENTS_STEP = "verify-components";
94091
+ var ENSURE_BANKS_STEP = "ensure-banks";
93611
94092
  function normalizeVersion(v) {
93612
94093
  return v.trim().replace(/^v/, "");
93613
94094
  }
93614
94095
  function isVersionAssertable(target) {
93615
94096
  return /^v?\d+\.\d+\.\d+$/.test(target.trim());
93616
94097
  }
93617
- function orderAgentsCanaryFirst(agents) {
94098
+ var ROLLOUT_PRIORITY_AGENTS = [
94099
+ "overlord",
94100
+ "carrie",
94101
+ "finn"
94102
+ ];
94103
+ function orderAgentsForRoll(agents) {
93618
94104
  const canary = agents.filter((a) => a === "test-harness");
93619
- const rest = agents.filter((a) => a !== "test-harness");
93620
- return [...canary, ...rest];
94105
+ const priority = ROLLOUT_PRIORITY_AGENTS.filter((p) => p !== "test-harness" && agents.includes(p));
94106
+ const prioritySet = new Set(priority);
94107
+ const rest = agents.filter((a) => a !== "test-harness" && !prioritySet.has(a));
94108
+ return [...canary, ...priority, ...rest];
93621
94109
  }
93622
94110
  function planRollout(agents, opts = {}) {
93623
94111
  const steps = [];
93624
- const ordered = orderAgentsCanaryFirst(agents);
94112
+ const ordered = orderAgentsForRoll(agents);
93625
94113
  if (opts.hostdContext) {
93626
94114
  steps.push({ kind: "apply" });
94115
+ steps.push({ kind: "refresh-hindsight" });
93627
94116
  const [canary, ...rest] = ordered;
93628
94117
  if (canary !== undefined) {
93629
94118
  steps.push({ kind: "restart-agent", agent: canary });
@@ -93639,23 +94128,26 @@ function planRollout(agents, opts = {}) {
93639
94128
  if (!opts.skipWeb) {
93640
94129
  steps.push({ kind: "refresh-web" });
93641
94130
  }
93642
- steps.push({ kind: "refresh-hindsight" });
93643
94131
  steps.push({ kind: "sweep" });
94132
+ steps.push({ kind: "ensure-banks" });
93644
94133
  steps.push({ kind: "verify-components" });
93645
94134
  return steps;
93646
94135
  }
93647
94136
  if (opts.pinToPersist)
93648
94137
  steps.push({ kind: "persist-pin", pin: opts.pinToPersist });
93649
94138
  steps.push({ kind: "apply" });
94139
+ if (!opts.skipWeb) {
94140
+ steps.push({ kind: "refresh-hindsight" });
94141
+ }
93650
94142
  for (const agent of ordered) {
93651
94143
  steps.push({ kind: "restart-agent", agent });
93652
94144
  }
93653
94145
  if (!opts.skipWeb) {
93654
94146
  steps.push({ kind: "refresh-web" });
93655
94147
  steps.push({ kind: "refresh-hostd" });
93656
- steps.push({ kind: "refresh-hindsight" });
93657
94148
  }
93658
94149
  steps.push({ kind: "sweep" });
94150
+ steps.push({ kind: "ensure-banks" });
93659
94151
  steps.push({ kind: "verify-components" });
93660
94152
  return steps;
93661
94153
  }
@@ -93686,6 +94178,9 @@ function formatRolloutPlan(steps, target) {
93686
94178
  case "sweep":
93687
94179
  lines.push(` ${n}. sweep \u2014 print per-agent version table`);
93688
94180
  break;
94181
+ case "ensure-banks":
94182
+ lines.push(` ${n}. ensure-banks \u2014 create any missing agent Hindsight bank ` + `(idempotent backstop; structured failure if one stays missing)`);
94183
+ break;
93689
94184
  case "verify-components":
93690
94185
  lines.push(` ${n}. verify-components \u2014 assert EVERY in-scope component is on ${target} ` + `(same inventory \`switchroom update --check\` uses); non-zero exit if any is behind`);
93691
94186
  break;
@@ -93747,7 +94242,7 @@ function evaluateRolloutDrift(components, target, steps, hostdContext) {
93747
94242
  const { gated, exempt } = partitionDrift(report.behind, { owners, rolledAgents });
93748
94243
  return { gated, exempt, target: report.target };
93749
94244
  }
93750
- function executeRollout(steps, target, deps, execOpts = {}) {
94245
+ async function executeRollout(steps, target, deps, execOpts = {}) {
93751
94246
  const targetNorm = normalizeVersion(target);
93752
94247
  const rolled = [];
93753
94248
  const warnings = [];
@@ -93756,6 +94251,7 @@ function executeRollout(steps, target, deps, execOpts = {}) {
93756
94251
  let agentIndex = 0;
93757
94252
  let pinPersisted = false;
93758
94253
  let driftVerdict = null;
94254
+ const bankEnsureFailed = [];
93759
94255
  const fail4 = (partial) => {
93760
94256
  let pinReverted = false;
93761
94257
  if (pinPersisted && deps.revertPin) {
@@ -93860,6 +94356,10 @@ function executeRollout(steps, target, deps, execOpts = {}) {
93860
94356
  const webTag = deps.webImageTag?.();
93861
94357
  if (webTag !== undefined && webTag !== null && isVersionAssertable(webTag) && normalizeVersion(webTag) !== targetNorm) {
93862
94358
  warnings.push(`web refresh completed but switchroom-web is running ${webTag}, ` + `NOT the roll target ${target} (most likely its downgrade guard ` + `skipped the install). Finish it host-side: \`switchroom webd ` + `install --tag ${target} --allow-downgrade\`.`);
94359
+ break;
94360
+ }
94361
+ if (webTag !== undefined && webTag !== null && isVersionAssertable(webTag) && normalizeVersion(webTag) === targetNorm) {
94362
+ emit({ phase: "web-refresh-done", target });
93863
94363
  }
93864
94364
  break;
93865
94365
  }
@@ -93875,9 +94375,11 @@ function executeRollout(steps, target, deps, execOpts = {}) {
93875
94375
  const hindsightExists = deps.hindsightExists ?? isHindsightContainerExists;
93876
94376
  if (!hindsightExists()) {
93877
94377
  deps.log(`ROLL_STEP refresh-hindsight \u2014 no hindsight container on this host; skipping`);
94378
+ emit({ phase: "hindsight-skipped", target });
93878
94379
  break;
93879
94380
  }
93880
94381
  deps.log(`ROLL_STEP refresh-hindsight \u2014 memory setup --recreate --tag ${target} (pull ${target} + recreate)`);
94382
+ emit({ phase: "hindsight-refresh", target });
93881
94383
  const r = deps.run(["memory", "setup", "--recreate", "--tag", target]);
93882
94384
  if (r.status !== 0) {
93883
94385
  deps.log(` \u2717 hindsight recreate FAILED \u2014 the memory backend is DOWN (the ` + `recreate stopped + removed the old container before the failed ` + `start). Run \`switchroom memory setup\` to bring it back. \u2014 STOPPING`);
@@ -93886,6 +94388,7 @@ function executeRollout(steps, target, deps, execOpts = {}) {
93886
94388
  ...r.timedOut ? { timedOut: true } : {}
93887
94389
  });
93888
94390
  }
94391
+ emit({ phase: "hindsight-refresh-done", target });
93889
94392
  break;
93890
94393
  }
93891
94394
  case "sweep": {
@@ -93896,6 +94399,38 @@ function executeRollout(steps, target, deps, execOpts = {}) {
93896
94399
  }
93897
94400
  break;
93898
94401
  }
94402
+ case "ensure-banks": {
94403
+ const hindsightExists = deps.hindsightExists ?? isHindsightContainerExists;
94404
+ if (!hindsightExists()) {
94405
+ deps.log(`ROLL_STEP ensure-banks \u2014 no hindsight container on this host; skipping`);
94406
+ break;
94407
+ }
94408
+ if (!deps.ensureBank) {
94409
+ warnings.push(`ensure-banks ran with NO bank-ensure hook wired, so brand-new ` + `agents' Hindsight banks were NOT verified. If an agent's first ` + `\`retain\` fails with a foreign-key error, its bank was never ` + `created \u2014 recreate it by restarting that agent.`);
94410
+ break;
94411
+ }
94412
+ deps.log(`ROLL_STEP ensure-banks \u2014 creating any missing agent bank (idempotent) for ${rolled.length} agent(s)`);
94413
+ for (const a of rolled) {
94414
+ let res;
94415
+ try {
94416
+ res = await deps.ensureBank(a);
94417
+ } catch (e) {
94418
+ res = { ok: false, reason: e.message };
94419
+ }
94420
+ if (res.ok) {
94421
+ deps.log(` ${a}: bank present`);
94422
+ continue;
94423
+ }
94424
+ if (/unreachable|timeout/i.test(res.reason)) {
94425
+ deps.log(` ${a}: bank check ${res.reason.toLowerCase()} (degraded to warning)`);
94426
+ warnings.push(`ensure-banks could not reach Hindsight to verify ${a}'s bank ` + `(${res.reason}). If ${a} is brand-new and its first \`retain\` ` + `fails with a foreign-key error, restart it to recreate the bank.`);
94427
+ continue;
94428
+ }
94429
+ deps.log(` \u2717 ${a}: bank could NOT be created \u2014 ${res.reason}`);
94430
+ bankEnsureFailed.push(a);
94431
+ }
94432
+ break;
94433
+ }
93899
94434
  case "verify-components": {
93900
94435
  deps.log(`ROLL_STEP verify-components \u2014 asserting every in-scope component is on ${target}`);
93901
94436
  if (!deps.collectComponents) {
@@ -93934,6 +94469,19 @@ function executeRollout(steps, target, deps, execOpts = {}) {
93934
94469
  warnings.push(`roll succeeded but committing the durable release.pin threw: ` + `${e.message}. The pin IS written; a stale rollout ` + `journal may cause a later boot to revert it \u2014 verify host-side.`);
93935
94470
  }
93936
94471
  }
94472
+ if (bankEnsureFailed.length > 0) {
94473
+ for (const a of bankEnsureFailed) {
94474
+ warnings.push(`${a}'s Hindsight bank could NOT be created during the roll \u2014 its ` + `first \`retain\` will fail with a foreign-key error until the bank ` + `exists. Restart ${a} to re-run its bank creation now that Hindsight ` + `is back up.`);
94475
+ }
94476
+ deps.log(` \u2717 ensure-banks FAILED \u2014 ${bankEnsureFailed.length} agent bank(s) ` + `could not be created: ${bankEnsureFailed.join(", ")}`);
94477
+ return {
94478
+ ok: false,
94479
+ rolled,
94480
+ warnings,
94481
+ failedStep: ENSURE_BANKS_STEP,
94482
+ drifted: bankEnsureFailed
94483
+ };
94484
+ }
93937
94485
  if (driftVerdict && driftVerdict.gated.length > 0) {
93938
94486
  const normalizedTarget = `v${normalizeVersion(target)}`;
93939
94487
  for (const c of driftVerdict.gated) {
@@ -93990,7 +94538,7 @@ function isSpawnTimeout(r, killSignal) {
93990
94538
  var ROLLOUT_KILL_SIGNAL = "SIGKILL";
93991
94539
  function createRolloutDeps(params) {
93992
94540
  const { configPath, scriptPath, hostdCtx } = params;
93993
- const spawn6 = params.spawn ?? spawnSync18;
94541
+ const spawn6 = params.spawn ?? spawnSync19;
93994
94542
  const warn = params.warn ?? ((line) => process.stderr.write(line));
93995
94543
  const self = resolveSelfInvocation({
93996
94544
  execPath: params.execPath ?? process.execPath,
@@ -94075,7 +94623,7 @@ function createRolloutDeps(params) {
94075
94623
  return { status: r.ok ? 0 : 1, stdout: r.stdout };
94076
94624
  }),
94077
94625
  persistPin: (pin) => {
94078
- const before = readFileSync76(configPath, "utf8");
94626
+ const before = readFileSync77(configPath, "utf8");
94079
94627
  const after = setReleasePinInConfig(before, pin);
94080
94628
  if (after === before)
94081
94629
  return false;
@@ -94100,7 +94648,12 @@ function createRolloutDeps(params) {
94100
94648
  `);
94101
94649
  }
94102
94650
  return outcome.reverted;
94103
- }
94651
+ },
94652
+ ...params.hindsightApiUrl && params.resolveBankId ? {
94653
+ ensureBank: (agent) => createBank(params.hindsightApiUrl, params.resolveBankId(agent), {
94654
+ timeoutMs: 5000
94655
+ })
94656
+ } : {}
94104
94657
  };
94105
94658
  }
94106
94659
  function registerRolloutCommand(program3) {
@@ -94193,10 +94746,23 @@ function registerRolloutCommand(program3) {
94193
94746
  }
94194
94747
  const configPath = getConfigPath(program3);
94195
94748
  const scriptPath = process.argv[1] ?? "";
94196
- const deps = createRolloutDeps({ configPath, scriptPath, hostdCtx });
94749
+ let hindsightApiUrl;
94750
+ let resolveBankId;
94751
+ if (isHindsightEnabled(config)) {
94752
+ const rawUrl = config.memory?.config?.url;
94753
+ hindsightApiUrl = rawUrl ? rawUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "") : HINDSIGHT_DEFAULT_API_BASE_URL;
94754
+ resolveBankId = (agent) => config.agents?.[agent]?.memory?.collection ?? agent;
94755
+ }
94756
+ const deps = createRolloutDeps({
94757
+ configPath,
94758
+ scriptPath,
94759
+ hostdCtx,
94760
+ hindsightApiUrl,
94761
+ resolveBankId
94762
+ });
94197
94763
  process.stdout.write(`${opts.allowDowngrade ? "Rolling back" : "Rolling"} ${requested.length} agent(s) to ${target}\u2026
94198
94764
  `);
94199
- const result = executeRollout(steps, target, deps, {
94765
+ const result = await executeRollout(steps, target, deps, {
94200
94766
  hostdContext: hostdCtx,
94201
94767
  allowDowngrade: opts.allowDowngrade
94202
94768
  });
@@ -94217,6 +94783,15 @@ function registerRolloutCommand(program3) {
94217
94783
  \u2717 Rollout INCOMPLETE \u2014 ${(result.drifted ?? []).length} component(s) ` + `still behind ${target}: ${(result.drifted ?? []).join(", ") || "unknown"}.
94218
94784
  ` + ` ${result.rolled.length} agent(s) DID reach ${target} and the pin is ` + `committed \u2014 re-running the agent roll will not fix this.
94219
94785
  ` + ` Run the per-component command named in the warning(s) above, then ` + `\`switchroom update --check\` to confirm the host is converged.
94786
+ `);
94787
+ process.exitCode = 1;
94788
+ return;
94789
+ }
94790
+ if (!result.ok && result.failedStep === ENSURE_BANKS_STEP) {
94791
+ process.stderr.write(`
94792
+ \u2717 Rollout INCOMPLETE \u2014 ${(result.drifted ?? []).length} agent(s) ` + `reached ${target} but their Hindsight bank could NOT be created: ` + `${(result.drifted ?? []).join(", ") || "unknown"}.
94793
+ ` + ` The pin is committed and the agents ARE on the target \u2014 re-running ` + `the agent roll will not fix this.
94794
+ ` + ` Restart the named agent(s) to re-run bank creation now that ` + `Hindsight is back up (their first \`retain\` fails with a foreign-key ` + `error until the bank exists).
94220
94795
  `);
94221
94796
  process.exitCode = 1;
94222
94797
  return;
@@ -94263,8 +94838,8 @@ init_helpers();
94263
94838
  init_lifecycle();
94264
94839
  init_resolve_version();
94265
94840
  import { execSync as execSync3 } from "node:child_process";
94266
- import { existsSync as existsSync82, readFileSync as readFileSync77 } from "node:fs";
94267
- import { dirname as dirname35, join as join86 } from "node:path";
94841
+ import { existsSync as existsSync83, readFileSync as readFileSync78 } from "node:fs";
94842
+ import { dirname as dirname36, join as join86 } from "node:path";
94268
94843
  function getClaudeCodeVersion() {
94269
94844
  try {
94270
94845
  const out = execSync3("claude --version 2>/dev/null", {
@@ -94315,15 +94890,15 @@ function locateSwitchroomInstallDir() {
94315
94890
  let dir = import.meta.dirname;
94316
94891
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
94317
94892
  const pkgPath = join86(dir, "package.json");
94318
- if (existsSync82(pkgPath)) {
94893
+ if (existsSync83(pkgPath)) {
94319
94894
  try {
94320
- const pkg = JSON.parse(readFileSync77(pkgPath, "utf-8"));
94321
- if (pkg.name === "switchroom" && existsSync82(join86(dir, ".git"))) {
94895
+ const pkg = JSON.parse(readFileSync78(pkgPath, "utf-8"));
94896
+ if (pkg.name === "switchroom" && existsSync83(join86(dir, ".git"))) {
94322
94897
  return dir;
94323
94898
  }
94324
94899
  } catch {}
94325
94900
  }
94326
- dir = dirname35(dir);
94901
+ dir = dirname36(dir);
94327
94902
  }
94328
94903
  return null;
94329
94904
  }
@@ -94497,7 +95072,7 @@ init_merge();
94497
95072
 
94498
95073
  // src/agents/session-retention.ts
94499
95074
  import {
94500
- existsSync as existsSync83,
95075
+ existsSync as existsSync84,
94501
95076
  readdirSync as readdirSync28,
94502
95077
  statSync as statSync48,
94503
95078
  unlinkSync as unlinkSync16
@@ -94508,7 +95083,7 @@ var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
94508
95083
  var MIN_KEEP = 2;
94509
95084
  function collectSessionJsonl(claudeConfigDir) {
94510
95085
  const projects = join87(claudeConfigDir, "projects");
94511
- if (!existsSync83(projects))
95086
+ if (!existsSync84(projects))
94512
95087
  return [];
94513
95088
  const found = [];
94514
95089
  const walk2 = (dir) => {
@@ -94644,15 +95219,15 @@ function registerHandoffCommand(program3) {
94644
95219
  // src/issues/store.ts
94645
95220
  import {
94646
95221
  closeSync as closeSync16,
94647
- existsSync as existsSync84,
94648
- mkdirSync as mkdirSync47,
95222
+ existsSync as existsSync85,
95223
+ mkdirSync as mkdirSync48,
94649
95224
  openSync as openSync16,
94650
95225
  readdirSync as readdirSync29,
94651
- readFileSync as readFileSync78,
94652
- renameSync as renameSync21,
95226
+ readFileSync as readFileSync79,
95227
+ renameSync as renameSync22,
94653
95228
  statSync as statSync49,
94654
95229
  unlinkSync as unlinkSync17,
94655
- writeFileSync as writeFileSync32,
95230
+ writeFileSync as writeFileSync33,
94656
95231
  writeSync as writeSync9
94657
95232
  } from "node:fs";
94658
95233
  import { join as join88 } from "node:path";
@@ -94683,11 +95258,11 @@ var ISSUES_FILE = "issues.jsonl";
94683
95258
  var ISSUES_LOCK = "issues.lock";
94684
95259
  function readAll(stateDir) {
94685
95260
  const path7 = join88(stateDir, ISSUES_FILE);
94686
- if (!existsSync84(path7))
95261
+ if (!existsSync85(path7))
94687
95262
  return [];
94688
95263
  let raw;
94689
95264
  try {
94690
- raw = readFileSync78(path7, "utf-8");
95265
+ raw = readFileSync79(path7, "utf-8");
94691
95266
  } catch {
94692
95267
  return [];
94693
95268
  }
@@ -94760,7 +95335,7 @@ function record(stateDir, input, nowFn = Date.now) {
94760
95335
  });
94761
95336
  }
94762
95337
  function resolve49(stateDir, fingerprint, nowFn = Date.now) {
94763
- if (!existsSync84(join88(stateDir, ISSUES_FILE)))
95338
+ if (!existsSync85(join88(stateDir, ISSUES_FILE)))
94764
95339
  return 0;
94765
95340
  return withLock(stateDir, () => {
94766
95341
  const all = readAll(stateDir);
@@ -94778,7 +95353,7 @@ function resolve49(stateDir, fingerprint, nowFn = Date.now) {
94778
95353
  });
94779
95354
  }
94780
95355
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
94781
- if (!existsSync84(join88(stateDir, ISSUES_FILE)))
95356
+ if (!existsSync85(join88(stateDir, ISSUES_FILE)))
94782
95357
  return 0;
94783
95358
  return withLock(stateDir, () => {
94784
95359
  const all = readAll(stateDir);
@@ -94796,7 +95371,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
94796
95371
  });
94797
95372
  }
94798
95373
  function prune(stateDir, opts = {}) {
94799
- if (!existsSync84(join88(stateDir, ISSUES_FILE)))
95374
+ if (!existsSync85(join88(stateDir, ISSUES_FILE)))
94800
95375
  return 0;
94801
95376
  return withLock(stateDir, () => {
94802
95377
  const all = readAll(stateDir);
@@ -94826,7 +95401,7 @@ function prune(stateDir, opts = {}) {
94826
95401
  });
94827
95402
  }
94828
95403
  function ensureDir(stateDir) {
94829
- mkdirSync47(stateDir, { recursive: true });
95404
+ mkdirSync48(stateDir, { recursive: true });
94830
95405
  }
94831
95406
  function writeAll(stateDir, events) {
94832
95407
  const path7 = join88(stateDir, ISSUES_FILE);
@@ -94835,8 +95410,8 @@ function writeAll(stateDir, events) {
94835
95410
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
94836
95411
  `) + `
94837
95412
  `;
94838
- writeFileSync32(tmp, body, "utf-8");
94839
- renameSync21(tmp, path7);
95413
+ writeFileSync33(tmp, body, "utf-8");
95414
+ renameSync22(tmp, path7);
94840
95415
  }
94841
95416
  var ORPHAN_TMP_TTL_MS = 60000;
94842
95417
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
@@ -94898,7 +95473,7 @@ function withLock(stateDir, fn) {
94898
95473
  function tryStealStaleLock(lockPath) {
94899
95474
  let pidStr;
94900
95475
  try {
94901
- pidStr = readFileSync78(lockPath, "utf-8").trim();
95476
+ pidStr = readFileSync79(lockPath, "utf-8").trim();
94902
95477
  } catch {
94903
95478
  return true;
94904
95479
  }
@@ -95146,20 +95721,20 @@ function relTime(deltaMs) {
95146
95721
 
95147
95722
  // src/cli/deps.ts
95148
95723
  init_source();
95149
- import { existsSync as existsSync87 } from "node:fs";
95724
+ import { existsSync as existsSync88 } from "node:fs";
95150
95725
  import { homedir as homedir49 } from "node:os";
95151
95726
  import { join as join91, resolve as resolve50 } from "node:path";
95152
95727
 
95153
95728
  // src/deps/python.ts
95154
95729
  import { createHash as createHash18 } from "node:crypto";
95155
95730
  import {
95156
- existsSync as existsSync85,
95157
- mkdirSync as mkdirSync48,
95158
- readFileSync as readFileSync79,
95731
+ existsSync as existsSync86,
95732
+ mkdirSync as mkdirSync49,
95733
+ readFileSync as readFileSync80,
95159
95734
  rmSync as rmSync15,
95160
- writeFileSync as writeFileSync33
95735
+ writeFileSync as writeFileSync34
95161
95736
  } from "node:fs";
95162
- import { dirname as dirname36, join as join89 } from "node:path";
95737
+ import { dirname as dirname37, join as join89 } from "node:path";
95163
95738
  import { homedir as homedir47 } from "node:os";
95164
95739
  import { execFileSync as execFileSync24 } from "node:child_process";
95165
95740
 
@@ -95175,13 +95750,13 @@ function defaultPythonCacheRoot() {
95175
95750
  return join89(homedir47(), ".switchroom", "deps", "python");
95176
95751
  }
95177
95752
  function hashFile(path7) {
95178
- return createHash18("sha256").update(readFileSync79(path7)).digest("hex");
95753
+ return createHash18("sha256").update(readFileSync80(path7)).digest("hex");
95179
95754
  }
95180
95755
  function ensurePythonEnv(opts) {
95181
95756
  const { skillName, requirementsPath, force = false } = opts;
95182
95757
  const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
95183
95758
  const hostPython = opts.pythonBin ?? "python3";
95184
- if (!existsSync85(requirementsPath)) {
95759
+ if (!existsSync86(requirementsPath)) {
95185
95760
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
95186
95761
  }
95187
95762
  const venvDir = join89(cacheRoot, skillName);
@@ -95190,8 +95765,8 @@ function ensurePythonEnv(opts) {
95190
95765
  const pythonBin = join89(binDir, "python");
95191
95766
  const pipBin = join89(binDir, "pip");
95192
95767
  const targetHash = hashFile(requirementsPath);
95193
- if (!force && existsSync85(stampPath) && existsSync85(pythonBin)) {
95194
- const existingHash = readFileSync79(stampPath, "utf8").trim();
95768
+ if (!force && existsSync86(stampPath) && existsSync86(pythonBin)) {
95769
+ const existingHash = readFileSync80(stampPath, "utf8").trim();
95195
95770
  if (existingHash === targetHash) {
95196
95771
  return {
95197
95772
  skillName,
@@ -95203,10 +95778,10 @@ function ensurePythonEnv(opts) {
95203
95778
  };
95204
95779
  }
95205
95780
  }
95206
- if (existsSync85(venvDir)) {
95781
+ if (existsSync86(venvDir)) {
95207
95782
  rmSync15(venvDir, { recursive: true, force: true });
95208
95783
  }
95209
- mkdirSync48(dirname36(venvDir), { recursive: true });
95784
+ mkdirSync49(dirname37(venvDir), { recursive: true });
95210
95785
  try {
95211
95786
  execFileSync24(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
95212
95787
  } catch (err) {
@@ -95225,7 +95800,7 @@ function ensurePythonEnv(opts) {
95225
95800
  const e = err;
95226
95801
  throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
95227
95802
  }
95228
- writeFileSync33(stampPath, targetHash + `
95803
+ writeFileSync34(stampPath, targetHash + `
95229
95804
  `);
95230
95805
  return {
95231
95806
  skillName,
@@ -95241,13 +95816,13 @@ function ensurePythonEnv(opts) {
95241
95816
  import { createHash as createHash19 } from "node:crypto";
95242
95817
  import {
95243
95818
  copyFileSync as copyFileSync12,
95244
- existsSync as existsSync86,
95245
- mkdirSync as mkdirSync49,
95246
- readFileSync as readFileSync80,
95819
+ existsSync as existsSync87,
95820
+ mkdirSync as mkdirSync50,
95821
+ readFileSync as readFileSync81,
95247
95822
  rmSync as rmSync16,
95248
- writeFileSync as writeFileSync34
95823
+ writeFileSync as writeFileSync35
95249
95824
  } from "node:fs";
95250
- import { dirname as dirname37, join as join90 } from "node:path";
95825
+ import { dirname as dirname38, join as join90 } from "node:path";
95251
95826
  import { homedir as homedir48 } from "node:os";
95252
95827
  import { execFileSync as execFileSync25 } from "node:child_process";
95253
95828
 
@@ -95274,20 +95849,20 @@ function defaultNodeCacheRoot() {
95274
95849
  return join90(homedir48(), ".switchroom", "deps", "node");
95275
95850
  }
95276
95851
  function hashDepInputs(packageJsonPath) {
95277
- const sourceDir = dirname37(packageJsonPath);
95852
+ const sourceDir = dirname38(packageJsonPath);
95278
95853
  const hasher = createHash19("sha256");
95279
95854
  hasher.update(`package.json
95280
95855
  `);
95281
- hasher.update(readFileSync80(packageJsonPath));
95856
+ hasher.update(readFileSync81(packageJsonPath));
95282
95857
  for (const lockName of ALL_LOCKFILES) {
95283
95858
  const lockPath = join90(sourceDir, lockName);
95284
- if (existsSync86(lockPath)) {
95859
+ if (existsSync87(lockPath)) {
95285
95860
  hasher.update(`
95286
95861
  `);
95287
95862
  hasher.update(lockName);
95288
95863
  hasher.update(`
95289
95864
  `);
95290
- hasher.update(readFileSync80(lockPath));
95865
+ hasher.update(readFileSync81(lockPath));
95291
95866
  }
95292
95867
  }
95293
95868
  return hasher.digest("hex");
@@ -95296,17 +95871,17 @@ function ensureNodeEnv(opts) {
95296
95871
  const { skillName, packageJsonPath, force = false } = opts;
95297
95872
  const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
95298
95873
  const installer = opts.installer ?? "bun";
95299
- if (!existsSync86(packageJsonPath)) {
95874
+ if (!existsSync87(packageJsonPath)) {
95300
95875
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
95301
95876
  }
95302
- const sourceDir = dirname37(packageJsonPath);
95877
+ const sourceDir = dirname38(packageJsonPath);
95303
95878
  const envDir = join90(cacheRoot, skillName);
95304
95879
  const stampPath = join90(envDir, ".package.sha256");
95305
95880
  const nodeModulesDir = join90(envDir, "node_modules");
95306
95881
  const binDir = join90(nodeModulesDir, ".bin");
95307
95882
  const targetHash = hashDepInputs(packageJsonPath);
95308
- if (!force && existsSync86(stampPath) && existsSync86(nodeModulesDir)) {
95309
- const existingHash = readFileSync80(stampPath, "utf8").trim();
95883
+ if (!force && existsSync87(stampPath) && existsSync87(nodeModulesDir)) {
95884
+ const existingHash = readFileSync81(stampPath, "utf8").trim();
95310
95885
  if (existingHash === targetHash) {
95311
95886
  return {
95312
95887
  skillName,
@@ -95317,15 +95892,15 @@ function ensureNodeEnv(opts) {
95317
95892
  };
95318
95893
  }
95319
95894
  }
95320
- if (existsSync86(envDir)) {
95895
+ if (existsSync87(envDir)) {
95321
95896
  rmSync16(envDir, { recursive: true, force: true });
95322
95897
  }
95323
- mkdirSync49(envDir, { recursive: true });
95898
+ mkdirSync50(envDir, { recursive: true });
95324
95899
  copyFileSync12(packageJsonPath, join90(envDir, "package.json"));
95325
95900
  let copiedLockfile = false;
95326
95901
  for (const lockName of LOCKFILES_FOR[installer]) {
95327
95902
  const lockPath = join90(sourceDir, lockName);
95328
- if (existsSync86(lockPath)) {
95903
+ if (existsSync87(lockPath)) {
95329
95904
  copyFileSync12(lockPath, join90(envDir, lockName));
95330
95905
  copiedLockfile = true;
95331
95906
  }
@@ -95342,7 +95917,7 @@ function ensureNodeEnv(opts) {
95342
95917
  const e = err;
95343
95918
  throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
95344
95919
  }
95345
- writeFileSync34(stampPath, targetHash + `
95920
+ writeFileSync35(stampPath, targetHash + `
95346
95921
  `);
95347
95922
  return {
95348
95923
  skillName,
@@ -95361,22 +95936,22 @@ function registerDepsCommand(program3) {
95361
95936
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
95362
95937
  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) => {
95363
95938
  const skillsRoot = builtinSkillsRoot();
95364
- if (!existsSync87(skillsRoot)) {
95939
+ if (!existsSync88(skillsRoot)) {
95365
95940
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
95366
95941
  process.exit(1);
95367
95942
  }
95368
95943
  const skillDir = join91(skillsRoot, skill);
95369
- if (!existsSync87(skillDir)) {
95944
+ if (!existsSync88(skillDir)) {
95370
95945
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
95371
95946
  process.exit(1);
95372
95947
  }
95373
95948
  const requirementsPath = join91(skillDir, "requirements.txt");
95374
95949
  const packageJsonPath = join91(skillDir, "package.json");
95375
- const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync87(requirementsPath));
95376
- const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync87(packageJsonPath));
95950
+ const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync88(requirementsPath));
95951
+ const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync88(packageJsonPath));
95377
95952
  let did = 0;
95378
95953
  if (wantPython) {
95379
- if (!existsSync87(requirementsPath)) {
95954
+ if (!existsSync88(requirementsPath)) {
95380
95955
  console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
95381
95956
  process.exit(1);
95382
95957
  }
@@ -95400,7 +95975,7 @@ function registerDepsCommand(program3) {
95400
95975
  }
95401
95976
  }
95402
95977
  if (wantNode) {
95403
- if (!existsSync87(packageJsonPath)) {
95978
+ if (!existsSync88(packageJsonPath)) {
95404
95979
  console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
95405
95980
  process.exit(1);
95406
95981
  }
@@ -95433,9 +96008,9 @@ function registerDepsCommand(program3) {
95433
96008
  // src/cli/workspace.ts
95434
96009
  init_helpers();
95435
96010
  init_loader();
95436
- import { existsSync as existsSync88 } from "node:fs";
96011
+ import { existsSync as existsSync89 } from "node:fs";
95437
96012
  import { resolve as resolve51, sep as sep4 } from "node:path";
95438
- import { spawnSync as spawnSync19 } from "node:child_process";
96013
+ import { spawnSync as spawnSync20 } from "node:child_process";
95439
96014
 
95440
96015
  // src/agents/workspace.ts
95441
96016
  import { readFile as readFile2, stat } from "node:fs/promises";
@@ -96148,7 +96723,7 @@ function registerWorkspaceCommand(program3) {
96148
96723
  process.exit(1);
96149
96724
  }
96150
96725
  const editor = process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
96151
- const child = spawnSync19(editor, [target], { stdio: "inherit" });
96726
+ const child = spawnSync20(editor, [target], { stdio: "inherit" });
96152
96727
  if (child.status !== 0 && child.status !== null) {
96153
96728
  process.exit(child.status);
96154
96729
  }
@@ -96210,12 +96785,12 @@ function registerWorkspaceCommand(program3) {
96210
96785
  if (!dir)
96211
96786
  return;
96212
96787
  const gitDir = resolve51(dir, ".git");
96213
- if (!existsSync88(gitDir)) {
96788
+ if (!existsSync89(gitDir)) {
96214
96789
  process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
96215
96790
  `);
96216
96791
  return;
96217
96792
  }
96218
- const statusResult = spawnSync19("git", ["status", "--short"], {
96793
+ const statusResult = spawnSync20("git", ["status", "--short"], {
96219
96794
  cwd: dir,
96220
96795
  encoding: "utf-8"
96221
96796
  });
@@ -96230,7 +96805,7 @@ function registerWorkspaceCommand(program3) {
96230
96805
  return;
96231
96806
  }
96232
96807
  const message = opts.message || `checkpoint: ${new Date().toISOString()}`;
96233
- const addResult = spawnSync19("git", ["add", "-A"], {
96808
+ const addResult = spawnSync20("git", ["add", "-A"], {
96234
96809
  cwd: dir,
96235
96810
  encoding: "utf-8"
96236
96811
  });
@@ -96239,7 +96814,7 @@ function registerWorkspaceCommand(program3) {
96239
96814
  `);
96240
96815
  process.exit(1);
96241
96816
  }
96242
- const commitResult = spawnSync19("git", ["commit", "-m", message], {
96817
+ const commitResult = spawnSync20("git", ["commit", "-m", message], {
96243
96818
  cwd: dir,
96244
96819
  encoding: "utf-8"
96245
96820
  });
@@ -96248,7 +96823,7 @@ function registerWorkspaceCommand(program3) {
96248
96823
  `);
96249
96824
  process.exit(1);
96250
96825
  }
96251
- const shaResult = spawnSync19("git", ["rev-parse", "--short", "HEAD"], {
96826
+ const shaResult = spawnSync20("git", ["rev-parse", "--short", "HEAD"], {
96252
96827
  cwd: dir,
96253
96828
  encoding: "utf-8"
96254
96829
  });
@@ -96264,12 +96839,12 @@ function registerWorkspaceCommand(program3) {
96264
96839
  if (!dir)
96265
96840
  return;
96266
96841
  const gitDir = resolve51(dir, ".git");
96267
- if (!existsSync88(gitDir)) {
96842
+ if (!existsSync89(gitDir)) {
96268
96843
  process.stdout.write(`Workspace is not a git repository.
96269
96844
  `);
96270
96845
  return;
96271
96846
  }
96272
- const child = spawnSync19("git", ["status", "--short"], {
96847
+ const child = spawnSync20("git", ["status", "--short"], {
96273
96848
  cwd: dir,
96274
96849
  stdio: "inherit"
96275
96850
  });
@@ -96289,7 +96864,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
96289
96864
  const agentsDir = resolveAgentsDir(config);
96290
96865
  const agentDir = resolve51(agentsDir, agentName);
96291
96866
  const dir = resolveAgentWorkspaceDir(agentDir);
96292
- if (!existsSync88(dir)) {
96867
+ if (!existsSync89(dir)) {
96293
96868
  process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
96294
96869
  `);
96295
96870
  return;
@@ -96325,7 +96900,7 @@ function safeParseInt(value, fallback) {
96325
96900
  init_helpers();
96326
96901
  init_loader();
96327
96902
  init_merge();
96328
- import { copyFileSync as copyFileSync13, existsSync as existsSync89, readFileSync as readFileSync81, writeFileSync as writeFileSync35 } from "node:fs";
96903
+ import { copyFileSync as copyFileSync13, existsSync as existsSync90, readFileSync as readFileSync82, writeFileSync as writeFileSync36 } from "node:fs";
96329
96904
  import { join as join92, resolve as resolve52 } from "node:path";
96330
96905
  init_scaffold();
96331
96906
  init_profiles();
@@ -96343,7 +96918,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
96343
96918
  const agentsDir = resolveAgentsDir(config);
96344
96919
  const agentDir = resolve52(agentsDir, agentName);
96345
96920
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
96346
- if (!existsSync89(workspaceDir)) {
96921
+ if (!existsSync90(workspaceDir)) {
96347
96922
  console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
96348
96923
  process.exit(1);
96349
96924
  }
@@ -96369,11 +96944,11 @@ function registerSoulCommand(program3) {
96369
96944
  const t = resolveSoulTargetOrExit(program3, agentName);
96370
96945
  if (!t)
96371
96946
  return;
96372
- if (!existsSync89(t.soulPath)) {
96947
+ if (!existsSync90(t.soulPath)) {
96373
96948
  console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
96374
96949
  process.exit(1);
96375
96950
  }
96376
- process.stdout.write(readFileSync81(t.soulPath, "utf-8"));
96951
+ process.stdout.write(readFileSync82(t.soulPath, "utf-8"));
96377
96952
  }));
96378
96953
  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) => {
96379
96954
  const t = resolveSoulTargetOrExit(program3, agentName);
@@ -96384,7 +96959,7 @@ function registerSoulCommand(program3) {
96384
96959
  console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
96385
96960
  process.exit(1);
96386
96961
  }
96387
- const exists = existsSync89(t.soulPath);
96962
+ const exists = existsSync90(t.soulPath);
96388
96963
  if (exists && !opts.yes) {
96389
96964
  if (!isInteractive()) {
96390
96965
  console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
@@ -96399,12 +96974,12 @@ function registerSoulCommand(program3) {
96399
96974
  let backupPath;
96400
96975
  if (exists) {
96401
96976
  backupPath = `${t.soulPath}.bak`;
96402
- if (existsSync89(backupPath)) {
96977
+ if (existsSync90(backupPath)) {
96403
96978
  backupPath = `${t.soulPath}.bak.${Date.now()}`;
96404
96979
  }
96405
96980
  copyFileSync13(t.soulPath, backupPath);
96406
96981
  }
96407
- writeFileSync35(t.soulPath, content, "utf-8");
96982
+ writeFileSync36(t.soulPath, content, "utf-8");
96408
96983
  if (backupPath) {
96409
96984
  console.log(`soul: re-seeded ${agentName}'s SOUL.md from profile ` + `"${t.profileName}".
96410
96985
  ` + ` Previous version saved to ${backupPath}`);
@@ -96418,7 +96993,7 @@ function registerSoulCommand(program3) {
96418
96993
  // src/cli/debug.ts
96419
96994
  init_helpers();
96420
96995
  init_loader();
96421
- import { existsSync as existsSync90, readFileSync as readFileSync82, readdirSync as readdirSync30, statSync as statSync50 } from "node:fs";
96996
+ import { existsSync as existsSync91, readFileSync as readFileSync83, readdirSync as readdirSync30, statSync as statSync50 } from "node:fs";
96422
96997
  import { resolve as resolve53, join as join93 } from "node:path";
96423
96998
  import { createHash as createHash20 } from "node:crypto";
96424
96999
  init_merge();
@@ -96431,10 +97006,10 @@ function estimateTokens(bytes) {
96431
97006
  }
96432
97007
  function readMcpServerNames(agentDir) {
96433
97008
  const mcpPath = join93(agentDir, ".mcp.json");
96434
- if (!existsSync90(mcpPath))
97009
+ if (!existsSync91(mcpPath))
96435
97010
  return [];
96436
97011
  try {
96437
- const parsed = JSON.parse(readFileSync82(mcpPath, "utf-8"));
97012
+ const parsed = JSON.parse(readFileSync83(mcpPath, "utf-8"));
96438
97013
  return Object.keys(parsed.mcpServers ?? {});
96439
97014
  } catch {
96440
97015
  return null;
@@ -96445,7 +97020,7 @@ function sha256(content) {
96445
97020
  }
96446
97021
  function findLatestTranscriptJsonl(claudeConfigDir) {
96447
97022
  const projectsDir = join93(claudeConfigDir, "projects");
96448
- if (!existsSync90(projectsDir))
97023
+ if (!existsSync91(projectsDir))
96449
97024
  return;
96450
97025
  try {
96451
97026
  const entries = readdirSync30(projectsDir, { withFileTypes: true });
@@ -96455,7 +97030,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
96455
97030
  continue;
96456
97031
  const projectPath = join93(projectsDir, entry.name);
96457
97032
  const transcriptPath = join93(projectPath, "transcript.jsonl");
96458
- if (!existsSync90(transcriptPath))
97033
+ if (!existsSync91(transcriptPath))
96459
97034
  continue;
96460
97035
  const stat3 = statSync50(transcriptPath);
96461
97036
  if (!latest || stat3.mtimeMs > latest.mtime) {
@@ -96469,7 +97044,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
96469
97044
  }
96470
97045
  function extractLatestUserMessage(transcriptPath) {
96471
97046
  try {
96472
- const content = readFileSync82(transcriptPath, "utf-8");
97047
+ const content = readFileSync83(transcriptPath, "utf-8");
96473
97048
  const lines = content.trim().split(`
96474
97049
  `).filter(Boolean);
96475
97050
  for (let i = lines.length - 1;i >= 0; i--) {
@@ -96518,7 +97093,7 @@ function registerDebugCommand(program3) {
96518
97093
  }
96519
97094
  const agentsDir = resolveAgentsDir(config);
96520
97095
  const agentDir = resolve53(agentsDir, agentName);
96521
- if (!existsSync90(agentDir)) {
97096
+ if (!existsSync91(agentDir)) {
96522
97097
  console.error(`Agent directory not found: ${agentDir}`);
96523
97098
  process.exit(1);
96524
97099
  }
@@ -96573,7 +97148,7 @@ function registerDebugCommand(program3) {
96573
97148
  }
96574
97149
  console.log(`=== Append System Prompt (per-session) ===
96575
97150
  `);
96576
- const handoffContent = existsSync90(handoffPath) ? readFileSync82(handoffPath, "utf-8") : "";
97151
+ const handoffContent = existsSync91(handoffPath) ? readFileSync83(handoffPath, "utf-8") : "";
96577
97152
  if (handoffContent.trim().length > 0) {
96578
97153
  console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
96579
97154
  console.log(handoffContent);
@@ -96584,7 +97159,7 @@ function registerDebugCommand(program3) {
96584
97159
  }
96585
97160
  console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
96586
97161
  `);
96587
- const claudeMdContent = existsSync90(claudeMdPath) ? readFileSync82(claudeMdPath, "utf-8") : "";
97162
+ const claudeMdContent = existsSync91(claudeMdPath) ? readFileSync83(claudeMdPath, "utf-8") : "";
96588
97163
  if (claudeMdContent.trim().length > 0) {
96589
97164
  console.log(`(${formatBytes(claudeMdContent.length)})`);
96590
97165
  console.log(claudeMdContent);
@@ -96595,7 +97170,7 @@ function registerDebugCommand(program3) {
96595
97170
  }
96596
97171
  console.log(`=== Persona (SOUL.md) ===
96597
97172
  `);
96598
- const soulMdContent = existsSync90(soulMdPath) ? readFileSync82(soulMdPath, "utf-8") : existsSync90(workspaceSoulMdPath) ? readFileSync82(workspaceSoulMdPath, "utf-8") : "";
97173
+ const soulMdContent = existsSync91(soulMdPath) ? readFileSync83(soulMdPath, "utf-8") : existsSync91(workspaceSoulMdPath) ? readFileSync83(workspaceSoulMdPath, "utf-8") : "";
96599
97174
  if (soulMdContent.trim().length > 0) {
96600
97175
  console.log(`(${formatBytes(soulMdContent.length)})`);
96601
97176
  console.log(soulMdContent);
@@ -96659,8 +97234,8 @@ function registerDebugCommand(program3) {
96659
97234
  const fleetDir = join93(agentsDir, "..", "fleet");
96660
97235
  const fleetInvPath = join93(fleetDir, "switchroom-invariants.md");
96661
97236
  const fleetClaudePath = join93(fleetDir, "CLAUDE.md");
96662
- const fleetInvBytes = existsSync90(fleetInvPath) ? readFileSync82(fleetInvPath, "utf-8").length : 0;
96663
- const fleetClaudeBytes = existsSync90(fleetClaudePath) ? readFileSync82(fleetClaudePath, "utf-8").length : 0;
97237
+ const fleetInvBytes = existsSync91(fleetInvPath) ? readFileSync83(fleetInvPath, "utf-8").length : 0;
97238
+ const fleetClaudeBytes = existsSync91(fleetClaudePath) ? readFileSync83(fleetClaudePath, "utf-8").length : 0;
96664
97239
  const fleetBytes = fleetInvBytes + fleetClaudeBytes;
96665
97240
  const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
96666
97241
  console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
@@ -96693,20 +97268,20 @@ init_source();
96693
97268
 
96694
97269
  // src/worktree/claim.ts
96695
97270
  import { execFileSync as execFileSync26 } from "node:child_process";
96696
- import { closeSync as closeSync17, mkdirSync as mkdirSync51, openSync as openSync17, existsSync as existsSync92, unlinkSync as unlinkSync19 } from "node:fs";
97271
+ import { closeSync as closeSync17, mkdirSync as mkdirSync52, openSync as openSync17, existsSync as existsSync93, unlinkSync as unlinkSync19 } from "node:fs";
96697
97272
  import { join as join95, resolve as resolve55 } from "node:path";
96698
97273
  import { homedir as homedir51 } from "node:os";
96699
97274
  import { randomBytes as randomBytes14 } from "node:crypto";
96700
97275
 
96701
97276
  // src/worktree/registry.ts
96702
97277
  import {
96703
- mkdirSync as mkdirSync50,
96704
- writeFileSync as writeFileSync36,
96705
- readFileSync as readFileSync83,
97278
+ mkdirSync as mkdirSync51,
97279
+ writeFileSync as writeFileSync37,
97280
+ readFileSync as readFileSync84,
96706
97281
  readdirSync as readdirSync31,
96707
97282
  unlinkSync as unlinkSync18,
96708
- existsSync as existsSync91,
96709
- renameSync as renameSync22
97283
+ existsSync as existsSync92,
97284
+ renameSync as renameSync23
96710
97285
  } from "node:fs";
96711
97286
  import { join as join94, resolve as resolve54 } from "node:path";
96712
97287
  import { homedir as homedir50 } from "node:os";
@@ -96717,20 +97292,20 @@ function recordPath(id) {
96717
97292
  return join94(registryDir(), `${id}.json`);
96718
97293
  }
96719
97294
  function ensureDir2() {
96720
- mkdirSync50(registryDir(), { recursive: true });
97295
+ mkdirSync51(registryDir(), { recursive: true });
96721
97296
  }
96722
97297
  function writeRecord(record2) {
96723
97298
  ensureDir2();
96724
97299
  const target = recordPath(record2.id);
96725
97300
  const tmp = `${target}.tmp${process.pid}`;
96726
- writeFileSync36(tmp, JSON.stringify(record2, null, 2) + `
97301
+ writeFileSync37(tmp, JSON.stringify(record2, null, 2) + `
96727
97302
  `, { mode: 384 });
96728
- renameSync22(tmp, target);
97303
+ renameSync23(tmp, target);
96729
97304
  }
96730
97305
  function readRecord(id) {
96731
97306
  const path10 = recordPath(id);
96732
97307
  try {
96733
- const raw = readFileSync83(path10, "utf8");
97308
+ const raw = readFileSync84(path10, "utf8");
96734
97309
  return JSON.parse(raw);
96735
97310
  } catch {
96736
97311
  return null;
@@ -96763,7 +97338,7 @@ function countByRepo(repoPath) {
96763
97338
  // src/worktree/claim.ts
96764
97339
  function acquireRepoLock(repoPath) {
96765
97340
  const lockDir = registryDir();
96766
- mkdirSync51(lockDir, { recursive: true });
97341
+ mkdirSync52(lockDir, { recursive: true });
96767
97342
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
96768
97343
  const lockPath = join95(lockDir, `.lock-${lockName}`);
96769
97344
  const deadline = Date.now() + 5000;
@@ -96819,7 +97394,7 @@ function expandHome(p) {
96819
97394
  }
96820
97395
  async function claimWorktree(input, codeRepos) {
96821
97396
  const repoPath = resolveRepoPath(input.repo, codeRepos);
96822
- if (!existsSync92(repoPath)) {
97397
+ if (!existsSync93(repoPath)) {
96823
97398
  throw new Error(`Repository path does not exist: ${repoPath}`);
96824
97399
  }
96825
97400
  let concurrencyCap = DEFAULT_CONCURRENCY;
@@ -96841,7 +97416,7 @@ async function claimWorktree(input, codeRepos) {
96841
97416
  const taskSuffix = input.taskName ? sanitizeTaskName(input.taskName) : "task";
96842
97417
  branch = `task/${taskSuffix}-${id}`;
96843
97418
  const baseDir = worktreesBaseDir();
96844
- mkdirSync51(baseDir, { recursive: true });
97419
+ mkdirSync52(baseDir, { recursive: true });
96845
97420
  worktreePath = join95(baseDir, `${id}-${taskSuffix}`);
96846
97421
  const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
96847
97422
  const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
@@ -96875,7 +97450,7 @@ async function claimWorktree(input, codeRepos) {
96875
97450
 
96876
97451
  // src/worktree/release.ts
96877
97452
  import { execFileSync as execFileSync27 } from "node:child_process";
96878
- import { existsSync as existsSync93 } from "node:fs";
97453
+ import { existsSync as existsSync94 } from "node:fs";
96879
97454
  function releaseWorktree(input) {
96880
97455
  const { id } = input;
96881
97456
  const record2 = readRecord(id);
@@ -96883,7 +97458,7 @@ function releaseWorktree(input) {
96883
97458
  return { released: true };
96884
97459
  }
96885
97460
  let gitSuccess = true;
96886
- if (existsSync93(record2.path)) {
97461
+ if (existsSync94(record2.path)) {
96887
97462
  try {
96888
97463
  execFileSync27("git", ["worktree", "remove", "--force", record2.path], {
96889
97464
  cwd: record2.repo,
@@ -96922,7 +97497,7 @@ function listWorktrees() {
96922
97497
 
96923
97498
  // src/worktree/reaper.ts
96924
97499
  import { execFileSync as execFileSync28 } from "node:child_process";
96925
- import { existsSync as existsSync94 } from "node:fs";
97500
+ import { existsSync as existsSync95 } from "node:fs";
96926
97501
  var STALE_THRESHOLD_MS = 10 * 60 * 1000;
96927
97502
  function reapSkipReasonText(action) {
96928
97503
  switch (action) {
@@ -96983,7 +97558,7 @@ function planReaper(nowMs, deps = {}) {
96983
97558
  const plan = [];
96984
97559
  for (const record2 of listRecords()) {
96985
97560
  const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
96986
- const worktreeExists = existsSync94(record2.path);
97561
+ const worktreeExists = existsSync95(record2.path);
96987
97562
  if (!worktreeExists) {
96988
97563
  plan.push({
96989
97564
  record: record2,
@@ -97064,12 +97639,12 @@ function runReaper(nowMs, deps = {}) {
97064
97639
  // src/worktree/gc.ts
97065
97640
  import { execFileSync as execFileSync29 } from "node:child_process";
97066
97641
  import {
97067
- existsSync as existsSync95,
97068
- readFileSync as readFileSync84,
97642
+ existsSync as existsSync96,
97643
+ readFileSync as readFileSync85,
97069
97644
  readdirSync as readdirSync32,
97070
97645
  statSync as statSync51,
97071
- renameSync as renameSync23,
97072
- mkdirSync as mkdirSync52,
97646
+ renameSync as renameSync24,
97647
+ mkdirSync as mkdirSync53,
97073
97648
  rmSync as rmSync17
97074
97649
  } from "node:fs";
97075
97650
  import { homedir as homedir52 } from "node:os";
@@ -97191,9 +97766,9 @@ function trashRoot() {
97191
97766
  return resolve56(process.env.SWITCHROOM_WORKTREE_TRASH ?? join96(homedir52(), ".switchroom", "worktree-gc-trash"));
97192
97767
  }
97193
97768
  function planGc(roots, deps = {}) {
97194
- const exists = deps.existsSync ?? existsSync95;
97769
+ const exists = deps.existsSync ?? existsSync96;
97195
97770
  const readDir = deps.readDir ?? ((p) => readdirSync32(p));
97196
- const readFile4 = deps.readFile ?? ((p) => readFileSync84(p, "utf8"));
97771
+ const readFile4 = deps.readFile ?? ((p) => readFileSync85(p, "utf8"));
97197
97772
  const stat3 = deps.stat ?? ((p) => statSync51(p));
97198
97773
  const exec = deps.exec ?? defaultExec3;
97199
97774
  const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
@@ -97321,10 +97896,10 @@ function planGc(roots, deps = {}) {
97321
97896
  }
97322
97897
  function applyGc(plan, deps = {}) {
97323
97898
  const exec = deps.exec ?? defaultExec3;
97324
- const mkdirp = deps.mkdirp ?? ((p) => void mkdirSync52(p, { recursive: true }));
97899
+ const mkdirp = deps.mkdirp ?? ((p) => void mkdirSync53(p, { recursive: true }));
97325
97900
  const move = deps.move ?? ((src, dest) => {
97326
97901
  try {
97327
- renameSync23(src, dest);
97902
+ renameSync24(src, dest);
97328
97903
  } catch {
97329
97904
  exec("mv", [src, dest]);
97330
97905
  }
@@ -97370,7 +97945,7 @@ function selectPurgeTargets(entries, olderThanDays) {
97370
97945
  return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
97371
97946
  }
97372
97947
  function listTrashEntries(nowMs, deps = {}) {
97373
- const exists = deps.existsSync ?? existsSync95;
97948
+ const exists = deps.existsSync ?? existsSync96;
97374
97949
  const readDir = deps.readDir ?? ((p) => readdirSync32(p));
97375
97950
  const root = trashRoot();
97376
97951
  if (!exists(root))
@@ -97632,10 +98207,10 @@ init_drive();
97632
98207
  init_scaffold_integration();
97633
98208
  import {
97634
98209
  chmodSync as chmodSync16,
97635
- mkdirSync as mkdirSync53,
98210
+ mkdirSync as mkdirSync54,
97636
98211
  readdirSync as readdirSync33,
97637
98212
  rmSync as rmSync18,
97638
- writeFileSync as writeFileSync37
98213
+ writeFileSync as writeFileSync38
97639
98214
  } from "node:fs";
97640
98215
  import { join as join97 } from "node:path";
97641
98216
  function encodeCredentialsFilename(email) {
@@ -97830,14 +98405,14 @@ function resolveCredentialsDir(env2) {
97830
98405
  return join97(stateBase, "google-workspace-mcp", "credentials");
97831
98406
  }
97832
98407
  function writeSeedFile(dir, email, seed) {
97833
- mkdirSync53(dir, { recursive: true, mode: 448 });
98408
+ mkdirSync54(dir, { recursive: true, mode: 448 });
97834
98409
  chmodSync16(dir, 448);
97835
98410
  for (const name of readdirSync33(dir)) {
97836
98411
  rmSync18(join97(dir, name), { force: true, recursive: true });
97837
98412
  }
97838
98413
  const filename = encodeCredentialsFilename(email);
97839
98414
  const filePath = join97(dir, filename);
97840
- writeFileSync37(filePath, JSON.stringify(seed), { mode: 384 });
98415
+ writeFileSync38(filePath, JSON.stringify(seed), { mode: 384 });
97841
98416
  chmodSync16(filePath, 384);
97842
98417
  return filePath;
97843
98418
  }
@@ -97995,8 +98570,8 @@ function registerDriveMcpLauncherCommand(program3) {
97995
98570
  // src/cli/m365-mcp-launcher.ts
97996
98571
  init_scaffold_integration();
97997
98572
  import { spawn as spawn6 } from "node:child_process";
97998
- import { writeFileSync as writeFileSync38, mkdirSync as mkdirSync54 } from "node:fs";
97999
- import { dirname as dirname38, join as join98 } from "node:path";
98573
+ import { writeFileSync as writeFileSync39, mkdirSync as mkdirSync55 } from "node:fs";
98574
+ import { dirname as dirname39, join as join98 } from "node:path";
98000
98575
  var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
98001
98576
  var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
98002
98577
  var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
@@ -98032,8 +98607,8 @@ function computeRefreshDelayMs(expiresAt, now, leadMs = DEFAULT_REFRESH_LEAD_MS)
98032
98607
  function writeRefreshHeartbeat(agentName, data, account) {
98033
98608
  const path10 = heartbeatPath(agentName, account);
98034
98609
  try {
98035
- mkdirSync54(dirname38(path10), { recursive: true });
98036
- writeFileSync38(path10, JSON.stringify(data, null, 2), { mode: 420 });
98610
+ mkdirSync55(dirname39(path10), { recursive: true });
98611
+ writeFileSync39(path10, JSON.stringify(data, null, 2), { mode: 420 });
98037
98612
  } catch {}
98038
98613
  }
98039
98614
  function heartbeatPath(agentName, account) {
@@ -98273,8 +98848,8 @@ function registerM365McpLauncherCommand(program3) {
98273
98848
  // src/cli/notion-mcp-launcher.ts
98274
98849
  init_scaffold_integration();
98275
98850
  import { spawn as spawn7 } from "node:child_process";
98276
- import { existsSync as existsSync96, mkdirSync as mkdirSync55, writeFileSync as writeFileSync39 } from "node:fs";
98277
- import { dirname as dirname39 } from "node:path";
98851
+ import { existsSync as existsSync97, mkdirSync as mkdirSync56, writeFileSync as writeFileSync40 } from "node:fs";
98852
+ import { dirname as dirname40 } from "node:path";
98278
98853
  var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
98279
98854
  var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
98280
98855
  var DEFAULT_VAULT_KEY = "notion/integration-token";
@@ -98284,10 +98859,10 @@ function buildNotionMcpArgs(opts) {
98284
98859
  }
98285
98860
  function defaultWriteHeartbeat(path10, contents) {
98286
98861
  try {
98287
- const dir = dirname39(path10);
98288
- if (!existsSync96(dir))
98289
- mkdirSync55(dir, { recursive: true });
98290
- writeFileSync39(path10, contents);
98862
+ const dir = dirname40(path10);
98863
+ if (!existsSync97(dir))
98864
+ mkdirSync56(dir, { recursive: true });
98865
+ writeFileSync40(path10, contents);
98291
98866
  } catch {}
98292
98867
  }
98293
98868
  async function runNotionMcpLauncher(opts, runtime) {
@@ -98396,7 +98971,7 @@ function registerNotionMcpLauncherCommand(program3) {
98396
98971
  }
98397
98972
 
98398
98973
  // src/cli/hindsight-mcp-shim.ts
98399
- import { mkdirSync as mkdirSync56, readFileSync as readFileSync85, renameSync as renameSync24, writeFileSync as writeFileSync40 } from "node:fs";
98974
+ import { mkdirSync as mkdirSync57, readFileSync as readFileSync86, renameSync as renameSync25, writeFileSync as writeFileSync41 } from "node:fs";
98400
98975
  import { tmpdir as tmpdir5 } from "node:os";
98401
98976
  import { join as join99 } from "node:path";
98402
98977
  import { createInterface as createInterface6 } from "node:readline";
@@ -98848,18 +99423,18 @@ class HindsightShim {
98848
99423
  }
98849
99424
  writeCache(result) {
98850
99425
  try {
98851
- mkdirSync56(this.opts.cacheDir, { recursive: true });
99426
+ mkdirSync57(this.opts.cacheDir, { recursive: true });
98852
99427
  const tmp = join99(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
98853
- writeFileSync40(tmp, JSON.stringify(result, null, 2) + `
99428
+ writeFileSync41(tmp, JSON.stringify(result, null, 2) + `
98854
99429
  `);
98855
- renameSync24(tmp, this.cachePath);
99430
+ renameSync25(tmp, this.cachePath);
98856
99431
  } catch (err) {
98857
99432
  this.log(`[hindsight-shim] cache write failed: ${String(err)}`);
98858
99433
  }
98859
99434
  }
98860
99435
  readCache() {
98861
99436
  try {
98862
- const parsed = JSON.parse(readFileSync85(this.cachePath, "utf-8"));
99437
+ const parsed = JSON.parse(readFileSync86(this.cachePath, "utf-8"));
98863
99438
  if (Array.isArray(parsed.tools))
98864
99439
  return parsed;
98865
99440
  return null;
@@ -99092,7 +99667,7 @@ function registerHindsightMcpShimCommand(program3) {
99092
99667
 
99093
99668
  // src/cli/deliver-file.ts
99094
99669
  init_client2();
99095
- import { readFileSync as readFileSync86, statSync as statSync52 } from "node:fs";
99670
+ import { readFileSync as readFileSync87, statSync as statSync52 } from "node:fs";
99096
99671
  import { basename as basename13 } from "node:path";
99097
99672
 
99098
99673
  // src/delivery/onedrive.ts
@@ -99432,7 +100007,7 @@ async function defaultResolveProvider() {
99432
100007
  async function runDeliverFile(localPath, deps = {}) {
99433
100008
  const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
99434
100009
  const sizeOf = deps.fileSize ?? ((p) => statSync52(p).size);
99435
- const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync86(p)));
100010
+ const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync87(p)));
99436
100011
  const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
99437
100012
  let size;
99438
100013
  try {
@@ -99753,7 +100328,7 @@ function runRedactStdin() {
99753
100328
  }
99754
100329
 
99755
100330
  // src/cli/status-ask.ts
99756
- import { readFileSync as readFileSync87, existsSync as existsSync97, readdirSync as readdirSync34 } from "node:fs";
100331
+ import { readFileSync as readFileSync88, existsSync as existsSync98, readdirSync as readdirSync34 } from "node:fs";
99757
100332
  import { join as join100 } from "node:path";
99758
100333
  import { homedir as homedir53 } from "node:os";
99759
100334
 
@@ -100029,7 +100604,7 @@ function runReport(opts) {
100029
100604
  for (const src of sources) {
100030
100605
  let content;
100031
100606
  try {
100032
- content = readFileSync87(src.path, "utf-8");
100607
+ content = readFileSync88(src.path, "utf-8");
100033
100608
  } catch (err) {
100034
100609
  process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
100035
100610
  `);
@@ -100076,7 +100651,7 @@ function runReport(opts) {
100076
100651
  function resolveSources(explicitPath) {
100077
100652
  if (explicitPath != null && explicitPath.trim() !== "") {
100078
100653
  const trimmed = explicitPath.trim();
100079
- if (!existsSync97(trimmed)) {
100654
+ if (!existsSync98(trimmed)) {
100080
100655
  process.stderr.write(`status-ask report: ${trimmed}: file not found
100081
100656
  `);
100082
100657
  process.exit(1);
@@ -100092,7 +100667,7 @@ function resolveSources(explicitPath) {
100092
100667
  } catch {
100093
100668
  agentsDir = join100(homedir53(), ".switchroom", "agents");
100094
100669
  }
100095
- if (!existsSync97(agentsDir))
100670
+ if (!existsSync98(agentsDir))
100096
100671
  return [];
100097
100672
  const sources = [];
100098
100673
  let entries;
@@ -100103,7 +100678,7 @@ function resolveSources(explicitPath) {
100103
100678
  }
100104
100679
  for (const name of entries) {
100105
100680
  const path10 = join100(agentsDir, name, "runtime-metrics.jsonl");
100106
- if (existsSync97(path10)) {
100681
+ if (existsSync98(path10)) {
100107
100682
  sources.push({ path: path10, agent: name });
100108
100683
  }
100109
100684
  }
@@ -100133,13 +100708,13 @@ var import_yaml21 = __toESM(require_dist(), 1);
100133
100708
  init_paths();
100134
100709
  import {
100135
100710
  closeSync as closeSync18,
100136
- existsSync as existsSync98,
100711
+ existsSync as existsSync99,
100137
100712
  fsyncSync as fsyncSync8,
100138
- mkdirSync as mkdirSync57,
100713
+ mkdirSync as mkdirSync58,
100139
100714
  openSync as openSync18,
100140
100715
  readdirSync as readdirSync35,
100141
- readFileSync as readFileSync88,
100142
- renameSync as renameSync25,
100716
+ readFileSync as readFileSync89,
100717
+ renameSync as renameSync26,
100143
100718
  statSync as statSync53,
100144
100719
  unlinkSync as unlinkSync20,
100145
100720
  writeSync as writeSync10
@@ -100163,15 +100738,15 @@ function overlayPathsFor(agent, opts = {}) {
100163
100738
  };
100164
100739
  }
100165
100740
  function ensureDirs(paths) {
100166
- mkdirSync57(paths.scheduleDir, { recursive: true });
100167
- mkdirSync57(paths.scheduleStagingDir, { recursive: true });
100741
+ mkdirSync58(paths.scheduleDir, { recursive: true });
100742
+ mkdirSync58(paths.scheduleStagingDir, { recursive: true });
100168
100743
  }
100169
100744
  function ensureSkillsDirs(paths) {
100170
- mkdirSync57(paths.skillsDir, { recursive: true });
100171
- mkdirSync57(paths.skillsStagingDir, { recursive: true });
100745
+ mkdirSync58(paths.skillsDir, { recursive: true });
100746
+ mkdirSync58(paths.skillsStagingDir, { recursive: true });
100172
100747
  }
100173
100748
  function withAgentLock(paths, fn) {
100174
- mkdirSync57(paths.agentRoot, { recursive: true });
100749
+ mkdirSync58(paths.agentRoot, { recursive: true });
100175
100750
  const start = Date.now();
100176
100751
  const TIMEOUT_MS = 5000;
100177
100752
  let fd = null;
@@ -100221,7 +100796,7 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
100221
100796
  } finally {
100222
100797
  closeSync18(fd);
100223
100798
  }
100224
- renameSync25(stagingPath, finalPath);
100799
+ renameSync26(stagingPath, finalPath);
100225
100800
  return finalPath;
100226
100801
  });
100227
100802
  }
@@ -100238,7 +100813,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
100238
100813
  } finally {
100239
100814
  closeSync18(fd);
100240
100815
  }
100241
- renameSync25(stagingPath, finalPath);
100816
+ renameSync26(stagingPath, finalPath);
100242
100817
  return finalPath;
100243
100818
  });
100244
100819
  }
@@ -100246,7 +100821,7 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
100246
100821
  const paths = overlayPathsFor(agent, opts);
100247
100822
  return withAgentLock(paths, () => {
100248
100823
  const finalPath = join101(paths.skillsDir, `${slug}.yaml`);
100249
- if (!existsSync98(finalPath))
100824
+ if (!existsSync99(finalPath))
100250
100825
  return false;
100251
100826
  unlinkSync20(finalPath);
100252
100827
  return true;
@@ -100254,7 +100829,7 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
100254
100829
  }
100255
100830
  function listSkillsOverlayEntries(agent, opts = {}) {
100256
100831
  const paths = overlayPathsFor(agent, opts);
100257
- if (!existsSync98(paths.skillsDir))
100832
+ if (!existsSync99(paths.skillsDir))
100258
100833
  return [];
100259
100834
  const out = [];
100260
100835
  for (const name of readdirSync35(paths.skillsDir)) {
@@ -100262,7 +100837,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
100262
100837
  continue;
100263
100838
  const full = join101(paths.skillsDir, name);
100264
100839
  try {
100265
- const raw = readFileSync88(full, "utf-8");
100840
+ const raw = readFileSync89(full, "utf-8");
100266
100841
  const slug = name.replace(/\.ya?ml$/i, "");
100267
100842
  out.push({ slug, path: full, raw });
100268
100843
  } catch {}
@@ -100273,7 +100848,7 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
100273
100848
  const paths = overlayPathsFor(agent, opts);
100274
100849
  return withAgentLock(paths, () => {
100275
100850
  const finalPath = join101(paths.scheduleDir, `${slug}.yaml`);
100276
- if (!existsSync98(finalPath))
100851
+ if (!existsSync99(finalPath))
100277
100852
  return false;
100278
100853
  unlinkSync20(finalPath);
100279
100854
  return true;
@@ -100281,7 +100856,7 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
100281
100856
  }
100282
100857
  function listOverlayEntries(agent, opts = {}) {
100283
100858
  const paths = overlayPathsFor(agent, opts);
100284
- if (!existsSync98(paths.scheduleDir))
100859
+ if (!existsSync99(paths.scheduleDir))
100285
100860
  return [];
100286
100861
  const out = [];
100287
100862
  for (const name of readdirSync35(paths.scheduleDir)) {
@@ -100289,7 +100864,7 @@ function listOverlayEntries(agent, opts = {}) {
100289
100864
  continue;
100290
100865
  const full = join101(paths.scheduleDir, name);
100291
100866
  try {
100292
- const raw = readFileSync88(full, "utf-8");
100867
+ const raw = readFileSync89(full, "utf-8");
100293
100868
  const slug = name.replace(/\.ya?ml$/i, "");
100294
100869
  out.push({ slug, path: full, raw });
100295
100870
  } catch {}
@@ -100509,15 +101084,15 @@ function reconcileAgentCronOnly(agent) {
100509
101084
  // src/cli/agent-config-pending.ts
100510
101085
  import {
100511
101086
  closeSync as closeSync19,
100512
- existsSync as existsSync99,
101087
+ existsSync as existsSync100,
100513
101088
  fsyncSync as fsyncSync9,
100514
- mkdirSync as mkdirSync58,
101089
+ mkdirSync as mkdirSync59,
100515
101090
  openSync as openSync19,
100516
101091
  readdirSync as readdirSync36,
100517
- readFileSync as readFileSync89,
100518
- renameSync as renameSync26,
101092
+ readFileSync as readFileSync90,
101093
+ renameSync as renameSync27,
100519
101094
  unlinkSync as unlinkSync21,
100520
- writeFileSync as writeFileSync41,
101095
+ writeFileSync as writeFileSync42,
100521
101096
  writeSync as writeSync11
100522
101097
  } from "node:fs";
100523
101098
  import { join as join102 } from "node:path";
@@ -100529,7 +101104,7 @@ function pendingDir(agent, opts = {}) {
100529
101104
  }
100530
101105
  function ensurePendingDir(agent, opts = {}) {
100531
101106
  const dir = pendingDir(agent, opts);
100532
- mkdirSync58(dir, { recursive: true });
101107
+ mkdirSync59(dir, { recursive: true });
100533
101108
  return dir;
100534
101109
  }
100535
101110
  function newStageId() {
@@ -100558,15 +101133,15 @@ function stagePendingScheduleEntry(opts) {
100558
101133
  } finally {
100559
101134
  closeSync19(fd);
100560
101135
  }
100561
- renameSync26(yamlTmp, yamlPath);
101136
+ renameSync27(yamlTmp, yamlPath);
100562
101137
  }
100563
- writeFileSync41(metaPath, JSON.stringify(meta, null, 2) + `
101138
+ writeFileSync42(metaPath, JSON.stringify(meta, null, 2) + `
100564
101139
  `, { mode: 384 });
100565
101140
  return { stageId, yamlPath, metaPath };
100566
101141
  }
100567
101142
  function listPendingScheduleEntries(agent, opts = {}) {
100568
101143
  const dir = pendingDir(agent, opts);
100569
- if (!existsSync99(dir))
101144
+ if (!existsSync100(dir))
100570
101145
  return [];
100571
101146
  const out = [];
100572
101147
  for (const name of readdirSync36(dir).sort()) {
@@ -100575,10 +101150,10 @@ function listPendingScheduleEntries(agent, opts = {}) {
100575
101150
  const stageId = name.slice(0, -".meta.json".length);
100576
101151
  const metaPath = join102(dir, name);
100577
101152
  const yamlPath = join102(dir, `${stageId}.yaml`);
100578
- if (!existsSync99(yamlPath))
101153
+ if (!existsSync100(yamlPath))
100579
101154
  continue;
100580
101155
  try {
100581
- const meta = JSON.parse(readFileSync89(metaPath, "utf-8"));
101156
+ const meta = JSON.parse(readFileSync90(metaPath, "utf-8"));
100582
101157
  if (meta?.v !== 1 || typeof meta.stage_id !== "string")
100583
101158
  continue;
100584
101159
  out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
@@ -100594,10 +101169,10 @@ function commitPendingScheduleEntry(opts) {
100594
101169
  const slug = match.meta.entry.name ?? match.stageId;
100595
101170
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
100596
101171
  const finalPath = join102(paths.scheduleDir, `${slug}.yaml`);
100597
- if (existsSync99(finalPath)) {
101172
+ if (existsSync100(finalPath)) {
100598
101173
  return { committed: false, reason: "slug_collision" };
100599
101174
  }
100600
- renameSync26(match.yamlPath, finalPath);
101175
+ renameSync27(match.yamlPath, finalPath);
100601
101176
  unlinkSync21(match.metaPath);
100602
101177
  return { committed: true, path: finalPath, slug };
100603
101178
  }
@@ -100616,7 +101191,7 @@ function denyPendingScheduleEntry(opts) {
100616
101191
  }
100617
101192
 
100618
101193
  // src/cli/agent-config-write.ts
100619
- import { existsSync as existsSync100, readFileSync as readFileSync90 } from "node:fs";
101194
+ import { existsSync as existsSync101, readFileSync as readFileSync91 } from "node:fs";
100620
101195
  import { execFileSync as execFileSync30 } from "node:child_process";
100621
101196
 
100622
101197
  // src/scheduler/schedule-report.ts
@@ -101006,8 +101581,8 @@ function scheduleRemove(opts) {
101006
101581
  }
101007
101582
  let priorContent = null;
101008
101583
  try {
101009
- if (existsSync100(match.path))
101010
- priorContent = readFileSync90(match.path, "utf-8");
101584
+ if (existsSync101(match.path))
101585
+ priorContent = readFileSync91(match.path, "utf-8");
101011
101586
  } catch {}
101012
101587
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
101013
101588
  const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
@@ -101210,7 +101785,7 @@ function registerAgentConfigWriteCommands(program3) {
101210
101785
  }
101211
101786
  let blob;
101212
101787
  if (opts.jsonl) {
101213
- blob = existsSync100(opts.jsonl) ? readFileSync90(opts.jsonl, "utf-8") : "";
101788
+ blob = existsSync101(opts.jsonl) ? readFileSync91(opts.jsonl, "utf-8") : "";
101214
101789
  } else {
101215
101790
  try {
101216
101791
  blob = execFileSync30("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
@@ -101242,7 +101817,7 @@ function registerAgentConfigWriteCommands(program3) {
101242
101817
 
101243
101818
  // src/cli/agent-config-skill-write.ts
101244
101819
  var import_yaml22 = __toESM(require_dist(), 1);
101245
- import { existsSync as existsSync101 } from "node:fs";
101820
+ import { existsSync as existsSync102 } from "node:fs";
101246
101821
  init_reconcile_default_skills();
101247
101822
  init_agent_config();
101248
101823
  var import_yaml23 = __toESM(require_dist(), 1);
@@ -101322,7 +101897,7 @@ function skillInstall(opts) {
101322
101897
  }
101323
101898
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
101324
101899
  const skillPath = join103(poolDir, skillName);
101325
- if (!existsSync101(skillPath)) {
101900
+ if (!existsSync102(skillPath)) {
101326
101901
  return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
101327
101902
  }
101328
101903
  const yamlText = import_yaml22.stringify({ skills: [skillName] });
@@ -101486,22 +102061,22 @@ function registerAgentConfigSkillWriteCommands(program3) {
101486
102061
  // src/cli/skill.ts
101487
102062
  import {
101488
102063
  closeSync as closeSync20,
101489
- existsSync as existsSync102,
102064
+ existsSync as existsSync103,
101490
102065
  lstatSync as lstatSync12,
101491
- mkdirSync as mkdirSync59,
102066
+ mkdirSync as mkdirSync60,
101492
102067
  mkdtempSync as mkdtempSync5,
101493
102068
  openSync as openSync20,
101494
- readFileSync as readFileSync91,
102069
+ readFileSync as readFileSync92,
101495
102070
  readdirSync as readdirSync37,
101496
102071
  realpathSync as realpathSync7,
101497
- renameSync as renameSync27,
102072
+ renameSync as renameSync28,
101498
102073
  rmSync as rmSync19,
101499
102074
  statSync as statSync54,
101500
- writeFileSync as writeFileSync42
102075
+ writeFileSync as writeFileSync43
101501
102076
  } from "node:fs";
101502
102077
  import { tmpdir as tmpdir6, homedir as homedir54 } from "node:os";
101503
- import { dirname as dirname40, join as join104, relative as relative4, resolve as resolve58 } from "node:path";
101504
- import { spawnSync as spawnSync20 } from "node:child_process";
102078
+ import { dirname as dirname41, join as join104, relative as relative4, resolve as resolve58 } from "node:path";
102079
+ import { spawnSync as spawnSync21 } from "node:child_process";
101505
102080
 
101506
102081
  // src/cli/skill-common.ts
101507
102082
  var import_yaml24 = __toESM(require_dist(), 1);
@@ -101784,7 +102359,7 @@ function loadFromDir(dir) {
101784
102359
  continue;
101785
102360
  }
101786
102361
  if (ent.isFile()) {
101787
- const buf = readFileSync91(full);
102362
+ const buf = readFileSync92(full);
101788
102363
  files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
101789
102364
  }
101790
102365
  }
@@ -101795,7 +102370,7 @@ function loadFromDir(dir) {
101795
102370
  function loadFromTarball(tarPath) {
101796
102371
  const isGz = tarPath.endsWith(".gz") || tarPath.endsWith(".tgz");
101797
102372
  const listFlags = isGz ? ["-tzf"] : ["-tf"];
101798
- const list2 = spawnSync20("tar", [...listFlags, tarPath], {
102373
+ const list2 = spawnSync21("tar", [...listFlags, tarPath], {
101799
102374
  encoding: "utf-8",
101800
102375
  stdio: ["ignore", "pipe", "pipe"]
101801
102376
  });
@@ -101812,7 +102387,7 @@ function loadFromTarball(tarPath) {
101812
102387
  const staging = mkdtempSync5(join104(tmpdir6(), "skill-apply-extract-"));
101813
102388
  try {
101814
102389
  const flags = isGz ? ["-xzf"] : ["-xf"];
101815
- const r = spawnSync20("tar", [
102390
+ const r = spawnSync21("tar", [
101816
102391
  ...flags,
101817
102392
  tarPath,
101818
102393
  "-C",
@@ -101833,7 +102408,7 @@ function loadFromTarball(tarPath) {
101833
102408
  }
101834
102409
  }
101835
102410
  function loadSingleFile(filePath) {
101836
- const content = readFileSync91(filePath, "utf-8");
102411
+ const content = readFileSync92(filePath, "utf-8");
101837
102412
  return { "SKILL.md": content };
101838
102413
  }
101839
102414
  function loadFromStdin() {
@@ -101887,7 +102462,7 @@ function validatePayload(name, files) {
101887
102462
  if (errors2.length === 0) {
101888
102463
  for (const [path10, content] of Object.entries(files)) {
101889
102464
  if (SH_SCRIPT_RE2.test(path10)) {
101890
- const r = spawnSync20("bash", ["-n"], {
102465
+ const r = spawnSync21("bash", ["-n"], {
101891
102466
  input: content,
101892
102467
  encoding: "utf-8"
101893
102468
  });
@@ -101898,8 +102473,8 @@ function validatePayload(name, files) {
101898
102473
  const tmp = mkdtempSync5(join104(tmpdir6(), "skill-apply-py-"));
101899
102474
  const tmpPy = join104(tmp, "check.py");
101900
102475
  try {
101901
- writeFileSync42(tmpPy, content);
101902
- const r = spawnSync20("python3", ["-m", "py_compile", tmpPy], {
102476
+ writeFileSync43(tmpPy, content);
102477
+ const r = spawnSync21("python3", ["-m", "py_compile", tmpPy], {
101903
102478
  encoding: "utf-8"
101904
102479
  });
101905
102480
  if (r.status !== 0) {
@@ -101916,7 +102491,7 @@ function validatePayload(name, files) {
101916
102491
  function diffSummary(currentDir, files) {
101917
102492
  const lines = [];
101918
102493
  const currentFiles = {};
101919
- if (existsSync102(currentDir)) {
102494
+ if (existsSync103(currentDir)) {
101920
102495
  const walk2 = (sub) => {
101921
102496
  for (const ent of readdirSync37(sub, { withFileTypes: true })) {
101922
102497
  const full = join104(sub, ent.name);
@@ -101924,7 +102499,7 @@ function diffSummary(currentDir, files) {
101924
102499
  if (ent.isDirectory()) {
101925
102500
  walk2(full);
101926
102501
  } else if (ent.isFile()) {
101927
- currentFiles[rel.replace(/\\/g, "/")] = readFileSync91(full, "utf-8");
102502
+ currentFiles[rel.replace(/\\/g, "/")] = readFileSync92(full, "utf-8");
101928
102503
  }
101929
102504
  }
101930
102505
  };
@@ -101952,8 +102527,8 @@ function diffSummary(currentDir, files) {
101952
102527
  `);
101953
102528
  }
101954
102529
  function writePayload(poolDir, name, files) {
101955
- if (!existsSync102(poolDir)) {
101956
- mkdirSync59(poolDir, { recursive: true, mode: 493 });
102530
+ if (!existsSync103(poolDir)) {
102531
+ mkdirSync60(poolDir, { recursive: true, mode: 493 });
101957
102532
  }
101958
102533
  const target = join104(poolDir, name);
101959
102534
  let targetIsSymlink = false;
@@ -101971,10 +102546,10 @@ function writePayload(poolDir, name, files) {
101971
102546
  try {
101972
102547
  for (const [path10, content] of Object.entries(files)) {
101973
102548
  const full = join104(staging, path10);
101974
- mkdirSync59(dirname40(full), { recursive: true, mode: 493 });
102549
+ mkdirSync60(dirname41(full), { recursive: true, mode: 493 });
101975
102550
  const fd = openSync20(full, "wx");
101976
102551
  try {
101977
- writeFileSync42(fd, content);
102552
+ writeFileSync43(fd, content);
101978
102553
  } finally {
101979
102554
  closeSync20(fd);
101980
102555
  }
@@ -101990,9 +102565,9 @@ function writePayload(poolDir, name, files) {
101990
102565
  } catch {}
101991
102566
  if (targetExists) {
101992
102567
  oldRename = `${target}.skill-apply-old-${Date.now()}`;
101993
- renameSync27(target, oldRename);
102568
+ renameSync28(target, oldRename);
101994
102569
  }
101995
- renameSync27(staging, target);
102570
+ renameSync28(staging, target);
101996
102571
  if (oldRename) {
101997
102572
  rmSync19(oldRename, { recursive: true, force: true });
101998
102573
  oldRename = null;
@@ -102001,12 +102576,12 @@ function writePayload(poolDir, name, files) {
102001
102576
  try {
102002
102577
  rmSync19(staging, { recursive: true, force: true });
102003
102578
  } catch {}
102004
- if (oldRename && existsSync102(oldRename)) {
102579
+ if (oldRename && existsSync103(oldRename)) {
102005
102580
  try {
102006
- if (existsSync102(target)) {
102581
+ if (existsSync103(target)) {
102007
102582
  rmSync19(target, { recursive: true, force: true });
102008
102583
  }
102009
- renameSync27(oldRename, target);
102584
+ renameSync28(oldRename, target);
102010
102585
  } catch {}
102011
102586
  }
102012
102587
  throw err2;
@@ -102024,7 +102599,7 @@ function registerSkillCommand(program3) {
102024
102599
  files = loadFromStdin();
102025
102600
  } else {
102026
102601
  const fromPath = resolve58(opts.from);
102027
- if (!existsSync102(fromPath)) {
102602
+ if (!existsSync103(fromPath)) {
102028
102603
  fail4(`--from path does not exist: ${opts.from}`);
102029
102604
  }
102030
102605
  const st = statSync54(fromPath);
@@ -102062,7 +102637,7 @@ function registerSkillCommand(program3) {
102062
102637
  \u2713 Wrote ${name} to ${currentDir}`));
102063
102638
  const self = selfInvokeArgv(["apply", "--non-interactive"]);
102064
102639
  console.log(source_default.gray(`Running \`switchroom apply --non-interactive\`...`));
102065
- const r = spawnSync20(self.command, self.argv, { stdio: "inherit" });
102640
+ const r = spawnSync21(self.command, self.argv, { stdio: "inherit" });
102066
102641
  if (r.status !== 0) {
102067
102642
  console.error(source_default.yellow(`(warning: \`switchroom apply\` exited ${r.status} \u2014 skill is ` + `in the pool but symlinks may not be refreshed. Re-run manually.)`));
102068
102643
  }
@@ -102080,22 +102655,22 @@ function sumBytes(files) {
102080
102655
  init_esm();
102081
102656
  import {
102082
102657
  closeSync as closeSync21,
102083
- existsSync as existsSync103,
102658
+ existsSync as existsSync104,
102084
102659
  lstatSync as lstatSync13,
102085
- mkdirSync as mkdirSync60,
102660
+ mkdirSync as mkdirSync61,
102086
102661
  mkdtempSync as mkdtempSync6,
102087
102662
  openSync as openSync21,
102088
- readFileSync as readFileSync92,
102663
+ readFileSync as readFileSync93,
102089
102664
  readdirSync as readdirSync38,
102090
- renameSync as renameSync28,
102665
+ renameSync as renameSync29,
102091
102666
  rmSync as rmSync20,
102092
102667
  statSync as statSync55,
102093
102668
  utimesSync,
102094
- writeFileSync as writeFileSync43
102669
+ writeFileSync as writeFileSync44
102095
102670
  } from "node:fs";
102096
- import { dirname as dirname41, join as join105, relative as relative5, resolve as resolve59 } from "node:path";
102671
+ import { dirname as dirname42, join as join105, relative as relative5, resolve as resolve59 } from "node:path";
102097
102672
  import { homedir as homedir55, tmpdir as tmpdir7 } from "node:os";
102098
- import { spawnSync as spawnSync21 } from "node:child_process";
102673
+ import { spawnSync as spawnSync22 } from "node:child_process";
102099
102674
  init_helpers();
102100
102675
  init_agent_config();
102101
102676
  init_source();
@@ -102106,14 +102681,14 @@ var PERSONAL_SKILLS_SUBPATH = "personal-skills";
102106
102681
  function resolveConfigSkillsDir(agent) {
102107
102682
  const override = process.env.SWITCHROOM_CONFIG_DIR;
102108
102683
  const candidate = override ? resolve59(override) : join105(homedir55(), ".switchroom-config");
102109
- if (!existsSync103(candidate))
102684
+ if (!existsSync104(candidate))
102110
102685
  return null;
102111
102686
  return join105(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
102112
102687
  }
102113
102688
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
102114
102689
  function sweepMirrorPriors(configSkillsRoot) {
102115
102690
  try {
102116
- if (!existsSync103(configSkillsRoot))
102691
+ if (!existsSync104(configSkillsRoot))
102117
102692
  return;
102118
102693
  const now = Date.now();
102119
102694
  for (const ent of readdirSync38(configSkillsRoot)) {
@@ -102149,17 +102724,17 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
102149
102724
  }
102150
102725
  if (liveSkillDir === null) {
102151
102726
  sweepMirrorPriors(configSkillsRoot);
102152
- if (existsSync103(dest)) {
102727
+ if (existsSync104(dest)) {
102153
102728
  const trash = join105(configSkillsRoot, `.${name}-trash-${Date.now()}`);
102154
- renameSync28(dest, trash);
102729
+ renameSync29(dest, trash);
102155
102730
  }
102156
102731
  return;
102157
102732
  }
102158
- mkdirSync60(configSkillsRoot, { recursive: true, mode: 493 });
102733
+ mkdirSync61(configSkillsRoot, { recursive: true, mode: 493 });
102159
102734
  sweepMirrorPriors(configSkillsRoot);
102160
102735
  const staging = mkdtempSync6(join105(configSkillsRoot, `.${name}-staging-`));
102161
102736
  const walk2 = (src, dst) => {
102162
- mkdirSync60(dst, { recursive: true, mode: 493 });
102737
+ mkdirSync61(dst, { recursive: true, mode: 493 });
102163
102738
  for (const ent of readdirSync38(src, { withFileTypes: true })) {
102164
102739
  const s = join105(src, ent.name);
102165
102740
  const d = join105(dst, ent.name);
@@ -102168,16 +102743,16 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
102168
102743
  if (ent.isDirectory())
102169
102744
  walk2(s, d);
102170
102745
  else if (ent.isFile()) {
102171
- writeFileSync43(d, readFileSync92(s));
102746
+ writeFileSync44(d, readFileSync93(s));
102172
102747
  }
102173
102748
  }
102174
102749
  };
102175
102750
  walk2(liveSkillDir, staging);
102176
- if (existsSync103(dest)) {
102751
+ if (existsSync104(dest)) {
102177
102752
  const prior = join105(configSkillsRoot, `.${name}-prior-${Date.now()}`);
102178
- renameSync28(dest, prior);
102753
+ renameSync29(dest, prior);
102179
102754
  }
102180
- renameSync28(staging, dest);
102755
+ renameSync29(staging, dest);
102181
102756
  } catch (err2) {
102182
102757
  process.stderr.write(source_default.yellow(`warning: mirror to ${dest} failed (${err2.message ?? err2}); ` + `live copy still works, but this skill is not version-controlled until next successful sync.
102183
102758
  `));
@@ -102214,7 +102789,7 @@ function trashDir(agentsRoot, agent) {
102214
102789
  }
102215
102790
  function countPersonalSkills(agentsRoot, agent) {
102216
102791
  const skillsDir = join105(agentsRoot, agent, ".claude", "skills");
102217
- if (!existsSync103(skillsDir))
102792
+ if (!existsSync104(skillsDir))
102218
102793
  return 0;
102219
102794
  let n = 0;
102220
102795
  for (const ent of readdirSync38(skillsDir, { withFileTypes: true })) {
@@ -102261,7 +102836,7 @@ function loadFromDir2(dir) {
102261
102836
  }
102262
102837
  if (ent.isFile()) {
102263
102838
  const rel = relative5(abs, full).replace(/\\/g, "/");
102264
- files[rel] = readFileSync92(full, "utf-8");
102839
+ files[rel] = readFileSync93(full, "utf-8");
102265
102840
  }
102266
102841
  }
102267
102842
  };
@@ -102299,7 +102874,7 @@ function behavioralValidate(files) {
102299
102874
  const errors2 = [];
102300
102875
  for (const [path10, content] of Object.entries(files)) {
102301
102876
  if (SH_SCRIPT_RE.test(path10)) {
102302
- const r = spawnSync21("bash", ["-n"], { input: content, encoding: "utf-8" });
102877
+ const r = spawnSync22("bash", ["-n"], { input: content, encoding: "utf-8" });
102303
102878
  if (r.status !== 0) {
102304
102879
  errors2.push(`${path10} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
102305
102880
  }
@@ -102307,8 +102882,8 @@ function behavioralValidate(files) {
102307
102882
  const tmp = mkdtempSync6(join105(tmpdir7(), "skill-personal-py-"));
102308
102883
  const tmpPy = join105(tmp, "check.py");
102309
102884
  try {
102310
- writeFileSync43(tmpPy, content);
102311
- const r = spawnSync21("python3", ["-m", "py_compile", tmpPy], {
102885
+ writeFileSync44(tmpPy, content);
102886
+ const r = spawnSync22("python3", ["-m", "py_compile", tmpPy], {
102312
102887
  encoding: "utf-8"
102313
102888
  });
102314
102889
  if (r.status !== 0) {
@@ -102323,7 +102898,7 @@ function behavioralValidate(files) {
102323
102898
  }
102324
102899
  function sweepTrash(agentsRoot, agent) {
102325
102900
  const trash = trashDir(agentsRoot, agent);
102326
- if (!existsSync103(trash))
102901
+ if (!existsSync104(trash))
102327
102902
  return;
102328
102903
  const now = Date.now();
102329
102904
  for (const ent of readdirSync38(trash, { withFileTypes: true })) {
@@ -102349,16 +102924,16 @@ function writePersonalSkill(targetDir, files) {
102349
102924
  if (targetIsSymlink) {
102350
102925
  fail5(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
102351
102926
  }
102352
- mkdirSync60(dirname41(targetDir), { recursive: true, mode: 493 });
102353
- const staging = mkdtempSync6(join105(dirname41(targetDir), `.skill-personal-stage-`));
102927
+ mkdirSync61(dirname42(targetDir), { recursive: true, mode: 493 });
102928
+ const staging = mkdtempSync6(join105(dirname42(targetDir), `.skill-personal-stage-`));
102354
102929
  let oldRename = null;
102355
102930
  try {
102356
102931
  for (const [path10, content] of Object.entries(files)) {
102357
102932
  const full = join105(staging, path10);
102358
- mkdirSync60(dirname41(full), { recursive: true, mode: 493 });
102933
+ mkdirSync61(dirname42(full), { recursive: true, mode: 493 });
102359
102934
  const fd = openSync21(full, "wx");
102360
102935
  try {
102361
- writeFileSync43(fd, content);
102936
+ writeFileSync44(fd, content);
102362
102937
  } finally {
102363
102938
  closeSync21(fd);
102364
102939
  }
@@ -102374,9 +102949,9 @@ function writePersonalSkill(targetDir, files) {
102374
102949
  } catch {}
102375
102950
  if (targetExists) {
102376
102951
  oldRename = `${targetDir}.personal-old-${Date.now()}`;
102377
- renameSync28(targetDir, oldRename);
102952
+ renameSync29(targetDir, oldRename);
102378
102953
  }
102379
- renameSync28(staging, targetDir);
102954
+ renameSync29(staging, targetDir);
102380
102955
  if (oldRename) {
102381
102956
  rmSync20(oldRename, { recursive: true, force: true });
102382
102957
  oldRename = null;
@@ -102385,12 +102960,12 @@ function writePersonalSkill(targetDir, files) {
102385
102960
  try {
102386
102961
  rmSync20(staging, { recursive: true, force: true });
102387
102962
  } catch {}
102388
- if (oldRename && existsSync103(oldRename)) {
102963
+ if (oldRename && existsSync104(oldRename)) {
102389
102964
  try {
102390
- if (existsSync103(targetDir)) {
102965
+ if (existsSync104(targetDir)) {
102391
102966
  rmSync20(targetDir, { recursive: true, force: true });
102392
102967
  }
102393
- renameSync28(oldRename, targetDir);
102968
+ renameSync29(oldRename, targetDir);
102394
102969
  } catch {}
102395
102970
  }
102396
102971
  throw err2;
@@ -102453,7 +103028,7 @@ function loadFiles(opts) {
102453
103028
  return loadFromStdin2();
102454
103029
  }
102455
103030
  const p = resolve59(opts.from);
102456
- if (!existsSync103(p)) {
103031
+ if (!existsSync104(p)) {
102457
103032
  fail5(`--from path does not exist: ${opts.from}`);
102458
103033
  }
102459
103034
  const st = statSync55(p);
@@ -102461,7 +103036,7 @@ function loadFiles(opts) {
102461
103036
  return loadFromDir2(p);
102462
103037
  }
102463
103038
  if (p.endsWith(".md")) {
102464
- return { "SKILL.md": readFileSync92(p, "utf-8") };
103039
+ return { "SKILL.md": readFileSync93(p, "utf-8") };
102465
103040
  }
102466
103041
  fail5(`--from must be a directory or a .md file. Got: ${opts.from}`);
102467
103042
  }
@@ -102515,7 +103090,7 @@ function resolveCloneSource(source, opts) {
102515
103090
  const slug = m[2];
102516
103091
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
102517
103092
  const dir = join105(root, slug);
102518
- if (!existsSync103(dir)) {
103093
+ if (!existsSync104(dir)) {
102519
103094
  fail5(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
102520
103095
  }
102521
103096
  const st = lstatSync13(dir);
@@ -102550,7 +103125,7 @@ function readSourceFiles(dir) {
102550
103125
  fail5(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
102551
103126
  }
102552
103127
  } catch {}
102553
- files[rel] = readFileSync92(full, "utf-8");
103128
+ files[rel] = readFileSync93(full, "utf-8");
102554
103129
  }
102555
103130
  }
102556
103131
  };
@@ -102639,10 +103214,10 @@ function removePersonalAction(name, opts) {
102639
103214
  throw err2;
102640
103215
  }
102641
103216
  const trashRoot2 = trashDir(agentsRoot, agent);
102642
- mkdirSync60(trashRoot2, { recursive: true, mode: 493 });
103217
+ mkdirSync61(trashRoot2, { recursive: true, mode: 493 });
102643
103218
  const ts = Date.now();
102644
103219
  const trashTarget = join105(trashRoot2, `${name}-${ts}`);
102645
- renameSync28(target, trashTarget);
103220
+ renameSync29(target, trashTarget);
102646
103221
  const now = new Date(ts);
102647
103222
  utimesSync(trashTarget, now, now);
102648
103223
  mirrorToConfigRepo(agent, name, null);
@@ -102662,7 +103237,7 @@ function listPersonalAction(opts) {
102662
103237
  sweepTrash(agentsRoot, agent);
102663
103238
  const skillsDir = join105(agentsRoot, agent, ".claude", "skills");
102664
103239
  const personal = [];
102665
- if (existsSync103(skillsDir)) {
103240
+ if (existsSync104(skillsDir)) {
102666
103241
  for (const ent of readdirSync38(skillsDir, { withFileTypes: true })) {
102667
103242
  if (!ent.isDirectory())
102668
103243
  continue;
@@ -102720,7 +103295,7 @@ function registerSkillPersonalCommands(program3) {
102720
103295
  import { createConnection as createConnection4 } from "node:net";
102721
103296
  import { homedir as homedir56 } from "node:os";
102722
103297
  import { join as join106 } from "node:path";
102723
- import { readFileSync as readFileSync93 } from "node:fs";
103298
+ import { readFileSync as readFileSync94 } from "node:fs";
102724
103299
  var IPC_CONNECT_TIMEOUT_MS = 5000;
102725
103300
  function gatewaySocketPath() {
102726
103301
  return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join106(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join106(homedir56(), ".claude", "channels", "telegram", "gateway.sock"));
@@ -102758,7 +103333,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
102758
103333
  fail6("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
102759
103334
  let draft;
102760
103335
  try {
102761
- draft = JSON.parse(readFileSync93(opts.draft, "utf-8"));
103336
+ draft = JSON.parse(readFileSync94(opts.draft, "utf-8"));
102762
103337
  } catch (e) {
102763
103338
  fail6(`failed to read/parse --draft: ${e.message}`);
102764
103339
  }
@@ -102795,7 +103370,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
102795
103370
  init_esm();
102796
103371
  init_helpers();
102797
103372
  var import_yaml25 = __toESM(require_dist(), 1);
102798
- import { existsSync as existsSync104, readdirSync as readdirSync39, readFileSync as readFileSync94, statSync as statSync56 } from "node:fs";
103373
+ import { existsSync as existsSync105, readdirSync as readdirSync39, readFileSync as readFileSync95, statSync as statSync56 } from "node:fs";
102799
103374
  import { homedir as homedir57 } from "node:os";
102800
103375
  import { join as join107, resolve as resolve60 } from "node:path";
102801
103376
  var PERSONAL_PREFIX2 = "personal-";
@@ -102812,11 +103387,11 @@ function defaultBundledRoot2() {
102812
103387
  }
102813
103388
  function readSkillFrontmatter(skillDir) {
102814
103389
  const mdPath = join107(skillDir, "SKILL.md");
102815
- if (!existsSync104(mdPath))
103390
+ if (!existsSync105(mdPath))
102816
103391
  return null;
102817
103392
  let content;
102818
103393
  try {
102819
- content = readFileSync94(mdPath, "utf-8");
103394
+ content = readFileSync95(mdPath, "utf-8");
102820
103395
  } catch {
102821
103396
  return null;
102822
103397
  }
@@ -102856,7 +103431,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
102856
103431
  if (!AGENT_NAME_RE3.test(agent))
102857
103432
  return [];
102858
103433
  const skillsDir = join107(agentsRoot, agent, ".claude/skills");
102859
- if (!existsSync104(skillsDir))
103434
+ if (!existsSync105(skillsDir))
102860
103435
  return [];
102861
103436
  const out = [];
102862
103437
  let entries;
@@ -102894,7 +103469,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
102894
103469
  return out;
102895
103470
  }
102896
103471
  function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
102897
- if (!existsSync104(sharedRoot))
103472
+ if (!existsSync105(sharedRoot))
102898
103473
  return [];
102899
103474
  const out = [];
102900
103475
  let entries;
@@ -102932,7 +103507,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
102932
103507
  return out;
102933
103508
  }
102934
103509
  function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
102935
- if (!existsSync104(bundledRoot))
103510
+ if (!existsSync105(bundledRoot))
102936
103511
  return [];
102937
103512
  const out = [];
102938
103513
  let entries;
@@ -103090,10 +103665,10 @@ init_source();
103090
103665
  init_helpers();
103091
103666
  init_operator_uid();
103092
103667
  import {
103093
- existsSync as existsSync106,
103094
- mkdirSync as mkdirSync61,
103668
+ existsSync as existsSync107,
103669
+ mkdirSync as mkdirSync62,
103095
103670
  readdirSync as readdirSync40,
103096
- writeFileSync as writeFileSync44,
103671
+ writeFileSync as writeFileSync45,
103097
103672
  statSync as statSync57,
103098
103673
  lstatSync as lstatSync14,
103099
103674
  realpathSync as realpathSync8,
@@ -103101,13 +103676,13 @@ import {
103101
103676
  } from "node:fs";
103102
103677
  import { homedir as homedir58 } from "node:os";
103103
103678
  import { join as join108 } from "node:path";
103104
- import { spawnSync as spawnSync24 } from "node:child_process";
103679
+ import { spawnSync as spawnSync25 } from "node:child_process";
103105
103680
 
103106
103681
  // src/cli/singleton-stale-cleanup.ts
103107
- import { spawnSync as spawnSync23 } from "node:child_process";
103682
+ import { spawnSync as spawnSync24 } from "node:child_process";
103108
103683
  function makeDockerRunner() {
103109
103684
  return (args) => {
103110
- const r = spawnSync23("docker", args, { encoding: "utf8" });
103685
+ const r = spawnSync24("docker", args, { encoding: "utf8" });
103111
103686
  return {
103112
103687
  ok: r.status === 0,
103113
103688
  stdout: r.stdout ?? "",
@@ -103466,7 +104041,7 @@ function resolveHostdSkillsTarget(hostHome) {
103466
104041
  console.warn(`switchroom hostd install: ~/.switchroom/skills is a symlink whose target ` + `does not resolve (dangling) \u2014 skipping the skills bind mount. Bundled ` + `skills will be unavailable to rollout/update until the symlink is fixed.`);
103467
104042
  return;
103468
104043
  }
103469
- if (!existsSync106(target)) {
104044
+ if (!existsSync107(target)) {
103470
104045
  console.warn(`switchroom hostd install: ~/.switchroom/skills resolves to "${target}", ` + `which does not exist \u2014 skipping the skills bind mount. Bundled skills ` + `will be unavailable to rollout/update until the symlink target exists.`);
103471
104046
  return;
103472
104047
  }
@@ -103480,7 +104055,7 @@ function hostdComposePath() {
103480
104055
  }
103481
104056
  function backupExistingCompose() {
103482
104057
  const p = hostdComposePath();
103483
- if (!existsSync106(p))
104058
+ if (!existsSync107(p))
103484
104059
  return null;
103485
104060
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
103486
104061
  const bak = `${p}.bak-${ts}`;
@@ -103488,7 +104063,7 @@ function backupExistingCompose() {
103488
104063
  return bak;
103489
104064
  }
103490
104065
  function runDocker(args) {
103491
- const r = spawnSync24("docker", args, { encoding: "utf8" });
104066
+ const r = spawnSync25("docker", args, { encoding: "utf8" });
103492
104067
  return {
103493
104068
  ok: r.status === 0,
103494
104069
  stdout: r.stdout ?? "",
@@ -103513,7 +104088,7 @@ async function doInstall(opts, program3) {
103513
104088
  }
103514
104089
  const dir = hostdDir();
103515
104090
  const composePath = hostdComposePath();
103516
- mkdirSync61(dir, { recursive: true });
104091
+ mkdirSync62(dir, { recursive: true });
103517
104092
  const imageTag = resolveHostdImageTag(opts.tag, cfg.release);
103518
104093
  const guard = checkDowngrade({
103519
104094
  container: "switchroom-hostd",
@@ -103546,7 +104121,7 @@ async function doInstall(opts, program3) {
103546
104121
  const bak = backupExistingCompose();
103547
104122
  if (bak)
103548
104123
  console.log(source_default.dim(` Backed up existing compose to ${bak}`));
103549
- writeFileSync44(composePath, yaml, "utf8");
104124
+ writeFileSync45(composePath, yaml, "utf8");
103550
104125
  console.log(source_default.green(` \u2713 Wrote ${composePath}`));
103551
104126
  const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
103552
104127
  console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
@@ -103578,7 +104153,7 @@ function doStatus() {
103578
104153
  const composeYml = hostdComposePath();
103579
104154
  console.log(source_default.bold("switchroom-hostd"));
103580
104155
  console.log("");
103581
- if (!existsSync106(composeYml)) {
104156
+ if (!existsSync107(composeYml)) {
103582
104157
  console.log(source_default.yellow(" compose: not installed"));
103583
104158
  console.log(source_default.dim(" run `switchroom hostd install` to set up."));
103584
104159
  return;
@@ -103599,14 +104174,14 @@ function doStatus() {
103599
104174
  } else {
103600
104175
  console.log(source_default.green(` container: ${ps.stdout.trim()}`));
103601
104176
  }
103602
- if (existsSync106(dir)) {
104177
+ if (existsSync107(dir)) {
103603
104178
  const entries = [];
103604
104179
  try {
103605
104180
  for (const name of readdirSync40(dir)) {
103606
104181
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
103607
104182
  continue;
103608
104183
  const sockPath = join108(dir, name, "sock");
103609
- if (existsSync106(sockPath)) {
104184
+ if (existsSync107(sockPath)) {
103610
104185
  const st = statSync57(sockPath);
103611
104186
  if ((st.mode & 61440) === 49152) {
103612
104187
  entries.push(`${name} \u2192 ${sockPath}`);
@@ -103625,7 +104200,7 @@ function doStatus() {
103625
104200
  }
103626
104201
  function doUninstall() {
103627
104202
  const composeYml = hostdComposePath();
103628
- if (!existsSync106(composeYml)) {
104203
+ if (!existsSync107(composeYml)) {
103629
104204
  console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
103630
104205
  return;
103631
104206
  }
@@ -103649,7 +104224,7 @@ function registerHostdCommand(program3) {
103649
104224
  hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
103650
104225
  hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--verbose", "Show the captured stderr / error tail under each failed row").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
103651
104226
  const logPath = opts.path ?? defaultAuditLogPath2();
103652
- if (!existsSync106(logPath)) {
104227
+ if (!existsSync107(logPath)) {
103653
104228
  console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
103654
104229
  The log is created when hostd handles its first privileged-verb request.`));
103655
104230
  return;
@@ -103698,10 +104273,10 @@ init_source();
103698
104273
  init_helpers();
103699
104274
  init_operator_uid();
103700
104275
  init_compose();
103701
- import { chownSync as chownSync10, existsSync as existsSync107, mkdirSync as mkdirSync62, writeFileSync as writeFileSync45, copyFileSync as copyFileSync15 } from "node:fs";
104276
+ import { chownSync as chownSync10, existsSync as existsSync108, mkdirSync as mkdirSync63, writeFileSync as writeFileSync46, copyFileSync as copyFileSync15 } from "node:fs";
103702
104277
  import { homedir as homedir59 } from "node:os";
103703
104278
  import { join as join109 } from "node:path";
103704
- import { spawnSync as spawnSync25 } from "node:child_process";
104279
+ import { spawnSync as spawnSync26 } from "node:child_process";
103705
104280
  function resolveWebImageTag(explicitTag, release) {
103706
104281
  if (explicitTag)
103707
104282
  return explicitTag;
@@ -103809,7 +104384,7 @@ function webdComposePath() {
103809
104384
  }
103810
104385
  function backupExistingCompose2() {
103811
104386
  const p = webdComposePath();
103812
- if (!existsSync107(p))
104387
+ if (!existsSync108(p))
103813
104388
  return null;
103814
104389
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
103815
104390
  const bak = `${p}.bak-${ts}`;
@@ -103817,7 +104392,7 @@ function backupExistingCompose2() {
103817
104392
  return bak;
103818
104393
  }
103819
104394
  function runDocker2(args) {
103820
- const r = spawnSync25("docker", args, { encoding: "utf8" });
104395
+ const r = spawnSync26("docker", args, { encoding: "utf8" });
103821
104396
  return {
103822
104397
  ok: r.status === 0,
103823
104398
  stdout: r.stdout ?? "",
@@ -103840,7 +104415,7 @@ async function doInstall2(opts, program3) {
103840
104415
  }
103841
104416
  const dir = webdDir();
103842
104417
  const composePath = webdComposePath();
103843
- mkdirSync62(dir, { recursive: true });
104418
+ mkdirSync63(dir, { recursive: true });
103844
104419
  const cfg = getConfig(program3);
103845
104420
  const imageTag = resolveWebImageTag(opts.tag, cfg.release);
103846
104421
  const port = cfg.web_service?.port ?? 8080;
@@ -103875,7 +104450,7 @@ async function doInstall2(opts, program3) {
103875
104450
  const bak = backupExistingCompose2();
103876
104451
  if (bak)
103877
104452
  console.log(source_default.dim(` Backed up existing compose to ${bak}`));
103878
- writeFileSync45(composePath, yaml, "utf8");
104453
+ writeFileSync46(composePath, yaml, "utf8");
103879
104454
  try {
103880
104455
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
103881
104456
  chownSync10(dir, operatorUid, operatorUid);
@@ -103917,7 +104492,7 @@ function doStatus2() {
103917
104492
  const composeYml = webdComposePath();
103918
104493
  console.log(source_default.bold("switchroom-web"));
103919
104494
  console.log("");
103920
- if (!existsSync107(composeYml)) {
104495
+ if (!existsSync108(composeYml)) {
103921
104496
  console.log(source_default.yellow(" compose: not installed"));
103922
104497
  console.log(source_default.dim(" run `switchroom webd install` to set up."));
103923
104498
  return;
@@ -103941,7 +104516,7 @@ function doStatus2() {
103941
104516
  }
103942
104517
  function doUninstall2() {
103943
104518
  const composeYml = webdComposePath();
103944
- if (!existsSync107(composeYml)) {
104519
+ if (!existsSync108(composeYml)) {
103945
104520
  console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
103946
104521
  return;
103947
104522
  }
@@ -104216,13 +104791,13 @@ init_helpers();
104216
104791
  init_scan();
104217
104792
 
104218
104793
  // src/fleet-health/gh-sync.ts
104219
- import { spawnSync as spawnSync26 } from "node:child_process";
104794
+ import { spawnSync as spawnSync27 } from "node:child_process";
104220
104795
  var LABEL = "fleet-health";
104221
104796
  function defaultGhDeps(log) {
104222
104797
  return {
104223
104798
  log,
104224
104799
  run: (args) => {
104225
- const r = spawnSync26("gh", args, { encoding: "utf-8" });
104800
+ const r = spawnSync27("gh", args, { encoding: "utf-8" });
104226
104801
  return {
104227
104802
  ok: r.status === 0,
104228
104803
  stdout: (r.stdout ?? "").trim(),
@@ -104427,7 +105002,7 @@ function printDeepDiveBrief(targets) {
104427
105002
 
104428
105003
  // src/cli/hindsight-watch.ts
104429
105004
  init_source();
104430
- import { existsSync as existsSync112, readdirSync as readdirSync45 } from "node:fs";
105005
+ import { existsSync as existsSync113, readdirSync as readdirSync45 } from "node:fs";
104431
105006
  import { homedir as homedir63 } from "node:os";
104432
105007
  import { join as join112, resolve as resolve63 } from "node:path";
104433
105008
 
@@ -104497,8 +105072,8 @@ function postOperatorNoticeViaGateways(candidates, text, log, connectTimeoutMs =
104497
105072
  init_install_cron();
104498
105073
 
104499
105074
  // src/hindsight-watch/probe.ts
104500
- import { spawnSync as spawnSync27 } from "node:child_process";
104501
- import { readFileSync as readFileSync99, readdirSync as readdirSync44, statSync as statSync59 } from "node:fs";
105075
+ import { spawnSync as spawnSync28 } from "node:child_process";
105076
+ import { readFileSync as readFileSync100, readdirSync as readdirSync44, statSync as statSync59 } from "node:fs";
104502
105077
  import { homedir as homedir62 } from "node:os";
104503
105078
  import { resolve as resolve62 } from "node:path";
104504
105079
 
@@ -104626,7 +105201,7 @@ function readLlmSignals(series) {
104626
105201
  }
104627
105202
 
104628
105203
  // src/hindsight-watch/recall-log.ts
104629
- import { closeSync as closeSync22, existsSync as existsSync111, openSync as openSync22, readdirSync as readdirSync43, readSync as readSync6, statSync as statSync58 } from "node:fs";
105204
+ import { closeSync as closeSync22, existsSync as existsSync112, openSync as openSync22, readdirSync as readdirSync43, readSync as readSync6, statSync as statSync58 } from "node:fs";
104630
105205
  import { join as join111 } from "node:path";
104631
105206
  var RECALL_WINDOW_ROWS = 200;
104632
105207
  var RECALL_MAX_TAIL_BYTES = 1024 * 1024;
@@ -104635,7 +105210,7 @@ function recallLogPath2(agentDir) {
104635
105210
  return join111(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
104636
105211
  }
104637
105212
  function readRecallLogTail2(path10, windowRows = RECALL_WINDOW_ROWS) {
104638
- if (!existsSync111(path10))
105213
+ if (!existsSync112(path10))
104639
105214
  return [];
104640
105215
  let text;
104641
105216
  let truncatedHead = false;
@@ -104847,7 +105422,7 @@ async function probeMetrics(url = DEFAULT_METRICS_URL, fetchImpl = fetch) {
104847
105422
  }
104848
105423
  }
104849
105424
  var defaultRunner4 = (cmd, args) => {
104850
- const r = spawnSync27(cmd, args, { stdio: "pipe", timeout: 1e4, encoding: "utf8" });
105425
+ const r = spawnSync28(cmd, args, { stdio: "pipe", timeout: 1e4, encoding: "utf8" });
104851
105426
  if (r.error)
104852
105427
  return { status: null, stdout: "", stderr: r.error.message };
104853
105428
  return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
@@ -104875,7 +105450,7 @@ function readDropCount(hindsightDir) {
104875
105450
  try {
104876
105451
  if (statSync59(path10).size > DROPS_LEDGER_MAX_BYTES)
104877
105452
  return 0;
104878
- parsed = JSON.parse(readFileSync99(path10, "utf8"));
105453
+ parsed = JSON.parse(readFileSync100(path10, "utf8"));
104879
105454
  } catch {
104880
105455
  return 0;
104881
105456
  }
@@ -105923,7 +106498,7 @@ function collectRelayCandidates(agentsDir) {
105923
106498
  const candidates = [];
105924
106499
  for (const name of readdirSync45(agentsDir).sort()) {
105925
106500
  const sock = resolve63(agentsDir, name, "telegram", "gateway.sock");
105926
- if (existsSync112(sock))
106501
+ if (existsSync113(sock))
105927
106502
  candidates.push({ agent: name, sock });
105928
106503
  }
105929
106504
  return orderRelayCandidates(candidates);
@@ -105945,13 +106520,13 @@ async function notifyOperator(agentsDir, text, log) {
105945
106520
 
105946
106521
  // src/cli/openrouter-watch.ts
105947
106522
  init_source();
105948
- import { existsSync as existsSync114, readdirSync as readdirSync46 } from "node:fs";
106523
+ import { existsSync as existsSync115, readdirSync as readdirSync46 } from "node:fs";
105949
106524
  import { homedir as homedir65 } from "node:os";
105950
106525
  import { join as join113, resolve as resolve65 } from "node:path";
105951
106526
 
105952
106527
  // src/openrouter/install-cron.ts
105953
- import { existsSync as existsSync113, mkdirSync as mkdirSync64, readFileSync as readFileSync100, renameSync as renameSync29, writeFileSync as writeFileSync47 } from "node:fs";
105954
- import { dirname as dirname43 } from "node:path";
106528
+ import { existsSync as existsSync114, mkdirSync as mkdirSync65, readFileSync as readFileSync101, renameSync as renameSync30, writeFileSync as writeFileSync48 } from "node:fs";
106529
+ import { dirname as dirname44 } from "node:path";
105955
106530
  var CRON_PATH2 = "/etc/cron.d/openrouter-watch";
105956
106531
  var CRON_SCHEDULE2 = "17 * * * *";
105957
106532
  var CRON_LOG_PATH2 = "/var/log/openrouter-watch.log";
@@ -105968,30 +106543,30 @@ function renderCron2(opts) {
105968
106543
  function installCron2(opts) {
105969
106544
  const path10 = opts.path ?? CRON_PATH2;
105970
106545
  const content = renderCron2(opts);
105971
- if (existsSync113(path10)) {
106546
+ if (existsSync114(path10)) {
105972
106547
  try {
105973
- if (readFileSync100(path10, "utf8") === content) {
106548
+ if (readFileSync101(path10, "utf8") === content) {
105974
106549
  return { status: "unchanged", path: path10, content };
105975
106550
  }
105976
106551
  } catch {}
105977
106552
  }
105978
- mkdirSync64(dirname43(path10), { recursive: true });
106553
+ mkdirSync65(dirname44(path10), { recursive: true });
105979
106554
  const tmp = `${path10}.${process.pid}.tmp`;
105980
- writeFileSync47(tmp, content, { mode: 420 });
105981
- renameSync29(tmp, path10);
106555
+ writeFileSync48(tmp, content, { mode: 420 });
106556
+ renameSync30(tmp, path10);
105982
106557
  return { status: "installed", path: path10, content };
105983
106558
  }
105984
106559
  // src/openrouter/state.ts
105985
- import { mkdirSync as mkdirSync65, readFileSync as readFileSync101, renameSync as renameSync30, writeFileSync as writeFileSync48 } from "node:fs";
106560
+ import { mkdirSync as mkdirSync66, readFileSync as readFileSync102, renameSync as renameSync31, writeFileSync as writeFileSync49 } from "node:fs";
105986
106561
  import { homedir as homedir64 } from "node:os";
105987
- import { dirname as dirname44, resolve as resolve64 } from "node:path";
106562
+ import { dirname as dirname45, resolve as resolve64 } from "node:path";
105988
106563
  function defaultStatePath3(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir64()) {
105989
106564
  return resolve64(home2, ".switchroom", "openrouter-watch", "state.json");
105990
106565
  }
105991
106566
  function loadState2(path10) {
105992
106567
  let parsed;
105993
106568
  try {
105994
- parsed = JSON.parse(readFileSync101(path10, "utf8"));
106569
+ parsed = JSON.parse(readFileSync102(path10, "utf8"));
105995
106570
  } catch {
105996
106571
  return {};
105997
106572
  }
@@ -106011,12 +106586,12 @@ function loadState2(path10) {
106011
106586
  return out;
106012
106587
  }
106013
106588
  function saveState2(path10, state) {
106014
- mkdirSync65(dirname44(path10), { recursive: true, mode: 448 });
106589
+ mkdirSync66(dirname45(path10), { recursive: true, mode: 448 });
106015
106590
  const tmp = `${path10}.${process.pid}.tmp`;
106016
106591
  const payload = { v: 1, ...state };
106017
- writeFileSync48(tmp, JSON.stringify(payload, null, 2) + `
106592
+ writeFileSync49(tmp, JSON.stringify(payload, null, 2) + `
106018
106593
  `, { mode: 384 });
106019
- renameSync30(tmp, path10);
106594
+ renameSync31(tmp, path10);
106020
106595
  }
106021
106596
 
106022
106597
  // src/openrouter/watch.ts
@@ -106201,7 +106776,7 @@ async function notifyOperator2(agentsDir, text, log) {
106201
106776
  try {
106202
106777
  for (const name of readdirSync46(agentsDir).sort()) {
106203
106778
  const sock = resolve65(agentsDir, name, "telegram", "gateway.sock");
106204
- if (existsSync114(sock))
106779
+ if (existsSync115(sock))
106205
106780
  candidates.push({ agent: name, sock });
106206
106781
  }
106207
106782
  } catch (e) {