switchroom 0.19.22 → 0.19.23

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 (37) hide show
  1. package/dist/agent-scheduler/index.js +2 -1
  2. package/dist/auth-broker/index.js +68 -1
  3. package/dist/cli/notion-write-pretool.mjs +2 -1
  4. package/dist/cli/switchroom.js +552 -320
  5. package/dist/host-control/main.js +69 -2
  6. package/dist/vault/approvals/kernel-server.js +71 -4
  7. package/dist/vault/broker/server.js +71 -4
  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 +13 -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 +90 -107
  17. package/telegram-plugin/bunfig.toml +10 -0
  18. package/telegram-plugin/dist/gateway/gateway.js +108 -16
  19. package/telegram-plugin/gateway/backstop-delivery.ts +97 -16
  20. package/telegram-plugin/gateway/captured-answer-resume.ts +46 -17
  21. package/telegram-plugin/gateway/gateway.ts +9 -7
  22. package/telegram-plugin/gateway/outbound-send-path.ts +8 -1
  23. package/telegram-plugin/gateway/stream-render.ts +6 -0
  24. package/telegram-plugin/gateway/turn-record-status.ts +19 -0
  25. package/telegram-plugin/gateway/turns-jsonl-rotate.ts +65 -0
  26. package/telegram-plugin/tests/agent-state-dir-preload.test.ts +33 -0
  27. package/telegram-plugin/tests/backstop-delivery.test.ts +204 -7
  28. package/telegram-plugin/tests/backstop-readback-probe.test.ts +12 -0
  29. package/telegram-plugin/tests/captured-answer-resume.test.ts +104 -0
  30. package/telegram-plugin/tests/turns-jsonl-rotate.test.ts +92 -1
  31. package/vendor/hindsight-memory/scripts/drain_pending.py +113 -11
  32. package/vendor/hindsight-memory/scripts/lib/pending.py +802 -65
  33. package/vendor/hindsight-memory/scripts/lib/retain_split.py +54 -7
  34. package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +1445 -11
  35. package/vendor/hindsight-memory/scripts/tests/test_retain_split.py +78 -6
  36. package/vendor/hindsight-memory/tests/test_drain_pending.py +17 -2
  37. package/vendor/hindsight-memory/tests/test_pending.py +12 -4
@@ -68,7 +68,8 @@ from typing import Optional
68
68
  # The bound — derived, not chosen
69
69
  # ---------------------------------------------------------------------------
70
70
  #
71
- # max_content_chars = retain_chunk_size × floor(client_deadline / chunk_latency)
71
+ # max_content_chars = retain_chunk_size
72
+ # × floor(client_deadline × deadline_safety / chunk_latency)
72
73
  #
73
74
  # Each input is a measured or read property of the deployment, not a taste
74
75
  # call, and each is env-overridable so the bound tracks the deployment:
@@ -112,16 +113,45 @@ from typing import Optional
112
113
  # NOT a hand-set number on either side — change
113
114
  # `src/litellm/timeout-budget.ts` and both move.
114
115
  #
115
- # floor(310 / 18.4) = 16 chunks → 16 × 3000 = 48,000 chars
116
+ # deadline_safety 0.7 — the FRACTION of the deadline a maximally-sized
117
+ # part is allowed to consume. See below; this
118
+ # input did not exist until #3693 and was
119
+ # effectively 1.0.
116
120
  #
117
- # Sanity check against the same backlog: every entry at or below 60,000 chars
118
- # drained successfully inside the (then 280s) deadline (observed per-entry
119
- # times 0.3s–153.4s at concurrency 3), so 48,000 still sits inside
120
- # demonstrated-good territory with margin for a slower model or a busier box.
121
+ # floor(310 × 0.7 / 18.4) = 11 chunks → 11 × 3000 = 33,000 chars
122
+ #
123
+ # WHY THERE IS A SAFETY FRACTION AT ALL (#3693). Without it the bound was
124
+ # `floor(deadline / latency)`, which sizes a maximally-sized part to consume
125
+ # ~100% of the deadline BY CONSTRUCTION: 16 × 18.4 = 294.4s against a 310s
126
+ # deadline is 15.6s — 5% — of headroom for the POST/response, server queueing,
127
+ # and any chunk slower than the n=752 MEAN the latency input is. Half the
128
+ # extraction calls are slower than the mean; a part sized to the mean therefore
129
+ # misses the deadline roughly half the time. `drain_pending._backlog_timeout()`
130
+ # defaults to the SAME deadline, so the drainer inherits the same zero margin,
131
+ # times the entry out, bumps its attempt count, and after MAX_ATTEMPTS ages it
132
+ # to `.dead`. That is not a theory: on this fleet every `.dead` marker measured
133
+ # on 2026-07-26 held content of 16,076–44,568 chars — every one of them UNDER
134
+ # the then-current 45,000 bound. The bound was manufacturing dead memories.
135
+ #
136
+ # 0.7 is not a taste call either: it is the utilisation at which the observed
137
+ # per-chunk latency distribution fits, and it independently matches the 30,000
138
+ # char limit the host-side stopgap converged on empirically before this landed.
139
+ # At 0.7 a maximally-sized part is 11 × 18.4 = 202.4s against 310s, leaving
140
+ # 107.6s — enough for a chunk distribution ~50% worse than its own mean.
141
+ #
142
+ # The trade is more parts per logical memory (33,000 rather than 48,000 chars
143
+ # each). That costs nothing in total LLM work — the chunk count across the
144
+ # whole memory is unchanged, only its grouping — and each part now finishes.
145
+ # A part that finishes is worth strictly more than a larger part that does not.
121
146
  DEFAULT_RETAIN_CHUNK_SIZE = 3000
122
147
  DEFAULT_RETAIN_CHUNK_LATENCY_S = 18.4
123
148
  DEFAULT_RETAIN_CLIENT_DEADLINE_S = 310.0
124
149
 
150
+ # Fraction of the client deadline a maximally-sized part may consume.
151
+ # Clamped to (0, 1]: a value above 1.0 would size parts to overrun the
152
+ # deadline outright, which is the defect this input exists to prevent.
153
+ DEFAULT_RETAIN_DEADLINE_SAFETY = 0.7
154
+
125
155
  # Absolute floor: one chunk. A bound below one chunk would split every
126
156
  # transcript into extraction-sized confetti and is never the right answer.
127
157
  MIN_RETAIN_CONTENT_CHARS = DEFAULT_RETAIN_CHUNK_SIZE
@@ -151,6 +181,18 @@ def retain_client_deadline() -> float:
151
181
  return _env_number("HINDSIGHT_RETAIN_CLIENT_DEADLINE_S", DEFAULT_RETAIN_CLIENT_DEADLINE_S)
152
182
 
153
183
 
184
+ def retain_deadline_safety() -> float:
185
+ """Fraction of the client deadline a maximally-sized part may consume.
186
+
187
+ Clamped to ``(0, 1]``. A value above 1.0 is not honoured: it would size
188
+ parts to overrun the deadline by construction, which is exactly the defect
189
+ (#3693) this input exists to prevent, so it is treated as "no margin at
190
+ all" — 1.0 — rather than as a licence to go further.
191
+ """
192
+ value = _env_number("HINDSIGHT_RETAIN_DEADLINE_SAFETY", DEFAULT_RETAIN_DEADLINE_SAFETY)
193
+ return min(1.0, value)
194
+
195
+
154
196
  def retain_content_limit() -> int:
155
197
  """Max chars of retain content that can complete inside the client deadline.
156
198
 
@@ -170,7 +212,12 @@ def retain_content_limit() -> int:
170
212
  latency = _env_number("HINDSIGHT_RETAIN_CHUNK_LATENCY_S", DEFAULT_RETAIN_CHUNK_LATENCY_S)
171
213
  deadline = retain_client_deadline()
172
214
 
173
- chunks = int(deadline // latency)
215
+ # A FRACTION of the deadline, not all of it (#3693). Sizing to the whole
216
+ # deadline leaves a maximally-sized part no headroom for the POST itself,
217
+ # server queueing, or a chunk slower than the MEAN `latency` is measured
218
+ # as — so the part times out, the drain bumps its attempt count, and it
219
+ # ages to `.dead`. See the derivation block at the top of this module.
220
+ chunks = int((deadline * retain_deadline_safety()) // latency)
174
221
  if chunks < 1:
175
222
  chunks = 1
176
223
  return max(MIN_RETAIN_CONTENT_CHARS, chunk_size * chunks)