switchroom 0.19.43 → 0.19.45

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 (29) hide show
  1. package/dist/agent-scheduler/index.js +4 -1
  2. package/dist/auth-broker/index.js +254 -119
  3. package/dist/cli/notion-write-pretool.mjs +4 -1
  4. package/dist/cli/switchroom.js +3995 -3196
  5. package/dist/host-control/main.js +270 -130
  6. package/dist/vault/approvals/kernel-server.js +201 -66
  7. package/dist/vault/broker/server.js +263 -128
  8. package/package.json +1 -1
  9. package/profiles/_base/start.sh.hbs +11 -4
  10. package/telegram-plugin/dist/gateway/gateway.js +945 -638
  11. package/telegram-plugin/flushed-turn-supersede.ts +190 -16
  12. package/telegram-plugin/gateway/cron-session.ts +31 -0
  13. package/telegram-plugin/gateway/gateway.ts +66 -65
  14. package/telegram-plugin/gateway/outbound-send-path.ts +60 -8
  15. package/telegram-plugin/gateway/reply-owner-wiring.ts +128 -0
  16. package/telegram-plugin/gateway/stream-render.ts +91 -1
  17. package/telegram-plugin/gateway/subagent-handback-marker.ts +49 -9
  18. package/telegram-plugin/gateway/subagent-reply-authority.ts +147 -0
  19. package/telegram-plugin/gateway/turn-end.ts +9 -1
  20. package/telegram-plugin/reply-owner-resolve.ts +102 -26
  21. package/telegram-plugin/tests/flushed-turn-supersede.test.ts +110 -8
  22. package/telegram-plugin/tests/narrative-lane-golden.test.ts +2 -0
  23. package/telegram-plugin/tests/reply-owner-resolve.test.ts +297 -27
  24. package/telegram-plugin/tests/reply-quote-wire.test.ts +2 -0
  25. package/telegram-plugin/tests/send-reply-golden.test.ts +567 -5
  26. package/telegram-plugin/tests/stream-render-golden.test.ts +204 -1
  27. package/vendor/hindsight-memory/scripts/lib/config.py +15 -0
  28. package/vendor/hindsight-memory/scripts/tests/test_retain_provenance_tag.py +135 -0
  29. package/vendor/hindsight-memory/settings.json +1 -1
@@ -21547,7 +21547,7 @@ var init_observation_scopes = __esm(() => {
21547
21547
  });
21548
21548
 
21549
21549
  // ../src/config/schema.ts
21550
- 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;
21550
+ 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;
21551
21551
  var init_schema = __esm(() => {
21552
21552
  init_zod();
21553
21553
  init_observation_scopes();
@@ -21664,6 +21664,7 @@ var init_schema = __esm(() => {
21664
21664
  }).optional();
21665
21665
  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).");
21666
21666
  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).");
21667
+ 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).");
21667
21668
  AgentMemorySchema = exports_external.object({
21668
21669
  collection: exports_external.string().describe("Hindsight collection name for this agent"),
21669
21670
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
@@ -21699,6 +21700,7 @@ var init_schema = __esm(() => {
21699
21700
  empathy: exports_external.number().int().min(1).max(5).optional().describe("How much the bank weights emotional/relational context (1-5; engine default 3).")
21700
21701
  }).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`)."),
21701
21702
  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)."),
21703
+ anti_confabulation_directive: AntiConfabulationDirectiveSchema,
21702
21704
  observation_scopes: ObservationScopesSchema,
21703
21705
  observation_scope_strategy: ObservationScopeStrategySchema,
21704
21706
  recall: exports_external.object({
@@ -21969,7 +21971,7 @@ var init_schema = __esm(() => {
21969
21971
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
21970
21972
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
21971
21973
  }).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."),
21972
- 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 \u2014 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 \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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), 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 \u2014 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 \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
21974
+ 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 \u2014 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 \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
21973
21975
  });
21974
21976
  MicrosoftWorkspaceConfigSchema = exports_external.object({
21975
21977
  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 \u2014 omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
@@ -22103,6 +22105,7 @@ var init_schema = __esm(() => {
22103
22105
  isolation: exports_external.enum(["default", "strict"]).optional(),
22104
22106
  profile: exports_external.string().optional(),
22105
22107
  directive_capture_nudge: exports_external.boolean().optional(),
22108
+ anti_confabulation_directive: AntiConfabulationDirectiveSchema,
22106
22109
  observation_scopes: ObservationScopesSchema,
22107
22110
  observation_scope_strategy: ObservationScopeStrategySchema,
22108
22111
  recall: exports_external.object({
@@ -30147,7 +30150,7 @@ var init_turn_flush_suppression = () => {};
30147
30150
 
30148
30151
  // ../src/agents/generation-stamp.ts
30149
30152
  import { createHash as createHash6 } from "node:crypto";
30150
- import { existsSync as existsSync32, readFileSync as readFileSync31, writeFileSync as writeFileSync28 } from "node:fs";
30153
+ import { existsSync as existsSync33, readFileSync as readFileSync32, writeFileSync as writeFileSync28 } from "node:fs";
30151
30154
  import { join as join41 } from "node:path";
30152
30155
  function sha256Text(s) {
30153
30156
  return createHash6("sha256").update(s, "utf-8").digest("hex");
@@ -30159,11 +30162,11 @@ function hashManagedClaudeMd(content3) {
30159
30162
  }
30160
30163
  function hashStampedFileOnDisk(agentDir, rel) {
30161
30164
  const p = join41(agentDir, rel);
30162
- if (!existsSync32(p))
30165
+ if (!existsSync33(p))
30163
30166
  return null;
30164
30167
  let content3;
30165
30168
  try {
30166
- content3 = readFileSync31(p, "utf-8");
30169
+ content3 = readFileSync32(p, "utf-8");
30167
30170
  } catch {
30168
30171
  return null;
30169
30172
  }
@@ -30171,10 +30174,10 @@ function hashStampedFileOnDisk(agentDir, rel) {
30171
30174
  }
30172
30175
  function readGenerationStamp(agentDir) {
30173
30176
  const p = join41(agentDir, GENERATION_STAMP_FILE);
30174
- if (!existsSync32(p))
30177
+ if (!existsSync33(p))
30175
30178
  return null;
30176
30179
  try {
30177
- const parsed = JSON.parse(readFileSync31(p, "utf-8"));
30180
+ const parsed = JSON.parse(readFileSync32(p, "utf-8"));
30178
30181
  if (parsed?.version !== 1 || typeof parsed.files !== "object" || parsed.files === null || Array.isArray(parsed.files)) {
30179
30182
  return null;
30180
30183
  }
@@ -35984,19 +35987,19 @@ function renderAuthLine(state7, agentName3, now = Date.now()) {
35984
35987
  }
35985
35988
 
35986
35989
  // gateway/quota-cache.ts
35987
- import { existsSync as existsSync44, readFileSync as readFileSync44, writeFileSync as writeFileSync37, mkdirSync as mkdirSync39 } from "fs";
35988
- import { join as join54, dirname as dirname22 } from "path";
35990
+ import { existsSync as existsSync45, readFileSync as readFileSync45, writeFileSync as writeFileSync37, mkdirSync as mkdirSync39 } from "fs";
35991
+ import { join as join53, dirname as dirname22 } from "path";
35989
35992
  function defaultCachePath() {
35990
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join54(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35993
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join53(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35991
35994
  }
35992
35995
  function readQuotaCache(opts = {}) {
35993
35996
  const path3 = opts.path ?? defaultCachePath();
35994
35997
  const now = opts.now ?? Date.now();
35995
- if (!existsSync44(path3))
35998
+ if (!existsSync45(path3))
35996
35999
  return null;
35997
36000
  let entry;
35998
36001
  try {
35999
- entry = JSON.parse(readFileSync44(path3, "utf8"));
36002
+ entry = JSON.parse(readFileSync45(path3, "utf8"));
36000
36003
  } catch {
36001
36004
  return null;
36002
36005
  }
@@ -36033,8 +36036,8 @@ var init_quota_cache = __esm(() => {
36033
36036
  });
36034
36037
 
36035
36038
  // gateway/boot-probes.ts
36036
- import { readFileSync as readFileSync45, readdirSync as readdirSync12, existsSync as existsSync45 } from "fs";
36037
- import { join as join55 } from "path";
36039
+ import { readFileSync as readFileSync46, readdirSync as readdirSync12, existsSync as existsSync46 } from "fs";
36040
+ import { join as join54 } from "path";
36038
36041
  import { execFile as execFileCb } from "child_process";
36039
36042
  import { promisify } from "util";
36040
36043
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -36076,11 +36079,11 @@ function mapPlan(billingType, hasExtra) {
36076
36079
  }
36077
36080
  async function probeAccount(agentDir) {
36078
36081
  return withTimeout("Account", (async () => {
36079
- const claudeDir = join55(agentDir, ".claude");
36080
- const claudeJsonPath = join55(claudeDir, ".claude.json");
36082
+ const claudeDir = join54(agentDir, ".claude");
36083
+ const claudeJsonPath = join54(claudeDir, ".claude.json");
36081
36084
  let cfg = {};
36082
36085
  try {
36083
- const raw = readFileSync45(claudeJsonPath, "utf8");
36086
+ const raw = readFileSync46(claudeJsonPath, "utf8");
36084
36087
  cfg = JSON.parse(raw);
36085
36088
  } catch {
36086
36089
  return { status: "fail", label: "Account", detail: "no .claude.json" };
@@ -36098,12 +36101,12 @@ async function probeAccount(agentDir) {
36098
36101
  let tokenStr = "";
36099
36102
  let status = "ok";
36100
36103
  for (const candidate of [
36101
- join55(claudeDir, ".oauth-token.meta.json"),
36102
- join55(claudeDir, "accounts", "default", ".oauth-token.meta.json")
36104
+ join54(claudeDir, ".oauth-token.meta.json"),
36105
+ join54(claudeDir, "accounts", "default", ".oauth-token.meta.json")
36103
36106
  ]) {
36104
- if (existsSync45(candidate)) {
36107
+ if (existsSync46(candidate)) {
36105
36108
  try {
36106
- const meta = JSON.parse(readFileSync45(candidate, "utf8"));
36109
+ const meta = JSON.parse(readFileSync46(candidate, "utf8"));
36107
36110
  if (meta.expiresAt) {
36108
36111
  tokenStr = " \u00b7 " + formatDaysFromNow(meta.expiresAt);
36109
36112
  const daysLeft = Math.round((meta.expiresAt - Date.now()) / 86400000);
@@ -36278,9 +36281,9 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
36278
36281
  if (!cgroup)
36279
36282
  return null;
36280
36283
  const procsPath = `/sys/fs/cgroup${cgroup}/cgroup.procs`;
36281
- if (!existsSync45(procsPath))
36284
+ if (!existsSync46(procsPath))
36282
36285
  return null;
36283
- const pidsRaw = readFileSync45(procsPath, "utf-8");
36286
+ const pidsRaw = readFileSync46(procsPath, "utf-8");
36284
36287
  const pids = pidsRaw.split(`
36285
36288
  `).map((s) => s.trim()).filter(Boolean);
36286
36289
  if (pids.length === 0)
@@ -36293,7 +36296,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
36293
36296
  let rss = 0;
36294
36297
  let comm = "";
36295
36298
  try {
36296
- const status = readFileSync45(`/proc/${pid}/status`, "utf-8");
36299
+ const status = readFileSync46(`/proc/${pid}/status`, "utf-8");
36297
36300
  const rssLine = status.split(`
36298
36301
  `).find((l) => l.startsWith("VmRSS:"));
36299
36302
  if (rssLine) {
@@ -36305,7 +36308,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
36305
36308
  continue;
36306
36309
  }
36307
36310
  try {
36308
- comm = readFileSync45(`/proc/${pid}/comm`, "utf-8").trim();
36311
+ comm = readFileSync46(`/proc/${pid}/comm`, "utf-8").trim();
36309
36312
  } catch {}
36310
36313
  candidates.push({ pid, rss, comm });
36311
36314
  }
@@ -36485,9 +36488,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
36485
36488
  let claudeDirForProbe = null;
36486
36489
  for (const candidate of [
36487
36490
  claudeConfigDir,
36488
- join55(claudeConfigDir, "accounts", "default")
36491
+ join54(claudeConfigDir, "accounts", "default")
36489
36492
  ]) {
36490
- if (existsSync45(join55(candidate, ".oauth-token"))) {
36493
+ if (existsSync46(join54(candidate, ".oauth-token"))) {
36491
36494
  claudeDirForProbe = candidate;
36492
36495
  break;
36493
36496
  }
@@ -36552,7 +36555,7 @@ async function probeHindsight(bankName, fetchImpl = fetch) {
36552
36555
  }
36553
36556
  function readContainerBootTimeMsForProbe() {
36554
36557
  try {
36555
- const stat1 = readFileSync45("/proc/1/stat", "utf8");
36558
+ const stat1 = readFileSync46("/proc/1/stat", "utf8");
36556
36559
  const lastParen = stat1.lastIndexOf(")");
36557
36560
  if (lastParen < 0)
36558
36561
  return null;
@@ -36560,7 +36563,7 @@ function readContainerBootTimeMsForProbe() {
36560
36563
  const starttimeTicks = Number(after[19]);
36561
36564
  if (!Number.isFinite(starttimeTicks))
36562
36565
  return null;
36563
- const procStat = readFileSync45("/proc/stat", "utf8");
36566
+ const procStat = readFileSync46("/proc/stat", "utf8");
36564
36567
  const btimeLine = procStat.split(`
36565
36568
  `).find((l) => l.startsWith("btime "));
36566
36569
  if (!btimeLine)
@@ -36658,7 +36661,7 @@ async function probeUds(label, socketPath, opts = {}) {
36658
36661
  }
36659
36662
  return withTimeout(label, (async () => {
36660
36663
  if (!opts.connectImpl) {
36661
- if (!existsSync45(socketPath)) {
36664
+ if (!existsSync46(socketPath)) {
36662
36665
  return {
36663
36666
  status: "fail",
36664
36667
  label,
@@ -36722,7 +36725,7 @@ async function probeSkills(agentDir, opts = {}) {
36722
36725
  return withTimeout("Skills", (async () => {
36723
36726
  const fs3 = opts.fs ?? realSkillsFs;
36724
36727
  const max = opts.maxNamesShown ?? 3;
36725
- const skillsDir = join55(agentDir, ".claude", "skills");
36728
+ const skillsDir = join54(agentDir, ".claude", "skills");
36726
36729
  if (!fs3.exists(skillsDir)) {
36727
36730
  return { status: "ok", label: "Skills", detail: "no skills dir" };
36728
36731
  }
@@ -36737,17 +36740,17 @@ async function probeSkills(agentDir, opts = {}) {
36737
36740
  }
36738
36741
  const dangling = [];
36739
36742
  for (const name of entries) {
36740
- const skillPath = join55(skillsDir, name);
36743
+ const skillPath = join54(skillsDir, name);
36741
36744
  if (!fs3.exists(skillPath)) {
36742
36745
  dangling.push(name);
36743
36746
  continue;
36744
36747
  }
36745
- const skillMd = join55(skillPath, "SKILL.md");
36748
+ const skillMd = join54(skillPath, "SKILL.md");
36746
36749
  if (!fs3.exists(skillMd) && !fs3.exists(skillPath + ".md")) {
36747
36750
  continue;
36748
36751
  }
36749
36752
  }
36750
- const overlayDir = opts.overlaySkillsDir ?? join55(agentDir, "skills.d");
36753
+ const overlayDir = opts.overlaySkillsDir ?? join54(agentDir, "skills.d");
36751
36754
  const overlaySlugs = new Set;
36752
36755
  if (fs3.exists(overlayDir)) {
36753
36756
  let overlayEntries = [];
@@ -36789,8 +36792,8 @@ function renderBucketedSkills(switchroom, agent) {
36789
36792
  }
36790
36793
  async function probeConnections(agentDir, opts = {}) {
36791
36794
  return withTimeout("Connections", (async () => {
36792
- const path3 = join55(agentDir, ".claude", "connection-health.json");
36793
- const read = opts.readFileImpl ?? ((p) => readFileSync45(p, "utf8"));
36795
+ const path3 = join54(agentDir, ".claude", "connection-health.json");
36796
+ const read = opts.readFileImpl ?? ((p) => readFileSync46(p, "utf8"));
36794
36797
  let issues = [];
36795
36798
  try {
36796
36799
  const parsed = JSON.parse(read(path3));
@@ -36830,7 +36833,7 @@ async function probeDrift(agentDir, opts = {}) {
36830
36833
  }
36831
36834
  } catch {}
36832
36835
  try {
36833
- const raw = readFileSync45(join55(agentDir, ".switchroom-drift.json"), "utf8");
36836
+ const raw = readFileSync46(join54(agentDir, ".switchroom-drift.json"), "utf8");
36834
36837
  const report = JSON.parse(raw);
36835
36838
  if (report?.version === 1 && Array.isArray(report.findings)) {
36836
36839
  const gen = report.generatedAt ? Date.parse(report.generatedAt) : NaN;
@@ -36877,24 +36880,24 @@ var init_boot_probes = __esm(() => {
36877
36880
  execFile = promisify(execFileCb);
36878
36881
  realProcFs = {
36879
36882
  readdir: (p) => readdirSync12(p),
36880
- readFile: (p) => readFileSync45(p, "utf-8")
36883
+ readFile: (p) => readFileSync46(p, "utf-8")
36881
36884
  };
36882
36885
  realSchedulerFs = {
36883
- readFile: (p) => readFileSync45(p, "utf-8"),
36886
+ readFile: (p) => readFileSync46(p, "utf-8"),
36884
36887
  mtimeMs: (p) => {
36885
36888
  const { statSync: statSync17 } = __require("fs");
36886
36889
  return statSync17(p).mtimeMs;
36887
36890
  },
36888
- exists: (p) => existsSync45(p)
36891
+ exists: (p) => existsSync46(p)
36889
36892
  };
36890
36893
  realSkillsFs = {
36891
36894
  readdir: (p) => readdirSync12(p),
36892
- exists: (p) => existsSync45(p)
36895
+ exists: (p) => existsSync46(p)
36893
36896
  };
36894
36897
  });
36895
36898
 
36896
36899
  // gateway/boot-issue-cache.ts
36897
- import { existsSync as existsSync46, readFileSync as readFileSync46, writeFileSync as writeFileSync38, mkdirSync as mkdirSync40, renameSync as renameSync22 } from "fs";
36900
+ import { existsSync as existsSync47, readFileSync as readFileSync47, writeFileSync as writeFileSync38, mkdirSync as mkdirSync40, renameSync as renameSync22 } from "fs";
36898
36901
  import { dirname as dirname23 } from "path";
36899
36902
  function fingerprintProbe(key, r) {
36900
36903
  if (r.status === "ok")
@@ -36974,11 +36977,11 @@ function diffProbes(probes, cache, opts = {}) {
36974
36977
  return out;
36975
36978
  }
36976
36979
  function loadCache(path3, now = Date.now) {
36977
- if (!existsSync46(path3))
36980
+ if (!existsSync47(path3))
36978
36981
  return { ...EMPTY_CACHE, probes: {} };
36979
36982
  let raw;
36980
36983
  try {
36981
- raw = readFileSync46(path3, "utf-8");
36984
+ raw = readFileSync47(path3, "utf-8");
36982
36985
  } catch {
36983
36986
  return { ...EMPTY_CACHE, probes: {} };
36984
36987
  }
@@ -37037,7 +37040,7 @@ var init_boot_issue_cache = __esm(() => {
37037
37040
 
37038
37041
  // gateway/config-snapshot.ts
37039
37042
  import { createHash as createHash7 } from "crypto";
37040
- import { existsSync as existsSync47, readFileSync as readFileSync47, writeFileSync as writeFileSync39, mkdirSync as mkdirSync41, renameSync as renameSync23 } from "fs";
37043
+ import { existsSync as existsSync48, readFileSync as readFileSync48, writeFileSync as writeFileSync39, mkdirSync as mkdirSync41, renameSync as renameSync23 } from "fs";
37041
37044
  import { dirname as dirname24 } from "path";
37042
37045
  function hashStringArray(items) {
37043
37046
  if (!items || items.length === 0)
@@ -37102,11 +37105,11 @@ function renderConfigChangeDim(dim) {
37102
37105
  }
37103
37106
  }
37104
37107
  function loadSnapshot(path3, now = Date.now) {
37105
- if (!existsSync47(path3))
37108
+ if (!existsSync48(path3))
37106
37109
  return null;
37107
37110
  let raw;
37108
37111
  try {
37109
- raw = readFileSync47(path3, "utf-8");
37112
+ raw = readFileSync48(path3, "utf-8");
37110
37113
  } catch {
37111
37114
  return null;
37112
37115
  }
@@ -37147,13 +37150,13 @@ var init_config_snapshot = __esm(() => {
37147
37150
  });
37148
37151
 
37149
37152
  // gateway/boot-card-msgid.ts
37150
- import { readFileSync as readFileSync48, writeFileSync as writeFileSync40 } from "node:fs";
37153
+ import { readFileSync as readFileSync49, writeFileSync as writeFileSync40 } from "node:fs";
37151
37154
  function bootCardChatKey(chatId, threadId) {
37152
37155
  return `${chatId}:${threadId ?? ""}`;
37153
37156
  }
37154
37157
  function readStore(path3) {
37155
37158
  try {
37156
- const parsed = JSON.parse(readFileSync48(path3, "utf8"));
37159
+ const parsed = JSON.parse(readFileSync49(path3, "utf8"));
37157
37160
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
37158
37161
  return parsed;
37159
37162
  }
@@ -37190,8 +37193,8 @@ __export(exports_boot_card, {
37190
37193
  readRoutingMode: () => readRoutingMode,
37191
37194
  containerBootStartMs: () => containerBootStartMs
37192
37195
  });
37193
- import { join as join56 } from "path";
37194
- import { readFileSync as readFileSync49, statSync as statSync17 } from "fs";
37196
+ import { join as join55 } from "path";
37197
+ import { readFileSync as readFileSync50, statSync as statSync17 } from "fs";
37195
37198
  function resolvePersonaName(slug, loadConfig3) {
37196
37199
  try {
37197
37200
  const config = loadConfig3 ? loadConfig3() : loadConfig();
@@ -37213,7 +37216,7 @@ function shouldSkipDuplicateBootCard(gate, site) {
37213
37216
  }
37214
37217
  return { skip: false };
37215
37218
  }
37216
- function containerBootStartMs(fsImpl = { readFileSync: readFileSync49 }) {
37219
+ function containerBootStartMs(fsImpl = { readFileSync: readFileSync50 }) {
37217
37220
  try {
37218
37221
  const btimeLine = fsImpl.readFileSync("/proc/stat", "utf-8").split(`
37219
37222
  `).find((l) => l.startsWith("btime "));
@@ -37234,8 +37237,8 @@ function containerBootStartMs(fsImpl = { readFileSync: readFileSync49 }) {
37234
37237
  return null;
37235
37238
  }
37236
37239
  }
37237
- function readRoutingMode(agentDir, bootStartMs, fsImpl = { readFileSync: readFileSync49, statSync: statSync17 }) {
37238
- const path3 = join56(agentDir, ".routing-mode");
37240
+ function readRoutingMode(agentDir, bootStartMs, fsImpl = { readFileSync: readFileSync50, statSync: statSync17 }) {
37241
+ const path3 = join55(agentDir, ".routing-mode");
37239
37242
  try {
37240
37243
  const raw = fsImpl.readFileSync(path3, "utf-8");
37241
37244
  const line = raw.split(`
@@ -37342,7 +37345,7 @@ function renderBootCard(opts) {
37342
37345
  return stackCardLines(flatLines);
37343
37346
  }
37344
37347
  async function runAllProbes(opts) {
37345
- const claudeDir = join56(opts.agentDir, ".claude");
37348
+ const claudeDir = join55(opts.agentDir, ".claude");
37346
37349
  const probes = {};
37347
37350
  const slug = opts.agentSlug ?? opts.agentName;
37348
37351
  await Promise.allSettled([
@@ -39140,7 +39143,7 @@ __export(exports_materialize_bot_token, {
39140
39143
  materializeBotToken: () => materializeBotToken,
39141
39144
  BotTokenMaterializeError: () => BotTokenMaterializeError
39142
39145
  });
39143
- import { existsSync as existsSync56 } from "node:fs";
39146
+ import { existsSync as existsSync57 } from "node:fs";
39144
39147
  function pickConfiguredToken(config, agentName3) {
39145
39148
  if (agentName3) {
39146
39149
  const agent = config.agents?.[agentName3];
@@ -39154,7 +39157,7 @@ function tryDirectVaultRead4(ref, config, passphrase) {
39154
39157
  if (!passphrase)
39155
39158
  return null;
39156
39159
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
39157
- if (!existsSync56(vaultPath))
39160
+ if (!existsSync57(vaultPath))
39158
39161
  return null;
39159
39162
  try {
39160
39163
  const secrets = openVault(passphrase, vaultPath);
@@ -39309,18 +39312,18 @@ var import_runner3 = __toESM(require_mod3(), 1);
39309
39312
  import { randomBytes as randomBytes13, createHash as createHash8 } from "crypto";
39310
39313
  import { execFileSync as execFileSync9, execSync as execSync2, spawn as spawn2 } from "child_process";
39311
39314
  import {
39312
- readFileSync as readFileSync60,
39315
+ readFileSync as readFileSync61,
39313
39316
  writeFileSync as writeFileSync50,
39314
39317
  mkdirSync as mkdirSync51,
39315
39318
  readdirSync as readdirSync16,
39316
39319
  rmSync as rmSync8,
39317
39320
  statSync as statSync23,
39318
39321
  renameSync as renameSync27,
39319
- realpathSync as realpathSync4,
39322
+ realpathSync as realpathSync5,
39320
39323
  chmodSync as chmodSync14,
39321
39324
  openSync as openSync15,
39322
39325
  closeSync as closeSync15,
39323
- existsSync as existsSync57,
39326
+ existsSync as existsSync58,
39324
39327
  unlinkSync as unlinkSync31,
39325
39328
  appendFileSync as appendFileSync9
39326
39329
  } from "fs";
@@ -39376,7 +39379,7 @@ function fsyncPathSync(path) {
39376
39379
 
39377
39380
  // gateway/gateway.ts
39378
39381
  import { homedir as homedir20 } from "os";
39379
- import { join as join69, sep as sep4, basename as basename17 } from "path";
39382
+ import { join as join68, sep as sep4, basename as basename17 } from "path";
39380
39383
 
39381
39384
  // plugin-logger.ts
39382
39385
  import { appendFileSync, mkdirSync, renameSync as renameSync2, statSync, existsSync } from "fs";
@@ -41866,7 +41869,8 @@ function normalizeForDedup(text) {
41866
41869
  }
41867
41870
 
41868
41871
  // flushed-turn-supersede.ts
41869
- var DEFAULT_SUPERSEDE_TTL_MS = 60000;
41872
+ var SUPERSEDE_OPEN_WINDOW_CAP_MS = 30 * 60000;
41873
+ var SUPERSEDE_COMPLETED_GRACE_MS = 60000;
41870
41874
  var SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS = 32;
41871
41875
  function normalizeForMatch(text) {
41872
41876
  return text.replace(/\s+/g, " ").trim();
@@ -41885,10 +41889,12 @@ function flushedAnswerMatchesReply(flushedText, replyText) {
41885
41889
  return false;
41886
41890
  }
41887
41891
  function decideSupersede(record, args) {
41888
- const ttlMs = args.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS;
41892
+ const openCapMs = args.openCapMs ?? SUPERSEDE_OPEN_WINDOW_CAP_MS;
41893
+ const completedGraceMs = args.completedGraceMs ?? SUPERSEDE_COMPLETED_GRACE_MS;
41889
41894
  if (record == null)
41890
41895
  return { supersede: false, deleteMessageIds: [], reason: "no-record" };
41891
- if (args.now - record.ts > ttlMs) {
41896
+ const expired = record.completedAt == null ? args.now - record.ts > openCapMs : args.now - record.completedAt > completedGraceMs;
41897
+ if (expired) {
41892
41898
  return { supersede: false, deleteMessageIds: [], reason: "expired" };
41893
41899
  }
41894
41900
  const sameTurn = record.turnId != null ? record.turnId === args.liveTurnId : args.liveTurnId == null;
@@ -41912,9 +41918,11 @@ function turnKey(turnId) {
41912
41918
 
41913
41919
  class FlushedTurnSupersedeRegistry {
41914
41920
  entries = new Map;
41915
- ttlMs;
41921
+ openCapMs;
41922
+ completedGraceMs;
41916
41923
  constructor(opts = {}) {
41917
- this.ttlMs = opts.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS;
41924
+ this.openCapMs = opts.openCapMs ?? SUPERSEDE_OPEN_WINDOW_CAP_MS;
41925
+ this.completedGraceMs = opts.completedGraceMs ?? SUPERSEDE_COMPLETED_GRACE_MS;
41918
41926
  }
41919
41927
  record(chatId, threadId, rec, now) {
41920
41928
  if (rec.messageIds.length === 0)
@@ -41930,9 +41938,18 @@ class FlushedTurnSupersedeRegistry {
41930
41938
  turnId: rec.turnId,
41931
41939
  messageIds: [...rec.messageIds],
41932
41940
  text: rec.text,
41933
- ts: now
41941
+ ts: now,
41942
+ completedAt: rec.completedAt ?? null
41934
41943
  });
41935
41944
  }
41945
+ completeAll(now) {
41946
+ for (const laneMap of this.entries.values()) {
41947
+ for (const rec of laneMap.values()) {
41948
+ if (rec.completedAt == null)
41949
+ rec.completedAt = now;
41950
+ }
41951
+ }
41952
+ }
41936
41953
  peek(chatId, threadId, args) {
41937
41954
  const rec = this.entries.get(makeKey2(chatId, threadId))?.get(turnKey(args.liveTurnId));
41938
41955
  return decideSupersede(rec, {
@@ -41940,7 +41957,8 @@ class FlushedTurnSupersedeRegistry {
41940
41957
  replyText: args.replyText,
41941
41958
  positiveAttribution: args.positiveAttribution,
41942
41959
  now: args.now,
41943
- ttlMs: this.ttlMs
41960
+ openCapMs: this.openCapMs,
41961
+ completedGraceMs: this.completedGraceMs
41944
41962
  });
41945
41963
  }
41946
41964
  take(chatId, threadId, args) {
@@ -41963,7 +41981,8 @@ class FlushedTurnSupersedeRegistry {
41963
41981
  sweep(now) {
41964
41982
  for (const [lane, laneMap] of this.entries) {
41965
41983
  for (const [tk, rec] of laneMap) {
41966
- if (now - rec.ts > this.ttlMs)
41984
+ const expired = rec.completedAt == null ? now - rec.ts > this.openCapMs : now - rec.completedAt > this.completedGraceMs;
41985
+ if (expired)
41967
41986
  laneMap.delete(tk);
41968
41987
  }
41969
41988
  if (laneMap.size === 0)
@@ -41982,6 +42001,302 @@ function makeKey2(chatId, threadId) {
41982
42001
  return threadId == null ? chatId : `${chatId}|${threadId}`;
41983
42002
  }
41984
42003
 
42004
+ // flushed-turn-supersede.ts
42005
+ var SUPERSEDE_OPEN_WINDOW_CAP_MS2 = 30 * 60000;
42006
+ var SUPERSEDE_COMPLETED_GRACE_MS2 = 60000;
42007
+ var SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 = 32;
42008
+ function normalizeForMatch2(text) {
42009
+ return text.replace(/\s+/g, " ").trim();
42010
+ }
42011
+ function flushedAnswerMatchesReply2(flushedText, replyText) {
42012
+ const flushed = normalizeForMatch2(flushedText);
42013
+ const reply = normalizeForMatch2(replyText);
42014
+ if (flushed.length === 0 || reply.length === 0)
42015
+ return false;
42016
+ if (flushed === reply)
42017
+ return true;
42018
+ if (reply.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && flushed.includes(reply))
42019
+ return true;
42020
+ if (flushed.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && reply.includes(flushed))
42021
+ return true;
42022
+ return false;
42023
+ }
42024
+ function decideSupersede2(record, args) {
42025
+ const openCapMs = args.openCapMs ?? SUPERSEDE_OPEN_WINDOW_CAP_MS2;
42026
+ const completedGraceMs = args.completedGraceMs ?? SUPERSEDE_COMPLETED_GRACE_MS2;
42027
+ if (record == null)
42028
+ return { supersede: false, deleteMessageIds: [], reason: "no-record" };
42029
+ const expired = record.completedAt == null ? args.now - record.ts > openCapMs : args.now - record.completedAt > completedGraceMs;
42030
+ if (expired) {
42031
+ return { supersede: false, deleteMessageIds: [], reason: "expired" };
42032
+ }
42033
+ const sameTurn = record.turnId != null ? record.turnId === args.liveTurnId : args.liveTurnId == null;
42034
+ if (!sameTurn) {
42035
+ return { supersede: false, deleteMessageIds: [], reason: "different-turn" };
42036
+ }
42037
+ if (!args.positiveAttribution && args.replyText != null && !flushedAnswerMatchesReply2(record.text, args.replyText)) {
42038
+ return { supersede: false, deleteMessageIds: [], reason: "new-content", recordText: record.text };
42039
+ }
42040
+ return {
42041
+ supersede: true,
42042
+ deleteMessageIds: [...record.messageIds],
42043
+ reason: "supersede",
42044
+ recordText: record.text
42045
+ };
42046
+ }
42047
+ function decideSupersedeCorrection(input) {
42048
+ const eligible = input.flushMessageIds.length === 1 && input.chunkCount === 1 && !input.hasFiles && !input.suppressText && !input.hasOpenPreview;
42049
+ if (eligible) {
42050
+ return { mode: "edit-in-place", editMessageId: input.flushMessageIds[0], deleteMessageIds: [] };
42051
+ }
42052
+ return { mode: "delete-resend", deleteMessageIds: [...input.flushMessageIds] };
42053
+ }
42054
+ var NULL_TURN_KEY2 = "<<null-turn>>";
42055
+ function turnKey2(turnId) {
42056
+ return turnId == null ? NULL_TURN_KEY2 : turnId;
42057
+ }
42058
+
42059
+ class FlushedTurnSupersedeRegistry2 {
42060
+ entries = new Map;
42061
+ openCapMs;
42062
+ completedGraceMs;
42063
+ constructor(opts = {}) {
42064
+ this.openCapMs = opts.openCapMs ?? SUPERSEDE_OPEN_WINDOW_CAP_MS2;
42065
+ this.completedGraceMs = opts.completedGraceMs ?? SUPERSEDE_COMPLETED_GRACE_MS2;
42066
+ }
42067
+ record(chatId, threadId, rec, now) {
42068
+ if (rec.messageIds.length === 0)
42069
+ return;
42070
+ this.sweep(now);
42071
+ const lane = makeKey3(chatId, threadId);
42072
+ let laneMap = this.entries.get(lane);
42073
+ if (laneMap == null) {
42074
+ laneMap = new Map;
42075
+ this.entries.set(lane, laneMap);
42076
+ }
42077
+ laneMap.set(turnKey2(rec.turnId), {
42078
+ turnId: rec.turnId,
42079
+ messageIds: [...rec.messageIds],
42080
+ text: rec.text,
42081
+ ts: now,
42082
+ completedAt: rec.completedAt ?? null
42083
+ });
42084
+ }
42085
+ completeAll(now) {
42086
+ for (const laneMap of this.entries.values()) {
42087
+ for (const rec of laneMap.values()) {
42088
+ if (rec.completedAt == null)
42089
+ rec.completedAt = now;
42090
+ }
42091
+ }
42092
+ }
42093
+ peek(chatId, threadId, args) {
42094
+ const rec = this.entries.get(makeKey3(chatId, threadId))?.get(turnKey2(args.liveTurnId));
42095
+ return decideSupersede2(rec, {
42096
+ liveTurnId: args.liveTurnId,
42097
+ replyText: args.replyText,
42098
+ positiveAttribution: args.positiveAttribution,
42099
+ now: args.now,
42100
+ openCapMs: this.openCapMs,
42101
+ completedGraceMs: this.completedGraceMs
42102
+ });
42103
+ }
42104
+ take(chatId, threadId, args) {
42105
+ const lane = makeKey3(chatId, threadId);
42106
+ const decision = this.peek(chatId, threadId, args);
42107
+ if (decision.supersede) {
42108
+ const laneMap = this.entries.get(lane);
42109
+ laneMap?.delete(turnKey2(args.liveTurnId));
42110
+ if (laneMap != null && laneMap.size === 0)
42111
+ this.entries.delete(lane);
42112
+ }
42113
+ return decision;
42114
+ }
42115
+ forget(chatId, threadId) {
42116
+ this.entries.delete(makeKey3(chatId, threadId));
42117
+ }
42118
+ clear() {
42119
+ this.entries.clear();
42120
+ }
42121
+ sweep(now) {
42122
+ for (const [lane, laneMap] of this.entries) {
42123
+ for (const [tk, rec] of laneMap) {
42124
+ const expired = rec.completedAt == null ? now - rec.ts > this.openCapMs : now - rec.completedAt > this.completedGraceMs;
42125
+ if (expired)
42126
+ laneMap.delete(tk);
42127
+ }
42128
+ if (laneMap.size === 0)
42129
+ this.entries.delete(lane);
42130
+ }
42131
+ }
42132
+ size(now) {
42133
+ this.sweep(now);
42134
+ let total = 0;
42135
+ for (const laneMap of this.entries.values())
42136
+ total += laneMap.size;
42137
+ return total;
42138
+ }
42139
+ }
42140
+ function makeKey3(chatId, threadId) {
42141
+ return threadId == null ? chatId : `${chatId}|${threadId}`;
42142
+ }
42143
+
42144
+ class FlushCompletionTracker {
42145
+ pending = [];
42146
+ open(turn) {
42147
+ turn.realEndObservedAt = null;
42148
+ if (!this.pending.includes(turn))
42149
+ this.pending.push(turn);
42150
+ }
42151
+ closeAll(now) {
42152
+ let closed = 0;
42153
+ for (const turn of this.pending) {
42154
+ if (turn.realEndObservedAt == null) {
42155
+ turn.realEndObservedAt = now;
42156
+ closed++;
42157
+ }
42158
+ }
42159
+ this.pending = [];
42160
+ return closed;
42161
+ }
42162
+ pendingCount() {
42163
+ return this.pending.length;
42164
+ }
42165
+ }
42166
+
42167
+ // reply-owner-resolve.ts
42168
+ function latestEndedAccepted(candidates) {
42169
+ if (candidates.latestEndedTurnId == null)
42170
+ return false;
42171
+ const age = candidates.latestEndedAgeMs;
42172
+ const ttl = candidates.latestEndedTtlMs;
42173
+ if (age == null || ttl == null)
42174
+ return false;
42175
+ const realEndAge = candidates.latestEndedRealEndAgeMs;
42176
+ if (realEndAge === undefined || realEndAge === null) {
42177
+ return age <= ttl;
42178
+ }
42179
+ const grace = candidates.latestEndedCompletedGraceMs ?? SUPERSEDE_COMPLETED_GRACE_MS2;
42180
+ return realEndAge <= grace;
42181
+ }
42182
+ function resolveReplyOwnerTier(candidates) {
42183
+ if (candidates.liveTurnId != null)
42184
+ return "live";
42185
+ if (candidates.originTurnId != null)
42186
+ return "origin";
42187
+ if (candidates.quotedTurnId != null)
42188
+ return "quoted";
42189
+ if (latestEndedAccepted(candidates))
42190
+ return "latest-ended";
42191
+ return "none";
42192
+ }
42193
+ function resolveReplyOwnerTurnId(candidates) {
42194
+ switch (resolveReplyOwnerTier(candidates)) {
42195
+ case "live":
42196
+ return candidates.liveTurnId;
42197
+ case "origin":
42198
+ return candidates.originTurnId;
42199
+ case "quoted":
42200
+ return candidates.quotedTurnId;
42201
+ case "latest-ended":
42202
+ return candidates.latestEndedTurnId;
42203
+ case "none":
42204
+ return null;
42205
+ }
42206
+ }
42207
+ function decideContentGateBypass(input) {
42208
+ if (input.tier === "live")
42209
+ return true;
42210
+ if (input.tier === "none")
42211
+ return false;
42212
+ if (input.handbackCouldOwnReply)
42213
+ return false;
42214
+ if (input.subagentCouldOwnReply !== false)
42215
+ return false;
42216
+ if (input.candidates == null)
42217
+ return false;
42218
+ if (!latestEndedAccepted(input.candidates))
42219
+ return false;
42220
+ return input.resolvedTurnId != null && input.resolvedTurnId === input.candidates.latestEndedTurnId;
42221
+ }
42222
+ function decideAnswerLatchSuppression(input) {
42223
+ if (input.superseded)
42224
+ return false;
42225
+ if (!input.replySubstantive)
42226
+ return false;
42227
+ if (!input.isLateReply)
42228
+ return false;
42229
+ if (input.replyMatchesFlushedAnswer === false)
42230
+ return false;
42231
+ return input.ownerAnswerDelivered === "flush";
42232
+ }
42233
+
42234
+ // gateway/reply-owner-wiring.ts
42235
+ var SUPERSEDE_OPEN_CAP_MS = (() => {
42236
+ const raw = Number(process.env.SWITCHROOM_SUPERSEDE_OPEN_CAP_MS);
42237
+ return Number.isFinite(raw) && raw > 0 ? raw : SUPERSEDE_OPEN_WINDOW_CAP_MS2;
42238
+ })();
42239
+ var SUPERSEDE_GRACE_MS = (() => {
42240
+ const raw = Number(process.env.SWITCHROOM_SUPERSEDE_GRACE_MS);
42241
+ return Number.isFinite(raw) && raw > 0 ? raw : SUPERSEDE_COMPLETED_GRACE_MS2;
42242
+ })();
42243
+ function resolveReplyOwnerTurnWith(lookups, liveTurn, chatId, args) {
42244
+ const origin = lookups.findTurnByOriginId(args.origin_turn_id);
42245
+ const quoted = lookups.findTurnByQuotedMessageId(chatId, args.reply_to);
42246
+ const latestEnded = lookups.findLatestTurnForChat(chatId, { endedOnly: true });
42247
+ const byId = new Map;
42248
+ for (const t of [latestEnded, quoted, origin, liveTurn]) {
42249
+ if (t != null)
42250
+ byId.set(t.turnId, t);
42251
+ }
42252
+ const latestEndedAgeMs = latestEnded?.endedAt != null ? lookups.now() - latestEnded.endedAt : null;
42253
+ const latestEndedRealEndAgeMs = latestEnded == null ? null : latestEnded.realEndObservedAt != null ? lookups.now() - latestEnded.realEndObservedAt : null;
42254
+ const candidates = {
42255
+ liveTurnId: liveTurn?.turnId ?? null,
42256
+ originTurnId: origin?.turnId ?? null,
42257
+ quotedTurnId: quoted?.turnId ?? null,
42258
+ latestEndedTurnId: latestEnded?.turnId ?? null,
42259
+ latestEndedAgeMs,
42260
+ latestEndedTtlMs: SUPERSEDE_OPEN_CAP_MS,
42261
+ latestEndedRealEndAgeMs,
42262
+ latestEndedCompletedGraceMs: SUPERSEDE_GRACE_MS
42263
+ };
42264
+ const tier = resolveReplyOwnerTier(candidates);
42265
+ const winnerId = resolveReplyOwnerTurnId(candidates);
42266
+ return { turn: winnerId != null ? byId.get(winnerId) ?? null : null, tier, candidates };
42267
+ }
42268
+
42269
+ // gateway/subagent-reply-authority.ts
42270
+ var SUB_AGENT_KIND_PREFIX = "sub_agent_";
42271
+
42272
+ class SubagentReplyAuthority {
42273
+ live = new Set;
42274
+ noteSessionEvent(ev) {
42275
+ if (ev == null)
42276
+ return;
42277
+ const kind = ev.kind;
42278
+ if (typeof kind !== "string" || !kind.startsWith(SUB_AGENT_KIND_PREFIX))
42279
+ return;
42280
+ const agentId = ev.agentId;
42281
+ if (typeof agentId !== "string" || agentId === "")
42282
+ return;
42283
+ if (kind === `${SUB_AGENT_KIND_PREFIX}turn_end`)
42284
+ this.live.delete(agentId);
42285
+ else
42286
+ this.live.add(agentId);
42287
+ }
42288
+ subagentCouldOwnReply() {
42289
+ return this.live.size > 0;
42290
+ }
42291
+ reset() {
42292
+ this.live.clear();
42293
+ }
42294
+ liveCount() {
42295
+ return this.live.size;
42296
+ }
42297
+ }
42298
+ var subagentReplyAuthority = new SubagentReplyAuthority;
42299
+
41985
42300
  // gateway/inbound-coalesce.ts
41986
42301
  function createInboundCoalescer(opts) {
41987
42302
  const buffer = new Map;
@@ -53951,11 +54266,11 @@ function createHandbackPreturnSignal(deps) {
53951
54266
  }
53952
54267
  return false;
53953
54268
  },
53954
- isPreTurnRecord(turnKey2) {
53955
- return turnKey2.startsWith(PRETURN_TURNKEY_PREFIX);
54269
+ isPreTurnRecord(turnKey3) {
54270
+ return turnKey3.startsWith(PRETURN_TURNKEY_PREFIX);
53956
54271
  },
53957
- handleReaped(turnKey2) {
53958
- const statusKey = bySyntheticKey.get(turnKey2);
54272
+ handleReaped(turnKey3) {
54273
+ const statusKey = bySyntheticKey.get(turnKey3);
53959
54274
  if (statusKey == null)
53960
54275
  return;
53961
54276
  const entry = byKey.get(statusKey);
@@ -71285,16 +71600,16 @@ function writeSilentEndState(args, deps) {
71285
71600
  `);
71286
71601
  }
71287
71602
  }
71288
- function clearSilentEndState(turnKey2, deps) {
71603
+ function clearSilentEndState(turnKey3, deps) {
71289
71604
  const statePath = resolveStatePath2(deps);
71290
71605
  if (!existsSync16(statePath))
71291
71606
  return;
71292
71607
  try {
71293
71608
  const prev = JSON.parse(readFileSync18(statePath, "utf8"));
71294
- if (prev.turnKey != null && prev.turnKey !== turnKey2)
71609
+ if (prev.turnKey != null && prev.turnKey !== turnKey3)
71295
71610
  return;
71296
71611
  unlinkSync12(statePath);
71297
- emitLog(deps, `silent-end: cleared state file turnKey=${turnKey2}
71612
+ emitLog(deps, `silent-end: cleared state file turnKey=${turnKey3}
71298
71613
  `);
71299
71614
  } catch {}
71300
71615
  }
@@ -75369,12 +75684,12 @@ class PendingUserNoticeGate {
75369
75684
  this.pending = this.pending.filter((p) => !(p.agent === notice.agent && p.key === notice.key));
75370
75685
  this.pending.push(notice);
75371
75686
  }
75372
- resolveTurnEnd(turnKey2, turnDeliveredReply, now = Date.now()) {
75687
+ resolveTurnEnd(turnKey3, turnDeliveredReply, now = Date.now()) {
75373
75688
  this.prune(now);
75374
75689
  const resolved = [];
75375
75690
  const remaining = [];
75376
75691
  for (const p of this.pending) {
75377
- if (p.key === turnKey2 || p.key === undefined)
75692
+ if (p.key === turnKey3 || p.key === undefined)
75378
75693
  resolved.push(p);
75379
75694
  else
75380
75695
  remaining.push(p);
@@ -78002,166 +78317,76 @@ ${input.newReplyText}`;
78002
78317
  };
78003
78318
  }
78004
78319
 
78005
- // flushed-turn-supersede.ts
78006
- var DEFAULT_SUPERSEDE_TTL_MS2 = 60000;
78007
- var SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 = 32;
78008
- function normalizeForMatch2(text4) {
78009
- return text4.replace(/\s+/g, " ").trim();
78010
- }
78011
- function flushedAnswerMatchesReply2(flushedText, replyText) {
78012
- const flushed = normalizeForMatch2(flushedText);
78013
- const reply = normalizeForMatch2(replyText);
78014
- if (flushed.length === 0 || reply.length === 0)
78320
+ // gateway/subagent-handback-marker.ts
78321
+ var INBOUND_SOURCE_CLASSIFICATION = {
78322
+ subagent_handback: { decoupledCompletion: true },
78323
+ cron: { decoupledCompletion: false },
78324
+ reaction: { decoupledCompletion: false },
78325
+ subagent_progress: { decoupledCompletion: false },
78326
+ resume_interrupted: { decoupledCompletion: false },
78327
+ resume_deferred: { decoupledCompletion: false },
78328
+ resume_watchdog_timeout: { decoupledCompletion: false },
78329
+ vault_grant_approved: { decoupledCompletion: false },
78330
+ vault_grant_denied: { decoupledCompletion: false },
78331
+ vault_grant_timeout: { decoupledCompletion: false },
78332
+ vault_save_completed: { decoupledCompletion: false },
78333
+ vault_save_discarded: { decoupledCompletion: false },
78334
+ vault_save_failed: { decoupledCompletion: false },
78335
+ vault_save_timeout: { decoupledCompletion: false },
78336
+ secret_provided: { decoupledCompletion: false },
78337
+ secret_declined: { decoupledCompletion: false },
78338
+ secret_provide_failed: { decoupledCompletion: false },
78339
+ secret_request_timeout: { decoupledCompletion: false },
78340
+ mental_model_propose_timeout: { decoupledCompletion: false },
78341
+ bridge_dead_restart: { decoupledCompletion: false },
78342
+ obligation_represent: { decoupledCompletion: false },
78343
+ missed_approval_retry: { decoupledCompletion: false },
78344
+ skill_proposal_apply: { decoupledCompletion: false },
78345
+ warmup: { decoupledCompletion: false },
78346
+ mental_model_proposal_applied: { decoupledCompletion: false },
78347
+ mental_model_proposal_denied: { decoupledCompletion: false },
78348
+ mental_model_proposal_failed: { decoupledCompletion: false },
78349
+ webhook: { decoupledCompletion: false },
78350
+ linear: { decoupledCompletion: false }
78351
+ };
78352
+ function stampsHandbackMarker(source) {
78353
+ if (source == null)
78015
78354
  return false;
78016
- if (flushed === reply)
78017
- return true;
78018
- if (reply.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && flushed.includes(reply))
78019
- return true;
78020
- if (flushed.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && reply.includes(flushed))
78355
+ const known = INBOUND_SOURCE_CLASSIFICATION[source];
78356
+ if (known == null)
78021
78357
  return true;
78022
- return false;
78023
- }
78024
- function decideSupersede2(record, args) {
78025
- const ttlMs = args.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS2;
78026
- if (record == null)
78027
- return { supersede: false, deleteMessageIds: [], reason: "no-record" };
78028
- if (args.now - record.ts > ttlMs) {
78029
- return { supersede: false, deleteMessageIds: [], reason: "expired" };
78030
- }
78031
- const sameTurn = record.turnId != null ? record.turnId === args.liveTurnId : args.liveTurnId == null;
78032
- if (!sameTurn) {
78033
- return { supersede: false, deleteMessageIds: [], reason: "different-turn" };
78034
- }
78035
- if (!args.positiveAttribution && args.replyText != null && !flushedAnswerMatchesReply2(record.text, args.replyText)) {
78036
- return { supersede: false, deleteMessageIds: [], reason: "new-content", recordText: record.text };
78037
- }
78038
- return {
78039
- supersede: true,
78040
- deleteMessageIds: [...record.messageIds],
78041
- reason: "supersede",
78042
- recordText: record.text
78043
- };
78044
- }
78045
- function decideSupersedeCorrection(input) {
78046
- const eligible = input.flushMessageIds.length === 1 && input.chunkCount === 1 && !input.hasFiles && !input.suppressText && !input.hasOpenPreview;
78047
- if (eligible) {
78048
- return { mode: "edit-in-place", editMessageId: input.flushMessageIds[0], deleteMessageIds: [] };
78049
- }
78050
- return { mode: "delete-resend", deleteMessageIds: [...input.flushMessageIds] };
78051
- }
78052
- var NULL_TURN_KEY2 = "<<null-turn>>";
78053
- function turnKey2(turnId) {
78054
- return turnId == null ? NULL_TURN_KEY2 : turnId;
78358
+ return known.decoupledCompletion;
78055
78359
  }
78360
+ var HANDBACK_RECENCY_WINDOW_MS = 60000;
78361
+ var MAIN_THREAD_KEY = "<main>";
78056
78362
 
78057
- class FlushedTurnSupersedeRegistry2 {
78058
- entries = new Map;
78059
- ttlMs;
78060
- constructor(opts = {}) {
78061
- this.ttlMs = opts.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS2;
78062
- }
78063
- record(chatId, threadId, rec, now) {
78064
- if (rec.messageIds.length === 0)
78065
- return;
78066
- this.sweep(now);
78067
- const lane = makeKey3(chatId, threadId);
78068
- let laneMap = this.entries.get(lane);
78069
- if (laneMap == null) {
78070
- laneMap = new Map;
78071
- this.entries.set(lane, laneMap);
78072
- }
78073
- laneMap.set(turnKey2(rec.turnId), {
78074
- turnId: rec.turnId,
78075
- messageIds: [...rec.messageIds],
78076
- text: rec.text,
78077
- ts: now
78078
- });
78079
- }
78080
- peek(chatId, threadId, args) {
78081
- const rec = this.entries.get(makeKey3(chatId, threadId))?.get(turnKey2(args.liveTurnId));
78082
- return decideSupersede2(rec, {
78083
- liveTurnId: args.liveTurnId,
78084
- replyText: args.replyText,
78085
- positiveAttribution: args.positiveAttribution,
78086
- now: args.now,
78087
- ttlMs: this.ttlMs
78088
- });
78363
+ class SubagentHandbackMarker {
78364
+ byChat = new Map;
78365
+ threadKey(threadId) {
78366
+ return threadId == null ? MAIN_THREAD_KEY : String(threadId);
78089
78367
  }
78090
- take(chatId, threadId, args) {
78091
- const lane = makeKey3(chatId, threadId);
78092
- const decision = this.peek(chatId, threadId, args);
78093
- if (decision.supersede) {
78094
- const laneMap = this.entries.get(lane);
78095
- laneMap?.delete(turnKey2(args.liveTurnId));
78096
- if (laneMap != null && laneMap.size === 0)
78097
- this.entries.delete(lane);
78368
+ record(chatId, threadId, now) {
78369
+ let inner = this.byChat.get(chatId);
78370
+ if (inner == null) {
78371
+ inner = new Map;
78372
+ this.byChat.set(chatId, inner);
78098
78373
  }
78099
- return decision;
78100
- }
78101
- forget(chatId, threadId) {
78102
- this.entries.delete(makeKey3(chatId, threadId));
78103
- }
78104
- clear() {
78105
- this.entries.clear();
78374
+ inner.set(this.threadKey(threadId), now);
78106
78375
  }
78107
- sweep(now) {
78108
- for (const [lane, laneMap] of this.entries) {
78109
- for (const [tk, rec] of laneMap) {
78110
- if (now - rec.ts > this.ttlMs)
78111
- laneMap.delete(tk);
78112
- }
78113
- if (laneMap.size === 0)
78114
- this.entries.delete(lane);
78115
- }
78376
+ lastAt(chatId, threadId) {
78377
+ return this.byChat.get(chatId)?.get(this.threadKey(threadId)) ?? null;
78116
78378
  }
78117
- size(now) {
78118
- this.sweep(now);
78119
- let total = 0;
78120
- for (const laneMap of this.entries.values())
78121
- total += laneMap.size;
78122
- return total;
78379
+ lastAtInChat(chatId) {
78380
+ const inner = this.byChat.get(chatId);
78381
+ if (inner == null || inner.size === 0)
78382
+ return null;
78383
+ let max = -Infinity;
78384
+ for (const ts of inner.values())
78385
+ if (ts > max)
78386
+ max = ts;
78387
+ return max === -Infinity ? null : max;
78123
78388
  }
78124
78389
  }
78125
- function makeKey3(chatId, threadId) {
78126
- return threadId == null ? chatId : `${chatId}|${threadId}`;
78127
- }
78128
-
78129
- // reply-owner-resolve.ts
78130
- function latestEndedAccepted(candidates) {
78131
- if (candidates.latestEndedTurnId == null)
78132
- return false;
78133
- const age = candidates.latestEndedAgeMs;
78134
- const ttl = candidates.latestEndedTtlMs;
78135
- if (age === null)
78136
- return false;
78137
- if (age === undefined || ttl == null)
78138
- return true;
78139
- return age <= ttl;
78140
- }
78141
- function decideContentGateBypass(input) {
78142
- if (input.tier === "live")
78143
- return true;
78144
- if (input.tier === "none")
78145
- return false;
78146
- if (input.handbackCouldOwnReply)
78147
- return false;
78148
- if (input.candidates == null)
78149
- return false;
78150
- if (!latestEndedAccepted(input.candidates))
78151
- return false;
78152
- return input.resolvedTurnId != null && input.resolvedTurnId === input.candidates.latestEndedTurnId;
78153
- }
78154
- function decideAnswerLatchSuppression(input) {
78155
- if (input.superseded)
78156
- return false;
78157
- if (!input.replySubstantive)
78158
- return false;
78159
- if (!input.isLateReply)
78160
- return false;
78161
- if (input.replyMatchesFlushedAnswer === false)
78162
- return false;
78163
- return input.ownerAnswerDelivered === "flush";
78164
- }
78165
78390
 
78166
78391
  // telegraph.ts
78167
78392
  function deriveTelegraphTitle2(text4) {
@@ -78736,6 +78961,7 @@ async function sendReply(deps, req) {
78736
78961
  resolveThreadId,
78737
78962
  getLatestInboundMessageId: getLatestInboundMessageId2,
78738
78963
  getLastSubagentHandbackAt,
78964
+ subagentReplyAuthority: subagentReplyAuthority2,
78739
78965
  recordOutbound: recordOutbound2,
78740
78966
  emissionAuthorityFor,
78741
78967
  clearActivitySummary,
@@ -78810,7 +79036,10 @@ async function sendReply(deps, req) {
78810
79036
  }
78811
79037
  }
78812
79038
  let supersedeFlushIds = [];
78813
- {
79039
+ if (req.callerIsForeignSession === true) {
79040
+ process.stderr.write(`telegram gateway: reply: foreign-session caller \u2014 supersede/latch skipped (#4172) ` + `chatId=${chat_id}
79041
+ `);
79042
+ } else {
78814
79043
  const replyThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
78815
79044
  const {
78816
79045
  turn: ownerTurn,
@@ -78822,12 +79051,14 @@ async function sendReply(deps, req) {
78822
79051
  const gateThreadId = ownerTurn?.sessionThreadId ?? replyThreadId;
78823
79052
  const handbackAt = getLastSubagentHandbackAt(chat_id);
78824
79053
  const now = Date.now();
78825
- const handbackCouldOwnReply = handbackAt != null && ownerEndedAt != null && handbackAt > ownerEndedAt && now - handbackAt <= DEFAULT_SUPERSEDE_TTL_MS2;
79054
+ const handbackCouldOwnReply = handbackAt != null && ownerEndedAt != null && handbackAt > ownerEndedAt && now - handbackAt <= HANDBACK_RECENCY_WINDOW_MS;
79055
+ const subagentCouldOwnReply = subagentReplyAuthority2.subagentCouldOwnReply();
78826
79056
  const replyIsOwnAnswer = decideContentGateBypass({
78827
79057
  tier: ownerTier,
78828
79058
  resolvedTurnId,
78829
79059
  candidates: ownerCandidates,
78830
- handbackCouldOwnReply
79060
+ handbackCouldOwnReply,
79061
+ subagentCouldOwnReply
78831
79062
  });
78832
79063
  const decision = flushedTurnSupersede.take(chat_id, gateThreadId, { liveTurnId: resolvedTurnId, replyText: text4, positiveAttribution: replyIsOwnAnswer, now });
78833
79064
  if (decision.supersede) {
@@ -78853,7 +79084,7 @@ async function sendReply(deps, req) {
78853
79084
  replyMatchesFlushedAnswer
78854
79085
  });
78855
79086
  if (decision.reason === "new-content") {
78856
- process.stderr.write(`telegram gateway: reply: flush supersede declined \u2014 new content (#3429) ` + `chatId=${chat_id} ownerTurnId=${JSON.stringify(resolvedTurnId)} ` + `tier=${ownerTier} latestEnded=${JSON.stringify(ownerCandidates.latestEndedTurnId)} ` + `handbackInWindow=${handbackCouldOwnReply}; sending fresh
79087
+ process.stderr.write(`telegram gateway: reply: flush supersede declined \u2014 new content (#3429) ` + `chatId=${chat_id} ownerTurnId=${JSON.stringify(resolvedTurnId)} ` + `tier=${ownerTier} latestEnded=${JSON.stringify(ownerCandidates.latestEndedTurnId)} ` + `handbackInWindow=${handbackCouldOwnReply} subagentLive=${subagentCouldOwnReply}; sending fresh
78857
79088
  `);
78858
79089
  }
78859
79090
  if (suppressByLatch) {
@@ -80107,6 +80338,37 @@ function emitRuntimeMetric2(event) {
80107
80338
  captureEvent(event.kind, { ...event, ts: wrapped.ts });
80108
80339
  }
80109
80340
 
80341
+ // gateway/subagent-reply-authority.ts
80342
+ var SUB_AGENT_KIND_PREFIX2 = "sub_agent_";
80343
+
80344
+ class SubagentReplyAuthority2 {
80345
+ live = new Set;
80346
+ noteSessionEvent(ev) {
80347
+ if (ev == null)
80348
+ return;
80349
+ const kind = ev.kind;
80350
+ if (typeof kind !== "string" || !kind.startsWith(SUB_AGENT_KIND_PREFIX2))
80351
+ return;
80352
+ const agentId = ev.agentId;
80353
+ if (typeof agentId !== "string" || agentId === "")
80354
+ return;
80355
+ if (kind === `${SUB_AGENT_KIND_PREFIX2}turn_end`)
80356
+ this.live.delete(agentId);
80357
+ else
80358
+ this.live.add(agentId);
80359
+ }
80360
+ subagentCouldOwnReply() {
80361
+ return this.live.size > 0;
80362
+ }
80363
+ reset() {
80364
+ this.live.clear();
80365
+ }
80366
+ liveCount() {
80367
+ return this.live.size;
80368
+ }
80369
+ }
80370
+ var subagentReplyAuthority2 = new SubagentReplyAuthority2;
80371
+
80110
80372
  // gateway/feed-open-gate.ts
80111
80373
  function shouldEarlyOpenLiveness(input) {
80112
80374
  if (!input.enabled)
@@ -80835,6 +81097,12 @@ function discardParkedTurnStart(rawContent) {
80835
81097
  }
80836
81098
  return null;
80837
81099
  }
81100
+ var flushCompletionTracker = new FlushCompletionTracker;
81101
+ function closeFlushCompletionWindows(registry, now) {
81102
+ const closed = flushCompletionTracker.closeAll(now);
81103
+ registry?.completeAll?.(now);
81104
+ return closed;
81105
+ }
80838
81106
  function drainParkedTurnStartsForChat(deps, chatId, threadId) {
80839
81107
  const wantThread = envThreadIdNum(threadId);
80840
81108
  const drained = [];
@@ -80967,6 +81235,7 @@ function beginTurn(deps, ev) {
80967
81235
  answerDelivered: false,
80968
81236
  flushedAnswerText: null,
80969
81237
  endedAt: null,
81238
+ realEndObservedAt: null,
80970
81239
  firstPingAt: null,
80971
81240
  firstPingWasSubstantive: false,
80972
81241
  silentAnchorMessageId: null,
@@ -81022,6 +81291,7 @@ function beginTurn(deps, ev) {
81022
81291
  `);
81023
81292
  }
81024
81293
  startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
81294
+ closeFlushCompletionWindows(deps.flushedTurnSupersede, Date.now());
81025
81295
  setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
81026
81296
  scheduleEarlyLivenessOpen(next);
81027
81297
  process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
@@ -81160,6 +81430,7 @@ function handleSessionEvent(deps, ev) {
81160
81430
  const durationMs = ev.kind === "turn_end" ? ev.durationMs : undefined;
81161
81431
  idleTracker.noteEvent(ev.kind, Date.now(), durationMs);
81162
81432
  }
81433
+ subagentReplyAuthority2.noteSessionEvent(ev);
81163
81434
  switch (ev.kind) {
81164
81435
  case "enqueue": {
81165
81436
  const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
@@ -81472,6 +81743,7 @@ function handleSessionEvent(deps, ev) {
81472
81743
  return;
81473
81744
  }
81474
81745
  }
81746
+ closeFlushCompletionWindows(flushedTurnSupersede, Date.now());
81475
81747
  const turnEndBackstopTurn = getCurrentTurn();
81476
81748
  const turnEndBackstopKey = turnEndBackstopTurn != null ? statusKey(turnEndBackstopTurn.sessionChatId, turnEndBackstopTurn.sessionThreadId) : null;
81477
81749
  withTurnEndGateBackstop(turnEndBackstopKey, turnEndBackstopTurn, () => {
@@ -81645,6 +81917,9 @@ function handleSessionEvent(deps, ev) {
81645
81917
  const backstopCardMessageId = cardTakeover.wasEmitted && cardTakeover.turnKey != null ? getPinnedProgressCardMessageId?.(cardTakeover.turnKey) ?? null : null;
81646
81918
  const backstopCardTurnKey = cardTakeover.turnKey;
81647
81919
  const backstopTurnEndedAt = endCurrentTurnAtomic(turn, { deferRecord: true, deferObligationClose: true });
81920
+ if (ev.durationMs === -1) {
81921
+ flushCompletionTracker.open(turn);
81922
+ }
81648
81923
  preambleSuppressor.dropNow();
81649
81924
  {
81650
81925
  const tKey = statusKey(chatId, threadId);
@@ -81722,7 +81997,12 @@ function handleSessionEvent(deps, ev) {
81722
81997
  turn.landedUnconfirmed = delivery.landedUnconfirmed;
81723
81998
  outboundDedup.record(backstopChatId, backstopThreadId, capturedText, Date.now(), getCurrentTurn()?.registryKey ?? null);
81724
81999
  if (sentIds.length > 0) {
81725
- flushedTurnSupersede.record(backstopChatId, backstopThreadId, { turnId: turn.turnId, messageIds: sentIds, text: capturedText }, Date.now());
82000
+ flushedTurnSupersede.record(backstopChatId, backstopThreadId, {
82001
+ turnId: turn.turnId,
82002
+ messageIds: sentIds,
82003
+ text: capturedText,
82004
+ completedAt: turn.realEndObservedAt
82005
+ }, Date.now());
81726
82006
  }
81727
82007
  if (!delivered) {
81728
82008
  if (backstopCtrl)
@@ -82695,51 +82975,13 @@ function isTurnFlushSafetyEnabled(env = process.env) {
82695
82975
  return true;
82696
82976
  }
82697
82977
 
82698
- // reply-owner-resolve.ts
82699
- function latestEndedAccepted2(candidates) {
82700
- if (candidates.latestEndedTurnId == null)
82701
- return false;
82702
- const age = candidates.latestEndedAgeMs;
82703
- const ttl = candidates.latestEndedTtlMs;
82704
- if (age === null)
82705
- return false;
82706
- if (age === undefined || ttl == null)
82707
- return true;
82708
- return age <= ttl;
82709
- }
82710
- function resolveReplyOwnerTier(candidates) {
82711
- if (candidates.liveTurnId != null)
82712
- return "live";
82713
- if (candidates.originTurnId != null)
82714
- return "origin";
82715
- if (candidates.quotedTurnId != null)
82716
- return "quoted";
82717
- if (latestEndedAccepted2(candidates))
82718
- return "latest-ended";
82719
- return "none";
82720
- }
82721
- function resolveReplyOwnerTurnId(candidates) {
82722
- switch (resolveReplyOwnerTier(candidates)) {
82723
- case "live":
82724
- return candidates.liveTurnId;
82725
- case "origin":
82726
- return candidates.originTurnId;
82727
- case "quoted":
82728
- return candidates.quotedTurnId;
82729
- case "latest-ended":
82730
- return candidates.latestEndedTurnId;
82731
- case "none":
82732
- return null;
82733
- }
82734
- }
82735
-
82736
82978
  // gateway/subagent-handback-marker.ts
82737
- var MAIN_THREAD_KEY = "<main>";
82979
+ var MAIN_THREAD_KEY2 = "<main>";
82738
82980
 
82739
- class SubagentHandbackMarker {
82981
+ class SubagentHandbackMarker2 {
82740
82982
  byChat = new Map;
82741
82983
  threadKey(threadId) {
82742
- return threadId == null ? MAIN_THREAD_KEY : String(threadId);
82984
+ return threadId == null ? MAIN_THREAD_KEY2 : String(threadId);
82743
82985
  }
82744
82986
  record(chatId, threadId, now) {
82745
82987
  let inner = this.byChat.get(chatId);
@@ -85592,9 +85834,66 @@ async function discoverModels(agentName3, opts = {}) {
85592
85834
  }
85593
85835
 
85594
85836
  // ../src/agents/scaffold.ts
85595
- import { dirname as dirname18, isAbsolute, join as join44, relative, resolve as resolve8 } from "node:path";
85596
85837
  init_atomic();
85597
85838
 
85839
+ // ../src/util/shipped-assets.ts
85840
+ import { existsSync as existsSync32, readFileSync as readFileSync31, realpathSync as realpathSync2 } from "node:fs";
85841
+ import { dirname as dirname18, resolve as resolve7 } from "node:path";
85842
+ var FHS_SHARE_ROOTS = [
85843
+ "/usr/local/share/switchroom",
85844
+ "/usr/share/switchroom"
85845
+ ];
85846
+ var PROFILES_ASSET = {
85847
+ asset: "profiles",
85848
+ envVar: "SWITCHROOM_PROFILES_ROOT"
85849
+ };
85850
+ function orderedCandidates(spec, probe) {
85851
+ const out = [];
85852
+ const push2 = (path2, source) => {
85853
+ if (!out.some((c) => c.path === path2))
85854
+ out.push({ path: path2, source });
85855
+ };
85856
+ if (probe.bundleDir) {
85857
+ push2(resolve7(probe.bundleDir, "../..", spec.asset), "npm");
85858
+ push2(resolve7(probe.bundleDir, spec.asset), "image");
85859
+ }
85860
+ if (probe.execPath) {
85861
+ push2(resolve7(dirname18(probe.execPath), "../share/switchroom", spec.asset), "sea-sibling");
85862
+ }
85863
+ for (const root of FHS_SHARE_ROOTS) {
85864
+ push2(resolve7(root, spec.asset), "fhs");
85865
+ }
85866
+ return out;
85867
+ }
85868
+ function resolveShippedAsset(spec, probe) {
85869
+ const env = probe.env ?? process.env;
85870
+ const override = env[spec.envVar]?.trim();
85871
+ if (override) {
85872
+ const path2 = canonicalise(resolve7(override), probe);
85873
+ return { path: path2, candidates: [path2], source: "env" };
85874
+ }
85875
+ const exists = probe.exists ?? existsSync32;
85876
+ const candidates = orderedCandidates(spec, probe);
85877
+ for (const c of candidates) {
85878
+ if (exists(c.path)) {
85879
+ return {
85880
+ path: canonicalise(c.path, probe),
85881
+ candidates: candidates.map((x) => x.path),
85882
+ source: c.source
85883
+ };
85884
+ }
85885
+ }
85886
+ return { path: null, candidates: candidates.map((x) => x.path), source: "none" };
85887
+ }
85888
+ function canonicalise(path2, probe) {
85889
+ const realpath = probe.realpath ?? realpathSync2;
85890
+ try {
85891
+ return realpath(path2);
85892
+ } catch {
85893
+ return path2;
85894
+ }
85895
+ }
85896
+
85598
85897
  // ../src/agents/agent-uid.ts
85599
85898
  init_peercred();
85600
85899
 
@@ -85641,37 +85940,29 @@ var AUDIT_ROOT = join42(homedir11(), ".switchroom", "audit");
85641
85940
 
85642
85941
  // ../src/agents/profiles.ts
85643
85942
  var import_handlebars = __toESM(require_lib(), 1);
85644
- import { readFileSync as readFileSync32, writeFileSync as writeFileSync29, existsSync as existsSync33, readdirSync as readdirSync8, statSync as statSync13, copyFileSync, mkdirSync as mkdirSync32, realpathSync as realpathSync2 } from "node:fs";
85645
- import { resolve as resolve7, join as join43, sep as pathSep } from "node:path";
85646
- function resolveProfilesRoot() {
85647
- const envOverride = process.env.SWITCHROOM_PROFILES_ROOT?.trim();
85648
- if (envOverride) {
85649
- return resolve7(envOverride);
85650
- }
85651
- const candidates = [
85652
- resolve7(import.meta.dirname, "../../profiles"),
85653
- resolve7(import.meta.dirname, "profiles")
85654
- ];
85655
- for (const candidate of candidates) {
85656
- if (existsSync33(candidate)) {
85657
- return candidate;
85658
- }
85659
- }
85660
- return candidates[0];
85943
+ import { readFileSync as readFileSync33, writeFileSync as writeFileSync29, existsSync as existsSync34, readdirSync as readdirSync8, statSync as statSync13, copyFileSync, mkdirSync as mkdirSync32, realpathSync as realpathSync3 } from "node:fs";
85944
+ import { resolve as resolve8, join as join43, sep as pathSep } from "node:path";
85945
+ function resolveProfilesRootDetailed() {
85946
+ return resolveShippedAsset(PROFILES_ASSET, {
85947
+ bundleDir: import.meta.dirname,
85948
+ execPath: process.execPath
85949
+ });
85661
85950
  }
85662
- var PROFILES_ROOT = resolveProfilesRoot();
85951
+ var PROFILES_RESOLUTION = resolveProfilesRootDetailed();
85952
+ var PROFILES_ROOT = PROFILES_RESOLUTION.path ?? PROFILES_RESOLUTION.candidates[0];
85953
+ var PROFILES_ROOT_SEARCH = PROFILES_RESOLUTION.candidates;
85663
85954
  import_handlebars.default.registerHelper("json", (value) => {
85664
85955
  return new import_handlebars.default.SafeString(JSON.stringify(value, null, 2));
85665
85956
  });
85666
85957
  import_handlebars.default.registerHelper("isNumber", (value) => {
85667
85958
  return typeof value === "number" && Number.isFinite(value);
85668
85959
  });
85669
- var SHARED_FRAGMENTS_DIR = resolve7(PROFILES_ROOT, "_shared");
85960
+ var SHARED_FRAGMENTS_DIR = resolve8(PROFILES_ROOT, "_shared");
85670
85961
  var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
85671
85962
  for (const name of SHARED_FRAGMENTS) {
85672
85963
  const fragPath = join43(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
85673
- if (existsSync33(fragPath)) {
85674
- import_handlebars.default.registerPartial(name, readFileSync32(fragPath, "utf-8"));
85964
+ if (existsSync34(fragPath)) {
85965
+ import_handlebars.default.registerPartial(name, readFileSync33(fragPath, "utf-8"));
85675
85966
  }
85676
85967
  }
85677
85968
 
@@ -85874,7 +86165,8 @@ var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
85874
86165
  "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY",
85875
86166
  "HINDSIGHT_MCP_RECALL_BUDGET_MODE",
85876
86167
  "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT",
85877
- "HINDSIGHT_API_RETAIN_WALL_TIMEOUT"
86168
+ "HINDSIGHT_API_RETAIN_WALL_TIMEOUT",
86169
+ "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT"
85878
86170
  ]);
85879
86171
  var HINDSIGHT_WORKER_SLOT_TYPES = [
85880
86172
  "consolidation",
@@ -85969,6 +86261,15 @@ var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and
85969
86261
  ` + `- Hindsight's own errors, retries, backlogs, or internal state \u2014 the memory
85970
86262
  ` + ` system's self-reports are not memories.
85971
86263
  ` + `- Restatements of the user's current request or the task in progress.
86264
+ ` + `- The assistant's own answers, summaries, recaps, or reflect output. Before
86265
+ ` + ` extracting, ask: does anything in this transcript support this claim OTHER
86266
+ ` + ` than the assistant having asserted it? If not, drop it \u2014 a model's own
86267
+ ` + ` synthesis re-extracted as a fact is how a guess becomes permanent.
86268
+ ` + ` Concretely, never produce a fact whose only support is the assistant
86269
+ ` + ` stating a date, a version, an attribution, a total, or a decision that the
86270
+ ` + ` user never confirmed and no tool output shows. An unverified claim recalls
86271
+ ` + ` later as though it had been established, which is worse than not
86272
+ ` + ` remembering it at all.
85972
86273
  ` + `- Volatile state written as a timeless assertion. A version, count, size,
85973
86274
  ` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
85974
86275
  ` + ` claim is true only at the instant it was said. Concretely, never produce a
@@ -86124,6 +86425,64 @@ var SUPERSEDED_RETAIN_MISSIONS = [
86124
86425
  ` + `
86125
86426
  ` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
86126
86427
  ` + `nothing durable remains, return an empty facts list.
86428
+ `,
86429
+ `Extract durable facts that will still be true and useful weeks from now, once this session is forgotten.
86430
+ ` + `
86431
+ ` + `DURABILITY GATE. Emit a candidate ONLY if it is one of these five classes:
86432
+ ` + `- PREFERENCE \u2014 what the user likes, wants, or always does; a standing rule or correction.
86433
+ ` + `- DECISION \u2014 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 \u2014 drop it unless it establishes a standing rule or permanently changes a system.
86434
+ ` + `- FINDING \u2014 a root cause, a measurement, or verified behaviour of a system. Include the number.
86435
+ ` + `- OUTCOME \u2014 a completed result that changed the world: what shipped, what a thing turned out to be. Not the act of shipping it.
86436
+ ` + `- RELATIONSHIP \u2014 who a person is, what a project or tool is, and how they connect.
86437
+ ` + `If a candidate fits none of the five, it is not a memory. Drop it; do not reword it into one.
86438
+ ` + `
86439
+ ` + `A preference revealed by a request is durable \u2014 record the preference (what the user likes, wants, or always does), not the request itself.
86440
+ ` + `
86441
+ ` + `A TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this
86442
+ ` + `candidate a file path, a command/process/agent/session id, a temp directory, or
86443
+ ` + `the location where some output was written? If yes, drop it \u2014 it is transcript
86444
+ ` + `exhaust, not memory.
86445
+ ` + `
86446
+ ` + `NEVER extract:
86447
+ ` + `- Tool results verbatim or paraphrased. Concretely, never produce a fact whose
86448
+ ` + ` text resembles any of these: "File created successfully at /path/to/file",
86449
+ ` + ` "A background command with ID bctz4yskm is running, and its output will be
86450
+ ` + ` written to /tmp/...", "Async agent a745598ba84e71df1 was launched successfully
86451
+ ` + ` and is running in the background", "User executed a Bash command to sleep for
86452
+ ` + ` 200 seconds", "The assistant used grep to locate 'truncateSync' in src/foo.ts".
86453
+ ` + `- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.
86454
+ ` + `- Agent tool-use traces or narration of what the assistant did (e.g. "the
86455
+ ` + ` assistant used X to query Y", "ran a search", "sent the message").
86456
+ ` + `- The act of delegating, dispatching, spawning, launching, steering or merging
86457
+ ` + ` work \u2014 including when it succeeded. "X was dispatched and completed" is the
86458
+ ` + ` session describing itself. Record only what the work LEARNED or CHANGED.
86459
+ ` + `- In-flight workflow/process narration (a sub-task started, paused, or is still
86460
+ ` + ` running) \u2014 retain the outcome only once the task completes or a decision is made.
86461
+ ` + `- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.
86462
+ ` + `- Slash commands the user typed and their effects (e.g. "User issued /clear to
86463
+ ` + ` reset assistant state").
86464
+ ` + `- Hindsight's own errors, retries, backlogs, or internal state \u2014 the memory
86465
+ ` + ` system's self-reports are not memories.
86466
+ ` + `- Restatements of the user's current request or the task in progress.
86467
+ ` + `- Volatile state written as a timeless assertion. A version, count, size,
86468
+ ` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
86469
+ ` + ` claim is true only at the instant it was said. Concretely, never produce a
86470
+ ` + ` fact whose text resembles any of these: "Switchroom fleet is running image
86471
+ ` + ` version v0.18.19", "The switchroom repo is at /path/to/fleet, version
86472
+ ` + ` v0.19.5", "Bank overlord has 43155 pending consolidations", "The build is
86473
+ ` + ` currently green". If the claim is worth keeping, put the date INSIDE the
86474
+ ` + ` fact text ("As of 2026-07-19 the fleet was running v0.18.19"); if you
86475
+ ` + ` cannot date it, drop it. An undated one is recalled forever as though it
86476
+ ` + ` were still true, which is worse than not remembering it at all.
86477
+ ` + `- Transient state (unread counts, build status, what is running right now) unless
86478
+ ` + ` the fact is explicitly dated, in which case record it as a dated observation.
86479
+ ` + `- Greetings, acknowledgements, and routine operational chatter.
86480
+ ` + `
86481
+ ` + `Write each fact so it stands alone: name the thing, the number, and the date. A
86482
+ ` + `sentence that only makes sense while reading this transcript is not durable.
86483
+ ` + `
86484
+ ` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
86485
+ ` + `nothing durable remains, return an empty facts list.
86127
86486
  `
86128
86487
  ];
86129
86488
  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.
@@ -86201,6 +86560,22 @@ var PROFILE_MEMORY_DEFAULTS = {
86201
86560
  var warnedMissingPool = new Set;
86202
86561
  var warnedMissingDefault = new Set;
86203
86562
 
86563
+ // ../src/memory/hindsight-retain-provenance.ts
86564
+ var RETAIN_PROVENANCE_TAG = "source:transcript";
86565
+ var RETAIN_TAGS_DEFAULT = Object.freeze([
86566
+ "{session_id}",
86567
+ RETAIN_PROVENANCE_TAG
86568
+ ]);
86569
+
86570
+ // ../src/memory/hindsight-seed-directives.ts
86571
+ var ANTI_CONFABULATION_DIRECTIVE = `When the retrieved memories do not support an answer, say so instead of composing one.
86572
+ ` + `
86573
+ ` + `- 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.
86574
+ ` + `- 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 \u2014 do not supply one.
86575
+ ` + `- Keep what the bank RECORDED separate from what you INFER. State inferences as inferences, and say which memory a recorded fact came from.
86576
+ ` + `- Treat a memory that was extracted from a conversation transcript as an unverified claim, not an established fact \u2014 it may be an assistant's own earlier guess. If it is the only support for an answer, say that it is.
86577
+ ` + "- Partial knowledge with its gaps named is more useful than a fluent answer that fills them in.";
86578
+
86204
86579
  // ../src/telegram/state.ts
86205
86580
  init_paths();
86206
86581
 
@@ -86238,7 +86613,6 @@ var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
86238
86613
  init_paths();
86239
86614
 
86240
86615
  // ../src/agents/scaffold.ts
86241
- var REPO_ROOT = resolve8(import.meta.dirname, "../..");
86242
86616
  var SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-5";
86243
86617
  var SWITCHROOM_DEFAULT_THINKING_EFFORT = "low";
86244
86618
  function resolveMainModel(model) {
@@ -86798,7 +87172,7 @@ init_overlay_loader();
86798
87172
  init_merge();
86799
87173
  init_timezone();
86800
87174
  var import_yaml4 = __toESM(require_dist(), 1);
86801
- import { readFileSync as readFileSync33, existsSync as existsSync34 } from "node:fs";
87175
+ import { readFileSync as readFileSync34, existsSync as existsSync35 } from "node:fs";
86802
87176
  import { homedir as homedir12 } from "node:os";
86803
87177
  import { resolve as resolve9 } from "node:path";
86804
87178
 
@@ -86874,7 +87248,7 @@ function findConfigFile2(startDir) {
86874
87248
  resolve9(userDir, "clerk.yml")
86875
87249
  ].filter(Boolean);
86876
87250
  for (const path2 of searchPaths) {
86877
- if (existsSync34(path2)) {
87251
+ if (existsSync35(path2)) {
86878
87252
  return path2;
86879
87253
  }
86880
87254
  }
@@ -86882,12 +87256,12 @@ function findConfigFile2(startDir) {
86882
87256
  }
86883
87257
  function loadConfig2(configPath) {
86884
87258
  const filePath = configPath ?? findConfigFile2();
86885
- if (!existsSync34(filePath)) {
87259
+ if (!existsSync35(filePath)) {
86886
87260
  throw new ConfigError2(`Config file not found: ${filePath}`);
86887
87261
  }
86888
87262
  let raw;
86889
87263
  try {
86890
- raw = readFileSync33(filePath, "utf-8");
87264
+ raw = readFileSync34(filePath, "utf-8");
86891
87265
  } catch (err) {
86892
87266
  throw new ConfigError2(`Failed to read config file: ${filePath}`, [
86893
87267
  ` ${err.message}`
@@ -87417,15 +87791,15 @@ function topicForRecipient(args) {
87417
87791
  }
87418
87792
 
87419
87793
  // ../src/agents/perf.ts
87420
- import { existsSync as existsSync35, readFileSync as readFileSync34 } from "node:fs";
87794
+ import { existsSync as existsSync36, readFileSync as readFileSync35 } from "node:fs";
87421
87795
  function readTurnUsages(jsonlPath, lastN) {
87422
- if (!existsSync35(jsonlPath))
87796
+ if (!existsSync36(jsonlPath))
87423
87797
  return [];
87424
87798
  if (lastN <= 0)
87425
87799
  return [];
87426
87800
  let raw;
87427
87801
  try {
87428
- raw = readFileSync34(jsonlPath, "utf-8");
87802
+ raw = readFileSync35(jsonlPath, "utf-8");
87429
87803
  } catch {
87430
87804
  return [];
87431
87805
  }
@@ -87517,7 +87891,7 @@ function numField(obj, key) {
87517
87891
 
87518
87892
  // gateway/context-occupancy.ts
87519
87893
  import { mkdirSync as mkdirSync33, writeFileSync as writeFileSync30 } from "node:fs";
87520
- import { join as join45 } from "node:path";
87894
+ import { join as join44 } from "node:path";
87521
87895
  var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
87522
87896
  var TIGHT_FRACTION = 0.8;
87523
87897
  function buildContextOccupancy(occupancy, cap, now) {
@@ -87540,7 +87914,7 @@ function buildContextOccupancy(occupancy, cap, now) {
87540
87914
  }
87541
87915
  function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
87542
87916
  try {
87543
- const path2 = join45(stateDir, CONTEXT_OCCUPANCY_FILENAME);
87917
+ const path2 = join44(stateDir, CONTEXT_OCCUPANCY_FILENAME);
87544
87918
  (deps?.mkdir ?? ((p, o) => mkdirSync33(p, o)))(stateDir, { recursive: true });
87545
87919
  (deps?.writeFile ?? ((p, d) => writeFileSync30(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
87546
87920
  `);
@@ -87696,7 +88070,7 @@ function nextCompactNotify(state6, ev) {
87696
88070
  }
87697
88071
 
87698
88072
  // gateway/hostd-dispatch.ts
87699
- import { existsSync as existsSync36 } from "node:fs";
88073
+ import { existsSync as existsSync37 } from "node:fs";
87700
88074
  import { randomBytes as randomBytes10 } from "node:crypto";
87701
88075
  init_loader();
87702
88076
  var _hostdEnabled2;
@@ -87720,13 +88094,13 @@ function hostdSocketPath2(agentName3) {
87720
88094
  function hostdWillBeUsed2(agentName3) {
87721
88095
  if (!isHostdEnabled2())
87722
88096
  return false;
87723
- return existsSync36(hostdSocketPath2(agentName3));
88097
+ return existsSync37(hostdSocketPath2(agentName3));
87724
88098
  }
87725
88099
  async function tryHostdDispatch2(agentName3, req, timeoutMs = 5000) {
87726
88100
  if (!isHostdEnabled2())
87727
88101
  return "not-configured";
87728
88102
  const sockPath = hostdSocketPath2(agentName3);
87729
- if (!existsSync36(sockPath))
88103
+ if (!existsSync37(sockPath))
87730
88104
  return "not-configured";
87731
88105
  try {
87732
88106
  return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
@@ -87755,7 +88129,7 @@ async function pollHostdStatus(agentName3, targetRequestId, opts) {
87755
88129
  if (!isHostdEnabled2())
87756
88130
  return "not-configured";
87757
88131
  const sockPath = hostdSocketPath2(agentName3);
87758
- if (!existsSync36(sockPath))
88132
+ if (!existsSync37(sockPath))
87759
88133
  return "not-configured";
87760
88134
  const now = opts.now ?? Date.now;
87761
88135
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -88721,7 +89095,7 @@ function createGatewayStalePinSweeper(w) {
88721
89095
 
88722
89096
  // gateway/webhook-ingest-server.ts
88723
89097
  import net4 from "node:net";
88724
- import { chmodSync as chmodSync10, existsSync as existsSync37, unlinkSync as unlinkSync18 } from "node:fs";
89098
+ import { chmodSync as chmodSync10, existsSync as existsSync38, unlinkSync as unlinkSync18 } from "node:fs";
88725
89099
  var MAX_REQUEST_BYTES = 1024 * 1024;
88726
89100
  function fdOf(conn) {
88727
89101
  const handle = conn._handle;
@@ -88733,7 +89107,7 @@ function startWebhookIngestServer(opts) {
88733
89107
  const log = opts.log ?? ((s) => process.stderr.write(s));
88734
89108
  const allowed = new Set(opts.allowedUids);
88735
89109
  try {
88736
- if (existsSync37(opts.socketPath))
89110
+ if (existsSync38(opts.socketPath))
88737
89111
  unlinkSync18(opts.socketPath);
88738
89112
  } catch (err) {
88739
89113
  log(`webhook-ingest-server: could not unlink stale socket: ${err.message}
@@ -88826,7 +89200,7 @@ function startWebhookIngestServer(opts) {
88826
89200
  server.close();
88827
89201
  } catch {}
88828
89202
  try {
88829
- if (existsSync37(opts.socketPath))
89203
+ if (existsSync38(opts.socketPath))
88830
89204
  unlinkSync18(opts.socketPath);
88831
89205
  } catch {}
88832
89206
  }
@@ -88835,12 +89209,12 @@ function startWebhookIngestServer(opts) {
88835
89209
 
88836
89210
  // ../src/web/webhook-gateway-record.ts
88837
89211
  import { appendFileSync as appendFileSync8, mkdirSync as mkdirSync36 } from "fs";
88838
- import { join as join49 } from "path";
89212
+ import { join as join48 } from "path";
88839
89213
  import { homedir as homedir14 } from "os";
88840
89214
 
88841
89215
  // ../src/web/webhook-handler.ts
88842
- import { appendFileSync as appendFileSync7, existsSync as existsSync39, mkdirSync as mkdirSync34, readFileSync as readFileSync35, writeFileSync as writeFileSync31 } from "fs";
88843
- import { join as join47 } from "path";
89216
+ import { appendFileSync as appendFileSync7, existsSync as existsSync40, mkdirSync as mkdirSync34, readFileSync as readFileSync36, writeFileSync as writeFileSync31 } from "fs";
89217
+ import { join as join46 } from "path";
88844
89218
 
88845
89219
  // ../src/util/log-rotation.ts
88846
89220
  import * as fs2 from "node:fs";
@@ -89178,9 +89552,9 @@ var DEDUP_MAX = 1000;
89178
89552
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
89179
89553
  function loadDedupFile(path3) {
89180
89554
  try {
89181
- if (!existsSync39(path3))
89555
+ if (!existsSync40(path3))
89182
89556
  return {};
89183
- const raw = JSON.parse(readFileSync35(path3, "utf-8"));
89557
+ const raw = JSON.parse(readFileSync36(path3, "utf-8"));
89184
89558
  return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
89185
89559
  } catch {
89186
89560
  return {};
@@ -89202,8 +89576,8 @@ var agentDedupCache = new Map;
89202
89576
  function createFileDedupStore(resolveAgentDir) {
89203
89577
  return {
89204
89578
  check(agent, deliveryId, now) {
89205
- const telegramDir = join47(resolveAgentDir(agent), "telegram");
89206
- const filePath = join47(telegramDir, "webhook-dedup.json");
89579
+ const telegramDir = join46(resolveAgentDir(agent), "telegram");
89580
+ const filePath = join46(telegramDir, "webhook-dedup.json");
89207
89581
  if (!agentDedupCache.has(agent)) {
89208
89582
  agentDedupCache.set(agent, loadDedupFile(filePath));
89209
89583
  }
@@ -89224,8 +89598,8 @@ var tokenBuckets = new Map;
89224
89598
  var throttleIssueWindow = new Map;
89225
89599
 
89226
89600
  // ../src/web/webhook-dispatch.ts
89227
- import { existsSync as existsSync40, mkdirSync as mkdirSync35, readFileSync as readFileSync36, writeFileSync as writeFileSync32 } from "fs";
89228
- import { join as join48 } from "path";
89601
+ import { existsSync as existsSync41, mkdirSync as mkdirSync35, readFileSync as readFileSync37, writeFileSync as writeFileSync32 } from "fs";
89602
+ import { join as join47 } from "path";
89229
89603
  import { homedir as homedir13 } from "os";
89230
89604
 
89231
89605
  // ../src/agent-scheduler/ipc-client.ts
@@ -89524,9 +89898,9 @@ function cooldownKey(source, eventType, repo, number, ruleIndex) {
89524
89898
  }
89525
89899
  function loadCooldownFile(path3) {
89526
89900
  try {
89527
- if (!existsSync40(path3))
89901
+ if (!existsSync41(path3))
89528
89902
  return {};
89529
- const raw = JSON.parse(readFileSync36(path3, "utf-8"));
89903
+ const raw = JSON.parse(readFileSync37(path3, "utf-8"));
89530
89904
  return typeof raw.dispatches === "object" && raw.dispatches !== null ? raw.dispatches : {};
89531
89905
  } catch {
89532
89906
  return {};
@@ -89545,8 +89919,8 @@ function createFileCooldownStore(resolveAgentDir) {
89545
89919
  isCoolingDown(agent, key, cooldownMs, now) {
89546
89920
  if (cooldownMs <= 0)
89547
89921
  return false;
89548
- const telegramDir = join48(resolveAgentDir(agent), "telegram");
89549
- const filePath = join48(telegramDir, "webhook-cooldown.json");
89922
+ const telegramDir = join47(resolveAgentDir(agent), "telegram");
89923
+ const filePath = join47(telegramDir, "webhook-cooldown.json");
89550
89924
  if (!cache.has(agent)) {
89551
89925
  cache.set(agent, loadCooldownFile(filePath));
89552
89926
  }
@@ -89603,9 +89977,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
89603
89977
  }
89604
89978
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
89605
89979
  const log = deps.log ?? ((s) => process.stderr.write(s));
89606
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join48(homedir13(), ".switchroom", "agents", a));
89980
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join47(homedir13(), ".switchroom", "agents", a));
89607
89981
  const now = (deps.now ?? Date.now)();
89608
- const socketPath = join48(resolveAgentDir(agent), "telegram", "gateway.sock");
89982
+ const socketPath = join47(resolveAgentDir(agent), "telegram", "gateway.sock");
89609
89983
  const inbound = {
89610
89984
  type: "inbound",
89611
89985
  chatId: ctx.chatId,
@@ -89677,7 +90051,7 @@ function evaluateDispatch(args, deps = {}) {
89677
90051
  const log = deps.log ?? ((s) => process.stderr.write(s));
89678
90052
  const now = (deps.now ?? Date.now)();
89679
90053
  const nowDate = deps.nowDate ?? (() => new Date(now));
89680
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join48(homedir13(), ".switchroom", "agents", a));
90054
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join47(homedir13(), ".switchroom", "agents", a));
89681
90055
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
89682
90056
  if (!DISPATCH_SOURCES.includes(args.source))
89683
90057
  return 0;
@@ -89755,10 +90129,10 @@ var CONSOLIDATION_COMPLETED_EVENT = "consolidation.completed";
89755
90129
  function recordWebhookEvent(rec, deps = {}) {
89756
90130
  const log = deps.log ?? ((s) => process.stderr.write(s));
89757
90131
  const now = rec.ts || (deps.now ?? Date.now)();
89758
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join49(homedir14(), ".switchroom", "agents", a));
90132
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join48(homedir14(), ".switchroom", "agents", a));
89759
90133
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
89760
90134
  const agent = rec.agent;
89761
- const telegramDir = join49(resolveAgentDir(agent), "telegram");
90135
+ const telegramDir = join48(resolveAgentDir(agent), "telegram");
89762
90136
  if (rec.source === "github" && rec.delivery_id) {
89763
90137
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
89764
90138
  if (originalTs !== undefined) {
@@ -89767,7 +90141,7 @@ function recordWebhookEvent(rec, deps = {}) {
89767
90141
  return { status: "deduped", ts: originalTs };
89768
90142
  }
89769
90143
  }
89770
- const logPath = join49(telegramDir, "webhook-events.jsonl");
90144
+ const logPath = join48(telegramDir, "webhook-events.jsonl");
89771
90145
  try {
89772
90146
  mkdirSync36(telegramDir, { recursive: true });
89773
90147
  rotateWebhookLogIfNeeded(logPath);
@@ -91047,76 +91421,6 @@ function buildDiffPreviewCard(input) {
91047
91421
  return { text: text4, reply_markup: kb };
91048
91422
  }
91049
91423
 
91050
- // gateway/subagent-handback-marker.ts
91051
- var INBOUND_SOURCE_CLASSIFICATION = {
91052
- subagent_handback: { decoupledCompletion: true },
91053
- cron: { decoupledCompletion: false },
91054
- reaction: { decoupledCompletion: false },
91055
- subagent_progress: { decoupledCompletion: false },
91056
- resume_interrupted: { decoupledCompletion: false },
91057
- resume_deferred: { decoupledCompletion: false },
91058
- resume_watchdog_timeout: { decoupledCompletion: false },
91059
- vault_grant_approved: { decoupledCompletion: false },
91060
- vault_grant_denied: { decoupledCompletion: false },
91061
- vault_grant_timeout: { decoupledCompletion: false },
91062
- vault_save_completed: { decoupledCompletion: false },
91063
- vault_save_discarded: { decoupledCompletion: false },
91064
- vault_save_failed: { decoupledCompletion: false },
91065
- vault_save_timeout: { decoupledCompletion: false },
91066
- secret_provided: { decoupledCompletion: false },
91067
- secret_declined: { decoupledCompletion: false },
91068
- secret_provide_failed: { decoupledCompletion: false },
91069
- secret_request_timeout: { decoupledCompletion: false },
91070
- mental_model_propose_timeout: { decoupledCompletion: false },
91071
- bridge_dead_restart: { decoupledCompletion: false },
91072
- obligation_represent: { decoupledCompletion: false },
91073
- missed_approval_retry: { decoupledCompletion: false },
91074
- skill_proposal_apply: { decoupledCompletion: false },
91075
- warmup: { decoupledCompletion: false },
91076
- mental_model_proposal_applied: { decoupledCompletion: false },
91077
- mental_model_proposal_denied: { decoupledCompletion: false },
91078
- mental_model_proposal_failed: { decoupledCompletion: false },
91079
- webhook: { decoupledCompletion: false },
91080
- linear: { decoupledCompletion: false }
91081
- };
91082
- function stampsHandbackMarker(source) {
91083
- if (source == null)
91084
- return false;
91085
- const known = INBOUND_SOURCE_CLASSIFICATION[source];
91086
- if (known == null)
91087
- return true;
91088
- return known.decoupledCompletion;
91089
- }
91090
- var MAIN_THREAD_KEY2 = "<main>";
91091
-
91092
- class SubagentHandbackMarker2 {
91093
- byChat = new Map;
91094
- threadKey(threadId) {
91095
- return threadId == null ? MAIN_THREAD_KEY2 : String(threadId);
91096
- }
91097
- record(chatId, threadId, now) {
91098
- let inner = this.byChat.get(chatId);
91099
- if (inner == null) {
91100
- inner = new Map;
91101
- this.byChat.set(chatId, inner);
91102
- }
91103
- inner.set(this.threadKey(threadId), now);
91104
- }
91105
- lastAt(chatId, threadId) {
91106
- return this.byChat.get(chatId)?.get(this.threadKey(threadId)) ?? null;
91107
- }
91108
- lastAtInChat(chatId) {
91109
- const inner = this.byChat.get(chatId);
91110
- if (inner == null || inner.size === 0)
91111
- return null;
91112
- let max = -Infinity;
91113
- for (const ts of inner.values())
91114
- if (ts > max)
91115
- max = ts;
91116
- return max === -Infinity ? null : max;
91117
- }
91118
- }
91119
-
91120
91424
  // gateway/pending-inbound-buffer.ts
91121
91425
  var DEFAULT_PENDING_INBOUND_CAP = 32;
91122
91426
  function redeliverBufferedInbound(buffer, agent, send, spool, onDelivered) {
@@ -91270,6 +91574,11 @@ function cronIdentity(agent) {
91270
91574
  function isCronIdentity(name) {
91271
91575
  return typeof name === "string" && name.endsWith(CRON_IDENTITY_SUFFIX);
91272
91576
  }
91577
+ function replyCallerIsForeignSession(name) {
91578
+ if (name == null || name === "")
91579
+ return true;
91580
+ return isCronIdentity(name);
91581
+ }
91273
91582
  function isCronInjectFire(meta) {
91274
91583
  return meta?.source === "cron" || meta?.session === "cron";
91275
91584
  }
@@ -93069,6 +93378,7 @@ function createTurnEndFunnel(deps) {
93069
93378
  endCurrentTurnForKey(turn, key);
93070
93379
  const turnEndedAt = Date.now();
93071
93380
  turn.endedAt = turnEndedAt;
93381
+ turn.realEndObservedAt = turnEndedAt;
93072
93382
  process.stderr.write(`telegram gateway: ${formatTurnLifecycle("clear", "turn_end", turn, turnEndedAt)}
93073
93383
  `);
93074
93384
  if (opts?.deferRecord !== true) {
@@ -94030,14 +94340,14 @@ function purgeStaleTurnsForChat(chatId, keys, purger, isStale = () => true) {
94030
94340
  // gateway/compaction-marker.ts
94031
94341
  import { statSync as statSync15, unlinkSync as unlinkSync21 } from "node:fs";
94032
94342
  import { homedir as homedir15 } from "node:os";
94033
- import { join as join50 } from "node:path";
94343
+ import { join as join49 } from "node:path";
94034
94344
  var COMPACTION_MARKER_FILE = "compaction-in-flight.json";
94035
94345
  function resolveTelegramStateDir() {
94036
- return process.env.TELEGRAM_STATE_DIR ?? join50(homedir15(), ".claude", "channels", "telegram");
94346
+ return process.env.TELEGRAM_STATE_DIR ?? join49(homedir15(), ".claude", "channels", "telegram");
94037
94347
  }
94038
94348
  function readCompactionMarkerAgeMs(stateDir, now) {
94039
94349
  try {
94040
- const st = statSync15(join50(stateDir, COMPACTION_MARKER_FILE));
94350
+ const st = statSync15(join49(stateDir, COMPACTION_MARKER_FILE));
94041
94351
  return (now ?? Date.now()) - st.mtimeMs;
94042
94352
  } catch {
94043
94353
  return null;
@@ -94045,7 +94355,7 @@ function readCompactionMarkerAgeMs(stateDir, now) {
94045
94355
  }
94046
94356
  function removeCompactionMarker(stateDir) {
94047
94357
  try {
94048
- unlinkSync21(join50(stateDir, COMPACTION_MARKER_FILE));
94358
+ unlinkSync21(join49(stateDir, COMPACTION_MARKER_FILE));
94049
94359
  } catch {}
94050
94360
  }
94051
94361
 
@@ -94867,26 +95177,26 @@ function skillProposalKeyboard(id) {
94867
95177
  // ../src/self-improve/skill-proposals.ts
94868
95178
  import {
94869
95179
  closeSync as closeSync9,
94870
- existsSync as existsSync41,
95180
+ existsSync as existsSync42,
94871
95181
  mkdirSync as mkdirSync37,
94872
95182
  openSync as openSync9,
94873
- readFileSync as readFileSync37,
95183
+ readFileSync as readFileSync38,
94874
95184
  writeSync as writeSync6
94875
95185
  } from "node:fs";
94876
- import { join as join51 } from "node:path";
95186
+ import { join as join50 } from "node:path";
94877
95187
  import { randomUUID as randomUUID5 } from "node:crypto";
94878
95188
  var PROPOSALS_FILE2 = "skill-proposals.jsonl";
94879
95189
  var REJECTED_FILE2 = "skill-proposals-rejected.jsonl";
94880
95190
  var REJECTION_TTL_MS2 = 90 * 24 * 60 * 60 * 1000;
94881
95191
  var PROPOSAL_SIM_THRESHOLD = 0.5;
94882
95192
  function proposalsPath2(stateDir) {
94883
- return join51(stateDir, PROPOSALS_FILE2);
95193
+ return join50(stateDir, PROPOSALS_FILE2);
94884
95194
  }
94885
95195
  function rejectedPath2(stateDir) {
94886
- return join51(stateDir, REJECTED_FILE2);
95196
+ return join50(stateDir, REJECTED_FILE2);
94887
95197
  }
94888
95198
  function ensureDir3(stateDir) {
94889
- if (!existsSync41(stateDir)) {
95199
+ if (!existsSync42(stateDir)) {
94890
95200
  mkdirSync37(stateDir, { recursive: true, mode: 493 });
94891
95201
  }
94892
95202
  }
@@ -94900,11 +95210,11 @@ function appendLine2(path3, obj) {
94900
95210
  }
94901
95211
  }
94902
95212
  function readLines2(path3, isValid2) {
94903
- if (!existsSync41(path3))
95213
+ if (!existsSync42(path3))
94904
95214
  return [];
94905
95215
  let raw;
94906
95216
  try {
94907
- raw = readFileSync37(path3, "utf-8");
95217
+ raw = readFileSync38(path3, "utf-8");
94908
95218
  } catch {
94909
95219
  return [];
94910
95220
  }
@@ -95501,7 +95811,7 @@ function escapeBody2(s) {
95501
95811
  }
95502
95812
 
95503
95813
  // gateway/pid-file.ts
95504
- import { writeFileSync as writeFileSync33, readFileSync as readFileSync38, unlinkSync as unlinkSync22, renameSync as renameSync18 } from "node:fs";
95814
+ import { writeFileSync as writeFileSync33, readFileSync as readFileSync39, unlinkSync as unlinkSync22, renameSync as renameSync18 } from "node:fs";
95505
95815
  function writePidFile(path3, record) {
95506
95816
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
95507
95817
  writeFileSync33(tmp, JSON.stringify(record), "utf-8");
@@ -95520,10 +95830,10 @@ import {
95520
95830
  writeFile as writeFileAsync,
95521
95831
  readFile as readFileAsync
95522
95832
  } from "node:fs/promises";
95523
- import { readFileSync as readFileSync39 } from "node:fs";
95833
+ import { readFileSync as readFileSync40 } from "node:fs";
95524
95834
  function readCurrentBootId() {
95525
95835
  try {
95526
- const stat = readFileSync39("/proc/1/stat", "utf-8");
95836
+ const stat = readFileSync40("/proc/1/stat", "utf-8");
95527
95837
  const lastParen = stat.lastIndexOf(")");
95528
95838
  if (lastParen < 0)
95529
95839
  return null;
@@ -95726,7 +96036,7 @@ function safeCount(fn) {
95726
96036
  }
95727
96037
 
95728
96038
  // gateway/session-marker.ts
95729
- import { writeFileSync as writeFileSync34, readFileSync as readFileSync40, renameSync as renameSync19, unlinkSync as unlinkSync23 } from "node:fs";
96039
+ import { writeFileSync as writeFileSync34, readFileSync as readFileSync41, renameSync as renameSync19, unlinkSync as unlinkSync23 } from "node:fs";
95730
96040
  function writeSessionMarker(path3, marker) {
95731
96041
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
95732
96042
  writeFileSync34(tmp, JSON.stringify(marker), "utf-8");
@@ -95734,7 +96044,7 @@ function writeSessionMarker(path3, marker) {
95734
96044
  }
95735
96045
  function readSessionMarker(path3) {
95736
96046
  try {
95737
- const raw = readFileSync40(path3, "utf-8");
96047
+ const raw = readFileSync41(path3, "utf-8");
95738
96048
  const parsed = JSON.parse(raw);
95739
96049
  if (typeof parsed.pid === "number" && typeof parsed.startedAtMs === "number" && Number.isFinite(parsed.pid) && Number.isFinite(parsed.startedAtMs)) {
95740
96050
  return { pid: parsed.pid, startedAtMs: parsed.startedAtMs };
@@ -95756,7 +96066,7 @@ function shouldFireRestartBanner(input) {
95756
96066
  }
95757
96067
 
95758
96068
  // gateway/clean-shutdown-marker.ts
95759
- import { writeFileSync as writeFileSync35, readFileSync as readFileSync41, renameSync as renameSync20, unlinkSync as unlinkSync24 } from "node:fs";
96069
+ import { writeFileSync as writeFileSync35, readFileSync as readFileSync42, renameSync as renameSync20, unlinkSync as unlinkSync24 } from "node:fs";
95760
96070
  var DEFAULT_MAX_AGE_MS = 60000;
95761
96071
  function writeCleanShutdownMarker(path3, marker) {
95762
96072
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
@@ -95765,7 +96075,7 @@ function writeCleanShutdownMarker(path3, marker) {
95765
96075
  }
95766
96076
  function readCleanShutdownMarker(path3) {
95767
96077
  try {
95768
- const raw = readFileSync41(path3, "utf-8");
96078
+ const raw = readFileSync42(path3, "utf-8");
95769
96079
  const parsed = JSON.parse(raw);
95770
96080
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.signal === "string" && parsed.signal.length > 0) {
95771
96081
  const out = { ts: parsed.ts, signal: parsed.signal };
@@ -96137,16 +96447,16 @@ function classifyAdminGate(text4, myAgentName) {
96137
96447
 
96138
96448
  // subagent-watcher.ts
96139
96449
  import {
96140
- existsSync as existsSync42,
96450
+ existsSync as existsSync43,
96141
96451
  openSync as openSync10,
96142
96452
  readSync as readSync2,
96143
96453
  statSync as statSync16,
96144
96454
  closeSync as closeSync10,
96145
96455
  watch,
96146
96456
  readdirSync as readdirSync10,
96147
- readFileSync as readFileSync42
96457
+ readFileSync as readFileSync43
96148
96458
  } from "fs";
96149
- import { join as join52 } from "path";
96459
+ import { join as join51 } from "path";
96150
96460
 
96151
96461
  // session-tail.ts
96152
96462
  function sanitizeCwdToProjectName2(cwd) {
@@ -96496,7 +96806,7 @@ function backfillJsonlAgentId(db3, jsonlPath, agentId, log) {
96496
96806
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
96497
96807
  let meta;
96498
96808
  try {
96499
- const raw = readFileSync42(metaPath, "utf8");
96809
+ const raw = readFileSync43(metaPath, "utf8");
96500
96810
  meta = JSON.parse(raw);
96501
96811
  } catch {
96502
96812
  log?.(`subagent-watcher: backfill skip ${agentId} \u2014 meta.json not readable at ${metaPath}`);
@@ -96951,7 +97261,7 @@ function startSubagentWatcher(config) {
96951
97261
  clearTimeout(ref.ref);
96952
97262
  });
96953
97263
  const fs3 = config.fs ?? {
96954
- existsSync: existsSync42,
97264
+ existsSync: existsSync43,
96955
97265
  readdirSync: readdirSync10,
96956
97266
  statSync: statSync16,
96957
97267
  openSync: openSync10,
@@ -97469,8 +97779,8 @@ function startSubagentWatcher(config) {
97469
97779
  if (stopped)
97470
97780
  return;
97471
97781
  pruneVanishedDirWatchers();
97472
- const claudeHome = join52(agentDir, ".claude");
97473
- const projectsRoot = join52(claudeHome, "projects");
97782
+ const claudeHome = join51(agentDir, ".claude");
97783
+ const projectsRoot = join51(claudeHome, "projects");
97474
97784
  if (!fs3.existsSync(projectsRoot))
97475
97785
  return;
97476
97786
  let projectDirs;
@@ -97504,7 +97814,7 @@ function startSubagentWatcher(config) {
97504
97814
  continue;
97505
97815
  }
97506
97816
  warnedForeignSlugs.delete(pDir);
97507
- const projectPath = join52(projectsRoot, pDir);
97817
+ const projectPath = join51(projectsRoot, pDir);
97508
97818
  let sessionDirs;
97509
97819
  try {
97510
97820
  sessionDirs = fs3.readdirSync(projectPath);
@@ -97514,7 +97824,7 @@ function startSubagentWatcher(config) {
97514
97824
  for (const sDir of sessionDirs) {
97515
97825
  if (sDir.endsWith(".jsonl"))
97516
97826
  continue;
97517
- const subagentsPath = join52(projectPath, sDir, "subagents");
97827
+ const subagentsPath = join51(projectPath, sDir, "subagents");
97518
97828
  if (!fs3.existsSync(subagentsPath))
97519
97829
  continue;
97520
97830
  const watchAndScan = (dirPath) => {
@@ -97523,7 +97833,7 @@ function startSubagentWatcher(config) {
97523
97833
  const w = fs3.watch(dirPath, (_event, filename) => {
97524
97834
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
97525
97835
  return;
97526
- const filePath = join52(dirPath, filename.toString());
97836
+ const filePath = join51(dirPath, filename.toString());
97527
97837
  if (!knownFiles.has(filePath)) {
97528
97838
  scanSubagentsDir(dirPath);
97529
97839
  }
@@ -97537,7 +97847,7 @@ function startSubagentWatcher(config) {
97537
97847
  scanSubagentsDir(dirPath);
97538
97848
  };
97539
97849
  watchAndScan(subagentsPath);
97540
- const workflowsPath = join52(subagentsPath, "workflows");
97850
+ const workflowsPath = join51(subagentsPath, "workflows");
97541
97851
  if (fs3.existsSync(workflowsPath)) {
97542
97852
  let wfDirs;
97543
97853
  try {
@@ -97547,7 +97857,7 @@ function startSubagentWatcher(config) {
97547
97857
  }
97548
97858
  for (const wfDir of wfDirs) {
97549
97859
  try {
97550
- const wfPath = join52(workflowsPath, wfDir);
97860
+ const wfPath = join51(workflowsPath, wfDir);
97551
97861
  if (!fs3.statSync(wfPath).isDirectory())
97552
97862
  continue;
97553
97863
  watchAndScan(wfPath);
@@ -97567,7 +97877,7 @@ function startSubagentWatcher(config) {
97567
97877
  for (const e of entries) {
97568
97878
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
97569
97879
  continue;
97570
- const filePath = join52(subagentsPath, e);
97880
+ const filePath = join51(subagentsPath, e);
97571
97881
  if (knownFiles.has(filePath))
97572
97882
  continue;
97573
97883
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -97697,19 +98007,19 @@ function startSubagentWatcher(config) {
97697
98007
  import {
97698
98008
  mkdirSync as mkdirSync38,
97699
98009
  writeFileSync as writeFileSync36,
97700
- readFileSync as readFileSync43,
98010
+ readFileSync as readFileSync44,
97701
98011
  readdirSync as readdirSync11,
97702
98012
  unlinkSync as unlinkSync25,
97703
- existsSync as existsSync43,
98013
+ existsSync as existsSync44,
97704
98014
  renameSync as renameSync21
97705
98015
  } from "node:fs";
97706
- import { join as join53, resolve as resolve10 } from "node:path";
98016
+ import { join as join52, resolve as resolve10 } from "node:path";
97707
98017
  import { homedir as homedir16 } from "node:os";
97708
98018
  function registryDir() {
97709
- return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join53(homedir16(), ".switchroom", "worktrees"));
98019
+ return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join52(homedir16(), ".switchroom", "worktrees"));
97710
98020
  }
97711
98021
  function recordPath(id) {
97712
- return join53(registryDir(), `${id}.json`);
98022
+ return join52(registryDir(), `${id}.json`);
97713
98023
  }
97714
98024
  function ensureDir4() {
97715
98025
  mkdirSync38(registryDir(), { recursive: true });
@@ -97725,7 +98035,7 @@ function writeRecord(record) {
97725
98035
  function readRecord(id) {
97726
98036
  const path3 = recordPath(id);
97727
98037
  try {
97728
- const raw = readFileSync43(path3, "utf8");
98038
+ const raw = readFileSync44(path3, "utf8");
97729
98039
  return JSON.parse(raw);
97730
98040
  } catch {
97731
98041
  return null;
@@ -97755,11 +98065,11 @@ function touchHeartbeat(id, onAfterRead) {
97755
98065
  writeRecord({ ...rec, heartbeatAt: new Date().toISOString() });
97756
98066
  }
97757
98067
  function recordExists(id) {
97758
- return existsSync43(recordPath(id));
98068
+ return existsSync44(recordPath(id));
97759
98069
  }
97760
98070
 
97761
98071
  // worktree-watch-cwds.ts
97762
- import { realpathSync as realpathSync3 } from "node:fs";
98072
+ import { realpathSync as realpathSync4 } from "node:fs";
97763
98073
  import { basename as basename13 } from "node:path";
97764
98074
  var identityEscalated = false;
97765
98075
  function defaultDeriveName(agentDir) {
@@ -97785,7 +98095,7 @@ function ownedWorktreeCwds(opts) {
97785
98095
  }
97786
98096
  return [];
97787
98097
  }
97788
- const rp = opts.realpath ?? realpathSync3;
98098
+ const rp = opts.realpath ?? realpathSync4;
97789
98099
  try {
97790
98100
  return opts.listRecords().filter((r) => r.ownerAgent === resolved).map((r) => {
97791
98101
  try {
@@ -97884,20 +98194,20 @@ function determineRestartReason(opts) {
97884
98194
  }
97885
98195
 
97886
98196
  // gateway/update-announce.ts
97887
- import { existsSync as existsSync48, mkdirSync as mkdirSync42, openSync as openSync11, closeSync as closeSync11, readFileSync as readFileSync50 } from "node:fs";
97888
- import { join as join58 } from "node:path";
98197
+ import { existsSync as existsSync49, mkdirSync as mkdirSync42, openSync as openSync11, closeSync as closeSync11, readFileSync as readFileSync51 } from "node:fs";
98198
+ import { join as join57 } from "node:path";
97889
98199
  import { homedir as homedir18 } from "node:os";
97890
98200
 
97891
98201
  // ../src/host-control/audit-reader.ts
97892
98202
  import { homedir as homedir17 } from "node:os";
97893
- import { join as join57 } from "node:path";
98203
+ import { join as join56 } from "node:path";
97894
98204
 
97895
98205
  // ../src/host-control/audit-rotation-config.ts
97896
98206
  var DEFAULT_HOSTD_AUDIT_MAX_BYTES = 32 * 1024 * 1024;
97897
98207
 
97898
98208
  // ../src/host-control/audit-reader.ts
97899
98209
  function defaultAuditLogPath(home2 = homedir17()) {
97900
- return join57(home2, ".switchroom", "host-control-audit.log");
98210
+ return join56(home2, ".switchroom", "host-control-audit.log");
97901
98211
  }
97902
98212
  var AUDIT_READ_WINDOW_BYTES = 4 * 1024 * 1024;
97903
98213
  function parseAuditLine(line) {
@@ -98031,8 +98341,8 @@ init_status_no_truncate();
98031
98341
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
98032
98342
  function readLastTerminalUpdateAudit(opts = {}) {
98033
98343
  const path3 = opts.auditLogPath ?? defaultAuditLogPath();
98034
- const exists = opts.exists ?? existsSync48;
98035
- const readFile = opts.readFile ?? ((p) => readFileSync50(p, "utf-8"));
98344
+ const exists = opts.exists ?? existsSync49;
98345
+ const readFile = opts.readFile ?? ((p) => readFileSync51(p, "utf-8"));
98036
98346
  if (!exists(path3))
98037
98347
  return null;
98038
98348
  let raw;
@@ -98096,15 +98406,15 @@ function renderUpdateOutcomeLine(entry) {
98096
98406
  `);
98097
98407
  }
98098
98408
  function claimUpdateAnnouncement(requestId, opts = {}) {
98099
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join58(homedir18(), ".switchroom");
98100
- const dir = join58(stateDir, "update-announced");
98409
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join57(homedir18(), ".switchroom");
98410
+ const dir = join57(stateDir, "update-announced");
98101
98411
  try {
98102
98412
  mkdirSync42(dir, { recursive: true });
98103
98413
  } catch {
98104
98414
  return false;
98105
98415
  }
98106
98416
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
98107
- const path3 = join58(dir, safeId);
98417
+ const path3 = join57(dir, safeId);
98108
98418
  try {
98109
98419
  const fd = openSync11(path3, "wx");
98110
98420
  closeSync11(fd);
@@ -98124,7 +98434,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
98124
98434
 
98125
98435
  // issues-card.ts
98126
98436
  init_card_format();
98127
- import { readFileSync as readFileSync51, writeFileSync as writeFileSync41 } from "node:fs";
98437
+ import { readFileSync as readFileSync52, writeFileSync as writeFileSync41 } from "node:fs";
98128
98438
  var SEVERITY_EMOJI = {
98129
98439
  info: "\u2139\ufe0f",
98130
98440
  warn: "\u26a0\ufe0f",
@@ -98216,7 +98526,7 @@ function extractRetryAfterSecs2(err) {
98216
98526
  var COOLDOWN_JITTER_MS2 = 500;
98217
98527
  function readPersistedMessageId(path3, log) {
98218
98528
  try {
98219
- const raw = readFileSync51(path3, "utf8");
98529
+ const raw = readFileSync52(path3, "utf8");
98220
98530
  const parsed = JSON.parse(raw);
98221
98531
  const v = parsed.messageId;
98222
98532
  if (typeof v === "number" && Number.isInteger(v) && v > 0)
@@ -98325,24 +98635,24 @@ function createIssuesCardHandle(opts) {
98325
98635
  }
98326
98636
 
98327
98637
  // issues-watcher.ts
98328
- import { existsSync as existsSync50, statSync as statSync19 } from "node:fs";
98329
- import { join as join60 } from "node:path";
98638
+ import { existsSync as existsSync51, statSync as statSync19 } from "node:fs";
98639
+ import { join as join59 } from "node:path";
98330
98640
 
98331
98641
  // ../src/issues/store.ts
98332
98642
  import {
98333
98643
  closeSync as closeSync12,
98334
- existsSync as existsSync49,
98644
+ existsSync as existsSync50,
98335
98645
  mkdirSync as mkdirSync43,
98336
98646
  openSync as openSync12,
98337
98647
  readdirSync as readdirSync13,
98338
- readFileSync as readFileSync52,
98648
+ readFileSync as readFileSync53,
98339
98649
  renameSync as renameSync24,
98340
98650
  statSync as statSync18,
98341
98651
  unlinkSync as unlinkSync26,
98342
98652
  writeFileSync as writeFileSync42,
98343
98653
  writeSync as writeSync7
98344
98654
  } from "node:fs";
98345
- import { join as join59 } from "node:path";
98655
+ import { join as join58 } from "node:path";
98346
98656
  import { randomBytes as randomBytes11 } from "node:crypto";
98347
98657
  import { execSync } from "node:child_process";
98348
98658
 
@@ -98361,12 +98671,12 @@ init_redact();
98361
98671
  var ISSUES_FILE = "issues.jsonl";
98362
98672
  var ISSUES_LOCK = "issues.lock";
98363
98673
  function readAll(stateDir) {
98364
- const path3 = join59(stateDir, ISSUES_FILE);
98365
- if (!existsSync49(path3))
98674
+ const path3 = join58(stateDir, ISSUES_FILE);
98675
+ if (!existsSync50(path3))
98366
98676
  return [];
98367
98677
  let raw;
98368
98678
  try {
98369
- raw = readFileSync52(path3, "utf-8");
98679
+ raw = readFileSync53(path3, "utf-8");
98370
98680
  } catch {
98371
98681
  return [];
98372
98682
  }
@@ -98398,7 +98708,7 @@ function list2(stateDir, opts = {}) {
98398
98708
  });
98399
98709
  }
98400
98710
  function resolve11(stateDir, fingerprint, nowFn = Date.now) {
98401
- if (!existsSync49(join59(stateDir, ISSUES_FILE)))
98711
+ if (!existsSync50(join58(stateDir, ISSUES_FILE)))
98402
98712
  return 0;
98403
98713
  return withLock(stateDir, () => {
98404
98714
  const all2 = readAll(stateDir);
@@ -98416,7 +98726,7 @@ function resolve11(stateDir, fingerprint, nowFn = Date.now) {
98416
98726
  });
98417
98727
  }
98418
98728
  function writeAll(stateDir, events) {
98419
- const path3 = join59(stateDir, ISSUES_FILE);
98729
+ const path3 = join58(stateDir, ISSUES_FILE);
98420
98730
  sweepOrphanTmpFiles(stateDir);
98421
98731
  const tmp = `${path3}.tmp-${process.pid}-${randomBytes11(4).toString("hex")}`;
98422
98732
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -98438,7 +98748,7 @@ function sweepOrphanTmpFiles(stateDir) {
98438
98748
  for (const entry of entries) {
98439
98749
  if (!entry.startsWith(TMP_PREFIX))
98440
98750
  continue;
98441
- const tmpPath2 = join59(stateDir, entry);
98751
+ const tmpPath2 = join58(stateDir, entry);
98442
98752
  try {
98443
98753
  const stat = statSync18(tmpPath2);
98444
98754
  if (stat.mtimeMs < cutoff) {
@@ -98450,7 +98760,7 @@ function sweepOrphanTmpFiles(stateDir) {
98450
98760
  var LOCK_RETRY_MS = 25;
98451
98761
  var LOCK_TIMEOUT_MS = 1e4;
98452
98762
  function withLock(stateDir, fn) {
98453
- const lockPath = join59(stateDir, ISSUES_LOCK);
98763
+ const lockPath = join58(stateDir, ISSUES_LOCK);
98454
98764
  const startedAt = Date.now();
98455
98765
  let fd = null;
98456
98766
  while (fd === null) {
@@ -98485,7 +98795,7 @@ function withLock(stateDir, fn) {
98485
98795
  function tryStealStaleLock(lockPath) {
98486
98796
  let pidStr;
98487
98797
  try {
98488
- pidStr = readFileSync52(lockPath, "utf-8").trim();
98798
+ pidStr = readFileSync53(lockPath, "utf-8").trim();
98489
98799
  } catch {
98490
98800
  return true;
98491
98801
  }
@@ -98535,7 +98845,7 @@ function isIssueEvent(v) {
98535
98845
  // issues-watcher.ts
98536
98846
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
98537
98847
  function startIssuesWatcher(opts) {
98538
- const path3 = join60(opts.stateDir, ISSUES_FILE);
98848
+ const path3 = join59(opts.stateDir, ISSUES_FILE);
98539
98849
  const log = opts.log ?? (() => {});
98540
98850
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
98541
98851
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -98583,7 +98893,7 @@ function startIssuesWatcher(opts) {
98583
98893
  };
98584
98894
  }
98585
98895
  function defaultSignatureProvider(path3) {
98586
- if (!existsSync50(path3))
98896
+ if (!existsSync51(path3))
98587
98897
  return null;
98588
98898
  try {
98589
98899
  const stat = statSync19(path3);
@@ -99295,7 +99605,7 @@ function readBashCommand(inputPreview) {
99295
99605
 
99296
99606
  // gateway/scoped-grant-store.ts
99297
99607
  init_atomic();
99298
- import { join as join61 } from "node:path";
99608
+ import { join as join60 } from "node:path";
99299
99609
 
99300
99610
  // scoped-approval.ts
99301
99611
  var SCOPED_APPROVAL_DEFAULT_TTL_MS2 = 30 * 60 * 1000;
@@ -99337,7 +99647,7 @@ function scopedGrantPersistEnabled(env = process.env) {
99337
99647
  return env.SWITCHROOM_SCOPED_GRANT_PERSIST !== "0";
99338
99648
  }
99339
99649
  function createScopedGrantStore(stateDir, env = process.env, log = (l) => process.stderr.write(l)) {
99340
- const filePath = join61(stateDir, "scoped-grants.json");
99650
+ const filePath = join60(stateDir, "scoped-grants.json");
99341
99651
  const enabled7 = scopedGrantPersistEnabled(env);
99342
99652
  let unreadable = false;
99343
99653
  function read() {
@@ -99715,8 +100025,8 @@ function isDiffPreApproved(agentName3, unifiedDiff, deps) {
99715
100025
 
99716
100026
  // credits-watch.ts
99717
100027
  init_card_format();
99718
- import { readFileSync as readFileSync53, writeFileSync as writeFileSync43, existsSync as existsSync51, mkdirSync as mkdirSync44 } from "fs";
99719
- import { join as join62 } from "path";
100028
+ import { readFileSync as readFileSync54, writeFileSync as writeFileSync43, existsSync as existsSync52, mkdirSync as mkdirSync44 } from "fs";
100029
+ import { join as join61 } from "path";
99720
100030
  var STATE_FILE = "credits-watch.json";
99721
100031
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
99722
100032
  var KNOWN_CREDIT_REASONS = [
@@ -99738,12 +100048,12 @@ function emptyCreditState() {
99738
100048
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
99739
100049
  }
99740
100050
  function readClaudeJsonOverage(claudeConfigDir) {
99741
- const path3 = join62(claudeConfigDir, ".claude.json");
99742
- if (!existsSync51(path3))
100051
+ const path3 = join61(claudeConfigDir, ".claude.json");
100052
+ if (!existsSync52(path3))
99743
100053
  return null;
99744
100054
  let raw;
99745
100055
  try {
99746
- raw = readFileSync53(path3, "utf-8");
100056
+ raw = readFileSync54(path3, "utf-8");
99747
100057
  } catch {
99748
100058
  return null;
99749
100059
  }
@@ -99821,11 +100131,11 @@ function humanizeReason(reason) {
99821
100131
  }
99822
100132
  }
99823
100133
  function loadCreditState(stateDir) {
99824
- const path3 = join62(stateDir, STATE_FILE);
99825
- if (!existsSync51(path3))
100134
+ const path3 = join61(stateDir, STATE_FILE);
100135
+ if (!existsSync52(path3))
99826
100136
  return emptyCreditState();
99827
100137
  try {
99828
- const raw = readFileSync53(path3, "utf-8");
100138
+ const raw = readFileSync54(path3, "utf-8");
99829
100139
  const parsed = JSON.parse(raw);
99830
100140
  if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
99831
100141
  return {
@@ -99838,7 +100148,7 @@ function loadCreditState(stateDir) {
99838
100148
  }
99839
100149
  function saveCreditState(stateDir, state7) {
99840
100150
  mkdirSync44(stateDir, { recursive: true });
99841
- const path3 = join62(stateDir, STATE_FILE);
100151
+ const path3 = join61(stateDir, STATE_FILE);
99842
100152
  writeFileSync43(path3, JSON.stringify(state7, null, 2) + `
99843
100153
  `, { mode: 384 });
99844
100154
  }
@@ -99846,8 +100156,8 @@ function saveCreditState(stateDir, state7) {
99846
100156
  // quota-watch.ts
99847
100157
  init_auth_snapshot_format();
99848
100158
  init_card_format();
99849
- import { readFileSync as readFileSync54, writeFileSync as writeFileSync44, existsSync as existsSync52, mkdirSync as mkdirSync45 } from "fs";
99850
- import { join as join63 } from "path";
100159
+ import { readFileSync as readFileSync55, writeFileSync as writeFileSync44, existsSync as existsSync53, mkdirSync as mkdirSync45 } from "fs";
100160
+ import { join as join62 } from "path";
99851
100161
  var STATE_FILE2 = "quota-watch.json";
99852
100162
  function emptyQuotaWatchState() {
99853
100163
  return {};
@@ -100085,11 +100395,11 @@ function buildRecoveryMessage(agentName3, snap) {
100085
100395
  `);
100086
100396
  }
100087
100397
  function loadQuotaWatchState(stateDir) {
100088
- const path3 = join63(stateDir, STATE_FILE2);
100089
- if (!existsSync52(path3))
100398
+ const path3 = join62(stateDir, STATE_FILE2);
100399
+ if (!existsSync53(path3))
100090
100400
  return emptyQuotaWatchState();
100091
100401
  try {
100092
- const raw = readFileSync54(path3, "utf-8");
100402
+ const raw = readFileSync55(path3, "utf-8");
100093
100403
  const parsed = JSON.parse(raw);
100094
100404
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
100095
100405
  return emptyQuotaWatchState();
@@ -100107,7 +100417,7 @@ function loadQuotaWatchState(stateDir) {
100107
100417
  }
100108
100418
  function saveQuotaWatchState(stateDir, state7) {
100109
100419
  mkdirSync45(stateDir, { recursive: true });
100110
- const path3 = join63(stateDir, STATE_FILE2);
100420
+ const path3 = join62(stateDir, STATE_FILE2);
100111
100421
  writeFileSync44(path3, JSON.stringify(state7, null, 2) + `
100112
100422
  `, { mode: 384 });
100113
100423
  }
@@ -100156,27 +100466,27 @@ init_auth_snapshot_format2();
100156
100466
  // gateway/turn-active-marker.ts
100157
100467
  import {
100158
100468
  closeSync as closeSync13,
100159
- existsSync as existsSync53,
100469
+ existsSync as existsSync54,
100160
100470
  mkdirSync as mkdirSync46,
100161
100471
  openSync as openSync13,
100162
- readFileSync as readFileSync55,
100472
+ readFileSync as readFileSync56,
100163
100473
  statSync as statSync20,
100164
100474
  unlinkSync as unlinkSync27,
100165
100475
  utimesSync as utimesSync2,
100166
100476
  writeFileSync as writeFileSync45
100167
100477
  } from "node:fs";
100168
- import { join as join64 } from "node:path";
100478
+ import { join as join63 } from "node:path";
100169
100479
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
100170
100480
  var TURN_ACTIVE_HARD_TTL_MS2 = 10 * 60000;
100171
100481
  var TURN_ACTIVE_IDLE_SWEEP_MS = 60000;
100172
100482
  function removeTurnActiveMarker2(stateDir) {
100173
100483
  try {
100174
- unlinkSync27(join64(stateDir, TURN_ACTIVE_MARKER_FILE2));
100484
+ unlinkSync27(join63(stateDir, TURN_ACTIVE_MARKER_FILE2));
100175
100485
  } catch {}
100176
100486
  }
100177
100487
  function sweepStaleTurnActiveMarker(stateDir, opts) {
100178
- const path3 = join64(stateDir, TURN_ACTIVE_MARKER_FILE2);
100179
- if (!existsSync53(path3))
100488
+ const path3 = join63(stateDir, TURN_ACTIVE_MARKER_FILE2);
100489
+ if (!existsSync54(path3))
100180
100490
  return false;
100181
100491
  const now = opts.now ?? Date.now();
100182
100492
  try {
@@ -100188,7 +100498,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
100188
100498
  return false;
100189
100499
  let payload = null;
100190
100500
  try {
100191
- payload = readFileSync55(path3, "utf8");
100501
+ payload = readFileSync56(path3, "utf8");
100192
100502
  } catch {}
100193
100503
  unlinkSync27(path3);
100194
100504
  if (opts.onRemove) {
@@ -100206,7 +100516,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
100206
100516
  }
100207
100517
  }
100208
100518
  function readTurnActiveMarkerAgeMs2(stateDir, now) {
100209
- const path3 = join64(stateDir, TURN_ACTIVE_MARKER_FILE2);
100519
+ const path3 = join63(stateDir, TURN_ACTIVE_MARKER_FILE2);
100210
100520
  try {
100211
100521
  const st = statSync20(path3);
100212
100522
  return (now ?? Date.now()) - st.mtimeMs;
@@ -100220,11 +100530,11 @@ function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
100220
100530
 
100221
100531
  // gateway/gateway-heartbeat.ts
100222
100532
  import { mkdirSync as mkdirSync47, utimesSync as utimesSync3, writeFileSync as writeFileSync46 } from "node:fs";
100223
- import { join as join65 } from "node:path";
100533
+ import { join as join64 } from "node:path";
100224
100534
  var GATEWAY_HEARTBEAT_FILE = "gateway-heartbeat";
100225
100535
  var GATEWAY_HEARTBEAT_INTERVAL_MS = 15000;
100226
100536
  function touchGatewayHeartbeat(stateDir) {
100227
- const path3 = join65(stateDir, GATEWAY_HEARTBEAT_FILE);
100537
+ const path3 = join64(stateDir, GATEWAY_HEARTBEAT_FILE);
100228
100538
  const now = new Date;
100229
100539
  try {
100230
100540
  utimesSync3(path3, now, now);
@@ -100250,12 +100560,12 @@ import {
100250
100560
  fsyncSync as fsyncSync5,
100251
100561
  mkdirSync as mkdirSync48,
100252
100562
  openSync as openSync14,
100253
- readFileSync as readFileSync56,
100563
+ readFileSync as readFileSync57,
100254
100564
  renameSync as renameSync25,
100255
100565
  unlinkSync as unlinkSync28,
100256
100566
  writeFileSync as writeFileSync47
100257
100567
  } from "node:fs";
100258
- import { join as join66 } from "node:path";
100568
+ import { join as join65 } from "node:path";
100259
100569
  var BOOT_BEACON_FILE = "gateway-beacon.json";
100260
100570
  var HOST_BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id";
100261
100571
  var CGROUP_MEMORY_EVENTS_PATH = "/sys/fs/cgroup/memory.events";
@@ -100332,7 +100642,7 @@ function serializeBootBeacon(beacon) {
100332
100642
  }
100333
100643
  function readTextOrNull(path3) {
100334
100644
  try {
100335
- return readFileSync56(path3, "utf-8");
100645
+ return readFileSync57(path3, "utf-8");
100336
100646
  } catch {
100337
100647
  return null;
100338
100648
  }
@@ -100354,7 +100664,7 @@ function sampleBeaconHost(paths = {}) {
100354
100664
  return sample3;
100355
100665
  }
100356
100666
  function writeBootBeaconFile(stateDir, beacon) {
100357
- const path3 = join66(stateDir, BOOT_BEACON_FILE);
100667
+ const path3 = join65(stateDir, BOOT_BEACON_FILE);
100358
100668
  const tmp = `${path3}.tmp-${process.pid}`;
100359
100669
  let fd;
100360
100670
  try {
@@ -100731,10 +101041,10 @@ function startOutboxSweep(deps) {
100731
101041
  }
100732
101042
 
100733
101043
  // ../src/build-info.ts
100734
- var VERSION2 = "0.19.43";
100735
- var COMMIT_SHA = "4aaf442f";
100736
- var COMMIT_DATE = "2026-08-01T10:38:38Z";
100737
- var LATEST_PR = 4159;
101044
+ var VERSION2 = "0.19.45";
101045
+ var COMMIT_SHA = "2ad33d5d";
101046
+ var COMMIT_DATE = "2026-08-01T23:47:32Z";
101047
+ var LATEST_PR = 4192;
100738
101048
  var COMMITS_AHEAD_OF_TAG = 0;
100739
101049
 
100740
101050
  // gateway/boot-version.ts
@@ -100812,11 +101122,11 @@ init_peercred();
100812
101122
  import * as net5 from "node:net";
100813
101123
  import * as fs3 from "node:fs";
100814
101124
  import { homedir as homedir19 } from "node:os";
100815
- import { join as join67 } from "node:path";
101125
+ import { join as join66 } from "node:path";
100816
101126
  var DEFAULT_TIMEOUT_MS4 = 2000;
100817
101127
  var UNLOCK_TIMEOUT_MS = 30000;
100818
- var LEGACY_SOCKET_PATH2 = join67(homedir19(), ".switchroom", "vault-broker.sock");
100819
- var OPERATOR_SOCKET_PATH2 = join67(homedir19(), ".switchroom", "broker-operator", "sock");
101128
+ var LEGACY_SOCKET_PATH2 = join66(homedir19(), ".switchroom", "vault-broker.sock");
101129
+ var OPERATOR_SOCKET_PATH2 = join66(homedir19(), ".switchroom", "broker-operator", "sock");
100820
101130
  function defaultBrokerSocketPath2() {
100821
101131
  if (fs3.existsSync(OPERATOR_SOCKET_PATH2))
100822
101132
  return OPERATOR_SOCKET_PATH2;
@@ -101699,7 +102009,7 @@ function resolveVaultApprovalPosture(broker) {
101699
102009
 
101700
102010
  // registry/turns-schema.ts
101701
102011
  import { chmodSync as chmodSync12, mkdirSync as mkdirSync49 } from "fs";
101702
- import { join as join68 } from "path";
102012
+ import { join as join67 } from "path";
101703
102013
  var DatabaseClass3 = null;
101704
102014
  function loadDatabaseClass3() {
101705
102015
  if (DatabaseClass3 != null)
@@ -101771,9 +102081,9 @@ function applySchema(db3) {
101771
102081
  }
101772
102082
  function openTurnsDb(agentDir) {
101773
102083
  const Database = loadDatabaseClass3();
101774
- const dir = join68(agentDir, "telegram");
102084
+ const dir = join67(agentDir, "telegram");
101775
102085
  mkdirSync49(dir, { recursive: true, mode: 448 });
101776
- const path3 = join68(dir, "registry.db");
102086
+ const path3 = join67(dir, "registry.db");
101777
102087
  const db3 = new Database(path3, { create: true });
101778
102088
  applySchema(db3);
101779
102089
  try {
@@ -102118,7 +102428,7 @@ function selectResumeBuilder(endedVia, opts) {
102118
102428
  }
102119
102429
 
102120
102430
  // gateway/bridge-dead-watchdog.ts
102121
- import { readFileSync as readFileSync58, writeFileSync as writeFileSync48, renameSync as renameSync26, unlinkSync as unlinkSync29 } from "node:fs";
102431
+ import { readFileSync as readFileSync59, writeFileSync as writeFileSync48, renameSync as renameSync26, unlinkSync as unlinkSync29 } from "node:fs";
102122
102432
 
102123
102433
  // gateway/cron-session.ts
102124
102434
  var CRON_IDENTITY_SUFFIX2 = "-cron";
@@ -102151,7 +102461,7 @@ function readFreshCrashLogTail(path3, opts = {}) {
102151
102461
  const nowMs3 = opts.nowMs ?? Date.now();
102152
102462
  const freshWindowMs = opts.freshWindowMs ?? CRASH_LOG_FRESH_WINDOW_MS;
102153
102463
  const maxLines = opts.maxLines ?? CRASH_LOG_TAIL_LINES;
102154
- const readFile = opts.readFile ?? ((p) => readFileSync58(p, "utf8"));
102464
+ const readFile = opts.readFile ?? ((p) => readFileSync59(p, "utf8"));
102155
102465
  let raw;
102156
102466
  try {
102157
102467
  raw = readFile(path3);
@@ -102178,7 +102488,7 @@ function writeBridgeDeadEscalationMarker(path3, marker) {
102178
102488
  function consumeBridgeDeadEscalationMarker(path3, nowMs3 = Date.now(), maxAgeMs = ESCALATION_MARKER_MAX_AGE_MS) {
102179
102489
  let marker = null;
102180
102490
  try {
102181
- const parsed = JSON.parse(readFileSync58(path3, "utf8"));
102491
+ const parsed = JSON.parse(readFileSync59(path3, "utf8"));
102182
102492
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.reason === "string") {
102183
102493
  const age = nowMs3 - parsed.ts;
102184
102494
  if (age >= 0 && age < maxAgeMs) {
@@ -102331,7 +102641,7 @@ function createBridgeDeadWatchdog(opts) {
102331
102641
  }
102332
102642
 
102333
102643
  // gateway/boot-probes.ts
102334
- import { readFileSync as readFileSync59, readdirSync as readdirSync14, existsSync as existsSync55 } from "fs";
102644
+ import { readFileSync as readFileSync60, readdirSync as readdirSync14, existsSync as existsSync56 } from "fs";
102335
102645
  init_quota_cache();
102336
102646
  init_generation_stamp();
102337
102647
  init_quota_check();
@@ -102340,7 +102650,7 @@ import { promisify as promisify2 } from "util";
102340
102650
  var execFile2 = promisify2(execFileCb2);
102341
102651
  var realProcFs2 = {
102342
102652
  readdir: (p) => readdirSync14(p),
102343
- readFile: (p) => readFileSync59(p, "utf-8")
102653
+ readFile: (p) => readFileSync60(p, "utf-8")
102344
102654
  };
102345
102655
  function findAgentProcessInContainer2(fs4 = realProcFs2) {
102346
102656
  let entries;
@@ -102590,7 +102900,7 @@ if (isGatewayMain) {
102590
102900
  shutdownAnalytics();
102591
102901
  });
102592
102902
  }
102593
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join69(homedir20(), ".claude", "channels", "telegram");
102903
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join68(homedir20(), ".claude", "channels", "telegram");
102594
102904
  var permCardStore = createPermissionCardStore(STATE_DIR);
102595
102905
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
102596
102906
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -102629,7 +102939,7 @@ function alwaysAllowDrainDeps() {
102629
102939
  return {
102630
102940
  readConfigText: () => {
102631
102941
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
102632
- return readFileSync60(cfgPath, "utf8");
102942
+ return readFileSync61(cfgPath, "utf8");
102633
102943
  },
102634
102944
  resolveAllowList: (_configText, agentName3) => {
102635
102945
  const cfg = loadConfig2();
@@ -102690,11 +103000,11 @@ function scheduleAlwaysAllowPersistDrain() {
102690
103000
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
102691
103001
  timer3.unref?.();
102692
103002
  }
102693
- var ACCESS_FILE = join69(STATE_DIR, "access.json");
102694
- var APPROVED_DIR = join69(STATE_DIR, "approved");
102695
- var ENV_FILE = join69(STATE_DIR, ".env");
102696
- var INBOX_DIR = join69(STATE_DIR, "inbox");
102697
- var PEOPLE_FILE = join69(STATE_DIR, "people.json");
103003
+ var ACCESS_FILE = join68(STATE_DIR, "access.json");
103004
+ var APPROVED_DIR = join68(STATE_DIR, "approved");
103005
+ var ENV_FILE = join68(STATE_DIR, ".env");
103006
+ var INBOX_DIR = join68(STATE_DIR, "inbox");
103007
+ var PEOPLE_FILE = join68(STATE_DIR, "people.json");
102698
103008
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
102699
103009
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
102700
103010
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -102759,7 +103069,7 @@ function formatBootVersion() {
102759
103069
  }
102760
103070
  try {
102761
103071
  chmodSync14(ENV_FILE, 384);
102762
- for (const line of readFileSync60(ENV_FILE, "utf8").split(`
103072
+ for (const line of readFileSync61(ENV_FILE, "utf8").split(`
102763
103073
  `)) {
102764
103074
  const m = line.match(/^(\w+)=(.*)$/);
102765
103075
  if (m && process.env[m[1]] === undefined)
@@ -102780,7 +103090,7 @@ var bot;
102780
103090
  var lastGetUpdatesHeartbeatMs = Date.now();
102781
103091
  var GRAMMY_VERSION = (() => {
102782
103092
  try {
102783
- const raw = readFileSync60(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
103093
+ const raw = readFileSync61(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
102784
103094
  return JSON.parse(raw).version ?? "unknown";
102785
103095
  } catch {
102786
103096
  return "unknown";
@@ -102819,12 +103129,12 @@ function assertSendable(f) {
102819
103129
  }
102820
103130
  let real, stateReal;
102821
103131
  try {
102822
- real = realpathSync4(f);
102823
- stateReal = realpathSync4(STATE_DIR);
103132
+ real = realpathSync5(f);
103133
+ stateReal = realpathSync5(STATE_DIR);
102824
103134
  } catch {
102825
103135
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
102826
103136
  }
102827
- const inbox = join69(stateReal, "inbox");
103137
+ const inbox = join68(stateReal, "inbox");
102828
103138
  if (real.startsWith(stateReal + sep4) && !real.startsWith(inbox + sep4)) {
102829
103139
  throw new Error(`refusing to send channel state: ${f}`);
102830
103140
  }
@@ -102843,7 +103153,7 @@ function assertSendable(f) {
102843
103153
  }
102844
103154
  function readAccessFile() {
102845
103155
  try {
102846
- const raw = readFileSync60(ACCESS_FILE, "utf8");
103156
+ const raw = readFileSync61(ACCESS_FILE, "utf8");
102847
103157
  const parsed = JSON.parse(raw);
102848
103158
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
102849
103159
  const groups = {};
@@ -102905,7 +103215,7 @@ function loadAccess() {
102905
103215
  }
102906
103216
  function readPeopleFile() {
102907
103217
  try {
102908
- const raw = readFileSync60(PEOPLE_FILE, "utf8");
103218
+ const raw = readFileSync61(PEOPLE_FILE, "utf8");
102909
103219
  const parsed = JSON.parse(raw);
102910
103220
  if (!Array.isArray(parsed.entries))
102911
103221
  return [];
@@ -102949,7 +103259,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
102949
103259
  if (isGatewayMain && HISTORY_ENABLED) {
102950
103260
  try {
102951
103261
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
102952
- process.stderr.write(`telegram gateway: history capture enabled at ${join69(STATE_DIR, "history.db")}
103262
+ process.stderr.write(`telegram gateway: history capture enabled at ${join68(STATE_DIR, "history.db")}
102953
103263
  `);
102954
103264
  } catch (err) {
102955
103265
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -102968,12 +103278,12 @@ if (isGatewayMain)
102968
103278
  let markerTurnKey = null;
102969
103279
  let markerAgeMs = null;
102970
103280
  try {
102971
- const markerPath = join69(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
102972
- if (existsSync57(markerPath)) {
103281
+ const markerPath = join68(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
103282
+ if (existsSync58(markerPath)) {
102973
103283
  const st = statSync23(markerPath);
102974
103284
  markerAgeMs = Date.now() - st.mtimeMs;
102975
103285
  try {
102976
- const payload = JSON.parse(readFileSync60(markerPath, "utf8"));
103286
+ const payload = JSON.parse(readFileSync61(markerPath, "utf8"));
102977
103287
  if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
102978
103288
  markerTurnKey = payload.turnKey;
102979
103289
  }
@@ -102993,10 +103303,10 @@ if (isGatewayMain)
102993
103303
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)` + `${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
102994
103304
  `);
102995
103305
  } else {
102996
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join69(agentDir, "telegram", "registry.db")}
103306
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join68(agentDir, "telegram", "registry.db")}
102997
103307
  `);
102998
103308
  }
102999
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join69(STATE_DIR, "bridge-dead-escalation.json"));
103309
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join68(STATE_DIR, "bridge-dead-escalation.json"));
103000
103310
  if (bridgeDeadMarker != null) {
103001
103311
  bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
103002
103312
  process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}` + `, consecutive=${bridgeDeadPriorStreak}` + `${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
@@ -103009,7 +103319,7 @@ if (isGatewayMain)
103009
103319
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
103010
103320
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
103011
103321
  if (pending2 != null && selfAgent) {
103012
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join69(STATE_DIR, "clean-shutdown.json");
103322
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join68(STATE_DIR, "clean-shutdown.json");
103013
103323
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
103014
103324
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
103015
103325
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -103122,7 +103432,7 @@ if (isGatewayMain)
103122
103432
  `);
103123
103433
  }
103124
103434
  }
103125
- const pendingEnvPath = join69(agentDir, ".pending-turn.env");
103435
+ const pendingEnvPath = join68(agentDir, ".pending-turn.env");
103126
103436
  try {
103127
103437
  if (pending2 != null) {
103128
103438
  const lines = [
@@ -103142,7 +103452,7 @@ if (isGatewayMain)
103142
103452
  renameSync27(pendingEnvTmp, pendingEnvPath);
103143
103453
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
103144
103454
  `);
103145
- } else if (existsSync57(pendingEnvPath)) {
103455
+ } else if (existsSync58(pendingEnvPath)) {
103146
103456
  rmSync8(pendingEnvPath, { force: true });
103147
103457
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
103148
103458
  `);
@@ -103250,7 +103560,7 @@ function checkApprovals() {
103250
103560
  return;
103251
103561
  }
103252
103562
  for (const senderId of files) {
103253
- const file = join69(APPROVED_DIR, senderId);
103563
+ const file = join68(APPROVED_DIR, senderId);
103254
103564
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync8(file, { force: true }), (err) => {
103255
103565
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
103256
103566
  `);
@@ -103277,7 +103587,10 @@ var deferredDoneReactions = new DeferredDoneReactions({
103277
103587
  purge: (key) => purgeReactionTracking(key)
103278
103588
  });
103279
103589
  var outboundDedup = new OutboundDedupCache;
103280
- var flushedTurnSupersede = new FlushedTurnSupersedeRegistry;
103590
+ var flushedTurnSupersede = new FlushedTurnSupersedeRegistry({
103591
+ openCapMs: SUPERSEDE_OPEN_CAP_MS,
103592
+ completedGraceMs: SUPERSEDE_GRACE_MS
103593
+ });
103281
103594
  var backstopDeliveryLedger = new BackstopDeliveryLedger2;
103282
103595
  var BACKSTOP_DELIVERY_MAX_ATTEMPTS = (() => {
103283
103596
  const raw = Number(process.env.SWITCHROOM_BACKSTOP_DELIVERY_MAX_ATTEMPTS);
@@ -103425,12 +103738,12 @@ function noteAgentOutputAt(key, ts) {
103425
103738
  lastAgentOutputAt.delete(oldest);
103426
103739
  }
103427
103740
  }
103428
- var OBLIGATION_STORE_PATH = join69(STATE_DIR, "obligations.json");
103741
+ var OBLIGATION_STORE_PATH = join68(STATE_DIR, "obligations.json");
103429
103742
  var obligationStoreFs = {
103430
- readFileSync: (p) => readFileSync60(p, "utf8"),
103743
+ readFileSync: (p) => readFileSync61(p, "utf8"),
103431
103744
  writeFileSync: (p, d) => writeFileSync50(p, d),
103432
103745
  renameSync: (a, b) => renameSync27(a, b),
103433
- existsSync: (p) => existsSync57(p),
103746
+ existsSync: (p) => existsSync58(p),
103434
103747
  fsyncFileSync: fsyncPathSync,
103435
103748
  fsyncDirSync: fsyncPathSync,
103436
103749
  unlinkSync: unlinkSync31
@@ -103641,7 +103954,7 @@ function cardDrainGate(turn, ea, run4) {
103641
103954
  var RECENT_TURNS_MAX = 32;
103642
103955
  var recentTurnsById = new Map;
103643
103956
  var recentTurnIdBySourceMessageId = new Map;
103644
- var subagentHandbackMarker = new SubagentHandbackMarker;
103957
+ var subagentHandbackMarker = new SubagentHandbackMarker2;
103645
103958
  var getLastSubagentHandbackAt = (chatId) => subagentHandbackMarker.lastAtInChat(chatId);
103646
103959
  function rememberRecentTurn(turn) {
103647
103960
  recentTurnsById.set(turn.turnId, turn);
@@ -103695,26 +104008,7 @@ function findLatestTurnForChat(chatId, opts) {
103695
104008
  return latestTurnForChat(recentTurnsById.values(), chatId, opts);
103696
104009
  }
103697
104010
  function resolveReplyOwnerTurn(liveTurn, chatId, args) {
103698
- const origin = findTurnByOriginId(args.origin_turn_id);
103699
- const quoted = findTurnByQuotedMessageId(chatId, args.reply_to);
103700
- const latestEnded = findLatestTurnForChat(chatId, { endedOnly: true });
103701
- const byId = new Map;
103702
- for (const t of [latestEnded, quoted, origin, liveTurn]) {
103703
- if (t != null)
103704
- byId.set(t.turnId, t);
103705
- }
103706
- const latestEndedAgeMs = latestEnded?.endedAt != null ? Date.now() - latestEnded.endedAt : null;
103707
- const candidates = {
103708
- liveTurnId: liveTurn?.turnId ?? null,
103709
- originTurnId: origin?.turnId ?? null,
103710
- quotedTurnId: quoted?.turnId ?? null,
103711
- latestEndedTurnId: latestEnded?.turnId ?? null,
103712
- latestEndedAgeMs,
103713
- latestEndedTtlMs: DEFAULT_SUPERSEDE_TTL_MS
103714
- };
103715
- const tier = resolveReplyOwnerTier(candidates);
103716
- const winnerId = resolveReplyOwnerTurnId(candidates);
103717
- return { turn: winnerId != null ? byId.get(winnerId) ?? null : null, tier, candidates };
104011
+ return resolveReplyOwnerTurnWith({ findTurnByOriginId, findTurnByQuotedMessageId, findLatestTurnForChat, now: Date.now }, liveTurn, chatId, args);
103718
104012
  }
103719
104013
  function resolveAnswerThreadWithLog(chatId, explicitThreadId, originTurn, originVia, liveTurn, surface) {
103720
104014
  const recovered = LATE_REPLY_TOPIC_RECOVERY_ENABLED && explicitThreadId == null && originTurn == null && liveTurn == null ? findLatestTurnForChat(chatId, { endedOnly: false }) : null;
@@ -105773,28 +106067,28 @@ var PIN_STATUS_WHILE_WORKING = (() => {
105773
106067
  })();
105774
106068
  var statusPinClaims = new Map;
105775
106069
  var statusPinRightsCache = new PinRightsCache2;
105776
- var STATUS_PIN_STORE_PATH = join69(STATE_DIR, "status-pins.json");
106070
+ var STATUS_PIN_STORE_PATH = join68(STATE_DIR, "status-pins.json");
105777
106071
  var statusPinStoreFs = {
105778
- readFileSync: (p) => readFileSync60(p, "utf8"),
106072
+ readFileSync: (p) => readFileSync61(p, "utf8"),
105779
106073
  writeFileSync: (p, d) => writeFileSync50(p, d),
105780
106074
  renameSync: (a, b) => renameSync27(a, b),
105781
- existsSync: (p) => existsSync57(p)
106075
+ existsSync: (p) => existsSync58(p)
105782
106076
  };
105783
106077
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
105784
- var ACTIVITY_CARD_STORE_PATH = join69(STATE_DIR, "activity-cards-pending.json");
106078
+ var ACTIVITY_CARD_STORE_PATH = join68(STATE_DIR, "activity-cards-pending.json");
105785
106079
  var activityCardStoreFs = {
105786
- readFileSync: (p) => readFileSync60(p, "utf8"),
106080
+ readFileSync: (p) => readFileSync61(p, "utf8"),
105787
106081
  writeFileSync: (p, d) => writeFileSync50(p, d),
105788
106082
  renameSync: (a, b) => renameSync27(a, b),
105789
- existsSync: (p) => existsSync57(p)
106083
+ existsSync: (p) => existsSync58(p)
105790
106084
  };
105791
106085
  var activityCardPersistEnabled = !STATIC;
105792
- var QUEUED_CARD_STORE_PATH = join69(STATE_DIR, "queued-cards-pending.json");
106086
+ var QUEUED_CARD_STORE_PATH = join68(STATE_DIR, "queued-cards-pending.json");
105793
106087
  var queuedCardStoreFs = {
105794
- readFileSync: (p) => readFileSync60(p, "utf8"),
106088
+ readFileSync: (p) => readFileSync61(p, "utf8"),
105795
106089
  writeFileSync: (p, d) => writeFileSync50(p, d),
105796
106090
  renameSync: (a, b) => renameSync27(a, b),
105797
- existsSync: (p) => existsSync57(p)
106091
+ existsSync: (p) => existsSync58(p)
105798
106092
  };
105799
106093
  var queuedCardPersistEnabled = !STATIC;
105800
106094
  function persistQueuedCard(key, chatId, threadId, messageId) {
@@ -106081,7 +106375,7 @@ async function unpinAllStatusPins() {
106081
106375
  }
106082
106376
  }
106083
106377
  var stalePinSweepEligible = false;
106084
- var STALE_PIN_SWEEP_STORE_PATH = join69(STATE_DIR, "stale-pin-sweep.json");
106378
+ var STALE_PIN_SWEEP_STORE_PATH = join68(STATE_DIR, "stale-pin-sweep.json");
106085
106379
  var stalePinSweeper = createGatewayStalePinSweeper({
106086
106380
  telegram: { handle: () => lockedBot, call: robustApiCall },
106087
106381
  claims: () => statusPinClaims.values(),
@@ -106090,9 +106384,9 @@ var stalePinSweeper = createGatewayStalePinSweeper({
106090
106384
  store: {
106091
106385
  path: STALE_PIN_SWEEP_STORE_PATH,
106092
106386
  fs: {
106093
- readFileSync: (p) => readFileSync60(p, "utf-8"),
106387
+ readFileSync: (p) => readFileSync61(p, "utf-8"),
106094
106388
  writeFileSync: (p, data) => atomicWriteFileSync(p, data, 384),
106095
- existsSync: (p) => existsSync57(p)
106389
+ existsSync: (p) => existsSync58(p)
106096
106390
  }
106097
106391
  },
106098
106392
  allowUnpinAllForumTopic: process.env.SWITCHROOM_PIN_SWEEP_UNPIN_ALL_TOPIC == null ? undefined : process.env.SWITCHROOM_PIN_SWEEP_UNPIN_ALL_TOPIC === "1"
@@ -106122,12 +106416,12 @@ var getPinnedProgressCardMessageId = null;
106122
106416
  var completeProgressCardTurn = null;
106123
106417
  var subagentWatcher = null;
106124
106418
  var workerActivityFeed = null;
106125
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join69(STATE_DIR, "gateway.sock");
106419
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join68(STATE_DIR, "gateway.sock");
106126
106420
  if (isGatewayMain)
106127
106421
  mkdirSync51(STATE_DIR, { recursive: true, mode: 448 });
106128
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join69(STATE_DIR, "gateway.pid.json");
106129
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join69(STATE_DIR, "gateway-session.json");
106130
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join69(STATE_DIR, "clean-shutdown.json");
106422
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join68(STATE_DIR, "gateway.pid.json");
106423
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join68(STATE_DIR, "gateway-session.json");
106424
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join68(STATE_DIR, "clean-shutdown.json");
106131
106425
  var GATEWAY_STARTED_AT_MS = Date.now();
106132
106426
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
106133
106427
  var activeBootCard = null;
@@ -106156,7 +106450,7 @@ function ensureIssuesCard(chatId, threadId) {
106156
106450
  bot: botApi,
106157
106451
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
106158
106452
  `),
106159
- persistPath: join69(stateDir, "issues-card.json")
106453
+ persistPath: join68(stateDir, "issues-card.json")
106160
106454
  });
106161
106455
  activeIssuesWatcher = startIssuesWatcher({
106162
106456
  stateDir,
@@ -106361,13 +106655,13 @@ if (isGatewayMain)
106361
106655
  var inboundSpool;
106362
106656
  if (isGatewayMain)
106363
106657
  inboundSpool = STATIC ? undefined : createInboundSpool({
106364
- path: join69(STATE_DIR, "inbound-spool.jsonl"),
106658
+ path: join68(STATE_DIR, "inbound-spool.jsonl"),
106365
106659
  fs: {
106366
106660
  appendFileSync: (p, d) => appendFileSync9(p, d),
106367
- readFileSync: (p) => readFileSync60(p, "utf8"),
106661
+ readFileSync: (p) => readFileSync61(p, "utf8"),
106368
106662
  writeFileSync: (p, d) => writeFileSync50(p, d),
106369
106663
  renameSync: (a, b) => renameSync27(a, b),
106370
- existsSync: (p) => existsSync57(p),
106664
+ existsSync: (p) => existsSync58(p),
106371
106665
  statSizeSync: (p) => statSync23(p).size,
106372
106666
  fsyncFileSync: fsyncPathSync,
106373
106667
  fsyncDirSync: fsyncPathSync
@@ -106431,13 +106725,13 @@ async function maybeRedeliverUndeliveredAnswer() {
106431
106725
  let transcriptText;
106432
106726
  try {
106433
106727
  const projectsDir = getProjectsDirForCwd();
106434
- const path3 = join69(projectsDir, `${sessionId}.jsonl`);
106435
- if (!existsSync57(path3)) {
106728
+ const path3 = join68(projectsDir, `${sessionId}.jsonl`);
106729
+ if (!existsSync58(path3)) {
106436
106730
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path3}); skipping
106437
106731
  `);
106438
106732
  return;
106439
106733
  }
106440
- transcriptText = readFileSync60(path3, "utf8");
106734
+ transcriptText = readFileSync61(path3, "utf8");
106441
106735
  } catch (err) {
106442
106736
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript read failed turnKey=${turn.turn_key}: ${err.message}
106443
106737
  `);
@@ -106596,8 +106890,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
106596
106890
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
106597
106891
  isShuttingDown: () => shuttingDown,
106598
106892
  escalate: (reason) => triggerSelfRestart(process.env.SWITCHROOM_AGENT_NAME ?? "", reason, 1500),
106599
- crashLogPath: join69(STATE_DIR, "bridge-crash.log"),
106600
- markerPath: join69(STATE_DIR, "bridge-dead-escalation.json"),
106893
+ crashLogPath: join68(STATE_DIR, "bridge-crash.log"),
106894
+ markerPath: join68(STATE_DIR, "bridge-dead-escalation.json"),
106601
106895
  log: (line) => process.stderr.write(`${line}
106602
106896
  `),
106603
106897
  priorStreak: bridgeDeadPriorStreak,
@@ -106703,8 +106997,8 @@ if (isGatewayMain)
106703
106997
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
106704
106998
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
106705
106999
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
106706
- configSnapshotPath: join69(resolvedAgentDirForCard, ".config-snapshot.json"),
106707
- bootCardStatePath: join69(resolvedAgentDirForCard, ".boot-card-msgid.json"),
107000
+ configSnapshotPath: join68(resolvedAgentDirForCard, ".config-snapshot.json"),
107001
+ bootCardStatePath: join68(resolvedAgentDirForCard, ".boot-card-msgid.json"),
106708
107002
  floodStatePath: FLOOD_STATE_PATH,
106709
107003
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
106710
107004
  }, ackMsgId).then((handle) => {
@@ -106770,12 +107064,20 @@ if (isGatewayMain)
106770
107064
  log: (msg) => process.stderr.write(`${msg}
106771
107065
  `)
106772
107066
  });
107067
+ if (client3.agentName != null && !isCronIdentity(client3.agentName)) {
107068
+ subagentReplyAuthority.reset();
107069
+ const closedWindows = closeFlushCompletionWindows(flushedTurnSupersede, Date.now());
107070
+ if (closedWindows > 0) {
107071
+ process.stderr.write(`telegram gateway: bridge disconnect closed ${closedWindows} open flush-completion window(s) (#4173)
107072
+ `);
107073
+ }
107074
+ }
106773
107075
  },
106774
107076
  async onToolCall(client3, msg) {
106775
107077
  process.stderr.write(`telegram gateway: ipc: tool_call tool=${msg.tool} agent=${client3.agentName ?? "-"} clientId=${client3.id ?? "-"} callId=${msg.id}
106776
107078
  `);
106777
107079
  try {
106778
- const result = await executeToolCall(msg.tool, msg.args);
107080
+ const result = await executeToolCall(msg.tool, msg.args, client3.agentName);
106779
107081
  return { type: "tool_call_result", id: msg.id, success: true, result };
106780
107082
  } catch (err) {
106781
107083
  return {
@@ -107371,7 +107673,7 @@ if (isGatewayMain)
107371
107673
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
107372
107674
  if (Number.isInteger(receiverUid))
107373
107675
  allowedUids.push(receiverUid);
107374
- const socketPath = join69(STATE_DIR, "webhook.sock");
107676
+ const socketPath = join68(STATE_DIR, "webhook.sock");
107375
107677
  const webhookInject = (agentName3, inbound) => {
107376
107678
  const msg = inbound;
107377
107679
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -107453,13 +107755,13 @@ var ALLOWED_TOOLS = new Set([
107453
107755
  "linear_create_issue",
107454
107756
  "linear_agent_setup"
107455
107757
  ]);
107456
- async function executeToolCall(tool, args) {
107758
+ async function executeToolCall(tool, args, callerAgentName = null) {
107457
107759
  if (!ALLOWED_TOOLS.has(tool)) {
107458
107760
  throw new Error(`tool not allowed: ${tool}`);
107459
107761
  }
107460
107762
  switch (tool) {
107461
107763
  case "reply":
107462
- return executeReply(args);
107764
+ return executeReply(args, callerAgentName);
107463
107765
  case "progress_update":
107464
107766
  return executeProgressUpdate(args);
107465
107767
  case "react":
@@ -107622,9 +107924,9 @@ function redactOutboundText(text5, site) {
107622
107924
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
107623
107925
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
107624
107926
  var voiceOnDemandCache = new VoiceOnDemandCache({
107625
- persistPath: join69(STATE_DIR, "voice-ondemand.json")
107927
+ persistPath: join68(STATE_DIR, "voice-ondemand.json")
107626
107928
  });
107627
- var VOICE_CACHE_DIR = join69(STATE_DIR, "voice-cache");
107929
+ var VOICE_CACHE_DIR = join68(STATE_DIR, "voice-cache");
107628
107930
  var voicePreSynthQueue = new PreSynthQueue({
107629
107931
  runJob: async (job) => {
107630
107932
  const sidecarToken = await materializeSidecarToken2();
@@ -107740,8 +108042,12 @@ async function synthesizeVoiceOut(plan) {
107740
108042
  return null;
107741
108043
  }
107742
108044
  }
107743
- async function executeReply(args) {
107744
- return sendReply(gatewaySendReplyDeps(), { args, turn: currentTurn });
108045
+ async function executeReply(args, callerAgentName = null) {
108046
+ return sendReply(gatewaySendReplyDeps(), {
108047
+ args,
108048
+ turn: currentTurn,
108049
+ callerIsForeignSession: replyCallerIsForeignSession(callerAgentName)
108050
+ });
107745
108051
  }
107746
108052
  function gatewaySendReplyDeps() {
107747
108053
  return {
@@ -107780,6 +108086,7 @@ function gatewaySendReplyDeps() {
107780
108086
  resolveThreadId,
107781
108087
  getLatestInboundMessageId,
107782
108088
  getLastSubagentHandbackAt,
108089
+ subagentReplyAuthority,
107783
108090
  recordOutbound,
107784
108091
  emissionAuthorityFor,
107785
108092
  clearActivitySummary,
@@ -108026,11 +108333,11 @@ async function executeSendGif(rawArgs) {
108026
108333
  };
108027
108334
  }
108028
108335
  async function publishToTelegraph(text5, shortName, authorName) {
108029
- const accountPath = join69(STATE_DIR, "telegraph-account.json");
108336
+ const accountPath = join68(STATE_DIR, "telegraph-account.json");
108030
108337
  let account = null;
108031
108338
  try {
108032
- if (existsSync57(accountPath)) {
108033
- const raw = readFileSync60(accountPath, "utf-8");
108339
+ if (existsSync58(accountPath)) {
108340
+ const raw = readFileSync61(accountPath, "utf-8");
108034
108341
  const parsed = JSON.parse(raw);
108035
108342
  if (parsed.shortName && parsed.accessToken) {
108036
108343
  account = parsed;
@@ -108166,7 +108473,7 @@ _The secret was NOT saved. The agent can re-request with \`request_secret\`._`,
108166
108473
  }
108167
108474
  function readLiveSwitchroomConfigText() {
108168
108475
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? findConfigFile2();
108169
- return readFileSync60(cfgPath, "utf8");
108476
+ return readFileSync61(cfgPath, "utf8");
108170
108477
  }
108171
108478
  async function executeReact(args) {
108172
108479
  if (!args.chat_id)
@@ -109579,7 +109886,7 @@ function restartMarkerPath() {
109579
109886
  const agentDir = resolveAgentDirFromEnv();
109580
109887
  if (!agentDir)
109581
109888
  return null;
109582
- return join69(agentDir, "restart-pending.json");
109889
+ return join68(agentDir, "restart-pending.json");
109583
109890
  }
109584
109891
  function writeRestartMarker(marker) {
109585
109892
  const p = restartMarkerPath();
@@ -109605,7 +109912,7 @@ function readRestartMarker() {
109605
109912
  if (!p)
109606
109913
  return null;
109607
109914
  try {
109608
- return JSON.parse(readFileSync60(p, "utf8"));
109915
+ return JSON.parse(readFileSync61(p, "utf8"));
109609
109916
  } catch {
109610
109917
  return null;
109611
109918
  }
@@ -109754,7 +110061,7 @@ var _dockerReachable;
109754
110061
  function isDockerReachable() {
109755
110062
  if (_dockerReachable !== undefined)
109756
110063
  return _dockerReachable;
109757
- if (!existsSync57("/var/run/docker.sock")) {
110064
+ if (!existsSync58("/var/run/docker.sock")) {
109758
110065
  _dockerReachable = false;
109759
110066
  return _dockerReachable;
109760
110067
  }
@@ -109771,7 +110078,7 @@ function _resetDockerReachableCache() {
109771
110078
  }
109772
110079
  function spawnSwitchroomDetached(args, onFailure) {
109773
110080
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
109774
- const logPath = join69(STATE_DIR, "detached-spawn.log");
110081
+ const logPath = join68(STATE_DIR, "detached-spawn.log");
109775
110082
  let outFd = null;
109776
110083
  try {
109777
110084
  mkdirSync51(STATE_DIR, { recursive: true });
@@ -109802,7 +110109,7 @@ function spawnSwitchroomDetached(args, onFailure) {
109802
110109
  return;
109803
110110
  let tail = "";
109804
110111
  try {
109805
- const full = readFileSync60(logPath, "utf8");
110112
+ const full = readFileSync61(logPath, "utf8");
109806
110113
  tail = full.split(`
109807
110114
  `).slice(-30).join(`
109808
110115
  `).trim();
@@ -110064,10 +110371,10 @@ ${preBlock(formatSwitchroomOutput(detail))}`, { html: true });
110064
110371
  }
110065
110372
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
110066
110373
  try {
110067
- const auditPath = join69(homedir20(), ".switchroom", "vault-audit.log");
110068
- if (!existsSync57(auditPath))
110374
+ const auditPath = join68(homedir20(), ".switchroom", "vault-audit.log");
110375
+ if (!existsSync58(auditPath))
110069
110376
  return [];
110070
- const raw = readFileSync60(auditPath, "utf8");
110377
+ const raw = readFileSync61(auditPath, "utf8");
110071
110378
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
110072
110379
  } catch {
110073
110380
  return [];
@@ -110118,7 +110425,7 @@ async function buildAgentMetadata(agentName3) {
110118
110425
  try {
110119
110426
  const agentDir = resolveAgentDirFromEnv();
110120
110427
  if (agentDir) {
110121
- const raw = readFileSync60(join69(agentDir, ".claude", ".claude.json"), "utf8");
110428
+ const raw = readFileSync61(join68(agentDir, ".claude", ".claude.json"), "utf8");
110122
110429
  claudeJson = JSON.parse(raw);
110123
110430
  }
110124
110431
  } catch {}
@@ -110247,7 +110554,7 @@ function buildModelDeps(restartCtx) {
110247
110554
  try {
110248
110555
  const agentDir = resolveAgentDirFromEnv();
110249
110556
  if (agentDir) {
110250
- const local = await fetchQuota2({ claudeConfigDir: join69(agentDir, ".claude") });
110557
+ const local = await fetchQuota2({ claudeConfigDir: join68(agentDir, ".claude") });
110251
110558
  if (local.ok)
110252
110559
  return formatQuotaLine2(local.data);
110253
110560
  }
@@ -110494,9 +110801,9 @@ function effortMenuReplyMarkup(reply) {
110494
110801
  function flushAgentHandoff(agentDir) {
110495
110802
  let removed = 0;
110496
110803
  for (const fname of [".handoff.md", ".handoff-topic"]) {
110497
- const p = join69(agentDir, fname);
110804
+ const p = join68(agentDir, fname);
110498
110805
  try {
110499
- if (existsSync57(p)) {
110806
+ if (existsSync58(p)) {
110500
110807
  unlinkSync31(p);
110501
110808
  removed++;
110502
110809
  }
@@ -110552,7 +110859,7 @@ async function handleNewCommand(ctx) {
110552
110859
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
110553
110860
  if (agentDir != null) {
110554
110861
  try {
110555
- writeFileSync50(join69(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
110862
+ writeFileSync50(join68(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
110556
110863
  `, "utf8");
110557
110864
  } catch (err) {
110558
110865
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -110665,16 +110972,16 @@ function buildFolderPickerDeps() {
110665
110972
  };
110666
110973
  }
110667
110974
  var lockoutOps = {
110668
- readFileSync: (p, enc) => readFileSync60(p, enc),
110975
+ readFileSync: (p, enc) => readFileSync61(p, enc),
110669
110976
  writeFileSync: (p, data, opts) => writeFileSync50(p, data, opts),
110670
- existsSync: (p) => existsSync57(p),
110977
+ existsSync: (p) => existsSync58(p),
110671
110978
  mkdirSync: (p, opts) => mkdirSync51(p, opts),
110672
- joinPath: (...parts) => join69(...parts)
110979
+ joinPath: (...parts) => join68(...parts)
110673
110980
  };
110674
110981
  var FLEET_FALLBACK_DEDUP_MS = 30000;
110675
110982
  function isAuthBrokerSocketReachable() {
110676
110983
  try {
110677
- return existsSync57(resolveAuthBrokerSocketPath2());
110984
+ return existsSync58(resolveAuthBrokerSocketPath2());
110678
110985
  } catch {
110679
110986
  return false;
110680
110987
  }
@@ -110929,7 +111236,7 @@ async function runCreditWatch() {
110929
111236
  if (!agentDir)
110930
111237
  return;
110931
111238
  const agentName3 = getMyAgentName();
110932
- const claudeConfigDir = join69(agentDir, ".claude");
111239
+ const claudeConfigDir = join68(agentDir, ".claude");
110933
111240
  const stateDir = STATE_DIR;
110934
111241
  const reason = readClaudeJsonOverage(claudeConfigDir);
110935
111242
  const prev = loadCreditState(stateDir);
@@ -112685,7 +112992,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
112685
112992
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
112686
112993
  return;
112687
112994
  }
112688
- const result = await fetchQuota2({ claudeConfigDir: join69(agentDir, ".claude") });
112995
+ const result = await fetchQuota2({ claudeConfigDir: join68(agentDir, ".claude") });
112689
112996
  if (!result.ok) {
112690
112997
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
112691
112998
  return;
@@ -113072,7 +113379,7 @@ ${interimLabel}` : interimLabel
113072
113379
  const unifiedDiff = (() => {
113073
113380
  try {
113074
113381
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
113075
- const raw = readFileSync60(cfgPath, "utf8");
113382
+ const raw = readFileSync61(cfgPath, "utf8");
113076
113383
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
113077
113384
  } catch (err) {
113078
113385
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -113941,7 +114248,7 @@ async function startGateway() {
113941
114248
  return;
113942
114249
  }
113943
114250
  })();
113944
- const resolvedAgentDirForBootCard = agentDir ?? join69(homedir20(), ".switchroom", "agents", agentSlug);
114251
+ const resolvedAgentDirForBootCard = agentDir ?? join68(homedir20(), ".switchroom", "agents", agentSlug);
113945
114252
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
113946
114253
  agentName: agentDisplayName,
113947
114254
  agentSlug,
@@ -113955,8 +114262,8 @@ async function startGateway() {
113955
114262
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
113956
114263
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
113957
114264
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
113958
- configSnapshotPath: join69(resolvedAgentDirForBootCard, ".config-snapshot.json"),
113959
- bootCardStatePath: join69(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
114265
+ configSnapshotPath: join68(resolvedAgentDirForBootCard, ".config-snapshot.json"),
114266
+ bootCardStatePath: join68(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
113960
114267
  floodStatePath: FLOOD_STATE_PATH,
113961
114268
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
113962
114269
  }, ackMsgId);
@@ -113989,7 +114296,7 @@ async function startGateway() {
113989
114296
  if (smAgentDir) {
113990
114297
  const resolutionTimeoutMs = resolveSessionModelResolutionTimeoutMs(process.env.SWITCHROOM_SESSION_MODEL_RESOLUTION_TIMEOUT_MS);
113991
114298
  const resolved = await waitForSessionModelResolution({
113992
- barrierExists: () => existsSync57(join69(smAgentDir, ".session-model-resolved")),
114299
+ barrierExists: () => existsSync58(join68(smAgentDir, ".session-model-resolved")),
113993
114300
  timeoutMs: resolutionTimeoutMs
113994
114301
  });
113995
114302
  if (!resolved) {
@@ -113997,10 +114304,10 @@ async function startGateway() {
113997
114304
  process.stderr.write(`telegram gateway: gw /model relaunch UNRESOLVED agent=${getMyAgentName()} target=${target} (barrier timeout after ${resolutionTimeoutMs}ms)
113998
114305
  `);
113999
114306
  } else {
114000
- const activePath = join69(smAgentDir, ".active-session-model");
114001
- if (existsSync57(activePath)) {
114307
+ const activePath = join68(smAgentDir, ".active-session-model");
114308
+ if (existsSync58(activePath)) {
114002
114309
  try {
114003
- const launched = readFileSync60(activePath, "utf8").trim();
114310
+ const launched = readFileSync61(activePath, "utf8").trim();
114004
114311
  const configured = (() => {
114005
114312
  const d = switchroomExecJson(["agent", "list"]);
114006
114313
  const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
@@ -114033,24 +114340,24 @@ async function startGateway() {
114033
114340
  deliverModelSwitchBootNotice({
114034
114341
  ...modelBootCardDeps,
114035
114342
  confirmation,
114036
- hasSessionModelAlert: existsSync57(join69(smAgentDir, ".session-model-alert"))
114343
+ hasSessionModelAlert: existsSync58(join68(smAgentDir, ".session-model-alert"))
114037
114344
  });
114038
114345
  }
114039
114346
  } catch {}
114040
114347
  }
114041
- const activeEffortPath = join69(smAgentDir, ".active-session-effort");
114042
- if (existsSync57(activeEffortPath)) {
114348
+ const activeEffortPath = join68(smAgentDir, ".active-session-effort");
114349
+ if (existsSync58(activeEffortPath)) {
114043
114350
  try {
114044
- const launchedEffort = readFileSync60(activeEffortPath, "utf8").trim();
114351
+ const launchedEffort = readFileSync61(activeEffortPath, "utf8").trim();
114045
114352
  const configuredEffort = getConfiguredEffortForPersist();
114046
114353
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
114047
114354
  } catch {}
114048
114355
  }
114049
- const alertPath = join69(smAgentDir, ".session-model-alert");
114050
- if (existsSync57(alertPath)) {
114356
+ const alertPath = join68(smAgentDir, ".session-model-alert");
114357
+ if (existsSync58(alertPath)) {
114051
114358
  let alertText = null;
114052
114359
  try {
114053
- alertText = readFileSync60(alertPath, "utf8").trim();
114360
+ alertText = readFileSync61(alertPath, "utf8").trim();
114054
114361
  } catch {
114055
114362
  alertText = null;
114056
114363
  }