switchroom 0.19.19 → 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.
- package/dist/agent-scheduler/index.js +2 -1
- package/dist/auth-broker/index.js +121 -1
- package/dist/cli/notion-write-pretool.mjs +2 -1
- package/dist/cli/switchroom.js +2995 -1583
- package/dist/host-control/main.js +122 -2
- package/dist/vault/approvals/kernel-server.js +124 -4
- package/dist/vault/broker/server.js +124 -4
- package/package.json +7 -4
- package/profiles/_base/start.sh.hbs +101 -0
- package/profiles/_shared/agent-self-service.md.hbs +64 -109
- package/profiles/_shared/delegation-golden-rule.md.hbs +5 -5
- package/profiles/_shared/dev-protocol.md.hbs +13 -42
- package/profiles/_shared/execution-discipline.md.hbs +7 -14
- package/profiles/coding/CLAUDE.md.hbs +0 -6
- package/profiles/default/CLAUDE.md.hbs +21 -50
- package/skills/dev-protocol/SKILL.md +90 -107
- package/skills/switchroom-release/SKILL.md +103 -20
- package/telegram-plugin/bunfig.toml +10 -0
- package/telegram-plugin/card-format.ts +92 -3
- package/telegram-plugin/dist/gateway/gateway.js +873 -184
- package/telegram-plugin/edit-flood-fuse.ts +477 -0
- package/telegram-plugin/format.ts +19 -7
- package/telegram-plugin/gateway/backstop-delivery.ts +97 -16
- package/telegram-plugin/gateway/boot-sweep-gate.ts +164 -0
- package/telegram-plugin/gateway/callback-query-handlers.ts +454 -81
- package/telegram-plugin/gateway/captured-answer-resume.ts +46 -17
- package/telegram-plugin/gateway/gateway.ts +75 -63
- package/telegram-plugin/gateway/inbound-interceptors.ts +27 -4
- package/telegram-plugin/gateway/narrative-lane.ts +49 -3
- package/telegram-plugin/gateway/outbound-send-path.ts +8 -1
- package/telegram-plugin/gateway/status-pin-api.ts +145 -0
- package/telegram-plugin/gateway/stream-render.ts +6 -0
- package/telegram-plugin/gateway/turn-record-status.ts +19 -0
- package/telegram-plugin/gateway/turns-jsonl-rotate.ts +65 -0
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +325 -45
- package/telegram-plugin/retry-api-call.ts +15 -2
- package/telegram-plugin/send-gate.ts +1 -1
- package/telegram-plugin/status-no-truncate.ts +64 -1
- package/telegram-plugin/status-pin-driver.ts +50 -27
- package/telegram-plugin/status-pin.ts +43 -5
- package/telegram-plugin/tests/activity-card-send-gate.test.ts +275 -0
- package/telegram-plugin/tests/activity-card-wiring.test.ts +16 -7
- package/telegram-plugin/tests/agent-state-dir-preload.test.ts +33 -0
- package/telegram-plugin/tests/backstop-delivery.test.ts +204 -7
- package/telegram-plugin/tests/backstop-readback-probe.test.ts +12 -0
- package/telegram-plugin/tests/boot-pin-sweep-wiring.test.ts +101 -0
- package/telegram-plugin/tests/boot-sweep-gate.test.ts +293 -0
- package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
- package/telegram-plugin/tests/captured-answer-resume.test.ts +104 -0
- package/telegram-plugin/tests/edit-flood-fuse.test.ts +431 -0
- package/telegram-plugin/tests/pinned-card-collapse.test.ts +356 -0
- package/telegram-plugin/tests/status-pin-api.test.ts +178 -0
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +94 -11
- package/telegram-plugin/tests/status-pin.test.ts +106 -5
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +631 -1
- package/telegram-plugin/tests/tool-activity-summary.test.ts +19 -10
- package/telegram-plugin/tests/turns-jsonl-rotate.test.ts +92 -1
- package/telegram-plugin/tests/vault-approval-posture.test.ts +6 -1
- package/telegram-plugin/tests/vault-passphrase-retry.test.ts +666 -0
- package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +42 -21
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +233 -1
- package/telegram-plugin/tool-activity-summary.ts +85 -13
- package/telegram-plugin/worker-activity-feed.ts +5 -1
- package/vendor/hindsight-memory/scripts/drain_pending.py +304 -34
- package/vendor/hindsight-memory/scripts/lib/pending.py +886 -70
- package/vendor/hindsight-memory/scripts/lib/retain_split.py +71 -13
- package/vendor/hindsight-memory/scripts/recall.py +74 -5
- package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +1602 -14
- package/vendor/hindsight-memory/scripts/tests/test_pending_failure_class.py +105 -0
- package/vendor/hindsight-memory/scripts/tests/test_pending_wedge.py +300 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_degraded_notice.py +365 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +12 -4
- package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +27 -2
- package/vendor/hindsight-memory/scripts/tests/test_retain_split.py +93 -13
- package/vendor/hindsight-memory/tests/test_drain_pending.py +44 -3
- package/vendor/hindsight-memory/tests/test_pending.py +12 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Tests for drain_pending.drain() (#1071)."""
|
|
2
2
|
|
|
3
|
+
import io
|
|
3
4
|
import json
|
|
4
5
|
import os
|
|
5
6
|
import sys
|
|
@@ -14,6 +15,21 @@ if SCRIPTS_DIR not in sys.path:
|
|
|
14
15
|
sys.path.insert(0, SCRIPTS_DIR)
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
def _dead_marker(path: str) -> str:
|
|
19
|
+
"""Where `mark_dead` puts the marker for the queue entry at `path`.
|
|
20
|
+
|
|
21
|
+
NOT `<path>.dead` any more: a marker is the only remaining copy of its
|
|
22
|
+
memory, so it leaves the live queue directory for the `pending-dead/`
|
|
23
|
+
sibling rather than sitting in the directory external janitors glob
|
|
24
|
+
(switchroom #3697). Asserting the old spelling made this suite red on a
|
|
25
|
+
deliberate change -- invisibly, because CI discovers only
|
|
26
|
+
`scripts/tests/`.
|
|
27
|
+
"""
|
|
28
|
+
from lib.pending import dead_dir
|
|
29
|
+
|
|
30
|
+
return os.path.join(dead_dir(), os.path.basename(path) + ".dead")
|
|
31
|
+
|
|
32
|
+
|
|
17
33
|
class FakeOk:
|
|
18
34
|
"""Minimal urlopen() context-manager stand-in for a 200 OK."""
|
|
19
35
|
|
|
@@ -212,20 +228,45 @@ class DrainPendingTest(unittest.TestCase):
|
|
|
212
228
|
self.assertEqual(entry["attempt_count"], 2)
|
|
213
229
|
self.assertIn("last_attempt_at", entry)
|
|
214
230
|
|
|
215
|
-
def
|
|
231
|
+
def test_drain_max_attempts_marks_dead_on_a_permanent_failure(self):
|
|
232
|
+
"""Exhausting the budget on a 4xx still retires the entry."""
|
|
216
233
|
from lib.pending import MAX_ATTEMPTS
|
|
217
234
|
|
|
218
235
|
path = _seed_entry(self._pending, attempt=MAX_ATTEMPTS)
|
|
219
236
|
import drain_pending
|
|
220
237
|
|
|
221
238
|
def boom(*a, **kw):
|
|
222
|
-
raise urllib.error.
|
|
239
|
+
raise urllib.error.HTTPError(
|
|
240
|
+
"http://h/v1/x", 400, "Bad Request", {}, io.BytesIO(b"bad payload")
|
|
241
|
+
)
|
|
223
242
|
|
|
224
243
|
with patch("urllib.request.urlopen", side_effect=boom):
|
|
225
244
|
summary = drain_pending.drain({})
|
|
226
245
|
self.assertEqual(summary["dead"], 1)
|
|
227
246
|
self.assertFalse(os.path.exists(path))
|
|
228
|
-
self.assertTrue(os.path.exists(path
|
|
247
|
+
self.assertTrue(os.path.exists(_dead_marker(path)))
|
|
248
|
+
|
|
249
|
+
def test_drain_max_attempts_keeps_the_memory_on_a_transient_failure(self):
|
|
250
|
+
"""A dead upstream must never retire a memory, however many attempts.
|
|
251
|
+
|
|
252
|
+
``URLError`` is what an unreachable daemon raises. Before the
|
|
253
|
+
permanence gate this retired the entry at MAX_ATTEMPTS — i.e. an
|
|
254
|
+
outage destroyed memory that was perfectly saveable.
|
|
255
|
+
"""
|
|
256
|
+
from lib.pending import MAX_ATTEMPTS
|
|
257
|
+
|
|
258
|
+
path = _seed_entry(self._pending, attempt=MAX_ATTEMPTS)
|
|
259
|
+
import drain_pending
|
|
260
|
+
|
|
261
|
+
def boom(*a, **kw):
|
|
262
|
+
raise urllib.error.URLError("still down")
|
|
263
|
+
|
|
264
|
+
with patch("urllib.request.urlopen", side_effect=boom):
|
|
265
|
+
summary = drain_pending.drain({})
|
|
266
|
+
self.assertEqual(summary["dead"], 0)
|
|
267
|
+
self.assertEqual(summary["retried"], 1)
|
|
268
|
+
self.assertTrue(os.path.exists(path))
|
|
269
|
+
self.assertFalse(os.path.exists(_dead_marker(path)))
|
|
229
270
|
|
|
230
271
|
def test_drain_stall_guard_stops_after_threshold(self):
|
|
231
272
|
# Seed 10 entries; with a same-error-class stream, the stall
|
|
@@ -38,7 +38,10 @@ class PendingQueueTest(unittest.TestCase):
|
|
|
38
38
|
"api_url": "http://fake:9077",
|
|
39
39
|
"api_token": None,
|
|
40
40
|
"bank_id": "test-bank",
|
|
41
|
-
|
|
41
|
+
# Distinct per document: the queue's dedupe key is
|
|
42
|
+
# (bank_id, part_position, sha256(content)) since switchroom
|
|
43
|
+
# #3688, so a shared body would collapse these into one entry.
|
|
44
|
+
"content": f"user: hello\nassistant: hi ({document_id})",
|
|
42
45
|
"document_id": document_id,
|
|
43
46
|
"context": "claude-code",
|
|
44
47
|
"metadata": {"session_id": "sess-1"},
|
|
@@ -59,7 +62,7 @@ class PendingQueueTest(unittest.TestCase):
|
|
|
59
62
|
with open(path) as f:
|
|
60
63
|
entry = json.load(f)
|
|
61
64
|
self.assertEqual(entry["bank_id"], "test-bank")
|
|
62
|
-
self.assertEqual(entry["content"], "
|
|
65
|
+
self.assertEqual(entry["content"], self._sample_payload()["content"])
|
|
63
66
|
self.assertEqual(entry["document_id"], "doc-1")
|
|
64
67
|
self.assertEqual(entry["error_class"], "ValueError")
|
|
65
68
|
self.assertEqual(entry["error_message"], "nope")
|
|
@@ -200,8 +203,13 @@ class PendingQueueTest(unittest.TestCase):
|
|
|
200
203
|
self.assertNotIn(
|
|
201
204
|
"dead_at", e, "a live queue entry must never carry dead_at"
|
|
202
205
|
)
|
|
203
|
-
# The .dead marker exists and carries dead_at.
|
|
204
|
-
dead
|
|
206
|
+
# The .dead marker exists and carries dead_at. It lives in the
|
|
207
|
+
# `pending-dead/` SIBLING, not in the live queue directory: a marker
|
|
208
|
+
# is the only remaining copy of its memory, and the queue dir is the
|
|
209
|
+
# one external janitors glob (switchroom #3697).
|
|
210
|
+
dead = os.path.join(
|
|
211
|
+
pending_mod.dead_dir(), os.path.basename(path) + ".dead"
|
|
212
|
+
)
|
|
205
213
|
self.assertTrue(os.path.isfile(dead))
|
|
206
214
|
with open(dead) as f:
|
|
207
215
|
self.assertIn("dead_at", json.load(f))
|