switchroom 0.19.41 → 0.19.43

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 (90) hide show
  1. package/dist/agent-scheduler/index.js +23 -3
  2. package/dist/auth-broker/index.js +54 -9
  3. package/dist/cli/notion-write-pretool.mjs +23 -3
  4. package/dist/cli/switchroom.js +1956 -900
  5. package/dist/host-control/main.js +227 -30
  6. package/dist/vault/approvals/kernel-server.js +54 -9
  7. package/dist/vault/broker/server.js +54 -9
  8. package/package.json +3 -3
  9. package/profiles/_base/start.sh.hbs +82 -4
  10. package/telegram-plugin/bridge/bridge.ts +1 -1
  11. package/telegram-plugin/card-layout.ts +63 -1
  12. package/telegram-plugin/dist/bridge/bridge.js +4 -1
  13. package/telegram-plugin/dist/gateway/gateway.js +1318 -693
  14. package/telegram-plugin/dist/server.js +5 -2
  15. package/telegram-plugin/flood-429-ledger.ts +5 -3
  16. package/telegram-plugin/flood-circuit-breaker.ts +11 -3
  17. package/telegram-plugin/format.ts +223 -13
  18. package/telegram-plugin/gateway/approval-callback-consume-record.test.ts +132 -0
  19. package/telegram-plugin/gateway/boot-card.ts +7 -2
  20. package/telegram-plugin/gateway/flood-reply-queue.ts +5 -0
  21. package/telegram-plugin/gateway/gateway.ts +56 -58
  22. package/telegram-plugin/gateway/handback-orphan-recovery.ts +69 -0
  23. package/telegram-plugin/gateway/handback-preturn-signal.ts +170 -32
  24. package/telegram-plugin/gateway/ipc-protocol.ts +31 -3
  25. package/telegram-plugin/gateway/ipc-server.ts +13 -4
  26. package/telegram-plugin/gateway/liveness-wiring.ts +12 -0
  27. package/telegram-plugin/gateway/model-command.ts +21 -109
  28. package/telegram-plugin/gateway/obligation-store.ts +55 -0
  29. package/telegram-plugin/gateway/outbound-send-path.ts +127 -11
  30. package/telegram-plugin/gateway/outbox-sweep.ts +371 -61
  31. package/telegram-plugin/gateway/rollout-status-edit.ts +175 -0
  32. package/telegram-plugin/gateway/stream-render.ts +62 -0
  33. package/telegram-plugin/gateway/update-announce.ts +11 -4
  34. package/telegram-plugin/hooks/audience-classify.d.mts +50 -0
  35. package/telegram-plugin/hooks/audience-classify.mjs +364 -0
  36. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +86 -3
  37. package/telegram-plugin/hooks/silent-end-scan.mjs +80 -4
  38. package/telegram-plugin/outbox.ts +103 -2
  39. package/telegram-plugin/render/rich-render.ts +10 -2
  40. package/telegram-plugin/reply-quote.ts +150 -0
  41. package/telegram-plugin/retry-api-call.ts +254 -11
  42. package/telegram-plugin/shared/bot-runtime.ts +69 -34
  43. package/telegram-plugin/shared/gw-trace-gate.ts +14 -2
  44. package/telegram-plugin/silent-end.ts +34 -2
  45. package/telegram-plugin/status-no-truncate.ts +31 -16
  46. package/telegram-plugin/stream-controller.ts +44 -9
  47. package/telegram-plugin/stream-reply-handler.ts +5 -3
  48. package/telegram-plugin/tests/boot-card-render.test.ts +2 -1
  49. package/telegram-plugin/tests/card-budget-never-overflows.test.ts +165 -0
  50. package/telegram-plugin/tests/card-hard-truncate-entity-safe.test.ts +153 -0
  51. package/telegram-plugin/tests/card-variants.golden.txt +9 -9
  52. package/telegram-plugin/tests/flood-reply-queue.test.ts +89 -4
  53. package/telegram-plugin/tests/format-consistency.test.ts +160 -14
  54. package/telegram-plugin/tests/framework-fallback-drains-parked.test.ts +134 -0
  55. package/telegram-plugin/tests/handback-orphan-recovery.test.ts +131 -0
  56. package/telegram-plugin/tests/handback-preturn-signal.test.ts +212 -17
  57. package/telegram-plugin/tests/helpers/liveness-wiring-fixture.ts +3 -0
  58. package/telegram-plugin/tests/nested-indent-idiom.test.ts +162 -0
  59. package/telegram-plugin/tests/nested-prefix-indent.test.ts +115 -0
  60. package/telegram-plugin/tests/no-robust-api-call-factory.test.ts +62 -0
  61. package/telegram-plugin/tests/obligation-store.test.ts +51 -0
  62. package/telegram-plugin/tests/outbox-audience-3865.test.ts +579 -0
  63. package/telegram-plugin/tests/outbox-provenance-4141.test.ts +1126 -0
  64. package/telegram-plugin/tests/outbox-sweep-single-flight.test.ts +138 -0
  65. package/telegram-plugin/tests/pinned-card-collapse.test.ts +12 -10
  66. package/telegram-plugin/tests/plain-fallback-4096-cap.test.ts +509 -0
  67. package/telegram-plugin/tests/reply-quote-wire.test.ts +433 -0
  68. package/telegram-plugin/tests/retry-grammy-http-error.test.ts +404 -0
  69. package/telegram-plugin/tests/rollout-narration-edit-socket.test.ts +170 -0
  70. package/telegram-plugin/tests/rollout-status-edit-retry-policy.test.ts +366 -0
  71. package/telegram-plugin/tests/rollout-status-edit.test.ts +140 -0
  72. package/telegram-plugin/tests/rollout-status-wiring.test.ts +40 -7
  73. package/telegram-plugin/tests/tg-post-logger-error-shape.test.ts +4 -0
  74. package/telegram-plugin/tests/tg-post-retry-attribution.test.ts +196 -0
  75. package/telegram-plugin/tests/tool-activity-summary.test.ts +23 -17
  76. package/telegram-plugin/tool-activity-summary.ts +58 -30
  77. package/telegram-plugin/uat/scenarios/jtbd-foreground-subagent-activity-dm.test.ts +3 -2
  78. package/vendor/hindsight-memory/CHANGELOG.md +28 -0
  79. package/vendor/hindsight-memory/docs/measurements/subagent-volume-gate-3994.md +84 -0
  80. package/vendor/hindsight-memory/scripts/backfill_transcripts.py +254 -40
  81. package/vendor/hindsight-memory/scripts/lib/content.py +11 -2
  82. package/vendor/hindsight-memory/scripts/recall.py +18 -3
  83. package/vendor/hindsight-memory/scripts/subagent_retain.py +59 -38
  84. package/vendor/hindsight-memory/scripts/tests/data/replay_volume_gate_3994.py +295 -0
  85. package/vendor/hindsight-memory/scripts/tests/test_backfill_from_logs.py +109 -0
  86. package/vendor/hindsight-memory/scripts/tests/test_overlap_tokens.py +135 -0
  87. package/vendor/hindsight-memory/scripts/tests/test_recall_no_lexical_gate.py +20 -23
  88. package/vendor/hindsight-memory/scripts/tests/test_recall_query_shaping.py +48 -0
  89. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +94 -1
  90. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain_learnings.py +98 -42
@@ -4407,11 +4407,11 @@ var init_protocol = __esm(() => {
4407
4407
  OkRevokeGrantResponseSchema,
4408
4408
  OkApprovalRequestResponseSchema,
4409
4409
  OkApprovalLookupResponseSchema,
4410
+ OkApprovalConsumeRecordResponseSchema,
4410
4411
  OkApprovalConsumeResponseSchema,
4411
4412
  OkApprovalRevokeResponseSchema,
4412
4413
  OkApprovalListResponseSchema,
4413
4414
  OkApprovalRecordResponseSchema,
4414
- OkApprovalConsumeRecordResponseSchema,
4415
4415
  ErrorResponseSchema
4416
4416
  ]);
4417
4417
  });
@@ -4556,6 +4556,7 @@ var init_schema = __esm(() => {
4556
4556
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
4557
4557
  file: exports_external.boolean().default(true).describe("Maintain a curated workspace MEMORY.md file (seeded once, " + "auto-loaded every turn). Set false for hindsight-only memory: " + "the file is not seeded or re-created, so once migrated into " + "Hindsight and deleted it stays gone. Recall + directives carry " + "the memory instead. Cascade: override (per-agent wins over default)."),
4558
4558
  isolation: exports_external.enum(["default", "strict"]).default("default").describe("strict = never shared cross-agent, default = eligible for reflect"),
4559
+ profile: exports_external.string().optional().describe("Memory profile bank this agent's curated memory defaults key off — " + "the built-in disposition + observations_mission in PROFILE_MEMORY_DEFAULTS. " + "Decouples the memory profile from `extends` (the filesystem persona " + "profile), so an agent on `extends: default` can opt into the `coding` " + "memory bundle via `memory.profile: coding` without inheriting the coding " + "persona. Resolution: memory.profile → extends → DEFAULT_PROFILE (see " + "resolveMemoryProfile). Unset ⇒ byte-identical to keying off `extends`."),
4559
4560
  bank_mission: exports_external.string().optional().describe("Bank-level mission statement used during recall to contextualize " + "results. NOTE: this is an alias for the Hindsight engine's " + "`reflect_mission` field (verified live: switchroom's bank_mission " + "lands in `config.reflect_mission`). Prefer `reflect_mission` going " + "forward; `bank_mission` is retained for back-compat. If both are " + "set, `reflect_mission` wins. Cascade: override."),
4560
4561
  reflect_mission: exports_external.string().optional().describe("Mission/context steering Hindsight Reflect operations (the bank's " + "'who am I / what matters' framing applied during recall). The " + "engine-accurate name for what `bank_mission` sets. Cascade: override."),
4561
4562
  retain_mission: exports_external.string().optional().describe("Instructions for the fact extraction LLM during retain. Cascade: override."),
@@ -4849,11 +4850,13 @@ var init_schema = __esm(() => {
4849
4850
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
4850
4851
  model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
4851
4852
  context_window: exports_external.number().int().positive().optional().describe("GLOBAL context window (tokens) of the backend serving hindsight's " + "LLM ops — the declared size switchroom derives every token budget " + "from. Set this to the real window of whatever you point " + "`hindsight.llm` at (e.g. 32768 for a llama.cpp slot launched with " + "`-c 65536 -np 2`, 131072 for a large-window OpenRouter model). " + "Absent → a per-provider default: 200000 for `claude-code`, a " + "conservative 32768 for everything else. Overflowing a local " + "backend's window does NOT error — llama.cpp context-shift silently " + "drops the system prompt and the model answers conversationally " + "with HTTP 200 — so this value is what makes the failure " + "detectable at setup time instead of never."),
4853
+ base_url: exports_external.string().min(1).optional().describe("GLOBAL LLM base URL (upstream `HINDSIGHT_API_LLM_BASE_URL`). The " + "default endpoint every op inherits absent a per-op " + "`hindsight.llm.<op>.base_url`. Optional passthrough; unset → the " + "engine's provider default. When this points at host loopback the " + "hindsight container is forced onto host networking, same as a " + "per-op base URL, so the endpoint stays reachable (#3687)."),
4854
+ api_key: exports_external.string().min(1).optional().describe("GLOBAL LLM API key (upstream `HINDSIGHT_API_LLM_API_KEY`). Literal " + "or `vault:` reference. The default credential every op inherits " + "absent a per-op `hindsight.llm.<op>.api_key`; the engine reads it as " + "a plain env fallback. Optional passthrough (#3687)."),
4852
4855
  retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent → uses the global model/provider."),
4853
4856
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
4854
4857
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
4855
4858
  }).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."),
4856
- env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_MAX_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_MAX_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_MAX_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
4859
+ env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY — the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE — the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT — upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
4857
4860
  });
4858
4861
  MicrosoftWorkspaceConfigSchema = exports_external.object({
4859
4862
  microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
@@ -4985,6 +4988,7 @@ var init_schema = __esm(() => {
4985
4988
  auto_recall: exports_external.boolean().optional(),
4986
4989
  file: exports_external.boolean().optional(),
4987
4990
  isolation: exports_external.enum(["default", "strict"]).optional(),
4991
+ profile: exports_external.string().optional(),
4988
4992
  directive_capture_nudge: exports_external.boolean().optional(),
4989
4993
  observation_scopes: ObservationScopesSchema,
4990
4994
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -5026,7 +5030,23 @@ var init_schema = __esm(() => {
5026
5030
  max_query_chars: exports_external.number().int().min(1).optional(),
5027
5031
  parallel: exports_external.boolean().optional(),
5028
5032
  additional_banks: exports_external.array(exports_external.string()).optional(),
5029
- sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
5033
+ sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional(),
5034
+ types: exports_external.array(exports_external.string()).optional(),
5035
+ skip_trivial: exports_external.boolean().optional(),
5036
+ topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional()
5037
+ }).optional(),
5038
+ retain: exports_external.object({
5039
+ every_n_turns: exports_external.number().int().min(1).optional(),
5040
+ overlap_turns: exports_external.number().int().min(0).optional()
5041
+ }).optional(),
5042
+ bank_mission: exports_external.string().optional(),
5043
+ reflect_mission: exports_external.string().optional(),
5044
+ retain_mission: exports_external.string().optional(),
5045
+ observations_mission: exports_external.string().optional(),
5046
+ disposition: exports_external.object({
5047
+ skepticism: exports_external.number().int().min(1).max(5).optional(),
5048
+ literalism: exports_external.number().int().min(1).max(5).optional(),
5049
+ empathy: exports_external.number().int().min(1).max(5).optional()
5030
5050
  }).optional()
5031
5051
  }).optional(),
5032
5052
  schedule: exports_external.array(ScheduleEntrySchema).optional(),
@@ -19393,7 +19413,9 @@ var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
19393
19413
  var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
19394
19414
  var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
19395
19415
  var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
19396
- var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
19416
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS = 1;
19417
+ var HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3;
19418
+ var HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false";
19397
19419
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
19398
19420
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
19399
19421
  var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
@@ -19439,8 +19461,8 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
19439
19461
  String(HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S)
19440
19462
  ],
19441
19463
  [
19442
- "HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS",
19443
- String(HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS)
19464
+ "HINDSIGHT_API_WORKER_CONSOLIDATION_RESERVED_SLOTS",
19465
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS)
19444
19466
  ],
19445
19467
  [
19446
19468
  "HINDSIGHT_API_WORKER_CONSOLIDATION_SLOT_LIMIT",
@@ -19457,6 +19479,10 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
19457
19479
  [
19458
19480
  "HINDSIGHT_API_RECENCY_DECAY_HALFLIFE_DAYS",
19459
19481
  String(HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS)
19482
+ ],
19483
+ [
19484
+ "HINDSIGHT_API_GRAPH_SEED_MIN_SIMILARITY",
19485
+ String(HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY)
19460
19486
  ]
19461
19487
  ];
19462
19488
  var HINDSIGHT_PERF_DEFAULTS_GPU = [
@@ -19477,22 +19503,41 @@ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
19477
19503
  String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
19478
19504
  ],
19479
19505
  ["HINDSIGHT_API_LLM_STRICT_SCHEMA", HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA],
19480
- ["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)]
19506
+ ["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)],
19507
+ ["HINDSIGHT_API_LLM_SUPPORTS_MAX_ITEMS", HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS]
19481
19508
  ];
19482
19509
  var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
19483
19510
  "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY",
19484
19511
  "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP",
19485
19512
  "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS",
19486
19513
  "HINDSIGHT_API_WORKER_MAX_SLOTS",
19487
- "HINDSIGHT_API_WORKER_RETAIN_MAX_SLOTS"
19514
+ "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS",
19515
+ "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY",
19516
+ "HINDSIGHT_MCP_RECALL_BUDGET_MODE",
19517
+ "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT",
19518
+ "HINDSIGHT_API_RETAIN_WALL_TIMEOUT"
19488
19519
  ]);
19520
+ var HINDSIGHT_WORKER_SLOT_TYPES = [
19521
+ "consolidation",
19522
+ "file_convert_retain",
19523
+ "graph_maintenance",
19524
+ "import_documents",
19525
+ "refresh_mental_model",
19526
+ "retain"
19527
+ ];
19528
+ var HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES = new Map(HINDSIGHT_WORKER_SLOT_TYPES.map((type) => [
19529
+ `HINDSIGHT_API_WORKER_${type.toUpperCase()}_MAX_SLOTS`,
19530
+ `HINDSIGHT_API_WORKER_${type.toUpperCase()}_RESERVED_SLOTS`
19531
+ ]));
19489
19532
  var HINDSIGHT_PERF_ENV_KEYS = new Set([
19490
19533
  ...[
19491
19534
  ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
19492
19535
  ...HINDSIGHT_PERF_DEFAULTS_GPU,
19493
19536
  ...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
19494
19537
  ].map(([k]) => k),
19495
- ...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS
19538
+ ...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS,
19539
+ ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.keys(),
19540
+ ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values()
19496
19541
  ]);
19497
19542
 
19498
19543
  // src/setup/hindsight-context-budget.ts
@@ -4152,6 +4152,7 @@ var init_schema = __esm(() => {
4152
4152
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
4153
4153
  file: exports_external.boolean().default(true).describe("Maintain a curated workspace MEMORY.md file (seeded once, " + "auto-loaded every turn). Set false for hindsight-only memory: " + "the file is not seeded or re-created, so once migrated into " + "Hindsight and deleted it stays gone. Recall + directives carry " + "the memory instead. Cascade: override (per-agent wins over default)."),
4154
4154
  isolation: exports_external.enum(["default", "strict"]).default("default").describe("strict = never shared cross-agent, default = eligible for reflect"),
4155
+ profile: exports_external.string().optional().describe("Memory profile bank this agent's curated memory defaults key off — " + "the built-in disposition + observations_mission in PROFILE_MEMORY_DEFAULTS. " + "Decouples the memory profile from `extends` (the filesystem persona " + "profile), so an agent on `extends: default` can opt into the `coding` " + "memory bundle via `memory.profile: coding` without inheriting the coding " + "persona. Resolution: memory.profile → extends → DEFAULT_PROFILE (see " + "resolveMemoryProfile). Unset ⇒ byte-identical to keying off `extends`."),
4155
4156
  bank_mission: exports_external.string().optional().describe("Bank-level mission statement used during recall to contextualize " + "results. NOTE: this is an alias for the Hindsight engine's " + "`reflect_mission` field (verified live: switchroom's bank_mission " + "lands in `config.reflect_mission`). Prefer `reflect_mission` going " + "forward; `bank_mission` is retained for back-compat. If both are " + "set, `reflect_mission` wins. Cascade: override."),
4156
4157
  reflect_mission: exports_external.string().optional().describe("Mission/context steering Hindsight Reflect operations (the bank's " + "'who am I / what matters' framing applied during recall). The " + "engine-accurate name for what `bank_mission` sets. Cascade: override."),
4157
4158
  retain_mission: exports_external.string().optional().describe("Instructions for the fact extraction LLM during retain. Cascade: override."),
@@ -4445,11 +4446,13 @@ var init_schema = __esm(() => {
4445
4446
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
4446
4447
  model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
4447
4448
  context_window: exports_external.number().int().positive().optional().describe("GLOBAL context window (tokens) of the backend serving hindsight's " + "LLM ops — the declared size switchroom derives every token budget " + "from. Set this to the real window of whatever you point " + "`hindsight.llm` at (e.g. 32768 for a llama.cpp slot launched with " + "`-c 65536 -np 2`, 131072 for a large-window OpenRouter model). " + "Absent → a per-provider default: 200000 for `claude-code`, a " + "conservative 32768 for everything else. Overflowing a local " + "backend's window does NOT error — llama.cpp context-shift silently " + "drops the system prompt and the model answers conversationally " + "with HTTP 200 — so this value is what makes the failure " + "detectable at setup time instead of never."),
4449
+ base_url: exports_external.string().min(1).optional().describe("GLOBAL LLM base URL (upstream `HINDSIGHT_API_LLM_BASE_URL`). The " + "default endpoint every op inherits absent a per-op " + "`hindsight.llm.<op>.base_url`. Optional passthrough; unset → the " + "engine's provider default. When this points at host loopback the " + "hindsight container is forced onto host networking, same as a " + "per-op base URL, so the endpoint stays reachable (#3687)."),
4450
+ api_key: exports_external.string().min(1).optional().describe("GLOBAL LLM API key (upstream `HINDSIGHT_API_LLM_API_KEY`). Literal " + "or `vault:` reference. The default credential every op inherits " + "absent a per-op `hindsight.llm.<op>.api_key`; the engine reads it as " + "a plain env fallback. Optional passthrough (#3687)."),
4448
4451
  retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent → uses the global model/provider."),
4449
4452
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
4450
4453
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
4451
4454
  }).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."),
4452
- env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_MAX_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_MAX_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_MAX_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
4455
+ env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY — the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE — the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT — upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
4453
4456
  });
4454
4457
  MicrosoftWorkspaceConfigSchema = exports_external.object({
4455
4458
  microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
@@ -4581,6 +4584,7 @@ var init_schema = __esm(() => {
4581
4584
  auto_recall: exports_external.boolean().optional(),
4582
4585
  file: exports_external.boolean().optional(),
4583
4586
  isolation: exports_external.enum(["default", "strict"]).optional(),
4587
+ profile: exports_external.string().optional(),
4584
4588
  directive_capture_nudge: exports_external.boolean().optional(),
4585
4589
  observation_scopes: ObservationScopesSchema,
4586
4590
  observation_scope_strategy: ObservationScopeStrategySchema,
@@ -4622,7 +4626,23 @@ var init_schema = __esm(() => {
4622
4626
  max_query_chars: exports_external.number().int().min(1).optional(),
4623
4627
  parallel: exports_external.boolean().optional(),
4624
4628
  additional_banks: exports_external.array(exports_external.string()).optional(),
4625
- sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
4629
+ sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional(),
4630
+ types: exports_external.array(exports_external.string()).optional(),
4631
+ skip_trivial: exports_external.boolean().optional(),
4632
+ topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional()
4633
+ }).optional(),
4634
+ retain: exports_external.object({
4635
+ every_n_turns: exports_external.number().int().min(1).optional(),
4636
+ overlap_turns: exports_external.number().int().min(0).optional()
4637
+ }).optional(),
4638
+ bank_mission: exports_external.string().optional(),
4639
+ reflect_mission: exports_external.string().optional(),
4640
+ retain_mission: exports_external.string().optional(),
4641
+ observations_mission: exports_external.string().optional(),
4642
+ disposition: exports_external.object({
4643
+ skepticism: exports_external.number().int().min(1).max(5).optional(),
4644
+ literalism: exports_external.number().int().min(1).max(5).optional(),
4645
+ empathy: exports_external.number().int().min(1).max(5).optional()
4626
4646
  }).optional()
4627
4647
  }).optional(),
4628
4648
  schedule: exports_external.array(ScheduleEntrySchema).optional(),
@@ -19406,11 +19426,11 @@ var init_protocol = __esm(() => {
19406
19426
  OkRevokeGrantResponseSchema,
19407
19427
  OkApprovalRequestResponseSchema,
19408
19428
  OkApprovalLookupResponseSchema,
19429
+ OkApprovalConsumeRecordResponseSchema,
19409
19430
  OkApprovalConsumeResponseSchema,
19410
19431
  OkApprovalRevokeResponseSchema,
19411
19432
  OkApprovalListResponseSchema,
19412
19433
  OkApprovalRecordResponseSchema,
19413
- OkApprovalConsumeRecordResponseSchema,
19414
19434
  ErrorResponseSchema
19415
19435
  ]);
19416
19436
  });
@@ -19806,7 +19826,9 @@ var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
19806
19826
  var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
19807
19827
  var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
19808
19828
  var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
19809
- var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
19829
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS = 1;
19830
+ var HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3;
19831
+ var HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false";
19810
19832
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
19811
19833
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
19812
19834
  var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
@@ -19852,8 +19874,8 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
19852
19874
  String(HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S)
19853
19875
  ],
19854
19876
  [
19855
- "HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS",
19856
- String(HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS)
19877
+ "HINDSIGHT_API_WORKER_CONSOLIDATION_RESERVED_SLOTS",
19878
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS)
19857
19879
  ],
19858
19880
  [
19859
19881
  "HINDSIGHT_API_WORKER_CONSOLIDATION_SLOT_LIMIT",
@@ -19870,6 +19892,10 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
19870
19892
  [
19871
19893
  "HINDSIGHT_API_RECENCY_DECAY_HALFLIFE_DAYS",
19872
19894
  String(HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS)
19895
+ ],
19896
+ [
19897
+ "HINDSIGHT_API_GRAPH_SEED_MIN_SIMILARITY",
19898
+ String(HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY)
19873
19899
  ]
19874
19900
  ];
19875
19901
  var HINDSIGHT_PERF_DEFAULTS_GPU = [
@@ -19890,22 +19916,41 @@ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
19890
19916
  String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
19891
19917
  ],
19892
19918
  ["HINDSIGHT_API_LLM_STRICT_SCHEMA", HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA],
19893
- ["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)]
19919
+ ["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)],
19920
+ ["HINDSIGHT_API_LLM_SUPPORTS_MAX_ITEMS", HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS]
19894
19921
  ];
19895
19922
  var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
19896
19923
  "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY",
19897
19924
  "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP",
19898
19925
  "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS",
19899
19926
  "HINDSIGHT_API_WORKER_MAX_SLOTS",
19900
- "HINDSIGHT_API_WORKER_RETAIN_MAX_SLOTS"
19927
+ "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS",
19928
+ "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY",
19929
+ "HINDSIGHT_MCP_RECALL_BUDGET_MODE",
19930
+ "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT",
19931
+ "HINDSIGHT_API_RETAIN_WALL_TIMEOUT"
19901
19932
  ]);
19933
+ var HINDSIGHT_WORKER_SLOT_TYPES = [
19934
+ "consolidation",
19935
+ "file_convert_retain",
19936
+ "graph_maintenance",
19937
+ "import_documents",
19938
+ "refresh_mental_model",
19939
+ "retain"
19940
+ ];
19941
+ var HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES = new Map(HINDSIGHT_WORKER_SLOT_TYPES.map((type) => [
19942
+ `HINDSIGHT_API_WORKER_${type.toUpperCase()}_MAX_SLOTS`,
19943
+ `HINDSIGHT_API_WORKER_${type.toUpperCase()}_RESERVED_SLOTS`
19944
+ ]));
19902
19945
  var HINDSIGHT_PERF_ENV_KEYS = new Set([
19903
19946
  ...[
19904
19947
  ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
19905
19948
  ...HINDSIGHT_PERF_DEFAULTS_GPU,
19906
19949
  ...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
19907
19950
  ].map(([k]) => k),
19908
- ...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS
19951
+ ...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS,
19952
+ ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.keys(),
19953
+ ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values()
19909
19954
  ]);
19910
19955
 
19911
19956
  // src/setup/hindsight-context-budget.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "switchroom",
3
3
  "//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
4
- "version": "0.19.41",
4
+ "version": "0.19.43",
5
5
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -24,9 +24,9 @@
24
24
  "build": "node scripts/build.mjs",
25
25
  "build:cli": "node scripts/build.mjs && bun build --compile --target=bun-linux-x64 --minify bin/switchroom.ts --outfile switchroom-linux-amd64",
26
26
  "pretest": "npm run build",
27
- "test": "vitest run && bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
27
+ "test": "vitest run && bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/rollout-narration-edit-socket.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
28
28
  "test:vitest": "vitest run",
29
- "test:bun": "bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/self-approval-bypass.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
29
+ "test:bun": "bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/self-approval-bypass.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/rollout-narration-edit-socket.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
30
30
  "test:watch": "vitest",
31
31
  "lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-test-runner-coverage.mjs && node scripts/check-bun-module-mock-scope.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-auth-test-hermeticity.mjs && node scripts/check-agent-state-dir-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-mcp-instructions-budget.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-retry-flood-hooks.mjs && node scripts/check-callback-ctx-wrapping.mjs && node scripts/check-status-pin-single-path.mjs && node scripts/check-litellm-config-guard.mjs && node scripts/check-release-asset-names.mjs && node scripts/check-agent-attribution-trailers.mjs && node scripts/check-hindsight-write-redaction.mjs && bun scripts/check-secret-pattern-parity.ts",
32
32
  "lint:tsc": "tsc --noEmit",
@@ -2002,10 +2002,88 @@ esac
2002
2002
  # changed) appends to `.session-model-alert` so the gateway's boot relay
2003
2003
  # tells the operator once.
2004
2004
  #
2005
- # NB: the CONFIGURED default resolution is untouched (#3186 constraint)
2006
- # the configured `thinking_effort` (see #1978 /
2007
- # src/config/thinking-effort-risk.ts) resolves exactly as before.
2008
- _EFFECTIVE_EFFORT={{#if thinkingEffort}}'{{thinkingEffort}}'{{else}}''{{/if}}
2005
+ # --- Live configured-default resolution (effort sibling of the model block) ---
2006
+ #
2007
+ # The apply-time bake is only the LAST-DITCH fallback: editing
2008
+ # `thinking_effort:` in switchroom.yaml and restarting must take effect
2009
+ # WITHOUT a full `switchroom apply`, exactly as `model:` already does. Before
2010
+ # this, model was live-read and effort was bake-only, so a yaml effort change
2011
+ # needed an apply to re-render every start.sh — and the gateway's /effort menu
2012
+ # (which derives its configured default from `agent list --json`, i.e. the LIVE
2013
+ # yaml) disagreed with what the launcher actually passed to `--effort`.
2014
+ #
2015
+ # Same resolver as the gateway, asked as a verb (`switchroom agent
2016
+ # effective-effort`), never re-implemented in shell. Fallback chain, never
2017
+ # silent past step 1:
2018
+ # 1. live read of $SWITCHROOM_CONFIG (one bounded retry — a host-side
2019
+ # in-place rewrite can present a torn file for a moment);
2020
+ # 2. `.configured-default-effort` — the last-known-good live value this
2021
+ # launcher recorded on every prior boot (read BEFORE it is overwritten);
2022
+ # 3. the apply-time bake.
2023
+ # Steps 2/3 append to `.session-model-alert` so the gateway tells the operator
2024
+ # once at boot. No SWITCHROOM_CONFIG mount at all means there is nothing to
2025
+ # read live — the bake IS the truth there (stderr note only).
2026
+ #
2027
+ # Unlike the model sibling there is no routing class to flip: effort is a pure
2028
+ # CLI flag, so a live change is always safe to apply on a bare restart.
2029
+ #
2030
+ # NB {{thinkingEffort}} renders inside single quotes here (the scaffold does
2031
+ # not pre-quote it, unlike modelQ), and an unset value renders as '' — which
2032
+ # means "no --effort flag", handled by the $_EFFORT_ARG guard below.
2033
+ _BAKED_EFFORT={{#if thinkingEffort}}'{{thinkingEffort}}'{{else}}''{{/if}}
2034
+ _LKG_EFFORT="$(cat "{{agentDir}}/.configured-default-effort" 2>/dev/null | tr -d '[:space:]' || true)"
2035
+ _EFFECTIVE_EFFORT=""
2036
+ # Set only when step 1 actually produced a live value — it gates the
2037
+ # `.configured-default-effort` write below so the last-known-good file can
2038
+ # never record a value that was never live truth.
2039
+ _LE_LIVE_OK=""
2040
+ if [ -n "$SWITCHROOM_CONFIG" ] && [ -f "$SWITCHROOM_CONFIG" ] && command -v switchroom >/dev/null 2>&1; then
2041
+ _le_live=""
2042
+ for _le_try in 1 2; do
2043
+ _le_live="$(timeout 20 switchroom --config "$SWITCHROOM_CONFIG" agent effective-effort "{{name}}" 2>/dev/null | tail -n 1 | tr -d '[:space:]')" || _le_live=""
2044
+ # Allowlist gate — kept in sync with EFFORT_LEVELS in
2045
+ # telegram-plugin/gateway/effort-command.ts, byte-identical to the carrier
2046
+ # gate below. Anything else is treated as a failed read, not a value.
2047
+ if [ -n "$_le_live" ] && printf '%s' "$_le_live" | grep -Eq '^(low|medium|high|xhigh|max)$'; then
2048
+ break
2049
+ fi
2050
+ _le_live=""
2051
+ if [ "$_le_try" -eq 1 ]; then sleep 1; fi
2052
+ done
2053
+ if [ -n "$_le_live" ]; then
2054
+ _EFFECTIVE_EFFORT="$_le_live"
2055
+ _LE_LIVE_OK=1
2056
+ if [ "$_le_live" != "$_BAKED_EFFORT" ]; then
2057
+ echo "session-effort: live configured thinking_effort '$_le_live' differs from apply-time bake '${_BAKED_EFFORT:-<unset>}' — using the live value (switchroom.yaml is the source of truth)" >&2
2058
+ fi
2059
+ elif [ -n "$_LKG_EFFORT" ] && printf '%s' "$_LKG_EFFORT" | grep -Eq '^(low|medium|high|xhigh|max)$'; then
2060
+ # Live read failed (torn write mid-read, yaml schema error, CLI wedge).
2061
+ # Last-known-good beats the bake: it was live truth on a prior boot, while
2062
+ # the bake can predate any number of yaml edits. Loud either way.
2063
+ _EFFECTIVE_EFFORT="$_LKG_EFFORT"
2064
+ echo "session-effort: live effort read FAILED or returned an unrecognised level — using last-known-good configured thinking_effort '$_LKG_EFFORT' (apply-time bake: '${_BAKED_EFFORT:-<unset>}')" >&2
2065
+ printf 'The live `thinking_effort:` could not be resolved at boot — switchroom.yaml could not be read, or it held a value this launcher does not recognise. The agent booted on the last-known-good configured effort `%s`. Check `thinking_effort:` in the yaml (low/medium/high/xhigh/max) and restart the agent.\n' "$_LKG_EFFORT" >> "{{agentDir}}/.session-model-alert" 2>/dev/null || true
2066
+ else
2067
+ _EFFECTIVE_EFFORT="$_BAKED_EFFORT"
2068
+ echo "session-effort: live effort read FAILED or returned an unrecognised level, and no last-known-good value — using apply-time baked thinking_effort '${_BAKED_EFFORT:-<unset>}'" >&2
2069
+ printf 'The live `thinking_effort:` could not be resolved at boot — switchroom.yaml could not be read, or it held a value this launcher does not recognise — and no prior boot recorded an effort. The agent booted on the apply-time default `%s`. Check `thinking_effort:` in the yaml (low/medium/high/xhigh/max) and restart the agent.\n' "${_BAKED_EFFORT:-<unset>}" >> "{{agentDir}}/.session-model-alert" 2>/dev/null || true
2070
+ fi
2071
+ unset _le_live _le_try
2072
+ else
2073
+ _EFFECTIVE_EFFORT="$_BAKED_EFFORT"
2074
+ fi
2075
+ unset _BAKED_EFFORT _LKG_EFFORT
2076
+ # Record the LIVE-RESOLVED configured default, BEFORE the carrier below can
2077
+ # override it — this is the last-known-good the fallback chain reads on the
2078
+ # NEXT boot, and it must be the configured value, not a session override.
2079
+ # Written ONLY when step 1 genuinely produced a live value: a bake/LKG-fallback
2080
+ # boot (or a boot with no config mount at all) has no live truth to record, and
2081
+ # writing the bake here would let a LATER failed read cite a "last-known-good"
2082
+ # that was never read from switchroom.yaml.
2083
+ if [ -n "$_LE_LIVE_OK" ]; then
2084
+ printf '%s\n' "$_EFFECTIVE_EFFORT" > "{{agentDir}}/.configured-default-effort" 2>/dev/null || true
2085
+ fi
2086
+ unset _LE_LIVE_OK
2009
2087
  if [ -f "{{agentDir}}/.session-effort" ]; then
2010
2088
  _sef="$(cat "{{agentDir}}/.session-effort" 2>/dev/null || true)"
2011
2089
  _se_level="$(printf '%s' "$_sef" | sed -n 's/.*"level"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
@@ -108,7 +108,7 @@ const TOOL_SCHEMAS = [
108
108
  format: { type: 'string', enum: ['html', 'markdownv2', 'text'], description: "Rendering mode. Default renders your text as rich GFM markdown (Bot API 10.1). 'text' sends plain text verbatim. 'html' and 'markdownv2' are legacy aliases that route through the same single rich GFM path — no separate HTML engine, no auto-escaping." },
109
109
  disable_web_page_preview: { type: 'boolean', description: 'Disable link preview thumbnails. Default: true.' },
110
110
  protect_content: { type: 'boolean', description: 'When true, Telegram prevents the message from being forwarded or saved.' },
111
- quote_text: { type: 'string', description: 'Surgical quote: specific text to highlight from the reply_to message. Requires reply_to.' },
111
+ quote_text: { type: 'string', description: 'Surgical quote: specific text to highlight from the reply_to message. Must be an EXACT substring of that message (copy it verbatim, do not paraphrase; max 1024 chars) — a quote Telegram cannot find is dropped and the reply lands unquoted. Requires reply_to.' },
112
112
  disable_notification: { type: 'boolean', description: 'When true, Telegram delivers the message silently — no device ping for this user. Default false (pings). Use true for mid-turn updates ("still working through X") so only the final answer pings. Always omit (or pass false) on the final answer of a turn.' },
113
113
  inline_keyboard: {
114
114
  type: 'array',
@@ -30,6 +30,7 @@
30
30
  */
31
31
 
32
32
  import { escapeMarkdown, stripMarkdown, truncate, stackCardLines } from './card-format.js'
33
+ import { truncateMarkdownSafe } from './format.js'
33
34
  import { formatModelLabel } from './model-label.js'
34
35
  import { STATUS_CARD_CHAR_BUDGET, STATUS_LINE_MAX, STATUS_ROLLING_LINES } from './status-no-truncate.js'
35
36
 
@@ -313,6 +314,13 @@ export interface CardCandidate {
313
314
  * an oversized newest bullet), while the combined card drops its newest worker
314
315
  * row into `+M more working…`. The MECHANISM — render, measure, accept or go
315
316
  * deeper — exists only here.
317
+ *
318
+ * The return is UNCONDITIONALLY within `STATUS_CARD_CHAR_BUDGET` (#3833). The
319
+ * loop used to return the deepest render unmeasured, so a card whose FIXED
320
+ * chrome/footer alone exceeds the budget (a 40k-char worker description — the
321
+ * #3682 repro) escaped over the wire limit and the send failed. No shrink level
322
+ * can rescue that case by construction — every level keeps the chrome — so the
323
+ * last resort hard-truncates. A clipped card beats no card.
316
324
  */
317
325
  export function fitCardToBudget(
318
326
  build: (level: number) => CardCandidate,
@@ -324,5 +332,59 @@ export function fitCardToBudget(
324
332
  text = renderCardSpec(candidate.spec)
325
333
  if (candidate.overflow !== true && text.length <= STATUS_CARD_CHAR_BUDGET) return text
326
334
  }
327
- return text
335
+ return hardTruncateCard(text)
336
+ }
337
+
338
+ /**
339
+ * Last-resort clip for a rendered card that no shrink level could fit — the
340
+ * only place a card's text is cut without going through a spec.
341
+ *
342
+ * Keeps as many WHOLE lines as fit, so line structure survives, and then runs
343
+ * the chosen cut through {@link truncateMarkdownSafe} so the emitted text is
344
+ * always PARSEABLE markdown (#4116). Two things make that necessary rather
345
+ * than decorative:
346
+ *
347
+ * - a first line longer than the whole budget keeps no whole line at all, so
348
+ * the cut lands mid-line — between a `**` pair, inside a `` ` `` span, in
349
+ * the middle of a `[label](href)` — and Telegram parse-REJECTS the result.
350
+ * The path that exists to guarantee delivery would itself fail the send.
351
+ * - even a whole-line cut can leave a FENCED block open, which is a
352
+ * multi-line entity and therefore invisible to a per-line rule.
353
+ *
354
+ * The tail is then cleaned of the stack's own break chrome (hard-break spaces /
355
+ * `COLLAPSE_SAFE_SEPARATOR`) and of a dangling lone surrogate, so the wire
356
+ * string is never invalid UTF-16.
357
+ *
358
+ * Exported for the budget tests; production callers go through
359
+ * {@link fitCardToBudget}.
360
+ */
361
+ export function hardTruncateCard(
362
+ text: string,
363
+ budget: number = STATUS_CARD_CHAR_BUDGET,
364
+ ): string {
365
+ if (text.length <= budget) return text
366
+ // The break chrome is measured on the KEPT prefix, not on the final line:
367
+ // a line that is followed by a break carries ` \n` (+ separator) which is
368
+ // stripped once it becomes the last line, so a prefix can fit after the
369
+ // strip even when its raw length is a few chars over.
370
+ let kept = ''
371
+ for (const line of text.split('\n')) {
372
+ const next = kept.length === 0 ? line : `${kept}\n${line}`
373
+ if (trimBreakChrome(next).length > budget) break
374
+ kept = next
375
+ }
376
+ // A first line longer than the budget keeps nothing; cut into it rather than
377
+ // emit a blank card. Either way the cut is taken against the FULL text — an
378
+ // entity straddling the boundary is only visible from there.
379
+ const target = kept.length > 0 ? kept.length : Math.min(budget, text.length)
380
+ let out = trimBreakChrome(truncateMarkdownSafe(text, target))
381
+ if (out.length > budget) out = trimBreakChrome(truncateMarkdownSafe(text, budget))
382
+ // Finally, never leave a lone high surrogate from slicing through an astral
383
+ // character.
384
+ return /[\uD800-\uDBFF]$/.test(out) ? out.slice(0, -1) : out
385
+ }
386
+
387
+ /** Strip a stacked card's trailing hard-break spaces / collapse separator. */
388
+ function trimBreakChrome(text: string): string {
389
+ return text.replace(/[ \t\r\u00A0]+$/, '')
328
390
  }
@@ -22970,6 +22970,9 @@ import {
22970
22970
  import { homedir as homedir2 } from "os";
22971
22971
  import { basename, join as join3 } from "path";
22972
22972
 
22973
+ // format.ts
22974
+ var DELIMITER_RUN_CHARS = new Set(["*", "_", "`", "~", "|"]);
22975
+
22973
22976
  // text-voice-scrub.ts
22974
22977
  var NULL = "\x00";
22975
22978
  var FENCE_PH = `${NULL}VS_FENCE`;
@@ -25083,7 +25086,7 @@ var TOOL_SCHEMAS = [
25083
25086
  format: { type: "string", enum: ["html", "markdownv2", "text"], description: "Rendering mode. Default renders your text as rich GFM markdown (Bot API 10.1). 'text' sends plain text verbatim. 'html' and 'markdownv2' are legacy aliases that route through the same single rich GFM path \u2014 no separate HTML engine, no auto-escaping." },
25084
25087
  disable_web_page_preview: { type: "boolean", description: "Disable link preview thumbnails. Default: true." },
25085
25088
  protect_content: { type: "boolean", description: "When true, Telegram prevents the message from being forwarded or saved." },
25086
- quote_text: { type: "string", description: "Surgical quote: specific text to highlight from the reply_to message. Requires reply_to." },
25089
+ quote_text: { type: "string", description: "Surgical quote: specific text to highlight from the reply_to message. Must be an EXACT substring of that message (copy it verbatim, do not paraphrase; max 1024 chars) \u2014 a quote Telegram cannot find is dropped and the reply lands unquoted. Requires reply_to." },
25087
25090
  disable_notification: { type: "boolean", description: 'When true, Telegram delivers the message silently \u2014 no device ping for this user. Default false (pings). Use true for mid-turn updates ("still working through X") so only the final answer pings. Always omit (or pass false) on the final answer of a turn.' },
25088
25091
  inline_keyboard: {
25089
25092
  type: "array",