switchroom 0.19.22 → 0.19.24

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 (51) hide show
  1. package/dist/agent-scheduler/index.js +5 -2
  2. package/dist/auth-broker/index.js +95 -2
  3. package/dist/cli/notion-write-pretool.mjs +5 -2
  4. package/dist/cli/switchroom.js +749 -357
  5. package/dist/host-control/main.js +96 -3
  6. package/dist/vault/approvals/kernel-server.js +98 -5
  7. package/dist/vault/broker/server.js +98 -5
  8. package/package.json +5 -4
  9. package/profiles/_base/start.sh.hbs +101 -0
  10. package/profiles/_shared/agent-self-service.md.hbs +64 -109
  11. package/profiles/_shared/delegation-golden-rule.md.hbs +5 -5
  12. package/profiles/_shared/dev-protocol.md.hbs +12 -42
  13. package/profiles/_shared/execution-discipline.md.hbs +7 -14
  14. package/profiles/coding/CLAUDE.md.hbs +0 -6
  15. package/profiles/default/CLAUDE.md.hbs +21 -50
  16. package/skills/dev-protocol/SKILL.md +97 -107
  17. package/skills/switchroom-release/SKILL.md +2 -1
  18. package/telegram-plugin/bunfig.toml +10 -0
  19. package/telegram-plugin/dist/gateway/gateway.js +267 -52
  20. package/telegram-plugin/gateway/backstop-delivery.ts +97 -16
  21. package/telegram-plugin/gateway/captured-answer-resume.ts +46 -17
  22. package/telegram-plugin/gateway/gateway.ts +43 -42
  23. package/telegram-plugin/gateway/latest-turn-lookup.ts +60 -0
  24. package/telegram-plugin/gateway/outbound-send-path.ts +61 -22
  25. package/telegram-plugin/gateway/stream-render.ts +6 -0
  26. package/telegram-plugin/gateway/subagent-handback-marker.ts +1 -1
  27. package/telegram-plugin/gateway/turn-end.ts +1 -1
  28. package/telegram-plugin/gateway/turn-record-status.ts +19 -0
  29. package/telegram-plugin/gateway/turns-jsonl-rotate.ts +65 -0
  30. package/telegram-plugin/reply-owner-resolve.ts +110 -9
  31. package/telegram-plugin/send-gate-degraded.test.ts +45 -16
  32. package/telegram-plugin/send-gate.ts +185 -24
  33. package/telegram-plugin/tests/activity-card-send-gate.test.ts +9 -9
  34. package/telegram-plugin/tests/agent-state-dir-preload.test.ts +33 -0
  35. package/telegram-plugin/tests/backstop-delivery.test.ts +204 -7
  36. package/telegram-plugin/tests/backstop-readback-probe.test.ts +12 -0
  37. package/telegram-plugin/tests/captured-answer-resume.test.ts +104 -0
  38. package/telegram-plugin/tests/latest-turn-lookup.test.ts +77 -0
  39. package/telegram-plugin/tests/narrative-lane-golden.test.ts +23 -1
  40. package/telegram-plugin/tests/reply-owner-resolve.test.ts +531 -0
  41. package/telegram-plugin/tests/send-reply-golden.test.ts +296 -28
  42. package/telegram-plugin/tests/stream-controller-send-gate.test.ts +134 -28
  43. package/telegram-plugin/tests/stream-render-golden.test.ts +25 -3
  44. package/telegram-plugin/tests/turns-jsonl-rotate.test.ts +92 -1
  45. package/vendor/hindsight-memory/scripts/drain_pending.py +113 -11
  46. package/vendor/hindsight-memory/scripts/lib/pending.py +802 -65
  47. package/vendor/hindsight-memory/scripts/lib/retain_split.py +54 -7
  48. package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +1445 -11
  49. package/vendor/hindsight-memory/scripts/tests/test_retain_split.py +78 -6
  50. package/vendor/hindsight-memory/tests/test_drain_pending.py +17 -2
  51. package/vendor/hindsight-memory/tests/test_pending.py +12 -4
@@ -21042,16 +21042,19 @@ var init_schema = __esm(() => {
21042
21042
  model: exports_external.string().min(1).optional().describe("Per-op model (upstream `HINDSIGHT_API_<OP>_LLM_MODEL`). Absent \u2192 " + "inherit the global `hindsight.llm.model`."),
21043
21043
  provider: exports_external.string().min(1).optional().describe("Per-op provider (upstream `HINDSIGHT_API_<OP>_LLM_PROVIDER`). " + "Absent \u2192 inherit the global `hindsight.llm.provider`."),
21044
21044
  base_url: exports_external.string().min(1).optional().describe("Per-op base URL (upstream `HINDSIGHT_API_<OP>_LLM_BASE_URL`). " + "Optional passthrough; absent \u2192 inherit the global."),
21045
- api_key: exports_external.string().min(1).optional().describe("Per-op API key (upstream `HINDSIGHT_API_<OP>_LLM_API_KEY`). Literal " + "or `vault:` reference. Optional passthrough; absent \u2192 inherit global.")
21045
+ api_key: exports_external.string().min(1).optional().describe("Per-op API key (upstream `HINDSIGHT_API_<OP>_LLM_API_KEY`). Literal " + "or `vault:` reference. Optional passthrough; absent \u2192 inherit global."),
21046
+ context_window: exports_external.number().int().positive().optional().describe("Context window (tokens) of the backend serving THIS op. NOT an " + "upstream env var \u2014 switchroom derives the op's token budget " + "(consolidation batch size / max-completion caps / reflect " + "max-context cap) from it so a single call can never overflow the " + "window. Absent \u2192 inherit " + "`hindsight.llm.context_window`, else a per-provider default " + "(conservative for non-`claude-code` providers, which usually mean " + "a local llama.cpp/Ollama slot; a self-hosted `base_url` \u2014 loopback, " + "RFC1918, `.local`/`.internal` \u2014 forces the conservative default too, " + "regardless of the provider NAME, since the endpoint is where the " + "traffic actually terminates). All three lanes (`retain`, " + "`reflect`, `consolidation`) are budgeted independently.")
21046
21047
  }).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback \u2014 switchroom emits only the vars set.");
21047
21048
  HindsightConfigSchema = exports_external.object({
21048
21049
  llm: exports_external.object({
21049
21050
  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."),
21050
21051
  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."),
21052
+ context_window: exports_external.number().int().positive().optional().describe("GLOBAL context window (tokens) of the backend serving hindsight's " + "LLM ops \u2014 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 \u2192 a per-provider default: 200000 for `claude-code`, a " + "conservative 32768 for everything else. Overflowing a local " + "backend's window does NOT error \u2014 llama.cpp context-shift silently " + "drops the system prompt and the model answers conversationally " + "with HTTP 200 \u2014 so this value is what makes the failure " + "detectable at setup time instead of never."),
21051
21053
  retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent \u2192 uses the global model/provider."),
21052
21054
  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."),
21053
21055
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
21054
- }).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.")
21056
+ }).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."),
21057
+ 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, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT), 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), 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).")
21055
21058
  });
21056
21059
  MicrosoftWorkspaceConfigSchema = exports_external.object({
21057
21060
  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)."),
@@ -63264,7 +63267,8 @@ function createSendGate(config) {
63264
63267
  pending: null,
63265
63268
  running: false,
63266
63269
  suppressedUntilMs: 0,
63267
- editWindowTs: []
63270
+ editWindowTs: [],
63271
+ criticalWake: null
63268
63272
  };
63269
63273
  perMessage.set(key, state);
63270
63274
  }
@@ -63350,9 +63354,11 @@ function createSendGate(config) {
63350
63354
  }
63351
63355
  }
63352
63356
  }
63353
- async function admitLoop(buckets, deadline) {
63357
+ async function admitLoop(buckets, deadline, interrupt) {
63354
63358
  let counted = false;
63355
63359
  for (;; ) {
63360
+ if (interrupt?.fired)
63361
+ return false;
63356
63362
  const now = clock.now();
63357
63363
  let wait = 0;
63358
63364
  for (const b of buckets)
@@ -63368,11 +63374,12 @@ function createSendGate(config) {
63368
63374
  counters.queued++;
63369
63375
  counted = true;
63370
63376
  }
63371
- await clock.sleep(deadline !== undefined ? Math.min(wait, deadline - now) : wait);
63377
+ const nap = clock.sleep(deadline !== undefined ? Math.min(wait, deadline - now) : wait);
63378
+ await (interrupt ? Promise.race([nap, interrupt.promise]) : nap);
63372
63379
  }
63373
63380
  }
63374
- function admit(buckets) {
63375
- return admitLoop(buckets);
63381
+ function admit(buckets, interrupt) {
63382
+ return admitLoop(buckets, undefined, interrupt);
63376
63383
  }
63377
63384
  let criticalTail = Promise.resolve();
63378
63385
  function criticalSerialize(job) {
@@ -63390,6 +63397,24 @@ function createSendGate(config) {
63390
63397
  }
63391
63398
  })();
63392
63399
  }
63400
+ function makeInterrupt() {
63401
+ let resolve6;
63402
+ const interrupt = {
63403
+ fired: false,
63404
+ promise: new Promise((r) => {
63405
+ resolve6 = r;
63406
+ })
63407
+ };
63408
+ return {
63409
+ interrupt,
63410
+ fire: () => {
63411
+ if (interrupt.fired)
63412
+ return;
63413
+ interrupt.fired = true;
63414
+ resolve6();
63415
+ }
63416
+ };
63417
+ }
63393
63418
  async function admitPriority(buckets, priority) {
63394
63419
  const now = clock.now();
63395
63420
  if (priority === "cosmetic") {
@@ -63487,7 +63512,18 @@ function createSendGate(config) {
63487
63512
  continue;
63488
63513
  }
63489
63514
  } else {
63490
- await admit(bucketsFor(opts));
63515
+ const { interrupt, fire } = makeInterrupt();
63516
+ state.criticalWake = fire;
63517
+ let admitted;
63518
+ try {
63519
+ admitted = await admit(bucketsFor(opts), interrupt);
63520
+ } finally {
63521
+ state.criticalWake = null;
63522
+ }
63523
+ if (!admitted) {
63524
+ supersede(state, p);
63525
+ continue;
63526
+ }
63491
63527
  }
63492
63528
  state.lastSentMs = clock.now();
63493
63529
  if (perMessageEditMaxPerWindow > 0 && p.priorityClass === "cosmetic") {
@@ -63512,6 +63548,31 @@ function createSendGate(config) {
63512
63548
  state.running = false;
63513
63549
  }
63514
63550
  }
63551
+ function supersede(state, p) {
63552
+ const next = state.pending;
63553
+ if (!next) {
63554
+ state.pending = p;
63555
+ return;
63556
+ }
63557
+ next.priorityClass = maxPriority(next.priorityClass, p.priorityClass);
63558
+ next.promise.then(p.resolve, p.reject);
63559
+ }
63560
+ function editUnderPressure(state, opts, now) {
63561
+ if (state.suppressedUntilMs > now)
63562
+ return true;
63563
+ for (const b of bucketsFor(opts)) {
63564
+ if (b.msUntilAvailable(now) > 0)
63565
+ return true;
63566
+ }
63567
+ return false;
63568
+ }
63569
+ function settleForCaller(state, pending, opts, priority, now) {
63570
+ if (priority !== "cosmetic" || !editUnderPressure(state, opts, now)) {
63571
+ return pending.promise;
63572
+ }
63573
+ pending.promise.catch(() => {});
63574
+ return Promise.resolve(undefined);
63575
+ }
63515
63576
  function handleEdit(fn, opts) {
63516
63577
  const messageId = opts.messageId;
63517
63578
  const key = messageKey(opts.chat_id, messageId);
@@ -63520,16 +63581,6 @@ function createSendGate(config) {
63520
63581
  const existing = perMessage.get(key);
63521
63582
  maybeEvict(now, existing === undefined);
63522
63583
  const state = existing ?? messageState(key);
63523
- if ((opts.priorityClass ?? "useful") === "cosmetic") {
63524
- let wait = 0;
63525
- for (const b of bucketsFor(opts))
63526
- wait = Math.max(wait, b.msUntilAvailable(now));
63527
- const msgWait = state.suppressedUntilMs > now ? state.suppressedUntilMs - now : 0;
63528
- if (wait > 0 || msgWait > 0) {
63529
- counters.shed++;
63530
- return Promise.resolve(SEND_GATE_SHED2);
63531
- }
63532
- }
63533
63584
  if (state.pending) {
63534
63585
  state.pending.priorityClass = maxPriority(state.pending.priorityClass, opts.priorityClass ?? "useful");
63535
63586
  if (state.pending.hash !== hash) {
@@ -63537,7 +63588,9 @@ function createSendGate(config) {
63537
63588
  state.pending.hash = hash;
63538
63589
  state.pending.fn = fn;
63539
63590
  }
63540
- return state.pending.promise;
63591
+ if (state.pending.priorityClass === "critical")
63592
+ state.criticalWake?.();
63593
+ return settleForCaller(state, state.pending, opts, opts.priorityClass ?? "useful", now);
63541
63594
  }
63542
63595
  if (hash === state.lastHash) {
63543
63596
  counters.dropped++;
@@ -63558,9 +63611,13 @@ function createSendGate(config) {
63558
63611
  priorityClass: opts.priorityClass ?? "useful"
63559
63612
  };
63560
63613
  state.pending = pending;
63561
- if (!state.running)
63614
+ if (state.running) {
63615
+ if (pending.priorityClass === "critical")
63616
+ state.criticalWake?.();
63617
+ } else {
63562
63618
  drive(state, opts);
63563
- return promise;
63619
+ }
63620
+ return settleForCaller(state, pending, opts, pending.priorityClass, now);
63564
63621
  }
63565
63622
  async function gate(fn, opts) {
63566
63623
  if (!enabled2)
@@ -75868,6 +75925,30 @@ function makeKey3(chatId, threadId) {
75868
75925
  }
75869
75926
 
75870
75927
  // reply-owner-resolve.ts
75928
+ function latestEndedAccepted(candidates) {
75929
+ if (candidates.latestEndedTurnId == null)
75930
+ return false;
75931
+ const age = candidates.latestEndedAgeMs;
75932
+ const ttl = candidates.latestEndedTtlMs;
75933
+ if (age === null)
75934
+ return false;
75935
+ if (age === undefined || ttl == null)
75936
+ return true;
75937
+ return age <= ttl;
75938
+ }
75939
+ function decideContentGateBypass(input) {
75940
+ if (input.tier === "live")
75941
+ return true;
75942
+ if (input.tier === "none")
75943
+ return false;
75944
+ if (input.handbackCouldOwnReply)
75945
+ return false;
75946
+ if (input.candidates == null)
75947
+ return false;
75948
+ if (!latestEndedAccepted(input.candidates))
75949
+ return false;
75950
+ return input.resolvedTurnId != null && input.resolvedTurnId === input.candidates.latestEndedTurnId;
75951
+ }
75871
75952
  function decideAnswerLatchSuppression(input) {
75872
75953
  if (input.superseded)
75873
75954
  return false;
@@ -76376,7 +76457,9 @@ function createBackstopReadBack(w) {
76376
76457
  };
76377
76458
  try {
76378
76459
  const r = await w.gate(() => w.editMessageText(messageId, body, editApiOpts), gateOpts);
76379
- return w.isShed(r) ? "ambiguous" : "exists";
76460
+ if (w.isShed(r) || r === undefined)
76461
+ return "ambiguous";
76462
+ return "exists";
76380
76463
  } catch (err) {
76381
76464
  return classifyReadBackError(err);
76382
76465
  }
@@ -76497,14 +76580,23 @@ async function sendReply(deps, req) {
76497
76580
  let supersedeFlushIds = [];
76498
76581
  {
76499
76582
  const replyThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
76500
- const { turn: ownerTurn, tier: ownerTier } = resolveReplyOwnerTurn(turn, chat_id, args);
76583
+ const {
76584
+ turn: ownerTurn,
76585
+ tier: ownerTier,
76586
+ candidates: ownerCandidates
76587
+ } = resolveReplyOwnerTurn(turn, chat_id, args);
76501
76588
  const resolvedTurnId = ownerTurn?.turnId ?? null;
76502
76589
  const ownerEndedAt = ownerTurn?.endedAt ?? null;
76503
76590
  const gateThreadId = ownerTurn?.sessionThreadId ?? replyThreadId;
76504
76591
  const handbackAt = getLastSubagentHandbackAt(chat_id);
76505
76592
  const now = Date.now();
76506
76593
  const handbackCouldOwnReply = handbackAt != null && ownerEndedAt != null && handbackAt > ownerEndedAt && now - handbackAt <= DEFAULT_SUPERSEDE_TTL_MS2;
76507
- const replyIsOwnAnswer = ownerTier === "live" || ownerTier === "latest-ended" && !handbackCouldOwnReply;
76594
+ const replyIsOwnAnswer = decideContentGateBypass({
76595
+ tier: ownerTier,
76596
+ resolvedTurnId,
76597
+ candidates: ownerCandidates,
76598
+ handbackCouldOwnReply
76599
+ });
76508
76600
  const decision = flushedTurnSupersede.take(chat_id, gateThreadId, { liveTurnId: resolvedTurnId, replyText: text4, positiveAttribution: replyIsOwnAnswer, now });
76509
76601
  if (decision.supersede) {
76510
76602
  process.stderr.write(`telegram gateway: reply: superseding flushed turn message(s) ` + `chatId=${chat_id} ids=${JSON.stringify(decision.deleteMessageIds)}
@@ -76529,7 +76621,7 @@ async function sendReply(deps, req) {
76529
76621
  replyMatchesFlushedAnswer
76530
76622
  });
76531
76623
  if (decision.reason === "new-content") {
76532
- process.stderr.write(`telegram gateway: reply: flush supersede declined \u2014 new content (#3429) ` + `chatId=${chat_id} ownerTurnId=${JSON.stringify(resolvedTurnId)}; sending fresh
76624
+ 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
76533
76625
  `);
76534
76626
  }
76535
76627
  if (suppressByLatch) {
@@ -79124,6 +79216,7 @@ function handleSessionEvent(deps, ev) {
79124
79216
  sentIds = delivery.sentIds;
79125
79217
  chunkCount = delivery.chunkCount;
79126
79218
  delivered = delivery.delivered;
79219
+ turn.landedUnconfirmed = delivery.landedUnconfirmed;
79127
79220
  outboundDedup.record(backstopChatId, backstopThreadId, capturedText, Date.now(), getCurrentTurn()?.registryKey ?? null);
79128
79221
  if (sentIds.length > 0) {
79129
79222
  flushedTurnSupersede.record(backstopChatId, backstopThreadId, { turnId: turn.turnId, messageIds: sentIds, text: capturedText }, Date.now());
@@ -80058,12 +80151,14 @@ function isTurnFlushSafetyEnabled(env = process.env) {
80058
80151
  }
80059
80152
 
80060
80153
  // reply-owner-resolve.ts
80061
- function latestEndedAccepted(candidates) {
80154
+ function latestEndedAccepted2(candidates) {
80062
80155
  if (candidates.latestEndedTurnId == null)
80063
80156
  return false;
80064
80157
  const age = candidates.latestEndedAgeMs;
80065
80158
  const ttl = candidates.latestEndedTtlMs;
80066
- if (age == null || ttl == null)
80159
+ if (age === null)
80160
+ return false;
80161
+ if (age === undefined || ttl == null)
80067
80162
  return true;
80068
80163
  return age <= ttl;
80069
80164
  }
@@ -80074,7 +80169,7 @@ function resolveReplyOwnerTier(candidates) {
80074
80169
  return "origin";
80075
80170
  if (candidates.quotedTurnId != null)
80076
80171
  return "quoted";
80077
- if (latestEndedAccepted(candidates))
80172
+ if (latestEndedAccepted2(candidates))
80078
80173
  return "latest-ended";
80079
80174
  return "none";
80080
80175
  }
@@ -83021,6 +83116,99 @@ function assertPositive(value, label) {
83021
83116
  }
83022
83117
  }
83023
83118
 
83119
+ // ../src/setup/host-capabilities.ts
83120
+ init_paths();
83121
+
83122
+ // ../src/setup/hindsight-perf-defaults.ts
83123
+ var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150;
83124
+ var HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE = Math.ceil(HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION * 0.4);
83125
+ var HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50;
83126
+ var HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2;
83127
+ var HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low";
83128
+ var HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4;
83129
+ var HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1;
83130
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT = 1;
83131
+ var HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true";
83132
+ var HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128;
83133
+ var HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true";
83134
+ var HINDSIGHT_DEFAULT_LLM_MAX_RETRIES = 2;
83135
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2;
83136
+ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
83137
+ [
83138
+ "HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
83139
+ String(HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE)
83140
+ ],
83141
+ [
83142
+ "HINDSIGHT_API_LINK_EXPANSION_PER_ENTITY_LIMIT",
83143
+ String(HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT)
83144
+ ],
83145
+ [
83146
+ "HINDSIGHT_API_LINK_EXPANSION_TIMEOUT",
83147
+ String(HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S)
83148
+ ],
83149
+ ["HINDSIGHT_API_LLM_REASONING_EFFORT", HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT],
83150
+ [
83151
+ "HINDSIGHT_API_CONSOLIDATION_LLM_PARALLELISM",
83152
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM)
83153
+ ]
83154
+ ];
83155
+ var HINDSIGHT_PERF_DEFAULTS_GPU = [
83156
+ ["HINDSIGHT_API_RERANKER_LOCAL_FP16", HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16],
83157
+ [
83158
+ "HINDSIGHT_API_RERANKER_LOCAL_BATCH_SIZE",
83159
+ String(HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE)
83160
+ ]
83161
+ ];
83162
+ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
83163
+ ["HINDSIGHT_API_LLM_MAX_CONCURRENT", String(HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT)],
83164
+ [
83165
+ "HINDSIGHT_API_RETAIN_LLM_MAX_CONCURRENT",
83166
+ String(HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT)
83167
+ ],
83168
+ [
83169
+ "HINDSIGHT_API_CONSOLIDATION_LLM_MAX_CONCURRENT",
83170
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
83171
+ ],
83172
+ ["HINDSIGHT_API_LLM_STRICT_SCHEMA", HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA],
83173
+ ["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)]
83174
+ ];
83175
+ var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
83176
+ "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY"
83177
+ ]);
83178
+ var HINDSIGHT_PERF_ENV_KEYS = new Set([
83179
+ ...[
83180
+ ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
83181
+ ...HINDSIGHT_PERF_DEFAULTS_GPU,
83182
+ ...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
83183
+ ].map(([k]) => k),
83184
+ ...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS
83185
+ ]);
83186
+
83187
+ // ../src/setup/hindsight-pg-defaults.ts
83188
+ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 8 * 1024;
83189
+ var HINDSIGHT_PG_APP_ANON_MIB = 2560;
83190
+ var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
83191
+ var HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
83192
+ var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 1536;
83193
+ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 4096;
83194
+ function pgMib(mib) {
83195
+ return `${mib}MB`;
83196
+ }
83197
+ var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
83198
+ var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
83199
+ var HINDSIGHT_PG_DEFAULTS = [
83200
+ [
83201
+ HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
83202
+ pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
83203
+ ],
83204
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
83205
+ ];
83206
+ var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
83207
+
83208
+ // ../src/setup/hindsight-context-budget.ts
83209
+ var HINDSIGHT_UPSTREAM_RETAIN_CHUNK_SIZE = 3000;
83210
+ var HINDSIGHT_RETAIN_MAX_COMPLETION_FLOOR = HINDSIGHT_UPSTREAM_RETAIN_CHUNK_SIZE + 72;
83211
+
83024
83212
  // ../src/setup/hindsight.ts
83025
83213
  var HINDSIGHT_DEFAULT_API_PORT = 18888;
83026
83214
  var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
@@ -87593,10 +87781,10 @@ function createCapturedResumeDispatcher(ports) {
87593
87781
  if (delivered) {
87594
87782
  ports.obligationLedger.close(o.originTurnId);
87595
87783
  ports.backstopDeliveryLedger.clear(o.originTurnId);
87596
- stderr(`telegram gateway: captured-answer resume delivered \u2014 origin=${o.originTurnId} ` + `${sentIds.length} chunk(s) confirmed; obligation closed
87784
+ stderr(`telegram gateway: captured-answer resume delivered \u2014 origin=${o.originTurnId} ` + `${sentIds.length} message id(s) landed; obligation closed
87597
87785
  `);
87598
87786
  } else {
87599
- stderr(`telegram gateway: captured-answer resume partial \u2014 origin=${o.originTurnId} ` + `tail still not confirmed; left OPEN for retry
87787
+ stderr(`telegram gateway: captured-answer resume partial \u2014 origin=${o.originTurnId} ` + `tail still not landed; left OPEN for retry
87600
87788
  `);
87601
87789
  }
87602
87790
  } catch (err) {
@@ -88467,8 +88655,29 @@ function resolveAnswerThreadId(input) {
88467
88655
  return input.liveThreadId;
88468
88656
  }
88469
88657
 
88658
+ // gateway/latest-turn-lookup.ts
88659
+ function latestTurnForChat(turns, chatId, opts) {
88660
+ let latest = null;
88661
+ for (const t of turns) {
88662
+ if (t.sessionChatId !== chatId)
88663
+ continue;
88664
+ if (opts.endedOnly && t.endedAt == null)
88665
+ continue;
88666
+ latest = t;
88667
+ }
88668
+ return latest;
88669
+ }
88670
+
88470
88671
  // gateway/turns-jsonl-rotate.ts
88471
88672
  var TURNS_JSONL_MAX_BYTES = 5 * 1024 * 1024;
88673
+ var DEFAULT_AGENT_STATE_DIR = "/state/agent";
88674
+ function resolveAgentStateDir(env = process.env) {
88675
+ const dir = env.SWITCHROOM_AGENT_STATE_DIR?.trim();
88676
+ return dir != null && dir !== "" ? dir.replace(/\/+$/, "") : DEFAULT_AGENT_STATE_DIR;
88677
+ }
88678
+ function resolveTurnsJsonlPath(env = process.env) {
88679
+ return `${resolveAgentStateDir(env)}/turns.jsonl`;
88680
+ }
88472
88681
  function maybeRotate(path3, fs3, maxBytes = TURNS_JSONL_MAX_BYTES) {
88473
88682
  const size = fs3.statSize(path3);
88474
88683
  if (size == null || size < maxBytes)
@@ -88497,7 +88706,8 @@ function buildTurnRecord(turn, endedAt) {
88497
88706
  duration_ms: turn.startedAt > 0 ? endedAt - turn.startedAt : 0,
88498
88707
  tools: turn.toolCallCount ?? 0,
88499
88708
  status: computeTurnStatus(turn),
88500
- turn_id: turn.turnId
88709
+ turn_id: turn.turnId,
88710
+ ...turn.landedUnconfirmed != null && turn.landedUnconfirmed > 0 ? { landed_unconfirmed: turn.landedUnconfirmed } : {}
88501
88711
  };
88502
88712
  }
88503
88713
 
@@ -88679,8 +88889,16 @@ async function runBackstopDelivery(ledger, turnId, chunks, cardMessageId, deps,
88679
88889
  break;
88680
88890
  }
88681
88891
  const sentIds = ledger.sentIds(turnId);
88682
- const delivered = ledger.allConfirmed(turnId, chunkCount) && backstopReceiptIds(ledger.confirmedIds(turnId), cardMessageId).length > 0;
88892
+ const confirmed = ledger.allConfirmed(turnId, chunkCount);
88893
+ const allLanded = chunkCount > 0 && ledger.unsentIndices(turnId, chunkCount).length === 0;
88894
+ const delivered = allLanded && backstopReceiptIds(sentIds, cardMessageId).length > 0;
88683
88895
  const exhausted = !delivered;
88896
+ const confirmedSet = new Set(ledger.confirmedIds(turnId));
88897
+ const landedUnconfirmedIds = sentIds.filter((id) => !confirmedSet.has(id));
88898
+ if (delivered && !confirmed) {
88899
+ stderr(`telegram gateway: backstop delivery landed-unconfirmed for turn ${turnId} \u2014 ` + `every chunk returned a fresh message id but the read-back probe was ` + `inconclusive for ${landedUnconfirmedIds.length} of ${sentIds.length} landed ` + `id(s); counting it delivered (an ambiguous probe is not a failure)
88900
+ `);
88901
+ }
88684
88902
  if (deps.recordOutbound && sentIds.length > 0) {
88685
88903
  const texts = [];
88686
88904
  const ids = [];
@@ -88692,7 +88910,7 @@ async function runBackstopDelivery(ledger, turnId, chunks, cardMessageId, deps,
88692
88910
  }
88693
88911
  deps.recordOutbound(ids, texts);
88694
88912
  }
88695
- return { sentIds, chunkCount, delivered, attempts, exhausted };
88913
+ return { sentIds, chunkCount, delivered, confirmed, landedUnconfirmedIds, attempts, exhausted };
88696
88914
  }
88697
88915
 
88698
88916
  // gateway/inbound-delivery-confirm.ts
@@ -96331,10 +96549,10 @@ function startOutboxSweep(deps) {
96331
96549
  }
96332
96550
 
96333
96551
  // ../src/build-info.ts
96334
- var VERSION2 = "0.19.22";
96335
- var COMMIT_SHA = "50ef9fb5";
96336
- var COMMIT_DATE = "2026-07-26T06:22:32Z";
96337
- var LATEST_PR = 3696;
96552
+ var VERSION2 = "0.19.24";
96553
+ var COMMIT_SHA = "0fa8ffb8";
96554
+ var COMMIT_DATE = "2026-07-27T05:19:34Z";
96555
+ var LATEST_PR = 3744;
96338
96556
  var COMMITS_AHEAD_OF_TAG = 0;
96339
96557
 
96340
96558
  // gateway/boot-version.ts
@@ -98194,7 +98412,7 @@ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join65(homedir19(), ".claude",
98194
98412
  var permCardStore = createPermissionCardStore(STATE_DIR);
98195
98413
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
98196
98414
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
98197
- var blockedApprovalStore = createBlockedApprovalStore(BLOCKED_APPROVALS_DIR, AGENT_NAME, process.env.SWITCHROOM_AGENT_STATE_DIR ?? "/state/agent");
98415
+ var blockedApprovalStore = createBlockedApprovalStore(BLOCKED_APPROVALS_DIR, AGENT_NAME, resolveAgentStateDir());
98198
98416
  function reconcileBlockedApprovals() {
98199
98417
  const oldest = selectOldestHeld(pendingPermissions);
98200
98418
  if (oldest == null) {
@@ -98975,7 +99193,8 @@ async function deliverAnswer(args) {
98975
99193
  sentIds: result.sentIds,
98976
99194
  chunkCount: result.chunkCount,
98977
99195
  delivered: result.delivered,
98978
- exhausted: result.exhausted
99196
+ exhausted: result.exhausted,
99197
+ landedUnconfirmed: result.landedUnconfirmedIds.length
98979
99198
  };
98980
99199
  }
98981
99200
  var chatAvailableReactions = new Map;
@@ -99304,18 +99523,13 @@ function findTurnByOriginId(originTurnId) {
99304
99523
  return recentTurnsById.get(originTurnId) ?? null;
99305
99524
  }
99306
99525
  var LATE_REPLY_TOPIC_RECOVERY_ENABLED = process.env.SWITCHROOM_LATE_REPLY_TOPIC_RECOVERY !== "0";
99307
- function findLatestEndedTurnForChat(chatId) {
99308
- let latest = null;
99309
- for (const t of recentTurnsById.values()) {
99310
- if (t.sessionChatId === chatId)
99311
- latest = t;
99312
- }
99313
- return latest;
99526
+ function findLatestTurnForChat(chatId, opts) {
99527
+ return latestTurnForChat(recentTurnsById.values(), chatId, opts);
99314
99528
  }
99315
99529
  function resolveReplyOwnerTurn(liveTurn, chatId, args) {
99316
99530
  const origin = findTurnByOriginId(args.origin_turn_id);
99317
99531
  const quoted = findTurnByQuotedMessageId(chatId, args.reply_to);
99318
- const latestEnded = findLatestEndedTurnForChat(chatId);
99532
+ const latestEnded = findLatestTurnForChat(chatId, { endedOnly: true });
99319
99533
  const byId = new Map;
99320
99534
  for (const t of [latestEnded, quoted, origin, liveTurn]) {
99321
99535
  if (t != null)
@@ -99332,10 +99546,10 @@ function resolveReplyOwnerTurn(liveTurn, chatId, args) {
99332
99546
  };
99333
99547
  const tier = resolveReplyOwnerTier(candidates);
99334
99548
  const winnerId = resolveReplyOwnerTurnId(candidates);
99335
- return { turn: winnerId != null ? byId.get(winnerId) ?? null : null, tier };
99549
+ return { turn: winnerId != null ? byId.get(winnerId) ?? null : null, tier, candidates };
99336
99550
  }
99337
99551
  function resolveAnswerThreadWithLog(chatId, explicitThreadId, originTurn, originVia, liveTurn, surface) {
99338
- const recovered = LATE_REPLY_TOPIC_RECOVERY_ENABLED && explicitThreadId == null && originTurn == null && liveTurn == null ? findLatestEndedTurnForChat(chatId) : null;
99552
+ const recovered = LATE_REPLY_TOPIC_RECOVERY_ENABLED && explicitThreadId == null && originTurn == null && liveTurn == null ? findLatestTurnForChat(chatId, { endedOnly: false }) : null;
99339
99553
  const threadId = resolveAnswerThreadId({
99340
99554
  explicitThreadId,
99341
99555
  originResolved: originTurn != null,
@@ -99802,7 +100016,7 @@ function snapshotContextOccupancy() {
99802
100016
  } catch {
99803
100017
  cap = null;
99804
100018
  }
99805
- const stateDir = process.env.SWITCHROOM_AGENT_STATE_DIR ?? "/state/agent";
100019
+ const stateDir = resolveAgentStateDir();
99806
100020
  writeContextOccupancySnapshot(stateDir, buildContextOccupancy(occupancy, cap, Date.now()));
99807
100021
  } catch {}
99808
100022
  }
@@ -99814,10 +100028,11 @@ function emitTurnRecord(turn, endedAt) {
99814
100028
  toolCallCount: turn.toolCallCount ?? 0,
99815
100029
  turnId: turn.turnId,
99816
100030
  finalAnswerDelivered: turn.finalAnswerDelivered,
99817
- deliveryOutcome: turn.deliveryOutcome
100031
+ deliveryOutcome: turn.deliveryOutcome,
100032
+ landedUnconfirmed: turn.landedUnconfirmed
99818
100033
  }, endedAt)) + `
99819
100034
  `;
99820
- const turnsPath = "/state/agent/turns.jsonl";
100035
+ const turnsPath = resolveTurnsJsonlPath();
99821
100036
  maybeRotate(turnsPath, {
99822
100037
  statSize: (p) => {
99823
100038
  try {