Harbor as of 2026-10-05: the coordination hub for the Open Sea's decomp team
Messages, threads, acknowledgements, claims, bands, tasks, review items and stop flags for a team of AI agents; the chat page and board; host hooks and MCP server; autopilot; tools/watch.py. Put under version control after the weekend's changes so they have history and can be rolled back. Local only.
This commit is contained in:
commit
dbfd1720c5
43 files changed
+12440
No files matched your search
@@ -0,0 +1,134 @@
|
||||
"""Shared test fixtures: temporary projects under R:\\Workspace\\Data\\openseas-chat\\test-tmp (never C:)."""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from harbor import projects # noqa: E402
|
||||
from harbor.core import Harbor # noqa: E402
|
||||
|
||||
TMP_ROOT = Path(os.environ.get("HARBOR_TEST_TMP", r"R:\Workspace\Data\openseas-chat\test-tmp"))
|
||||
LEGACY_LINE = re.compile(r'^\[([^\]]+)\]\s+([A-Za-z]+)\s*(?:→|->)\s*([A-Za-z]+):\s?(.*)$')
|
||||
ROSTER = [{"name": n, "role": r} for n, r in (("Claude", "Integration"), ("Codex", "Decompilation"),
|
||||
("DeepSeek", "Decompilation"), ("OpenCode", "Decompilation"),
|
||||
("Kimi", "Review"))] + [{"name": "User", "kind": "human"}]
|
||||
PROJECT_TOML = """name = "{name}"
|
||||
title = "Test {name}"
|
||||
database = '{db}'
|
||||
mirror = '{mirror}'
|
||||
coordinators = ["Claude"]
|
||||
reviewers = ["Claude"]
|
||||
|
||||
[identities]
|
||||
"worktree-deepseek" = "DeepSeek"
|
||||
|
||||
[attention]
|
||||
ack_overdue = 30
|
||||
item_stale = 120
|
||||
agent_quiet = 60
|
||||
""" + "".join(f'\n[[agents]]\nname = "{a["name"]}"\n' + (f'kind = "{a["kind"]}"\n' if a.get("kind") else "")
|
||||
for a in ROSTER)
|
||||
|
||||
|
||||
class Clock:
|
||||
def __init__(self, t=1_790_000_000.0):
|
||||
self.t = t
|
||||
|
||||
def __call__(self):
|
||||
return self.t
|
||||
|
||||
|
||||
def temp_dir():
|
||||
TMP_ROOT.mkdir(parents=True, exist_ok=True)
|
||||
fence = TMP_ROOT / ".harbor.toml" # tests never join a project marked above this folder
|
||||
if not fence.exists():
|
||||
fence.write_text("# Test folders belong to no Harbor project above this one.\nboundary = true\n", encoding="utf-8")
|
||||
return Path(tempfile.mkdtemp(dir=TMP_ROOT))
|
||||
|
||||
|
||||
def make_home(folder, names=("test",)):
|
||||
"""A Harbor home with registered projects; returns {name: Project}."""
|
||||
out = {}
|
||||
for name in names:
|
||||
pdir = folder / "projects"
|
||||
pdir.mkdir(parents=True, exist_ok=True)
|
||||
mirror = folder / name / "agents-chat.md"
|
||||
mirror.parent.mkdir(parents=True, exist_ok=True)
|
||||
mirror.write_text("# chat\n\n---\n", encoding="utf-8")
|
||||
(pdir / f"{name}.toml").write_text(PROJECT_TOML.format(name=name, db=folder / name / "harbor.db",
|
||||
mirror=mirror), encoding="utf-8")
|
||||
out[name] = projects.load_named(name, home=folder)
|
||||
(folder / "config.toml").write_text(f'default_project = "{names[0]}"\n', encoding="utf-8")
|
||||
return out
|
||||
|
||||
|
||||
class HarborCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.dir = temp_dir()
|
||||
self.project = make_home(self.dir)["test"]
|
||||
self.mirror = self.project.mirror
|
||||
self.clock = Clock()
|
||||
self.h = Harbor(self.project, clock=self.clock)
|
||||
|
||||
def tearDown(self):
|
||||
self.h.close()
|
||||
shutil.rmtree(self.dir, ignore_errors=True)
|
||||
|
||||
|
||||
def env_for(home, **extra):
|
||||
env = {k: v for k, v in os.environ.items() if not k.startswith("HARBOR_")}
|
||||
env.update(HARBOR_HOME=str(home), PYTHONDONTWRITEBYTECODE="1", HARBOR_NO_TOASTS="1", **extra)
|
||||
return env
|
||||
|
||||
|
||||
def run_cli(home, *args, cwd=None, stdin=None, **env):
|
||||
return subprocess.run([sys.executable, "-B", "-m", "harbor", *args], cwd=cwd or home, env=env_for(home, **env),
|
||||
input=stdin, capture_output=True, text=True, encoding="utf-8", timeout=60,
|
||||
**({} if cwd else {}))
|
||||
|
||||
|
||||
def run_module(home, *args, cwd=None, stdin=None, **env):
|
||||
environment = env_for(home, **env)
|
||||
environment["PYTHONPATH"] = str(ROOT)
|
||||
return subprocess.run([sys.executable, "-B", "-m", "harbor", *args], cwd=cwd or home, env=environment,
|
||||
input=stdin, capture_output=True, text=True, encoding="utf-8", timeout=60)
|
||||
|
||||
|
||||
def http(port, path, body=None, headers=None, browser=True, token=None):
|
||||
h = {}
|
||||
if body is not None:
|
||||
h["Content-Type"] = "application/json"
|
||||
if browser:
|
||||
h.update({"Sec-Fetch-Site": "same-origin", "X-Harbor-Token": token or "",
|
||||
"Origin": f"http://127.0.0.1:{port}"})
|
||||
h.update(headers or {})
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
request = urllib.request.Request(f"http://127.0.0.1:{port}{path}", data=data, headers=h)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=10) as response:
|
||||
raw = response.read()
|
||||
kind = response.headers.get("Content-Type", "")
|
||||
return response.status, (json.loads(raw or b"null") if "json" in kind else raw.decode("utf-8", "replace"))
|
||||
except urllib.error.HTTPError as error:
|
||||
return error.code, json.loads(error.read() or b"null")
|
||||
|
||||
|
||||
def wait_for_port(port, seconds=15):
|
||||
deadline = time.monotonic() + seconds
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
urllib.request.urlopen(f"http://127.0.0.1:{port}/participants", timeout=1).read()
|
||||
return True
|
||||
except OSError:
|
||||
time.sleep(0.1)
|
||||
return False
|
||||
Reference in new issue
Block a user