@matt82198/aesop 0.1.0-beta.3 → 0.1.0-beta.4
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/CHANGELOG.md +37 -94
- package/CLAUDE-TEMPLATE.md +115 -0
- package/README.md +89 -264
- package/aesop.config.example.json +27 -26
- package/bin/CLAUDE.md +39 -0
- package/bin/cli.js +368 -24
- package/daemons/CLAUDE.md +33 -0
- package/daemons/backup-fleet.sh +92 -21
- package/daemons/run-watchdog.sh +90 -10
- package/dash/CLAUDE.md +32 -0
- package/dash/dash-extra.mjs +194 -21
- package/dash/watchdog-gui.sh +23 -6
- package/docs/BEHAVIORAL-PR-REVIEW.md +86 -0
- package/docs/CHECKPOINTING.md +100 -0
- package/docs/DISPATCH-MODEL.md +11 -7
- package/docs/FORENSICS.md +204 -0
- package/docs/GOVERNANCE.md +4 -2
- package/docs/HOOK-INSTALL.md +307 -0
- package/docs/HOW-THE-LOOP-WORKS.md +175 -0
- package/docs/MEMORY-TEMPLATE.md +61 -0
- package/docs/README.md +44 -0
- package/docs/RELIABILITY.md +61 -0
- package/docs/RESTORE.md +397 -0
- package/docs/SCRIPTS-POLICY.md +97 -0
- package/docs/spikes/tiered-cognition/ACTIVATION.md +125 -0
- package/docs/spikes/tiered-cognition/DESIGN.md +287 -0
- package/docs/spikes/tiered-cognition/FINDINGS.md +113 -0
- package/docs/spikes/tiered-cognition/README.md +29 -0
- package/docs/spikes/tiered-cognition/aesop-cognition.example.md +32 -0
- package/docs/spikes/tiered-cognition/force-model-policy.merged.mjs +673 -0
- package/docs/spikes/tiered-cognition/strip-tools-hook.mjs +434 -0
- package/hooks/CLAUDE.md +38 -0
- package/hooks/claude/force-model-policy.mjs +159 -0
- package/hooks/pre-push-policy.sh +753 -0
- package/monitor/.signal-state.json +3 -0
- package/monitor/ACTIONS.log +1 -0
- package/monitor/BRIEF.md +24 -0
- package/monitor/CHARTER.md +29 -8
- package/monitor/CLAUDE.md +40 -0
- package/monitor/SIGNALS.json +54 -0
- package/monitor/collect-signals.mjs +529 -62
- package/package.json +12 -3
- package/skills/CLAUDE.md +3 -0
- package/skills/power/SKILL.md +161 -0
- package/tools/CLAUDE.md +191 -0
- package/tools/__pycache__/buildlog.cpython-314.pyc +0 -0
- package/tools/__pycache__/ci_merge_wait.cpython-314.pyc +0 -0
- package/tools/__pycache__/ensure_state.cpython-314.pyc +0 -0
- package/tools/__pycache__/eod_sweep.cpython-314.pyc +0 -0
- package/tools/__pycache__/fleet_ledger.cpython-314.pyc +0 -0
- package/tools/__pycache__/heartbeat.cpython-314.pyc +0 -0
- package/tools/__pycache__/inbox_drain.cpython-314.pyc +0 -0
- package/tools/__pycache__/launch_tui.cpython-314.pyc +0 -0
- package/tools/__pycache__/orchestrator_status.cpython-314.pyc +0 -0
- package/tools/__pycache__/power_selftest.cpython-314.pyc +0 -0
- package/tools/__pycache__/prepublish_scan.cpython-314.pyc +0 -0
- package/tools/__pycache__/rotate_logs.cpython-314.pyc +0 -0
- package/tools/__pycache__/scanner_selftest.cpython-314.pyc +0 -0
- package/tools/__pycache__/secret_scan.cpython-314.pyc +0 -0
- package/tools/__pycache__/stall_check.cpython-314.pyc +0 -0
- package/tools/__pycache__/verify_dash.cpython-314.pyc +0 -0
- package/tools/__pycache__/verify_submit_encoding.cpython-314.pyc +0 -0
- package/tools/agent-forensics.sh +186 -0
- package/tools/buildlog.py +182 -0
- package/tools/ci_merge_wait.py +204 -0
- package/tools/ensure_state.py +94 -0
- package/tools/eod_sweep.py +239 -0
- package/tools/fleet_ledger.py +272 -0
- package/tools/heartbeat.py +131 -0
- package/tools/inbox_drain.py +249 -0
- package/tools/launch_tui.py +51 -3
- package/tools/lock.mjs +211 -0
- package/tools/metrics_gate.py +205 -0
- package/tools/orchestrator_status.py +99 -0
- package/tools/power_selftest.py +386 -0
- package/tools/prepublish_scan.py +84 -0
- package/tools/proposals.mjs +248 -0
- package/tools/reconstitute.sh +467 -0
- package/tools/rotate_logs.py +228 -0
- package/tools/scanner_selftest.py +303 -0
- package/tools/secret_scan.py +131 -31
- package/tools/stall_check.py +172 -0
- package/tools/verify_dash.py +694 -0
- package/tools/verify_submit_encoding.py +194 -0
- package/ui/CLAUDE.md +109 -0
- package/ui/__pycache__/agents.cpython-314.pyc +0 -0
- package/ui/__pycache__/collectors.cpython-314.pyc +0 -0
- package/ui/__pycache__/config.cpython-314.pyc +0 -0
- package/ui/__pycache__/csrf.cpython-314.pyc +0 -0
- package/ui/__pycache__/handler.cpython-314.pyc +0 -0
- package/ui/__pycache__/render.cpython-314.pyc +0 -0
- package/ui/__pycache__/serve.cpython-314.pyc +0 -0
- package/ui/__pycache__/sse.cpython-314.pyc +0 -0
- package/ui/agents.py +179 -0
- package/ui/api/__init__.py +84 -0
- package/ui/api/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ui/api/__pycache__/submit.cpython-314.pyc +0 -0
- package/ui/api/__pycache__/tracker.cpython-314.pyc +0 -0
- package/ui/api/submit.py +58 -0
- package/ui/api/tracker.py +116 -0
- package/ui/collectors.py +586 -0
- package/ui/config.py +114 -0
- package/ui/csrf.py +140 -0
- package/ui/handler.py +426 -0
- package/ui/render.py +29 -0
- package/ui/serve.py +68 -796
- package/ui/sse.py +168 -0
- package/ui/templates/dashboard.html +1202 -0
package/ui/sse.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Aesop UI — SSE client registry + broadcast + background collector loop (wave-9 split)."""
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import queue
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
|
|
9
|
+
import config
|
|
10
|
+
from collectors import (parse_audit_backlog, _snapshot_data, _snapshot_tracker,
|
|
11
|
+
_snapshot_orchestrator_status, drain_tracker_inbox)
|
|
12
|
+
from agents import get_fleet_agents, _transcripts_fingerprint
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_sse_lock = threading.Lock()
|
|
16
|
+
|
|
17
|
+
_sse_clients = [] # list[queue.Queue]
|
|
18
|
+
|
|
19
|
+
_latest_lock = threading.Lock()
|
|
20
|
+
|
|
21
|
+
_latest_snapshots = {"data": None, "backlog": None, "agents": None, "tracker": None, "status": None} # name -> json str
|
|
22
|
+
|
|
23
|
+
_collector_lock = threading.Lock()
|
|
24
|
+
|
|
25
|
+
_collector_started = False
|
|
26
|
+
|
|
27
|
+
_collector_stop_event = threading.Event()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def reset_state():
|
|
31
|
+
"""Reset collector/SSE singleton state for a fresh serve import.
|
|
32
|
+
|
|
33
|
+
The sse module object is cached in sys.modules, so per-test re-imports of
|
|
34
|
+
serve would otherwise share one collector thread + snapshot dict (a prior
|
|
35
|
+
test's thread keeps polling its own dir; later tests never see their state).
|
|
36
|
+
serve.py calls this at import to restore the per-import isolation the
|
|
37
|
+
pre-split monolith had. In production serve is imported once, so this is a
|
|
38
|
+
harmless no-op before the collector ever starts.
|
|
39
|
+
"""
|
|
40
|
+
global _collector_started, _collector_stop_event
|
|
41
|
+
with _collector_lock:
|
|
42
|
+
_collector_stop_event.set() # stop a thread left over from a prior import
|
|
43
|
+
_collector_stop_event = threading.Event()
|
|
44
|
+
_collector_started = False
|
|
45
|
+
with _latest_lock:
|
|
46
|
+
for k in list(_latest_snapshots):
|
|
47
|
+
_latest_snapshots[k] = None
|
|
48
|
+
with _sse_lock:
|
|
49
|
+
_sse_clients.clear()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def register_sse_client():
|
|
53
|
+
"""Register a new SSE client queue. Returns the queue to read events from, or None if cap exceeded."""
|
|
54
|
+
with _sse_lock:
|
|
55
|
+
if len(_sse_clients) >= config.SSE_MAX_CLIENTS:
|
|
56
|
+
return None # Caller will return HTTP 503
|
|
57
|
+
q = queue.Queue(maxsize=config.SSE_QUEUE_MAXSIZE)
|
|
58
|
+
_sse_clients.append(q)
|
|
59
|
+
return q
|
|
60
|
+
|
|
61
|
+
def unregister_sse_client(q):
|
|
62
|
+
"""Remove a disconnected SSE client's queue."""
|
|
63
|
+
with _sse_lock:
|
|
64
|
+
if q in _sse_clients:
|
|
65
|
+
_sse_clients.remove(q)
|
|
66
|
+
|
|
67
|
+
def broadcast_sse(event_name, payload):
|
|
68
|
+
"""Push (event_name, payload) onto every currently-registered client queue.
|
|
69
|
+
|
|
70
|
+
If a client queue is full, drop the oldest event to make room (bounded backpressure).
|
|
71
|
+
This prevents one slow client from blocking the broadcast.
|
|
72
|
+
"""
|
|
73
|
+
with _sse_lock:
|
|
74
|
+
clients = list(_sse_clients)
|
|
75
|
+
for q in clients:
|
|
76
|
+
try:
|
|
77
|
+
q.put_nowait((event_name, payload))
|
|
78
|
+
except queue.Full:
|
|
79
|
+
# Queue is full: drop the oldest event and retry
|
|
80
|
+
try:
|
|
81
|
+
q.get_nowait() # Remove oldest
|
|
82
|
+
q.put_nowait((event_name, payload)) # Add new
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"[collector_loop] Exception: {type(e).__name__}: {e}", file=sys.stderr, flush=True)
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
def _maybe_emit(name, snapshot, last_hashes):
|
|
89
|
+
"""Hash-gate: only store + broadcast a section if its content actually changed."""
|
|
90
|
+
payload = json.dumps(snapshot, default=str, sort_keys=True)
|
|
91
|
+
digest = hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
92
|
+
if last_hashes.get(name) == digest:
|
|
93
|
+
return
|
|
94
|
+
last_hashes[name] = digest
|
|
95
|
+
with _latest_lock:
|
|
96
|
+
_latest_snapshots[name] = payload
|
|
97
|
+
broadcast_sse(name, payload)
|
|
98
|
+
|
|
99
|
+
def collector_loop(stop_event):
|
|
100
|
+
"""Background loop: poll cheap sources, gate expensive ones, broadcast on change."""
|
|
101
|
+
last_hashes = {}
|
|
102
|
+
last_backlog_mtime = object() # sentinel guaranteed != any real mtime/None
|
|
103
|
+
last_agents_fingerprint = None
|
|
104
|
+
cached_backlog_snapshot = {"tiers": []}
|
|
105
|
+
cached_agents_snapshot = []
|
|
106
|
+
last_tracker_mtime = object()
|
|
107
|
+
last_status_mtime = object()
|
|
108
|
+
cached_tracker_snapshot = {'items': []}
|
|
109
|
+
cached_status_snapshot = {'orchestrators': []}
|
|
110
|
+
|
|
111
|
+
while not stop_event.is_set():
|
|
112
|
+
try:
|
|
113
|
+
_maybe_emit("data", _snapshot_data(), last_hashes)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
backlog_mtime = config.AUDIT_BACKLOG_FILE.stat().st_mtime if config.AUDIT_BACKLOG_FILE.exists() else None
|
|
117
|
+
except OSError:
|
|
118
|
+
backlog_mtime = None
|
|
119
|
+
if backlog_mtime != last_backlog_mtime:
|
|
120
|
+
last_backlog_mtime = backlog_mtime
|
|
121
|
+
cached_backlog_snapshot = parse_audit_backlog()
|
|
122
|
+
_maybe_emit("backlog", cached_backlog_snapshot, last_hashes)
|
|
123
|
+
|
|
124
|
+
fingerprint = _transcripts_fingerprint()
|
|
125
|
+
if fingerprint != last_agents_fingerprint:
|
|
126
|
+
last_agents_fingerprint = fingerprint
|
|
127
|
+
cached_agents_snapshot = get_fleet_agents()
|
|
128
|
+
_maybe_emit("agents", cached_agents_snapshot, last_hashes)
|
|
129
|
+
|
|
130
|
+
# Emit tracker section (mtime-gated)
|
|
131
|
+
try:
|
|
132
|
+
tracker_mtime = (config.STATE_DIR / "tracker.json").stat().st_mtime if (config.STATE_DIR / "tracker.json").exists() else None
|
|
133
|
+
except OSError:
|
|
134
|
+
tracker_mtime = None
|
|
135
|
+
if tracker_mtime != last_tracker_mtime:
|
|
136
|
+
last_tracker_mtime = tracker_mtime
|
|
137
|
+
cached_tracker_snapshot = _snapshot_tracker()
|
|
138
|
+
_maybe_emit("tracker", cached_tracker_snapshot, last_hashes)
|
|
139
|
+
|
|
140
|
+
# Emit status section (mtime-gated)
|
|
141
|
+
try:
|
|
142
|
+
status_mtime = (config.STATE_DIR / "orchestrator-status.json").stat().st_mtime if (config.STATE_DIR / "orchestrator-status.json").exists() else None
|
|
143
|
+
except OSError:
|
|
144
|
+
status_mtime = None
|
|
145
|
+
if status_mtime != last_status_mtime:
|
|
146
|
+
last_status_mtime = status_mtime
|
|
147
|
+
cached_status_snapshot = _snapshot_orchestrator_status()
|
|
148
|
+
_maybe_emit("status", cached_status_snapshot, last_hashes)
|
|
149
|
+
|
|
150
|
+
# Drain inbox
|
|
151
|
+
try:
|
|
152
|
+
drain_tracker_inbox()
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"[collector] Inbox drain error: {e}", file=sys.stderr, flush=True)
|
|
155
|
+
except Exception as e:
|
|
156
|
+
print(f"[collector_loop] Exception: {type(e).__name__}: {e}", file=sys.stderr, flush=True)
|
|
157
|
+
stop_event.wait(config.COLLECTOR_INTERVAL)
|
|
158
|
+
|
|
159
|
+
def start_collector_thread():
|
|
160
|
+
"""Idempotently start the background collector daemon thread (safe to call from
|
|
161
|
+
multiple request handlers / run_server — only the first call actually starts it)."""
|
|
162
|
+
global _collector_started
|
|
163
|
+
with _collector_lock:
|
|
164
|
+
if _collector_started:
|
|
165
|
+
return
|
|
166
|
+
_collector_started = True
|
|
167
|
+
t = threading.Thread(target=collector_loop, args=(_collector_stop_event,), daemon=True)
|
|
168
|
+
t.start()
|