milhouse 1.0.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.
- package/AGENTS.md +49 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/src/banner.js +47 -0
- package/dist/src/cli.js +113 -0
- package/dist/src/codexRun.js +24 -0
- package/dist/src/index.js +190 -0
- package/dist/src/loop-runner.js +164 -0
- package/dist/src/paths.js +19 -0
- package/dist/ui/public/index.html +691 -0
- package/dist/ui/public/milhouse.gif +0 -0
- package/dist/ui/public/millhouse.gif +0 -0
- package/dist/ui/public/millhouse.png +0 -0
- package/dist/ui/server.js +369 -0
- package/package.json +63 -0
- package/prompts/build.md +11 -0
- package/prompts/plan.md +11 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Milhouse</title>
|
|
8
|
+
<style>
|
|
9
|
+
@font-face {
|
|
10
|
+
font-family: 'Chicago';
|
|
11
|
+
src: local('Chicago'), local('Charcoal');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
:root {
|
|
15
|
+
--platinum: #DDDDDD;
|
|
16
|
+
--platinum-dark: #BBBBBB;
|
|
17
|
+
--platinum-light: #EEEEEE;
|
|
18
|
+
--window-bg: #DDDDDD;
|
|
19
|
+
--text: #000000;
|
|
20
|
+
--highlight: #3366CC;
|
|
21
|
+
--title-bar: linear-gradient(180deg, #FFFFFF 0%, #CCCCCC 10%, #DDDDDD 50%, #AAAAAA 100%);
|
|
22
|
+
--button-face: linear-gradient(180deg, #FFFFFF 0%, #DDDDDD 45%, #BBBBBB 55%, #CCCCCC 100%);
|
|
23
|
+
--inset: inset 1px 1px 0 #888888, inset -1px -1px 0 #FFFFFF;
|
|
24
|
+
--outset: inset 1px 1px 0 #FFFFFF, inset -1px -1px 0 #888888;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
* {
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
body {
|
|
32
|
+
margin: 0;
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
background: #6699CC;
|
|
35
|
+
font-family: Geneva, 'Lucida Grande', Verdana, sans-serif;
|
|
36
|
+
font-size: 12px;
|
|
37
|
+
color: var(--text);
|
|
38
|
+
padding: 20px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Running indicator */
|
|
42
|
+
.milhouse-running {
|
|
43
|
+
position: fixed;
|
|
44
|
+
bottom: 20px;
|
|
45
|
+
right: 20px;
|
|
46
|
+
display: none;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
align-items: center;
|
|
49
|
+
z-index: 0;
|
|
50
|
+
pointer-events: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.milhouse-running.visible {
|
|
54
|
+
display: flex;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.milhouse-running img {
|
|
58
|
+
width: 120px;
|
|
59
|
+
height: 120px;
|
|
60
|
+
opacity: 0.8;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.milhouse-running .caption {
|
|
64
|
+
font-size: 10px;
|
|
65
|
+
color: #FFFFFF;
|
|
66
|
+
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
|
67
|
+
margin-top: 4px;
|
|
68
|
+
font-style: italic;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.desktop {
|
|
72
|
+
position: relative;
|
|
73
|
+
z-index: 1;
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
gap: 16px;
|
|
77
|
+
max-width: 800px;
|
|
78
|
+
margin: 0 auto;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Mac OS 9 Window */
|
|
82
|
+
.window {
|
|
83
|
+
background: var(--window-bg);
|
|
84
|
+
border: 1px solid #000000;
|
|
85
|
+
box-shadow: 1px 1px 0 #000000, 2px 2px 8px rgba(0, 0, 0, 0.3);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.title-bar {
|
|
89
|
+
background: var(--title-bar);
|
|
90
|
+
border-bottom: 1px solid #000000;
|
|
91
|
+
padding: 3px 6px;
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 8px;
|
|
95
|
+
height: 22px;
|
|
96
|
+
cursor: default;
|
|
97
|
+
user-select: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.title-bar-stripes {
|
|
101
|
+
flex: 1;
|
|
102
|
+
height: 12px;
|
|
103
|
+
background: repeating-linear-gradient(180deg,
|
|
104
|
+
#FFFFFF 0px,
|
|
105
|
+
#FFFFFF 1px,
|
|
106
|
+
#AAAAAA 1px,
|
|
107
|
+
#AAAAAA 2px,
|
|
108
|
+
#CCCCCC 2px,
|
|
109
|
+
#CCCCCC 3px);
|
|
110
|
+
border: 1px solid #888888;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.close-box {
|
|
114
|
+
width: 13px;
|
|
115
|
+
height: 13px;
|
|
116
|
+
background: var(--button-face);
|
|
117
|
+
border: 1px solid #000000;
|
|
118
|
+
box-shadow: var(--outset);
|
|
119
|
+
flex-shrink: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.window-title {
|
|
123
|
+
font-weight: bold;
|
|
124
|
+
font-size: 12px;
|
|
125
|
+
padding: 0 8px;
|
|
126
|
+
white-space: nowrap;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.window-content {
|
|
130
|
+
padding: 12px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Classic Mac Buttons */
|
|
134
|
+
.btn {
|
|
135
|
+
font-family: Geneva, 'Lucida Grande', Verdana, sans-serif;
|
|
136
|
+
font-size: 12px;
|
|
137
|
+
padding: 4px 16px;
|
|
138
|
+
background: var(--button-face);
|
|
139
|
+
border: 1px solid #000000;
|
|
140
|
+
box-shadow: var(--outset);
|
|
141
|
+
cursor: pointer;
|
|
142
|
+
min-height: 22px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.btn:active {
|
|
146
|
+
box-shadow: var(--inset);
|
|
147
|
+
background: var(--platinum-dark);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.btn-default {
|
|
151
|
+
border: 2px solid #000000;
|
|
152
|
+
border-radius: 6px;
|
|
153
|
+
padding: 4px 20px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Text Fields */
|
|
157
|
+
.text-field {
|
|
158
|
+
font-family: Geneva, 'Lucida Grande', Verdana, sans-serif;
|
|
159
|
+
font-size: 12px;
|
|
160
|
+
padding: 3px 4px;
|
|
161
|
+
background: #FFFFFF;
|
|
162
|
+
border: 1px solid #000000;
|
|
163
|
+
box-shadow: var(--inset);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.text-field:focus {
|
|
167
|
+
outline: none;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
textarea.text-field {
|
|
171
|
+
resize: vertical;
|
|
172
|
+
min-height: 60px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* Labels */
|
|
176
|
+
label {
|
|
177
|
+
display: block;
|
|
178
|
+
margin-bottom: 4px;
|
|
179
|
+
font-weight: bold;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Form Groups */
|
|
183
|
+
.form-group {
|
|
184
|
+
margin-bottom: 12px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Divider */
|
|
188
|
+
.divider {
|
|
189
|
+
border: none;
|
|
190
|
+
border-top: 1px solid #888888;
|
|
191
|
+
border-bottom: 1px solid #FFFFFF;
|
|
192
|
+
margin: 12px 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Status Area */
|
|
196
|
+
.status-bar {
|
|
197
|
+
background: var(--platinum-light);
|
|
198
|
+
border: 1px solid #888888;
|
|
199
|
+
box-shadow: var(--inset);
|
|
200
|
+
padding: 4px 8px;
|
|
201
|
+
font-size: 11px;
|
|
202
|
+
display: flex;
|
|
203
|
+
gap: 12px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.status-item {
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
gap: 4px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.status-led {
|
|
213
|
+
width: 8px;
|
|
214
|
+
height: 8px;
|
|
215
|
+
border-radius: 50%;
|
|
216
|
+
background: #00AA00;
|
|
217
|
+
border: 1px solid #006600;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.status-led.idle {
|
|
221
|
+
background: #888888;
|
|
222
|
+
border-color: #555555;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.status-led.running {
|
|
226
|
+
background: #00AA00;
|
|
227
|
+
border-color: #006600;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.status-led.error {
|
|
231
|
+
background: #CC0000;
|
|
232
|
+
border-color: #880000;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* Log Area */
|
|
236
|
+
.log-area {
|
|
237
|
+
background: #FFFFFF;
|
|
238
|
+
border: 1px solid #000000;
|
|
239
|
+
box-shadow: var(--inset);
|
|
240
|
+
font-family: Monaco, 'Courier New', monospace;
|
|
241
|
+
font-size: 10px;
|
|
242
|
+
padding: 6px;
|
|
243
|
+
height: 200px;
|
|
244
|
+
overflow-y: scroll;
|
|
245
|
+
white-space: pre-wrap;
|
|
246
|
+
word-break: break-all;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Button Row */
|
|
250
|
+
.button-row {
|
|
251
|
+
display: flex;
|
|
252
|
+
gap: 8px;
|
|
253
|
+
margin-top: 12px;
|
|
254
|
+
justify-content: flex-end;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.button-row-left {
|
|
258
|
+
justify-content: flex-start;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* Grid Layout */
|
|
262
|
+
.grid-2 {
|
|
263
|
+
display: grid;
|
|
264
|
+
grid-template-columns: 1fr 1fr;
|
|
265
|
+
gap: 12px;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* Sessions List */
|
|
269
|
+
.list-box {
|
|
270
|
+
background: #FFFFFF;
|
|
271
|
+
border: 1px solid #000000;
|
|
272
|
+
box-shadow: var(--inset);
|
|
273
|
+
max-height: 150px;
|
|
274
|
+
overflow-y: auto;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.list-item {
|
|
278
|
+
padding: 2px 6px;
|
|
279
|
+
border-bottom: 1px solid #DDDDDD;
|
|
280
|
+
font-size: 11px;
|
|
281
|
+
cursor: default;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.list-item:last-child {
|
|
285
|
+
border-bottom: none;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.list-item:hover {
|
|
289
|
+
background: var(--highlight);
|
|
290
|
+
color: #FFFFFF;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.list-item .meta {
|
|
294
|
+
color: #666666;
|
|
295
|
+
font-size: 10px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.list-item:hover .meta {
|
|
299
|
+
color: #CCCCCC;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/* Section Header */
|
|
303
|
+
.section-header {
|
|
304
|
+
display: flex;
|
|
305
|
+
align-items: center;
|
|
306
|
+
gap: 8px;
|
|
307
|
+
margin-bottom: 8px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.section-header h3 {
|
|
311
|
+
margin: 0;
|
|
312
|
+
font-size: 12px;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* Checkbox */
|
|
316
|
+
.checkbox-row {
|
|
317
|
+
display: flex;
|
|
318
|
+
align-items: center;
|
|
319
|
+
gap: 4px;
|
|
320
|
+
font-size: 11px;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@media (max-width: 600px) {
|
|
324
|
+
.grid-2 {
|
|
325
|
+
grid-template-columns: 1fr;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.milhouse-running img {
|
|
329
|
+
width: 80px;
|
|
330
|
+
height: 80px;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
</style>
|
|
334
|
+
</head>
|
|
335
|
+
|
|
336
|
+
<body>
|
|
337
|
+
<div id="milhouse-running" class="milhouse-running">
|
|
338
|
+
<img src="/milhouse.gif" alt="Milhouse">
|
|
339
|
+
<span class="caption">everything is coming up milhouse</span>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div class="desktop">
|
|
343
|
+
<!-- Main Window -->
|
|
344
|
+
<div class="window">
|
|
345
|
+
<div class="title-bar">
|
|
346
|
+
<div class="close-box"></div>
|
|
347
|
+
<div class="title-bar-stripes"></div>
|
|
348
|
+
<span class="window-title">Milhouse</span>
|
|
349
|
+
<div class="title-bar-stripes"></div>
|
|
350
|
+
</div>
|
|
351
|
+
<div class="window-content">
|
|
352
|
+
<div class="status-bar">
|
|
353
|
+
<div class="status-item">
|
|
354
|
+
<div id="status-led" class="status-led idle"></div>
|
|
355
|
+
<span id="status">Idle</span>
|
|
356
|
+
</div>
|
|
357
|
+
<div class="status-item">
|
|
358
|
+
<span id="thread"></span>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
|
|
362
|
+
<hr class="divider">
|
|
363
|
+
|
|
364
|
+
<div class="form-group">
|
|
365
|
+
<label for="goal">Goal:</label>
|
|
366
|
+
<textarea id="goal" class="text-field" style="width:100%;" placeholder="Enter your goal..."></textarea>
|
|
367
|
+
</div>
|
|
368
|
+
|
|
369
|
+
<div class="grid-2">
|
|
370
|
+
<div class="form-group">
|
|
371
|
+
<label for="workdir">Project Path:</label>
|
|
372
|
+
<input id="workdir" type="text" class="text-field" style="width:100%;" placeholder="C:\path\to\project">
|
|
373
|
+
<div class="button-row button-row-left">
|
|
374
|
+
<button id="open-project" class="btn">Browse...</button>
|
|
375
|
+
<button id="new-project" class="btn">New...</button>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
<div class="form-group">
|
|
379
|
+
<label for="max">Max Iterations:</label>
|
|
380
|
+
<input id="max" type="number" class="text-field" value="5" min="0" style="width:80px;">
|
|
381
|
+
<div style="font-size:10px;color:#666;margin-top:4px;">0 = unlimited</div>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<hr class="divider">
|
|
386
|
+
|
|
387
|
+
<div class="button-row">
|
|
388
|
+
<button id="stop" class="btn">Stop</button>
|
|
389
|
+
<button id="start" class="btn btn-default">Start</button>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<!-- Logs Window -->
|
|
395
|
+
<div class="window">
|
|
396
|
+
<div class="title-bar">
|
|
397
|
+
<div class="close-box"></div>
|
|
398
|
+
<div class="title-bar-stripes"></div>
|
|
399
|
+
<span class="window-title">Live Logs</span>
|
|
400
|
+
<div class="title-bar-stripes"></div>
|
|
401
|
+
</div>
|
|
402
|
+
<div class="window-content">
|
|
403
|
+
<div class="section-header">
|
|
404
|
+
<label class="checkbox-row">
|
|
405
|
+
<input type="checkbox" id="auto-scroll" checked>
|
|
406
|
+
Auto-scroll
|
|
407
|
+
</label>
|
|
408
|
+
<button id="clear-logs" class="btn">Clear</button>
|
|
409
|
+
</div>
|
|
410
|
+
<div id="logs" class="log-area"></div>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
|
|
414
|
+
<!-- Artifacts Window -->
|
|
415
|
+
<div class="window">
|
|
416
|
+
<div class="title-bar">
|
|
417
|
+
<div class="close-box"></div>
|
|
418
|
+
<div class="title-bar-stripes"></div>
|
|
419
|
+
<span class="window-title">Artifacts</span>
|
|
420
|
+
<div class="title-bar-stripes"></div>
|
|
421
|
+
</div>
|
|
422
|
+
<div class="window-content">
|
|
423
|
+
<label class="checkbox-row">
|
|
424
|
+
<input type="checkbox" id="show-advanced">
|
|
425
|
+
Show artifacts
|
|
426
|
+
</label>
|
|
427
|
+
<div id="artifacts" style="display:none;margin-top:8px;"></div>
|
|
428
|
+
</div>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<!-- Sessions Window -->
|
|
432
|
+
<div class="window">
|
|
433
|
+
<div class="title-bar">
|
|
434
|
+
<div class="close-box"></div>
|
|
435
|
+
<div class="title-bar-stripes"></div>
|
|
436
|
+
<span class="window-title">Sessions</span>
|
|
437
|
+
<div class="title-bar-stripes"></div>
|
|
438
|
+
</div>
|
|
439
|
+
<div class="window-content">
|
|
440
|
+
<div id="sessions" class="list-box">
|
|
441
|
+
<div class="list-item" style="color:#888;">No sessions yet.</div>
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
|
|
447
|
+
<script>
|
|
448
|
+
const logsEl = document.getElementById("logs");
|
|
449
|
+
const statusEl = document.getElementById("status");
|
|
450
|
+
const statusLed = document.getElementById("status-led");
|
|
451
|
+
const threadEl = document.getElementById("thread");
|
|
452
|
+
const artifactsEl = document.getElementById("artifacts");
|
|
453
|
+
const sessionsEl = document.getElementById("sessions");
|
|
454
|
+
const milhouseEl = document.getElementById("milhouse-running");
|
|
455
|
+
|
|
456
|
+
const goalEl = document.getElementById("goal");
|
|
457
|
+
const maxEl = document.getElementById("max");
|
|
458
|
+
const workdirEl = document.getElementById("workdir");
|
|
459
|
+
const openProjectBtn = document.getElementById("open-project");
|
|
460
|
+
const newProjectBtn = document.getElementById("new-project");
|
|
461
|
+
const startBtn = document.getElementById("start");
|
|
462
|
+
const stopBtn = document.getElementById("stop");
|
|
463
|
+
const autoScrollEl = document.getElementById("auto-scroll");
|
|
464
|
+
const clearLogsBtn = document.getElementById("clear-logs");
|
|
465
|
+
const showAdvEl = document.getElementById("show-advanced");
|
|
466
|
+
|
|
467
|
+
let es;
|
|
468
|
+
let running = false;
|
|
469
|
+
|
|
470
|
+
function appendLog(line) {
|
|
471
|
+
logsEl.textContent += line + "\n";
|
|
472
|
+
if (autoScrollEl.checked) logsEl.scrollTop = logsEl.scrollHeight;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function setStatus(text, state = "idle") {
|
|
476
|
+
statusEl.textContent = text;
|
|
477
|
+
statusLed.className = "status-led " + state;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function setThread(text) {
|
|
481
|
+
threadEl.textContent = text ? "Thread: " + text : "";
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function setRunning(value) {
|
|
485
|
+
running = Boolean(value);
|
|
486
|
+
updateControls();
|
|
487
|
+
if (running) {
|
|
488
|
+
milhouseEl.classList.add("visible");
|
|
489
|
+
} else {
|
|
490
|
+
milhouseEl.classList.remove("visible");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function updateControls() {
|
|
495
|
+
const hasGoal = goalEl.value.trim().length > 0;
|
|
496
|
+
startBtn.disabled = running || !hasGoal;
|
|
497
|
+
stopBtn.disabled = !running;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async function fetchStatus() {
|
|
501
|
+
const res = await fetch("/api/status");
|
|
502
|
+
const data = await res.json();
|
|
503
|
+
setStatus(data.running ? "Running" : "Idle", data.running ? "running" : "idle");
|
|
504
|
+
setThread(data.session?.threadId || data.artifacts?.threadId || "");
|
|
505
|
+
renderArtifacts(data.artifacts || {});
|
|
506
|
+
setRunning(data.running);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function fetchSessions() {
|
|
510
|
+
const res = await fetch("/api/sessions");
|
|
511
|
+
const data = await res.json();
|
|
512
|
+
const list = (data.sessions || [])
|
|
513
|
+
.slice()
|
|
514
|
+
.reverse()
|
|
515
|
+
.map(
|
|
516
|
+
(s) =>
|
|
517
|
+
`<div class="list-item">
|
|
518
|
+
<strong>${escapeHtml(s.goal || "No goal")}</strong>
|
|
519
|
+
<div class="meta">${s.status} • ${formatDateTime(s.startedAt)}</div>
|
|
520
|
+
</div>`,
|
|
521
|
+
)
|
|
522
|
+
.join("");
|
|
523
|
+
sessionsEl.innerHTML = list || '<div class="list-item" style="color:#888;">No sessions yet.</div>';
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function renderArtifacts(a) {
|
|
527
|
+
if (!showAdvEl.checked) {
|
|
528
|
+
artifactsEl.style.display = "none";
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
artifactsEl.style.display = "block";
|
|
532
|
+
const parts = [];
|
|
533
|
+
if (a.threadId)
|
|
534
|
+
parts.push(`<div><strong>Thread:</strong> ${escapeHtml(a.threadId)}</div>`);
|
|
535
|
+
|
|
536
|
+
const artifactDefs = [
|
|
537
|
+
{ key: "planLog", title: "Plan log" },
|
|
538
|
+
{ key: "buildLog", title: "Build log" },
|
|
539
|
+
{ key: "planFile", title: "IMPLEMENTATION_PLAN.md" },
|
|
540
|
+
];
|
|
541
|
+
|
|
542
|
+
artifactDefs.forEach(({ key, title }) => {
|
|
543
|
+
const content = a[key];
|
|
544
|
+
if (!content) return;
|
|
545
|
+
parts.push(
|
|
546
|
+
`<div style="margin-top:8px;">
|
|
547
|
+
<strong>${title}:</strong>
|
|
548
|
+
<div class="log-area" style="height:100px;margin-top:4px;">${escapeHtml(content)}</div>
|
|
549
|
+
</div>`,
|
|
550
|
+
);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
artifactsEl.innerHTML = parts.join("") || "<em>No artifacts yet.</em>";
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function escapeHtml(str) {
|
|
557
|
+
return str
|
|
558
|
+
.replace(/&/g, "&")
|
|
559
|
+
.replace(/</g, "<")
|
|
560
|
+
.replace(/>/g, ">");
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function formatDateTime(value) {
|
|
564
|
+
if (!value) return "";
|
|
565
|
+
const d = new Date(value);
|
|
566
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
567
|
+
return d.toLocaleString();
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function startStream() {
|
|
571
|
+
if (es) es.close();
|
|
572
|
+
es = new EventSource("/api/events");
|
|
573
|
+
es.onmessage = (ev) => {
|
|
574
|
+
appendLog(ev.data);
|
|
575
|
+
const threadMatch = ev.data.match(/thread:\s*([0-9a-zA-Z-]+)/);
|
|
576
|
+
if (threadMatch) setThread(threadMatch[1]);
|
|
577
|
+
const loopMatch = ev.data.match(/LOOP\s+(\d+)/i);
|
|
578
|
+
if (loopMatch) setStatus("Running (loop " + loopMatch[1] + ")", "running");
|
|
579
|
+
if (/\[exit\]/i.test(ev.data)) setRunning(false);
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
startBtn.onclick = async () => {
|
|
584
|
+
logsEl.textContent = "";
|
|
585
|
+
const trimmedGoal = goalEl.value.trim();
|
|
586
|
+
if (!trimmedGoal) {
|
|
587
|
+
appendLog("Error: goal is required");
|
|
588
|
+
setStatus("Error", "error");
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const body = {
|
|
592
|
+
goal: trimmedGoal,
|
|
593
|
+
maxIterations: Number(maxEl.value) || 0,
|
|
594
|
+
workdir: workdirEl.value.trim() || undefined,
|
|
595
|
+
};
|
|
596
|
+
setRunning(true);
|
|
597
|
+
const res = await fetch("/api/start", {
|
|
598
|
+
method: "POST",
|
|
599
|
+
headers: { "Content-Type": "application/json" },
|
|
600
|
+
body: JSON.stringify(body),
|
|
601
|
+
});
|
|
602
|
+
if (!res.ok) {
|
|
603
|
+
const err = await res.json();
|
|
604
|
+
appendLog("Error: " + (err.error || res.statusText));
|
|
605
|
+
setRunning(false);
|
|
606
|
+
setStatus("Error", "error");
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
setStatus("Running", "running");
|
|
610
|
+
fetchStatus();
|
|
611
|
+
fetchSessions();
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
stopBtn.onclick = async () => {
|
|
615
|
+
await fetch("/api/stop", { method: "POST" });
|
|
616
|
+
setStatus("Stopping", "idle");
|
|
617
|
+
setRunning(false);
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
clearLogsBtn.onclick = () => {
|
|
621
|
+
logsEl.textContent = "";
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
goalEl.addEventListener("input", updateControls);
|
|
625
|
+
|
|
626
|
+
openProjectBtn.onclick = () => {
|
|
627
|
+
fetch("/api/browse", {
|
|
628
|
+
method: "POST",
|
|
629
|
+
headers: { "Content-Type": "application/json" },
|
|
630
|
+
body: "{}"
|
|
631
|
+
})
|
|
632
|
+
.then((r) => r.text().then((t) => ({ ok: r.ok, text: t })))
|
|
633
|
+
.then(({ ok, text }) => {
|
|
634
|
+
try {
|
|
635
|
+
const data = JSON.parse(text);
|
|
636
|
+
if (data.path) workdirEl.value = data.path;
|
|
637
|
+
else if (data.error) appendLog("Browse error: " + data.error);
|
|
638
|
+
} catch {
|
|
639
|
+
if (!ok) appendLog("Browse error: " + text.slice(0, 200));
|
|
640
|
+
}
|
|
641
|
+
})
|
|
642
|
+
.catch((e) => appendLog("Browse error: " + e.message));
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
newProjectBtn.onclick = () => {
|
|
646
|
+
fetch("/api/browse", { method: "POST", headers: { "Content-Type": "application/json" } })
|
|
647
|
+
.then((r) => r.text().then((t) => ({ ok: r.ok, text: t })))
|
|
648
|
+
.then(({ ok, text }) => {
|
|
649
|
+
let base = "";
|
|
650
|
+
try {
|
|
651
|
+
const data = JSON.parse(text);
|
|
652
|
+
if (!data.path && data.error) {
|
|
653
|
+
appendLog("Browse error: " + data.error);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
base = data.path || "";
|
|
657
|
+
} catch {
|
|
658
|
+
if (!ok) {
|
|
659
|
+
appendLog("Browse error: " + text.slice(0, 200));
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const name = prompt("Enter new project folder name", "milhouse-project");
|
|
664
|
+
if (name) {
|
|
665
|
+
const sep = base.endsWith("\\") || base.endsWith("/") ? "" : pathSeparator(base);
|
|
666
|
+
workdirEl.value = base + sep + name;
|
|
667
|
+
}
|
|
668
|
+
})
|
|
669
|
+
.catch((e) => appendLog("Browse error: " + e.message));
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
function pathSeparator(p) {
|
|
673
|
+
return p.includes("\\") ? "\\" : "/";
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
showAdvEl.addEventListener("change", () => {
|
|
677
|
+
fetchStatus();
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
updateControls();
|
|
681
|
+
startStream();
|
|
682
|
+
fetchStatus();
|
|
683
|
+
fetchSessions();
|
|
684
|
+
setInterval(() => {
|
|
685
|
+
fetchStatus();
|
|
686
|
+
fetchSessions();
|
|
687
|
+
}, 5000);
|
|
688
|
+
</script>
|
|
689
|
+
</body>
|
|
690
|
+
|
|
691
|
+
</html>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|