notsleep 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/.env.example +117 -0
  2. package/LICENSE +21 -0
  3. package/README.md +341 -0
  4. package/dashboard/index.html +893 -0
  5. package/dist/agent.d.ts +162 -0
  6. package/dist/agent.d.ts.map +1 -0
  7. package/dist/agent.js +1064 -0
  8. package/dist/agent.js.map +1 -0
  9. package/dist/api.d.ts +10 -0
  10. package/dist/api.d.ts.map +1 -0
  11. package/dist/api.js +39 -0
  12. package/dist/api.js.map +1 -0
  13. package/dist/checkpoint.d.ts +52 -0
  14. package/dist/checkpoint.d.ts.map +1 -0
  15. package/dist/checkpoint.js +234 -0
  16. package/dist/checkpoint.js.map +1 -0
  17. package/dist/config.d.ts +24 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +265 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/context.d.ts +15 -0
  22. package/dist/context.d.ts.map +1 -0
  23. package/dist/context.js +237 -0
  24. package/dist/context.js.map +1 -0
  25. package/dist/cost.d.ts +26 -0
  26. package/dist/cost.d.ts.map +1 -0
  27. package/dist/cost.js +161 -0
  28. package/dist/cost.js.map +1 -0
  29. package/dist/dashboard.d.ts +96 -0
  30. package/dist/dashboard.d.ts.map +1 -0
  31. package/dist/dashboard.js +292 -0
  32. package/dist/dashboard.js.map +1 -0
  33. package/dist/fileconfig.d.ts +129 -0
  34. package/dist/fileconfig.d.ts.map +1 -0
  35. package/dist/fileconfig.js +391 -0
  36. package/dist/fileconfig.js.map +1 -0
  37. package/dist/index.d.ts +3 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +882 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/init.d.ts +9 -0
  42. package/dist/init.d.ts.map +1 -0
  43. package/dist/init.js +221 -0
  44. package/dist/init.js.map +1 -0
  45. package/dist/logger.d.ts +33 -0
  46. package/dist/logger.d.ts.map +1 -0
  47. package/dist/logger.js +150 -0
  48. package/dist/logger.js.map +1 -0
  49. package/dist/modelsdev.d.ts +63 -0
  50. package/dist/modelsdev.d.ts.map +1 -0
  51. package/dist/modelsdev.js +265 -0
  52. package/dist/modelsdev.js.map +1 -0
  53. package/dist/parallel.d.ts +54 -0
  54. package/dist/parallel.d.ts.map +1 -0
  55. package/dist/parallel.js +139 -0
  56. package/dist/parallel.js.map +1 -0
  57. package/dist/pool.d.ts +17 -0
  58. package/dist/pool.d.ts.map +1 -0
  59. package/dist/pool.js +53 -0
  60. package/dist/pool.js.map +1 -0
  61. package/dist/provider.d.ts +60 -0
  62. package/dist/provider.d.ts.map +1 -0
  63. package/dist/provider.js +396 -0
  64. package/dist/provider.js.map +1 -0
  65. package/dist/registry.d.ts +60 -0
  66. package/dist/registry.d.ts.map +1 -0
  67. package/dist/registry.js +153 -0
  68. package/dist/registry.js.map +1 -0
  69. package/dist/safety.d.ts +21 -0
  70. package/dist/safety.d.ts.map +1 -0
  71. package/dist/safety.js +118 -0
  72. package/dist/safety.js.map +1 -0
  73. package/dist/schema.d.ts +9 -0
  74. package/dist/schema.d.ts.map +1 -0
  75. package/dist/schema.js +166 -0
  76. package/dist/schema.js.map +1 -0
  77. package/dist/test-dashboard.d.ts +5 -0
  78. package/dist/test-dashboard.d.ts.map +1 -0
  79. package/dist/test-dashboard.js +202 -0
  80. package/dist/test-dashboard.js.map +1 -0
  81. package/dist/tools.d.ts +27 -0
  82. package/dist/tools.d.ts.map +1 -0
  83. package/dist/tools.js +1212 -0
  84. package/dist/tools.js.map +1 -0
  85. package/dist/types.d.ts +180 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +14 -0
  88. package/dist/types.js.map +1 -0
  89. package/dist/worktree.d.ts +50 -0
  90. package/dist/worktree.d.ts.map +1 -0
  91. package/dist/worktree.js +186 -0
  92. package/dist/worktree.js.map +1 -0
  93. package/package.json +67 -0
@@ -0,0 +1,893 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NotSleep - Dashboard</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0a0e17;
10
+ --bg2: #111827;
11
+ --bg3: #1a2233;
12
+ --border: #2a3548;
13
+ --text: #e2e8f0;
14
+ --text-dim: #8892a4;
15
+ --accent: #38bdf8;
16
+ --accent2: #818cf8;
17
+ --green: #34d399;
18
+ --yellow: #fbbf24;
19
+ --red: #f87171;
20
+ --orange: #fb923c;
21
+ --cyan: #22d3ee;
22
+ --purple: #a78bfa;
23
+ }
24
+
25
+ * { margin: 0; padding: 0; box-sizing: border-box; }
26
+
27
+ body {
28
+ font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', 'JetBrains Mono', monospace;
29
+ background: var(--bg);
30
+ color: var(--text);
31
+ min-height: 100vh;
32
+ overflow-x: hidden;
33
+ }
34
+
35
+ /* ─── Header ─────────────────────────────── */
36
+ .header {
37
+ background: linear-gradient(135deg, var(--bg2), var(--bg3));
38
+ border-bottom: 1px solid var(--border);
39
+ padding: 16px 24px;
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: space-between;
43
+ position: sticky;
44
+ top: 0;
45
+ z-index: 100;
46
+ backdrop-filter: blur(12px);
47
+ }
48
+
49
+ .header-left {
50
+ display: flex;
51
+ align-items: center;
52
+ gap: 16px;
53
+ }
54
+
55
+ .logo {
56
+ font-size: 20px;
57
+ font-weight: 700;
58
+ background: linear-gradient(135deg, var(--accent), var(--accent2));
59
+ -webkit-background-clip: text;
60
+ -webkit-text-fill-color: transparent;
61
+ letter-spacing: -0.5px;
62
+ }
63
+
64
+ .status-badge {
65
+ padding: 4px 12px;
66
+ border-radius: 20px;
67
+ font-size: 11px;
68
+ font-weight: 600;
69
+ text-transform: uppercase;
70
+ letter-spacing: 0.5px;
71
+ }
72
+
73
+ .status-running { background: rgba(52, 211, 153, 0.15); color: var(--green); border: 1px solid rgba(52, 211, 153, 0.3); }
74
+ .status-paused { background: rgba(251, 191, 36, 0.15); color: var(--yellow); border: 1px solid rgba(251, 191, 36, 0.3); }
75
+ .status-stopped { background: rgba(248, 113, 113, 0.15); color: var(--red); border: 1px solid rgba(248, 113, 113, 0.3); }
76
+ .status-completed { background: rgba(56, 189, 248, 0.15); color: var(--accent); border: 1px solid rgba(56, 189, 248, 0.3); }
77
+ .status-idle { background: rgba(136, 146, 164, 0.15); color: var(--text-dim); border: 1px solid rgba(136, 146, 164, 0.3); }
78
+
79
+ .connection-dot {
80
+ width: 8px;
81
+ height: 8px;
82
+ border-radius: 50%;
83
+ background: var(--red);
84
+ margin-left: 12px;
85
+ }
86
+ .connection-dot.connected { background: var(--green); box-shadow: 0 0 8px rgba(52, 211, 153, 0.5); }
87
+
88
+ .header-right {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 8px;
92
+ }
93
+
94
+ /* ─── Buttons ────────────────────────────── */
95
+ button {
96
+ font-family: inherit;
97
+ font-size: 12px;
98
+ font-weight: 600;
99
+ padding: 6px 14px;
100
+ border-radius: 6px;
101
+ border: 1px solid var(--border);
102
+ background: var(--bg3);
103
+ color: var(--text);
104
+ cursor: pointer;
105
+ transition: all 0.15s ease;
106
+ }
107
+
108
+ button:hover { background: var(--border); }
109
+ button:active { transform: scale(0.97); }
110
+
111
+ button.btn-danger { border-color: rgba(248, 113, 113, 0.4); color: var(--red); }
112
+ button.btn-danger:hover { background: rgba(248, 113, 113, 0.1); }
113
+
114
+ button.btn-primary { border-color: rgba(56, 189, 248, 0.4); color: var(--accent); }
115
+ button.btn-primary:hover { background: rgba(56, 189, 248, 0.1); }
116
+
117
+ button.btn-success { border-color: rgba(52, 211, 153, 0.4); color: var(--green); }
118
+ button.btn-success:hover { background: rgba(52, 211, 153, 0.1); }
119
+
120
+ button.btn-warning { border-color: rgba(251, 191, 36, 0.4); color: var(--yellow); }
121
+ button.btn-warning:hover { background: rgba(251, 191, 36, 0.1); }
122
+
123
+ /* ─── Layout ─────────────────────────────── */
124
+ .container {
125
+ display: grid;
126
+ grid-template-columns: 1fr 380px;
127
+ grid-template-rows: auto 1fr;
128
+ gap: 16px;
129
+ padding: 16px 24px;
130
+ height: calc(100vh - 65px);
131
+ }
132
+
133
+ .panel {
134
+ background: var(--bg2);
135
+ border: 1px solid var(--border);
136
+ border-radius: 10px;
137
+ overflow: hidden;
138
+ display: flex;
139
+ flex-direction: column;
140
+ }
141
+
142
+ .panel-header {
143
+ padding: 12px 16px;
144
+ border-bottom: 1px solid var(--border);
145
+ font-size: 12px;
146
+ font-weight: 700;
147
+ text-transform: uppercase;
148
+ letter-spacing: 1px;
149
+ color: var(--text-dim);
150
+ display: flex;
151
+ justify-content: space-between;
152
+ align-items: center;
153
+ background: var(--bg3);
154
+ }
155
+
156
+ .panel-body {
157
+ padding: 12px 16px;
158
+ overflow-y: auto;
159
+ flex: 1;
160
+ }
161
+
162
+ /* ─── Stats Row ──────────────────────────── */
163
+ .stats-row {
164
+ grid-column: 1 / -1;
165
+ }
166
+
167
+ .stats-grid {
168
+ display: grid;
169
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
170
+ gap: 12px;
171
+ padding: 12px 16px;
172
+ }
173
+
174
+ .stat-card {
175
+ background: var(--bg3);
176
+ border: 1px solid var(--border);
177
+ border-radius: 8px;
178
+ padding: 12px 16px;
179
+ text-align: center;
180
+ }
181
+
182
+ .stat-value {
183
+ font-size: 24px;
184
+ font-weight: 700;
185
+ color: var(--accent);
186
+ margin-bottom: 4px;
187
+ }
188
+
189
+ .stat-label {
190
+ font-size: 10px;
191
+ text-transform: uppercase;
192
+ letter-spacing: 1px;
193
+ color: var(--text-dim);
194
+ }
195
+
196
+ /* ─── Progress Bar ───────────────────────── */
197
+ .progress-bar-container {
198
+ padding: 0 16px 12px;
199
+ }
200
+
201
+ .progress-bar {
202
+ height: 4px;
203
+ background: var(--bg);
204
+ border-radius: 2px;
205
+ overflow: hidden;
206
+ }
207
+
208
+ .progress-bar-fill {
209
+ height: 100%;
210
+ background: linear-gradient(90deg, var(--accent), var(--accent2));
211
+ border-radius: 2px;
212
+ transition: width 0.5s ease;
213
+ width: 0%;
214
+ }
215
+
216
+ .progress-text {
217
+ font-size: 10px;
218
+ color: var(--text-dim);
219
+ margin-top: 4px;
220
+ text-align: right;
221
+ }
222
+
223
+ /* ─── Meters (iterations + context window) ─── */
224
+ .meter-head {
225
+ display: flex;
226
+ justify-content: space-between;
227
+ align-items: baseline;
228
+ font-size: 10px;
229
+ text-transform: uppercase;
230
+ letter-spacing: 1px;
231
+ color: var(--text-dim);
232
+ margin: 10px 0 4px;
233
+ }
234
+ .meter-head:first-child { margin-top: 0; }
235
+ .meter-head .meter-val { color: var(--text); font-weight: 600; letter-spacing: 0; text-transform: none; }
236
+
237
+ /* Context-window fill changes colour as it approaches the summarize point. */
238
+ .progress-bar-fill.ctx-ok { background: linear-gradient(90deg, var(--green), var(--cyan)); }
239
+ .progress-bar-fill.ctx-warn { background: linear-gradient(90deg, var(--yellow), var(--orange)); }
240
+ .progress-bar-fill.ctx-high { background: linear-gradient(90deg, var(--orange), var(--red)); }
241
+
242
+ /* ─── Sparkline ──────────────────────────── */
243
+ .sparkline-row {
244
+ padding: 6px 16px 12px;
245
+ display: flex;
246
+ align-items: center;
247
+ gap: 12px;
248
+ }
249
+ .sparkline-label {
250
+ font-size: 10px;
251
+ text-transform: uppercase;
252
+ letter-spacing: 1px;
253
+ color: var(--text-dim);
254
+ white-space: nowrap;
255
+ }
256
+ .sparkline {
257
+ flex: 1;
258
+ height: 34px;
259
+ width: 100%;
260
+ display: block;
261
+ }
262
+ .sparkline .spark-line { fill: none; stroke: var(--accent); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
263
+ .sparkline .spark-area { fill: rgba(56, 189, 248, 0.12); stroke: none; }
264
+
265
+ /* Sub-label under a stat value (e.g. cost per hour). */
266
+ .stat-sub {
267
+ font-size: 10px;
268
+ color: var(--text-dim);
269
+ margin-top: 2px;
270
+ }
271
+
272
+ /* ─── Log Panel ──────────────────────────── */
273
+ .log-panel {
274
+ grid-row: 2;
275
+ }
276
+
277
+ .log-body {
278
+ font-size: 12px;
279
+ line-height: 1.6;
280
+ padding: 8px 12px;
281
+ }
282
+
283
+ .log-line {
284
+ display: flex;
285
+ gap: 8px;
286
+ padding: 2px 4px;
287
+ border-radius: 3px;
288
+ word-break: break-word;
289
+ }
290
+
291
+ .log-line:hover {
292
+ background: rgba(255, 255, 255, 0.03);
293
+ }
294
+
295
+ .log-time {
296
+ color: var(--text-dim);
297
+ flex-shrink: 0;
298
+ font-size: 11px;
299
+ }
300
+
301
+ .log-level {
302
+ flex-shrink: 0;
303
+ font-weight: 600;
304
+ font-size: 11px;
305
+ min-width: 50px;
306
+ }
307
+
308
+ .log-level-DEBUG { color: var(--text-dim); }
309
+ .log-level-INFO { color: var(--accent); }
310
+ .log-level-WARN { color: var(--yellow); }
311
+ .log-level-ERROR { color: var(--red); }
312
+ .log-level-AGENT { color: var(--green); }
313
+ .log-level-PROGRESS { color: var(--cyan); }
314
+
315
+ .log-msg {
316
+ color: var(--text);
317
+ flex: 1;
318
+ }
319
+
320
+ .log-msg-AGENT { color: var(--green); }
321
+ .log-msg-ERROR { color: var(--red); }
322
+ .log-msg-WARN { color: var(--yellow); }
323
+
324
+ /* Tool log messages */
325
+ .log-level-TOOL { color: var(--purple); }
326
+ .log-msg-TOOL { color: var(--text-dim); }
327
+
328
+ /* ─── Sidebar ────────────────────────────── */
329
+ .sidebar {
330
+ grid-row: 2;
331
+ display: flex;
332
+ flex-direction: column;
333
+ gap: 16px;
334
+ }
335
+
336
+ /* ─── Task Queue ─────────────────────────── */
337
+ .task-list {
338
+ list-style: none;
339
+ }
340
+
341
+ .task-item {
342
+ padding: 10px 12px;
343
+ border-bottom: 1px solid var(--border);
344
+ display: flex;
345
+ flex-direction: column;
346
+ gap: 4px;
347
+ }
348
+
349
+ .task-item:last-child { border-bottom: none; }
350
+
351
+ .task-item-header {
352
+ display: flex;
353
+ align-items: center;
354
+ gap: 8px;
355
+ font-size: 11px;
356
+ }
357
+
358
+ .task-status {
359
+ padding: 2px 6px;
360
+ border-radius: 4px;
361
+ font-size: 9px;
362
+ font-weight: 700;
363
+ text-transform: uppercase;
364
+ }
365
+
366
+ .task-status-pending { background: rgba(136, 146, 164, 0.15); color: var(--text-dim); }
367
+ .task-status-in_progress { background: rgba(56, 189, 248, 0.15); color: var(--accent); }
368
+ .task-status-completed { background: rgba(52, 211, 153, 0.15); color: var(--green); }
369
+ .task-status-failed { background: rgba(248, 113, 113, 0.15); color: var(--red); }
370
+
371
+ .task-priority {
372
+ padding: 2px 6px;
373
+ border-radius: 4px;
374
+ font-size: 9px;
375
+ font-weight: 700;
376
+ text-transform: uppercase;
377
+ }
378
+
379
+ .task-priority-high { color: var(--red); }
380
+ .task-priority-medium { color: var(--yellow); }
381
+ .task-priority-low { color: var(--text-dim); }
382
+
383
+ .task-desc {
384
+ font-size: 12px;
385
+ color: var(--text);
386
+ line-height: 1.4;
387
+ }
388
+
389
+ .task-summary {
390
+ font-size: 11px;
391
+ color: var(--text-dim);
392
+ font-style: italic;
393
+ }
394
+
395
+ /* ─── Add Task Form ──────────────────────── */
396
+ .add-task-form {
397
+ padding: 12px 16px;
398
+ border-top: 1px solid var(--border);
399
+ display: flex;
400
+ flex-direction: column;
401
+ gap: 8px;
402
+ }
403
+
404
+ .add-task-form input,
405
+ .add-task-form select {
406
+ font-family: inherit;
407
+ font-size: 12px;
408
+ padding: 8px 10px;
409
+ border-radius: 6px;
410
+ border: 1px solid var(--border);
411
+ background: var(--bg);
412
+ color: var(--text);
413
+ outline: none;
414
+ }
415
+
416
+ .add-task-form input:focus,
417
+ .add-task-form select:focus {
418
+ border-color: var(--accent);
419
+ }
420
+
421
+ .add-task-row {
422
+ display: flex;
423
+ gap: 8px;
424
+ }
425
+
426
+ .add-task-row select { width: 100px; }
427
+ .add-task-row input { flex: 1; }
428
+
429
+ /* ─── Info Panel ─────────────────────────── */
430
+ .info-grid {
431
+ display: grid;
432
+ grid-template-columns: auto 1fr;
433
+ gap: 6px 12px;
434
+ font-size: 12px;
435
+ }
436
+
437
+ .info-label { color: var(--text-dim); }
438
+ .info-value { color: var(--text); font-weight: 600; }
439
+
440
+ /* ─── Scrollbar ──────────────────────────── */
441
+ ::-webkit-scrollbar { width: 6px; }
442
+ ::-webkit-scrollbar-track { background: transparent; }
443
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
444
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
445
+
446
+ /* ─── Responsive ─────────────────────────── */
447
+ @media (max-width: 900px) {
448
+ .container {
449
+ grid-template-columns: 1fr;
450
+ grid-template-rows: auto auto 1fr;
451
+ }
452
+ .sidebar { grid-row: 3; }
453
+ }
454
+
455
+ /* ─── Animations ─────────────────────────── */
456
+ @keyframes pulse {
457
+ 0%, 100% { opacity: 1; }
458
+ 50% { opacity: 0.5; }
459
+ }
460
+
461
+ .pulse { animation: pulse 2s ease-in-out infinite; }
462
+
463
+ .fade-in {
464
+ animation: fadeIn 0.3s ease;
465
+ }
466
+
467
+ @keyframes fadeIn {
468
+ from { opacity: 0; transform: translateY(4px); }
469
+ to { opacity: 1; transform: translateY(0); }
470
+ }
471
+
472
+ /* ─── Empty State ────────────────────────── */
473
+ .empty-state {
474
+ text-align: center;
475
+ padding: 32px 16px;
476
+ color: var(--text-dim);
477
+ font-size: 13px;
478
+ }
479
+
480
+ .empty-state-icon {
481
+ font-size: 32px;
482
+ margin-bottom: 12px;
483
+ opacity: 0.5;
484
+ }
485
+
486
+ /* ─── Current Task Banner ────────────────── */
487
+ .current-task {
488
+ padding: 8px 16px;
489
+ background: rgba(56, 189, 248, 0.05);
490
+ border-bottom: 1px solid var(--border);
491
+ font-size: 12px;
492
+ color: var(--accent);
493
+ display: flex;
494
+ align-items: center;
495
+ gap: 8px;
496
+ }
497
+
498
+ .current-task-label {
499
+ font-weight: 700;
500
+ text-transform: uppercase;
501
+ font-size: 10px;
502
+ letter-spacing: 0.5px;
503
+ }
504
+ </style>
505
+ </head>
506
+ <body>
507
+
508
+ <!-- ─── Header ──────────────────────────────── -->
509
+ <div class="header">
510
+ <div class="header-left">
511
+ <div class="logo">NotSleep</div>
512
+ <div id="statusBadge" class="status-badge status-idle">IDLE</div>
513
+ <div id="connectionDot" class="connection-dot" title="WebSocket disconnected"></div>
514
+ </div>
515
+ <div class="header-right">
516
+ <button id="btnPause" class="btn-warning" onclick="sendCommand('pause')" title="Pause agent">Pause</button>
517
+ <button id="btnResume" class="btn-success" onclick="sendCommand('resume')" title="Resume agent" style="display:none">Resume</button>
518
+ <button id="btnStop" class="btn-danger" onclick="confirmStop()" title="Stop agent">Stop</button>
519
+ </div>
520
+ </div>
521
+
522
+ <!-- ─── Main Layout ─────────────────────────── -->
523
+ <div class="container">
524
+
525
+ <!-- Stats Row -->
526
+ <div class="panel stats-row">
527
+ <div class="progress-bar-container">
528
+ <div class="meter-head"><span>Iterations</span><span id="progressText" class="meter-val">0</span></div>
529
+ <div class="progress-bar"><div id="progressFill" class="progress-bar-fill"></div></div>
530
+ <div class="meter-head"><span>Context window</span><span id="ctxLabel" class="meter-val">–</span></div>
531
+ <div class="progress-bar"><div id="ctxFill" class="progress-bar-fill ctx-ok"></div></div>
532
+ </div>
533
+ <div class="stats-grid">
534
+ <div class="stat-card">
535
+ <div id="statElapsed" class="stat-value">00:00:00</div>
536
+ <div class="stat-label">Elapsed</div>
537
+ </div>
538
+ <div class="stat-card">
539
+ <div id="statIterations" class="stat-value">0</div>
540
+ <div class="stat-label">Iterations</div>
541
+ </div>
542
+ <div class="stat-card">
543
+ <div id="statTokens" class="stat-value">0</div>
544
+ <div class="stat-label">Tokens</div>
545
+ </div>
546
+ <div class="stat-card">
547
+ <div id="statCost" class="stat-value">$0</div>
548
+ <div class="stat-label">Est. Cost</div>
549
+ <div id="statCostRate" class="stat-sub"></div>
550
+ </div>
551
+ <div class="stat-card">
552
+ <div id="statToolCalls" class="stat-value">0</div>
553
+ <div class="stat-label">Tool Calls</div>
554
+ </div>
555
+ <div class="stat-card">
556
+ <div id="statTasks" class="stat-value">0</div>
557
+ <div class="stat-label">Tasks Done</div>
558
+ </div>
559
+ <div class="stat-card">
560
+ <div id="statErrors" class="stat-value">0</div>
561
+ <div class="stat-label">Errors</div>
562
+ </div>
563
+ <div class="stat-card">
564
+ <div id="statResets" class="stat-value">0</div>
565
+ <div class="stat-label">Ctx Resets</div>
566
+ </div>
567
+ </div>
568
+ <div class="sparkline-row">
569
+ <span class="sparkline-label">Token trend</span>
570
+ <svg id="sparkline" class="sparkline" viewBox="0 0 300 34" preserveAspectRatio="none" aria-hidden="true"></svg>
571
+ </div>
572
+ </div>
573
+
574
+ <!-- Log Panel -->
575
+ <div class="panel log-panel">
576
+ <div class="panel-header">
577
+ <span>Live Logs</span>
578
+ <div style="display:flex;gap:8px;align-items:center;">
579
+ <label style="font-size:11px;font-weight:normal;text-transform:none;letter-spacing:0;display:flex;align-items:center;gap:4px;cursor:pointer;">
580
+ <input type="checkbox" id="autoScroll" checked> Auto-scroll
581
+ </label>
582
+ <button onclick="clearLogs()" style="font-size:10px;padding:3px 8px;">Clear</button>
583
+ </div>
584
+ </div>
585
+ <div id="currentTaskBanner" class="current-task" style="display:none;">
586
+ <span class="current-task-label">Current Task:</span>
587
+ <span id="currentTaskText"></span>
588
+ </div>
589
+ <div id="logBody" class="panel-body log-body"></div>
590
+ </div>
591
+
592
+ <!-- Sidebar -->
593
+ <div class="sidebar">
594
+ <!-- Info Panel -->
595
+ <div class="panel" style="flex:0 0 auto;">
596
+ <div class="panel-header"><span>Agent Info</span></div>
597
+ <div class="panel-body">
598
+ <div class="info-grid">
599
+ <span class="info-label">Provider</span>
600
+ <span id="infoProvider" class="info-value">-</span>
601
+ <span class="info-label">Model</span>
602
+ <span id="infoModel" class="info-value">-</span>
603
+ <span class="info-label">Small model</span>
604
+ <span id="infoSmallModel" class="info-value">-</span>
605
+ <span class="info-label">Context</span>
606
+ <span id="infoContext" class="info-value">-</span>
607
+ <span class="info-label">Mode</span>
608
+ <span id="infoMode" class="info-value">-</span>
609
+ </div>
610
+ </div>
611
+ </div>
612
+
613
+ <!-- Task Queue Panel -->
614
+ <div class="panel" style="flex:1;min-height:0;">
615
+ <div class="panel-header">
616
+ <span>Task Queue (<span id="taskCount">0</span>)</span>
617
+ </div>
618
+ <div id="taskQueueBody" class="panel-body">
619
+ <div class="empty-state">
620
+ <div class="empty-state-icon">&#x1f4cb;</div>
621
+ <div>No tasks in queue</div>
622
+ </div>
623
+ </div>
624
+ <div class="add-task-form">
625
+ <div class="add-task-row">
626
+ <select id="newTaskPriority">
627
+ <option value="high">High</option>
628
+ <option value="medium" selected>Medium</option>
629
+ <option value="low">Low</option>
630
+ </select>
631
+ <input type="text" id="newTaskInput" placeholder="Add a task..." onkeydown="if(event.key==='Enter')addTask()">
632
+ </div>
633
+ <button class="btn-primary" onclick="addTask()" style="width:100%;">+ Add Task</button>
634
+ </div>
635
+ </div>
636
+ </div>
637
+ </div>
638
+
639
+ <script>
640
+ // ─── State ──────────────────────────────────
641
+ let ws = null;
642
+ let state = {};
643
+ let reconnectTimer = null;
644
+ let elapsedTimer = null;
645
+ const maxLogLines = 2000;
646
+
647
+ // ─── WebSocket ──────────────────────────────
648
+ function connect() {
649
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
650
+ ws = new WebSocket(`${protocol}//${location.host}`);
651
+
652
+ ws.onopen = () => {
653
+ document.getElementById('connectionDot').classList.add('connected');
654
+ document.getElementById('connectionDot').title = 'Connected';
655
+ if (reconnectTimer) { clearInterval(reconnectTimer); reconnectTimer = null; }
656
+ };
657
+
658
+ ws.onclose = () => {
659
+ document.getElementById('connectionDot').classList.remove('connected');
660
+ document.getElementById('connectionDot').title = 'Disconnected - reconnecting...';
661
+ if (!reconnectTimer) {
662
+ reconnectTimer = setInterval(connect, 3000);
663
+ }
664
+ };
665
+
666
+ ws.onerror = () => {};
667
+
668
+ ws.onmessage = (event) => {
669
+ try {
670
+ const msg = JSON.parse(event.data);
671
+ if (msg.type === 'state') updateState(msg.data);
672
+ else if (msg.type === 'log') appendLog(msg.data);
673
+ } catch(e) {}
674
+ };
675
+ }
676
+
677
+ function sendCommand(type, extra) {
678
+ if (ws && ws.readyState === WebSocket.OPEN) {
679
+ ws.send(JSON.stringify({ type, ...extra }));
680
+ }
681
+ }
682
+
683
+ // ─── State Update ───────────────────────────
684
+ function updateState(s) {
685
+ state = s;
686
+
687
+ // Status badge
688
+ const badge = document.getElementById('statusBadge');
689
+ badge.textContent = s.status.toUpperCase();
690
+ badge.className = 'status-badge status-' + s.status;
691
+
692
+ // Pause/Resume buttons
693
+ if (s.paused || s.status === 'paused') {
694
+ document.getElementById('btnPause').style.display = 'none';
695
+ document.getElementById('btnResume').style.display = '';
696
+ } else {
697
+ document.getElementById('btnPause').style.display = '';
698
+ document.getElementById('btnResume').style.display = 'none';
699
+ }
700
+
701
+ // Stats
702
+ document.getElementById('statIterations').textContent = s.stats.totalIterations.toLocaleString();
703
+ document.getElementById('statTokens').textContent = formatNumber(s.stats.totalTokensUsed);
704
+ const cost = s.stats.totalCostUsd || 0;
705
+ document.getElementById('statCost').textContent = cost < 1 ? '$' + cost.toFixed(4) : '$' + cost.toFixed(2);
706
+ document.getElementById('statToolCalls').textContent = s.stats.totalToolCalls.toLocaleString();
707
+ document.getElementById('statTasks').textContent = s.stats.tasksCompleted.toLocaleString();
708
+ document.getElementById('statErrors').textContent = s.stats.errors.toLocaleString();
709
+ document.getElementById('statResets').textContent = s.stats.contextResets.toLocaleString();
710
+
711
+ // Iterations bar. maxIterations <= 0 means UNLIMITED — a percentage is
712
+ // meaningless there, so show the running count with an ∞ marker instead.
713
+ const unlimited = !s.maxIterations || s.maxIterations <= 0;
714
+ const iterPct = unlimited ? 0 : Math.min((s.stats.totalIterations / s.maxIterations) * 100, 100);
715
+ document.getElementById('progressFill').style.width = iterPct + '%';
716
+ document.getElementById('progressText').textContent = unlimited
717
+ ? `${s.stats.totalIterations.toLocaleString()} · ∞`
718
+ : `${s.stats.totalIterations.toLocaleString()} / ${s.maxIterations.toLocaleString()}`;
719
+
720
+ // Context-window meter — the key long-horizon metric. contextUsage is 0..1
721
+ // from the REAL API token count; colour shifts as it nears the summarize point.
722
+ const ctxPct = Math.round((s.contextUsage || 0) * 100);
723
+ const ctxFill = document.getElementById('ctxFill');
724
+ ctxFill.style.width = Math.min(ctxPct, 100) + '%';
725
+ ctxFill.className = 'progress-bar-fill ' + (ctxPct >= 85 ? 'ctx-high' : ctxPct >= 60 ? 'ctx-warn' : 'ctx-ok');
726
+ const ctxTokens = Math.round((s.contextUsage || 0) * (s.contextLimit || 0));
727
+ document.getElementById('ctxLabel').textContent =
728
+ `${ctxPct}% · ${formatNumber(ctxTokens)} / ${formatNumber(s.contextLimit || 0)}`;
729
+
730
+ // Cost-per-hour, derived from est. cost and elapsed time (relevant for long runs).
731
+ const elapsedHr = s.stats.startTime ? (Date.now() - s.stats.startTime) / 3600000 : 0;
732
+ const rate = elapsedHr > 0.002 ? cost / elapsedHr : 0;
733
+ document.getElementById('statCostRate').textContent =
734
+ rate > 0 ? `~$${rate < 10 ? rate.toFixed(2) : Math.round(rate)}/hr` : '';
735
+
736
+ // Token trend sparkline
737
+ renderSparkline(s.tokenHistory || []);
738
+
739
+ // Elapsed timer
740
+ updateElapsed(s.stats.startTime);
741
+
742
+ // Info
743
+ document.getElementById('infoProvider').textContent = s.provider || '-';
744
+ document.getElementById('infoModel').textContent = s.model || '-';
745
+ document.getElementById('infoSmallModel').textContent = s.smallModel || '(same as model)';
746
+ document.getElementById('infoContext').textContent = s.contextLimit
747
+ ? `${formatNumber(s.contextLimit)}${s.contextLimitSource ? ' (' + s.contextLimitSource + ')' : ''}`
748
+ : '-';
749
+ document.getElementById('infoMode').textContent = s.continuous ? 'Continuous' : 'Single Task';
750
+
751
+ // Current task
752
+ const banner = document.getElementById('currentTaskBanner');
753
+ if (s.currentTask) {
754
+ banner.style.display = 'flex';
755
+ document.getElementById('currentTaskText').textContent = s.currentTask;
756
+ } else {
757
+ banner.style.display = 'none';
758
+ }
759
+
760
+ // Task queue
761
+ renderTaskQueue(s.taskQueue || []);
762
+ }
763
+
764
+ // ─── Task Queue ─────────────────────────────
765
+ function renderTaskQueue(tasks) {
766
+ const body = document.getElementById('taskQueueBody');
767
+ const count = document.getElementById('taskCount');
768
+ count.textContent = tasks.length;
769
+
770
+ if (tasks.length === 0) {
771
+ body.innerHTML = '<div class="empty-state"><div class="empty-state-icon">&#x1f4cb;</div><div>No tasks in queue</div></div>';
772
+ return;
773
+ }
774
+
775
+ let html = '<ul class="task-list">';
776
+ for (const t of tasks) {
777
+ html += `<li class="task-item fade-in">
778
+ <div class="task-item-header">
779
+ <span class="task-status task-status-${t.status}">${t.status.replace('_', ' ')}</span>
780
+ <span class="task-priority task-priority-${t.priority}">${t.priority}</span>
781
+ </div>
782
+ <div class="task-desc">${escapeHtml(t.description)}</div>
783
+ ${t.summary ? `<div class="task-summary">${escapeHtml(t.summary)}</div>` : ''}
784
+ </li>`;
785
+ }
786
+ html += '</ul>';
787
+ body.innerHTML = html;
788
+ }
789
+
790
+ function addTask() {
791
+ const input = document.getElementById('newTaskInput');
792
+ const priority = document.getElementById('newTaskPriority').value;
793
+ const desc = input.value.trim();
794
+ if (!desc) return;
795
+
796
+ sendCommand('add_task', { description: desc, priority });
797
+ input.value = '';
798
+ }
799
+
800
+ // ─── Log Rendering ──────────────────────────
801
+ function appendLog(entry) {
802
+ const body = document.getElementById('logBody');
803
+ const autoScroll = document.getElementById('autoScroll').checked;
804
+
805
+ const div = document.createElement('div');
806
+ div.className = 'log-line fade-in';
807
+
808
+ // Determine level class
809
+ let levelClass = entry.level;
810
+ if (levelClass.startsWith('TOOL:')) levelClass = 'TOOL';
811
+
812
+ div.innerHTML = `
813
+ <span class="log-time">${entry.elapsed}</span>
814
+ <span class="log-level log-level-${levelClass}">${entry.level}</span>
815
+ <span class="log-msg log-msg-${levelClass}">${escapeHtml(entry.message)}</span>
816
+ `;
817
+
818
+ body.appendChild(div);
819
+
820
+ // Limit log lines
821
+ while (body.children.length > maxLogLines) {
822
+ body.removeChild(body.firstChild);
823
+ }
824
+
825
+ if (autoScroll) {
826
+ body.scrollTop = body.scrollHeight;
827
+ }
828
+ }
829
+
830
+ function clearLogs() {
831
+ document.getElementById('logBody').innerHTML = '';
832
+ }
833
+
834
+ // ─── Controls ───────────────────────────────
835
+ function confirmStop() {
836
+ if (confirm('Are you sure you want to stop the agent? It will save a checkpoint first.')) {
837
+ sendCommand('stop');
838
+ }
839
+ }
840
+
841
+ // ─── Helpers ────────────────────────────────
842
+ function escapeHtml(text) {
843
+ const div = document.createElement('div');
844
+ div.textContent = text;
845
+ return div.innerHTML;
846
+ }
847
+
848
+ function formatNumber(n) {
849
+ if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
850
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
851
+ return n.toLocaleString();
852
+ }
853
+
854
+ // Render the cumulative-token history as a small filled line chart.
855
+ function renderSparkline(history) {
856
+ const svg = document.getElementById('sparkline');
857
+ if (!svg) return;
858
+ const data = (Array.isArray(history) ? history : []).slice(-60);
859
+ if (data.length < 2) { svg.innerHTML = ''; return; }
860
+
861
+ const W = 300, H = 34, pad = 3;
862
+ const min = Math.min(...data), max = Math.max(...data);
863
+ const range = (max - min) || 1;
864
+ const n = data.length;
865
+ const pts = data.map((v, i) => [
866
+ (i / (n - 1)) * W,
867
+ H - pad - ((v - min) / range) * (H - 2 * pad),
868
+ ]);
869
+ const line = pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');
870
+ const area = 'M0 ' + H + ' ' + line.replace(/^M/, 'L') + ' L' + W + ' ' + H + ' Z';
871
+ svg.innerHTML = '<path class="spark-area" d="' + area + '"/><path class="spark-line" d="' + line + '"/>';
872
+ }
873
+
874
+ function updateElapsed(startTime) {
875
+ if (!startTime) return;
876
+ const ms = Date.now() - startTime;
877
+ const s = Math.floor(ms / 1000) % 60;
878
+ const m = Math.floor(ms / 60000) % 60;
879
+ const h = Math.floor(ms / 3600000);
880
+ document.getElementById('statElapsed').textContent =
881
+ `${h.toString().padStart(2,'0')}:${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;
882
+ }
883
+
884
+ // Update elapsed every second
885
+ setInterval(() => {
886
+ if (state.stats) updateElapsed(state.stats.startTime);
887
+ }, 1000);
888
+
889
+ // ─── Init ───────────────────────────────────
890
+ connect();
891
+ </script>
892
+ </body>
893
+ </html>