forked from Keelrleek/Harbor
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,69 @@
|
||||
"""Windows notifications from the Harbor server, so the human hears about questions and urgent messages even with the
|
||||
chat page closed. Off by default; switched on from the board (stored per project in the database)."""
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
APP_ID = r"{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe"
|
||||
|
||||
|
||||
def toast_script(title, body):
|
||||
xml = ('<toast><visual><binding template="ToastGeneric"><text>' + escape(str(title)[:80]) + "</text><text>"
|
||||
+ escape(str(body)[:300]) + "</text></binding></visual></toast>")
|
||||
xml = xml.replace("'", "''") # inside a PowerShell single-quoted string
|
||||
return ("[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] "
|
||||
"| Out-Null\n"
|
||||
"[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null\n"
|
||||
"$xml = New-Object Windows.Data.Xml.Dom.XmlDocument\n"
|
||||
f"$xml.LoadXml('{xml}')\n"
|
||||
"$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)\n"
|
||||
f"[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('{APP_ID}').Show($toast)\n")
|
||||
|
||||
|
||||
def toast(title, body):
|
||||
"""Show a Windows notification; returns True when PowerShell accepted it."""
|
||||
if sys.platform != "win32" or os.environ.get("HARBOR_NO_TOASTS"):
|
||||
return False
|
||||
encoded = base64.b64encode(toast_script(title, body).encode("utf-16-le")).decode("ascii")
|
||||
try:
|
||||
r = subprocess.run(["powershell", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded],
|
||||
capture_output=True, timeout=20, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
|
||||
return r.returncode == 0
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return False
|
||||
|
||||
|
||||
def pending_for_human(h, limit=3):
|
||||
"""Messages to the human that deserve a notification and were not notified yet (questions, urgent, direct)."""
|
||||
row = h.conn.execute("SELECT value FROM meta WHERE key='toast_last'").fetchone()
|
||||
last = int(row["value"]) if row else 0
|
||||
rows = [h._message_dict(r) for r in h.conn.execute(
|
||||
h.MESSAGE_SELECT + " JOIN deliveries d ON d.message_id=m.id AND d.agent=? WHERE d.read_at IS NULL AND m.id>? "
|
||||
"ORDER BY m.id LIMIT 200", (h.human, last))]
|
||||
worth = sorted((m for m in rows if m["recipients"] != ["All"] or m["importance"] == "urgent"
|
||||
or m["kind"] == "question"), key=lambda m: m["id"])
|
||||
top = max([m["id"] for m in rows], default=last)
|
||||
return worth[-limit:], top
|
||||
|
||||
|
||||
def enabled(h):
|
||||
row = h.conn.execute("SELECT value FROM meta WHERE key='toasts'").fetchone()
|
||||
return bool(row and row["value"] == "on")
|
||||
|
||||
|
||||
def set_enabled(h, on):
|
||||
top = h.conn.execute("SELECT COALESCE(MAX(id), 0) FROM messages").fetchone()[0]
|
||||
|
||||
def do(c):
|
||||
c.execute("INSERT INTO meta(key, value) VALUES ('toasts', ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value",
|
||||
("on" if on else "off",))
|
||||
c.execute("INSERT INTO meta(key, value) VALUES ('toast_last', ?) ON CONFLICT(key) DO UPDATE SET "
|
||||
"value=excluded.value", (str(top),)) # never notify the backlog
|
||||
h._write(do)
|
||||
|
||||
|
||||
def mark(h, top):
|
||||
h._write(lambda c: c.execute("INSERT INTO meta(key, value) VALUES ('toast_last', ?) ON CONFLICT(key) DO UPDATE "
|
||||
"SET value=excluded.value", (str(top),)))
|
||||
Reference in new issue
Block a user