switchroom 0.21.18 → 0.21.19
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.
- package/dist/cli/switchroom.js +150 -2
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/skills/switchroom-release/SKILL.md +6 -1
- package/telegram-plugin/connection-drop.ts +83 -0
- package/telegram-plugin/dist/bridge/bridge.js +41 -2
- package/telegram-plugin/dist/gateway/gateway.js +48 -6
- package/telegram-plugin/dist/server.js +46 -3
- package/telegram-plugin/llm-error-present.ts +25 -0
- package/telegram-plugin/session-tail.ts +38 -1
- package/telegram-plugin/tests/llm-error-present.test.ts +183 -0
- package/vendor/hindsight-memory/scripts/prefetch.py +33 -5
- package/vendor/hindsight-memory/scripts/recall.py +131 -17
- package/vendor/hindsight-memory/scripts/tests/test_prefetch_pipeline.py +97 -6
- package/vendor/hindsight-memory/scripts/tests/test_recall_buffer_join.py +70 -0
|
@@ -189,5 +189,75 @@ class KillSwitchOffTests(BufferJoinBase):
|
|
|
189
189
|
self.assertNotIn("a prefetched memory", ctx)
|
|
190
190
|
|
|
191
191
|
|
|
192
|
+
class StaleBufferTokenTests(BufferJoinBase):
|
|
193
|
+
"""F3 — a buffer consumed on turn N must NOT be re-served as fresh on
|
|
194
|
+
turns N+1..N+k when no strictly-newer sentinel has been produced."""
|
|
195
|
+
|
|
196
|
+
def test_consumed_buffer_is_not_reserved_as_fresh_next_turn(self):
|
|
197
|
+
recall_buffer.write_buffer(SESSION, "- a prefetched memory", {})
|
|
198
|
+
recall_buffer.write_sentinel(SESSION)
|
|
199
|
+
cfg = self._config(prefetch_enabled=True)
|
|
200
|
+
|
|
201
|
+
# Turn N+1: fresh hit — the buffer is consumed and its token recorded.
|
|
202
|
+
out1 = self._run(cfg, _DirectiveClient())
|
|
203
|
+
ctx1 = json.loads(out1)["hookSpecificOutput"]["additionalContext"]
|
|
204
|
+
self.assertIn("a prefetched memory", ctx1)
|
|
205
|
+
|
|
206
|
+
# Turn N+2: the producer did NOT run again (no new sentinel). The same
|
|
207
|
+
# on-disk buffer must now read as STALE (already-consumed token), so its
|
|
208
|
+
# memory must NOT be injected again. On the pre-fix code — which hard-
|
|
209
|
+
# wired last_consumed_token=None — read_if_fresh reported the old
|
|
210
|
+
# sentinel "fresh" and re-injected the turn-N memory here.
|
|
211
|
+
out2 = self._run(cfg, _DirectiveClient())
|
|
212
|
+
ctx2 = json.loads(out2)["hookSpecificOutput"]["additionalContext"]
|
|
213
|
+
self.assertNotIn(
|
|
214
|
+
"a prefetched memory", ctx2,
|
|
215
|
+
"a buffer consumed on a prior turn must not be re-served as fresh",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def test_a_newer_sentinel_is_served_after_consumption(self):
|
|
219
|
+
# Positive control: once the producer writes a STRICTLY-NEWER sentinel,
|
|
220
|
+
# the fresh path serves again — the token gate rejects only re-reads of
|
|
221
|
+
# an ALREADY-consumed sentinel, never a genuinely new one.
|
|
222
|
+
recall_buffer.write_buffer(SESSION, "- memory one", {})
|
|
223
|
+
recall_buffer.write_sentinel(SESSION)
|
|
224
|
+
cfg = self._config(prefetch_enabled=True)
|
|
225
|
+
|
|
226
|
+
out1 = self._run(cfg, _DirectiveClient())
|
|
227
|
+
self.assertIn("memory one", json.loads(out1)["hookSpecificOutput"]["additionalContext"])
|
|
228
|
+
|
|
229
|
+
# New turn's producer output.
|
|
230
|
+
recall_buffer.write_buffer(SESSION, "- memory two", {})
|
|
231
|
+
recall_buffer.write_sentinel(SESSION)
|
|
232
|
+
out2 = self._run(cfg, _DirectiveClient())
|
|
233
|
+
ctx2 = json.loads(out2)["hookSpecificOutput"]["additionalContext"]
|
|
234
|
+
self.assertIn("memory two", ctx2)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class ColdSessionSyncRecallTests(BufferJoinBase):
|
|
238
|
+
"""F4 — a flag-on cold session's first turn (no sentinel ever, no prior
|
|
239
|
+
recall, no directives) must fall through to SYNCHRONOUS recall, not emit a
|
|
240
|
+
degraded banner and short-circuit it."""
|
|
241
|
+
|
|
242
|
+
class _MemClient:
|
|
243
|
+
def list_directives(self, bank_id, active_only=True, timeout=2):
|
|
244
|
+
return {"items": []}
|
|
245
|
+
|
|
246
|
+
def recall(self, bank_id, query, **kwargs):
|
|
247
|
+
return {"results": [{
|
|
248
|
+
"text": "sync recalled memory", "type": "fact",
|
|
249
|
+
"mentioned_at": "2026-01-01", "id": "s1", "scores": {"final": 0.9},
|
|
250
|
+
}]}
|
|
251
|
+
|
|
252
|
+
def test_cold_session_with_no_directives_runs_synchronous_recall(self):
|
|
253
|
+
out = self._run(self._config(prefetch_enabled=True), self._MemClient())
|
|
254
|
+
self.assertTrue(out)
|
|
255
|
+
ctx = json.loads(out)["hookSpecificOutput"]["additionalContext"]
|
|
256
|
+
self.assertIn(
|
|
257
|
+
"sync recalled memory", ctx,
|
|
258
|
+
"cold session's first turn must run synchronous recall, not a degraded no-op",
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
192
262
|
if __name__ == "__main__":
|
|
193
263
|
unittest.main()
|