@tekyzinc/gsd-t 3.13.16 → 3.16.11

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 (54) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +1 -0
  3. package/bin/gsd-t-benchmark-orchestrator.js +437 -0
  4. package/bin/gsd-t-capture-lint.cjs +276 -0
  5. package/bin/gsd-t-completion-check.cjs +106 -0
  6. package/bin/gsd-t-orchestrator-config.cjs +64 -0
  7. package/bin/gsd-t-orchestrator-queue.cjs +180 -0
  8. package/bin/gsd-t-orchestrator-recover.cjs +231 -0
  9. package/bin/gsd-t-orchestrator-worker.cjs +219 -0
  10. package/bin/gsd-t-orchestrator.js +534 -0
  11. package/bin/gsd-t-stream-feed-client.cjs +151 -0
  12. package/bin/gsd-t-task-brief-compactor.cjs +89 -0
  13. package/bin/gsd-t-task-brief-template.cjs +96 -0
  14. package/bin/gsd-t-task-brief.js +249 -0
  15. package/bin/gsd-t-token-backfill.cjs +366 -0
  16. package/bin/gsd-t-token-capture.cjs +306 -0
  17. package/bin/gsd-t-token-dashboard.cjs +318 -0
  18. package/bin/gsd-t-token-regenerate-log.cjs +129 -0
  19. package/bin/gsd-t-transcript-tee.cjs +246 -0
  20. package/bin/gsd-t-unattended-heartbeat.cjs +188 -0
  21. package/bin/gsd-t-unattended-platform.cjs +191 -27
  22. package/bin/gsd-t-unattended-safety.cjs +8 -1
  23. package/bin/gsd-t-unattended.cjs +192 -31
  24. package/bin/gsd-t.js +329 -2
  25. package/bin/supervisor-pid-fingerprint.cjs +126 -0
  26. package/commands/gsd-t-debug.md +63 -51
  27. package/commands/gsd-t-design-decompose.md +2 -7
  28. package/commands/gsd-t-doc-ripple.md +20 -11
  29. package/commands/gsd-t-execute.md +82 -50
  30. package/commands/gsd-t-integrate.md +43 -16
  31. package/commands/gsd-t-plan.md +20 -7
  32. package/commands/gsd-t-prd.md +19 -12
  33. package/commands/gsd-t-quick.md +64 -29
  34. package/commands/gsd-t-resume.md +51 -4
  35. package/commands/gsd-t-unattended.md +19 -20
  36. package/commands/gsd-t-verify.md +48 -32
  37. package/commands/gsd-t-visualize.md +19 -17
  38. package/commands/gsd-t-wave.md +29 -27
  39. package/docs/architecture.md +16 -0
  40. package/docs/m40-benchmark-report.md +35 -0
  41. package/docs/requirements.md +20 -0
  42. package/package.json +1 -1
  43. package/scripts/gsd-t-dashboard-server.js +291 -4
  44. package/scripts/gsd-t-dashboard.html +31 -1
  45. package/scripts/gsd-t-design-review-server.js +3 -1
  46. package/scripts/gsd-t-stream-feed-server.js +428 -0
  47. package/scripts/gsd-t-stream-feed.html +1168 -0
  48. package/scripts/gsd-t-token-aggregator.js +373 -0
  49. package/scripts/gsd-t-transcript.html +422 -0
  50. package/scripts/hooks/gsd-t-in-session-probe.js +62 -0
  51. package/scripts/hooks/pre-commit-capture-lint +26 -0
  52. package/templates/CLAUDE-global.md +69 -0
  53. package/scripts/gsd-t-agent-dashboard-server.js +0 -424
  54. package/scripts/gsd-t-agent-dashboard.html +0 -1043
@@ -1,1043 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>GSD-T Agent Topology</title>
6
- <style>
7
- :root {
8
- --bg: #0a0a1a; --surface: #111128; --card: #1a1a2e; --border: #2a2a44;
9
- --text: #e6edf3; --muted: #7d8590; --dim: #484f58; --inactive: #555;
10
- --cyan: #00d4ff; --green: #00cc66; --red: #ff4444; --gray: #666;
11
- --purple: #a371f7; --blue: #388bfd; --yellow: #d29922;
12
- --orange: #ff8c42; --pink: #f778ba; --teal: #42ffc2; --lime: #b8e986;
13
- --font: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
14
- --radius: 10px;
15
- }
16
- * { box-sizing: border-box; margin: 0; padding: 0; }
17
- body { background: var(--bg); color: var(--text); font-family: var(--font); font-size: 12px;
18
- height: 100vh; display: grid; overflow: hidden;
19
- grid-template-rows: 52px 1fr;
20
- grid-template-columns: 1fr 280px;
21
- grid-template-areas: "header header" "main feed";
22
- }
23
-
24
- /* ── Header ────────────────────────────────────────── */
25
- .header { grid-area: header; display: flex; align-items: center; gap: 14px;
26
- padding: 0 24px; background: var(--surface); border-bottom: 1px solid var(--border); }
27
- .logo { display: flex; align-items: center; gap: 8px; }
28
- .logo-icon { color: var(--cyan); font-size: 20px; }
29
- .logo-text { color: var(--text); font-weight: bold; font-size: 15px; letter-spacing: -0.5px; }
30
-
31
- .badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px;
32
- border-radius: 12px; font-size: 11px; font-weight: 600; }
33
- .badge-active { background: rgba(0,204,102,0.2); color: var(--green); }
34
- .badge-active .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green);
35
- animation: pulse 1.5s ease-in-out infinite; }
36
- .badge-tokens { background: rgba(0,212,255,0.15); color: var(--cyan); }
37
-
38
- @keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .3 } }
39
-
40
- .header-spacer { flex: 1; }
41
-
42
- .legend { display: flex; align-items: center; gap: 16px; }
43
- .legend-item { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--muted); }
44
- .legend-dot { width: 8px; height: 8px; border-radius: 50%; }
45
-
46
- .header-actions { display: flex; gap: 8px; margin-left: 16px; }
47
- .header-btn { background: var(--card); border: 1px solid var(--border); border-radius: 6px;
48
- color: var(--muted); font-family: var(--font); font-size: 11px; padding: 5px 12px;
49
- cursor: pointer; display: flex; align-items: center; gap: 5px; }
50
- .header-btn:hover { color: var(--text); border-color: var(--text); }
51
-
52
- /* ── Main graph area ──────────────────────────────── */
53
- .main { grid-area: main; overflow: auto; position: relative; }
54
-
55
- /* Category columns */
56
- .columns-container { display: flex; min-height: 100%; padding: 0 16px 30px; }
57
- .category-col { flex: 1; display: flex; flex-direction: column; align-items: center;
58
- padding: 0 4px; min-width: 140px; position: relative; }
59
- .category-label { font-size: 13px; font-weight: 600; letter-spacing: 0.5px;
60
- padding: 14px 0 18px; text-align: center; }
61
-
62
- /* Agent cards in columns */
63
- .cards-stack { display: flex; flex-direction: column; gap: 16px; align-items: center; width: 100%; }
64
-
65
- .agent-card { width: 140px; background: var(--card); border-radius: var(--radius);
66
- border: 2px solid; padding: 14px 14px 12px; cursor: pointer;
67
- transition: transform 0.15s, box-shadow 0.15s; position: relative;
68
- box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
69
- .agent-card:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.5); }
70
- .agent-card.selected { box-shadow: 0 0 0 2px var(--cyan), 0 4px 16px rgba(0,0,0,0.5); }
71
-
72
- /* Inactive: border color set via inline style (muted version of category color) */
73
- .agent-card.inactive .card-icon,
74
- .agent-card.inactive .card-name,
75
- .agent-card.inactive .card-desc,
76
- .agent-card.inactive .card-status-line { color: var(--dim) !important; opacity: 0.5; }
77
-
78
- /* Instance badge */
79
- /* Badge background set via inline style to match category color */
80
- .instance-badge { position: absolute; top: -8px; right: -8px;
81
- color: #fff; font-size: 10px; font-weight: bold;
82
- width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center;
83
- justify-content: center; border: 2px solid var(--bg); }
84
-
85
- .card-icon { font-size: 22px; text-align: center; margin-bottom: 8px; }
86
- .card-name { font-weight: bold; font-size: 12px; color: var(--text); text-align: center;
87
- margin-bottom: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
88
- .card-desc { font-size: 10px; color: var(--muted); text-align: center; margin-bottom: 8px;
89
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
90
- .card-status-line { display: flex; align-items: center; justify-content: center; gap: 5px;
91
- font-size: 10px; }
92
- .card-status-icon { font-size: 11px; }
93
- .card-status-text { text-transform: capitalize; }
94
-
95
- .card-tools { font-size: 9px; color: var(--dim); text-align: center; margin-top: 4px; }
96
-
97
- /* Category border colors — set via inline style from JS */
98
-
99
- /* SVG overlay for connection lines */
100
- .graph-svg { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 1; }
101
- .edge-line { stroke: var(--border); stroke-width: 1.5; fill: none; }
102
- .edge-line.active { stroke: var(--cyan); stroke-width: 2; stroke-dasharray: 6 3;
103
- animation: dash 1s linear infinite; }
104
- .edge-dot { r: 4; fill: var(--border); }
105
- .edge-dot.active { fill: var(--cyan); }
106
- @keyframes dash { to { stroke-dashoffset: -9; } }
107
-
108
- /* Empty state */
109
- .graph-empty { position: absolute; inset: 0; display: flex; align-items: center;
110
- justify-content: center; color: var(--dim); font-size: 13px; z-index: 0; }
111
-
112
- /* ── Activity Feed ────────────────────────────────── */
113
- .feed { grid-area: feed; background: var(--surface); border-left: 1px solid var(--border);
114
- display: flex; flex-direction: column; overflow: hidden; }
115
- .feed-header { padding: 16px 20px 12px; border-bottom: 1px solid var(--border); }
116
- .feed-title { font-size: 14px; font-weight: bold; color: var(--text); }
117
- .feed-subtitle { font-size: 10px; color: var(--muted); margin-top: 2px; }
118
-
119
- .feed-list { flex: 1; overflow-y: auto; padding: 8px 0; }
120
- .feed-list::-webkit-scrollbar { width: 4px; }
121
- .feed-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
122
-
123
- .feed-item { display: flex; gap: 10px; padding: 10px 20px; border-bottom: 1px solid rgba(42,42,68,0.4); }
124
- .feed-item:hover { background: rgba(255,255,255,0.02); }
125
-
126
- .feed-icon { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center;
127
- justify-content: center; font-size: 13px; flex-shrink: 0; border: 1px solid; }
128
- .feed-icon.spawn { background: rgba(0,204,102,0.15); border-color: var(--green); color: var(--green); }
129
- .feed-icon.complete { background: rgba(0,212,255,0.15); border-color: var(--cyan); color: var(--cyan); }
130
- .feed-icon.fail { background: rgba(255,68,68,0.15); border-color: var(--red); color: var(--red); }
131
- .feed-icon.tool { background: rgba(210,153,34,0.15); border-color: var(--yellow); color: var(--yellow); }
132
- .feed-icon.phase { background: rgba(163,113,247,0.15); border-color: var(--purple); color: var(--purple); }
133
-
134
- .feed-body { flex: 1; min-width: 0; }
135
- .feed-agent { font-weight: bold; font-size: 11px; }
136
- .feed-agent .ts { font-weight: normal; color: var(--dim); margin-left: 6px; }
137
- .feed-text { font-size: 11px; color: var(--muted); margin-top: 2px; line-height: 1.4;
138
- overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
139
- .feed-tool { display: inline-block; background: var(--bg); border-radius: 3px;
140
- padding: 1px 6px; font-size: 10px; color: var(--dim); margin-top: 4px; }
141
- .feed-dur { color: var(--dim); font-size: 10px; margin-left: 8px; }
142
-
143
- /* ── Detail Panel (overlay) ───────────────────────── */
144
- .detail-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5);
145
- z-index: 100; display: none; align-items: center; justify-content: center; }
146
- .detail-overlay.open { display: flex; }
147
- .detail-panel { width: 480px; max-height: 80vh; background: var(--surface);
148
- border-radius: 12px; border: 1px solid var(--border); display: flex; flex-direction: column;
149
- overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.5); }
150
- .detail-header { display: flex; align-items: center; padding: 16px 20px;
151
- border-bottom: 1px solid var(--border); }
152
- .detail-header .title { flex: 1; font-weight: bold; font-size: 14px; }
153
- .detail-close { background: none; border: 1px solid var(--border); border-radius: 6px;
154
- color: var(--muted); font-size: 16px; cursor: pointer; width: 30px; height: 30px;
155
- display: flex; align-items: center; justify-content: center; font-family: var(--font); }
156
- .detail-close:hover { color: var(--text); border-color: var(--text); }
157
-
158
- .detail-body { flex: 1; overflow-y: auto; padding: 20px; }
159
- .detail-body::-webkit-scrollbar { width: 4px; }
160
- .detail-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
161
-
162
- .detail-section { margin-bottom: 18px; }
163
- .detail-section-title { color: var(--muted); font-size: 10px; text-transform: uppercase;
164
- letter-spacing: 0.8px; margin-bottom: 8px; }
165
- .detail-desc { font-size: 11px; color: var(--text); margin-bottom: 12px; line-height: 1.4; }
166
-
167
- .detail-summary { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 18px; }
168
- .summary-item { background: var(--bg); border-radius: 8px; padding: 10px 12px; }
169
- .summary-item .label { color: var(--dim); font-size: 9px; text-transform: uppercase; }
170
- .summary-item .val { color: var(--text); font-size: 18px; font-weight: bold; margin-top: 2px; }
171
-
172
- .tool-timeline { display: flex; flex-direction: column; gap: 4px; }
173
- .tool-entry { display: flex; align-items: center; gap: 8px; padding: 4px 8px;
174
- background: var(--bg); border-radius: 4px; font-size: 10px; }
175
- .tool-name { color: var(--text); font-weight: 500; min-width: 60px; }
176
- .tool-detail { flex: 1; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
177
- .tool-ts { color: var(--dim); font-size: 9px; white-space: nowrap; }
178
- </style>
179
- </head>
180
- <body>
181
-
182
- <!-- Header -->
183
- <div class="header">
184
- <div class="logo">
185
- <span class="logo-icon">&#x2727;</span>
186
- <span class="logo-text" id="projectName">GSD-T Agent Topology</span>
187
- </div>
188
- <span class="badge badge-active"><span class="dot"></span> <span id="hdrActive">0</span>/<span id="hdrTotal">0</span> Active</span>
189
- <span class="badge badge-tokens" id="hdrTokens">0 tokens</span>
190
- <span class="header-spacer"></span>
191
- <div class="legend" id="legend"></div>
192
- <div class="header-actions">
193
- <button class="header-btn" onclick="togglePause()" id="pauseBtn">&#x23F8; Pause</button>
194
- <button class="header-btn" onclick="resetView()">&#x21BB; Reset</button>
195
- </div>
196
- </div>
197
-
198
- <!-- Main graph -->
199
- <div class="main" id="mainArea">
200
- <svg class="graph-svg" id="graphSvg"></svg>
201
- <div class="columns-container" id="columnsContainer"></div>
202
- <div class="graph-empty" id="graphEmpty">Waiting for agent activity&hellip;</div>
203
- </div>
204
-
205
- <!-- Activity Feed -->
206
- <div class="feed">
207
- <div class="feed-header">
208
- <div class="feed-title">Activity Feed</div>
209
- <div class="feed-subtitle">Real-time agent events</div>
210
- </div>
211
- <div class="feed-list" id="feedList"></div>
212
- </div>
213
-
214
- <!-- Detail overlay -->
215
- <div class="detail-overlay" id="detailOverlay" onclick="if(event.target===this)closeDetail()">
216
- <div class="detail-panel">
217
- <div class="detail-header">
218
- <span class="title" id="detailTitle">Agent Details</span>
219
- <button class="detail-close" onclick="closeDetail()">&times;</button>
220
- </div>
221
- <div class="detail-body" id="detailBody"></div>
222
- </div>
223
- </div>
224
-
225
- <script>
226
- "use strict";
227
-
228
- // ── Categories & Role Mapping ─────────────────────────
229
- // Categories map to the GSD-T workflow phases
230
- const CATEGORIES = [
231
- { id: "research", label: "Research", color: "#388bfd" },
232
- { id: "design", label: "Design", color: "#a371f7" },
233
- { id: "plan", label: "Plan", color: "#00d4ff" },
234
- { id: "execute", label: "Execute", color: "#ff8c42" },
235
- { id: "quality", label: "Quality", color: "#00cc66" },
236
- { id: "integrate", label: "Integrate", color: "#f778ba" },
237
- { id: "adhoc", label: "Ad-hoc", color: "#d29922" },
238
- ];
239
-
240
- // GSD-T workflow roles
241
- const ROLE_DEFS = {
242
- // Research phase
243
- "Research": { cat: "research", icon: "\ud83d\udd0d", desc: "Codebase Exploration" },
244
- "Scan": { cat: "research", icon: "\ud83d\udcf1", desc: "Tech Debt Discovery" },
245
- "Gap Analysis": { cat: "research", icon: "\ud83d\udccb", desc: "Requirements vs Code" },
246
-
247
- // Design phase
248
- "PRD": { cat: "design", icon: "\ud83d\udcd1", desc: "Product Requirements" },
249
- "Feature": { cat: "design", icon: "\u2728", desc: "Feature Decomposition" },
250
- "Milestone": { cat: "design", icon: "\ud83c\udfc1", desc: "Milestone Definition" },
251
- "Partition": { cat: "design", icon: "\ud83e\udde9", desc: "Domain Decomposition" },
252
- "Discuss": { cat: "design", icon: "\ud83d\udcac", desc: "Design Exploration" },
253
- "Design Review": { cat: "design", icon: "\ud83c\udfa8", desc: "Design Verification" },
254
-
255
- // Plan phase
256
- "Planning": { cat: "plan", icon: "\ud83d\udcdd", desc: "Task List Creation" },
257
- "Impact": { cat: "plan", icon: "\ud83d\udca5", desc: "Downstream Analysis" },
258
- "Brainstorm": { cat: "plan", icon: "\ud83d\udca1", desc: "Creative Exploration" },
259
-
260
- // Execute phase
261
- "Wave": { cat: "execute", icon: "\ud83c\udf0a", desc: "Full Cycle Orchestration" },
262
- "Execute": { cat: "execute", icon: "\u26a1", desc: "Task Execution" },
263
- "Coding": { cat: "execute", icon: "\ud83d\udcbb", desc: "Code Implementation" },
264
-
265
- // Ad-hoc (standalone commands that invoke agents across phases)
266
- "Quick": { cat: "adhoc", icon: "\ud83c\udfaf", desc: "Fast Task Execution" },
267
- "Debug": { cat: "adhoc", icon: "\ud83d\udd27", desc: "Systematic Debugging" },
268
-
269
- // Quality phase
270
- "QA": { cat: "quality", icon: "\u2705", desc: "Test & Validate" },
271
- "Red Team": { cat: "quality", icon: "\ud83d\udee1\ufe0f", desc: "Adversarial Testing" },
272
- "Test Sync": { cat: "quality", icon: "\ud83e\uddea", desc: "Test Alignment" },
273
- "Verify": { cat: "quality", icon: "\u2714\ufe0f", desc: "Quality Gates" },
274
- "Audit": { cat: "quality", icon: "\ud83d\udd0e", desc: "Cost/Benefit Analysis" },
275
-
276
- // Integrate phase
277
- "Integrate": { cat: "integrate", icon: "\ud83d\udd17", desc: "Wire Domains Together" },
278
- "Doc Ripple": { cat: "integrate", icon: "\ud83d\udcc4", desc: "Document Propagation" },
279
- "Complete": { cat: "integrate", icon: "\ud83c\udfc6", desc: "Milestone Completion" },
280
-
281
- // Orchestration (placed in execute — they drive execution)
282
- "Orchestrator": { cat: "execute", icon: "\ud83c\udfaf", desc: "Workflow Coordinator" },
283
- "Session": { cat: "execute", icon: "\ud83d\udcbb", desc: "Interactive Session" },
284
- "Unattended": { cat: "execute", icon: "\ud83e\udd16", desc: "Background Supervisor" },
285
- "Agent": { cat: "execute", icon: "\ud83e\udd16", desc: "General Agent" },
286
- };
287
-
288
- // ── State ─────────────────────────────────────────────
289
- const state = {
290
- agents: new Map(),
291
- sessions: new Map(),
292
- context: { pct: 0, threshold: "normal", inputTokens: 0, windowSize: 1000000, ts: null },
293
- supervisor: null,
294
- events: [],
295
- contextHistory: [],
296
- modelCounts: { opus: 0, sonnet: 0, haiku: 0 },
297
- toolCount: 0,
298
- connected: false,
299
- selectedAgentId: null,
300
- agentToolCalls: new Map(),
301
- paused: false,
302
- };
303
-
304
- // ── Role Resolution ───────────────────────────────────
305
- function resolveRoleName(agent) {
306
- const type = (agent.type || "").toLowerCase();
307
- const desc = (agent.description || agent.type || "").toLowerCase();
308
-
309
- if (agent._isSession) return "Session";
310
-
311
- // Map Claude Code agent types to GSD-T roles
312
- if (type === "explore") return "Research";
313
- if (type === "plan") return "Planning";
314
- if (type === "claude-code-guide") return "Research";
315
-
316
- // Match GSD-T command/phase names in description
317
- if (type === "general-purpose" || type === "agent" || type === "") {
318
- // Execute/coding
319
- if (/execut/i.test(desc) && /domain/i.test(desc)) return "Coding";
320
- if (/execut/i.test(desc) && /orchestrat/i.test(desc)) return "Execute";
321
- if (/execut/i.test(desc)) return "Execute";
322
- if (/\bcoding\b/i.test(desc)) return "Coding";
323
- if (/\bquick\b/i.test(desc)) return "Quick";
324
- if (/\bdebug/i.test(desc)) return "Debug";
325
-
326
- // Wave
327
- if (/\bwave\b/i.test(desc)) return "Wave";
328
-
329
- // Quality
330
- if (/\bqa\b|validation/i.test(desc)) return "QA";
331
- if (/red\s*team/i.test(desc)) return "Red Team";
332
- if (/test.?sync/i.test(desc)) return "Test Sync";
333
- if (/verif/i.test(desc)) return "Verify";
334
- if (/audit/i.test(desc)) return "Audit";
335
-
336
- // Design
337
- if (/\bprd\b|product.?req/i.test(desc)) return "PRD";
338
- if (/\bfeature\b/i.test(desc)) return "Feature";
339
- if (/milestone/i.test(desc)) return "Milestone";
340
- if (/partition/i.test(desc)) return "Partition";
341
- if (/discuss/i.test(desc)) return "Discuss";
342
- if (/design/i.test(desc)) return "Design Review";
343
-
344
- // Plan
345
- if (/\bplan\b/i.test(desc)) return "Planning";
346
- if (/impact/i.test(desc)) return "Impact";
347
- if (/brainstorm/i.test(desc)) return "Brainstorm";
348
-
349
- // Integrate
350
- if (/doc.*ripple|doc.*update/i.test(desc)) return "Doc Ripple";
351
- if (/integrat/i.test(desc)) return "Integrate";
352
- if (/complete/i.test(desc)) return "Complete";
353
-
354
- // Research
355
- if (/research|scan|gap.?analysis/i.test(desc)) return "Research";
356
-
357
- // Support
358
- if (/orchestrat/i.test(desc)) return "Orchestrator";
359
- if (/unattend|supervisor/i.test(desc)) return "Unattended";
360
- }
361
-
362
- // Infer from tool usage patterns
363
- const tc = agent.toolCounts || {};
364
- const totalTools = Object.values(tc).reduce((s, v) => s + v, 0);
365
- if (totalTools >= 3) {
366
- const writes = (tc.Edit || 0) + (tc.Write || 0);
367
- const reads = (tc.Read || 0) + (tc.Grep || 0) + (tc.Glob || 0);
368
- const bash = tc.Bash || 0;
369
- const pct = (n) => n / totalTools;
370
- if (pct(writes) > 0.3) return "Coding";
371
- if (pct(bash) > 0.5) return "QA";
372
- if (pct(reads) > 0.6 && writes === 0) return "Research";
373
- if (reads > 0 && writes > 0 && pct(reads) > 0.4) return "Audit";
374
- }
375
-
376
- if (type && type !== "general-purpose" && type !== "agent") {
377
- const words = type.split(/[\s-]+/).slice(0, 2).join(" ");
378
- return words.charAt(0).toUpperCase() + words.slice(1);
379
- }
380
-
381
- // Distribute unknown agents across ALL GSD-T roles using id hash
382
- const distributionRoles = [
383
- // Research
384
- "Research", "Scan", "Gap Analysis",
385
- // Design
386
- "PRD", "Feature", "Milestone", "Partition", "Discuss",
387
- // Plan
388
- "Planning", "Impact", "Brainstorm",
389
- // Execute
390
- "Wave", "Execute", "Coding", "Quick", "Debug",
391
- // Quality
392
- "QA", "Red Team", "Test Sync", "Verify", "Audit",
393
- // Integrate
394
- "Integrate", "Doc Ripple", "Complete",
395
- ];
396
- let hash = 0;
397
- const idStr = agent.id || "";
398
- for (let i = 0; i < idStr.length; i++) hash = ((hash << 5) - hash + idStr.charCodeAt(i)) | 0;
399
- return distributionRoles[Math.abs(hash) % distributionRoles.length];
400
- }
401
-
402
- function getRoleDef(roleName) {
403
- return ROLE_DEFS[roleName] || ROLE_DEFS["Agent"];
404
- }
405
-
406
- function getCategoryForAgent(agent) {
407
- const roleName = resolveRoleName(agent);
408
- const def = getRoleDef(roleName);
409
- return def.cat;
410
- }
411
-
412
- // ── SSE Connection ────────────────────────────────────
413
- let es = null;
414
- let reconnectDelay = 1000;
415
-
416
- function connect() {
417
- es = new EventSource("/stream");
418
-
419
- es.addEventListener("snapshot", (e) => {
420
- const d = JSON.parse(e.data);
421
- if (d.agents) d.agents.forEach(a => state.agents.set(a.id, a));
422
- if (d.sessions) d.sessions.forEach(s => state.sessions.set(s.id, s));
423
- if (d.context) Object.assign(state.context, d.context);
424
- if (d.supervisor) state.supervisor = d.supervisor;
425
- if (d.events) {
426
- state.events = d.events.slice(-100);
427
- if (feedEvents.length === 0) {
428
- for (const ev of state.events) {
429
- let agentName = "Agent";
430
- if (ev.agentId) {
431
- const a = state.agents.get(ev.agentId);
432
- if (a) agentName = resolveRoleName(a);
433
- }
434
- if (agentName === "Agent" && ev.text) {
435
- const m = ev.text.match(/^(\S+)\s/);
436
- if (m && m[1] !== "agent") agentName = m[1].charAt(0).toUpperCase() + m[1].slice(1);
437
- }
438
- feedEvents.push({ type: ev.type || "tool", agent: agentName,
439
- text: ev.detail || "", ts: ev.ts });
440
- }
441
- renderFeed();
442
- }
443
- }
444
- if (d.contextHistory) state.contextHistory = d.contextHistory;
445
- if (d.modelCounts) Object.assign(state.modelCounts, d.modelCounts);
446
- if (d.toolCount) state.toolCount = d.toolCount;
447
- if (!state.paused) renderAll();
448
- });
449
-
450
- es.addEventListener("agent:spawn", (e) => {
451
- const a = JSON.parse(e.data);
452
- state.agents.set(a.id, { ...a, status: "running", toolCount: 0, toolCounts: {} });
453
- if (a.model) state.modelCounts[a.model] = (state.modelCounts[a.model] || 0) + 1;
454
- addFeedEvent({ type: "spawn", agent: resolveRoleName(a), text: "Agent spawned", ts: a.spawnTs });
455
- if (!state.paused) renderAll();
456
- });
457
-
458
- es.addEventListener("agent:complete", (e) => {
459
- const d = JSON.parse(e.data);
460
- const a = state.agents.get(d.id);
461
- if (a) { a.status = d.status; a.completeTs = d.completeTs; a.duration = d.duration; }
462
- const name = a ? resolveRoleName(a) : "Agent";
463
- addFeedEvent({ type: d.status === "failed" ? "fail" : "complete", agent: name,
464
- text: d.status === "failed" ? "Agent failed" : "Agent completed" + (d.duration ? " (" + fmtDur(d.duration) + ")" : ""), ts: d.completeTs });
465
- if (!state.paused) renderAll();
466
- });
467
-
468
- es.addEventListener("agent:tool", (e) => {
469
- const d = JSON.parse(e.data);
470
- state.toolCount++;
471
- const a = state.agents.get(d.agentId);
472
- if (a) {
473
- a.toolCount = (a.toolCount || 0) + 1; a.lastTool = d.tool; a.lastToolTs = d.ts;
474
- if (!a.toolCounts) a.toolCounts = {};
475
- a.toolCounts[d.tool] = (a.toolCounts[d.tool] || 0) + 1;
476
- }
477
- if (!state.agentToolCalls.has(d.agentId)) state.agentToolCalls.set(d.agentId, []);
478
- state.agentToolCalls.get(d.agentId).push({ tool: d.tool, file: d.file, ts: d.ts });
479
- const name = a ? resolveRoleName(a) : "Agent";
480
- addFeedEvent({ type: "tool", agent: name, text: d.tool + (d.file ? " " + d.file : ""),
481
- toolName: d.tool, ts: d.ts });
482
- if (!state.paused) { renderGraph(); renderHeader(); }
483
- if (state.selectedAgentId === d.agentId) renderDetail(d.agentId);
484
- });
485
-
486
- es.addEventListener("session:start", (e) => {
487
- const s = JSON.parse(e.data);
488
- state.sessions.set(s.id, { ...s, isActive: true, childAgentIds: [] });
489
- if (!state.paused) renderAll();
490
- });
491
-
492
- es.addEventListener("session:end", (e) => {
493
- const d = JSON.parse(e.data);
494
- const s = state.sessions.get(d.id);
495
- if (s) { s.isActive = false; s.endTs = d.ts; }
496
- if (!state.paused) renderAll();
497
- });
498
-
499
- es.addEventListener("phase:transition", (e) => {
500
- const d = JSON.parse(e.data);
501
- addFeedEvent({ type: "phase", agent: "Orchestrator", text: "Phase: " + (d.phase || ""), ts: d.ts });
502
- if (!state.paused) renderAll();
503
- });
504
-
505
- es.addEventListener("context:update", (e) => {
506
- const d = JSON.parse(e.data);
507
- Object.assign(state.context, d);
508
- state.contextHistory.push({ ts: d.ts, pct: d.pct });
509
- if (state.contextHistory.length > 200) state.contextHistory.shift();
510
- renderHeader();
511
- });
512
-
513
- es.addEventListener("supervisor:update", (e) => {
514
- state.supervisor = JSON.parse(e.data);
515
- renderHeader();
516
- });
517
-
518
- es.onopen = () => { state.connected = true; reconnectDelay = 1000; };
519
- es.onerror = () => {
520
- state.connected = false; es.close();
521
- setTimeout(connect, reconnectDelay);
522
- reconnectDelay = Math.min(reconnectDelay * 2, 8000);
523
- };
524
- }
525
-
526
- // ── Feed Events ───────────────────────────────────────
527
- const feedEvents = [];
528
- const MAX_FEED = 100;
529
-
530
- function addFeedEvent(ev) {
531
- feedEvents.push(ev);
532
- if (feedEvents.length > MAX_FEED) feedEvents.shift();
533
- renderFeed();
534
- }
535
-
536
- function renderFeed() {
537
- const list = document.getElementById("feedList");
538
- const isAtBottom = list.scrollTop + list.clientHeight >= list.scrollHeight - 30;
539
-
540
- let html = "";
541
- const items = feedEvents.slice(-60);
542
- for (let i = items.length - 1; i >= 0; i--) {
543
- const ev = items[i];
544
- const iconClass = ev.type || "tool";
545
- const iconSymbol = ev.type === "spawn" ? "\u2192" : ev.type === "complete" ? "\u2713" :
546
- ev.type === "fail" ? "\u2717" : ev.type === "phase" ? "\u25cb" : "\u2192";
547
- const agentName = ev.agent || "Agent";
548
- const agentColor = getFeedAgentColor(agentName);
549
- const roleDef = ROLE_DEFS[agentName];
550
- const roleIcon = roleDef ? roleDef.icon + " " : "";
551
- html += '<div class="feed-item">' +
552
- '<div class="feed-icon ' + iconClass + '">' + iconSymbol + '</div>' +
553
- '<div class="feed-body">' +
554
- '<div class="feed-agent" style="color:' + agentColor + '">' + roleIcon + escHtml(agentName) +
555
- '<span class="ts">' + fmtTime(ev.ts) + '</span></div>' +
556
- '<div class="feed-text">' + escHtml(ev.text || "") + '</div>' +
557
- (ev.toolName ? '<span class="feed-tool">' + escHtml(ev.toolName) + '</span>' +
558
- (ev.dur ? '<span class="feed-dur">' + ev.dur + '</span>' : '') : '') +
559
- '</div></div>';
560
- }
561
- list.innerHTML = html;
562
- }
563
-
564
- function getFeedAgentColor(roleName) {
565
- const def = ROLE_DEFS[roleName];
566
- if (def) {
567
- const cat = CATEGORIES.find(c => c.id === def.cat);
568
- if (cat) return cat.color;
569
- }
570
- return "var(--muted)";
571
- }
572
-
573
- function getFeedIconColor(type) {
574
- switch (type) {
575
- case "spawn": return "var(--green)";
576
- case "complete": return "var(--cyan)";
577
- case "fail": return "var(--red)";
578
- case "phase": return "var(--purple)";
579
- case "tool": return "var(--yellow)";
580
- default: return "var(--text)";
581
- }
582
- }
583
-
584
- // ── Render: Header ────────────────────────────────────
585
- function renderHeader() {
586
- const allAgents = [...state.agents.values()];
587
- const activeCount = allAgents.filter(a => a.status === "running").length;
588
- document.getElementById("hdrActive").textContent = activeCount;
589
- document.getElementById("hdrTotal").textContent = allAgents.length;
590
-
591
- const tokens = state.context.inputTokens || 0;
592
- const tokenStr = tokens >= 1000 ? (tokens / 1000).toFixed(1) + "k" : String(tokens);
593
- document.getElementById("hdrTokens").textContent = tokenStr + " tokens";
594
-
595
- // Build legend from active categories
596
- const usedCats = new Set();
597
- for (const a of allAgents) usedCats.add(getCategoryForAgent(a));
598
- const legendEl = document.getElementById("legend");
599
- let legendHtml = "";
600
- for (const cat of CATEGORIES) {
601
- if (usedCats.has(cat.id) || allAgents.length === 0) {
602
- legendHtml += '<div class="legend-item"><span class="legend-dot" style="background:' + cat.color + '"></span>' + cat.label + '</div>';
603
- }
604
- }
605
- legendEl.innerHTML = legendHtml;
606
- }
607
-
608
- // ── Render: Graph ─────────────────────────────────────
609
- function renderAll() {
610
- renderGraph();
611
- renderHeader();
612
- }
613
-
614
- function renderGraph() {
615
- const container = document.getElementById("columnsContainer");
616
- const svg = document.getElementById("graphSvg");
617
- const emptyEl = document.getElementById("graphEmpty");
618
-
619
- const allAgents = [...state.agents.values()];
620
-
621
- if (allAgents.length === 0) {
622
- emptyEl.style.display = "flex";
623
- container.innerHTML = "";
624
- svg.innerHTML = "";
625
- return;
626
- }
627
- emptyEl.style.display = "none";
628
-
629
- // Group agents by category, dedup same-role agents as instances
630
- const catGroups = {};
631
- for (const cat of CATEGORIES) catGroups[cat.id] = [];
632
-
633
- const roleInstances = new Map();
634
- for (const a of allAgents) {
635
- const roleName = resolveRoleName(a);
636
- const cat = getCategoryForAgent(a);
637
- const key = cat + ":" + roleName;
638
- if (!roleInstances.has(key)) {
639
- roleInstances.set(key, { roleName, cat, agents: [], bestAgent: a });
640
- }
641
- roleInstances.get(key).agents.push(a);
642
- const existing = roleInstances.get(key).bestAgent;
643
- if (a.status === "running" && existing.status !== "running") {
644
- roleInstances.get(key).bestAgent = a;
645
- }
646
- }
647
-
648
- for (const [, group] of roleInstances) {
649
- catGroups[group.cat].push(group);
650
- }
651
-
652
- // Build columns HTML
653
- let colsHtml = "";
654
- for (const cat of CATEGORIES) {
655
- const groups = catGroups[cat.id];
656
- if (groups.length === 0) continue;
657
- colsHtml += '<div class="category-col" data-cat="' + cat.id + '">' +
658
- '<div class="category-label" style="color:' + cat.color + '">' + cat.label + '</div>' +
659
- '<div class="cards-stack">';
660
-
661
- for (const group of groups) {
662
- const a = group.bestAgent;
663
- const roleName = group.roleName;
664
- const def = getRoleDef(roleName);
665
- const isActive = group.agents.some(ag => ag.status === "running");
666
- const isFailed = !isActive && group.agents.some(ag => ag.status === "failed");
667
- const statusText = isActive ? "Active" : isFailed ? "Failed" : "Idle";
668
- const statusIcon = isActive ? "\u26a1" : isFailed ? "\u2717" :
669
- (a.lastTool && isRecent(a.lastToolTs, 30000)) ? "\ud83d\udd27" : "\ud83d\udccb";
670
- const statusColor = isActive ? "var(--cyan)" : isFailed ? "var(--red)" : "var(--dim)";
671
- const instanceCount = group.agents.length;
672
- const totalToolCalls = group.agents.reduce((s, ag) => s + (ag.toolCount || 0), 0);
673
- const inactiveClass = isActive ? "" : " inactive";
674
- const selectedClass = group.agents.some(ag => ag.id === state.selectedAgentId) ? " selected" : "";
675
- const borderColor = isActive ? cat.color : mutedColor(cat.color, 0.35);
676
- const badgeBg = isActive ? cat.color : mutedColor(cat.color, 0.5);
677
-
678
- const descText = def.desc;
679
-
680
- colsHtml += '<div class="agent-card' + inactiveClass + selectedClass +
681
- '" style="border-color:' + borderColor + '" data-agent-id="' + escHtml(a.id) +
682
- '" onclick="selectAgent(\'' + escHtml(a.id) + '\')">' +
683
- (instanceCount > 1 ? '<div class="instance-badge" style="background:' + badgeBg + '">' + instanceCount + '</div>' : '') +
684
- '<div class="card-icon">' + def.icon + '</div>' +
685
- '<div class="card-name">' + escHtml(roleName) + '</div>' +
686
- '<div class="card-desc">' + escHtml(descText) + '</div>' +
687
- '<div class="card-status-line" style="color:' + statusColor + '">' +
688
- '<span class="card-status-icon">' + statusIcon + '</span>' +
689
- '<span class="card-status-text">' + statusText + '</span>' +
690
- '</div>' +
691
- (totalToolCalls > 0 ? '<div class="card-tools">tool calls: ' + totalToolCalls + '</div>' : '') +
692
- '</div>';
693
- }
694
- colsHtml += '</div></div>';
695
- }
696
- container.innerHTML = colsHtml;
697
-
698
- // Draw connection lines between parent → child across columns
699
- requestAnimationFrame(() => drawEdges(allAgents, roleInstances));
700
- }
701
-
702
- function drawEdges(allAgents, roleInstances) {
703
- const svg = document.getElementById("graphSvg");
704
- const mainArea = document.getElementById("mainArea");
705
- const rect = mainArea.getBoundingClientRect();
706
- svg.setAttribute("width", mainArea.scrollWidth);
707
- svg.setAttribute("height", mainArea.scrollHeight);
708
- svg.setAttribute("viewBox", "0 0 " + mainArea.scrollWidth + " " + mainArea.scrollHeight);
709
-
710
- const cards = [...document.querySelectorAll(".agent-card")];
711
- if (cards.length < 2) { svg.innerHTML = ""; return; }
712
-
713
- const scrollX = mainArea.scrollLeft;
714
- const scrollY = mainArea.scrollTop;
715
-
716
- // Map each agent id → the card DOM element representing its role group
717
- const agentToCard = new Map();
718
- for (const [, group] of roleInstances) {
719
- const card = document.querySelector('[data-agent-id="' + CSS.escape(group.bestAgent.id) + '"]');
720
- if (card) {
721
- for (const a of group.agents) agentToCard.set(a.id, card);
722
- }
723
- }
724
-
725
- // Build unique card-to-card edges from actual parent→child agent relationships
726
- const drawnPairs = new Set();
727
- const edgePairs = [];
728
-
729
- for (const a of allAgents) {
730
- if (!a.parentId) continue;
731
- const childCard = agentToCard.get(a.id);
732
- const parentCard = agentToCard.get(a.parentId);
733
- if (!childCard || !parentCard || childCard === parentCard) continue;
734
-
735
- const pairKey = parentCard.dataset.agentId + "<>" + childCard.dataset.agentId;
736
- const reversePairKey = childCard.dataset.agentId + "<>" + parentCard.dataset.agentId;
737
- if (drawnPairs.has(pairKey) || drawnPairs.has(reversePairKey)) continue;
738
- drawnPairs.add(pairKey);
739
-
740
- const hasRunning = a.status === "running" ||
741
- (state.agents.get(a.parentId) || {}).status === "running";
742
- edgePairs.push({ parentCard, childCard, active: hasRunning });
743
- }
744
-
745
- // If no real parent→child edges exist, fall back to column-flow edges
746
- if (edgePairs.length === 0) {
747
- const colCards = new Map();
748
- for (const card of cards) {
749
- const col = card.closest(".category-col");
750
- if (!col) continue;
751
- const catId = col.dataset.cat;
752
- if (!colCards.has(catId)) colCards.set(catId, []);
753
- colCards.get(catId).push(card);
754
- }
755
- const catOrder = CATEGORIES.map(c => c.id).filter(id => colCards.has(id));
756
- // Vertical within columns
757
- for (const [, colGroup] of colCards) {
758
- for (let i = 0; i < colGroup.length - 1; i++) {
759
- edgePairs.push({ parentCard: colGroup[i], childCard: colGroup[i + 1], active: false });
760
- }
761
- }
762
- // Horizontal between adjacent columns
763
- for (let i = 0; i < catOrder.length - 1; i++) {
764
- const from = colCards.get(catOrder[i]);
765
- const to = colCards.get(catOrder[i + 1]);
766
- if (from && to && from.length && to.length) {
767
- edgePairs.push({ parentCard: from[0], childCard: to[0], active: false });
768
- }
769
- }
770
- }
771
-
772
- // Draw all edges
773
- let edgeHtml = "";
774
- for (const { parentCard, childCard, active } of edgePairs) {
775
- const pRect = parentCard.getBoundingClientRect();
776
- const cRect = childCard.getBoundingClientRect();
777
- const pCx = pRect.left + pRect.width / 2 - rect.left + scrollX;
778
- const pCy = pRect.top + pRect.height / 2 - rect.top + scrollY;
779
- const cCx = cRect.left + cRect.width / 2 - rect.left + scrollX;
780
- const cCy = cRect.top + cRect.height / 2 - rect.top + scrollY;
781
-
782
- // Pick exit/entry points based on relative position (nearest edge)
783
- let x1, y1, x2, y2;
784
- const dx = Math.abs(pCx - cCx);
785
- const dy = Math.abs(pCy - cCy);
786
-
787
- if (dx > dy) {
788
- // More horizontal — connect from right/left edges
789
- if (pCx < cCx) {
790
- x1 = pRect.right - rect.left + scrollX; y1 = pCy;
791
- x2 = cRect.left - rect.left + scrollX; y2 = cCy;
792
- } else {
793
- x1 = pRect.left - rect.left + scrollX; y1 = pCy;
794
- x2 = cRect.right - rect.left + scrollX; y2 = cCy;
795
- }
796
- } else {
797
- // More vertical — connect from top/bottom edges
798
- if (pCy < cCy) {
799
- x1 = pCx; y1 = pRect.bottom - rect.top + scrollY;
800
- x2 = cCx; y2 = cRect.top - rect.top + scrollY;
801
- } else {
802
- x1 = pCx; y1 = pRect.top - rect.top + scrollY;
803
- x2 = cCx; y2 = cRect.bottom - rect.top + scrollY;
804
- }
805
- }
806
-
807
- // Smooth bezier curve
808
- const mx = (x1 + x2) / 2;
809
- const my = (y1 + y2) / 2;
810
- edgeHtml += '<path class="edge-line' + (active ? " active" : "") +
811
- '" d="M' + x1 + ',' + y1 + ' C' + mx + ',' + y1 + ' ' + mx + ',' + y2 + ' ' + x2 + ',' + y2 + '"/>';
812
- // Dot at the midpoint of each connection
813
- edgeHtml += '<circle cx="' + mx + '" cy="' + my + '" r="3" fill="' +
814
- (active ? "var(--cyan)" : "var(--border)") + '" opacity="0.6"/>';
815
- }
816
-
817
- svg.innerHTML = edgeHtml;
818
- }
819
-
820
- // ── Detail Panel ──────────────────────────────────────
821
- function selectAgent(id) {
822
- if (state.selectedAgentId === id) { closeDetail(); return; }
823
- state.selectedAgentId = id;
824
- renderDetail(id);
825
- document.getElementById("detailOverlay").classList.add("open");
826
- renderGraph();
827
- }
828
-
829
- function closeDetail() {
830
- state.selectedAgentId = null;
831
- document.getElementById("detailOverlay").classList.remove("open");
832
- renderGraph();
833
- }
834
-
835
- function renderDetail(id) {
836
- const a = state.agents.get(id) || [...state.sessions.values()].find(s => s.id === id);
837
- if (!a) return;
838
-
839
- document.getElementById("detailTitle").textContent = resolveRoleName(a);
840
- const bodyEl = document.getElementById("detailBody");
841
-
842
- const elapsed = computeElapsed(a);
843
- const tools = state.agentToolCalls.get(id) || [];
844
- const totalTools = a.toolCount || tools.length || 0;
845
- const desc = a.description || a.type || "Agent";
846
-
847
- let html =
848
- '<div class="detail-section">' +
849
- '<div class="detail-desc">' + escHtml(desc) + '</div>' +
850
- '<div class="detail-desc" style="color:var(--dim)">ID: ' + escHtml(a.id) +
851
- (a.parentId ? ' &middot; Parent: ' + escHtml(a.parentId.slice(0, 12)) : '') +
852
- '</div>' +
853
- '</div>' +
854
- '<div class="detail-summary">' +
855
- '<div class="summary-item"><div class="label">Duration</div><div class="val">' + elapsed + '</div></div>' +
856
- '<div class="summary-item"><div class="label">Tool Calls</div><div class="val">' + totalTools + '</div></div>' +
857
- '<div class="summary-item"><div class="label">Model</div><div class="val">' + escHtml(a.model || "\u2014") + '</div></div>' +
858
- '<div class="summary-item"><div class="label">Status</div><div class="val">' + escHtml(a.status || "pending") + '</div></div>' +
859
- '</div>';
860
-
861
- if (tools.length > 0) {
862
- html += '<div class="detail-section">' +
863
- '<div class="detail-section-title">Tool Call Timeline (' + tools.length + ')</div>' +
864
- '<div class="tool-timeline">';
865
- const recentTools = tools.slice(-50);
866
- for (const t of recentTools) {
867
- html += '<div class="tool-entry">' +
868
- '<span class="tool-name">' + escHtml(t.tool) + '</span>' +
869
- '<span class="tool-detail">' + escHtml(t.file || "") + '</span>' +
870
- '<span class="tool-ts">' + fmtTime(t.ts) + '</span>' +
871
- '</div>';
872
- }
873
- if (tools.length > 50) {
874
- html += '<div style="color:var(--dim);font-size:9px;text-align:center;padding:4px;">+ ' + (tools.length - 50) + ' earlier calls</div>';
875
- }
876
- html += '</div></div>';
877
- }
878
-
879
- bodyEl.innerHTML = html;
880
- }
881
-
882
- // ── Controls ──────────────────────────────────────────
883
- function togglePause() {
884
- state.paused = !state.paused;
885
- const btn = document.getElementById("pauseBtn");
886
- btn.innerHTML = state.paused ? "&#x25B6; Resume" : "&#x23F8; Pause";
887
- if (!state.paused) renderAll();
888
- }
889
-
890
- function resetView() {
891
- state.agents.clear();
892
- state.sessions.clear();
893
- state.agentToolCalls.clear();
894
- state.toolCount = 0;
895
- state.modelCounts = { opus: 0, sonnet: 0, haiku: 0 };
896
- feedEvents.length = 0;
897
- renderAll();
898
- renderFeed();
899
- }
900
-
901
- // ── Utilities ─────────────────────────────────────────
902
- function mutedColor(hex, opacity) {
903
- const r = parseInt(hex.slice(1, 3), 16);
904
- const g = parseInt(hex.slice(3, 5), 16);
905
- const b = parseInt(hex.slice(5, 7), 16);
906
- return "rgba(" + r + "," + g + "," + b + "," + opacity + ")";
907
- }
908
- function escHtml(s) { return String(s || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"); }
909
- function fmtDur(s) {
910
- if (!s && s !== 0) return "\u2014";
911
- s = Math.round(s);
912
- if (s < 60) return s + "s";
913
- if (s < 3600) return Math.floor(s / 60) + "m " + (s % 60) + "s";
914
- return Math.floor(s / 3600) + "h " + Math.floor((s % 3600) / 60) + "m";
915
- }
916
- function fmtTime(ts) {
917
- if (!ts) return "";
918
- try { return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); }
919
- catch { return ""; }
920
- }
921
- function isRecent(ts, ms) {
922
- if (!ts) return false;
923
- return Date.now() - new Date(ts).getTime() < ms;
924
- }
925
- function computeElapsed(a) {
926
- if (a.duration) return fmtDur(a.duration);
927
- if (a.spawnTs) return fmtDur((Date.now() - new Date(a.spawnTs).getTime()) / 1000);
928
- return "\u2014";
929
- }
930
-
931
- // ── Elapsed timer ─────────────────────────────────────
932
- setInterval(() => {
933
- if (state.paused) return;
934
- const running = [...state.agents.values()].some(a => a.status === "running");
935
- if (running) renderGraph();
936
- }, 5000);
937
-
938
- // ── Demo Data ─────────────────────────────────────────
939
- function loadDemoData() {
940
- const now = new Date();
941
- const ago = (m) => new Date(now - m * 60000).toISOString();
942
-
943
- state.sessions.set("sess-001", { id: "sess-001", model: "opus-4-6", source: "interactive", isActive: true, startTs: ago(15) });
944
-
945
- const agents = [
946
- // Research
947
- { id: "scan-1", parentId: "sess-001", type: "Explore", description: "scan codebase for tech debt", model: "sonnet", status: "done", spawnTs: ago(30), completeTs: ago(25), duration: 300, toolCount: 45, lastTool: "Grep" },
948
- { id: "research-1", parentId: "sess-001", type: "Explore", description: "research API patterns", model: "sonnet", status: "done", spawnTs: ago(28), completeTs: ago(26), duration: 120, toolCount: 18, lastTool: "Read" },
949
- { id: "gap-1", parentId: "sess-001", type: "general-purpose", description: "gap analysis: requirements vs code", model: "sonnet", status: "done", spawnTs: ago(26), completeTs: ago(24), duration: 120, toolCount: 22, lastTool: "Grep" },
950
- // Design
951
- { id: "prd-1", parentId: "sess-001", type: "general-purpose", description: "PRD generation", model: "opus", status: "done", spawnTs: ago(24), completeTs: ago(22), duration: 120, toolCount: 15, lastTool: "Write" },
952
- { id: "feature-1", parentId: "sess-001", type: "general-purpose", description: "feature decomposition: user auth", model: "sonnet", status: "done", spawnTs: ago(22), completeTs: ago(20), duration: 120, toolCount: 20, lastTool: "Edit" },
953
- { id: "milestone-1", parentId: "sess-001", type: "general-purpose", description: "milestone M8 definition", model: "sonnet", status: "done", spawnTs: ago(20), completeTs: ago(19), duration: 60, toolCount: 8, lastTool: "Write" },
954
- { id: "partition-1", parentId: "sess-001", type: "general-purpose", description: "partition into domains", model: "sonnet", status: "done", spawnTs: ago(19), completeTs: ago(17), duration: 120, toolCount: 12, lastTool: "Write" },
955
- // Plan
956
- { id: "plan-1", parentId: "sess-001", type: "Plan", description: "plan task lists per domain", model: "sonnet", status: "done", spawnTs: ago(17), completeTs: ago(15), duration: 120, toolCount: 10, lastTool: "Read" },
957
- { id: "impact-1", parentId: "sess-001", type: "general-purpose", description: "impact analysis: auth changes", model: "opus", status: "done", spawnTs: ago(15), completeTs: ago(14), duration: 60, toolCount: 14, lastTool: "Grep" },
958
- // Execute
959
- { id: "wave-1", parentId: "sess-001", type: "general-purpose", description: "wave orchestrator M8", model: "sonnet", status: "running", spawnTs: ago(14), toolCount: 47, lastTool: "Agent", lastToolTs: ago(0.5) },
960
- { id: "exec-1", parentId: "wave-1", type: "general-purpose", description: "execute domain: auth-service", model: "sonnet", status: "done", spawnTs: ago(13), completeTs: ago(9), duration: 240, toolCount: 34, lastTool: "Edit" },
961
- { id: "exec-2", parentId: "wave-1", type: "general-purpose", description: "execute domain: api-gateway", model: "sonnet", status: "running", spawnTs: ago(8), toolCount: 28, lastTool: "Edit", lastToolTs: ago(0.3) },
962
- { id: "exec-1b", parentId: "wave-1", type: "general-purpose", description: "execute domain: auth-service", model: "sonnet", status: "running", spawnTs: ago(5), toolCount: 18, lastTool: "Bash", lastToolTs: ago(0.2) },
963
- // Quality
964
- { id: "qa-1", parentId: "wave-1", type: "general-purpose", description: "QA validation suite", model: "haiku", status: "done", spawnTs: ago(8), completeTs: ago(6), duration: 120, toolCount: 22, lastTool: "Bash" },
965
- { id: "red-team-1", parentId: "wave-1", type: "general-purpose", description: "Red Team adversarial testing", model: "opus", status: "running", spawnTs: ago(3), toolCount: 15, lastTool: "Bash", lastToolTs: ago(0.1) },
966
- { id: "verify-1", parentId: "wave-1", type: "general-purpose", description: "verify quality gates", model: "sonnet", status: "done", spawnTs: ago(5.5), completeTs: ago(5), duration: 30, toolCount: 10, lastTool: "Bash" },
967
- { id: "test-sync-1", parentId: "wave-1", type: "general-purpose", description: "test-sync alignment check", model: "haiku", status: "done", spawnTs: ago(6.5), completeTs: ago(6), duration: 30, toolCount: 8, lastTool: "Bash" },
968
- // Integrate
969
- { id: "integrate-1", parentId: "wave-1", type: "general-purpose", description: "integrate domains together", model: "sonnet", status: "done", spawnTs: ago(5), completeTs: ago(4), duration: 60, toolCount: 16, lastTool: "Edit" },
970
- { id: "doc-ripple-1", parentId: "wave-1", type: "general-purpose", description: "doc-ripple update pass", model: "haiku", status: "done", spawnTs: ago(4), completeTs: ago(3.5), duration: 30, toolCount: 6, lastTool: "Edit" },
971
- // Ad-hoc
972
- { id: "quick-1", parentId: "sess-001", type: "general-purpose", description: "quick fix: login validation", model: "sonnet", status: "done", spawnTs: ago(2), completeTs: ago(1.5), duration: 30, toolCount: 12, lastTool: "Edit" },
973
- { id: "debug-1", parentId: "sess-001", type: "general-purpose", description: "debug: session timeout issue", model: "opus", status: "running", spawnTs: ago(1), toolCount: 8, lastTool: "Grep", lastToolTs: ago(0.1) },
974
- // Quick spawns its own QA
975
- { id: "quick-qa", parentId: "quick-1", type: "general-purpose", description: "QA validation for quick fix", model: "haiku", status: "done", spawnTs: ago(1.8), completeTs: ago(1.6), duration: 12, toolCount: 5, lastTool: "Bash" },
976
- // Debug spawns research
977
- { id: "debug-research", parentId: "debug-1", type: "Explore", description: "research session lifecycle", model: "sonnet", status: "done", spawnTs: ago(0.8), completeTs: ago(0.5), duration: 18, toolCount: 10, lastTool: "Grep" },
978
- // Orchestrator (parent of the sequential phases)
979
- { id: "orchestrator-1", parentId: "sess-001", type: "general-purpose", description: "orchestrator: milestone M8", model: "sonnet", status: "running", spawnTs: ago(30), toolCount: 60, lastTool: "Agent", lastToolTs: ago(0.5) },
980
- ];
981
- agents.forEach(a => {
982
- a.sessionId = "sess-001";
983
- state.agents.set(a.id, a);
984
- });
985
-
986
- state.agentToolCalls.set("exec-1", [
987
- { tool: "Read", file: "src/auth/middleware.ts", ts: ago(13) },
988
- { tool: "Edit", file: "src/auth/middleware.ts", ts: ago(12.5) },
989
- { tool: "Bash", file: "npm test -- --grep auth", ts: ago(12) },
990
- { tool: "Read", file: "src/auth/session.ts", ts: ago(11.5) },
991
- { tool: "Edit", file: "src/auth/session.ts", ts: ago(11) },
992
- { tool: "Grep", file: "validateToken", ts: ago(10.5) },
993
- { tool: "Edit", file: "src/auth/token.ts", ts: ago(10) },
994
- { tool: "Bash", file: "npm test", ts: ago(9.5) },
995
- ]);
996
- state.agentToolCalls.set("red-team-1", [
997
- { tool: "Read", file: ".gsd-t/contracts/api-contract.md", ts: ago(2.5) },
998
- { tool: "Bash", file: "curl -X POST /api/auth/login -d '{}'", ts: ago(2) },
999
- { tool: "Read", file: "src/auth/middleware.ts", ts: ago(1.5) },
1000
- { tool: "Bash", file: "curl /api/protected -H 'Auth: expired'", ts: ago(1) },
1001
- { tool: "Read", file: "src/api/routes.ts", ts: ago(0.5) },
1002
- ]);
1003
-
1004
- state.context = { pct: 47.8, threshold: "normal", inputTokens: 242700, windowSize: 500000, ts: ago(0) };
1005
- state.supervisor = { status: "running", milestone: "M8", iter: 12, elapsed: 8040000 };
1006
- state.modelCounts = { opus: 4, sonnet: 18, haiku: 6 };
1007
- state.toolCount = 177;
1008
-
1009
- // Populate feed with demo events
1010
- const demoFeed = [
1011
- { type: "complete", agent: "Scan", text: "Tech debt discovery complete \u2014 14 items found", ts: ago(25) },
1012
- { type: "complete", agent: "Research", text: "API pattern investigation complete", ts: ago(26) },
1013
- { type: "complete", agent: "PRD", text: "Product requirements document generated", ts: ago(22) },
1014
- { type: "phase", agent: "Wave", text: "Phase transition: Plan \u2192 Execute", ts: ago(14) },
1015
- { type: "spawn", agent: "Execute", text: "Spawned execute domain: auth-service", ts: ago(13) },
1016
- { type: "complete", agent: "Execute", text: "Domain auth-service complete (4m 0s)", ts: ago(9) },
1017
- { type: "spawn", agent: "Execute", text: "Spawned execute domain: api-gateway", ts: ago(8) },
1018
- { type: "complete", agent: "QA", text: "Unit: 47/47 pass | E2E: 12/12 pass | Contract: compliant", ts: ago(6) },
1019
- { type: "complete", agent: "Test Sync", text: "All tests aligned with code changes", ts: ago(6) },
1020
- { type: "complete", agent: "Verify", text: "Quality gates passed", ts: ago(5) },
1021
- { type: "complete", agent: "Integrate", text: "Domains wired together successfully", ts: ago(4) },
1022
- { type: "complete", agent: "Doc Ripple", text: "6 docs updated (architecture, requirements, workflows)", ts: ago(3.5) },
1023
- { type: "spawn", agent: "Red Team", text: "Adversarial testing auth + API surface", ts: ago(3) },
1024
- { type: "tool", agent: "Red Team", text: "Testing auth bypass vectors", toolName: "Bash", ts: ago(1) },
1025
- { type: "tool", agent: "Execute", text: "Editing api-gateway routes", toolName: "Edit", ts: ago(0.5) },
1026
- ];
1027
- demoFeed.forEach(addFeedEvent);
1028
-
1029
- renderAll();
1030
- }
1031
-
1032
- // ── Init ──────────────────────────────────────────────
1033
- window.addEventListener("resize", () => { if (!state.paused) renderGraph(); });
1034
-
1035
- try {
1036
- connect();
1037
- setTimeout(() => { if (state.agents.size === 0) loadDemoData(); }, 2000);
1038
- } catch {
1039
- loadDemoData();
1040
- }
1041
- </script>
1042
- </body>
1043
- </html>