opencastle 0.31.7 → 0.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/README.md +4 -1
  2. package/bin/cli.mjs +15 -0
  3. package/dist/cli/agents.d.ts.map +1 -1
  4. package/dist/cli/agents.js +19 -5
  5. package/dist/cli/agents.js.map +1 -1
  6. package/dist/cli/artifacts-cli.d.ts +3 -0
  7. package/dist/cli/artifacts-cli.d.ts.map +1 -0
  8. package/dist/cli/artifacts-cli.js +36 -0
  9. package/dist/cli/artifacts-cli.js.map +1 -0
  10. package/dist/cli/baselines.d.ts.map +1 -1
  11. package/dist/cli/baselines.js +11 -0
  12. package/dist/cli/baselines.js.map +1 -1
  13. package/dist/cli/convoy/artifacts.d.ts +25 -0
  14. package/dist/cli/convoy/artifacts.d.ts.map +1 -0
  15. package/dist/cli/convoy/artifacts.js +129 -0
  16. package/dist/cli/convoy/artifacts.js.map +1 -0
  17. package/dist/cli/convoy/artifacts.test.d.ts +2 -0
  18. package/dist/cli/convoy/artifacts.test.d.ts.map +1 -0
  19. package/dist/cli/convoy/artifacts.test.js +169 -0
  20. package/dist/cli/convoy/artifacts.test.js.map +1 -0
  21. package/dist/cli/convoy/compaction.d.ts +23 -0
  22. package/dist/cli/convoy/compaction.d.ts.map +1 -0
  23. package/dist/cli/convoy/compaction.js +117 -0
  24. package/dist/cli/convoy/compaction.js.map +1 -0
  25. package/dist/cli/convoy/compaction.test.d.ts +2 -0
  26. package/dist/cli/convoy/compaction.test.d.ts.map +1 -0
  27. package/dist/cli/convoy/compaction.test.js +205 -0
  28. package/dist/cli/convoy/compaction.test.js.map +1 -0
  29. package/dist/cli/convoy/contracts.d.ts +22 -0
  30. package/dist/cli/convoy/contracts.d.ts.map +1 -0
  31. package/dist/cli/convoy/contracts.js +254 -0
  32. package/dist/cli/convoy/contracts.js.map +1 -0
  33. package/dist/cli/convoy/contracts.test.d.ts +2 -0
  34. package/dist/cli/convoy/contracts.test.d.ts.map +1 -0
  35. package/dist/cli/convoy/contracts.test.js +239 -0
  36. package/dist/cli/convoy/contracts.test.js.map +1 -0
  37. package/dist/cli/convoy/dag-analysis.d.ts +40 -0
  38. package/dist/cli/convoy/dag-analysis.d.ts.map +1 -0
  39. package/dist/cli/convoy/dag-analysis.js +282 -0
  40. package/dist/cli/convoy/dag-analysis.js.map +1 -0
  41. package/dist/cli/convoy/dag-analysis.test.d.ts +2 -0
  42. package/dist/cli/convoy/dag-analysis.test.d.ts.map +1 -0
  43. package/dist/cli/convoy/dag-analysis.test.js +289 -0
  44. package/dist/cli/convoy/dag-analysis.test.js.map +1 -0
  45. package/dist/cli/convoy/effort-scaling.d.ts +20 -0
  46. package/dist/cli/convoy/effort-scaling.d.ts.map +1 -0
  47. package/dist/cli/convoy/effort-scaling.js +82 -0
  48. package/dist/cli/convoy/effort-scaling.js.map +1 -0
  49. package/dist/cli/convoy/effort-scaling.test.d.ts +2 -0
  50. package/dist/cli/convoy/effort-scaling.test.d.ts.map +1 -0
  51. package/dist/cli/convoy/effort-scaling.test.js +120 -0
  52. package/dist/cli/convoy/effort-scaling.test.js.map +1 -0
  53. package/dist/cli/convoy/engine.d.ts.map +1 -1
  54. package/dist/cli/convoy/engine.js +280 -6
  55. package/dist/cli/convoy/engine.js.map +1 -1
  56. package/dist/cli/convoy/engine.test.js +155 -18
  57. package/dist/cli/convoy/engine.test.js.map +1 -1
  58. package/dist/cli/convoy/event-schemas.d.ts.map +1 -1
  59. package/dist/cli/convoy/event-schemas.js +55 -0
  60. package/dist/cli/convoy/event-schemas.js.map +1 -1
  61. package/dist/cli/convoy/isolation.d.ts +27 -0
  62. package/dist/cli/convoy/isolation.d.ts.map +1 -0
  63. package/dist/cli/convoy/isolation.js +120 -0
  64. package/dist/cli/convoy/isolation.js.map +1 -0
  65. package/dist/cli/convoy/isolation.test.d.ts +2 -0
  66. package/dist/cli/convoy/isolation.test.d.ts.map +1 -0
  67. package/dist/cli/convoy/isolation.test.js +105 -0
  68. package/dist/cli/convoy/isolation.test.js.map +1 -0
  69. package/dist/cli/convoy/review-stages.d.ts +9 -0
  70. package/dist/cli/convoy/review-stages.d.ts.map +1 -0
  71. package/dist/cli/convoy/review-stages.js +134 -0
  72. package/dist/cli/convoy/review-stages.js.map +1 -0
  73. package/dist/cli/convoy/review-stages.test.d.ts +2 -0
  74. package/dist/cli/convoy/review-stages.test.d.ts.map +1 -0
  75. package/dist/cli/convoy/review-stages.test.js +197 -0
  76. package/dist/cli/convoy/review-stages.test.js.map +1 -0
  77. package/dist/cli/convoy/skill-refinement.d.ts +39 -0
  78. package/dist/cli/convoy/skill-refinement.d.ts.map +1 -0
  79. package/dist/cli/convoy/skill-refinement.js +239 -0
  80. package/dist/cli/convoy/skill-refinement.js.map +1 -0
  81. package/dist/cli/convoy/skill-refinement.test.d.ts +2 -0
  82. package/dist/cli/convoy/skill-refinement.test.d.ts.map +1 -0
  83. package/dist/cli/convoy/skill-refinement.test.js +230 -0
  84. package/dist/cli/convoy/skill-refinement.test.js.map +1 -0
  85. package/dist/cli/convoy/spec-builder.d.ts +1 -0
  86. package/dist/cli/convoy/spec-builder.d.ts.map +1 -1
  87. package/dist/cli/convoy/spec-builder.js +11 -0
  88. package/dist/cli/convoy/spec-builder.js.map +1 -1
  89. package/dist/cli/convoy/spec-builder.test.js +54 -0
  90. package/dist/cli/convoy/spec-builder.test.js.map +1 -1
  91. package/dist/cli/convoy/store.d.ts +3 -2
  92. package/dist/cli/convoy/store.d.ts.map +1 -1
  93. package/dist/cli/convoy/store.js +20 -2
  94. package/dist/cli/convoy/store.js.map +1 -1
  95. package/dist/cli/convoy/store.test.js +15 -15
  96. package/dist/cli/convoy/store.test.js.map +1 -1
  97. package/dist/cli/convoy/tdd-gate.d.ts +15 -0
  98. package/dist/cli/convoy/tdd-gate.d.ts.map +1 -0
  99. package/dist/cli/convoy/tdd-gate.js +119 -0
  100. package/dist/cli/convoy/tdd-gate.js.map +1 -0
  101. package/dist/cli/convoy/tdd-gate.test.d.ts +2 -0
  102. package/dist/cli/convoy/tdd-gate.test.d.ts.map +1 -0
  103. package/dist/cli/convoy/tdd-gate.test.js +227 -0
  104. package/dist/cli/convoy/tdd-gate.test.js.map +1 -0
  105. package/dist/cli/convoy/types.d.ts +91 -0
  106. package/dist/cli/convoy/types.d.ts.map +1 -1
  107. package/dist/cli/convoy/types.js +8 -0
  108. package/dist/cli/convoy/types.js.map +1 -1
  109. package/dist/cli/insights.d.ts +3 -0
  110. package/dist/cli/insights.d.ts.map +1 -0
  111. package/dist/cli/insights.js +94 -0
  112. package/dist/cli/insights.js.map +1 -0
  113. package/dist/cli/lesson.d.ts.map +1 -1
  114. package/dist/cli/lesson.js +7 -0
  115. package/dist/cli/lesson.js.map +1 -1
  116. package/dist/cli/log.d.ts.map +1 -1
  117. package/dist/cli/log.js +7 -0
  118. package/dist/cli/log.js.map +1 -1
  119. package/dist/cli/package-config.d.ts +12 -0
  120. package/dist/cli/package-config.d.ts.map +1 -0
  121. package/dist/cli/package-config.js +37 -0
  122. package/dist/cli/package-config.js.map +1 -0
  123. package/dist/cli/package.d.ts +23 -0
  124. package/dist/cli/package.d.ts.map +1 -0
  125. package/dist/cli/package.js +285 -0
  126. package/dist/cli/package.js.map +1 -0
  127. package/dist/cli/package.test.d.ts +2 -0
  128. package/dist/cli/package.test.d.ts.map +1 -0
  129. package/dist/cli/package.test.js +236 -0
  130. package/dist/cli/package.test.js.map +1 -0
  131. package/dist/cli/pipeline.d.ts +6 -0
  132. package/dist/cli/pipeline.d.ts.map +1 -1
  133. package/dist/cli/pipeline.js +15 -2
  134. package/dist/cli/pipeline.js.map +1 -1
  135. package/dist/cli/run/schema.d.ts.map +1 -1
  136. package/dist/cli/run/schema.js +32 -0
  137. package/dist/cli/run/schema.js.map +1 -1
  138. package/dist/cli/run/schema.test.js +51 -0
  139. package/dist/cli/run/schema.test.js.map +1 -1
  140. package/dist/cli/skills.d.ts +3 -0
  141. package/dist/cli/skills.d.ts.map +1 -0
  142. package/dist/cli/skills.js +107 -0
  143. package/dist/cli/skills.js.map +1 -0
  144. package/dist/cli/types.d.ts +4 -1
  145. package/dist/cli/types.d.ts.map +1 -1
  146. package/dist/dashboard/scripts/etl.d.ts.map +1 -1
  147. package/dist/dashboard/scripts/etl.js +44 -11
  148. package/dist/dashboard/scripts/etl.js.map +1 -1
  149. package/package.json +2 -1
  150. package/src/cli/agents.ts +20 -5
  151. package/src/cli/artifacts-cli.ts +41 -0
  152. package/src/cli/baselines.ts +12 -0
  153. package/src/cli/convoy/artifacts.test.ts +201 -0
  154. package/src/cli/convoy/artifacts.ts +186 -0
  155. package/src/cli/convoy/compaction.test.ts +245 -0
  156. package/src/cli/convoy/compaction.ts +164 -0
  157. package/src/cli/convoy/contracts.test.ts +279 -0
  158. package/src/cli/convoy/contracts.ts +280 -0
  159. package/src/cli/convoy/dag-analysis.test.ts +349 -0
  160. package/src/cli/convoy/dag-analysis.ts +371 -0
  161. package/src/cli/convoy/effort-scaling.test.ts +140 -0
  162. package/src/cli/convoy/effort-scaling.ts +90 -0
  163. package/src/cli/convoy/engine.test.ts +175 -18
  164. package/src/cli/convoy/engine.ts +301 -7
  165. package/src/cli/convoy/event-schemas.ts +55 -0
  166. package/src/cli/convoy/isolation.test.ts +137 -0
  167. package/src/cli/convoy/isolation.ts +165 -0
  168. package/src/cli/convoy/review-stages.test.ts +235 -0
  169. package/src/cli/convoy/review-stages.ts +166 -0
  170. package/src/cli/convoy/skill-refinement.test.ts +277 -0
  171. package/src/cli/convoy/skill-refinement.ts +306 -0
  172. package/src/cli/convoy/spec-builder.test.ts +61 -0
  173. package/src/cli/convoy/spec-builder.ts +9 -0
  174. package/src/cli/convoy/store.test.ts +15 -15
  175. package/src/cli/convoy/store.ts +26 -4
  176. package/src/cli/convoy/tdd-gate.test.ts +281 -0
  177. package/src/cli/convoy/tdd-gate.ts +154 -0
  178. package/src/cli/convoy/types.ts +51 -0
  179. package/src/cli/insights.ts +99 -0
  180. package/src/cli/lesson.ts +8 -0
  181. package/src/cli/log.ts +8 -0
  182. package/src/cli/package-config.ts +48 -0
  183. package/src/cli/package.test.ts +276 -0
  184. package/src/cli/package.ts +329 -0
  185. package/src/cli/pipeline.ts +21 -2
  186. package/src/cli/run/schema.test.ts +58 -0
  187. package/src/cli/run/schema.ts +33 -0
  188. package/src/cli/skills.ts +121 -0
  189. package/src/cli/types.ts +4 -1
  190. package/src/dashboard/dist/_astro/index.D6quLrA6.css +1 -0
  191. package/src/dashboard/dist/data/convoy-list.json +21 -7
  192. package/src/dashboard/dist/data/convoys/demo-api-v2.json +3 -3
  193. package/src/dashboard/dist/data/convoys/demo-auth-revamp.json +5 -5
  194. package/src/dashboard/dist/data/convoys/demo-convoy-1.json +2 -2
  195. package/src/dashboard/dist/data/convoys/demo-convoy-2.json +1 -1
  196. package/src/dashboard/dist/data/convoys/demo-dashboard-ui.json +7 -7
  197. package/src/dashboard/dist/data/convoys/demo-data-pipeline.json +3 -3
  198. package/src/dashboard/dist/data/convoys/demo-deploy-ci.json +2 -2
  199. package/src/dashboard/dist/data/convoys/demo-docs-update.json +2 -2
  200. package/src/dashboard/dist/data/convoys/demo-perf-opt.json +4 -4
  201. package/src/dashboard/dist/index.html +306 -33
  202. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  203. package/src/dashboard/public/data/convoy-list.json +21 -7
  204. package/src/dashboard/public/data/convoys/demo-api-v2.json +3 -3
  205. package/src/dashboard/public/data/convoys/demo-auth-revamp.json +5 -5
  206. package/src/dashboard/public/data/convoys/demo-convoy-1.json +2 -2
  207. package/src/dashboard/public/data/convoys/demo-convoy-2.json +1 -1
  208. package/src/dashboard/public/data/convoys/demo-dashboard-ui.json +7 -7
  209. package/src/dashboard/public/data/convoys/demo-data-pipeline.json +3 -3
  210. package/src/dashboard/public/data/convoys/demo-deploy-ci.json +2 -2
  211. package/src/dashboard/public/data/convoys/demo-docs-update.json +2 -2
  212. package/src/dashboard/public/data/convoys/demo-perf-opt.json +4 -4
  213. package/src/dashboard/scripts/etl.test.ts +14 -0
  214. package/src/dashboard/scripts/etl.ts +48 -16
  215. package/src/dashboard/scripts/generate-demo-db.ts +18 -10
  216. package/src/dashboard/src/pages/index.astro +348 -45
  217. package/src/dashboard/src/styles/dashboard.css +56 -0
  218. package/src/orchestrator/prompts/assess-complexity.prompt.md +13 -0
  219. package/src/orchestrator/prompts/generate-convoy.prompt.md +19 -0
  220. package/src/dashboard/dist/_astro/index.BRDFmNzR.css +0 -1
@@ -39,6 +39,8 @@ try {
39
39
  <ul class="dash-sidebar__list">
40
40
  <li><a class="dash-sidebar__link dash-sidebar__link--active" href="#overall-section" data-section="overall-section" data-view="home" aria-label="Overview section">Overview</a></li>
41
41
  <li><a class="dash-sidebar__link" href="#convoy-list-section" data-section="convoy-list-section" data-view="home" aria-label="Convoys section">Convoys</a></li>
42
+ <li><a class="dash-sidebar__link" href="#activity-timeline-section" data-section="activity-timeline-section" data-view="home" aria-label="Activity Timeline section">Activity</a></li>
43
+ <li><a class="dash-sidebar__link" href="#sessions-section" data-section="sessions-section" data-view="home" aria-label="Sessions section">Sessions</a></li>
42
44
  <li><a class="dash-sidebar__link" href="#tasks-section" data-section="tasks-section" data-view="detail" aria-label="Tasks section">Tasks</a></li>
43
45
  <li><a class="dash-sidebar__link" href="#pipeline-section" data-section="pipeline-section" data-view="detail" aria-label="Pipeline section">Pipeline</a></li>
44
46
  <li><a class="dash-sidebar__link" href="#agent-section" data-section="agent-section" data-view="detail" aria-label="Agents section">Agents</a></li>
@@ -50,8 +52,8 @@ try {
50
52
  <li><a class="dash-sidebar__link" href="#outputs-section" data-section="outputs-section" data-view="detail" aria-label="Outputs section">Outputs</a></li>
51
53
  <li><a class="dash-sidebar__link" href="#execution-section" data-section="execution-section" data-view="detail" aria-label="Execution Log section">Execution Log</a></li>
52
54
  <li><a class="dash-sidebar__link" href="#event-timeline-section" data-section="event-timeline-section" data-view="detail" aria-label="Event Log section">Event Log</a></li>
53
- <li><a class="dash-sidebar__link" href="#panel-section" data-section="panel-section" data-view="detail" aria-label="Panel Reviews section">Panel Reviews</a></li>
54
55
  <li><a class="dash-sidebar__link" href="#reviews-section" data-section="reviews-section" data-view="detail" aria-label="Fast Reviews section">Fast Reviews</a></li>
56
+ <li><a class="dash-sidebar__link" href="#panel-section" data-section="panel-section" data-view="detail" aria-label="Panel Reviews section">Panel Reviews</a></li>
55
57
  </ul>
56
58
  </nav>
57
59
 
@@ -93,9 +95,26 @@ try {
93
95
  <span class="overall-kpi__label">Total Cost</span>
94
96
  <span class="overall-kpi__value">&mdash;</span>
95
97
  </div>
98
+ <div class="overall-kpi" id="overall-total-tasks">
99
+ <span class="overall-kpi__label">Total Tasks</span>
100
+ <span class="overall-kpi__value">&mdash;</span>
101
+ </div>
102
+ <div class="overall-kpi" id="overall-total-retries">
103
+ <span class="overall-kpi__label">Total Retries</span>
104
+ <span class="overall-kpi__value">&mdash;</span>
105
+ </div>
96
106
  </div>
97
107
  </section>
98
108
 
109
+ <!-- Activity Timeline -->
110
+ <section class="chart-card" id="activity-timeline-section" data-nav-section>
111
+ <div class="chart-card__header">
112
+ <h2 class="chart-card__title">Activity Timeline</h2>
113
+ <p class="chart-card__desc">Convoy runs over time</p>
114
+ </div>
115
+ <div class="chart-card__body" id="activity-timeline-chart"></div>
116
+ </section>
117
+
99
118
  <!-- Convoy List Section -->
100
119
  <section class="convoy-list-section" id="convoy-list-section">
101
120
  <div class="convoy-list-section__header">
@@ -136,12 +155,23 @@ try {
136
155
  <p class="convoy-list-empty__text">No convoys match your filters</p>
137
156
  </div>
138
157
  </section>
158
+
159
+ <!-- Recent Sessions -->
160
+ <section class="chart-card" id="sessions-section" data-nav-section>
161
+ <div class="chart-card__header">
162
+ <h2 class="chart-card__title">Recent Sessions</h2>
163
+ <p class="chart-card__desc">Latest agent sessions from observability log</p>
164
+ </div>
165
+ <div class="chart-card__body chart-card__body--table" id="sessions-table"></div>
166
+ </section>
139
167
  </div><!-- .view-home -->
140
168
 
141
169
  <div class="view-convoy-detail" id="view-convoy-detail" data-view-hidden>
142
170
  <section class="convoy-detail-hero" id="convoy-detail-hero">
143
- <div class="convoy-detail-hero__title" id="detail-hero-title"></div>
144
- <span class="convoy-detail-hero__status" id="detail-hero-status"></span>
171
+ <div class="convoy-detail-hero__top">
172
+ <div class="convoy-detail-hero__title" id="detail-hero-title"></div>
173
+ <span class="convoy-detail-hero__status" id="detail-hero-status"></span>
174
+ </div>
145
175
  <div class="convoy-detail-hero__meta" id="detail-hero-meta"></div>
146
176
  </section>
147
177
 
@@ -163,7 +193,7 @@ try {
163
193
  <section class="chart-card" id="pipeline-section" data-nav-section>
164
194
  <div class="chart-card__header">
165
195
  <h2 class="chart-card__title">Task Pipeline</h2>
166
- <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Task flow across execution phases." data-tooltip="Task flow across execution phases."><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
196
+ <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Visualizes how tasks progress through sequential execution phases." data-tooltip="Visualizes how tasks progress through sequential execution phases — from initial setup through integration, validation, and final quality gate."><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
167
197
  <p class="chart-card__desc">Task flow across execution phases</p>
168
198
  </div>
169
199
  <div class="chart-card__body" id="pipeline-view"></div>
@@ -275,7 +305,7 @@ try {
275
305
  <div class="chart-card__header">
276
306
  <h2 class="chart-card__title">Event Timeline</h2>
277
307
  <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Chronological log of everything that happened during this run." data-tooltip="Chronological log of everything that happened during this run."><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
278
- <p class="chart-card__desc">Convoy events in reverse chronological order</p>
308
+ <p class="chart-card__desc">Convoy events in chronological order</p>
279
309
  </div>
280
310
  <div class="chart-card__body">
281
311
  <div id="event-timeline-filters" class="timeline-filters"></div>
@@ -296,16 +326,6 @@ try {
296
326
  <div class="chart-card__body" id="execution-log"></div>
297
327
  </section>
298
328
 
299
- <!-- Panel Reviews -->
300
- <section class="chart-card" id="panel-section" data-nav-section>
301
- <div class="chart-card__header">
302
- <h2 class="chart-card__title">Panel Reviews</h2>
303
- <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Quality gate verdicts from majority-vote panels." data-tooltip="Quality gate verdicts from majority-vote panels."><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
304
- <p class="chart-card__desc">Quality gate verdicts and fix items</p>
305
- </div>
306
- <div class="chart-card__body" id="panel-chart"></div>
307
- </section>
308
-
309
329
  <!-- Fast Reviews -->
310
330
  <section class="chart-card" id="reviews-section" data-nav-section>
311
331
  <div class="chart-card__header">
@@ -316,6 +336,16 @@ try {
316
336
  <div class="chart-card__body chart-card__body--table" id="reviews-table"></div>
317
337
  </section>
318
338
 
339
+ <!-- Panel Reviews -->
340
+ <section class="chart-card" id="panel-section" data-nav-section>
341
+ <div class="chart-card__header">
342
+ <h2 class="chart-card__title">Panel Reviews</h2>
343
+ <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Quality gate verdicts from majority-vote panels." data-tooltip="Quality gate verdicts from majority-vote panels."><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
344
+ <p class="chart-card__desc">Quality gate verdicts and fix items</p>
345
+ </div>
346
+ <div class="chart-card__body" id="panel-chart"></div>
347
+ </section>
348
+
319
349
  </div><!-- .view-convoy-detail -->
320
350
 
321
351
 
@@ -361,6 +391,7 @@ try {
361
391
  standard: '#a78bfa',
362
392
  utility: '#3b82f6',
363
393
  economy: '#64748b',
394
+ unknown: '#94a3b8',
364
395
  };
365
396
 
366
397
  const MODEL_COLORS = {
@@ -493,6 +524,8 @@ try {
493
524
  }
494
525
  renderOverallStats();
495
526
  renderConvoyList();
527
+ renderActivityTimeline();
528
+ renderRecentSessions();
496
529
  } catch {
497
530
  // Non-fatal
498
531
  }
@@ -678,6 +711,14 @@ try {
678
711
  }
679
712
 
680
713
  // ── Convoy List ─────────────────────────────────────────
714
+ var convoyListCurrentPage = 1;
715
+ var CONVOY_LIST_PAGE_SIZE = 10;
716
+ var convoyChainExpanded = {};
717
+ var executionLogPageSize = 20;
718
+ var executionLogCurrentPage = 1;
719
+ var executionLogAllSortedTasks = [];
720
+ var executionLogLimit = 10;
721
+
681
722
  function renderConvoyList() {
682
723
  const data = window.__DASHBOARD_DATA__;
683
724
  const list = data?.convoyList ?? [];
@@ -698,36 +739,112 @@ try {
698
739
 
699
740
  if (filtered.length === 0) {
700
741
  wrap.innerHTML = '';
742
+ var existingPag = document.getElementById('convoy-list-pagination');
743
+ if (existingPag) existingPag.remove();
701
744
  if (emptyEl) emptyEl.style.display = '';
702
745
  return;
703
746
  }
704
747
  if (emptyEl) emptyEl.style.display = 'none';
705
748
 
706
749
  const statusBadgeClass = (s) => {
707
- const map = { done: '--done', running: '--running', failed: '--failed', 'gate-failed': '--gate-failed', pending: '--pending' };
750
+ const map = { done: '--done', running: '--running', failed: '--failed', 'gate-failed': '--gate-failed', gate_failed: '--gate-failed', pending: '--pending' };
708
751
  return 'status-badge ' + (map[s] || '');
709
752
  };
710
753
 
754
+ // Group convoys by pipeline_id (non-null pipeline_id forms a chain)
755
+ var grouped = [];
756
+ var pipelineMap = {};
757
+ for (var gi = 0; gi < filtered.length; gi++) {
758
+ var gc = filtered[gi];
759
+ if (gc.pipeline_id) {
760
+ if (!pipelineMap[gc.pipeline_id]) {
761
+ pipelineMap[gc.pipeline_id] = { pipeline_id: gc.pipeline_id, convoys: [] };
762
+ grouped.push(pipelineMap[gc.pipeline_id]);
763
+ }
764
+ pipelineMap[gc.pipeline_id].convoys.push(gc);
765
+ } else {
766
+ grouped.push(gc);
767
+ }
768
+ }
769
+
770
+ // Pagination on grouped top-level items
771
+ var totalPages = Math.ceil(grouped.length / CONVOY_LIST_PAGE_SIZE);
772
+ if (convoyListCurrentPage > totalPages) convoyListCurrentPage = 1;
773
+ var pageStart = (convoyListCurrentPage - 1) * CONVOY_LIST_PAGE_SIZE;
774
+ var paginated = grouped.slice(pageStart, pageStart + CONVOY_LIST_PAGE_SIZE);
775
+
711
776
  let html = '<table class="convoy-list-table"><thead><tr>' +
712
777
  '<th>Name</th><th>Status</th><th>Tasks</th><th>Created</th><th>Duration</th>' +
713
778
  '</tr></thead><tbody>';
714
779
 
715
- for (const c of filtered) {
716
- const name = escapeHtml(c.name || c.id);
717
- const dateStr = c.created_at ? formatTime(c.created_at) : '\u2014';
718
- const duration = c.started_at && c.finished_at ? formatDuration(c.started_at, c.finished_at) : (c.status === 'running' ? 'In progress' : '\u2014');
719
- const taskCount = c.task_count ?? c.taskCount ?? '\u2014';
720
- html += '<tr data-convoy-id="' + escapeHtml(c.id) + '" class="task-row--clickable">' +
721
- '<td><strong>' + name + '</strong></td>' +
722
- '<td><span class="' + statusBadgeClass(c.status) + '">' + escapeHtml(c.status || 'unknown') + '</span></td>' +
723
- '<td>' + taskCount + '</td>' +
724
- '<td>' + dateStr + '</td>' +
725
- '<td>' + (duration || '\u2014') + '</td>' +
726
- '</tr>';
780
+ for (var pi = 0; pi < paginated.length; pi++) {
781
+ var item = paginated[pi];
782
+ if (item.convoys) {
783
+ // Pipeline group row
784
+ var grpConvoys = item.convoys;
785
+ var grpPipelineId = item.pipeline_id;
786
+ var isExpanded = !!convoyChainExpanded[grpPipelineId];
787
+ var grpStatus = (function(cs) {
788
+ if (cs.some(function(x) { return x.status === 'failed' || x.status === 'gate_failed'; })) return 'gate_failed';
789
+ if (cs.some(function(x) { return x.status === 'running'; })) return 'running';
790
+ if (cs.every(function(x) { return x.status === 'done'; })) return 'done';
791
+ return cs[0] ? cs[0].status : 'pending';
792
+ })(grpConvoys);
793
+ var grpTasks = grpConvoys.reduce(function(s, x) { return s + (x.task_count || 0); }, 0);
794
+ var grpEarliestCreated = grpConvoys.reduce(function(a, x) { return (!a || (x.created_at && x.created_at < a)) ? x.created_at : a; }, null);
795
+ var grpLatestFinished = grpConvoys.reduce(function(a, x) { return (!a || (x.finished_at && x.finished_at > a)) ? x.finished_at : a; }, null);
796
+ var grpAllFinished = grpConvoys.every(function(x) { return !!x.finished_at; });
797
+ var grpDuration = grpEarliestCreated && grpAllFinished && grpLatestFinished ? formatDuration(grpEarliestCreated, grpLatestFinished) : (grpStatus === 'running' ? 'In progress' : '\u2014');
798
+ var grpName = grpConvoys[0].pipeline_name || grpPipelineId.replace(/-/g, ' ').replace(/\b\w/g, function(l) { return l.toUpperCase(); });
799
+ var toggleIcon = isExpanded ? '\u25bc' : '\u25b6';
800
+ html += '<tr class="convoy-chain-row task-row--clickable" data-pipeline-group-id="' + escapeHtml(grpPipelineId) + '" aria-expanded="' + isExpanded + '" role="row">' +
801
+ '<td><span class="convoy-chain-toggle">' + toggleIcon + '</span><strong>' + escapeHtml(grpName) + '</strong></td>' +
802
+ '<td><span class="' + statusBadgeClass(grpStatus) + '">' + escapeHtml(grpStatus) + '</span></td>' +
803
+ '<td>' + grpTasks + '</td>' +
804
+ '<td>' + (grpEarliestCreated ? formatTime(grpEarliestCreated) : '\u2014') + '</td>' +
805
+ '<td>' + grpDuration + '</td>' +
806
+ '</tr>';
807
+ if (isExpanded) {
808
+ for (var ci = 0; ci < grpConvoys.length; ci++) {
809
+ var cc = grpConvoys[ci];
810
+ var ccName = escapeHtml(cc.name || cc.id);
811
+ var ccDate = cc.created_at ? formatTime(cc.created_at) : '\u2014';
812
+ var ccDur = cc.started_at && cc.finished_at ? formatDuration(cc.started_at, cc.finished_at) : (cc.status === 'running' ? 'In progress' : '\u2014');
813
+ var ccTasks = cc.task_count != null ? cc.task_count : '\u2014';
814
+ html += '<tr data-convoy-id="' + escapeHtml(cc.id) + '" class="task-row--clickable convoy-chain-child">' +
815
+ '<td><strong>' + ccName + '</strong></td>' +
816
+ '<td><span class="' + statusBadgeClass(cc.status) + '">' + escapeHtml(cc.status || 'unknown') + '</span></td>' +
817
+ '<td>' + ccTasks + '</td>' +
818
+ '<td>' + ccDate + '</td>' +
819
+ '<td>' + ccDur + '</td>' +
820
+ '</tr>';
821
+ }
822
+ }
823
+ } else {
824
+ var name = escapeHtml(item.name || item.id);
825
+ var dateStr = item.created_at ? formatTime(item.created_at) : '\u2014';
826
+ var duration = item.started_at && item.finished_at ? formatDuration(item.started_at, item.finished_at) : (item.status === 'running' ? 'In progress' : '\u2014');
827
+ var taskCount = item.task_count != null ? item.task_count : '\u2014';
828
+ html += '<tr data-convoy-id="' + escapeHtml(item.id) + '" class="task-row--clickable">' +
829
+ '<td><strong>' + name + '</strong></td>' +
830
+ '<td><span class="' + statusBadgeClass(item.status) + '">' + escapeHtml(item.status || 'unknown') + '</span></td>' +
831
+ '<td>' + taskCount + '</td>' +
832
+ '<td>' + dateStr + '</td>' +
833
+ '<td>' + (duration || '\u2014') + '</td>' +
834
+ '</tr>';
835
+ }
727
836
  }
728
837
  html += '</tbody></table>';
729
838
  wrap.innerHTML = html;
730
839
 
840
+ wrap.querySelectorAll('tr[data-pipeline-group-id]').forEach(row => {
841
+ row.addEventListener('click', () => {
842
+ const groupId = row.dataset.pipelineGroupId;
843
+ convoyChainExpanded[groupId] = !convoyChainExpanded[groupId];
844
+ renderConvoyList();
845
+ });
846
+ });
847
+
731
848
  wrap.querySelectorAll('tr[data-convoy-id]').forEach(row => {
732
849
  row.addEventListener('click', () => {
733
850
  const id = row.dataset.convoyId;
@@ -735,6 +852,26 @@ try {
735
852
  showConvoyDetailView(id, nameCell ? nameCell.textContent : id);
736
853
  });
737
854
  });
855
+
856
+ // Render pagination controls
857
+ var oldPag = document.getElementById('convoy-list-pagination');
858
+ if (oldPag) oldPag.remove();
859
+ if (grouped.length > CONVOY_LIST_PAGE_SIZE) {
860
+ var pagEl = document.createElement('div');
861
+ pagEl.id = 'convoy-list-pagination';
862
+ pagEl.className = 'convoy-list-pagination';
863
+ pagEl.innerHTML =
864
+ '<button class="dash-btn dash-btn--ghost" id="convoy-list-prev" type="button"' + (convoyListCurrentPage === 1 ? ' disabled' : '') + '>Previous</button>' +
865
+ '<span class="convoy-list-pagination__info">Page ' + convoyListCurrentPage + ' of ' + totalPages + '</span>' +
866
+ '<button class="dash-btn dash-btn--ghost" id="convoy-list-next" type="button"' + (convoyListCurrentPage === totalPages ? ' disabled' : '') + '>Next</button>';
867
+ wrap.after(pagEl);
868
+ pagEl.querySelector('#convoy-list-prev').addEventListener('click', function() {
869
+ if (convoyListCurrentPage > 1) { convoyListCurrentPage--; renderConvoyList(); }
870
+ });
871
+ pagEl.querySelector('#convoy-list-next').addEventListener('click', function() {
872
+ if (convoyListCurrentPage < totalPages) { convoyListCurrentPage++; renderConvoyList(); }
873
+ });
874
+ }
738
875
  }
739
876
 
740
877
  // ── Overall Stats Rendering ──────────────────────────────
@@ -757,8 +894,28 @@ try {
757
894
 
758
895
  const avgSec = ds.avg_sec;
759
896
  setKpiValue('overall-avg-duration', avgSec != null ? formatDurationSec(avgSec) : '\u2014');
760
- setKpiValue('overall-total-tokens', formatTokens(tc.total_tokens || 0));
761
- setKpiValue('overall-total-cost', '$' + (tc.total_cost_usd || 0).toFixed(2));
897
+ var totalTokens = (tc && tc.total_tokens) || 0;
898
+ var totalCost = (tc && tc.total_cost_usd) || 0;
899
+ setKpiValue('overall-total-tokens', formatTokens(totalTokens));
900
+ setKpiValue('overall-total-cost', '$' + totalCost.toFixed(2));
901
+
902
+ // Fallback: if overall stats show 0 tokens, sum from convoy list
903
+ if (totalTokens === 0) {
904
+ var convoyListFallback = (window.__DASHBOARD_DATA__ && window.__DASHBOARD_DATA__.convoyList) || [];
905
+ if (convoyListFallback.length > 0) {
906
+ var sumTokens = convoyListFallback.reduce(function(s, c) { return s + (c.total_tokens != null ? c.total_tokens : 0); }, 0);
907
+ var sumCost = convoyListFallback.reduce(function(s, c) { return s + (c.total_cost_usd != null ? c.total_cost_usd : 0); }, 0);
908
+ if (sumTokens > 0) {
909
+ setKpiValue('overall-total-tokens', formatTokens(sumTokens));
910
+ setKpiValue('overall-total-cost', '$' + sumCost.toFixed(2));
911
+ }
912
+ }
913
+ }
914
+
915
+ // Total tasks and retries
916
+ var tt = stats.taskTotals || {};
917
+ setKpiValue('overall-total-tasks', String(tt.totalTasks || 0));
918
+ setKpiValue('overall-total-retries', String(tt.totalRetries || 0));
762
919
  }
763
920
 
764
921
  function setKpiValue(id, value) {
@@ -779,7 +936,70 @@ try {
779
936
  return hr + 'h ' + remMin + 'm';
780
937
  }
781
938
 
939
+ function renderActivityTimeline() {
940
+ var el = document.getElementById('activity-timeline-chart');
941
+ if (!el) return;
942
+ var data = window.__DASHBOARD_DATA__;
943
+ var timeline = (data && data.overallStats && data.overallStats.activityTimeline) || [];
944
+ if (timeline.length === 0) {
945
+ el.innerHTML = emptyStateHtml('timeline', 'No activity yet', 'Convoy run activity will appear here as runs complete.');
946
+ return;
947
+ }
948
+ var maxCount = Math.max.apply(null, timeline.map(function(d) { return d.count; }));
949
+ var html = '<div class="activity-timeline">';
950
+ for (var i = 0; i < timeline.length; i++) {
951
+ var d = timeline[i];
952
+ var pct = maxCount > 0 ? Math.round((d.count / maxCount) * 100) : 0;
953
+ html += '<div class="bar-row">' +
954
+ '<span class="bar-label">' + formatShortDate(d.date) + '</span>' +
955
+ '<div class="bar-track"><div class="bar-segment bar--accent" style="width:' + pct + '%"></div></div>' +
956
+ '<span class="bar-value">' + d.count + '</span>' +
957
+ '</div>';
958
+ }
959
+ html += '</div>';
960
+ el.innerHTML = html;
961
+ }
962
+
963
+ async function renderRecentSessions() {
964
+ var el = document.getElementById('sessions-table');
965
+ if (!el) return;
966
+ try {
967
+ var allEvents = await loadNdjson(base + 'data/events.ndjson');
968
+ var sessions = allEvents.filter(function(e) { return e.type === 'session'; });
969
+ sessions.sort(function(a, b) {
970
+ return (b.timestamp || '').localeCompare(a.timestamp || '');
971
+ });
972
+ sessions = sessions.slice(0, 15);
973
+ if (sessions.length === 0) {
974
+ el.innerHTML = emptyStateHtml('sessions', 'No sessions recorded yet', 'Agent session data will appear here when sessions are logged via opencastle log.');
975
+ return;
976
+ }
977
+ var thead = '<thead><tr>' +
978
+ '<th>Agent</th><th>Task</th><th>Outcome</th><th>Duration</th><th>Retries</th><th>Files</th><th>Time</th>' +
979
+ '</tr></thead>';
980
+ var tbody = '<tbody>';
981
+ for (var i = 0; i < sessions.length; i++) {
982
+ var s = sessions[i];
983
+ var outcomeClass = s.outcome === 'success' ? 'outcome-badge--success' : s.outcome === 'failed' ? 'outcome-badge--failed' : 'outcome-badge--partial';
984
+ tbody += '<tr>' +
985
+ '<td class="td-agent">' + escapeHtml(s.agent || '\u2014') + '</td>' +
986
+ '<td class="td-task">' + escapeHtml(s.task || '\u2014') + '</td>' +
987
+ '<td><span class="outcome-badge ' + outcomeClass + '">' + escapeHtml(s.outcome || '\u2014') + '</span></td>' +
988
+ '<td>' + (s.duration_min != null ? s.duration_min + 'm' : '\u2014') + '</td>' +
989
+ '<td>' + (s.retries || 0) + '</td>' +
990
+ '<td>' + (s.files_changed || 0) + '</td>' +
991
+ '<td>' + (s.timestamp ? formatTime(s.timestamp) : '\u2014') + '</td>' +
992
+ '</tr>';
993
+ }
994
+ tbody += '</tbody>';
995
+ el.innerHTML = '<table class="sessions-table">' + thead + tbody + '</table>';
996
+ } catch (e) {
997
+ el.innerHTML = emptyStateHtml('sessions', 'No sessions recorded yet', 'Agent session data will appear here when sessions are logged.');
998
+ }
999
+ }
1000
+
782
1001
  async function loadConvoyDetail(convoyId) {
1002
+ executionLogLimit = 10;
783
1003
  if (!convoyId) {
784
1004
  renderConvoyDetailHeader(null);
785
1005
  ['pipeline-section', 'agent-section', 'tier-section', 'model-section', 'quality-section', 'reliability-section', 'drift-section', 'outputs-section', 'execution-section', 'event-timeline-section', 'panel-section', 'reviews-section'].forEach(function(id) {
@@ -1034,6 +1254,21 @@ try {
1034
1254
 
1035
1255
  function deriveTier(model) {
1036
1256
  if (!model) return 'unknown';
1257
+ var TIER_MAP = {
1258
+ 'claude-opus-4-6': 'premium',
1259
+ 'claude-opus-4': 'premium',
1260
+ 'claude-sonnet-4-6': 'standard',
1261
+ 'claude-sonnet-4': 'standard',
1262
+ 'claude-haiku-3-5': 'economy',
1263
+ 'claude-haiku-4': 'economy',
1264
+ 'gpt-5-mini': 'economy',
1265
+ 'gpt-5.3-codex': 'standard',
1266
+ 'gpt-5': 'standard',
1267
+ 'gemini-3.1-pro': 'standard',
1268
+ 'gemini-3.0-flash': 'economy',
1269
+ };
1270
+ if (TIER_MAP[model]) return TIER_MAP[model];
1271
+ // Fallback heuristics for unknown models
1037
1272
  var m = model.toLowerCase();
1038
1273
  if (m.includes('opus')) return 'premium';
1039
1274
  if (m.includes('sonnet') || m.includes('pro')) return 'standard';
@@ -1186,8 +1421,9 @@ try {
1186
1421
  var mechOrder = ['sub-agent', 'background', 'unknown'];
1187
1422
  var mechs = mechOrder.filter(function(m) { return mechCounts[m]; }).map(function(m) { return { name: m, count: mechCounts[m] }; });
1188
1423
 
1189
- if (mechs.length === 0) {
1190
- el.innerHTML = emptyStateHtml('mechanism', 'No delegation data yet', 'The split between sub-agent and background delegations will be shown here.');
1424
+ var isAllUnknown = mechs.length === 0 || (mechs.length === 1 && mechs[0].name === 'unknown');
1425
+ if (isAllUnknown) {
1426
+ el.innerHTML = emptyStateHtml('mechanism', 'No delegation data', 'Delegation mechanism data is not available for this convoy.');
1191
1427
  return;
1192
1428
  }
1193
1429
 
@@ -1265,7 +1501,7 @@ try {
1265
1501
  var models = Object.entries(modelCounts).sort(function(a, b) { return b[1] - a[1]; });
1266
1502
 
1267
1503
  if (models.length === 0) {
1268
- el.innerHTML = emptyStateHtml('models', 'No model data yet', 'Model utilization across tasks will be shown here.');
1504
+ el.innerHTML = emptyStateHtml('models', tasks.length > 0 ? 'Model information unavailable' : 'No model data yet', tasks.length > 0 ? 'No model information was recorded for tasks in this convoy.' : 'Model utilization across tasks will be shown here.');
1269
1505
  return;
1270
1506
  }
1271
1507
 
@@ -1293,7 +1529,8 @@ try {
1293
1529
  return;
1294
1530
  }
1295
1531
 
1296
- var sorted = tasks.slice().filter(function(t) { return t.started_at; }).sort(function(a, b) { return new Date(b.started_at) - new Date(a.started_at); }).slice(0, 10);
1532
+ var allSorted = tasks.slice().filter(function(t) { return t.started_at; }).sort(function(a, b) { return new Date(a.started_at) - new Date(b.started_at); });
1533
+ var sorted = allSorted.slice(0, executionLogLimit);
1297
1534
 
1298
1535
  if (sorted.length === 0) {
1299
1536
  el.innerHTML = emptyStateHtml('execLog', 'No execution history yet', 'A step-by-step trace of task activity will appear here.');
@@ -1335,6 +1572,15 @@ try {
1335
1572
  '</div>';
1336
1573
  }).join('') +
1337
1574
  '</div>';
1575
+ if (allSorted.length > executionLogLimit) {
1576
+ var remaining = allSorted.length - executionLogLimit;
1577
+ el.innerHTML += '<div style="text-align:center;padding:16px"><button class="dash-btn dash-btn--ghost" id="exec-log-more-btn" type="button">Show more (' + remaining + ' remaining)</button></div>';
1578
+ document.getElementById('exec-log-more-btn')?.addEventListener('click', function() {
1579
+ executionLogLimit = allSorted.length;
1580
+ var convoy = window.__SELECTED_CONVOY__;
1581
+ renderDetailExecutionLog((convoy && convoy.tasks) ? convoy.tasks : []);
1582
+ });
1583
+ }
1338
1584
  }
1339
1585
 
1340
1586
  // ── Convoy Detail: Panel Reviews ─────────────────────────
@@ -1346,7 +1592,7 @@ try {
1346
1592
  var panelTasks = (tasks || []).filter(function(t) { return t.panel_attempts > 0; });
1347
1593
 
1348
1594
  if (panelTasks.length === 0) {
1349
- el.innerHTML = emptyStateHtml('panels', 'No panel reviews yet', 'Quality gate verdicts from majority-vote panels will be shown here.');
1595
+ el.innerHTML = emptyStateHtml('panels', 'No panel reviews', 'This convoy completed without requiring panel reviews.');
1350
1596
  return;
1351
1597
  }
1352
1598
 
@@ -1382,7 +1628,7 @@ try {
1382
1628
  var reviewedTasks = (tasks || []).filter(function(t) { return t.review_level != null; });
1383
1629
 
1384
1630
  if (reviewedTasks.length === 0) {
1385
- el.innerHTML = emptyStateHtml('panels', 'No fast reviews yet', 'Single-reviewer quality gate results will be listed here.');
1631
+ el.innerHTML = emptyStateHtml('panels', 'No fast reviews', 'This convoy completed without fast review gates.');
1386
1632
  return;
1387
1633
  }
1388
1634
 
@@ -1487,13 +1733,27 @@ try {
1487
1733
 
1488
1734
  var dlqCount = detail.dlq_count || 0;
1489
1735
  var dlqEntries = detail.dlq_entries || [];
1736
+ var tasks = detail.tasks || [];
1490
1737
 
1491
1738
  var dlqCardEl = document.getElementById('reliability-dlq-card');
1492
1739
  if (dlqCardEl) {
1740
+ var totalRetries = tasks.reduce(function(s, t) { return s + (t.retries || 0); }, 0);
1741
+ var doneTasks = tasks.filter(function(t) { return t.status === 'done'; }).length;
1742
+ var successRate = tasks.length > 0 ? Math.round((doneTasks / tasks.length) * 100) + '%' : '\u2014';
1493
1743
  dlqCardEl.innerHTML =
1744
+ '<div class="task-summary-cards">' +
1494
1745
  '<div class="task-summary-card task-summary-card--' + (dlqCount > 0 ? 'errors' : 'done') + '">' +
1495
1746
  '<span class="task-summary-card__label">Retry Queue <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Tasks that failed too many times and need manual attention. Also known as Dead Letter Queue (DLQ)." data-tooltip="Tasks that failed too many times and need manual attention. Also known as Dead Letter Queue (DLQ).">' + INFO_ICON + '</span></span>' +
1496
1747
  '<span class="task-summary-card__value">' + dlqCount + '</span>' +
1748
+ '</div>' +
1749
+ '<div class="task-summary-card task-summary-card--running">' +
1750
+ '<span class="task-summary-card__label">Total Retries <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Total number of task retries across all tasks in this convoy." data-tooltip="Total number of task retries across all tasks in this convoy.">' + INFO_ICON + '</span></span>' +
1751
+ '<span class="task-summary-card__value">' + totalRetries + '</span>' +
1752
+ '</div>' +
1753
+ '<div class="task-summary-card task-summary-card--done">' +
1754
+ '<span class="task-summary-card__label">Task Success Rate <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Percentage of tasks that completed successfully." data-tooltip="Percentage of tasks that completed successfully.">' + INFO_ICON + '</span></span>' +
1755
+ '<span class="task-summary-card__value">' + successRate + '</span>' +
1756
+ '</div>' +
1497
1757
  '</div>';
1498
1758
  }
1499
1759
 
@@ -1523,7 +1783,6 @@ try {
1523
1783
 
1524
1784
  var errorEl = document.getElementById('reliability-error-overview');
1525
1785
  if (errorEl) {
1526
- var tasks = detail.tasks || [];
1527
1786
  var errorCategories = [
1528
1787
  { key: 'failed', label: 'Failed', color: '#ef4444' },
1529
1788
  { key: 'gate-failed', label: 'Quality Check Failed', color: '#f59e0b' },
@@ -1605,8 +1864,8 @@ try {
1605
1864
  if (!section) return;
1606
1865
  section.style.display = '';
1607
1866
 
1608
- var artifactCount = detail.artifact_count != null ? detail.artifact_count : 0;
1609
1867
  var artifacts = detail.artifacts || [];
1868
+ var artifactCount = artifacts.length;
1610
1869
 
1611
1870
  var cardsEl = document.getElementById('outputs-cards');
1612
1871
  if (cardsEl) {
@@ -1649,7 +1908,7 @@ try {
1649
1908
  }
1650
1909
 
1651
1910
  // ── Event Timeline Section ─────────────────────────────
1652
- var eventTimelinePageSize = 50;
1911
+ var eventTimelinePageSize = 20;
1653
1912
  var eventTimelineCurrentPage = 1;
1654
1913
  var eventTimelineActiveFilter = 'all';
1655
1914
  var eventTimelineAllEvents = [];
@@ -1686,7 +1945,10 @@ try {
1686
1945
  if (!section) return;
1687
1946
  section.style.display = '';
1688
1947
 
1689
- eventTimelineAllEvents = detail.events || [];
1948
+ eventTimelineAllEvents = (detail.events || []).slice().sort(function(a, b) {
1949
+ if (!a.created_at || !b.created_at) return 0;
1950
+ return a.created_at < b.created_at ? -1 : a.created_at > b.created_at ? 1 : 0;
1951
+ });
1690
1952
  eventTimelineCurrentPage = 1;
1691
1953
  eventTimelineActiveFilter = 'all';
1692
1954
 
@@ -1772,8 +2034,15 @@ try {
1772
2034
  });
1773
2035
 
1774
2036
  var loadMoreEl = document.getElementById('event-timeline-load-more');
2037
+ var loadMoreBtn = document.getElementById('event-timeline-more-btn');
1775
2038
  if (loadMoreEl) {
1776
- loadMoreEl.style.display = visible.length < filtered.length ? '' : 'none';
2039
+ if (visible.length < filtered.length) {
2040
+ loadMoreEl.style.display = '';
2041
+ var remaining = filtered.length - visible.length;
2042
+ if (loadMoreBtn) loadMoreBtn.textContent = 'Show more (' + remaining + ' remaining)';
2043
+ } else {
2044
+ loadMoreEl.style.display = 'none';
2045
+ }
1777
2046
  }
1778
2047
  }
1779
2048
 
@@ -1823,6 +2092,8 @@ try {
1823
2092
 
1824
2093
  renderOverallStats();
1825
2094
  renderConvoyList();
2095
+ renderActivityTimeline();
2096
+ renderRecentSessions();
1826
2097
 
1827
2098
  var loadMoreBtn = document.getElementById('event-timeline-more-btn');
1828
2099
  if (loadMoreBtn) {
@@ -1837,15 +2108,47 @@ try {
1837
2108
  showHomeView();
1838
2109
  });
1839
2110
 
1840
- document.getElementById('cl-filter-search')?.addEventListener('input', renderConvoyList);
1841
- document.getElementById('cl-filter-status')?.addEventListener('change', renderConvoyList);
1842
- document.getElementById('cl-filter-from')?.addEventListener('change', renderConvoyList);
1843
- document.getElementById('cl-filter-to')?.addEventListener('change', renderConvoyList);
2111
+ document.getElementById('export-btn')?.addEventListener('click', function() {
2112
+ var exportData = {
2113
+ overallStats: window.__DASHBOARD_DATA__ && window.__DASHBOARD_DATA__.overallStats,
2114
+ convoyList: window.__DASHBOARD_DATA__ && window.__DASHBOARD_DATA__.convoyList,
2115
+ };
2116
+ if (window.__SELECTED_CONVOY__) {
2117
+ exportData.selectedConvoy = window.__SELECTED_CONVOY__;
2118
+ }
2119
+ var blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
2120
+ var url = URL.createObjectURL(blob);
2121
+ var a = document.createElement('a');
2122
+ a.href = url;
2123
+ a.download = 'opencastle-dashboard-export.json';
2124
+ document.body.appendChild(a);
2125
+ a.click();
2126
+ document.body.removeChild(a);
2127
+ URL.revokeObjectURL(url);
2128
+ });
2129
+
2130
+ document.getElementById('cl-filter-search')?.addEventListener('input', function() {
2131
+ convoyListCurrentPage = 1;
2132
+ renderConvoyList();
2133
+ });
2134
+ document.getElementById('cl-filter-status')?.addEventListener('change', function() {
2135
+ convoyListCurrentPage = 1;
2136
+ renderConvoyList();
2137
+ });
2138
+ document.getElementById('cl-filter-from')?.addEventListener('change', function() {
2139
+ convoyListCurrentPage = 1;
2140
+ renderConvoyList();
2141
+ });
2142
+ document.getElementById('cl-filter-to')?.addEventListener('change', function() {
2143
+ convoyListCurrentPage = 1;
2144
+ renderConvoyList();
2145
+ });
1844
2146
  document.getElementById('cl-filter-reset')?.addEventListener('click', () => {
1845
2147
  const s = document.getElementById('cl-filter-search'); if (s) s.value = '';
1846
2148
  const st = document.getElementById('cl-filter-status'); if (st) st.value = '';
1847
2149
  const f = document.getElementById('cl-filter-from'); if (f) f.value = '';
1848
2150
  const t = document.getElementById('cl-filter-to'); if (t) t.value = '';
2151
+ convoyListCurrentPage = 1;
1849
2152
  renderConvoyList();
1850
2153
  });
1851
2154