"""Tasks, handoffs, pins, the human's availability, corrections, due times, search filters, connections, doctor.""" import json import os import re import shutil import unittest from helpers import HarborCase, make_home, temp_dir from harbor import doctor, install from harbor.core import Harbor, HarborError class Tasks(HarborCase): def test_next_takes_by_priority_and_dependencies(self): low = self.h.add_task("Claude", "tidy names", priority="low") first = self.h.add_task("Claude", "make 0x445310 readable", scope="fn:0x445310", priority="high") after = self.h.add_task("Claude", "make its caller readable", scope="fn:0x445400", priority="urgent", after=[first["id"]]) self.assertEqual(after["waiting_on"], [first["id"]]) took = self.h.next_task("DeepSeek") self.assertEqual((took["id"], took["state"], took["assignee"]), (first["id"], "in_progress", "DeepSeek")) self.assertEqual(self.h.active_claims(agent="DeepSeek")[0]["scope"], "fn:0x445310") self.assertIn(f"task #{first['id']}", next(a for a in self.h.agents() if a["name"] == "DeepSeek")["doing"]) self.assertEqual(self.h.next_task("Codex")["id"], low["id"]) # the urgent one still waits with self.assertRaises(HarborError): self.h.take_task("OpenCode", after["id"]) self.h.update_task("DeepSeek", first["id"], "done", note="landed in abc") self.assertEqual(self.h.active_claims(agent="DeepSeek"), []) # claim released self.assertIsNone(next(a for a in self.h.agents() if a["name"] == "DeepSeek")["doing"]) self.assertEqual(self.h.next_task("OpenCode")["id"], after["id"]) self.assertIn("is done", self.h.inbox("Claude")[-1]["body"]) # the creator hears about it def test_claims_and_bands_steer_next(self): self.h.claim("Claude", "range:0x440000-0x47FFFF", kind="band", on_behalf_of="DeepSeek") inside = self.h.add_task("Claude", "inside DeepSeek's band", scope="fn:0x450000") held = self.h.add_task("Claude", "already drafted", scope="fn:0x490000") self.h.claim("OpenCode", "fn:0x490000") self.assertIsNone(self.h.next_task("Codex")) self.assertEqual(self.h.next_task("DeepSeek")["id"], inside["id"]) self.assertEqual(self.h.next_task("OpenCode", take=False)["id"], held["id"]) assigned = self.h.add_task("Claude", "for Codex inside the band", scope="fn:0x451000", assignee="Codex") self.assertEqual(self.h.next_task("Codex")["id"], assigned["id"]) # explicit assignment wins self.assertEqual(self.h.inbox("Codex")[-1]["refs"]["task"], assigned["id"]) self.h.update_task("Codex", assigned["id"], "open", note="later") # handed back for now self.assertEqual(self.h.task(assigned["id"])[0]["assignee"], "Codex") # still the coordinator's choice self.assertEqual(self.h.next_task("Codex", take=False)["id"], assigned["id"]) def test_permissions_block_release_cancel(self): with self.assertRaises(HarborError): self.h.add_task("Codex", "for someone else", assignee="Kimi") mine = self.h.add_task("Codex", "my own", assignee="Codex") self.h.take_task("Codex", mine["id"]) with self.assertRaises(HarborError): self.h.update_task("Codex", mine["id"], "blocked") # say why with self.assertRaises(HarborError): self.h.update_task("Kimi", mine["id"], "done") self.h.update_task("Codex", mine["id"], "blocked", note="needs 0x4E2680 named first") self.assertTrue(any(a["kind"] == "task_blocked" for a in self.h.attention())) self.assertIn("blocked", self.h.inbox("Claude")[-1]["body"]) self.h.update_task("Codex", mine["id"], "open") self.assertIsNone(self.h.task(mine["id"])[0]["assignee"]) # handed back self.h.update_task("Claude", mine["id"], "cancelled", note="no longer needed") self.h.update_task("Codex", mine["id"], "open") # the creator may reopen with self.assertRaises(HarborError): self.h.update_task("Codex", mine["id"], "review") web = self.h.add_task("User", "from the board", origin="web", priority="urgent") self.assertEqual(web["created_by"], "User") with self.assertRaises(HarborError): self.h.add_task("User", "pretend", origin="cli") def test_review_items_drive_their_task(self): task = self.h.next_task("DeepSeek") if False else self.h.add_task("Claude", "make 0x445310 readable", scope="fn:0x445310") self.h.take_task("DeepSeek", task["id"]) item = self.h.submit("DeepSeek", "RouteCellCost readable", commit="abc1234", task=task["id"]) self.assertEqual(self.h.task(task["id"])[0]["state"], "review") self.assertEqual(self.h.item(item["id"])[0]["scope"], "fn:0x445310") # scope comes from the task self.h.transition("Claude", item["id"], "changes_requested", note="signed read") self.assertEqual(self.h.task(task["id"])[0]["state"], "in_progress") self.h.transition("DeepSeek", item["id"], "submitted", commit="def5678") self.assertEqual(self.h.task(task["id"])[0]["state"], "review") self.h.transition("Claude", item["id"], "approved") self.h.transition("Claude", item["id"], "landed", commit="b4b44f0") done = self.h.task(task["id"])[0] self.assertEqual((done["state"], done["item_id"]), ("done", item["id"])) self.assertEqual(self.h.active_claims(agent="DeepSeek"), []) with self.assertRaises(HarborError): self.h.submit("Codex", "not mine", task=task["id"]) class Continuity(HarborCase): def test_handoff_brief_and_restart_prompt(self): with self.assertRaises(HarborError): self.h.handoff("Kimi", " ") self.h.handoff("Kimi", "Reviewed batch 48.\nIn flight: 0x406F60 VisitTavern, half the strings checked.\nNext: batch 49.") brief = self.h.brief("Kimi") self.assertIn("Your last handoff", brief) self.assertIn("In flight: 0x406F60", brief) self.h.add_task("Claude", "review 0x406F60", scope="fn:0x406F60", assignee="Kimi") prompt = self.h.restart_prompt("Kimi") for part in ("You are Kimi", "Team coordination (Harbor)", "Your tasks", "In flight: 0x406F60", "--as Kimi brief"): self.assertIn(part, prompt) self.h.set_flag("stop:Kimi", "on", "Claude", "cli") self.assertIn("STOPPED", self.h.restart_prompt("Kimi")) self.assertNotIn("Your last handoff", self.h.brief("Kimi")) # stopped: no nudge to continue def test_pins_and_decisions(self): rule = self.h.send("Claude", "All", "Main-thread float math is single precision.", kind="decision") self.h.pin("Codex", rule["id"], note="single precision rule") self.assertIn("single precision rule", self.h.brief("Kimi")) self.assertEqual([m["id"] for m in self.h.decisions()], [rule["id"]]) self.h.unpin("Kimi", rule["id"]) self.assertEqual(self.h.pins(), []) with self.assertRaises(HarborError): self.h.pin("Kimi", 99999) def test_human_availability(self): with self.assertRaises(HarborError): self.h.set_presence("User", "cli", "away") with self.assertRaises(HarborError): self.h.set_presence("Codex", "web", "away") self.h.set_presence("User", "web", "away", until="2h", note="decide with Claude") self.assertIn("User is away until", self.h.brief("Codex")) question = self.h.send("Codex", ["User"], "Should I rename 0x445310?") self.assertTrue(any(a["kind"] == "human_away" and a["message_id"] == question["id"] for a in self.h.attention())) self.clock.t += 3 * 3600 self.assertEqual(self.h.presence()["status"], "here") # the away time ran out self.assertNotIn("is away", self.h.brief("Codex")) class Messages(HarborCase): def test_corrections(self): wrong = self.h.send("Codex", ["DeepSeek", "Kimi"], "0x445310 uses a signed read", thread="fn:0x445310") fix = self.h.send("Codex", None, "It is unsigned (movzx), not signed.", corrects=wrong["id"]) self.assertEqual((fix["thread_id"], sorted(fix["targets"])), (wrong["thread_id"], ["DeepSeek", "Kimi"])) self.assertEqual(self.h.message(fix["id"])["kind"], "retraction") self.assertEqual(self.h.corrections_for([wrong["id"]]), {wrong["id"]: [fix["id"]]}) self.assertIn(f"corrects #{wrong['id']}", self.mirror.read_text(encoding="utf-8")) with self.assertRaises(HarborError): self.h.send("Kimi", None, "not yours", corrects=wrong["id"]) self.assertTrue(self.h.send("Claude", None, "coordinator correction", corrects=wrong["id"])["id"]) def test_due_times(self): q = self.h.send("Claude", ["Kimi"], "Is 0x406F60 exact?", due="30m") self.assertIn("Answers due from you", self.h.brief("Kimi")) self.assertFalse(any(a["kind"] == "due_overdue" for a in self.h.attention())) self.clock.t += 3600 self.assertTrue(any(a["kind"] == "due_overdue" and a["message_id"] == q["id"] for a in self.h.attention())) self.h.ack("Kimi", [q["id"]], note="yes, exact") self.assertFalse(any(a["kind"] == "due_overdue" for a in self.h.attention())) with self.assertRaises(HarborError): self.h.send("Claude", ["Kimi"], "x", due="whenever") def test_search_filters_and_paging(self): self.h.send("Codex", "All", "LayoutTextMesh glyph key", thread="fn:0x50A720") self.h.send("Kimi", ["Claude"], "LayoutTextMesh reviewed", kind="decision") self.h.send("Kimi", ["Codex"], "unrelated words") self.assertEqual(len(self.h.search("LayoutTextMesh")), 2) self.assertEqual([m["sender"] for m in self.h.search("LayoutTextMesh from:kimi")], ["Kimi"]) self.assertEqual(len(self.h.search("kind:decision")), 1) self.assertEqual(len(self.h.search("thread:fn:0x50A720")), 1) self.assertEqual(len(self.h.search("to:Codex")), 1) # not its own broadcast self.assertEqual(len(self.h.search("since:1h LayoutTextMesh")), 2) ids = [m["id"] for m in self.h.recent(10)] self.assertEqual([m["id"] for m in self.h.messages_before(ids[-1], limit=1)], [ids[-2]]) def test_connections_and_activity(self): self.h.connect("Codex", "mcp", "codex") self.h.activity("Codex", "shell", "codex") links = {(r["channel"], r["host"]) for r in self.h.connections()["Codex"]} self.assertEqual(links, {("mcp", "codex"), ("hook", "codex")}) codex = next(a for a in self.h.agents() if a["name"] == "Codex") self.assertTrue(self.h.working(codex)) self.assertEqual(codex["last_tool"], "shell") self.clock.t += 600 self.assertFalse(self.h.working(next(a for a in self.h.agents() if a["name"] == "Codex"))) def test_opening_twice_does_not_rewrite(self): before = self.h.conn.execute("SELECT value FROM meta WHERE key='signature'").fetchone()["value"] again = Harbor(self.project, clock=self.clock) self.assertEqual(again.conn.execute("SELECT value FROM meta WHERE key='signature'").fetchone()["value"], before) again.close() class Doctor(unittest.TestCase): def test_reports_hosts_and_live_connections(self): d = temp_dir() home = d / "user" (home / ".codex").mkdir(parents=True) (home / ".codex" / "config.toml").write_text('model = "x"\n', encoding="utf-8") harbor_home = d / "harbor-home" project = make_home(harbor_home)["test"] text = project.source.read_text(encoding="utf-8").replace('name = "Codex"\n', 'name = "Codex"\nhost = "codex"\n') project.source.write_text(text, encoding="utf-8") saved = {k: os.environ.get(k) for k in ("HARBOR_INSTALL_HOME", "HARBOR_HOME", "HARBOR_PROJECT")} os.environ.update(HARBOR_INSTALL_HOME=str(home), HARBOR_HOME=str(harbor_home)) os.environ.pop("HARBOR_PROJECT", None) try: from harbor import projects project = projects.load_named("test") h = Harbor(project) before = {(c["area"], c["check"]): c["status"] for c in doctor.run(project, h, port=8797)} self.assertEqual(before[("Codex", "MCP server")], "fail") self.assertEqual(before[("Database", "integrity")], "ok") self.assertEqual(before[("Server", "http://127.0.0.1:8797")], "warn") install.install("codex", python="py -3.12") h.connect("Codex", "mcp", "codex") after = {(c["area"], c["check"]): c["status"] for c in doctor.run(project, h, port=8797)} self.assertEqual((after[("Codex", "MCP server")], after[("Codex", "hooks")]), ("ok", "ok")) self.assertEqual(after[("Codex", "hooks trusted")], "warn") self.assertEqual(after[("Codex", "live connection")], "ok") self.assertIn("FAIL", doctor.render(doctor.run(project, h, port=8797)) + "FAIL") h.close() finally: for k, v in saved.items(): if v is None: os.environ.pop(k, None) else: os.environ[k] = v shutil.rmtree(d, ignore_errors=True) if __name__ == "__main__": unittest.main()