项目文件夹

文件
Devika 6c1d1e7ae2 Phase 3: multi-inbox routing + on-event wake
- inbox_routing.py: named inboxes with delivery bindings (in-app / Slack / Telegram);
  route_for resolves session-override > persona-default > default; deliver() mirrors an
  item to its bound channel with the item id embedded ([ocw:<id>]); resolve_from_reply()
  correlates an inbound channel reply back to its item and resolves it (allow/deny or a
  free-text answer). Manager owns the routing store; REST GET/POST /v1/inbox/routing.
- selfwake: on-event wake (add_event/fire_event + wake_on_event tool), completing the
  trigger set (timer / completion / event).

Tests: test_inbox_routing (7) + on-event wake; full suite 438 passed. Marketplace
settled as dir/GitHub load + snapshot (no hosted service). Remaining Phase 3 work is
live wiring (approver-swap, scheduler-resume, gateway inbound) + the GUI pass.
2026-06-27 07:39:18 +05:30

67 行
2.3 KiB
Python

"""Phase 2 gate — self-wake: timer + on-completion wake records and the tools."""
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from coworker.selfwake import WakeStore, selfwake_tools
def _now():
return datetime.now(timezone.utc)
def test_timer_due_only_after_fire_time(tmp_path):
store = WakeStore(tmp_path / "wakes.json")
soon = store.add_timer("s1", _now() + timedelta(seconds=60))
past = store.add_timer("s1", _now() - timedelta(seconds=1))
due_ids = {w.id for w in store.due()}
assert past.id in due_ids and soon.id not in due_ids
def test_completion_due_only_after_job_completes(tmp_path):
store = WakeStore(tmp_path / "wakes.json")
w = store.add_completion("s1", job_id="job-42")
assert w.id not in {x.id for x in store.due()} # not yet
marked = store.complete_job("job-42")
assert [x.id for x in marked] == [w.id]
assert w.id in {x.id for x in store.due()} # now due
def test_mark_fired_removes_from_due(tmp_path):
store = WakeStore(tmp_path / "wakes.json")
w = store.add_timer("s1", _now() - timedelta(seconds=1))
store.mark_fired(w.id)
assert w.id not in {x.id for x in store.due()}
assert w.id not in {x.id for x in store.pending("s1")}
def test_persistence(tmp_path):
store = WakeStore(tmp_path / "wakes.json")
w = store.add_completion("s1", "job-1")
reloaded = WakeStore(tmp_path / "wakes.json")
assert any(x.id == w.id for x in reloaded.pending("s1"))
def test_event_due_only_after_event_fires(tmp_path):
store = WakeStore(tmp_path / "wakes.json")
w = store.add_event("s1", event_key="pr-opened")
assert w.id not in {x.id for x in store.due()}
marked = store.fire_event("pr-opened")
assert [x.id for x in marked] == [w.id]
assert w.id in {x.id for x in store.due()}
def test_selfwake_tools(tmp_path):
store = WakeStore(tmp_path / "wakes.json")
sleep_for, sleep_until, wake_on, wake_on_event = selfwake_tools(store, "s1")
assert sleep_for(30)["ok"]
assert wake_on("job-9")["job_id"] == "job-9"
assert sleep_until((_now() + timedelta(minutes=5)).isoformat())["fire_at"]
assert wake_on_event("alert-fired")["event_key"] == "alert-fired"
pend = store.pending("s1")
assert len(pend) == 4
assert {w.kind for w in pend} == {"timer", "completion", "event"}