switchroom 0.17.10 → 0.18.3

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 (61) hide show
  1. package/bin/workspace-dynamic-hook.sh +12 -13
  2. package/dist/agent-scheduler/index.js +27 -1
  3. package/dist/auth-broker/index.js +6161 -151
  4. package/dist/cli/notion-write-pretool.mjs +29 -2
  5. package/dist/cli/switchroom.js +578 -454
  6. package/dist/host-control/main.js +6182 -172
  7. package/dist/vault/approvals/kernel-server.js +5891 -164
  8. package/dist/vault/broker/server.js +6597 -881
  9. package/package.json +1 -1
  10. package/profiles/_base/settings.json.hbs +2 -2
  11. package/profiles/_base/start.sh.hbs +170 -21
  12. package/profiles/coding/CLAUDE.md.hbs +1 -1
  13. package/profiles/default/CLAUDE.md +2 -2
  14. package/profiles/default/CLAUDE.md.hbs +2 -2
  15. package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
  16. package/profiles/health-coach/CLAUDE.md.hbs +1 -1
  17. package/telegram-plugin/auth-snapshot-format.ts +22 -24
  18. package/telegram-plugin/context-exhaustion.ts +124 -0
  19. package/telegram-plugin/dist/gateway/gateway.js +24086 -8727
  20. package/telegram-plugin/gateway/activity-card-store.ts +76 -0
  21. package/telegram-plugin/gateway/gateway.ts +480 -85
  22. package/telegram-plugin/gateway/inbound-delivery-gate.ts +26 -0
  23. package/telegram-plugin/gateway/model-command.ts +70 -10
  24. package/telegram-plugin/package.json +6 -0
  25. package/telegram-plugin/quota-watch.ts +4 -6
  26. package/telegram-plugin/registry/turns-schema.test.ts +97 -0
  27. package/telegram-plugin/registry/turns-schema.ts +78 -0
  28. package/telegram-plugin/render/ir.ts +209 -0
  29. package/telegram-plugin/render/parse.ts +363 -0
  30. package/telegram-plugin/render/render.ts +440 -0
  31. package/telegram-plugin/render/rich-render.ts +72 -0
  32. package/telegram-plugin/stream-controller.ts +14 -3
  33. package/telegram-plugin/tests/activity-card-store.test.ts +94 -0
  34. package/telegram-plugin/tests/auth-command-format2.test.ts +1 -1
  35. package/telegram-plugin/tests/auth-snapshot-format.test.ts +30 -16
  36. package/telegram-plugin/tests/claude-code-event-contract.test.ts +48 -0
  37. package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +11 -0
  38. package/telegram-plugin/tests/feed-survival.test.ts +39 -0
  39. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +81 -0
  40. package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +82 -0
  41. package/telegram-plugin/tests/liveness-tracker.test.ts +228 -0
  42. package/telegram-plugin/tests/model-command.test.ts +193 -16
  43. package/telegram-plugin/tests/narrative-render.test.ts +125 -0
  44. package/telegram-plugin/tests/orphaned-reply-rearm.test.ts +123 -163
  45. package/telegram-plugin/tests/quota-watch.test.ts +1 -4
  46. package/telegram-plugin/tests/rapid-fire-delivery-ordering.test.ts +149 -0
  47. package/telegram-plugin/tests/render/parse-torture.test.ts +136 -0
  48. package/telegram-plugin/tests/render/parse.test.ts +393 -0
  49. package/telegram-plugin/tests/render/render.test.ts +436 -0
  50. package/telegram-plugin/tests/render/rich-render.test.ts +85 -0
  51. package/telegram-plugin/tests/telegram-activity-visibility-integration.test.ts +155 -1
  52. package/telegram-plugin/tests/worktree-watch-cwds.test.ts +98 -3
  53. package/telegram-plugin/turn-liveness-floor.ts +35 -1
  54. package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +99 -7
  55. package/telegram-plugin/worktree-watch-cwds.ts +92 -17
  56. package/vendor/hindsight-memory/scripts/lib/client.py +11 -1
  57. package/vendor/hindsight-memory/scripts/lib/config.py +9 -2
  58. package/vendor/hindsight-memory/scripts/recall.py +64 -6
  59. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +1 -0
  60. package/vendor/hindsight-memory/tests/test_client.py +43 -0
  61. package/vendor/hindsight-memory/tests/test_recall_precision.py +114 -0
@@ -277,6 +277,82 @@ export async function runActivityCardBootReaper(args: {
277
277
  return { finalized, vanished, unpinned, total: persisted.length }
278
278
  }
279
279
 
280
+ /**
281
+ * Mid-session periodic orphan reaper (#2918). The boot reaper above only runs
282
+ * once at startup, so a card whose owning turn's process dies MID-session (SDK
283
+ * subprocess SIGKILL / OOM / crash) without a clean finalize stays frozen on
284
+ * its last "working…" frame until the NEXT gateway boot — hours later. This
285
+ * variant runs inside the LIVE gateway on a timer and finalizes ONLY records
286
+ * that are demonstrably orphaned:
287
+ *
288
+ * - `isLive(record)` — the caller's liveness predicate (gateway:
289
+ * `currentTurnMap.byKey.has(record.turnKey)` OR the singleton `currentTurn`
290
+ * mirror owns this topic). A record whose topic is still live is NEVER
291
+ * touched, so the currently-spinning card of an in-flight turn is safe.
292
+ * - age gate — `now - startedAt >= ttlMs`, a secondary guard so a card that
293
+ * JUST opened (before its turn populated the live map) is protected.
294
+ *
295
+ * Same AT-MOST-ONCE contract as the boot reaper: each record is deleted from
296
+ * the store BEFORE its finalize/unpin is attempted, and failures are swallowed
297
+ * without retry. The two reapers share `runOneCardFinalize` semantics via the
298
+ * identical delete-first ordering — a boot reaper and a mid-session sweep can
299
+ * never double-finalize the same message (whichever deletes the record first
300
+ * wins; the other sees an empty/short list).
301
+ */
302
+ export async function runActivityCardMidSessionReaper(args: {
303
+ path: string
304
+ fs: ActivityCardStoreFsSeam
305
+ /** True IFF the record's topic is still owned by a live in-flight turn. */
306
+ isLive: (record: ActivityCardRecord) => boolean
307
+ ttlMs: number
308
+ now?: number
309
+ finalizeCard: (record: ActivityCardRecord) => Promise<unknown>
310
+ unpinCard: (record: ActivityCardRecord) => Promise<unknown>
311
+ log?: (line: string) => void
312
+ }): Promise<{ finalized: number; vanished: number; unpinned: number; total: number }> {
313
+ const log = args.log ?? ((l: string) => process.stderr.write(l))
314
+ const now = args.now ?? Date.now()
315
+ const cutoff = now - args.ttlMs
316
+ const persisted = loadActivityCards(args.path, args.fs)
317
+ // Only ownerless cards aged past the TTL. Liveness does the real work; the
318
+ // age gate only closes the just-opened-not-yet-tracked race window.
319
+ const stale = persisted.filter(
320
+ (r) => !args.isLive(r) && r.startedAt <= cutoff,
321
+ )
322
+ if (stale.length === 0) return { finalized: 0, vanished: 0, unpinned: 0, total: 0 }
323
+ let finalized = 0
324
+ let vanished = 0
325
+ let unpinned = 0
326
+ for (const record of stale) {
327
+ // Delete BEFORE the edit — the same idempotency guard the boot reaper uses.
328
+ clearActivityCardRecord(args.path, args.fs, record.turnKey, record.activityMessageId, log)
329
+ try {
330
+ const res = await args.finalizeCard(record)
331
+ if (res != null) finalized++
332
+ else vanished++
333
+ } catch (err) {
334
+ log(
335
+ `activity-card-store: mid-session reaper finalize failed ` +
336
+ `(chat=${record.chatId} msg=${record.activityMessageId}): ` +
337
+ `${(err as Error).message}\n`,
338
+ )
339
+ }
340
+ if (record.pinned) {
341
+ try {
342
+ await args.unpinCard(record)
343
+ unpinned++
344
+ } catch (err) {
345
+ log(
346
+ `activity-card-store: mid-session reaper unpin failed ` +
347
+ `(chat=${record.chatId} msg=${record.activityMessageId}): ` +
348
+ `${(err as Error).message}\n`,
349
+ )
350
+ }
351
+ }
352
+ }
353
+ return { finalized, vanished, unpinned, total: stale.length }
354
+ }
355
+
280
356
  /**
281
357
  * Honest finalize text for an orphaned card — the single edit the boot
282
358
  * reaper applies. Mirrors `silentEndFallbackText`'s honesty-about-elapsed