agents-harness 0.3.0 → 0.3.1
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.
|
@@ -6,305 +6,614 @@
|
|
|
6
6
|
<title>agents-harness</title>
|
|
7
7
|
<style>
|
|
8
8
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #0d1117; --bg-card: #161b22; --border: #30363d; --border-hover: #58a6ff;
|
|
11
|
+
--text: #c9d1d9; --text-dim: #8b949e; --text-bright: #f0f6fc;
|
|
12
|
+
--blue: #58a6ff; --green: #3fb950; --red: #f85149; --yellow: #d29922;
|
|
13
|
+
--font: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
|
|
12
14
|
}
|
|
13
|
-
|
|
15
|
+
body { font-family: var(--font); background: var(--bg); color: var(--text); line-height: 1.5; }
|
|
16
|
+
|
|
17
|
+
/* === HEADER === */
|
|
18
|
+
.header {
|
|
14
19
|
display: flex; justify-content: space-between; align-items: center;
|
|
15
|
-
padding: 12px
|
|
20
|
+
padding: 12px 20px; border-bottom: 1px solid var(--border);
|
|
16
21
|
}
|
|
17
|
-
header
|
|
18
|
-
.header
|
|
19
|
-
.header-stats span { color: #c9d1d9; }
|
|
22
|
+
.header-left { display: flex; align-items: center; gap: 10px; }
|
|
23
|
+
.header h1 { font-size: 15px; font-weight: 600; }
|
|
20
24
|
#connection-dot {
|
|
21
|
-
width: 8px; height: 8px; border-radius: 50%;
|
|
22
|
-
background:
|
|
25
|
+
width: 8px; height: 8px; border-radius: 50%;
|
|
26
|
+
background: var(--red); display: inline-block;
|
|
27
|
+
}
|
|
28
|
+
#connection-dot.connected { background: var(--green); }
|
|
29
|
+
.header-stats { display: flex; gap: 16px; font-size: 12px; color: var(--text-dim); }
|
|
30
|
+
.header-stats span { color: var(--text); }
|
|
31
|
+
|
|
32
|
+
/* === PHASE PIPELINE === */
|
|
33
|
+
.pipeline {
|
|
34
|
+
display: flex; align-items: center; gap: 0; padding: 14px 20px;
|
|
35
|
+
border-bottom: 1px solid var(--border); overflow-x: auto;
|
|
36
|
+
}
|
|
37
|
+
.phase-step {
|
|
38
|
+
display: flex; align-items: center; gap: 0; white-space: nowrap;
|
|
39
|
+
}
|
|
40
|
+
.phase-dot {
|
|
41
|
+
width: 28px; height: 28px; border-radius: 50%; display: flex;
|
|
42
|
+
align-items: center; justify-content: center; font-size: 10px; font-weight: 700;
|
|
43
|
+
border: 2px solid var(--border); color: var(--text-dim); background: var(--bg);
|
|
44
|
+
transition: all 0.3s;
|
|
45
|
+
}
|
|
46
|
+
.phase-label {
|
|
47
|
+
font-size: 11px; color: var(--text-dim); margin-left: 6px; margin-right: 6px;
|
|
48
|
+
transition: color 0.3s;
|
|
49
|
+
}
|
|
50
|
+
.phase-connector {
|
|
51
|
+
width: 24px; height: 2px; background: var(--border); margin: 0 2px;
|
|
52
|
+
transition: background 0.3s;
|
|
53
|
+
}
|
|
54
|
+
.phase-step.done .phase-dot { border-color: var(--green); color: var(--green); background: rgba(63,185,80,0.1); }
|
|
55
|
+
.phase-step.done .phase-label { color: var(--green); }
|
|
56
|
+
.phase-step.done + .phase-step .phase-connector,
|
|
57
|
+
.phase-step.done .phase-connector { background: var(--green); }
|
|
58
|
+
.phase-step.active .phase-dot {
|
|
59
|
+
border-color: var(--blue); color: var(--blue); background: rgba(88,166,255,0.15);
|
|
60
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
23
61
|
}
|
|
24
|
-
|
|
62
|
+
.phase-step.active .phase-label { color: var(--blue); font-weight: 600; }
|
|
63
|
+
@keyframes pulse {
|
|
64
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(88,166,255,0.4); }
|
|
65
|
+
50% { box-shadow: 0 0 0 6px rgba(88,166,255,0); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.sprint-label {
|
|
69
|
+
padding: 6px 20px; font-size: 12px; color: var(--text-dim);
|
|
70
|
+
border-bottom: 1px solid var(--border);
|
|
71
|
+
}
|
|
72
|
+
.sprint-label strong { color: var(--text); }
|
|
25
73
|
|
|
74
|
+
/* === MAIN SPLIT === */
|
|
75
|
+
.main {
|
|
76
|
+
display: grid; grid-template-columns: 320px 1fr;
|
|
77
|
+
height: calc(100vh - 170px); min-height: 300px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* === LEFT: SPRINT LIST === */
|
|
81
|
+
.sprint-list {
|
|
82
|
+
border-right: 1px solid var(--border); overflow-y: auto; padding: 8px;
|
|
83
|
+
}
|
|
26
84
|
.sprint-card {
|
|
27
|
-
background:
|
|
28
|
-
padding: 12px
|
|
29
|
-
|
|
30
|
-
.sprint-card:hover { border-color: #58a6ff; }
|
|
31
|
-
.sprint-header {
|
|
32
|
-
display: flex; align-items: center; gap: 12px; font-size: 14px;
|
|
33
|
-
}
|
|
34
|
-
.sprint-header .icon { font-size: 14px; width: 20px; text-align: center; }
|
|
35
|
-
.sprint-header .name { font-weight: 600; }
|
|
36
|
-
.sprint-header .meta { margin-left: auto; color: #8b949e; font-size: 12px; }
|
|
37
|
-
.status-passed { color: #3fb950; }
|
|
38
|
-
.status-failed { color: #f85149; }
|
|
39
|
-
.status-progress { color: #d29922; }
|
|
40
|
-
|
|
41
|
-
.sprint-details {
|
|
42
|
-
display: none; margin-top: 10px; padding-top: 10px;
|
|
43
|
-
border-top: 1px solid #30363d; font-size: 12px;
|
|
44
|
-
}
|
|
45
|
-
.sprint-details.open { display: block; }
|
|
46
|
-
.criteria-list { padding-left: 8px; margin: 4px 0; }
|
|
47
|
-
.criteria-list .pass { color: #3fb950; }
|
|
48
|
-
.criteria-list .fail { color: #f85149; }
|
|
49
|
-
.critique { color: #8b949e; margin-top: 6px; font-style: italic; }
|
|
50
|
-
|
|
51
|
-
.activity-section {
|
|
52
|
-
background: #161b22; border: 1px solid #30363d; border-radius: 6px;
|
|
53
|
-
margin-top: 16px; max-height: 240px; overflow-y: auto;
|
|
54
|
-
}
|
|
55
|
-
.activity-section h2 {
|
|
56
|
-
font-size: 13px; padding: 10px 16px; border-bottom: 1px solid #30363d;
|
|
57
|
-
position: sticky; top: 0; background: #161b22; z-index: 1;
|
|
85
|
+
background: var(--bg-card); border: 1px solid var(--border); border-radius: 6px;
|
|
86
|
+
padding: 10px 12px; margin-bottom: 6px; cursor: pointer; user-select: none;
|
|
87
|
+
transition: border-color 0.2s;
|
|
58
88
|
}
|
|
59
|
-
.
|
|
60
|
-
|
|
89
|
+
.sprint-card:hover { border-color: var(--border-hover); }
|
|
90
|
+
.sprint-card.selected { border-color: var(--blue); border-left: 3px solid var(--blue); }
|
|
91
|
+
.sprint-card-header { display: flex; align-items: center; gap: 8px; font-size: 13px; }
|
|
92
|
+
.sprint-icon { font-size: 13px; width: 18px; text-align: center; }
|
|
93
|
+
.sprint-icon.passed { color: var(--green); }
|
|
94
|
+
.sprint-icon.failed { color: var(--red); }
|
|
95
|
+
.sprint-icon.in_progress { color: var(--yellow); }
|
|
96
|
+
.sprint-icon.pending { color: var(--text-dim); }
|
|
97
|
+
.sprint-name { font-weight: 600; font-size: 13px; }
|
|
98
|
+
.sprint-meta { margin-left: auto; color: var(--text-dim); font-size: 11px; }
|
|
99
|
+
|
|
100
|
+
.sprint-eval {
|
|
101
|
+
margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); font-size: 11px;
|
|
61
102
|
}
|
|
62
|
-
.
|
|
63
|
-
.
|
|
103
|
+
.crit-pass { color: var(--green); }
|
|
104
|
+
.crit-fail { color: var(--red); }
|
|
105
|
+
.critique-text { color: var(--text-dim); font-style: italic; margin-top: 4px; }
|
|
64
106
|
|
|
65
|
-
.
|
|
66
|
-
|
|
67
|
-
|
|
107
|
+
.empty-state { text-align: center; color: var(--text-dim); padding: 40px 12px; font-size: 13px; }
|
|
108
|
+
|
|
109
|
+
/* === RIGHT: FILE VIEWER === */
|
|
110
|
+
.file-viewer { display: flex; flex-direction: column; overflow: hidden; }
|
|
111
|
+
.file-tabs {
|
|
112
|
+
display: flex; border-bottom: 1px solid var(--border); background: var(--bg-card);
|
|
113
|
+
overflow-x: auto; flex-shrink: 0;
|
|
68
114
|
}
|
|
69
|
-
.
|
|
70
|
-
font-size: 12px; color:
|
|
71
|
-
|
|
115
|
+
.file-tab {
|
|
116
|
+
padding: 8px 14px; font-size: 12px; color: var(--text-dim); cursor: pointer;
|
|
117
|
+
border-bottom: 2px solid transparent; white-space: nowrap; position: relative;
|
|
118
|
+
transition: color 0.2s, border-color 0.2s; user-select: none;
|
|
119
|
+
}
|
|
120
|
+
.file-tab:hover { color: var(--text); }
|
|
121
|
+
.file-tab.active { color: var(--blue); border-bottom-color: var(--blue); }
|
|
122
|
+
.file-tab .badge {
|
|
123
|
+
width: 6px; height: 6px; border-radius: 50%; background: var(--blue);
|
|
124
|
+
position: absolute; top: 6px; right: 6px; display: none;
|
|
125
|
+
}
|
|
126
|
+
.file-tab .badge.show { display: block; }
|
|
127
|
+
.file-content {
|
|
128
|
+
flex: 1; overflow-y: auto; padding: 16px 20px; font-size: 12px;
|
|
129
|
+
white-space: pre-wrap; word-wrap: break-word; line-height: 1.6;
|
|
130
|
+
color: var(--text);
|
|
131
|
+
}
|
|
132
|
+
.file-content .empty-file { color: var(--text-dim); font-style: italic; }
|
|
133
|
+
|
|
134
|
+
/* === BOTTOM: ACTIVITY + BUDGET === */
|
|
135
|
+
.bottom-bar {
|
|
136
|
+
border-top: 1px solid var(--border);
|
|
137
|
+
}
|
|
138
|
+
.activity-toggle {
|
|
139
|
+
padding: 8px 20px; font-size: 12px; color: var(--text-dim); cursor: pointer;
|
|
140
|
+
display: flex; align-items: center; gap: 6px; user-select: none;
|
|
141
|
+
border-bottom: 1px solid var(--border);
|
|
72
142
|
}
|
|
73
|
-
.
|
|
74
|
-
|
|
143
|
+
.activity-toggle:hover { color: var(--text); }
|
|
144
|
+
.activity-toggle .arrow { transition: transform 0.2s; display: inline-block; }
|
|
145
|
+
.activity-toggle .arrow.open { transform: rotate(90deg); }
|
|
146
|
+
.activity-stream {
|
|
147
|
+
max-height: 0; overflow: hidden; transition: max-height 0.3s ease;
|
|
148
|
+
background: var(--bg-card);
|
|
75
149
|
}
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
150
|
+
.activity-stream.open { max-height: 200px; overflow-y: auto; }
|
|
151
|
+
.activity-entry {
|
|
152
|
+
padding: 3px 20px; font-size: 11px; border-bottom: 1px solid #21262d;
|
|
79
153
|
}
|
|
154
|
+
.activity-entry .time { color: var(--text-dim); margin-right: 6px; }
|
|
155
|
+
.activity-entry .role { color: var(--blue); margin-right: 4px; }
|
|
80
156
|
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
157
|
+
.budget-bar { padding: 8px 20px; }
|
|
158
|
+
.budget-label {
|
|
159
|
+
font-size: 11px; color: var(--text-dim); margin-bottom: 4px;
|
|
160
|
+
display: flex; justify-content: space-between;
|
|
84
161
|
}
|
|
85
|
-
.
|
|
86
|
-
.
|
|
87
|
-
.run-complete-banner.stopped { border-color: #d29922; color: #d29922; }
|
|
162
|
+
.bar-track { height: 6px; background: #21262d; border-radius: 3px; overflow: hidden; }
|
|
163
|
+
.bar-fill { height: 100%; background: var(--blue); border-radius: 3px; transition: width 0.3s ease; }
|
|
88
164
|
|
|
89
|
-
.
|
|
90
|
-
text-align: center;
|
|
165
|
+
.run-banner {
|
|
166
|
+
text-align: center; padding: 12px; margin: 8px 20px; font-size: 13px;
|
|
167
|
+
border: 1px solid var(--border); border-radius: 6px; display: none;
|
|
91
168
|
}
|
|
169
|
+
.run-banner.show { display: block; }
|
|
170
|
+
.run-banner.completed { border-color: var(--green); color: var(--green); }
|
|
171
|
+
.run-banner.failed { border-color: var(--red); color: var(--red); }
|
|
172
|
+
.run-banner.stopped { border-color: var(--yellow); color: var(--yellow); }
|
|
92
173
|
</style>
|
|
93
174
|
</head>
|
|
94
175
|
<body>
|
|
95
|
-
|
|
96
|
-
|
|
176
|
+
|
|
177
|
+
<!-- Header -->
|
|
178
|
+
<div class="header">
|
|
179
|
+
<div class="header-left">
|
|
180
|
+
<span id="connection-dot"></span>
|
|
181
|
+
<h1>agents-harness</h1>
|
|
182
|
+
</div>
|
|
97
183
|
<div class="header-stats">
|
|
98
184
|
<div>Cost: <span id="total-cost">$0.00</span></div>
|
|
99
|
-
<div>Duration: <span id="duration">
|
|
185
|
+
<div>Duration: <span id="duration">0s</span></div>
|
|
100
186
|
</div>
|
|
101
|
-
</header>
|
|
102
|
-
|
|
103
|
-
<div id="sprints-container">
|
|
104
|
-
<div class="empty-state" id="empty-state">Waiting for events...</div>
|
|
105
187
|
</div>
|
|
106
188
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
189
|
+
<!-- Phase Pipeline -->
|
|
190
|
+
<div class="pipeline" id="pipeline"></div>
|
|
191
|
+
|
|
192
|
+
<!-- Sprint Label -->
|
|
193
|
+
<div class="sprint-label" id="sprint-label">Waiting for run to start...</div>
|
|
111
194
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
195
|
+
<!-- Main Split -->
|
|
196
|
+
<div class="main">
|
|
197
|
+
<!-- Left: Sprint List -->
|
|
198
|
+
<div class="sprint-list" id="sprint-list">
|
|
199
|
+
<div class="empty-state" id="sprint-empty">No sprints yet</div>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<!-- Right: File Viewer -->
|
|
203
|
+
<div class="file-viewer">
|
|
204
|
+
<div class="file-tabs" id="file-tabs"></div>
|
|
205
|
+
<div class="file-content" id="file-content">
|
|
206
|
+
<span class="empty-file">Select a file tab to view contents</span>
|
|
207
|
+
</div>
|
|
116
208
|
</div>
|
|
117
|
-
<div class="bar-track"><div class="bar-fill" id="budget-fill" style="width:0%"></div></div>
|
|
118
209
|
</div>
|
|
119
210
|
|
|
120
|
-
|
|
211
|
+
<!-- Bottom -->
|
|
212
|
+
<div class="bottom-bar">
|
|
213
|
+
<div class="activity-toggle" id="activity-toggle">
|
|
214
|
+
<span class="arrow" id="activity-arrow">▶</span> Activity Stream
|
|
215
|
+
<span id="activity-count" style="margin-left:auto;font-size:11px"></span>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="activity-stream" id="activity-stream"></div>
|
|
218
|
+
<div class="budget-bar">
|
|
219
|
+
<div class="budget-label">
|
|
220
|
+
<span>Budget</span>
|
|
221
|
+
<span id="budget-text">$0.00 / $0.00</span>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="bar-track"><div class="bar-fill" id="budget-fill" style="width:0%"></div></div>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="run-banner" id="run-banner"></div>
|
|
226
|
+
</div>
|
|
121
227
|
|
|
122
228
|
<script>
|
|
123
229
|
(function() {
|
|
124
|
-
|
|
230
|
+
// --- Constants ---
|
|
231
|
+
var PHASES = ['plan', 'decompose', 'contract', 'generate', 'evaluate', 'handoff'];
|
|
232
|
+
var FILE_TABS = [
|
|
233
|
+
{ key: 'spec.md', label: 'Spec' },
|
|
234
|
+
{ key: 'sprints.md', label: 'Sprints' },
|
|
235
|
+
{ key: 'contract.md', label: 'Contract' },
|
|
236
|
+
{ key: 'evaluation.md', label: 'Evaluation' },
|
|
237
|
+
{ key: 'handoff.md', label: 'Handoff' },
|
|
238
|
+
];
|
|
239
|
+
var PHASE_TO_TAB = {
|
|
240
|
+
plan: 'spec.md', decompose: 'sprints.md', contract: 'contract.md',
|
|
241
|
+
generate: 'contract.md', evaluate: 'evaluation.md', handoff: 'handoff.md',
|
|
242
|
+
};
|
|
243
|
+
var MAX_ACTIVITIES = 100;
|
|
244
|
+
|
|
245
|
+
// --- State ---
|
|
246
|
+
var state = {
|
|
125
247
|
sprints: {},
|
|
248
|
+
files: {},
|
|
249
|
+
activities: [],
|
|
126
250
|
totalCost: 0,
|
|
127
251
|
budget: 0,
|
|
128
|
-
activities: [],
|
|
129
252
|
startTime: Date.now(),
|
|
130
253
|
runComplete: false,
|
|
131
|
-
|
|
254
|
+
currentPhase: null,
|
|
255
|
+
currentSprint: 0,
|
|
256
|
+
totalSprints: 0,
|
|
257
|
+
currentAttempt: 0,
|
|
258
|
+
selectedSprint: null,
|
|
259
|
+
activeTab: 'spec.md',
|
|
260
|
+
tabBadges: {},
|
|
261
|
+
activityOpen: false,
|
|
132
262
|
};
|
|
133
263
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function handleEvent(event) {
|
|
156
|
-
const { type, data } = event;
|
|
157
|
-
if (type === 'phase:start') onPhaseStart(data);
|
|
158
|
-
else if (type === 'agent:activity') onActivity(data);
|
|
159
|
-
else if (type === 'evaluation') onEvaluation(data);
|
|
160
|
-
else if (type === 'cost:update') onCostUpdate(data);
|
|
161
|
-
else if (type === 'sprint:complete') onSprintComplete(data);
|
|
162
|
-
else if (type === 'run:complete') onRunComplete(data);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function ensureSprint(num) {
|
|
166
|
-
if (!state.sprints[num]) {
|
|
167
|
-
state.sprints[num] = { status: 'in_progress', attempts: 0, cost: 0, eval: null };
|
|
264
|
+
var ws = null;
|
|
265
|
+
var durationInterval = null;
|
|
266
|
+
|
|
267
|
+
// --- Init DOM ---
|
|
268
|
+
buildPipeline();
|
|
269
|
+
buildFileTabs();
|
|
270
|
+
|
|
271
|
+
// --- Pipeline ---
|
|
272
|
+
function buildPipeline() {
|
|
273
|
+
var el = document.getElementById('pipeline');
|
|
274
|
+
var html = '';
|
|
275
|
+
for (var i = 0; i < PHASES.length; i++) {
|
|
276
|
+
var p = PHASES[i];
|
|
277
|
+
if (i > 0) html += '<div class="phase-connector" id="conn-' + i + '"></div>';
|
|
278
|
+
html += '<div class="phase-step" id="phase-' + p + '">';
|
|
279
|
+
html += '<div class="phase-dot">' + (i + 1) + '</div>';
|
|
280
|
+
html += '<span class="phase-label">' + p.charAt(0).toUpperCase() + p.slice(1) + '</span>';
|
|
281
|
+
html += '</div>';
|
|
168
282
|
}
|
|
169
|
-
|
|
283
|
+
el.innerHTML = html;
|
|
170
284
|
}
|
|
171
285
|
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
286
|
+
function updatePipeline() {
|
|
287
|
+
var activeIdx = PHASES.indexOf(state.currentPhase);
|
|
288
|
+
for (var i = 0; i < PHASES.length; i++) {
|
|
289
|
+
var el = document.getElementById('phase-' + PHASES[i]);
|
|
290
|
+
el.className = 'phase-step';
|
|
291
|
+
if (i < activeIdx) el.className += ' done';
|
|
292
|
+
else if (i === activeIdx) el.className += ' active';
|
|
176
293
|
}
|
|
177
|
-
renderSprints();
|
|
178
294
|
}
|
|
179
295
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
296
|
+
// --- File Tabs ---
|
|
297
|
+
function buildFileTabs() {
|
|
298
|
+
var container = document.getElementById('file-tabs');
|
|
299
|
+
var html = '';
|
|
300
|
+
for (var i = 0; i < FILE_TABS.length; i++) {
|
|
301
|
+
var t = FILE_TABS[i];
|
|
302
|
+
var cls = t.key === state.activeTab ? ' active' : '';
|
|
303
|
+
html += '<div class="file-tab' + cls + '" data-key="' + t.key + '" onclick="window.__selectTab(\'' + t.key + '\')">';
|
|
304
|
+
html += t.label;
|
|
305
|
+
html += '<span class="badge" id="badge-' + t.key.replace('.', '-') + '"></span>';
|
|
306
|
+
html += '</div>';
|
|
307
|
+
}
|
|
308
|
+
container.innerHTML = html;
|
|
192
309
|
}
|
|
193
310
|
|
|
194
|
-
function
|
|
195
|
-
state.
|
|
196
|
-
state.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const pct = d.budgetUsd > 0 ? Math.min(100, (d.totalCostUsd / d.budgetUsd) * 100) : 0;
|
|
201
|
-
document.getElementById('budget-fill').style.width = `${pct}%`;
|
|
202
|
-
}
|
|
311
|
+
window.__selectTab = function(key) {
|
|
312
|
+
state.activeTab = key;
|
|
313
|
+
state.tabBadges[key] = false;
|
|
314
|
+
renderFileTabs();
|
|
315
|
+
renderFileContent();
|
|
316
|
+
};
|
|
203
317
|
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
318
|
+
function renderFileTabs() {
|
|
319
|
+
var tabs = document.getElementById('file-tabs').children;
|
|
320
|
+
for (var i = 0; i < tabs.length; i++) {
|
|
321
|
+
var tab = tabs[i];
|
|
322
|
+
var key = tab.getAttribute('data-key');
|
|
323
|
+
tab.className = 'file-tab' + (key === state.activeTab ? ' active' : '');
|
|
324
|
+
var badge = tab.querySelector('.badge');
|
|
325
|
+
if (badge) badge.className = 'badge' + (state.tabBadges[key] ? ' show' : '');
|
|
326
|
+
}
|
|
210
327
|
}
|
|
211
328
|
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
banner.innerHTML = `<div class="run-complete-banner ${status}">Run ${status.toUpperCase()} — ${d.totalSprints} sprint${d.totalSprints !== 1 ? 's' : ''}, $${d.totalCostUsd.toFixed(2)}, ${formatDuration(d.durationMs)}</div>`;
|
|
329
|
+
function renderFileContent() {
|
|
330
|
+
var el = document.getElementById('file-content');
|
|
331
|
+
var content = state.files[state.activeTab];
|
|
332
|
+
if (content) {
|
|
333
|
+
el.textContent = content;
|
|
334
|
+
} else {
|
|
335
|
+
el.innerHTML = '<span class="empty-file">No content yet — file will appear when the agent writes to it</span>';
|
|
336
|
+
}
|
|
221
337
|
}
|
|
222
338
|
|
|
223
|
-
// ---
|
|
339
|
+
// --- Sprint List ---
|
|
224
340
|
function renderSprints() {
|
|
225
|
-
|
|
226
|
-
|
|
341
|
+
var container = document.getElementById('sprint-list');
|
|
342
|
+
var keys = Object.keys(state.sprints).map(Number).sort(function(a,b){return a-b;});
|
|
227
343
|
if (keys.length === 0) return;
|
|
228
344
|
|
|
229
|
-
document.getElementById('empty
|
|
345
|
+
var emptyEl = document.getElementById('sprint-empty');
|
|
346
|
+
if (emptyEl) emptyEl.remove();
|
|
230
347
|
|
|
231
|
-
|
|
232
|
-
for (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
348
|
+
var html = '';
|
|
349
|
+
for (var i = 0; i < keys.length; i++) {
|
|
350
|
+
var num = keys[i];
|
|
351
|
+
var s = state.sprints[num];
|
|
352
|
+
var sel = state.selectedSprint === num ? ' selected' : '';
|
|
353
|
+
var iconCls = s.status || 'pending';
|
|
354
|
+
var iconChar = s.status === 'passed' ? '✓' : s.status === 'failed' ? '✗' : s.status === 'in_progress' ? '●' : '○';
|
|
237
355
|
|
|
238
|
-
html +=
|
|
239
|
-
html +=
|
|
240
|
-
html +=
|
|
241
|
-
html +=
|
|
242
|
-
html +=
|
|
243
|
-
html +=
|
|
356
|
+
html += '<div class="sprint-card' + sel + '" onclick="window.__selectSprint(' + num + ')">';
|
|
357
|
+
html += '<div class="sprint-card-header">';
|
|
358
|
+
html += '<span class="sprint-icon ' + iconCls + '">' + iconChar + '</span>';
|
|
359
|
+
html += '<span class="sprint-name">Sprint ' + num + '</span>';
|
|
360
|
+
html += '<span class="sprint-meta">' + (s.attempts || 0) + ' att · $' + (s.cost || 0).toFixed(2) + '</span>';
|
|
361
|
+
html += '</div>';
|
|
244
362
|
|
|
245
363
|
if (s.eval) {
|
|
246
|
-
html +=
|
|
247
|
-
if (s.eval.passedCriteria.length > 0) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
364
|
+
html += '<div class="sprint-eval">';
|
|
365
|
+
if (s.eval.passedCriteria && s.eval.passedCriteria.length > 0) {
|
|
366
|
+
for (var j = 0; j < s.eval.passedCriteria.length; j++) {
|
|
367
|
+
html += '<div class="crit-pass">+ ' + esc(s.eval.passedCriteria[j]) + '</div>';
|
|
368
|
+
}
|
|
251
369
|
}
|
|
252
|
-
if (s.eval.failedCriteria.length > 0) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
370
|
+
if (s.eval.failedCriteria && s.eval.failedCriteria.length > 0) {
|
|
371
|
+
for (var j = 0; j < s.eval.failedCriteria.length; j++) {
|
|
372
|
+
html += '<div class="crit-fail">- ' + esc(s.eval.failedCriteria[j]) + '</div>';
|
|
373
|
+
}
|
|
256
374
|
}
|
|
257
375
|
if (s.eval.critique) {
|
|
258
|
-
html +=
|
|
376
|
+
html += '<div class="critique-text">' + esc(s.eval.critique.slice(0, 200)) + '</div>';
|
|
259
377
|
}
|
|
260
|
-
html +=
|
|
378
|
+
html += '</div>';
|
|
261
379
|
}
|
|
262
|
-
html +=
|
|
380
|
+
html += '</div>';
|
|
263
381
|
}
|
|
264
382
|
container.innerHTML = html;
|
|
265
383
|
}
|
|
266
384
|
|
|
267
|
-
window.
|
|
268
|
-
state.
|
|
385
|
+
window.__selectSprint = function(num) {
|
|
386
|
+
state.selectedSprint = num;
|
|
269
387
|
renderSprints();
|
|
270
388
|
};
|
|
271
389
|
|
|
390
|
+
// --- Sprint Label ---
|
|
391
|
+
function updateSprintLabel() {
|
|
392
|
+
var el = document.getElementById('sprint-label');
|
|
393
|
+
if (state.currentSprint > 0) {
|
|
394
|
+
var parts = ['<strong>Sprint ' + state.currentSprint + '</strong>'];
|
|
395
|
+
if (state.totalSprints > 0) parts[0] += ' of ' + state.totalSprints;
|
|
396
|
+
if (state.currentAttempt > 0) parts.push('Attempt ' + state.currentAttempt);
|
|
397
|
+
if (state.currentPhase) parts.push(state.currentPhase.charAt(0).toUpperCase() + state.currentPhase.slice(1) + ' phase');
|
|
398
|
+
el.innerHTML = parts.join(' — ');
|
|
399
|
+
} else if (state.currentPhase) {
|
|
400
|
+
el.innerHTML = '<strong>' + state.currentPhase.charAt(0).toUpperCase() + state.currentPhase.slice(1) + '</strong> phase';
|
|
401
|
+
} else if (state.runComplete) {
|
|
402
|
+
el.innerHTML = 'Run complete';
|
|
403
|
+
} else {
|
|
404
|
+
el.innerHTML = 'Waiting for run to start...';
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// --- Activity ---
|
|
272
409
|
function renderActivity() {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
for (
|
|
276
|
-
|
|
277
|
-
|
|
410
|
+
var el = document.getElementById('activity-stream');
|
|
411
|
+
var html = '';
|
|
412
|
+
for (var i = 0; i < state.activities.length; i++) {
|
|
413
|
+
var a = state.activities[i];
|
|
414
|
+
var t = new Date(a.timestamp).toLocaleTimeString();
|
|
415
|
+
html += '<div class="activity-entry">';
|
|
416
|
+
html += '<span class="time">' + t + '</span>';
|
|
417
|
+
html += '<span class="role">[' + esc(a.role) + ']</span> ';
|
|
418
|
+
html += esc(a.summary);
|
|
419
|
+
html += '</div>';
|
|
420
|
+
}
|
|
421
|
+
el.innerHTML = html;
|
|
422
|
+
el.scrollTop = el.scrollHeight;
|
|
423
|
+
document.getElementById('activity-count').textContent = state.activities.length > 0 ? '(' + state.activities.length + ')' : '';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
document.getElementById('activity-toggle').addEventListener('click', function() {
|
|
427
|
+
state.activityOpen = !state.activityOpen;
|
|
428
|
+
document.getElementById('activity-stream').className = 'activity-stream' + (state.activityOpen ? ' open' : '');
|
|
429
|
+
document.getElementById('activity-arrow').className = 'arrow' + (state.activityOpen ? ' open' : '');
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// --- Event Handlers ---
|
|
433
|
+
function ensureSprint(num) {
|
|
434
|
+
if (!state.sprints[num]) {
|
|
435
|
+
state.sprints[num] = { status: 'in_progress', attempts: 0, cost: 0, eval: null };
|
|
436
|
+
}
|
|
437
|
+
return state.sprints[num];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function handleEvent(event) {
|
|
441
|
+
var type = event.type;
|
|
442
|
+
var data = event.data;
|
|
443
|
+
|
|
444
|
+
if (type === 'phase:start') {
|
|
445
|
+
state.currentPhase = data.phase;
|
|
446
|
+
state.currentSprint = data.sprint;
|
|
447
|
+
state.currentAttempt = data.attempt;
|
|
448
|
+
if (data.sprint > 0) {
|
|
449
|
+
var s = ensureSprint(data.sprint);
|
|
450
|
+
s.attempts = Math.max(s.attempts, data.attempt || 1);
|
|
451
|
+
if (!state.selectedSprint) state.selectedSprint = data.sprint;
|
|
452
|
+
}
|
|
453
|
+
updatePipeline();
|
|
454
|
+
updateSprintLabel();
|
|
455
|
+
renderSprints();
|
|
456
|
+
// Auto-switch tab
|
|
457
|
+
var tabKey = PHASE_TO_TAB[data.phase];
|
|
458
|
+
if (tabKey) {
|
|
459
|
+
state.activeTab = tabKey;
|
|
460
|
+
state.tabBadges[tabKey] = false;
|
|
461
|
+
renderFileTabs();
|
|
462
|
+
renderFileContent();
|
|
463
|
+
}
|
|
278
464
|
}
|
|
279
|
-
|
|
280
|
-
|
|
465
|
+
else if (type === 'agent:activity') {
|
|
466
|
+
state.activities.push(data);
|
|
467
|
+
if (state.activities.length > MAX_ACTIVITIES) state.activities.shift();
|
|
468
|
+
if (data.sprint > 0) ensureSprint(data.sprint);
|
|
469
|
+
renderActivity();
|
|
470
|
+
}
|
|
471
|
+
else if (type === 'evaluation') {
|
|
472
|
+
var s = ensureSprint(data.sprint);
|
|
473
|
+
s.attempts = Math.max(s.attempts, data.attempt);
|
|
474
|
+
s.eval = data.result;
|
|
475
|
+
renderSprints();
|
|
476
|
+
}
|
|
477
|
+
else if (type === 'cost:update') {
|
|
478
|
+
state.totalCost = data.totalCostUsd;
|
|
479
|
+
state.budget = data.budgetUsd;
|
|
480
|
+
document.getElementById('total-cost').textContent = '$' + data.totalCostUsd.toFixed(2);
|
|
481
|
+
document.getElementById('budget-text').textContent =
|
|
482
|
+
'$' + data.totalCostUsd.toFixed(2) + ' / $' + data.budgetUsd.toFixed(2);
|
|
483
|
+
var pct = data.budgetUsd > 0 ? Math.min(100, (data.totalCostUsd / data.budgetUsd) * 100) : 0;
|
|
484
|
+
document.getElementById('budget-fill').style.width = pct + '%';
|
|
485
|
+
}
|
|
486
|
+
else if (type === 'sprint:complete') {
|
|
487
|
+
var s = ensureSprint(data.sprint);
|
|
488
|
+
s.status = data.status;
|
|
489
|
+
s.attempts = data.attempts;
|
|
490
|
+
s.cost = data.costUsd;
|
|
491
|
+
renderSprints();
|
|
492
|
+
}
|
|
493
|
+
else if (type === 'run:complete') {
|
|
494
|
+
state.runComplete = true;
|
|
495
|
+
if (durationInterval) { clearInterval(durationInterval); durationInterval = null; }
|
|
496
|
+
document.getElementById('duration').textContent = formatDuration(data.durationMs);
|
|
497
|
+
document.getElementById('total-cost').textContent = '$' + data.totalCostUsd.toFixed(2);
|
|
498
|
+
updateSprintLabel();
|
|
499
|
+
|
|
500
|
+
var banner = document.getElementById('run-banner');
|
|
501
|
+
banner.className = 'run-banner show ' + data.status;
|
|
502
|
+
banner.textContent = 'Run ' + data.status.toUpperCase() +
|
|
503
|
+
' \u2014 ' + data.totalSprints + ' sprint' + (data.totalSprints !== 1 ? 's' : '') +
|
|
504
|
+
', $' + data.totalCostUsd.toFixed(2) +
|
|
505
|
+
', ' + formatDuration(data.durationMs);
|
|
506
|
+
}
|
|
507
|
+
else if (type === 'file:update') {
|
|
508
|
+
state.files[data.name] = data.content;
|
|
509
|
+
if (data.name === state.activeTab) {
|
|
510
|
+
renderFileContent();
|
|
511
|
+
} else {
|
|
512
|
+
state.tabBadges[data.name] = true;
|
|
513
|
+
renderFileTabs();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else if (type === 'state:snapshot') {
|
|
517
|
+
// Restore files
|
|
518
|
+
if (data.files) {
|
|
519
|
+
for (var key in data.files) {
|
|
520
|
+
if (data.files[key] !== null) {
|
|
521
|
+
state.files[key] = data.files[key];
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Replay persisted events to rebuild full state (sprints, activities, cost, etc.)
|
|
526
|
+
if (data.events && data.events.length > 0) {
|
|
527
|
+
for (var i = 0; i < data.events.length; i++) {
|
|
528
|
+
handleEvent(data.events[i]);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// Restore progress (override with latest from progress file)
|
|
532
|
+
if (data.progress) {
|
|
533
|
+
var p = data.progress;
|
|
534
|
+
state.currentPhase = p.currentPhase;
|
|
535
|
+
state.currentSprint = p.currentSprint;
|
|
536
|
+
state.totalSprints = p.totalSprints;
|
|
537
|
+
state.currentAttempt = p.currentAttempt;
|
|
538
|
+
state.totalCost = p.costUsd || 0;
|
|
539
|
+
state.budget = p.maxBudgetUsd || 0;
|
|
540
|
+
if (p.sprints) {
|
|
541
|
+
for (var sn in p.sprints) {
|
|
542
|
+
var existing = state.sprints[sn];
|
|
543
|
+
state.sprints[sn] = {
|
|
544
|
+
status: p.sprints[sn].status,
|
|
545
|
+
attempts: p.sprints[sn].attempts,
|
|
546
|
+
cost: p.sprints[sn].costUsd || 0,
|
|
547
|
+
eval: existing ? existing.eval : null,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (p.status === 'completed' || p.status === 'failed' || p.status === 'stopped') {
|
|
552
|
+
state.runComplete = true;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Re-render everything
|
|
556
|
+
updatePipeline();
|
|
557
|
+
updateSprintLabel();
|
|
558
|
+
renderSprints();
|
|
559
|
+
renderFileTabs();
|
|
560
|
+
renderFileContent();
|
|
561
|
+
renderActivity();
|
|
562
|
+
if (state.totalCost > 0) {
|
|
563
|
+
document.getElementById('total-cost').textContent = '$' + state.totalCost.toFixed(2);
|
|
564
|
+
}
|
|
565
|
+
if (state.budget > 0) {
|
|
566
|
+
document.getElementById('budget-text').textContent =
|
|
567
|
+
'$' + state.totalCost.toFixed(2) + ' / $' + state.budget.toFixed(2);
|
|
568
|
+
var pct = Math.min(100, (state.totalCost / state.budget) * 100);
|
|
569
|
+
document.getElementById('budget-fill').style.width = pct + '%';
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// --- WebSocket ---
|
|
575
|
+
function connect() {
|
|
576
|
+
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
577
|
+
ws = new WebSocket(proto + '//' + location.host);
|
|
578
|
+
ws.onopen = function() {
|
|
579
|
+
document.getElementById('connection-dot').classList.add('connected');
|
|
580
|
+
};
|
|
581
|
+
ws.onclose = function() {
|
|
582
|
+
document.getElementById('connection-dot').classList.remove('connected');
|
|
583
|
+
setTimeout(connect, 3000);
|
|
584
|
+
};
|
|
585
|
+
ws.onerror = function() { ws.close(); };
|
|
586
|
+
ws.onmessage = function(e) {
|
|
587
|
+
try { handleEvent(JSON.parse(e.data)); } catch(err) { console.error('WS parse error', err); }
|
|
588
|
+
};
|
|
281
589
|
}
|
|
282
590
|
|
|
283
591
|
// --- Helpers ---
|
|
284
592
|
function esc(s) {
|
|
285
|
-
|
|
593
|
+
if (!s) return '';
|
|
594
|
+
var d = document.createElement('div');
|
|
286
595
|
d.textContent = s;
|
|
287
596
|
return d.innerHTML;
|
|
288
597
|
}
|
|
289
598
|
|
|
290
599
|
function formatDuration(ms) {
|
|
291
|
-
|
|
292
|
-
if (s < 60) return
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (m < 60) return
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return
|
|
600
|
+
var s = Math.floor(ms / 1000);
|
|
601
|
+
if (s < 60) return s + 's';
|
|
602
|
+
var m = Math.floor(s / 60);
|
|
603
|
+
var rs = s % 60;
|
|
604
|
+
if (m < 60) return m + 'm ' + rs + 's';
|
|
605
|
+
var h = Math.floor(m / 60);
|
|
606
|
+
var rm = m % 60;
|
|
607
|
+
return h + 'h ' + rm + 'm';
|
|
299
608
|
}
|
|
300
609
|
|
|
301
610
|
function updateDuration() {
|
|
302
611
|
if (state.runComplete) return;
|
|
303
|
-
|
|
612
|
+
var elapsed = Date.now() - state.startTime;
|
|
304
613
|
document.getElementById('duration').textContent = formatDuration(elapsed);
|
|
305
614
|
}
|
|
306
615
|
|
|
307
|
-
// ---
|
|
616
|
+
// --- Boot ---
|
|
308
617
|
connect();
|
|
309
618
|
durationInterval = setInterval(updateDuration, 1000);
|
|
310
619
|
})();
|