@mobrienv/autoloop 0.2.1 → 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 (93) hide show
  1. package/README.md +100 -29
  2. package/dist/agent-map.js.map +1 -1
  3. package/dist/backend/acp-client.js +6 -1
  4. package/dist/backend/acp-client.js.map +1 -1
  5. package/dist/backend/kiro-bridge.d.ts +2 -0
  6. package/dist/backend/kiro-bridge.js +38 -4
  7. package/dist/backend/kiro-bridge.js.map +1 -1
  8. package/dist/backend/kiro-worker.js +20 -7
  9. package/dist/backend/kiro-worker.js.map +1 -1
  10. package/dist/backend/run-kiro.js +5 -1
  11. package/dist/backend/run-kiro.js.map +1 -1
  12. package/dist/chains/load.js +1 -1
  13. package/dist/chains/load.js.map +1 -1
  14. package/dist/cli/color.d.ts +6 -0
  15. package/dist/cli/color.js +40 -0
  16. package/dist/cli/color.js.map +1 -0
  17. package/dist/commands/guide.d.ts +1 -0
  18. package/dist/commands/guide.js +33 -0
  19. package/dist/commands/guide.js.map +1 -0
  20. package/dist/commands/inspect.js +45 -5
  21. package/dist/commands/inspect.js.map +1 -1
  22. package/dist/commands/run.js +6 -1
  23. package/dist/commands/run.js.map +1 -1
  24. package/dist/config.js +1 -1
  25. package/dist/config.js.map +1 -1
  26. package/dist/dashboard/app.js +1 -1
  27. package/dist/dashboard/app.js.map +1 -1
  28. package/dist/dashboard/routes/api.js +84 -1
  29. package/dist/dashboard/routes/api.js.map +1 -1
  30. package/dist/dashboard/routes/pages.d.ts +1 -1
  31. package/dist/dashboard/routes/pages.js +4 -2
  32. package/dist/dashboard/routes/pages.js.map +1 -1
  33. package/dist/dashboard/views/shell.d.ts +1 -1
  34. package/dist/dashboard/views/shell.js +366 -18
  35. package/dist/dashboard/views/shell.js.map +1 -1
  36. package/dist/events/types.d.ts +4 -2
  37. package/dist/harness/artifacts.d.ts +50 -0
  38. package/dist/harness/artifacts.js +333 -0
  39. package/dist/harness/artifacts.js.map +1 -0
  40. package/dist/harness/config-helpers.js +10 -10
  41. package/dist/harness/config-helpers.js.map +1 -1
  42. package/dist/harness/emit.js +2 -0
  43. package/dist/harness/emit.js.map +1 -1
  44. package/dist/harness/index.d.ts +7 -0
  45. package/dist/harness/index.js +78 -7
  46. package/dist/harness/index.js.map +1 -1
  47. package/dist/harness/journal-format.d.ts +25 -0
  48. package/dist/harness/journal-format.js +153 -0
  49. package/dist/harness/journal-format.js.map +1 -0
  50. package/dist/harness/journal.d.ts +1 -0
  51. package/dist/harness/journal.js +3 -0
  52. package/dist/harness/journal.js.map +1 -1
  53. package/dist/harness/metareview.js +1 -1
  54. package/dist/harness/metareview.js.map +1 -1
  55. package/dist/harness/prompt.d.ts +2 -0
  56. package/dist/harness/prompt.js +45 -6
  57. package/dist/harness/prompt.js.map +1 -1
  58. package/dist/harness/types.d.ts +2 -2
  59. package/dist/loops/health.d.ts +1 -0
  60. package/dist/loops/health.js +1 -1
  61. package/dist/loops/health.js.map +1 -1
  62. package/dist/main.js +5 -0
  63. package/dist/main.js.map +1 -1
  64. package/dist/usage.js +4 -3
  65. package/dist/usage.js.map +1 -1
  66. package/dist/worktree/clean.js +18 -3
  67. package/dist/worktree/clean.js.map +1 -1
  68. package/package.json +21 -4
  69. package/plugins/autoloop/.claude-plugin/plugin.json +9 -0
  70. package/plugins/autoloop/skills/autoloop/SKILL.md +225 -0
  71. package/presets/autodebug/README.md +50 -0
  72. package/presets/autodebug/autoloops.toml +18 -0
  73. package/presets/autodebug/harness.md +31 -0
  74. package/presets/autodebug/roles/fixer.md +58 -0
  75. package/presets/autodebug/roles/investigator.md +52 -0
  76. package/presets/autodebug/roles/strategist.md +45 -0
  77. package/presets/autodebug/roles/verifier.md +66 -0
  78. package/presets/autodebug/topology.toml +34 -0
  79. package/presets/autopreset/README.md +35 -0
  80. package/presets/autopreset/autoloops.toml +18 -0
  81. package/presets/autopreset/harness.md +38 -0
  82. package/presets/autopreset/roles/designer.md +33 -0
  83. package/presets/autopreset/roles/finalizer.md +23 -0
  84. package/presets/autopreset/roles/generator.md +34 -0
  85. package/presets/autopreset/roles/validator.md +31 -0
  86. package/presets/autopreset/topology.toml +32 -0
  87. package/presets/autoqa/README.md +43 -14
  88. package/presets/autoqa/autoloops.toml +2 -2
  89. package/presets/autoqa/harness.md +30 -8
  90. package/presets/autoqa/roles/executor.md +64 -11
  91. package/presets/autoqa/roles/inspector.md +68 -13
  92. package/presets/autoqa/roles/planner.md +43 -11
  93. 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,6 +356,133 @@ 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>
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>
485
+ </div>
305
486
  </div>
306
487
 
307
488
  <script defer src="/static/alpine.min.js"></script>
@@ -319,15 +500,51 @@ function dashboard() {
319
500
  lastUpdated: null,
320
501
  sectionOpen: { active: false, watching: false, stuck: false, failed: false, completed: false },
321
502
  sectionUserToggled: {},
503
+ sectionShowAll: {},
322
504
  showVerbose: false,
505
+ guideMessage: "",
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
+ },
323
532
 
324
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);
325
542
  return [
326
- { key: "active", label: "Active", items: this.runs.active },
327
- { key: "watching", label: "Watching", items: this.runs.watching },
328
- { key: "stuck", label: "Stuck", items: this.runs.stuck },
329
- { key: "failed", label: "Failed", items: this.runs.recentFailed },
330
- { 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 },
331
548
  ];
332
549
  },
333
550
 
@@ -340,7 +557,12 @@ function dashboard() {
340
557
  async fetchRuns() {
341
558
  try {
342
559
  const res = await fetch("/api/runs");
343
- 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;
344
566
  this.lastUpdated = new Date().toLocaleTimeString();
345
567
  const defaultOpen = ['active', 'watching', 'stuck'];
346
568
  for (const cat of this.categories) {
@@ -365,6 +587,9 @@ function dashboard() {
365
587
 
366
588
  async selectRun(runId) {
367
589
  this.selectedRun = runId;
590
+ this.artifactsData = null;
591
+ this.artifactDocContent = null;
592
+ this.artifactDocPath = '';
368
593
  try {
369
594
  const detailRes = await fetch("/api/runs/" + runId);
370
595
  if (detailRes.ok) this.selectedRunDetail = await detailRes.json();
@@ -380,6 +605,30 @@ function dashboard() {
380
605
  } catch (e) { /* ignore */ }
381
606
  },
382
607
 
608
+ async sendGuide() {
609
+ if (!this.guideMessage.trim() || !this.selectedRun) return;
610
+ try {
611
+ const res = await fetch("/api/runs/" + this.selectedRun + "/guide", {
612
+ method: "POST",
613
+ headers: { "Content-Type": "application/json" },
614
+ body: JSON.stringify({ message: this.guideMessage }),
615
+ });
616
+ if (res.ok) {
617
+ this.guideMessage = "";
618
+ this.guideFlash = "Sent";
619
+ setTimeout(() => { this.guideFlash = ""; }, 2000);
620
+ await this.fetchEvents(this.selectedRun);
621
+ } else {
622
+ const data = await res.json().catch(() => ({}));
623
+ this.guideFlash = data.error || "Failed";
624
+ setTimeout(() => { this.guideFlash = ""; }, 3000);
625
+ }
626
+ } catch (e) {
627
+ this.guideFlash = "Error";
628
+ setTimeout(() => { this.guideFlash = ""; }, 3000);
629
+ }
630
+ },
631
+
383
632
  async startLoop() {
384
633
  if (!this.newPrompt.trim()) return;
385
634
  await fetch("/api/runs", {
@@ -507,17 +756,14 @@ function dashboard() {
507
756
  },
508
757
 
509
758
  eventDisplayEntries(ev) {
510
- if (ev.topic === 'iteration.start' && ev.fields && typeof ev.fields === 'object') {
759
+ if (ev.fields && typeof ev.fields === 'object') {
511
760
  const entries = [];
512
761
  for (const [k, v] of Object.entries(ev)) {
513
762
  if (k === 'fields') continue;
514
763
  entries.push([k, v]);
515
764
  }
516
- const fieldKeys = ['recent_event', 'suggested_roles', 'allowed_events', 'backpressure', 'prompt'];
517
- for (const fk of fieldKeys) {
518
- if (ev.fields[fk] !== undefined) {
519
- entries.push([fk, ev.fields[fk]]);
520
- }
765
+ for (const [fk, fv] of Object.entries(ev.fields)) {
766
+ entries.push([fk, fv]);
521
767
  }
522
768
  return entries;
523
769
  }
@@ -631,6 +877,49 @@ function dashboard() {
631
877
  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
632
878
  },
633
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
+
634
923
  visibleRunEvents() {
635
924
  return this.selectedRunEvents.filter(ev => this.showVerbose || !this.isBackendEvent(ev));
636
925
  },
@@ -656,6 +945,65 @@ function dashboard() {
656
945
  return classes.join(' ');
657
946
  },
658
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
+
659
1007
  eventSummary(ev) {
660
1008
  const parts = [];
661
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAssBD,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,11 +2,13 @@ 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";
8
+ export type OperatorTopic = "operator.guidance" | "operator.guidance.consumed";
7
9
  export type WaveTopic = string;
8
10
  export type CoreSystemTopic = LoopTopic | IterationTopic | BackendTopic | ReviewTopic | "event.invalid";
9
- export type KnownTopic = CoreSystemTopic | CoordinationTopic | ChainTopic | WaveTopic | string;
11
+ export type KnownTopic = CoreSystemTopic | CoordinationTopic | ChainTopic | OperatorTopic | WaveTopic | string;
10
12
  export interface EventBase {
11
13
  run: string;
12
14
  topic: KnownTopic;
@@ -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;