@matt82198/aesop 0.1.0-beta.3 → 0.1.0-beta.5

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.
Files changed (234) hide show
  1. package/CHANGELOG.md +79 -95
  2. package/CLAUDE-TEMPLATE.md +115 -0
  3. package/README.md +126 -245
  4. package/aesop.config.example.json +34 -26
  5. package/bin/CLAUDE.md +67 -0
  6. package/bin/cli.js +593 -36
  7. package/daemons/CLAUDE.md +36 -0
  8. package/daemons/backup-fleet.sh +92 -21
  9. package/daemons/run-watchdog.sh +95 -12
  10. package/dash/CLAUDE.md +32 -0
  11. package/dash/dash-extra.mjs +194 -21
  12. package/dash/watchdog-gui.sh +23 -6
  13. package/docs/BEHAVIORAL-PR-REVIEW.md +86 -0
  14. package/docs/CHECKPOINTING.md +100 -0
  15. package/docs/DISPATCH-MODEL.md +11 -7
  16. package/docs/FORENSICS.md +204 -0
  17. package/docs/GOVERNANCE.md +4 -2
  18. package/docs/HOOK-INSTALL.md +307 -0
  19. package/docs/HOW-THE-LOOP-WORKS.md +175 -0
  20. package/docs/MEMORY-TEMPLATE.md +61 -0
  21. package/docs/README.md +44 -0
  22. package/docs/RELEASING.md +159 -0
  23. package/docs/RELIABILITY.md +61 -0
  24. package/docs/RESTORE.md +397 -0
  25. package/docs/SCRIPTS-POLICY.md +97 -0
  26. package/docs/archive/README.md +3 -0
  27. package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +125 -0
  28. package/docs/archive/spikes/tiered-cognition/DESIGN.md +287 -0
  29. package/docs/archive/spikes/tiered-cognition/FINDINGS.md +113 -0
  30. package/docs/archive/spikes/tiered-cognition/README.md +27 -0
  31. package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +32 -0
  32. package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +673 -0
  33. package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +434 -0
  34. package/docs/case-study-portfolio.md +61 -0
  35. package/docs/self-stats-data.json +11 -0
  36. package/docs/templates/FLEET-OPS-ANALYSIS.example.md +27 -0
  37. package/docs/templates/FLEET-OPS-RECOMMENDATIONS.example.md +24 -0
  38. package/docs/templates/PROPOSALS-LOG.example.md +64 -0
  39. package/hooks/CLAUDE.md +61 -0
  40. package/hooks/claude/force-model-policy.mjs +159 -0
  41. package/hooks/pre-push-policy.sh +753 -0
  42. package/mcp/CLAUDE.md +213 -0
  43. package/mcp/package.json +26 -0
  44. package/mcp/server.mjs +543 -0
  45. package/monitor/.signal-state.json +3 -0
  46. package/monitor/ACTIONS.log +1 -0
  47. package/monitor/BRIEF.md +24 -0
  48. package/monitor/CHARTER.md +75 -88
  49. package/monitor/CLAUDE.md +40 -0
  50. package/monitor/SIGNALS.json +54 -0
  51. package/monitor/collect-signals.mjs +613 -61
  52. package/package.json +15 -3
  53. package/scan/fleet-scan.example.mjs +292 -0
  54. package/skills/CLAUDE.md +3 -0
  55. package/skills/healthcheck/SKILL.md +44 -0
  56. package/skills/power/SKILL.md +161 -0
  57. package/state_store/CLAUDE.md +39 -0
  58. package/state_store/__init__.py +24 -0
  59. package/state_store/__pycache__/__init__.cpython-314.pyc +0 -0
  60. package/state_store/__pycache__/api.cpython-314.pyc +0 -0
  61. package/state_store/__pycache__/export.cpython-314.pyc +0 -0
  62. package/state_store/__pycache__/ingest.cpython-314.pyc +0 -0
  63. package/state_store/__pycache__/projections.cpython-314.pyc +0 -0
  64. package/state_store/__pycache__/store.cpython-314.pyc +0 -0
  65. package/state_store/api.py +35 -0
  66. package/state_store/export.py +19 -0
  67. package/state_store/ingest.py +24 -0
  68. package/state_store/projections.py +52 -0
  69. package/state_store/store.py +102 -0
  70. package/tools/CLAUDE.md +72 -0
  71. package/tools/__pycache__/alert_bridge.cpython-314.pyc +0 -0
  72. package/tools/__pycache__/buildlog.cpython-314.pyc +0 -0
  73. package/tools/__pycache__/ci_merge_wait.cpython-314.pyc +0 -0
  74. package/tools/__pycache__/ensure_state.cpython-314.pyc +0 -0
  75. package/tools/__pycache__/eod_sweep.cpython-314.pyc +0 -0
  76. package/tools/__pycache__/fleet_ledger.cpython-314.pyc +0 -0
  77. package/tools/__pycache__/fleet_prompt_extractor.cpython-314.pyc +0 -0
  78. package/tools/__pycache__/healthcheck.cpython-314.pyc +0 -0
  79. package/tools/__pycache__/heartbeat.cpython-314.pyc +0 -0
  80. package/tools/__pycache__/inbox_drain.cpython-314.pyc +0 -0
  81. package/tools/__pycache__/launch_tui.cpython-314.pyc +0 -0
  82. package/tools/__pycache__/metrics_gate.cpython-314.pyc +0 -0
  83. package/tools/__pycache__/orchestrator_status.cpython-314.pyc +0 -0
  84. package/tools/__pycache__/power_selftest.cpython-314.pyc +0 -0
  85. package/tools/__pycache__/prepublish_scan.cpython-314.pyc +0 -0
  86. package/tools/__pycache__/rotate_logs.cpython-314.pyc +0 -0
  87. package/tools/__pycache__/scanner_selftest.cpython-314.pyc +0 -0
  88. package/tools/__pycache__/secret_scan.cpython-314.pyc +0 -0
  89. package/tools/__pycache__/self_stats.cpython-314.pyc +0 -0
  90. package/tools/__pycache__/session_usage_summary.cpython-314.pyc +0 -0
  91. package/tools/__pycache__/stall_check.cpython-314.pyc +0 -0
  92. package/tools/__pycache__/transcript_replay.cpython-314.pyc +0 -0
  93. package/tools/__pycache__/transcript_timeline.cpython-314.pyc +0 -0
  94. package/tools/__pycache__/verify_dash.cpython-314.pyc +0 -0
  95. package/tools/__pycache__/verify_submit_encoding.cpython-314.pyc +0 -0
  96. package/tools/agent-forensics.sh +186 -0
  97. package/tools/alert_bridge.py +449 -0
  98. package/tools/buildlog.py +182 -0
  99. package/tools/ci_merge_wait.py +317 -0
  100. package/tools/ensure_state.py +94 -0
  101. package/tools/eod_sweep.py +239 -0
  102. package/tools/fleet_ledger.py +272 -0
  103. package/tools/fleet_prompt_extractor.py +134 -0
  104. package/tools/healthcheck.py +296 -0
  105. package/tools/heartbeat.py +131 -0
  106. package/tools/inbox_drain.py +249 -0
  107. package/tools/launch_tui.py +51 -3
  108. package/tools/lock.mjs +211 -0
  109. package/tools/metrics_gate.py +205 -0
  110. package/tools/orchestrator_status.py +99 -0
  111. package/tools/power_selftest.py +386 -0
  112. package/tools/prepublish_scan.py +84 -0
  113. package/tools/proposals.mjs +248 -0
  114. package/tools/reconstitute.sh +467 -0
  115. package/tools/rotate_logs.py +228 -0
  116. package/tools/scanner_selftest.py +303 -0
  117. package/tools/secret_scan.py +138 -31
  118. package/tools/self_stats.py +509 -0
  119. package/tools/session_usage_summary.py +198 -0
  120. package/tools/stall_check.py +172 -0
  121. package/tools/svg_to_png.mjs +50 -0
  122. package/tools/transcript_replay.py +236 -0
  123. package/tools/transcript_timeline.py +184 -0
  124. package/tools/verify_dash.py +513 -0
  125. package/tools/verify_submit_encoding.py +202 -0
  126. package/ui/CLAUDE.md +125 -0
  127. package/ui/__pycache__/agents.cpython-314.pyc +0 -0
  128. package/ui/__pycache__/collectors.cpython-314.pyc +0 -0
  129. package/ui/__pycache__/config.cpython-314.pyc +0 -0
  130. package/ui/__pycache__/cost.cpython-314.pyc +0 -0
  131. package/ui/__pycache__/csrf.cpython-314.pyc +0 -0
  132. package/ui/__pycache__/handler.cpython-314.pyc +0 -0
  133. package/ui/__pycache__/render.cpython-314.pyc +0 -0
  134. package/ui/__pycache__/serve.cpython-314.pyc +0 -0
  135. package/ui/__pycache__/sse.cpython-314.pyc +0 -0
  136. package/ui/agents.py +183 -0
  137. package/ui/api/__init__.py +84 -0
  138. package/ui/api/__pycache__/__init__.cpython-314.pyc +0 -0
  139. package/ui/api/__pycache__/submit.cpython-314.pyc +0 -0
  140. package/ui/api/__pycache__/tracker.cpython-314.pyc +0 -0
  141. package/ui/api/submit.py +97 -0
  142. package/ui/api/tracker.py +116 -0
  143. package/ui/collectors.py +735 -0
  144. package/ui/config.py +123 -0
  145. package/ui/cost.py +260 -0
  146. package/ui/csrf.py +144 -0
  147. package/ui/handler.py +676 -0
  148. package/ui/render.py +49 -0
  149. package/ui/serve.py +68 -796
  150. package/ui/sse.py +183 -0
  151. package/ui/web/.gitattributes +13 -0
  152. package/ui/web/dist/assets/index-2LZDQirC.js +9 -0
  153. package/ui/web/dist/assets/index-D4M1qyOv.css +1 -0
  154. package/ui/web/dist/index.html +14 -0
  155. package/ui/web/index.html +13 -0
  156. package/ui/web/package-lock.json +2225 -0
  157. package/ui/web/package.json +26 -0
  158. package/ui/web/src/App.test.tsx +74 -0
  159. package/ui/web/src/App.tsx +142 -0
  160. package/ui/web/src/CONTRIBUTING-UI.md +49 -0
  161. package/ui/web/src/components/AgentRow.css +187 -0
  162. package/ui/web/src/components/AgentRow.test.tsx +209 -0
  163. package/ui/web/src/components/AgentRow.tsx +207 -0
  164. package/ui/web/src/components/AgentsPanel.css +108 -0
  165. package/ui/web/src/components/AgentsPanel.test.tsx +41 -0
  166. package/ui/web/src/components/AgentsPanel.tsx +58 -0
  167. package/ui/web/src/components/AlertsPanel.css +88 -0
  168. package/ui/web/src/components/AlertsPanel.test.tsx +51 -0
  169. package/ui/web/src/components/AlertsPanel.tsx +67 -0
  170. package/ui/web/src/components/BacklogPanel.test.tsx +126 -0
  171. package/ui/web/src/components/BacklogPanel.tsx +122 -0
  172. package/ui/web/src/components/CostChart.css +110 -0
  173. package/ui/web/src/components/CostChart.test.tsx +144 -0
  174. package/ui/web/src/components/CostChart.tsx +152 -0
  175. package/ui/web/src/components/CostTable.css +93 -0
  176. package/ui/web/src/components/CostTable.test.tsx +165 -0
  177. package/ui/web/src/components/CostTable.tsx +94 -0
  178. package/ui/web/src/components/EventsFeed.css +68 -0
  179. package/ui/web/src/components/EventsFeed.test.tsx +36 -0
  180. package/ui/web/src/components/EventsFeed.tsx +31 -0
  181. package/ui/web/src/components/HealthHeader.css +137 -0
  182. package/ui/web/src/components/HealthHeader.test.tsx +278 -0
  183. package/ui/web/src/components/HealthHeader.tsx +281 -0
  184. package/ui/web/src/components/InboxForm.css +135 -0
  185. package/ui/web/src/components/InboxForm.test.tsx +208 -0
  186. package/ui/web/src/components/InboxForm.tsx +116 -0
  187. package/ui/web/src/components/MessagesTail.module.css +144 -0
  188. package/ui/web/src/components/MessagesTail.test.tsx +176 -0
  189. package/ui/web/src/components/MessagesTail.tsx +94 -0
  190. package/ui/web/src/components/ReposPanel.css +90 -0
  191. package/ui/web/src/components/ReposPanel.test.tsx +45 -0
  192. package/ui/web/src/components/ReposPanel.tsx +67 -0
  193. package/ui/web/src/components/Scorecard.css +106 -0
  194. package/ui/web/src/components/Scorecard.test.tsx +117 -0
  195. package/ui/web/src/components/Scorecard.tsx +85 -0
  196. package/ui/web/src/components/Timeline.module.css +151 -0
  197. package/ui/web/src/components/Timeline.test.tsx +215 -0
  198. package/ui/web/src/components/Timeline.tsx +99 -0
  199. package/ui/web/src/components/TrackerBoard.test.tsx +121 -0
  200. package/ui/web/src/components/TrackerBoard.tsx +107 -0
  201. package/ui/web/src/components/TrackerCard.test.tsx +180 -0
  202. package/ui/web/src/components/TrackerCard.tsx +160 -0
  203. package/ui/web/src/components/TrackerForm.test.tsx +189 -0
  204. package/ui/web/src/components/TrackerForm.tsx +144 -0
  205. package/ui/web/src/lib/api.ts +218 -0
  206. package/ui/web/src/lib/format.test.ts +89 -0
  207. package/ui/web/src/lib/format.ts +103 -0
  208. package/ui/web/src/lib/sanitizeUrl.test.ts +84 -0
  209. package/ui/web/src/lib/sanitizeUrl.ts +38 -0
  210. package/ui/web/src/lib/types.ts +230 -0
  211. package/ui/web/src/lib/useHashRoute.test.ts +60 -0
  212. package/ui/web/src/lib/useHashRoute.ts +23 -0
  213. package/ui/web/src/lib/useSSE.ts +175 -0
  214. package/ui/web/src/main.tsx +10 -0
  215. package/ui/web/src/styles/global.css +179 -0
  216. package/ui/web/src/styles/theme.css +184 -0
  217. package/ui/web/src/styles/work.css +572 -0
  218. package/ui/web/src/test/fixtures.ts +385 -0
  219. package/ui/web/src/test/setup.ts +49 -0
  220. package/ui/web/src/views/Activity.module.css +43 -0
  221. package/ui/web/src/views/Activity.test.tsx +89 -0
  222. package/ui/web/src/views/Activity.tsx +31 -0
  223. package/ui/web/src/views/Cost.css +87 -0
  224. package/ui/web/src/views/Cost.test.tsx +142 -0
  225. package/ui/web/src/views/Cost.tsx +54 -0
  226. package/ui/web/src/views/Overview.css +51 -0
  227. package/ui/web/src/views/Overview.test.tsx +76 -0
  228. package/ui/web/src/views/Overview.tsx +46 -0
  229. package/ui/web/src/views/Work.test.tsx +82 -0
  230. package/ui/web/src/views/Work.tsx +79 -0
  231. package/ui/web/src/vite-env.d.ts +10 -0
  232. package/ui/web/tsconfig.json +22 -0
  233. package/ui/web/vite.config.ts +25 -0
  234. package/ui/web/vitest.config.ts +12 -0
package/ui/sse.py ADDED
@@ -0,0 +1,183 @@
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
+ import cost
11
+ from collectors import (parse_audit_backlog, _snapshot_data, _snapshot_tracker,
12
+ _snapshot_orchestrator_status, drain_tracker_inbox)
13
+ from agents import get_fleet_agents, _transcripts_fingerprint
14
+
15
+
16
+ _sse_lock = threading.Lock()
17
+
18
+ _sse_clients = [] # list[queue.Queue]
19
+
20
+ _latest_lock = threading.Lock()
21
+
22
+ _latest_snapshots = {"data": None, "backlog": None, "agents": None,
23
+ "tracker": None, "status": None, "cost": None} # name -> json str
24
+
25
+ _collector_lock = threading.Lock()
26
+
27
+ _collector_started = False
28
+
29
+ _collector_stop_event = threading.Event()
30
+
31
+
32
+ def reset_state():
33
+ """Reset collector/SSE singleton state for a fresh serve import.
34
+
35
+ The sse module object is cached in sys.modules, so per-test re-imports of
36
+ serve would otherwise share one collector thread + snapshot dict (a prior
37
+ test's thread keeps polling its own dir; later tests never see their state).
38
+ serve.py calls this at import to restore the per-import isolation the
39
+ pre-split monolith had. In production serve is imported once, so this is a
40
+ harmless no-op before the collector ever starts.
41
+ """
42
+ global _collector_started, _collector_stop_event
43
+ with _collector_lock:
44
+ _collector_stop_event.set() # stop a thread left over from a prior import
45
+ _collector_stop_event = threading.Event()
46
+ _collector_started = False
47
+ with _latest_lock:
48
+ for k in list(_latest_snapshots):
49
+ _latest_snapshots[k] = None
50
+ with _sse_lock:
51
+ _sse_clients.clear()
52
+
53
+
54
+ def register_sse_client():
55
+ """Register a new SSE client queue. Returns the queue to read events from, or None if cap exceeded."""
56
+ with _sse_lock:
57
+ if len(_sse_clients) >= config.SSE_MAX_CLIENTS:
58
+ return None # Caller will return HTTP 503
59
+ q = queue.Queue(maxsize=config.SSE_QUEUE_MAXSIZE)
60
+ _sse_clients.append(q)
61
+ return q
62
+
63
+ def unregister_sse_client(q):
64
+ """Remove a disconnected SSE client's queue."""
65
+ with _sse_lock:
66
+ if q in _sse_clients:
67
+ _sse_clients.remove(q)
68
+
69
+ def broadcast_sse(event_name, payload):
70
+ """Push (event_name, payload) onto every currently-registered client queue.
71
+
72
+ If a client queue is full, drop the oldest event to make room (bounded backpressure).
73
+ This prevents one slow client from blocking the broadcast.
74
+ """
75
+ with _sse_lock:
76
+ clients = list(_sse_clients)
77
+ for q in clients:
78
+ try:
79
+ q.put_nowait((event_name, payload))
80
+ except queue.Full:
81
+ # Queue is full: drop the oldest event and retry
82
+ try:
83
+ q.get_nowait() # Remove oldest
84
+ q.put_nowait((event_name, payload)) # Add new
85
+ except Exception as e:
86
+ print(f"[collector_loop] Exception: {type(e).__name__}: {e}", file=sys.stderr, flush=True)
87
+ except Exception:
88
+ pass
89
+
90
+ def _maybe_emit(name, snapshot, last_hashes):
91
+ """Hash-gate: only store + broadcast a section if its content actually changed."""
92
+ payload = json.dumps(snapshot, default=str, sort_keys=True)
93
+ digest = hashlib.sha256(payload.encode("utf-8")).hexdigest()
94
+ if last_hashes.get(name) == digest:
95
+ return
96
+ last_hashes[name] = digest
97
+ with _latest_lock:
98
+ _latest_snapshots[name] = payload
99
+ broadcast_sse(name, payload)
100
+
101
+ def collector_loop(stop_event):
102
+ """Background loop: poll cheap sources, gate expensive ones, broadcast on change."""
103
+ last_hashes = {}
104
+ last_backlog_mtime = object() # sentinel guaranteed != any real mtime/None
105
+ last_agents_fingerprint = None
106
+ cached_backlog_snapshot = {"tiers": []}
107
+ cached_agents_snapshot = []
108
+ last_tracker_mtime = object()
109
+ last_status_mtime = object()
110
+ last_cost_mtime = object()
111
+ cached_tracker_snapshot = {'items': []}
112
+ cached_status_snapshot = {'orchestrators': []}
113
+ cached_cost_snapshot = {}
114
+
115
+ while not stop_event.is_set():
116
+ try:
117
+ _maybe_emit("data", _snapshot_data(), last_hashes)
118
+
119
+ try:
120
+ backlog_mtime = config.AUDIT_BACKLOG_FILE.stat().st_mtime if config.AUDIT_BACKLOG_FILE.exists() else None
121
+ except OSError:
122
+ backlog_mtime = None
123
+ if backlog_mtime != last_backlog_mtime:
124
+ last_backlog_mtime = backlog_mtime
125
+ cached_backlog_snapshot = parse_audit_backlog()
126
+ _maybe_emit("backlog", cached_backlog_snapshot, last_hashes)
127
+
128
+ fingerprint = _transcripts_fingerprint()
129
+ if fingerprint != last_agents_fingerprint:
130
+ last_agents_fingerprint = fingerprint
131
+ cached_agents_snapshot = get_fleet_agents()
132
+ _maybe_emit("agents", cached_agents_snapshot, last_hashes)
133
+
134
+ # Emit tracker section (mtime-gated)
135
+ try:
136
+ tracker_mtime = (config.STATE_DIR / "tracker.json").stat().st_mtime if (config.STATE_DIR / "tracker.json").exists() else None
137
+ except OSError:
138
+ tracker_mtime = None
139
+ if tracker_mtime != last_tracker_mtime:
140
+ last_tracker_mtime = tracker_mtime
141
+ cached_tracker_snapshot = _snapshot_tracker()
142
+ _maybe_emit("tracker", cached_tracker_snapshot, last_hashes)
143
+
144
+ # Emit status section (mtime-gated)
145
+ try:
146
+ status_mtime = (config.STATE_DIR / "orchestrator-status.json").stat().st_mtime if (config.STATE_DIR / "orchestrator-status.json").exists() else None
147
+ except OSError:
148
+ status_mtime = None
149
+ if status_mtime != last_status_mtime:
150
+ last_status_mtime = status_mtime
151
+ cached_status_snapshot = _snapshot_orchestrator_status()
152
+ _maybe_emit("status", cached_status_snapshot, last_hashes)
153
+
154
+ # Emit cost section (wave-14 6th section; mtime-gated on the
155
+ # outcomes ledger, mirroring the tracker gate above)
156
+ try:
157
+ cost_mtime = config.LEDGER_FILE.stat().st_mtime if config.LEDGER_FILE.exists() else None
158
+ except OSError:
159
+ cost_mtime = None
160
+ if cost_mtime != last_cost_mtime:
161
+ last_cost_mtime = cost_mtime
162
+ cached_cost_snapshot = cost.get_cost_summary()
163
+ _maybe_emit("cost", cached_cost_snapshot, last_hashes)
164
+
165
+ # Drain inbox
166
+ try:
167
+ drain_tracker_inbox()
168
+ except Exception as e:
169
+ print(f"[collector] Inbox drain error: {e}", file=sys.stderr, flush=True)
170
+ except Exception as e:
171
+ print(f"[collector_loop] Exception: {type(e).__name__}: {e}", file=sys.stderr, flush=True)
172
+ stop_event.wait(config.COLLECTOR_INTERVAL)
173
+
174
+ def start_collector_thread():
175
+ """Idempotently start the background collector daemon thread (safe to call from
176
+ multiple request handlers / run_server — only the first call actually starts it)."""
177
+ global _collector_started
178
+ with _collector_lock:
179
+ if _collector_started:
180
+ return
181
+ _collector_started = True
182
+ t = threading.Thread(target=collector_loop, args=(_collector_stop_event,), daemon=True)
183
+ t.start()
@@ -0,0 +1,13 @@
1
+ # Built artifacts must stay byte-identical to the Vite output on every
2
+ # platform, or CI's dist drift gate (rebuild + git diff) false-positives on
3
+ # line endings (wave-14 plan, risk 2).
4
+ dist/** -text
5
+
6
+ # Source + config are LF everywhere (Vite emits LF; keeps Windows checkouts
7
+ # from reintroducing CRLF into files the build hashes).
8
+ *.ts text eol=lf
9
+ *.tsx text eol=lf
10
+ *.css text eol=lf
11
+ *.html text eol=lf
12
+ *.json text eol=lf
13
+ *.md text eol=lf