@mobrienv/autoloop 0.3.0 → 0.4.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 (69) hide show
  1. package/README.md +100 -29
  2. package/dist/backend/acp-client.js +6 -1
  3. package/dist/backend/acp-client.js.map +1 -1
  4. package/dist/backend/kiro-bridge.d.ts +2 -0
  5. package/dist/backend/kiro-bridge.js +32 -2
  6. package/dist/backend/kiro-bridge.js.map +1 -1
  7. package/dist/backend/kiro-worker.js +7 -3
  8. package/dist/backend/kiro-worker.js.map +1 -1
  9. package/dist/cli/color.d.ts +6 -0
  10. package/dist/cli/color.js +40 -0
  11. package/dist/cli/color.js.map +1 -0
  12. package/dist/commands/inspect.js +45 -5
  13. package/dist/commands/inspect.js.map +1 -1
  14. package/dist/config.js +1 -1
  15. package/dist/config.js.map +1 -1
  16. package/dist/dashboard/app.js +1 -1
  17. package/dist/dashboard/app.js.map +1 -1
  18. package/dist/dashboard/routes/api.js +56 -0
  19. package/dist/dashboard/routes/api.js.map +1 -1
  20. package/dist/dashboard/routes/pages.d.ts +1 -1
  21. package/dist/dashboard/routes/pages.js +4 -2
  22. package/dist/dashboard/routes/pages.js.map +1 -1
  23. package/dist/dashboard/views/shell.d.ts +1 -1
  24. package/dist/dashboard/views/shell.js +339 -23
  25. package/dist/dashboard/views/shell.js.map +1 -1
  26. package/dist/events/types.d.ts +2 -1
  27. package/dist/harness/artifacts.d.ts +50 -0
  28. package/dist/harness/artifacts.js +333 -0
  29. package/dist/harness/artifacts.js.map +1 -0
  30. package/dist/harness/config-helpers.js +3 -5
  31. package/dist/harness/config-helpers.js.map +1 -1
  32. package/dist/harness/index.d.ts +7 -0
  33. package/dist/harness/index.js +78 -7
  34. package/dist/harness/index.js.map +1 -1
  35. package/dist/harness/journal-format.d.ts +25 -0
  36. package/dist/harness/journal-format.js +153 -0
  37. package/dist/harness/journal-format.js.map +1 -0
  38. package/dist/loops/health.d.ts +1 -0
  39. package/dist/loops/health.js +1 -1
  40. package/dist/loops/health.js.map +1 -1
  41. package/dist/usage.js +4 -3
  42. package/dist/usage.js.map +1 -1
  43. package/dist/worktree/clean.js +18 -3
  44. package/dist/worktree/clean.js.map +1 -1
  45. package/package.json +19 -4
  46. package/plugins/autoloop/.claude-plugin/plugin.json +1 -1
  47. package/presets/autodebug/README.md +50 -0
  48. package/presets/autodebug/autoloops.toml +18 -0
  49. package/presets/autodebug/harness.md +31 -0
  50. package/presets/autodebug/roles/fixer.md +58 -0
  51. package/presets/autodebug/roles/investigator.md +52 -0
  52. package/presets/autodebug/roles/strategist.md +45 -0
  53. package/presets/autodebug/roles/verifier.md +66 -0
  54. package/presets/autodebug/topology.toml +34 -0
  55. package/presets/autopreset/README.md +35 -0
  56. package/presets/autopreset/autoloops.toml +18 -0
  57. package/presets/autopreset/harness.md +38 -0
  58. package/presets/autopreset/roles/designer.md +33 -0
  59. package/presets/autopreset/roles/finalizer.md +23 -0
  60. package/presets/autopreset/roles/generator.md +34 -0
  61. package/presets/autopreset/roles/validator.md +31 -0
  62. package/presets/autopreset/topology.toml +32 -0
  63. package/presets/autoqa/README.md +43 -14
  64. package/presets/autoqa/autoloops.toml +2 -2
  65. package/presets/autoqa/harness.md +30 -8
  66. package/presets/autoqa/roles/executor.md +64 -11
  67. package/presets/autoqa/roles/inspector.md +68 -13
  68. package/presets/autoqa/roles/planner.md +43 -11
  69. package/presets/autoqa/roles/reporter.md +51 -8
@@ -1,16 +1,20 @@
1
- export function htmlShell() {
1
+ export function htmlShell(projectName) {
2
+ const displayName = projectName || "autoloop";
2
3
  return `<!DOCTYPE html>
3
4
  <html lang="en">
4
5
  <head>
5
6
  <meta charset="utf-8">
6
7
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <title>autoloop dashboard</title>
8
+ <title>autoloop / ${displayName}</title>
8
9
  <style>
9
10
  :root {
10
11
  --bg: #fff; --fg: #1a1a1a; --muted: #666; --border: #e0e0e0;
11
12
  --card-bg: #fafafa; --badge-bg: #eee;
12
13
  --active: #2563eb; --watching: #d97706; --stuck: #dc2626;
13
14
  --failed: #dc2626; --completed: #16a34a;
15
+ --cat-loop: #06b6d4; --cat-iteration: #d97706; --cat-backend: #666;
16
+ --cat-review: #c026d3; --cat-coordination: #2563eb; --cat-error: #dc2626;
17
+ --cat-operator: #ef4444; --cat-completion: #16a34a;
14
18
  }
15
19
  @media (prefers-color-scheme: dark) {
16
20
  :root {
@@ -109,12 +113,43 @@ header .updated { font-size: 0.75rem; color: var(--muted); font-family: monospac
109
113
  .merged-badge { color: var(--completed); font-weight: 600; }
110
114
  .merge-detail { margin-top: 0.5rem; padding: 0.5rem; background: color-mix(in srgb, var(--completed) 8%, transparent); border: 1px solid color-mix(in srgb, var(--completed) 25%, transparent); border-radius: 4px; }
111
115
  .merge-detail .field label { color: var(--completed); }
116
+
117
+ .detail-tabs { display: flex; gap: 0; border-bottom: 2px solid var(--border); margin-top: 0.75rem; margin-bottom: 0; }
118
+ .detail-tab { background: none; border: none; padding: 0.4rem 0.8rem; font-size: 0.8rem; font-family: monospace; cursor: pointer; color: var(--muted); border-bottom: 2px solid transparent; margin-bottom: -2px; }
119
+ .detail-tab:hover { color: var(--fg); }
120
+ .detail-tab.active { color: var(--active); border-bottom-color: var(--active); font-weight: 600; }
121
+
122
+ .topic-chip { display: inline-flex; align-items: center; gap: 0.2rem; padding: 0.15rem 0.4rem; border: 1px solid var(--border); border-left-width: 3px; border-radius: 4px; background: none; font-size: 0.7rem; font-family: monospace; cursor: pointer; color: var(--muted); }
123
+ .topic-chip.active { color: var(--fg); background: var(--card-bg); }
124
+ .topic-chip .badge { font-size: 0.6rem; padding: 0 0.25rem; }
125
+
126
+ .journal-search { width: 100%; padding: 0.35rem 0.5rem; border: 1px solid var(--border); border-radius: 4px; font-size: 0.8rem; font-family: inherit; background: var(--bg); color: var(--fg); }
127
+
128
+ .iter-group { border: 1px solid var(--border); border-radius: 4px; margin-bottom: 0.4rem; }
129
+ .iter-group > summary { cursor: pointer; padding: 0.3rem 0.5rem; font-size: 0.75rem; font-family: monospace; color: var(--muted); background: var(--card-bg); list-style: none; }
130
+ .iter-group > summary::-webkit-details-marker { display: none; }
131
+ .iter-group > summary::before { content: "\\25b6 "; font-size: 0.6rem; }
132
+ .iter-group[open] > summary::before { content: "\\25bc "; }
133
+
134
+ .artifacts-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; margin-bottom: 0.75rem; }
135
+ .artifact-stat-card { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 0.5rem; text-align: center; }
136
+ .artifact-stat-value { font-size: 1.2rem; font-weight: 700; font-family: monospace; }
137
+ .artifact-stat-label { font-size: 0.7rem; color: var(--muted); }
138
+ .artifacts-bar-chart { margin: 0.5rem 0; }
139
+ .bar-row { display: grid; grid-template-columns: 80px 1fr 40px; gap: 0.4rem; align-items: center; font-size: 0.75rem; margin-bottom: 0.2rem; }
140
+ .bar-label { text-align: right; color: var(--muted); font-family: monospace; }
141
+ .bar-track { height: 14px; background: var(--badge-bg); border-radius: 3px; overflow: hidden; }
142
+ .bar-fill { height: 100%; border-radius: 3px; min-width: 2px; }
143
+ .bar-count { font-family: monospace; text-align: right; }
144
+ .artifact-doc-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0; border-bottom: 1px solid var(--border); font-size: 0.8rem; }
145
+ .artifact-view-btn { padding: 0.15rem 0.5rem; border: 1px solid var(--border); border-radius: 3px; background: var(--bg); color: var(--fg); cursor: pointer; font-size: 0.7rem; }
146
+ .artifact-view-btn:hover { background: var(--badge-bg); }
112
147
  </style>
113
148
  </head>
114
149
  <body x-data="dashboard()" x-init="startPolling()">
115
150
 
116
151
  <header>
117
- <h1>autoloop dashboard</h1>
152
+ <h1>autoloop <span style="color:var(--muted);font-weight:400">/ ${displayName}</span></h1>
118
153
  <span class="updated" x-text="lastUpdated ? 'updated ' + lastUpdated : 'loading...'"></span>
119
154
  </header>
120
155
 
@@ -132,7 +167,7 @@ header .updated { font-size: 0.75rem; color: var(--muted); font-family: monospac
132
167
  <details class="section" :open="sectionOpen[cat.key]" @toggle="sectionUserToggled[cat.key] = true; sectionOpen[cat.key] = $el.open">
133
168
  <summary>
134
169
  <span x-text="cat.label"></span>
135
- <span class="badge" :data-status="cat.key" x-text="cat.items.length"></span>
170
+ <span class="badge" :data-status="cat.key" x-text="cat.total"></span>
136
171
  </summary>
137
172
  <ul class="run-list">
138
173
  <template x-for="run in cat.items" :key="run.run_id">
@@ -160,6 +195,12 @@ header .updated { font-size: 0.75rem; color: var(--muted); font-family: monospac
160
195
  </li>
161
196
  </template>
162
197
  <li class="empty" x-show="cat.items.length === 0">None</li>
198
+ <li x-show="cat.capped" style="text-align:center;padding:0.3rem;list-style:none">
199
+ <button @click="sectionShowAll[cat.key] = true" style="background:none;border:1px solid var(--border);border-radius:4px;padding:0.25rem 0.6rem;color:var(--muted);cursor:pointer;font-size:0.75rem" x-text="'Show all ' + cat.total + ' runs'"></button>
200
+ </li>
201
+ <li x-show="sectionShowAll[cat.key] && cat.total > 10" style="text-align:center;padding:0.3rem;list-style:none">
202
+ <button @click="sectionShowAll[cat.key] = false" style="background:none;border:1px solid var(--border);border-radius:4px;padding:0.25rem 0.6rem;color:var(--muted);cursor:pointer;font-size:0.75rem">Show fewer</button>
203
+ </li>
163
204
  </ul>
164
205
  </details>
165
206
  </template>
@@ -170,7 +211,7 @@ header .updated { font-size: 0.75rem; color: var(--muted); font-family: monospac
170
211
  <div>
171
212
  <div class="field"><label>Status: </label><span x-text="selectedRunDetail.status"></span></div>
172
213
  <div class="field"><label>Preset: </label><span x-text="selectedRunDetail.preset"></span></div>
173
- <div class="field"><label>Objective: </label><span x-text="selectedRunDetail.objective"></span></div>
214
+ <div class="field"><label>Objective: </label><div class="md-content" style="display:inline" x-html="renderMarkdown(selectedRunDetail.objective)"></div></div>
174
215
  <div class="field"><label>Iteration: </label><span x-text="selectedRunDetail.iteration + '/' + (selectedRunDetail.max_iterations || '?')"></span></div>
175
216
  <div class="field"><label>Workspace: </label><span :title="workspacePath(selectedRunDetail)" x-text="workspaceLabel(selectedRunDetail)"></span></div>
176
217
  <div class="field"><label>Created: </label><span :title="selectedRunDetail.created_at" x-text="timeAgo(selectedRunDetail.created_at) + ' ago'"></span></div>
@@ -187,7 +228,20 @@ header .updated { font-size: 0.75rem; color: var(--muted); font-family: monospac
187
228
  </template>
188
229
  </div>
189
230
  </template>
190
- <div class="events-list">
231
+ <div x-show="selectedRunDetail && selectedRunDetail.status === 'running'" style="display:grid;grid-template-columns:1fr auto;gap:0.4rem;align-items:center;margin:0.75rem 0;padding:0.6rem;border:1px solid var(--border);border-radius:6px;background:var(--bg)">
232
+ <input type="text" x-model="guideMessage" placeholder="Send guidance to this run..." @keydown.enter="sendGuide()"
233
+ style="width:100%;box-sizing:border-box;padding:0.4rem 0.6rem;border:1px solid var(--border);border-radius:4px;font-size:0.8rem;font-family:inherit;background:var(--bg);color:var(--fg)">
234
+ <div style="display:flex;gap:0.4rem;align-items:center">
235
+ <button @click="sendGuide()" style="padding:0.4rem 0.8rem;border:none;border-radius:4px;background:var(--active);color:#fff;cursor:pointer;font-size:0.8rem;white-space:nowrap">Send</button>
236
+ <span x-show="guideFlash" x-text="guideFlash" x-transition.opacity style="font-size:0.75rem;color:var(--completed)"></span>
237
+ </div>
238
+ </div>
239
+ <div class="detail-tabs" x-show="selectedRunDetail">
240
+ <button class="detail-tab" :class="{ active: detailTab === 'events' }" @click="detailTab = 'events'">Events</button>
241
+ <button class="detail-tab" :class="{ active: detailTab === 'journal' }" @click="detailTab = 'journal'">Journal</button>
242
+ <button class="detail-tab" :class="{ active: detailTab === 'artifacts' }" @click="detailTab = 'artifacts'">Artifacts</button>
243
+ </div>
244
+ <div x-show="detailTab === 'events'" class="events-list">
191
245
  <div style="display:flex;justify-content:space-between;align-items:center">
192
246
  <h3>Events</h3>
193
247
  <label style="font-size:0.75rem;cursor:pointer"><input type="checkbox" x-model="showVerbose" style="margin-right:0.3rem">Show backend events</label>
@@ -302,11 +356,132 @@ header .updated { font-size: 0.75rem; color: var(--muted); font-family: monospac
302
356
  </template>
303
357
  <p class="empty" x-show="visibleRunEvents().length === 0">No events</p>
304
358
  </div>
305
- <div x-show="selectedRunDetail && selectedRunDetail.status === 'active'" style="display:flex;gap:0.4rem;align-items:center;margin-top:0.75rem;padding-top:0.75rem;border-top:1px solid var(--border)">
306
- <input type="text" x-model="guideMessage" placeholder="Send guidance..." @keydown.enter="sendGuide()"
307
- style="flex:1;padding:0.4rem 0.6rem;border:1px solid var(--border);border-radius:4px;font-size:0.8rem;font-family:inherit;background:var(--bg);color:var(--fg)">
308
- <button @click="sendGuide()" style="padding:0.4rem 0.8rem;border:none;border-radius:4px;background:var(--active);color:#fff;cursor:pointer;font-size:0.8rem;white-space:nowrap">Send</button>
309
- <span x-show="guideFlash" x-text="guideFlash" x-transition.opacity style="font-size:0.75rem;color:var(--completed)"></span>
359
+ <div x-show="detailTab === 'journal'" class="events-list">
360
+ <div style="margin-bottom:0.5rem">
361
+ <input type="text" class="journal-search" x-model="journalSearch" placeholder="Search events...">
362
+ </div>
363
+ <div style="display:flex;flex-wrap:wrap;gap:0.3rem;margin-bottom:0.5rem">
364
+ <template x-for="cat in journalCategoryChips()" :key="cat.key">
365
+ <button class="topic-chip" :class="{ active: journalCategoryFilter[cat.key] }" :style="'border-color:' + cat.color" @click="journalCategoryFilter[cat.key] = !journalCategoryFilter[cat.key]">
366
+ <span x-text="cat.label"></span>
367
+ <span class="badge" x-text="cat.count"></span>
368
+ </button>
369
+ </template>
370
+ </div>
371
+ <template x-for="group in journalGroups()" :key="group.key">
372
+ <details class="iter-group" open>
373
+ <summary x-text="group.label + ' (' + group.events.length + ' events)'"></summary>
374
+ <template x-for="(ev, idx) in group.events" :key="idx">
375
+ <details :class="eventClasses(ev)">
376
+ <summary x-text="eventSummary(ev)"></summary>
377
+ <div style="padding:0.3rem">
378
+ <template x-for="[k,v] in eventDisplayEntries(ev)" :key="k">
379
+ <div class="event-field">
380
+ <strong x-text="k + ':'"></strong>
381
+ <template x-if="isRoutingBadgeField(k)">
382
+ <span x-html="renderRoutingValue(k, v)"></span>
383
+ </template>
384
+ <template x-if="!isRoutingBadgeField(k) && isMarkdownField(ev.topic, k)">
385
+ <template x-if="String(typeof v === 'object' ? JSON.stringify(v,null,2) : v).length <= 200">
386
+ <div class="md-content" x-html="renderMarkdown(typeof v === 'object' ? JSON.stringify(v,null,2) : String(v))"></div>
387
+ </template>
388
+ </template>
389
+ <template x-if="!isRoutingBadgeField(k) && isMarkdownField(ev.topic, k)">
390
+ <template x-if="String(typeof v === 'object' ? JSON.stringify(v,null,2) : v).length > 200">
391
+ <details>
392
+ <summary x-text="k + ' (' + String(typeof v === 'object' ? JSON.stringify(v,null,2) : v).length + ' chars)'"></summary>
393
+ <div class="md-content" style="font-size:0.7rem;margin-top:0.2rem" x-html="renderMarkdown(typeof v === 'object' ? JSON.stringify(v,null,2) : String(v))"></div>
394
+ </details>
395
+ </template>
396
+ </template>
397
+ <template x-if="!isRoutingBadgeField(k) && !isMarkdownField(ev.topic, k)">
398
+ <template x-if="String(typeof v === 'object' ? JSON.stringify(v,null,2) : v).length <= 200">
399
+ <pre x-text="typeof v === 'object' ? JSON.stringify(v,null,2) : String(v)"></pre>
400
+ </template>
401
+ </template>
402
+ <template x-if="!isRoutingBadgeField(k) && !isMarkdownField(ev.topic, k)">
403
+ <template x-if="String(typeof v === 'object' ? JSON.stringify(v,null,2) : v).length > 200">
404
+ <details>
405
+ <summary x-text="k + ' (' + String(typeof v === 'object' ? JSON.stringify(v,null,2) : v).length + ' chars)'"></summary>
406
+ <pre style="white-space:pre-wrap;font-size:0.7rem;margin-top:0.2rem" x-text="typeof v === 'object' ? JSON.stringify(v,null,2) : String(v)"></pre>
407
+ </details>
408
+ </template>
409
+ </template>
410
+ </div>
411
+ </template>
412
+ </div>
413
+ </details>
414
+ </template>
415
+ </details>
416
+ </template>
417
+ <p class="empty" x-show="journalGroups().length === 0">No matching events</p>
418
+ </div>
419
+ <div x-show="detailTab === 'artifacts'" class="events-list">
420
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.75rem">
421
+ <h3 style="font-size:0.95rem">Artifacts</h3>
422
+ <button @click="fetchArtifacts()" style="padding:0.2rem 0.6rem;border:1px solid var(--border);border-radius:4px;background:var(--bg);color:var(--fg);cursor:pointer;font-size:0.75rem">Refresh</button>
423
+ </div>
424
+
425
+ <div x-show="artifactsData" class="artifacts-grid">
426
+ <div class="artifact-stat-card">
427
+ <div class="artifact-stat-value" x-text="artifactsData?.events?.total || 0"></div>
428
+ <div class="artifact-stat-label">Events</div>
429
+ </div>
430
+ <div class="artifact-stat-card">
431
+ <div class="artifact-stat-value" x-text="artifactsData?.iterations || 0"></div>
432
+ <div class="artifact-stat-label">Iterations</div>
433
+ </div>
434
+ <div class="artifact-stat-card">
435
+ <div class="artifact-stat-value" x-text="(artifactsData?.artifacts?.memoryLearnings || 0) + (artifactsData?.artifacts?.memoryMeta || 0)"></div>
436
+ <div class="artifact-stat-label">Memory</div>
437
+ </div>
438
+ <div class="artifact-stat-card">
439
+ <div class="artifact-stat-value" x-text="artifactsData?.artifacts?.guidanceSent || 0"></div>
440
+ <div class="artifact-stat-label">Guidance</div>
441
+ </div>
442
+ </div>
443
+
444
+ <div x-show="artifactsData" class="artifacts-bar-chart">
445
+ <template x-for="cat in artifactsCategoryBars()" :key="cat.key">
446
+ <div class="bar-row">
447
+ <span class="bar-label" x-text="cat.label"></span>
448
+ <div class="bar-track">
449
+ <div class="bar-fill" :style="'width:' + cat.pct + '%;background:' + cat.color"></div>
450
+ </div>
451
+ <span class="bar-count" x-text="cat.count"></span>
452
+ </div>
453
+ </template>
454
+ </div>
455
+
456
+ <div x-show="artifactsData" style="font-size:0.8rem;font-family:monospace;margin:0.75rem 0">
457
+ <div>commits: <span x-text="artifactsData?.output?.commits || 0"></span></div>
458
+ <div>files changed: <span x-text="artifactsData?.output?.filesChanged < 0 ? '-' : artifactsData?.output?.filesChanged"></span></div>
459
+ <div>journal size: <span x-text="formatBytes(artifactsData?.output?.journalSizeBytes || 0)"></span></div>
460
+ <div>backpressure: <span x-text="artifactsData?.artifacts?.backpressure || 0"></span> rejected</div>
461
+ </div>
462
+
463
+ <div x-show="artifactsData && artifactsData.documents && artifactsData.documents.length > 0">
464
+ <h4 style="font-size:0.85rem;margin:0.75rem 0 0.5rem">Documents</h4>
465
+ <template x-for="doc in artifactsData?.documents || []" :key="doc.path">
466
+ <div class="artifact-doc-row">
467
+ <span x-text="doc.title || doc.path"></span>
468
+ <span class="badge" x-text="doc.kind"></span>
469
+ <template x-if="doc.missing">
470
+ <span style="color:var(--failed);font-size:0.7rem">(missing)</span>
471
+ </template>
472
+ <template x-if="!doc.missing">
473
+ <button class="artifact-view-btn" @click="viewArtifactDoc(doc.path)">View</button>
474
+ </template>
475
+ </div>
476
+ </template>
477
+ </div>
478
+
479
+ <div x-show="artifactDocContent !== null" style="margin-top:0.75rem">
480
+ <button @click="artifactDocContent = null; artifactDocPath = ''" style="padding:0.2rem 0.6rem;border:1px solid var(--border);border-radius:4px;background:var(--bg);color:var(--fg);cursor:pointer;font-size:0.75rem;margin-bottom:0.5rem">\u2190 Back</button>
481
+ <div class="md-content" x-html="renderMarkdown(artifactDocContent || '')"></div>
482
+ </div>
483
+
484
+ <p class="empty" x-show="!artifactsData">Click Refresh to load artifacts</p>
310
485
  </div>
311
486
  </div>
312
487
 
@@ -325,17 +500,51 @@ function dashboard() {
325
500
  lastUpdated: null,
326
501
  sectionOpen: { active: false, watching: false, stuck: false, failed: false, completed: false },
327
502
  sectionUserToggled: {},
503
+ sectionShowAll: {},
328
504
  showVerbose: false,
329
505
  guideMessage: "",
330
506
  guideFlash: "",
507
+ artifactsData: null,
508
+ artifactDocContent: null,
509
+ artifactDocPath: '',
510
+ detailTab: 'events',
511
+ journalSearch: '',
512
+ journalCategoryFilter: {
513
+ loop: true,
514
+ iteration: true,
515
+ backend: false,
516
+ review: true,
517
+ coordination: true,
518
+ error: true,
519
+ operator: true,
520
+ completion: true,
521
+ },
522
+ categoryColors: {
523
+ loop: '#06b6d4',
524
+ iteration: '#d97706',
525
+ backend: '#666',
526
+ review: '#c026d3',
527
+ coordination: '#2563eb',
528
+ error: '#dc2626',
529
+ operator: '#ef4444',
530
+ completion: '#16a34a',
531
+ },
331
532
 
332
533
  get categories() {
534
+ const CAP = 10;
535
+ const cap = (key, items) => {
536
+ const total = items.length;
537
+ const show = this.sectionShowAll[key] ? items : items.slice(0, CAP);
538
+ return { items: show, total, capped: total > CAP && !this.sectionShowAll[key] };
539
+ };
540
+ const fc = cap("failed", this.runs.recentFailed);
541
+ const cc = cap("completed", this.runs.recentCompleted);
333
542
  return [
334
- { key: "active", label: "Active", items: this.runs.active },
335
- { key: "watching", label: "Watching", items: this.runs.watching },
336
- { key: "stuck", label: "Stuck", items: this.runs.stuck },
337
- { key: "failed", label: "Failed", items: this.runs.recentFailed },
338
- { key: "completed", label: "Completed", items: this.runs.recentCompleted },
543
+ { key: "active", label: "Active", items: this.runs.active, total: this.runs.active.length, capped: false },
544
+ { key: "watching", label: "Watching", items: this.runs.watching, total: this.runs.watching.length, capped: false },
545
+ { key: "stuck", label: "Stuck", items: this.runs.stuck, total: this.runs.stuck.length, capped: false },
546
+ { key: "failed", label: "Failed", items: fc.items, total: fc.total, capped: fc.capped },
547
+ { key: "completed", label: "Completed", items: cc.items, total: cc.total, capped: cc.capped },
339
548
  ];
340
549
  },
341
550
 
@@ -348,7 +557,12 @@ function dashboard() {
348
557
  async fetchRuns() {
349
558
  try {
350
559
  const res = await fetch("/api/runs");
351
- this.runs = await res.json();
560
+ const data = await res.json();
561
+ const byRecent = (a, b) => new Date(b.updated_at || 0) - new Date(a.updated_at || 0);
562
+ for (const key of ['active', 'watching', 'stuck', 'recentFailed', 'recentCompleted']) {
563
+ if (data[key]) data[key].sort(byRecent);
564
+ }
565
+ this.runs = data;
352
566
  this.lastUpdated = new Date().toLocaleTimeString();
353
567
  const defaultOpen = ['active', 'watching', 'stuck'];
354
568
  for (const cat of this.categories) {
@@ -373,6 +587,9 @@ function dashboard() {
373
587
 
374
588
  async selectRun(runId) {
375
589
  this.selectedRun = runId;
590
+ this.artifactsData = null;
591
+ this.artifactDocContent = null;
592
+ this.artifactDocPath = '';
376
593
  try {
377
594
  const detailRes = await fetch("/api/runs/" + runId);
378
595
  if (detailRes.ok) this.selectedRunDetail = await detailRes.json();
@@ -539,17 +756,14 @@ function dashboard() {
539
756
  },
540
757
 
541
758
  eventDisplayEntries(ev) {
542
- if (ev.topic === 'iteration.start' && ev.fields && typeof ev.fields === 'object') {
759
+ if (ev.fields && typeof ev.fields === 'object') {
543
760
  const entries = [];
544
761
  for (const [k, v] of Object.entries(ev)) {
545
762
  if (k === 'fields') continue;
546
763
  entries.push([k, v]);
547
764
  }
548
- const fieldKeys = ['recent_event', 'suggested_roles', 'allowed_events', 'backpressure', 'prompt'];
549
- for (const fk of fieldKeys) {
550
- if (ev.fields[fk] !== undefined) {
551
- entries.push([fk, ev.fields[fk]]);
552
- }
765
+ for (const [fk, fv] of Object.entries(ev.fields)) {
766
+ entries.push([fk, fv]);
553
767
  }
554
768
  return entries;
555
769
  }
@@ -663,6 +877,49 @@ function dashboard() {
663
877
  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
664
878
  },
665
879
 
880
+ async fetchArtifacts() {
881
+ if (!this.selectedRun) return;
882
+ try {
883
+ const res = await fetch('/api/runs/' + this.selectedRun + '/artifacts');
884
+ if (res.ok) this.artifactsData = await res.json();
885
+ } catch (e) { /* ignore */ }
886
+ },
887
+
888
+ async viewArtifactDoc(path) {
889
+ if (!this.selectedRun) return;
890
+ try {
891
+ const res = await fetch('/api/runs/' + this.selectedRun + '/artifact?path=' + encodeURIComponent(path));
892
+ if (res.ok) {
893
+ this.artifactDocContent = await res.text();
894
+ this.artifactDocPath = path;
895
+ }
896
+ } catch (e) { /* ignore */ }
897
+ },
898
+
899
+ artifactsCategoryBars() {
900
+ if (!this.artifactsData) return [];
901
+ const ev = this.artifactsData.events;
902
+ const max = Math.max(ev.loop, ev.iteration, ev.backend, ev.review, ev.coordination, ev.operator, ev.routing, ev.errors, 1);
903
+ return [
904
+ { key: 'loop', label: 'loop', count: ev.loop, pct: (ev.loop / max) * 100, color: 'var(--cat-loop)' },
905
+ { key: 'iteration', label: 'iteration', count: ev.iteration, pct: (ev.iteration / max) * 100, color: 'var(--cat-iteration)' },
906
+ { key: 'backend', label: 'backend', count: ev.backend, pct: (ev.backend / max) * 100, color: 'var(--cat-backend)' },
907
+ { key: 'review', label: 'review', count: ev.review, pct: (ev.review / max) * 100, color: 'var(--cat-review)' },
908
+ { key: 'coordination', label: 'coord', count: ev.coordination, pct: (ev.coordination / max) * 100, color: 'var(--cat-coordination)' },
909
+ { key: 'operator', label: 'operator', count: ev.operator, pct: (ev.operator / max) * 100, color: 'var(--cat-operator)' },
910
+ { key: 'routing', label: 'routing', count: ev.routing, pct: (ev.routing / max) * 100, color: 'var(--cat-routing, #6366f1)' },
911
+ { key: 'errors', label: 'errors', count: ev.errors, pct: (ev.errors / max) * 100, color: 'var(--cat-error)' },
912
+ ];
913
+ },
914
+
915
+ formatBytes(bytes) {
916
+ if (!bytes || bytes === 0) return '0 B';
917
+ if (bytes < 1024) return bytes + ' B';
918
+ const kb = bytes / 1024;
919
+ if (kb < 1024) return kb.toFixed(1) + ' KB';
920
+ return (kb / 1024).toFixed(1) + ' MB';
921
+ },
922
+
666
923
  visibleRunEvents() {
667
924
  return this.selectedRunEvents.filter(ev => this.showVerbose || !this.isBackendEvent(ev));
668
925
  },
@@ -688,6 +945,65 @@ function dashboard() {
688
945
  return classes.join(' ');
689
946
  },
690
947
 
948
+ topicCategoryKey(ev) {
949
+ const t = String(ev?.topic || '');
950
+ if (['event.invalid','wave.timeout','wave.failed','loop.stop'].includes(t)) return 'error';
951
+ if (t === 'task.complete' || t === 'loop.complete') return 'completion';
952
+ if (t.startsWith('loop.')) return 'loop';
953
+ if (t.startsWith('iteration.')) return 'iteration';
954
+ if (t.startsWith('backend.')) return 'backend';
955
+ if (t.startsWith('review.')) return 'review';
956
+ if (t.startsWith('operator.')) return 'operator';
957
+ return 'coordination';
958
+ },
959
+
960
+ journalCategoryChips() {
961
+ const counts = {};
962
+ for (const k of Object.keys(this.journalCategoryFilter)) counts[k] = 0;
963
+ for (const ev of this.selectedRunEvents) {
964
+ const cat = this.topicCategoryKey(ev);
965
+ if (cat in counts) counts[cat]++;
966
+ }
967
+ const labels = { loop: 'Loop', iteration: 'Iteration', backend: 'Backend', review: 'Review', coordination: 'Coordination', error: 'Error', operator: 'Operator', completion: 'Completion' };
968
+ return Object.keys(this.journalCategoryFilter).map(key => ({
969
+ key,
970
+ label: labels[key] || key,
971
+ color: this.categoryColors[key] || '#666',
972
+ count: counts[key] || 0,
973
+ enabled: this.journalCategoryFilter[key],
974
+ }));
975
+ },
976
+
977
+ filteredJournalEvents() {
978
+ const search = (this.journalSearch || '').toLowerCase();
979
+ return this.selectedRunEvents.filter(ev => {
980
+ const cat = this.topicCategoryKey(ev);
981
+ if (!this.journalCategoryFilter[cat]) return false;
982
+ if (search) {
983
+ const text = JSON.stringify(ev).toLowerCase();
984
+ if (!text.includes(search)) return false;
985
+ }
986
+ return true;
987
+ });
988
+ },
989
+
990
+ journalGroups() {
991
+ const events = this.filteredJournalEvents();
992
+ const groups = {};
993
+ const order = [];
994
+ for (const ev of events) {
995
+ const iter = ev.iteration;
996
+ const key = iter ? 'iter-' + iter : 'system';
997
+ if (!groups[key]) {
998
+ const label = iter ? '\\u2500\\u2500 iter ' + iter + ' \\u2500\\u2500' : '\\u2500\\u2500 system \\u2500\\u2500';
999
+ groups[key] = { key, label, events: [] };
1000
+ order.push(key);
1001
+ }
1002
+ groups[key].events.push(ev);
1003
+ }
1004
+ return order.map(k => groups[k]);
1005
+ },
1006
+
691
1007
  eventSummary(ev) {
692
1008
  const parts = [];
693
1009
  if (ev.iteration) parts.push('[iter ' + ev.iteration + ']');
@@ -1 +1 @@
1
- {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../../src/dashboard/views/shell.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS;IACvB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAsuBD,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../../src/dashboard/views/shell.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS,CAAC,WAAoB;IAC5C,MAAM,WAAW,GAAG,WAAW,IAAI,UAAU,CAAC;IAC9C,OAAO;;;;;oBAKW,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAgJqC,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA44BvE,CAAC;AACT,CAAC"}
@@ -2,7 +2,8 @@ export type LoopTopic = "loop.start" | "loop.complete" | "loop.stop";
2
2
  export type IterationTopic = "iteration.start" | "iteration.finish";
3
3
  export type BackendTopic = "backend.start" | "backend.finish";
4
4
  export type ReviewTopic = "review.start" | "review.finish";
5
- export type CoordinationTopic = "issue.discovered" | "issue.resolved" | "slice.started" | "slice.verified" | "slice.committed" | "context.archived" | "chain.spawn";
5
+ export type ArtifactTopic = "artifact.created";
6
+ export type CoordinationTopic = "issue.discovered" | "issue.resolved" | "slice.started" | "slice.verified" | "slice.committed" | "context.archived" | "chain.spawn" | ArtifactTopic;
6
7
  export type ChainTopic = "chain.start" | "chain.complete";
7
8
  export type OperatorTopic = "operator.guidance" | "operator.guidance.consumed";
8
9
  export type WaveTopic = string;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Run artifacts collection and formatting.
3
+ * Aggregates event counts, memory stats, guidance stats, commits,
4
+ * and document artifacts from journal lines.
5
+ */
6
+ export interface DocumentArtifact {
7
+ path: string;
8
+ kind: string;
9
+ title: string;
10
+ missing: boolean;
11
+ }
12
+ export interface RunArtifacts {
13
+ runId: string;
14
+ preset: string;
15
+ status: string;
16
+ durationMs: number;
17
+ iterations: number;
18
+ events: {
19
+ total: number;
20
+ loop: number;
21
+ iteration: number;
22
+ backend: number;
23
+ review: number;
24
+ coordination: number;
25
+ operator: number;
26
+ routing: number;
27
+ errors: number;
28
+ };
29
+ artifacts: {
30
+ scratchpadEntries: number;
31
+ memoryLearnings: number;
32
+ memoryMeta: number;
33
+ memoryPreferences: number;
34
+ guidanceSent: number;
35
+ guidanceConsumed: number;
36
+ backpressure: number;
37
+ };
38
+ output: {
39
+ commits: number;
40
+ filesChanged: number;
41
+ journalSizeBytes: number;
42
+ };
43
+ documents: DocumentArtifact[];
44
+ }
45
+ /**
46
+ * Collect aggregated artifacts from journal lines and filesystem.
47
+ */
48
+ export declare function collectArtifacts(lines: string[], projectDir: string, workDir?: string): RunArtifacts;
49
+ /** Format RunArtifacts into a human-readable terminal string. */
50
+ export declare function formatArtifacts(a: RunArtifacts): string;