@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.
- package/CHANGELOG.md +44 -0
- package/README.md +1 -0
- package/bin/gsd-t-benchmark-orchestrator.js +437 -0
- package/bin/gsd-t-capture-lint.cjs +276 -0
- package/bin/gsd-t-completion-check.cjs +106 -0
- package/bin/gsd-t-orchestrator-config.cjs +64 -0
- package/bin/gsd-t-orchestrator-queue.cjs +180 -0
- package/bin/gsd-t-orchestrator-recover.cjs +231 -0
- package/bin/gsd-t-orchestrator-worker.cjs +219 -0
- package/bin/gsd-t-orchestrator.js +534 -0
- package/bin/gsd-t-stream-feed-client.cjs +151 -0
- package/bin/gsd-t-task-brief-compactor.cjs +89 -0
- package/bin/gsd-t-task-brief-template.cjs +96 -0
- package/bin/gsd-t-task-brief.js +249 -0
- package/bin/gsd-t-token-backfill.cjs +366 -0
- package/bin/gsd-t-token-capture.cjs +306 -0
- package/bin/gsd-t-token-dashboard.cjs +318 -0
- package/bin/gsd-t-token-regenerate-log.cjs +129 -0
- package/bin/gsd-t-transcript-tee.cjs +246 -0
- package/bin/gsd-t-unattended-heartbeat.cjs +188 -0
- package/bin/gsd-t-unattended-platform.cjs +191 -27
- package/bin/gsd-t-unattended-safety.cjs +8 -1
- package/bin/gsd-t-unattended.cjs +192 -31
- package/bin/gsd-t.js +329 -2
- package/bin/supervisor-pid-fingerprint.cjs +126 -0
- package/commands/gsd-t-debug.md +63 -51
- package/commands/gsd-t-design-decompose.md +2 -7
- package/commands/gsd-t-doc-ripple.md +20 -11
- package/commands/gsd-t-execute.md +82 -50
- package/commands/gsd-t-integrate.md +43 -16
- package/commands/gsd-t-plan.md +20 -7
- package/commands/gsd-t-prd.md +19 -12
- package/commands/gsd-t-quick.md +64 -29
- package/commands/gsd-t-resume.md +51 -4
- package/commands/gsd-t-unattended.md +19 -20
- package/commands/gsd-t-verify.md +48 -32
- package/commands/gsd-t-visualize.md +19 -17
- package/commands/gsd-t-wave.md +29 -27
- package/docs/architecture.md +16 -0
- package/docs/m40-benchmark-report.md +35 -0
- package/docs/requirements.md +20 -0
- package/package.json +1 -1
- package/scripts/gsd-t-dashboard-server.js +291 -4
- package/scripts/gsd-t-dashboard.html +31 -1
- package/scripts/gsd-t-design-review-server.js +3 -1
- package/scripts/gsd-t-stream-feed-server.js +428 -0
- package/scripts/gsd-t-stream-feed.html +1168 -0
- package/scripts/gsd-t-token-aggregator.js +373 -0
- package/scripts/gsd-t-transcript.html +422 -0
- package/scripts/hooks/gsd-t-in-session-probe.js +62 -0
- package/scripts/hooks/pre-commit-capture-lint +26 -0
- package/templates/CLAUDE-global.md +69 -0
- package/scripts/gsd-t-agent-dashboard-server.js +0 -424
- 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">✧</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">⏸ Pause</button>
|
|
194
|
-
<button class="header-btn" onclick="resetView()">↻ 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…</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()">×</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 ? ' · 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 ? "▶ Resume" : "⏸ 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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); }
|
|
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>
|