switchroom 0.19.42 → 0.19.44

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 (75) hide show
  1. package/dist/agent-scheduler/index.js +4 -1
  2. package/dist/auth-broker/index.js +284 -124
  3. package/dist/cli/notion-write-pretool.mjs +4 -1
  4. package/dist/cli/switchroom.js +4094 -2711
  5. package/dist/host-control/main.js +472 -155
  6. package/dist/vault/approvals/kernel-server.js +231 -71
  7. package/dist/vault/broker/server.js +293 -133
  8. package/package.json +3 -3
  9. package/profiles/_base/start.sh.hbs +93 -8
  10. package/telegram-plugin/bridge/bridge.ts +1 -1
  11. package/telegram-plugin/card-layout.ts +63 -1
  12. package/telegram-plugin/dist/bridge/bridge.js +4 -1
  13. package/telegram-plugin/dist/gateway/gateway.js +1682 -936
  14. package/telegram-plugin/dist/server.js +5 -2
  15. package/telegram-plugin/flood-429-ledger.ts +5 -3
  16. package/telegram-plugin/flood-circuit-breaker.ts +11 -3
  17. package/telegram-plugin/format.ts +223 -13
  18. package/telegram-plugin/gateway/boot-card.ts +7 -2
  19. package/telegram-plugin/gateway/flood-reply-queue.ts +5 -0
  20. package/telegram-plugin/gateway/gateway.ts +49 -51
  21. package/telegram-plugin/gateway/handback-orphan-recovery.ts +69 -0
  22. package/telegram-plugin/gateway/handback-preturn-signal.ts +170 -32
  23. package/telegram-plugin/gateway/ipc-protocol.ts +31 -3
  24. package/telegram-plugin/gateway/ipc-server.ts +13 -4
  25. package/telegram-plugin/gateway/obligation-store.ts +55 -0
  26. package/telegram-plugin/gateway/outbound-send-path.ts +127 -11
  27. package/telegram-plugin/gateway/outbox-sweep.ts +371 -61
  28. package/telegram-plugin/gateway/rollout-status-edit.ts +175 -0
  29. package/telegram-plugin/gateway/stream-render.ts +5 -0
  30. package/telegram-plugin/gateway/update-announce.ts +11 -4
  31. package/telegram-plugin/hooks/audience-classify.d.mts +50 -0
  32. package/telegram-plugin/hooks/audience-classify.mjs +364 -0
  33. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +86 -3
  34. package/telegram-plugin/hooks/silent-end-scan.mjs +80 -4
  35. package/telegram-plugin/outbox.ts +103 -2
  36. package/telegram-plugin/render/rich-render.ts +10 -2
  37. package/telegram-plugin/reply-quote.ts +150 -0
  38. package/telegram-plugin/retry-api-call.ts +254 -11
  39. package/telegram-plugin/shared/bot-runtime.ts +69 -34
  40. package/telegram-plugin/shared/gw-trace-gate.ts +14 -2
  41. package/telegram-plugin/silent-end.ts +34 -2
  42. package/telegram-plugin/status-no-truncate.ts +31 -16
  43. package/telegram-plugin/stream-controller.ts +44 -9
  44. package/telegram-plugin/stream-reply-handler.ts +5 -3
  45. package/telegram-plugin/tests/boot-card-render.test.ts +2 -1
  46. package/telegram-plugin/tests/card-budget-never-overflows.test.ts +165 -0
  47. package/telegram-plugin/tests/card-hard-truncate-entity-safe.test.ts +153 -0
  48. package/telegram-plugin/tests/card-variants.golden.txt +9 -9
  49. package/telegram-plugin/tests/flood-reply-queue.test.ts +89 -4
  50. package/telegram-plugin/tests/format-consistency.test.ts +160 -14
  51. package/telegram-plugin/tests/handback-orphan-recovery.test.ts +131 -0
  52. package/telegram-plugin/tests/handback-preturn-signal.test.ts +212 -17
  53. package/telegram-plugin/tests/nested-indent-idiom.test.ts +162 -0
  54. package/telegram-plugin/tests/nested-prefix-indent.test.ts +115 -0
  55. package/telegram-plugin/tests/no-robust-api-call-factory.test.ts +62 -0
  56. package/telegram-plugin/tests/obligation-store.test.ts +51 -0
  57. package/telegram-plugin/tests/outbox-audience-3865.test.ts +579 -0
  58. package/telegram-plugin/tests/outbox-provenance-4141.test.ts +1126 -0
  59. package/telegram-plugin/tests/outbox-sweep-single-flight.test.ts +138 -0
  60. package/telegram-plugin/tests/pinned-card-collapse.test.ts +12 -10
  61. package/telegram-plugin/tests/plain-fallback-4096-cap.test.ts +509 -0
  62. package/telegram-plugin/tests/reply-quote-wire.test.ts +433 -0
  63. package/telegram-plugin/tests/retry-grammy-http-error.test.ts +404 -0
  64. package/telegram-plugin/tests/rollout-narration-edit-socket.test.ts +170 -0
  65. package/telegram-plugin/tests/rollout-status-edit-retry-policy.test.ts +366 -0
  66. package/telegram-plugin/tests/rollout-status-edit.test.ts +140 -0
  67. package/telegram-plugin/tests/rollout-status-wiring.test.ts +40 -7
  68. package/telegram-plugin/tests/tg-post-logger-error-shape.test.ts +4 -0
  69. package/telegram-plugin/tests/tg-post-retry-attribution.test.ts +196 -0
  70. package/telegram-plugin/tests/tool-activity-summary.test.ts +23 -17
  71. package/telegram-plugin/tool-activity-summary.ts +58 -30
  72. package/telegram-plugin/uat/scenarios/jtbd-foreground-subagent-activity-dm.test.ts +3 -2
  73. package/vendor/hindsight-memory/scripts/lib/config.py +15 -0
  74. package/vendor/hindsight-memory/scripts/tests/test_retain_provenance_tag.py +135 -0
  75. package/vendor/hindsight-memory/settings.json +1 -1
@@ -10994,7 +10994,7 @@ var init_observation_scopes = __esm(() => {
10994
10994
  });
10995
10995
 
10996
10996
  // src/config/schema.ts
10997
- 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, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
10997
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AntiConfabulationDirectiveSchema, 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, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
10998
10998
  var init_schema = __esm(() => {
10999
10999
  init_zod();
11000
11000
  init_observation_scopes();
@@ -11111,6 +11111,7 @@ var init_schema = __esm(() => {
11111
11111
  }).optional();
11112
11112
  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 — 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).");
11113
11113
  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 — keeping the stable " + "semantic ones on the source fact — 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).");
11114
+ AntiConfabulationDirectiveSchema = exports_external.union([exports_external.boolean(), exports_external.string().min(1)]).optional().describe("The seeded anti-confabulation directive: reflect has no relevance " + "floor (`min_scores` is a /recall-only parameter), so a bank holding " + "nothing relevant still yields a confident answer, and a question that " + "presupposes a decision reliably produces one. Directives ARE applied " + "during reflect, so this is the lever. Unset or true (the default) " + "seeds and maintains switchroom's own text; false disables seeding and " + "leaves any existing directive untouched; a string is operator-authored " + "text that wins outright. Switchroom only ever upgrades directive text " + "byte-equal to a default it shipped, so editing the directive in the " + "bank is also a permanent override. " + "Cascade: override (per-agent wins over default).");
11114
11115
  AgentMemorySchema = exports_external.object({
11115
11116
  collection: exports_external.string().describe("Hindsight collection name for this agent"),
11116
11117
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
@@ -11146,6 +11147,7 @@ var init_schema = __esm(() => {
11146
11147
  empathy: exports_external.number().int().min(1).max(5).optional().describe("How much the bank weights emotional/relational context (1-5; engine default 3).")
11147
11148
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — 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`)."),
11148
11149
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") 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 — 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)."),
11150
+ anti_confabulation_directive: AntiConfabulationDirectiveSchema,
11149
11151
  observation_scopes: ObservationScopesSchema,
11150
11152
  observation_scope_strategy: ObservationScopeStrategySchema,
11151
11153
  recall: exports_external.object({
@@ -11416,7 +11418,7 @@ var init_schema = __esm(() => {
11416
11418
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
11417
11419
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
11418
11420
  }).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults."),
11419
- env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_MAX_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_MAX_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_MAX_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
11421
+ env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY — the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE — the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT — upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1; and " + "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT — the background " + "half of switchroom's recall-admission split (#3660): of the " + "RECALL_MAX_CONCURRENT admission slots, at most this many may be held " + "by background consolidation recalls at once, so foreground per-turn " + "recall always keeps the remainder; must be >= 1 and strictly less " + "than RECALL_MAX_CONCURRENT or the engine refuses to boot; unset " + "means the image's derived default min(2, RECALL_MAX_CONCURRENT - 1); " + "1 biases hard toward the interactive lane while a consolidation " + "backlog drains), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
11420
11422
  });
11421
11423
  MicrosoftWorkspaceConfigSchema = exports_external.object({
11422
11424
  microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
@@ -11550,6 +11552,7 @@ var init_schema = __esm(() => {
11550
11552
  isolation: exports_external.enum(["default", "strict"]).optional(),
11551
11553
  profile: exports_external.string().optional(),
11552
11554
  directive_capture_nudge: exports_external.boolean().optional(),
11555
+ anti_confabulation_directive: AntiConfabulationDirectiveSchema,
11553
11556
  observation_scopes: ObservationScopesSchema,
11554
11557
  observation_scope_strategy: ObservationScopeStrategySchema,
11555
11558
  recall: exports_external.object({
@@ -18379,13 +18382,13 @@ var require_lib = __commonJS((exports, module) => {
18379
18382
 
18380
18383
  // src/vault/flock.ts
18381
18384
  import {
18382
- existsSync as existsSync6,
18385
+ existsSync as existsSync7,
18383
18386
  openSync as openSync2,
18384
18387
  closeSync as closeSync2,
18385
18388
  writeSync as writeSync2,
18386
18389
  fsyncSync as fsyncSync2,
18387
18390
  unlinkSync as unlinkSync2,
18388
- readFileSync as readFileSync5,
18391
+ readFileSync as readFileSync6,
18389
18392
  readdirSync as readdirSync3,
18390
18393
  rmdirSync,
18391
18394
  statSync as statSync3,
@@ -18418,8 +18421,8 @@ function pidStartTimeMs(pid) {
18418
18421
  if (process.platform !== "linux")
18419
18422
  return null;
18420
18423
  try {
18421
- const statLine = readFileSync5(`/proc/${pid}/stat`, "utf8");
18422
- const procStat = readFileSync5("/proc/stat", "utf8");
18424
+ const statLine = readFileSync6(`/proc/${pid}/stat`, "utf8");
18425
+ const procStat = readFileSync6("/proc/stat", "utf8");
18423
18426
  return parseProcStartTimeMs(statLine, procStat, Date.now());
18424
18427
  } catch {
18425
18428
  return null;
@@ -18815,13 +18818,13 @@ async function rpcRaw(req, opts) {
18815
18818
  async function rpc(req, opts) {
18816
18819
  const socketPath = resolveBrokerSocketPath(opts);
18817
18820
  const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
18818
- return new Promise((resolve5) => {
18821
+ return new Promise((resolve6) => {
18819
18822
  let settled = false;
18820
18823
  const settle = (val) => {
18821
18824
  if (settled)
18822
18825
  return;
18823
18826
  settled = true;
18824
- resolve5(val);
18827
+ resolve6(val);
18825
18828
  };
18826
18829
  const client = new net.Socket;
18827
18830
  const timer = setTimeout(() => {
@@ -20931,8 +20934,8 @@ var require_commander = __commonJS((exports2) => {
20931
20934
  // src/host-control/main.ts
20932
20935
  init_loader();
20933
20936
  import { homedir as homedir8 } from "node:os";
20934
- import { existsSync as existsSync16, readdirSync as readdirSync7 } from "node:fs";
20935
- import { join as join14, resolve as resolve10 } from "node:path";
20937
+ import { existsSync as existsSync17, readdirSync as readdirSync7 } from "node:fs";
20938
+ import { join as join13, resolve as resolve10 } from "node:path";
20936
20939
 
20937
20940
  // src/host-control/config-degraded.ts
20938
20941
  init_loader();
@@ -21323,9 +21326,66 @@ async function waitForConfigRecovery(opts) {
21323
21326
  init_schema();
21324
21327
 
21325
21328
  // src/agents/scaffold.ts
21326
- import { dirname, isAbsolute, join as join5, relative, resolve as resolve5 } from "node:path";
21327
21329
  init_atomic();
21328
21330
 
21331
+ // src/util/shipped-assets.ts
21332
+ import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync } from "node:fs";
21333
+ import { dirname, resolve as resolve4 } from "node:path";
21334
+ var FHS_SHARE_ROOTS = [
21335
+ "/usr/local/share/switchroom",
21336
+ "/usr/share/switchroom"
21337
+ ];
21338
+ var PROFILES_ASSET = {
21339
+ asset: "profiles",
21340
+ envVar: "SWITCHROOM_PROFILES_ROOT"
21341
+ };
21342
+ function orderedCandidates(spec, probe) {
21343
+ const out = [];
21344
+ const push = (path, source) => {
21345
+ if (!out.some((c) => c.path === path))
21346
+ out.push({ path, source });
21347
+ };
21348
+ if (probe.bundleDir) {
21349
+ push(resolve4(probe.bundleDir, "../..", spec.asset), "npm");
21350
+ push(resolve4(probe.bundleDir, spec.asset), "image");
21351
+ }
21352
+ if (probe.execPath) {
21353
+ push(resolve4(dirname(probe.execPath), "../share/switchroom", spec.asset), "sea-sibling");
21354
+ }
21355
+ for (const root of FHS_SHARE_ROOTS) {
21356
+ push(resolve4(root, spec.asset), "fhs");
21357
+ }
21358
+ return out;
21359
+ }
21360
+ function resolveShippedAsset(spec, probe) {
21361
+ const env = probe.env ?? process.env;
21362
+ const override = env[spec.envVar]?.trim();
21363
+ if (override) {
21364
+ const path = canonicalise(resolve4(override), probe);
21365
+ return { path, candidates: [path], source: "env" };
21366
+ }
21367
+ const exists = probe.exists ?? existsSync5;
21368
+ const candidates = orderedCandidates(spec, probe);
21369
+ for (const c of candidates) {
21370
+ if (exists(c.path)) {
21371
+ return {
21372
+ path: canonicalise(c.path, probe),
21373
+ candidates: candidates.map((x) => x.path),
21374
+ source: c.source
21375
+ };
21376
+ }
21377
+ }
21378
+ return { path: null, candidates: candidates.map((x) => x.path), source: "none" };
21379
+ }
21380
+ function canonicalise(path, probe) {
21381
+ const realpath = probe.realpath ?? realpathSync;
21382
+ try {
21383
+ return realpath(path);
21384
+ } catch {
21385
+ return path;
21386
+ }
21387
+ }
21388
+
21329
21389
  // src/agents/agent-uid.ts
21330
21390
  init_peercred();
21331
21391
  import { createHash } from "node:crypto";
@@ -21342,7 +21402,7 @@ function allocateAgentUid(name) {
21342
21402
  }
21343
21403
 
21344
21404
  // src/build-info.ts
21345
- var VERSION = "0.19.42";
21405
+ var VERSION = "0.19.44";
21346
21406
 
21347
21407
  // src/setup/hindsight-recall-tunables.ts
21348
21408
  var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
@@ -21387,37 +21447,29 @@ var AUDIT_ROOT = join2(homedir2(), ".switchroom", "audit");
21387
21447
 
21388
21448
  // src/agents/profiles.ts
21389
21449
  var import_handlebars = __toESM(require_lib(), 1);
21390
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2, copyFileSync, mkdirSync as mkdirSync2, realpathSync } from "node:fs";
21391
- import { resolve as resolve4, join as join3, sep as pathSep } from "node:path";
21392
- function resolveProfilesRoot() {
21393
- const envOverride = process.env.SWITCHROOM_PROFILES_ROOT?.trim();
21394
- if (envOverride) {
21395
- return resolve4(envOverride);
21396
- }
21397
- const candidates = [
21398
- resolve4(import.meta.dirname, "../../profiles"),
21399
- resolve4(import.meta.dirname, "profiles")
21400
- ];
21401
- for (const candidate of candidates) {
21402
- if (existsSync5(candidate)) {
21403
- return candidate;
21404
- }
21405
- }
21406
- return candidates[0];
21450
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2, copyFileSync, mkdirSync as mkdirSync2, realpathSync as realpathSync2 } from "node:fs";
21451
+ import { resolve as resolve5, join as join3, sep as pathSep } from "node:path";
21452
+ function resolveProfilesRootDetailed() {
21453
+ return resolveShippedAsset(PROFILES_ASSET, {
21454
+ bundleDir: import.meta.dirname,
21455
+ execPath: process.execPath
21456
+ });
21407
21457
  }
21408
- var PROFILES_ROOT = resolveProfilesRoot();
21458
+ var PROFILES_RESOLUTION = resolveProfilesRootDetailed();
21459
+ var PROFILES_ROOT = PROFILES_RESOLUTION.path ?? PROFILES_RESOLUTION.candidates[0];
21460
+ var PROFILES_ROOT_SEARCH = PROFILES_RESOLUTION.candidates;
21409
21461
  import_handlebars.default.registerHelper("json", (value) => {
21410
21462
  return new import_handlebars.default.SafeString(JSON.stringify(value, null, 2));
21411
21463
  });
21412
21464
  import_handlebars.default.registerHelper("isNumber", (value) => {
21413
21465
  return typeof value === "number" && Number.isFinite(value);
21414
21466
  });
21415
- var SHARED_FRAGMENTS_DIR = resolve4(PROFILES_ROOT, "_shared");
21467
+ var SHARED_FRAGMENTS_DIR = resolve5(PROFILES_ROOT, "_shared");
21416
21468
  var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
21417
21469
  for (const name of SHARED_FRAGMENTS) {
21418
21470
  const fragPath = join3(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
21419
- if (existsSync5(fragPath)) {
21420
- import_handlebars.default.registerPartial(name, readFileSync4(fragPath, "utf-8"));
21471
+ if (existsSync6(fragPath)) {
21472
+ import_handlebars.default.registerPartial(name, readFileSync5(fragPath, "utf-8"));
21421
21473
  }
21422
21474
  }
21423
21475
 
@@ -21518,7 +21570,9 @@ var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
21518
21570
  var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
21519
21571
  var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
21520
21572
  var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
21521
- var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
21573
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS = 1;
21574
+ var HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3;
21575
+ var HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false";
21522
21576
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
21523
21577
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
21524
21578
  var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
@@ -21564,8 +21618,8 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
21564
21618
  String(HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S)
21565
21619
  ],
21566
21620
  [
21567
- "HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS",
21568
- String(HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS)
21621
+ "HINDSIGHT_API_WORKER_CONSOLIDATION_RESERVED_SLOTS",
21622
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS)
21569
21623
  ],
21570
21624
  [
21571
21625
  "HINDSIGHT_API_WORKER_CONSOLIDATION_SLOT_LIMIT",
@@ -21582,6 +21636,10 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
21582
21636
  [
21583
21637
  "HINDSIGHT_API_RECENCY_DECAY_HALFLIFE_DAYS",
21584
21638
  String(HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS)
21639
+ ],
21640
+ [
21641
+ "HINDSIGHT_API_GRAPH_SEED_MIN_SIMILARITY",
21642
+ String(HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY)
21585
21643
  ]
21586
21644
  ];
21587
21645
  var HINDSIGHT_PERF_DEFAULTS_GPU = [
@@ -21602,22 +21660,42 @@ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
21602
21660
  String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
21603
21661
  ],
21604
21662
  ["HINDSIGHT_API_LLM_STRICT_SCHEMA", HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA],
21605
- ["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)]
21663
+ ["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)],
21664
+ ["HINDSIGHT_API_LLM_SUPPORTS_MAX_ITEMS", HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS]
21606
21665
  ];
21607
21666
  var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
21608
21667
  "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY",
21609
21668
  "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP",
21610
21669
  "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS",
21611
21670
  "HINDSIGHT_API_WORKER_MAX_SLOTS",
21612
- "HINDSIGHT_API_WORKER_RETAIN_MAX_SLOTS"
21671
+ "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS",
21672
+ "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY",
21673
+ "HINDSIGHT_MCP_RECALL_BUDGET_MODE",
21674
+ "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT",
21675
+ "HINDSIGHT_API_RETAIN_WALL_TIMEOUT",
21676
+ "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT"
21613
21677
  ]);
21678
+ var HINDSIGHT_WORKER_SLOT_TYPES = [
21679
+ "consolidation",
21680
+ "file_convert_retain",
21681
+ "graph_maintenance",
21682
+ "import_documents",
21683
+ "refresh_mental_model",
21684
+ "retain"
21685
+ ];
21686
+ var HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES = new Map(HINDSIGHT_WORKER_SLOT_TYPES.map((type) => [
21687
+ `HINDSIGHT_API_WORKER_${type.toUpperCase()}_MAX_SLOTS`,
21688
+ `HINDSIGHT_API_WORKER_${type.toUpperCase()}_RESERVED_SLOTS`
21689
+ ]));
21614
21690
  var HINDSIGHT_PERF_ENV_KEYS = new Set([
21615
21691
  ...[
21616
21692
  ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
21617
21693
  ...HINDSIGHT_PERF_DEFAULTS_GPU,
21618
21694
  ...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
21619
21695
  ].map(([k]) => k),
21620
- ...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS
21696
+ ...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS,
21697
+ ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.keys(),
21698
+ ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values()
21621
21699
  ]);
21622
21700
 
21623
21701
  // src/setup/hindsight-context-budget.ts
@@ -21690,6 +21768,15 @@ var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and
21690
21768
  ` + `- Hindsight's own errors, retries, backlogs, or internal state — the memory
21691
21769
  ` + ` system's self-reports are not memories.
21692
21770
  ` + `- Restatements of the user's current request or the task in progress.
21771
+ ` + `- The assistant's own answers, summaries, recaps, or reflect output. Before
21772
+ ` + ` extracting, ask: does anything in this transcript support this claim OTHER
21773
+ ` + ` than the assistant having asserted it? If not, drop it — a model's own
21774
+ ` + ` synthesis re-extracted as a fact is how a guess becomes permanent.
21775
+ ` + ` Concretely, never produce a fact whose only support is the assistant
21776
+ ` + ` stating a date, a version, an attribution, a total, or a decision that the
21777
+ ` + ` user never confirmed and no tool output shows. An unverified claim recalls
21778
+ ` + ` later as though it had been established, which is worse than not
21779
+ ` + ` remembering it at all.
21693
21780
  ` + `- Volatile state written as a timeless assertion. A version, count, size,
21694
21781
  ` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
21695
21782
  ` + ` claim is true only at the instant it was said. Concretely, never produce a
@@ -21845,6 +21932,64 @@ var SUPERSEDED_RETAIN_MISSIONS = [
21845
21932
  ` + `
21846
21933
  ` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
21847
21934
  ` + `nothing durable remains, return an empty facts list.
21935
+ `,
21936
+ `Extract durable facts that will still be true and useful weeks from now, once this session is forgotten.
21937
+ ` + `
21938
+ ` + `DURABILITY GATE. Emit a candidate ONLY if it is one of these five classes:
21939
+ ` + `- PREFERENCE — what the user likes, wants, or always does; a standing rule or correction.
21940
+ ` + `- DECISION — a settled choice that changes how future work is done, including a choice NOT to do something. A decision about the mechanics of the CURRENT task (which worker to dispatch, which branch to rebase, which PR to merge now, what to do next) is process narration, not a durable decision — drop it unless it establishes a standing rule or permanently changes a system.
21941
+ ` + `- FINDING — a root cause, a measurement, or verified behaviour of a system. Include the number.
21942
+ ` + `- OUTCOME — a completed result that changed the world: what shipped, what a thing turned out to be. Not the act of shipping it.
21943
+ ` + `- RELATIONSHIP — who a person is, what a project or tool is, and how they connect.
21944
+ ` + `If a candidate fits none of the five, it is not a memory. Drop it; do not reword it into one.
21945
+ ` + `
21946
+ ` + `A preference revealed by a request is durable — record the preference (what the user likes, wants, or always does), not the request itself.
21947
+ ` + `
21948
+ ` + `A TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this
21949
+ ` + `candidate a file path, a command/process/agent/session id, a temp directory, or
21950
+ ` + `the location where some output was written? If yes, drop it — it is transcript
21951
+ ` + `exhaust, not memory.
21952
+ ` + `
21953
+ ` + `NEVER extract:
21954
+ ` + `- Tool results verbatim or paraphrased. Concretely, never produce a fact whose
21955
+ ` + ` text resembles any of these: "File created successfully at /path/to/file",
21956
+ ` + ` "A background command with ID bctz4yskm is running, and its output will be
21957
+ ` + ` written to /tmp/...", "Async agent a745598ba84e71df1 was launched successfully
21958
+ ` + ` and is running in the background", "User executed a Bash command to sleep for
21959
+ ` + ` 200 seconds", "The assistant used grep to locate 'truncateSync' in src/foo.ts".
21960
+ ` + `- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.
21961
+ ` + `- Agent tool-use traces or narration of what the assistant did (e.g. "the
21962
+ ` + ` assistant used X to query Y", "ran a search", "sent the message").
21963
+ ` + `- The act of delegating, dispatching, spawning, launching, steering or merging
21964
+ ` + ` work — including when it succeeded. "X was dispatched and completed" is the
21965
+ ` + ` session describing itself. Record only what the work LEARNED or CHANGED.
21966
+ ` + `- In-flight workflow/process narration (a sub-task started, paused, or is still
21967
+ ` + ` running) — retain the outcome only once the task completes or a decision is made.
21968
+ ` + `- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.
21969
+ ` + `- Slash commands the user typed and their effects (e.g. "User issued /clear to
21970
+ ` + ` reset assistant state").
21971
+ ` + `- Hindsight's own errors, retries, backlogs, or internal state — the memory
21972
+ ` + ` system's self-reports are not memories.
21973
+ ` + `- Restatements of the user's current request or the task in progress.
21974
+ ` + `- Volatile state written as a timeless assertion. A version, count, size,
21975
+ ` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
21976
+ ` + ` claim is true only at the instant it was said. Concretely, never produce a
21977
+ ` + ` fact whose text resembles any of these: "Switchroom fleet is running image
21978
+ ` + ` version v0.18.19", "The switchroom repo is at /path/to/fleet, version
21979
+ ` + ` v0.19.5", "Bank overlord has 43155 pending consolidations", "The build is
21980
+ ` + ` currently green". If the claim is worth keeping, put the date INSIDE the
21981
+ ` + ` fact text ("As of 2026-07-19 the fleet was running v0.18.19"); if you
21982
+ ` + ` cannot date it, drop it. An undated one is recalled forever as though it
21983
+ ` + ` were still true, which is worse than not remembering it at all.
21984
+ ` + `- Transient state (unread counts, build status, what is running right now) unless
21985
+ ` + ` the fact is explicitly dated, in which case record it as a dated observation.
21986
+ ` + `- Greetings, acknowledgements, and routine operational chatter.
21987
+ ` + `
21988
+ ` + `Write each fact so it stands alone: name the thing, the number, and the date. A
21989
+ ` + `sentence that only makes sense while reading this transcript is not durable.
21990
+ ` + `
21991
+ ` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
21992
+ ` + `nothing durable remains, return an empty facts list.
21848
21993
  `
21849
21994
  ];
21850
21995
  var DEFAULT_OBSERVATIONS_MISSION = `Synthesise durable, standing knowledge about the people, projects, and systems this agent works with: preferences and standing rules, roles and relationships, skills and recurring patterns, technical and operational decisions with their rationale, and the state of long-running work once it lands.
@@ -21922,6 +22067,22 @@ var PROFILE_MEMORY_DEFAULTS = {
21922
22067
  var warnedMissingPool = new Set;
21923
22068
  var warnedMissingDefault = new Set;
21924
22069
 
22070
+ // src/memory/hindsight-retain-provenance.ts
22071
+ var RETAIN_PROVENANCE_TAG = "source:transcript";
22072
+ var RETAIN_TAGS_DEFAULT = Object.freeze([
22073
+ "{session_id}",
22074
+ RETAIN_PROVENANCE_TAG
22075
+ ]);
22076
+
22077
+ // src/memory/hindsight-seed-directives.ts
22078
+ var ANTI_CONFABULATION_DIRECTIVE = `When the retrieved memories do not support an answer, say so instead of composing one.
22079
+ ` + `
22080
+ ` + `- If nothing retrieved scores meaningfully above zero, or what came back is off-topic, the honest answer is that the bank does not know. Say that plainly, and say what would settle it.
22081
+ ` + `- Never assert a date, a version, a number, an attribution, a decision, or an outcome that no retrieved memory states. A question that presupposes something is not evidence for it: asked when a decision was made, when the bank records no such decision, answer that none is recorded — do not supply one.
22082
+ ` + `- Keep what the bank RECORDED separate from what you INFER. State inferences as inferences, and say which memory a recorded fact came from.
22083
+ ` + `- Treat a memory that was extracted from a conversation transcript as an unverified claim, not an established fact — it may be an assistant's own earlier guess. If it is the only support for an answer, say that it is.
22084
+ ` + "- Partial knowledge with its gaps named is more useful than a fluent answer that fills them in.";
22085
+
21925
22086
  // src/telegram/state.ts
21926
22087
  init_paths();
21927
22088
 
@@ -21959,7 +22120,6 @@ var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
21959
22120
  init_paths();
21960
22121
 
21961
22122
  // src/agents/scaffold.ts
21962
- var REPO_ROOT = resolve5(import.meta.dirname, "../..");
21963
22123
  var CLAUDE_MD_YOURS_PLACEHOLDER = "This space is yours. Add per-agent rules, exceptions, or context the " + "Switchroom template doesn't capture. Everything above the marker line is " + "regenerated on every apply; this section is preserved.";
21964
22124
  var pendingBankOps = new Set;
21965
22125
  var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
@@ -21991,8 +22151,8 @@ import { spawn as spawn2, spawnSync as spawnSync4 } from "node:child_process";
21991
22151
  import { mkdir as mkdir2, chmod, chown, unlink, appendFile } from "node:fs/promises";
21992
22152
  import {
21993
22153
  readdirSync as readdirSync6,
21994
- existsSync as existsSync15,
21995
- readFileSync as readFileSync11,
22154
+ existsSync as existsSync16,
22155
+ readFileSync as readFileSync12,
21996
22156
  writeFileSync as writeFileSync6,
21997
22157
  renameSync as renameSync5,
21998
22158
  mkdirSync as mkdirSync5,
@@ -22004,7 +22164,7 @@ import {
22004
22164
  copyFileSync as copyFileSync3,
22005
22165
  unlinkSync as unlinkSync5
22006
22166
  } from "node:fs";
22007
- import { join as join13, dirname as dirname9, resolve as resolve9 } from "node:path";
22167
+ import { join as join12, dirname as dirname9, resolve as resolve9 } from "node:path";
22008
22168
  import { createHash as createHash6, randomUUID as randomUUID2, randomBytes } from "node:crypto";
22009
22169
 
22010
22170
  // src/host-control/protocol.ts
@@ -26696,9 +26856,9 @@ class PostHog extends PostHogBackendClient {
26696
26856
  // src/analytics/posthog.ts
26697
26857
  init_paths();
26698
26858
  import {
26699
- existsSync as existsSync8,
26859
+ existsSync as existsSync9,
26700
26860
  mkdirSync as mkdirSync3,
26701
- readFileSync as readFileSync7,
26861
+ readFileSync as readFileSync8,
26702
26862
  writeFileSync as writeFileSync3
26703
26863
  } from "node:fs";
26704
26864
  import { dirname as dirname3 } from "node:path";
@@ -26717,8 +26877,8 @@ function getDistinctId() {
26717
26877
  return cachedDistinctId;
26718
26878
  const path = resolveStatePath("analytics-id");
26719
26879
  try {
26720
- if (existsSync8(path)) {
26721
- const existing = readFileSync7(path, "utf-8").trim();
26880
+ if (existsSync9(path)) {
26881
+ const existing = readFileSync8(path, "utf-8").trim();
26722
26882
  if (existing) {
26723
26883
  cachedDistinctId = existing;
26724
26884
  return existing;
@@ -27927,8 +28087,8 @@ function releaseBlockIsCommented(doc) {
27927
28087
 
27928
28088
  // src/cli/rollout-pin-journal.ts
27929
28089
  import {
27930
- existsSync as existsSync11,
27931
- readFileSync as readFileSync8,
28090
+ existsSync as existsSync12,
28091
+ readFileSync as readFileSync9,
27932
28092
  writeFileSync as writeFileSync4,
27933
28093
  renameSync as renameSync4,
27934
28094
  unlinkSync as unlinkSync4,
@@ -27937,7 +28097,7 @@ import {
27937
28097
  } from "node:fs";
27938
28098
  import { homedir as homedir5 } from "node:os";
27939
28099
  import { createHash as createHash4 } from "node:crypto";
27940
- import { join as join8, basename as basename3, resolve as resolve7, dirname as dirname6 } from "node:path";
28100
+ import { join as join7, basename as basename3, resolve as resolve7, dirname as dirname6 } from "node:path";
27941
28101
  init_flock();
27942
28102
  var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
27943
28103
  var STATE_DIR_NAME = ".switchroom";
@@ -27950,15 +28110,15 @@ function pinJournalDir(configPath) {
27950
28110
  if (basename3(dir) === STATE_DIR_NAME)
27951
28111
  return dir;
27952
28112
  }
27953
- return join8(homedir5(), STATE_DIR_NAME);
28113
+ return join7(homedir5(), STATE_DIR_NAME);
27954
28114
  }
27955
28115
  function pinJournalPath(configPath) {
27956
28116
  const abs = resolve7(configPath);
27957
28117
  const key = createHash4("sha256").update(abs).digest("hex").slice(0, 12);
27958
- return join8(pinJournalDir(abs), `.rollout-pin-journal.${basename3(abs)}.${key}.json`);
28118
+ return join7(pinJournalDir(abs), `.rollout-pin-journal.${basename3(abs)}.${key}.json`);
27959
28119
  }
27960
28120
  function hasPinJournal(configPath) {
27961
- return existsSync11(pinJournalPath(configPath));
28121
+ return existsSync12(pinJournalPath(configPath));
27962
28122
  }
27963
28123
  function isPidAlive(pid) {
27964
28124
  if (!Number.isInteger(pid) || pid <= 0)
@@ -27992,9 +28152,9 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
27992
28152
  const p = pinJournalPath(configPath);
27993
28153
  let raw;
27994
28154
  try {
27995
- raw = readFileSync8(p, "utf8");
28155
+ raw = readFileSync9(p, "utf8");
27996
28156
  } catch (e) {
27997
- if (existsSync11(p)) {
28157
+ if (existsSync12(p)) {
27998
28158
  warn(`⚠️ rollout pin journal: ${p} exists but could not be read ` + `(${e.message}). A provisional \`release.pin\` may be ` + `uncommitted — verify it host-side before the next reconcile.
27999
28159
  `);
28000
28160
  }
@@ -28039,7 +28199,7 @@ function commitPinPersist(configPath) {
28039
28199
  unlinkSync4(p);
28040
28200
  return null;
28041
28201
  } catch (e) {
28042
- if (!existsSync11(p))
28202
+ if (!existsSync12(p))
28043
28203
  return null;
28044
28204
  return `rollout pin journal: FAILED to clear ${p} after a SUCCESSFUL roll ` + `(${e.message}). Delete it host-side — while it exists, ` + `recovery may revert a proven \`release.pin\`.`;
28045
28205
  }
@@ -28061,7 +28221,7 @@ function rollbackPinPersist(configPath, opts = {}) {
28061
28221
  }
28062
28222
  }
28063
28223
  try {
28064
- const current = readFileSync8(configPath, "utf8");
28224
+ const current = readFileSync9(configPath, "utf8");
28065
28225
  const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
28066
28226
  let mode = 384;
28067
28227
  try {
@@ -28112,14 +28272,14 @@ function recoverPinJournal(configPath, opts = {}) {
28112
28272
  // src/cli/operator-uid.ts
28113
28273
  import {
28114
28274
  chownSync,
28115
- existsSync as existsSync12,
28275
+ existsSync as existsSync13,
28116
28276
  lstatSync as lstatSync2,
28117
28277
  readdirSync as readdirSync5,
28118
- realpathSync as realpathSync2,
28278
+ realpathSync as realpathSync3,
28119
28279
  statSync as statSync7
28120
28280
  } from "node:fs";
28121
28281
  import { homedir as homedir6 } from "node:os";
28122
- import { join as join9 } from "node:path";
28282
+ import { join as join8 } from "node:path";
28123
28283
  var CONTAINER_OPERATOR_HOME = "/host-home";
28124
28284
  var SWITCHROOM_HOME_MARKER = "switchroom.yaml";
28125
28285
  function operatorHomeCandidates(env, home2) {
@@ -28128,7 +28288,7 @@ function operatorHomeCandidates(env, home2) {
28128
28288
  for (const r of roots) {
28129
28289
  if (!r || r.length === 0)
28130
28290
  continue;
28131
- const dir = join9(r, ".switchroom");
28291
+ const dir = join8(r, ".switchroom");
28132
28292
  if (!out.includes(dir))
28133
28293
  out.push(dir);
28134
28294
  }
@@ -28137,7 +28297,7 @@ function operatorHomeCandidates(env, home2) {
28137
28297
  function resolveOperatorUidFromOwnership(deps = {}) {
28138
28298
  const env = deps.env ?? process.env;
28139
28299
  const home2 = deps.home ?? homedir6();
28140
- const exists = deps.exists ?? ((p) => existsSync12(p));
28300
+ const exists = deps.exists ?? ((p) => existsSync13(p));
28141
28301
  const statUid = deps.statUid ?? ((p) => {
28142
28302
  try {
28143
28303
  return statSync7(p).uid;
@@ -28146,7 +28306,7 @@ function resolveOperatorUidFromOwnership(deps = {}) {
28146
28306
  }
28147
28307
  });
28148
28308
  for (const dir of operatorHomeCandidates(env, home2)) {
28149
- if (!exists(join9(dir, SWITCHROOM_HOME_MARKER)))
28309
+ if (!exists(join8(dir, SWITCHROOM_HOME_MARKER)))
28150
28310
  continue;
28151
28311
  const uid = statUid(dir);
28152
28312
  if (uid !== undefined && Number.isFinite(uid) && uid > 0)
@@ -28179,15 +28339,15 @@ function resolveOperatorUid(ownershipDeps = {}) {
28179
28339
  init_atomic();
28180
28340
 
28181
28341
  // src/cli/resolve-version.ts
28182
- import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
28183
- import { dirname as dirname7, join as join10 } from "node:path";
28342
+ import { existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
28343
+ import { dirname as dirname7, join as join9 } from "node:path";
28184
28344
  function readPackageVersion() {
28185
28345
  let dir = import.meta.dirname;
28186
28346
  for (let i = 0;i < 12 && dir && dir !== "/"; i++) {
28187
- const p = join10(dir, "package.json");
28188
- if (existsSync13(p)) {
28347
+ const p = join9(dir, "package.json");
28348
+ if (existsSync14(p)) {
28189
28349
  try {
28190
- const pkg = JSON.parse(readFileSync9(p, "utf-8"));
28350
+ const pkg = JSON.parse(readFileSync10(p, "utf-8"));
28191
28351
  if (pkg?.name === "switchroom" && typeof pkg.version === "string") {
28192
28352
  return pkg.version;
28193
28353
  }
@@ -28588,7 +28748,7 @@ var import__2 = __toESM(require_commander(), 1);
28588
28748
 
28589
28749
  // src/cli/update.ts
28590
28750
  init_loader();
28591
- import { join as join11, dirname as dirname8, resolve as resolve8 } from "node:path";
28751
+ import { join as join10, dirname as dirname8 } from "node:path";
28592
28752
  import { homedir as homedir7 } from "node:os";
28593
28753
 
28594
28754
  // src/agents/lifecycle.ts
@@ -28691,7 +28851,12 @@ function getAllAgentStatuses(config) {
28691
28851
 
28692
28852
  // src/cli/update.ts
28693
28853
  init_atomic();
28694
- var DEFAULT_COMPOSE_PATH = join11(homedir7(), ".switchroom", "compose", "docker-compose.yml");
28854
+ var DEFAULT_COMPOSE_PATH = join10(homedir7(), ".switchroom", "compose", "docker-compose.yml");
28855
+ var PACKAGED_INSTALL_KINDS = new Set([
28856
+ "static-binary",
28857
+ "npm-global",
28858
+ "container"
28859
+ ]);
28695
28860
  var UPDATE_RESULT_SENTINEL = "SWITCHROOM_UPDATE_RESULT:";
28696
28861
  function parseUpdateResultLine(stdout) {
28697
28862
  const idx = stdout.lastIndexOf(UPDATE_RESULT_SENTINEL);
@@ -28777,9 +28942,9 @@ function resolvePriorPinFromFleet(components, target, scope) {
28777
28942
  // src/host-control/config-edit-validator.ts
28778
28943
  init_schema();
28779
28944
  var import_yaml5 = __toESM(require_dist(), 1);
28780
- import { mkdtempSync, writeFileSync as writeFileSync5, rmSync as rmSync2, existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
28945
+ import { mkdtempSync, writeFileSync as writeFileSync5, rmSync as rmSync2, existsSync as existsSync15, readFileSync as readFileSync11 } from "node:fs";
28781
28946
  import { tmpdir } from "node:os";
28782
- import { join as join12, isAbsolute as isAbsolute3, normalize, basename as basename4 } from "node:path";
28947
+ import { join as join11, isAbsolute as isAbsolute2, normalize, basename as basename4 } from "node:path";
28783
28948
  import { spawnSync as spawnSync3 } from "node:child_process";
28784
28949
  import { isDeepStrictEqual } from "node:util";
28785
28950
  var MAX_PATCH_BYTES = 1024 * 1024;
@@ -28792,12 +28957,12 @@ function isTargetPathHeader(headerPath, targetBasename) {
28792
28957
  let p = headerPath;
28793
28958
  if (p.startsWith("a/") || p.startsWith("b/"))
28794
28959
  p = p.slice(2);
28795
- if (isAbsolute3(p))
28960
+ if (isAbsolute2(p))
28796
28961
  return false;
28797
28962
  if (p.includes(".."))
28798
28963
  return false;
28799
28964
  const norm = normalize(p);
28800
- if (norm.includes("..") || isAbsolute3(norm))
28965
+ if (norm.includes("..") || isAbsolute2(norm))
28801
28966
  return false;
28802
28967
  return norm === targetBasename || basename4(norm) === targetBasename;
28803
28968
  }
@@ -28919,21 +29084,21 @@ function validateShape(unifiedDiff, targetPath) {
28919
29084
  return null;
28920
29085
  }
28921
29086
  function applyPatch(unifiedDiff, configPath, gitBin) {
28922
- if (!existsSync14(configPath)) {
29087
+ if (!existsSync15(configPath)) {
28923
29088
  return {
28924
29089
  ok: false,
28925
29090
  code: "E_PATCH_APPLY_FAILED",
28926
29091
  detail: `target config not found at ${configPath}`
28927
29092
  };
28928
29093
  }
28929
- const liveContent = readFileSync10(configPath, "utf8");
28930
- const scratchDir = mkdtempSync(join12(tmpdir(), "config-propose-edit-"));
29094
+ const liveContent = readFileSync11(configPath, "utf8");
29095
+ const scratchDir = mkdtempSync(join11(tmpdir(), "config-propose-edit-"));
28931
29096
  try {
28932
29097
  const targetBasename = configPath.split("/").pop() ?? "switchroom.yaml";
28933
- const scratchFile = join12(scratchDir, targetBasename);
29098
+ const scratchFile = join11(scratchDir, targetBasename);
28934
29099
  writeFileSync5(scratchFile, liveContent);
28935
29100
  const normalizedDiff = normalizeDiffHeadersToBasename(unifiedDiff, targetBasename);
28936
- const patchFile = join12(scratchDir, "proposal.patch");
29101
+ const patchFile = join11(scratchDir, "proposal.patch");
28937
29102
  writeFileSync5(patchFile, normalizedDiff);
28938
29103
  const bin = gitBin ?? "git";
28939
29104
  const baseArgs = [
@@ -28963,7 +29128,7 @@ function applyPatch(unifiedDiff, configPath, gitBin) {
28963
29128
  detail: `git apply failed: ${(real.stderr || "").trim().slice(0, 500)}`
28964
29129
  };
28965
29130
  }
28966
- const after = readFileSync10(scratchFile, "utf8");
29131
+ const after = readFileSync11(scratchFile, "utf8");
28967
29132
  return { ok: true, after };
28968
29133
  } finally {
28969
29134
  rmSync2(scratchDir, { recursive: true, force: true });
@@ -29161,7 +29326,7 @@ function validateConfigEdit(opts) {
29161
29326
  return schemaErr;
29162
29327
  let beforeData = {};
29163
29328
  try {
29164
- const beforeRaw = existsSync14(opts.configPath) ? readFileSync10(opts.configPath, "utf8") : "";
29329
+ const beforeRaw = existsSync15(opts.configPath) ? readFileSync11(opts.configPath, "utf8") : "";
29165
29330
  const beforeDoc = import_yaml5.parseDocument(beforeRaw, { merge: false, strict: false });
29166
29331
  beforeData = beforeDoc.toJS();
29167
29332
  } catch {
@@ -29543,6 +29708,17 @@ function deferredLines(target) {
29543
29708
  `- hostd template regen (only if the release changed hostd mounts/env) — \`switchroom hostd install --tag ${target}\``
29544
29709
  ];
29545
29710
  }
29711
+ function warningLines(warnings) {
29712
+ if (!warnings || warnings.length === 0)
29713
+ return [];
29714
+ const MAX_SHOWN = 8;
29715
+ const shown = warnings.slice(0, MAX_SHOWN);
29716
+ const lines = ["**Warnings:**", ...shown.map((w) => `- ⚠️ ${w}`)];
29717
+ if (warnings.length > MAX_SHOWN) {
29718
+ lines.push(`- …and ${warnings.length - MAX_SHOWN} more (see \`get_status\`).`);
29719
+ }
29720
+ return lines;
29721
+ }
29546
29722
  var MAX_RENDER_CHARS = 3800;
29547
29723
  function rolledList(rolled, compact) {
29548
29724
  if (rolled.length === 0)
@@ -29556,6 +29732,7 @@ function renderWith(s, compact) {
29556
29732
  const rolledCount = rolled.length;
29557
29733
  const checklist = checklistLines(s, compact);
29558
29734
  const singletons = singletonLines(s);
29735
+ const warnings = warningLines(s.warnings);
29559
29736
  const elapsedMs = s.elapsedMs ?? (s.startedAtMs !== undefined && s.nowMs !== undefined ? s.nowMs - s.startedAtMs : undefined);
29560
29737
  if (s.terminal === "completed") {
29561
29738
  const parts2 = [headerLine(s, "✅")];
@@ -29568,6 +29745,8 @@ function renderWith(s, compact) {
29568
29745
  parts2.push("", ...checklist);
29569
29746
  if (singletons.length > 0)
29570
29747
  parts2.push("", ...singletons);
29748
+ if (warnings.length > 0)
29749
+ parts2.push("", ...warnings);
29571
29750
  if (s.deferred !== false)
29572
29751
  parts2.push("", ...deferredLines(s.target));
29573
29752
  return parts2.join(`
@@ -29587,6 +29766,8 @@ function renderWith(s, compact) {
29587
29766
  parts2.push("", ...checklist);
29588
29767
  if (singletons.length > 0)
29589
29768
  parts2.push("", ...singletons);
29769
+ if (warnings.length > 0)
29770
+ parts2.push("", ...warnings);
29590
29771
  return parts2.join(`
29591
29772
  `);
29592
29773
  }
@@ -29602,6 +29783,8 @@ function renderWith(s, compact) {
29602
29783
  parts2.push("", ...checklist);
29603
29784
  if (singletons.length > 0)
29604
29785
  parts2.push("", ...singletons);
29786
+ if (warnings.length > 0)
29787
+ parts2.push("", ...warnings);
29605
29788
  return parts2.join(`
29606
29789
  `);
29607
29790
  }
@@ -29727,11 +29910,11 @@ function resolveDigests(imageRefs) {
29727
29910
  return out;
29728
29911
  }
29729
29912
  function readCachedInstallType(bindRoot) {
29730
- const cacheDir = join13(bindRoot, ".switchroom");
29731
- const cachePath = join13(cacheDir, "install-type.json");
29732
- if (existsSync15(cachePath)) {
29913
+ const cacheDir = join12(bindRoot, ".switchroom");
29914
+ const cachePath = join12(cacheDir, "install-type.json");
29915
+ if (existsSync16(cachePath)) {
29733
29916
  try {
29734
- const raw = readFileSync11(cachePath, "utf-8");
29917
+ const raw = readFileSync12(cachePath, "utf-8");
29735
29918
  const parsed = JSON.parse(raw);
29736
29919
  if (parsed && typeof parsed.install_type === "string" && typeof parsed.detected_at === "string") {
29737
29920
  return parsed;
@@ -29783,7 +29966,7 @@ function writeFileInPlacePreservingInode(targetPath, content) {
29783
29966
  } finally {
29784
29967
  closeSync5(fd);
29785
29968
  }
29786
- const readBack = readFileSync11(targetPath);
29969
+ const readBack = readFileSync12(targetPath);
29787
29970
  if (readBack.length !== buf.length) {
29788
29971
  throw new Error(`in-place write short: wrote ${buf.length} bytes but read back ${readBack.length}`);
29789
29972
  }
@@ -29818,11 +30001,11 @@ class HostdServer {
29818
30001
  fleetMutationInFlight = null;
29819
30002
  lastAutoRolloutFailedPin = null;
29820
30003
  autoRolloutLatchPath() {
29821
- return join13(this.hostdDirPath(), AUTO_ROLLOUT_LATCH_FILENAME);
30004
+ return join12(this.hostdDirPath(), AUTO_ROLLOUT_LATCH_FILENAME);
29822
30005
  }
29823
30006
  readAutoRolloutLatch() {
29824
30007
  try {
29825
- const raw = readFileSync11(this.autoRolloutLatchPath(), "utf-8");
30008
+ const raw = readFileSync12(this.autoRolloutLatchPath(), "utf-8");
29826
30009
  const parsed = JSON.parse(raw);
29827
30010
  if (parsed && parsed.v === 1 && typeof parsed.pin === "string" && parsed.pin.length > 0 && (parsed.outcome === "attempting" || parsed.outcome === "failed")) {
29828
30011
  return parsed;
@@ -29861,7 +30044,7 @@ class HostdServer {
29861
30044
  return resolveHostdConfigPath(this.opts.configPath);
29862
30045
  }
29863
30046
  fleetComposePath() {
29864
- return join13(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
30047
+ return join12(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
29865
30048
  }
29866
30049
  recoverRolloutPinJournal(context) {
29867
30050
  let note = null;
@@ -29900,7 +30083,7 @@ class HostdServer {
29900
30083
  return note;
29901
30084
  }
29902
30085
  async start() {
29903
- const hostdDir = join13(this.opts.homeDir, ".switchroom", "hostd");
30086
+ const hostdDir = join12(this.opts.homeDir, ".switchroom", "hostd");
29904
30087
  await mkdir2(hostdDir, { recursive: true });
29905
30088
  await chmod(hostdDir, 493).catch(() => {
29906
30089
  return;
@@ -29921,13 +30104,13 @@ class HostdServer {
29921
30104
  }
29922
30105
  try {
29923
30106
  for (const name of agentNames) {
29924
- const dir = join13(hostdDir, name);
29925
- const sockPath = join13(dir, "sock");
30107
+ const dir = join12(hostdDir, name);
30108
+ const sockPath = join12(dir, "sock");
29926
30109
  await mkdir2(dir, { recursive: true });
29927
30110
  await chmod(dir, 493).catch(() => {
29928
30111
  return;
29929
30112
  });
29930
- if (existsSync15(sockPath))
30113
+ if (existsSync16(sockPath))
29931
30114
  await unlink(sockPath).catch(() => {
29932
30115
  return;
29933
30116
  });
@@ -29958,13 +30141,13 @@ class HostdServer {
29958
30141
  process.stderr.write(`hostd: SWITCHROOM_HOSTD_OPERATOR_UID='${opUidStr}' is not a positive integer; skipping operator listener
29959
30142
  `);
29960
30143
  } else {
29961
- const dir = join13(hostdDir, "operator");
29962
- const sockPath = join13(dir, "sock");
30144
+ const dir = join12(hostdDir, "operator");
30145
+ const sockPath = join12(dir, "sock");
29963
30146
  await mkdir2(dir, { recursive: true });
29964
30147
  await chmod(dir, 493).catch(() => {
29965
30148
  return;
29966
30149
  });
29967
- if (existsSync15(sockPath))
30150
+ if (existsSync16(sockPath))
29968
30151
  await unlink(sockPath).catch(() => {
29969
30152
  return;
29970
30153
  });
@@ -30000,11 +30183,11 @@ class HostdServer {
30000
30183
  }
30001
30184
  async reconcileOrphanedFleetMutations() {
30002
30185
  const path3 = this.auditLogPath();
30003
- if (!existsSync15(path3))
30186
+ if (!existsSync16(path3))
30004
30187
  return;
30005
30188
  let raw;
30006
30189
  try {
30007
- raw = readFileSync11(path3, "utf-8");
30190
+ raw = readFileSync12(path3, "utf-8");
30008
30191
  } catch {
30009
30192
  return;
30010
30193
  }
@@ -30403,11 +30586,11 @@ class HostdServer {
30403
30586
  missingApplyAssets() {
30404
30587
  const root = this.opts.applyAssetsRoot ?? resolve9(import.meta.dirname, "../..");
30405
30588
  return [
30406
- join13(root, "profiles"),
30407
- join13(root, "profiles", "default"),
30408
- join13(root, "vendor", "hindsight-memory"),
30409
- join13(root, "skills")
30410
- ].filter((p) => !existsSync15(p));
30589
+ join12(root, "profiles"),
30590
+ join12(root, "profiles", "default"),
30591
+ join12(root, "vendor", "hindsight-memory"),
30592
+ join12(root, "skills")
30593
+ ].filter((p) => !existsSync16(p));
30411
30594
  }
30412
30595
  applyAssetPreflight(request_id, started) {
30413
30596
  const missing = this.missingApplyAssets();
@@ -30665,19 +30848,19 @@ class HostdServer {
30665
30848
  return entry;
30666
30849
  }
30667
30850
  hostdDirPath() {
30668
- return join13(this.opts.homeDir, ".switchroom", "hostd");
30851
+ return join12(this.opts.homeDir, ".switchroom", "hostd");
30669
30852
  }
30670
30853
  hostdDirHostPath() {
30671
30854
  const hostHome = this.opts.hostHomeDir ?? process.env.SWITCHROOM_HOST_HOME?.trim() ?? this.opts.homeDir;
30672
- return join13(hostHome, ".switchroom", "hostd");
30855
+ return join12(hostHome, ".switchroom", "hostd");
30673
30856
  }
30674
30857
  async beginSelfBump(req, caller, started) {
30675
30858
  const ownVersion = this.opts.selfVersion ?? SWITCHROOM_VERSION;
30676
- const composePath = join13(this.hostdDirPath(), "docker-compose.yml");
30677
- if (!existsSync15(composePath)) {
30859
+ const composePath = join12(this.hostdDirPath(), "docker-compose.yml");
30860
+ if (!existsSync16(composePath)) {
30678
30861
  return deniedResponse(req.request_id, `rollout: hostd's CLI (v${ownVersion}) is older than the target ` + `${req.args.pin} and needs a self-bump first, but its compose file ` + `is missing at ${composePath}. Run \`switchroom hostd install ` + `--tag ${req.args.pin}\` on the host, then re-run the roll. ` + `Nothing was changed.`, Date.now() - started);
30679
30862
  }
30680
- const before = readFileSync11(composePath, "utf8");
30863
+ const before = readFileSync12(composePath, "utf8");
30681
30864
  const bumped = bumpHostdComposeImageTag(before, req.args.pin);
30682
30865
  if (!bumped) {
30683
30866
  return deniedResponse(req.request_id, `rollout: hostd needs a self-bump to ${req.args.pin} but no ` + `switchroom-hostd image line was found in ${composePath} ` + `(hand-edited compose?). Run \`switchroom hostd install --tag ` + `${req.args.pin}\` on the host, then re-run the roll. Nothing ` + `was changed.`, Date.now() - started);
@@ -30698,10 +30881,10 @@ class HostdServer {
30698
30881
  try {
30699
30882
  const baks = readdirSync6(this.hostdDirPath()).filter((f) => f.startsWith("docker-compose.yml.bak-selfbump-")).sort();
30700
30883
  for (const f of baks.slice(0, Math.max(0, baks.length - 4))) {
30701
- unlinkSync5(join13(this.hostdDirPath(), f));
30884
+ unlinkSync5(join12(this.hostdDirPath(), f));
30702
30885
  }
30703
30886
  } catch {}
30704
- const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
30887
+ const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
30705
30888
  const marker = {
30706
30889
  v: 1,
30707
30890
  request_id: req.request_id,
@@ -30785,7 +30968,7 @@ class HostdServer {
30785
30968
  rollbackBump();
30786
30969
  entry.result = "error";
30787
30970
  entry.finished_at = Date.now();
30788
- entry.error = `self-bump helper failed (container exit ${Number.isFinite(code) ? code : "unknown"}). ` + `See ${join13(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)} on the host. ` + `Compose bump rolled back. Fallback: \`switchroom hostd install ` + `--tag ${req.args.pin}\` host-side, then re-run the roll.`;
30971
+ entry.error = `self-bump helper failed (container exit ${Number.isFinite(code) ? code : "unknown"}). ` + `See ${join12(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)} on the host. ` + `Compose bump rolled back. Fallback: \`switchroom hostd install ` + `--tag ${req.args.pin}\` host-side, then re-run the roll.`;
30789
30972
  if (this.fleetMutationInFlight && this.fleetMutationInFlight.request_id === entry.request_id) {
30790
30973
  this.fleetMutationInFlight = null;
30791
30974
  }
@@ -30812,10 +30995,10 @@ class HostdServer {
30812
30995
  try {
30813
30996
  if (!Number.isInteger(messageId) || messageId <= 0)
30814
30997
  return;
30815
- const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
30816
- if (!existsSync15(markerPath))
30998
+ const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
30999
+ if (!existsSync16(markerPath))
30817
31000
  return;
30818
- const marker = parsePendingRolloutMarker(readFileSync11(markerPath, "utf8"));
31001
+ const marker = parsePendingRolloutMarker(readFileSync12(markerPath, "utf8"));
30819
31002
  if (!marker)
30820
31003
  return;
30821
31004
  if (marker.request_id !== requestId)
@@ -30830,12 +31013,12 @@ class HostdServer {
30830
31013
  } catch {}
30831
31014
  }
30832
31015
  async resumePendingSelfBumpRollout() {
30833
- const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
30834
- if (!existsSync15(markerPath))
31016
+ const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
31017
+ if (!existsSync16(markerPath))
30835
31018
  return;
30836
31019
  let raw;
30837
31020
  try {
30838
- raw = readFileSync11(markerPath, "utf8");
31021
+ raw = readFileSync12(markerPath, "utf8");
30839
31022
  } finally {
30840
31023
  try {
30841
31024
  unlinkSync5(markerPath);
@@ -30886,7 +31069,7 @@ class HostdServer {
30886
31069
  return;
30887
31070
  }
30888
31071
  if (needsSelfBump(ownVersion, marker.pin)) {
30889
- await failResume(`hostd is still on v${ownVersion} after the self-bump to ` + `${marker.pin} (helper failed? see ` + `${join13(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)}). ` + `Fallback: \`switchroom hostd install --tag ${marker.pin}\` ` + `host-side, then re-issue the rollout.`);
31072
+ await failResume(`hostd is still on v${ownVersion} after the self-bump to ` + `${marker.pin} (helper failed? see ` + `${join12(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)}). ` + `Fallback: \`switchroom hostd install --tag ${marker.pin}\` ` + `host-side, then re-issue the rollout.`);
30890
31073
  return;
30891
31074
  }
30892
31075
  process.stderr.write(`hostd: resuming rollout ${marker.request_id} → ${marker.pin} after ` + `self-bump (v${marker.prior_hostd_version} → v${ownVersion})
@@ -30994,7 +31177,7 @@ class HostdServer {
30994
31177
  }
30995
31178
  let beforeContent;
30996
31179
  try {
30997
- beforeContent = readFileSync11(configPath, "utf-8");
31180
+ beforeContent = readFileSync12(configPath, "utf-8");
30998
31181
  } catch {
30999
31182
  beforeContent = "";
31000
31183
  }
@@ -31114,7 +31297,7 @@ class HostdServer {
31114
31297
  try {
31115
31298
  let snapshot;
31116
31299
  try {
31117
- snapshot = readFileSync11(configPath, "utf-8");
31300
+ snapshot = readFileSync12(configPath, "utf-8");
31118
31301
  } catch (e) {
31119
31302
  await approval.finalize({
31120
31303
  outcome: "reconcile_failed_rolled_back",
@@ -31412,8 +31595,8 @@ ${output.recovery.stderr}` : "";
31412
31595
  if (this.opts.imageRefsForDigests)
31413
31596
  return this.opts.imageRefsForDigests();
31414
31597
  try {
31415
- const composePath = join13(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
31416
- if (!existsSync15(composePath))
31598
+ const composePath = join12(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
31599
+ if (!existsSync16(composePath))
31417
31600
  return [];
31418
31601
  const r = spawnSync4("docker", [
31419
31602
  "compose",
@@ -31487,6 +31670,9 @@ ${output.recovery.stderr}` : "";
31487
31670
  if (parsed.drifted && parsed.drifted.length > 0) {
31488
31671
  entry.drifted = parsed.drifted;
31489
31672
  }
31673
+ if (parsed.warnings && parsed.warnings.length > 0) {
31674
+ entry.warnings = parsed.warnings;
31675
+ }
31490
31676
  if (parsed.got !== undefined)
31491
31677
  entry.got = parsed.got;
31492
31678
  if (parsed.timedOut)
@@ -31629,7 +31815,8 @@ ${pinNote}`.trim();
31629
31815
  ...entry.failed_step ? { failedStep: entry.failed_step } : {},
31630
31816
  ...entry.failed_agent ? { failedAgent: entry.failed_agent } : {},
31631
31817
  ...entry.got !== undefined ? { got: entry.got } : {},
31632
- ...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {}
31818
+ ...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
31819
+ ...entry.warnings && entry.warnings.length > 0 ? { warnings: entry.warnings } : {}
31633
31820
  });
31634
31821
  this.opts.rolloutRelay.postTerminal({
31635
31822
  requestId: entry.request_id,
@@ -31667,10 +31854,10 @@ ${pinNote}`.trim();
31667
31854
  }
31668
31855
  rolloutRowExistsInLog(targetRequestId) {
31669
31856
  const path3 = this.auditLogPath();
31670
- if (!existsSync15(path3))
31857
+ if (!existsSync16(path3))
31671
31858
  return false;
31672
31859
  try {
31673
- const raw = readFileSync11(path3, "utf-8");
31860
+ const raw = readFileSync12(path3, "utf-8");
31674
31861
  return latestRolloutRowForRequest(raw, targetRequestId) !== null;
31675
31862
  } catch {
31676
31863
  return false;
@@ -31678,11 +31865,11 @@ ${pinNote}`.trim();
31678
31865
  }
31679
31866
  rolloutStatusFromLog(responseRequestId, targetRequestId, started) {
31680
31867
  const path3 = this.auditLogPath();
31681
- if (!existsSync15(path3))
31868
+ if (!existsSync16(path3))
31682
31869
  return null;
31683
31870
  let raw;
31684
31871
  try {
31685
- raw = readFileSync11(path3, "utf-8");
31872
+ raw = readFileSync12(path3, "utf-8");
31686
31873
  } catch {
31687
31874
  return null;
31688
31875
  }
@@ -31712,7 +31899,7 @@ ${pinNote}`.trim();
31712
31899
  };
31713
31900
  }
31714
31901
  statusEntryToResponse(request_id, entry) {
31715
- const rolloutPayload = entry.op === "rollout" && (entry.rolled !== undefined || entry.failed_step !== undefined || entry.failed_agent !== undefined || entry.current_phase !== undefined) ? JSON.stringify({
31902
+ const rolloutPayload = entry.op === "rollout" && (entry.rolled !== undefined || entry.failed_step !== undefined || entry.failed_agent !== undefined || entry.warnings !== undefined && entry.warnings.length > 0 || entry.current_phase !== undefined) ? JSON.stringify({
31716
31903
  rolled: entry.rolled ?? [],
31717
31904
  ...entry.current_phase ? { phase: entry.current_phase } : {},
31718
31905
  ...entry.phase_n !== undefined ? { n: entry.phase_n } : {},
@@ -31722,6 +31909,7 @@ ${pinNote}`.trim();
31722
31909
  ...entry.failed_agent ? { failedAgent: entry.failed_agent } : {},
31723
31910
  ...entry.got !== undefined ? { got: entry.got } : {},
31724
31911
  ...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
31912
+ ...entry.warnings && entry.warnings.length > 0 ? { warnings: entry.warnings } : {},
31725
31913
  ...entry.pin ? { pin: entry.pin } : {},
31726
31914
  ...entry.prior_pin ? { prior_pin: entry.prior_pin } : {}
31727
31915
  }) : undefined;
@@ -31763,7 +31951,7 @@ ${pinNote}`.trim();
31763
31951
  }
31764
31952
  }
31765
31953
  auditLogPath() {
31766
- return this.opts.auditLogPath ?? join13(this.opts.homeDir, ".switchroom", "host-control-audit.log");
31954
+ return this.opts.auditLogPath ?? join12(this.opts.homeDir, ".switchroom", "host-control-audit.log");
31767
31955
  }
31768
31956
  maybeRotateAuditLog(path3) {
31769
31957
  const { maxBytes, maxFiles } = resolveRotationConfig({
@@ -32285,6 +32473,7 @@ class SocketRolloutRelay {
32285
32473
  // src/host-control/rollout-narration-relay.ts
32286
32474
  import { connect as connect4 } from "node:net";
32287
32475
  var DEFAULT_POST_REPLY_TIMEOUT_MS = 5000;
32476
+ var DEFAULT_EDIT_REPLY_TIMEOUT_MS = 5000;
32288
32477
 
32289
32478
  class SocketRolloutNarrationRelay {
32290
32479
  opts;
@@ -32382,18 +32571,40 @@ class SocketRolloutNarrationRelay {
32382
32571
  sockPath = this.opts.resolveGatewaySocket(args.agentName);
32383
32572
  } catch (e) {
32384
32573
  log(`narration edit resolveGatewaySocket threw: ${e.message}`);
32385
- return;
32574
+ return Promise.resolve({
32575
+ ok: false,
32576
+ gone: false,
32577
+ reason: `resolveGatewaySocket threw: ${e.message}`
32578
+ });
32386
32579
  }
32387
- if (sockPath === null)
32388
- return;
32389
- try {
32390
- const client2 = connect4({ path: sockPath });
32391
- client2.setTimeout(5000);
32392
- const done = () => {
32580
+ if (sockPath === null) {
32581
+ return Promise.resolve({
32582
+ ok: false,
32583
+ gone: false,
32584
+ reason: "no reachable gateway"
32585
+ });
32586
+ }
32587
+ return new Promise((resolve10) => {
32588
+ let settled = false;
32589
+ let client2;
32590
+ const finish = (outcome) => {
32591
+ if (settled)
32592
+ return;
32593
+ settled = true;
32393
32594
  try {
32394
32595
  client2.destroy();
32395
32596
  } catch {}
32597
+ resolve10(outcome);
32396
32598
  };
32599
+ try {
32600
+ client2 = connect4({ path: sockPath });
32601
+ } catch (e) {
32602
+ log(`narration edit connect threw: ${e.message}`);
32603
+ resolve10({ ok: false, gone: false, reason: e.message });
32604
+ return;
32605
+ }
32606
+ client2.setTimeout(this.opts.editReplyTimeoutMs ?? DEFAULT_EDIT_REPLY_TIMEOUT_MS);
32607
+ let buffer = "";
32397
32608
  client2.on("connect", () => {
32398
32609
  try {
32399
32610
  client2.write(JSON.stringify({
@@ -32404,21 +32615,46 @@ class SocketRolloutNarrationRelay {
32404
32615
  text: args.text
32405
32616
  }) + `
32406
32617
  `);
32407
- client2.end();
32408
32618
  } catch (e) {
32409
32619
  log(`narration edit write failed: ${e.message}`);
32410
- done();
32620
+ finish({ ok: false, gone: false, reason: e.message });
32411
32621
  }
32412
32622
  });
32413
- client2.on("timeout", done);
32623
+ client2.on("data", (chunk2) => {
32624
+ buffer += chunk2.toString("utf8");
32625
+ const lines = buffer.split(`
32626
+ `);
32627
+ buffer = lines.pop() ?? "";
32628
+ for (const line of lines) {
32629
+ if (!line.trim())
32630
+ continue;
32631
+ let obj;
32632
+ try {
32633
+ obj = JSON.parse(line);
32634
+ } catch {
32635
+ continue;
32636
+ }
32637
+ if (obj.type === "rollout_status_edited" && obj.requestId === args.requestId) {
32638
+ if (obj.ok === true) {
32639
+ finish({ ok: true });
32640
+ } else {
32641
+ finish({
32642
+ ok: false,
32643
+ gone: obj.gone === true,
32644
+ ...typeof obj.reason === "string" ? { reason: obj.reason } : {}
32645
+ });
32646
+ }
32647
+ return;
32648
+ }
32649
+ }
32650
+ });
32651
+ client2.on("timeout", () => finish({ ok: false, gone: false, reason: "edit reply timed out" }));
32414
32652
  client2.on("error", (e) => {
32415
32653
  log(`narration edit socket error: ${e.message}`);
32416
- done();
32654
+ finish({ ok: false, gone: false, reason: e.message });
32417
32655
  });
32418
- client2.on("close", () => {});
32419
- } catch (e) {
32420
- log(`narration edit connect threw: ${e.message}`);
32421
- }
32656
+ client2.on("close", () => finish({ ok: false, gone: false, reason: "no edit reply" }));
32657
+ });
32422
32658
  }
32423
32659
  }
32424
32660
 
@@ -32731,9 +32967,62 @@ class LogTailRolloutNarrator {
32731
32967
  singletons: initialSingletons(),
32732
32968
  frozen: false,
32733
32969
  timer: null,
32734
- pendingEditAfterPost: false
32970
+ pendingEditAfterPost: false,
32971
+ goneRepostUsed: false,
32972
+ goneRepostForMessageId: null
32735
32973
  };
32736
32974
  }
32975
+ escalateCard(esc) {
32976
+ try {
32977
+ if (this.opts.escalate) {
32978
+ this.opts.escalate(esc);
32979
+ return;
32980
+ }
32981
+ this.opts.log?.(`rollout card escalation request=${esc.requestId} agent=${esc.agentName} ` + `staleMessageId=${esc.staleMessageId} reason=${esc.reason}` + `${esc.detail !== undefined ? ` detail=${esc.detail}` : ""} ` + `— card could not be restored, no operator card issued (get_status remains the record)`);
32982
+ } catch {}
32983
+ }
32984
+ onEditOutcome(requestId, editedMessageId, outcome) {
32985
+ if (outcome.ok)
32986
+ return;
32987
+ const st = this.states.get(requestId);
32988
+ if (!st)
32989
+ return;
32990
+ if (!outcome.gone) {
32991
+ this.opts.log?.(`rollout narration edit failed (transient, requestId=${requestId}): ${outcome.reason ?? "unknown"}`);
32992
+ return;
32993
+ }
32994
+ if (st.messageId !== editedMessageId)
32995
+ return;
32996
+ if (st.goneRepostUsed) {
32997
+ this.escalateCard({
32998
+ requestId,
32999
+ agentName: st.agentName,
33000
+ staleMessageId: editedMessageId,
33001
+ reason: "gone-again",
33002
+ detail: outcome.reason
33003
+ });
33004
+ return;
33005
+ }
33006
+ st.goneRepostUsed = true;
33007
+ st.messageId = null;
33008
+ st.postFailed = false;
33009
+ st.goneRepostForMessageId = editedMessageId;
33010
+ if (st.timer) {
33011
+ clearTimeout(st.timer);
33012
+ st.timer = null;
33013
+ }
33014
+ this.opts.log?.(`rollout narration card ${editedMessageId} is gone (requestId=${requestId}); re-posting once`);
33015
+ if (!this.tryPost(requestId, true)) {
33016
+ st.goneRepostForMessageId = null;
33017
+ this.escalateCard({
33018
+ requestId,
33019
+ agentName: st.agentName,
33020
+ staleMessageId: editedMessageId,
33021
+ reason: "repost-unavailable",
33022
+ detail: "post-attempt cap reached"
33023
+ });
33024
+ }
33025
+ }
32737
33026
  ensureState(entry) {
32738
33027
  let st = this.states.get(entry.request_id);
32739
33028
  if (!st) {
@@ -32797,10 +33086,22 @@ class LogTailRolloutNarrator {
32797
33086
  if (mid === null) {
32798
33087
  st.postFailed = true;
32799
33088
  this.opts.log?.(`rollout narration post returned no message_id (requestId=${requestId}); backing off re-posts`);
33089
+ if (st.goneRepostForMessageId !== null) {
33090
+ const stale = st.goneRepostForMessageId;
33091
+ st.goneRepostForMessageId = null;
33092
+ this.escalateCard({
33093
+ requestId,
33094
+ agentName: st.agentName,
33095
+ staleMessageId: stale,
33096
+ reason: "repost-failed",
33097
+ detail: "post returned no message_id"
33098
+ });
33099
+ }
32800
33100
  return;
32801
33101
  }
32802
33102
  st.messageId = mid;
32803
33103
  st.postFailed = false;
33104
+ st.goneRepostForMessageId = null;
32804
33105
  try {
32805
33106
  this.opts.onMessageId?.(requestId, mid);
32806
33107
  } catch (e) {
@@ -32814,6 +33115,17 @@ class LogTailRolloutNarrator {
32814
33115
  st.posting = false;
32815
33116
  st.postFailed = true;
32816
33117
  this.opts.log?.(`rollout narration post failed (non-fatal): ${e.message}`);
33118
+ if (st.goneRepostForMessageId !== null) {
33119
+ const stale = st.goneRepostForMessageId;
33120
+ st.goneRepostForMessageId = null;
33121
+ this.escalateCard({
33122
+ requestId,
33123
+ agentName: st.agentName,
33124
+ staleMessageId: stale,
33125
+ reason: "repost-failed",
33126
+ detail: e.message
33127
+ });
33128
+ }
32817
33129
  });
32818
33130
  return true;
32819
33131
  }
@@ -32849,11 +33161,14 @@ class LogTailRolloutNarrator {
32849
33161
  return;
32850
33162
  }
32851
33163
  const text = renderRolloutStatus({ ...st.render, nowMs: this.now() });
33164
+ const editedMessageId = st.messageId;
32852
33165
  this.relay.edit({
32853
33166
  requestId,
32854
33167
  agentName: st.agentName,
32855
- messageId: st.messageId,
33168
+ messageId: editedMessageId,
32856
33169
  text
33170
+ }).then((outcome) => this.onEditOutcome(requestId, editedMessageId, outcome)).catch((e) => {
33171
+ this.opts.log?.(`rollout narration edit relay rejected (non-fatal): ${e.message}`);
32857
33172
  });
32858
33173
  }
32859
33174
  }
@@ -33120,7 +33435,7 @@ function makeRestart(switchroomBin) {
33120
33435
  }
33121
33436
 
33122
33437
  // src/host-control/update-notifier.ts
33123
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, renameSync as renameSync6, writeFileSync as writeFileSync7 } from "node:fs";
33438
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync7 } from "node:fs";
33124
33439
  import { randomBytes as randomBytes3 } from "node:crypto";
33125
33440
  import { dirname as dirname10 } from "node:path";
33126
33441
  var FINALIZE_DETAIL_MAX = 450;
@@ -33237,7 +33552,7 @@ class UpdateNotifier {
33237
33552
  }
33238
33553
  readState() {
33239
33554
  try {
33240
- const raw = readFileSync12(this.opts.statePath, "utf8");
33555
+ const raw = readFileSync13(this.opts.statePath, "utf8");
33241
33556
  const parsed = JSON.parse(raw);
33242
33557
  if (parsed === null || typeof parsed !== "object")
33243
33558
  return null;
@@ -33378,13 +33693,13 @@ async function loadConfigResilient() {
33378
33693
  } catch {
33379
33694
  configPath = undefined;
33380
33695
  }
33381
- const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join14(homedir8(), ".switchroom", "agents");
33696
+ const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join13(homedir8(), ".switchroom", "agents");
33382
33697
  const listGatewayCandidates = () => {
33383
33698
  const out = [];
33384
33699
  try {
33385
33700
  for (const name of readdirSync7(agentsDir).sort()) {
33386
33701
  const sock = resolve10(agentsDir, name, "telegram", "gateway.sock");
33387
- if (existsSync16(sock))
33702
+ if (existsSync17(sock))
33388
33703
  out.push({ agent: name, sock });
33389
33704
  }
33390
33705
  } catch {}
@@ -33431,10 +33746,10 @@ async function main() {
33431
33746
  `);
33432
33747
  process.exit(2);
33433
33748
  }
33434
- const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join14(homedir8(), ".switchroom", "agents");
33749
+ const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join13(homedir8(), ".switchroom", "agents");
33435
33750
  const resolveGatewaySocket = (agentName) => {
33436
33751
  const sock = resolve10(agentsDir, agentName, "telegram", "gateway.sock");
33437
- return existsSync16(sock) ? sock : null;
33752
+ return existsSync17(sock) ? sock : null;
33438
33753
  };
33439
33754
  const approvalGateway = new SocketApprovalGateway({
33440
33755
  resolveGatewaySocket,
@@ -33453,7 +33768,9 @@ async function main() {
33453
33768
  }), {
33454
33769
  log: (m) => process.stderr.write(`hostd: rollout-narration — ${m}
33455
33770
  `),
33456
- onMessageId: (rid, mid) => server?.persistNarrationMessageId(rid, mid)
33771
+ onMessageId: (rid, mid) => server?.persistNarrationMessageId(rid, mid),
33772
+ escalate: (esc) => process.stderr.write(`hostd: rollout card escalation request=${esc.requestId} agent=${esc.agentName} ` + `staleMessageId=${esc.staleMessageId} reason=${esc.reason}` + `${esc.detail !== undefined ? ` detail=${esc.detail}` : ""} ` + `— card could not be restored, no operator card issued
33773
+ `)
33457
33774
  });
33458
33775
  const server = new HostdServer({
33459
33776
  homeDir: homedir8(),
@@ -33486,7 +33803,7 @@ async function main() {
33486
33803
  const autoRel = config.host_control?.auto_release_check;
33487
33804
  const watcherMode = resolveReleaseWatcherMode(config);
33488
33805
  if (watcherMode.mode !== "off") {
33489
- const eventsLog = join14(homedir8(), ".switchroom", "release-watcher-events.jsonl");
33806
+ const eventsLog = join13(homedir8(), ".switchroom", "release-watcher-events.jsonl");
33490
33807
  const log = (m) => {
33491
33808
  process.stderr.write(`hostd: ${m}
33492
33809
  `);
@@ -33545,7 +33862,7 @@ async function main() {
33545
33862
  `);
33546
33863
  } else
33547
33864
  notifier = new UpdateNotifier({
33548
- statePath: join14(homedir8(), ".switchroom", "release-notify-state.json"),
33865
+ statePath: join13(homedir8(), ".switchroom", "release-notify-state.json"),
33549
33866
  repostSuppressMs: cardTimeoutMs,
33550
33867
  isFleetMutationInFlight: () => server.isFleetMutationLocked(),
33551
33868
  startApply: (requestId) => {