opencastle 0.27.0 → 0.27.2

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 (242) hide show
  1. package/bin/cli.mjs +6 -0
  2. package/dist/cli/agents.d.ts +3 -0
  3. package/dist/cli/agents.d.ts.map +1 -0
  4. package/dist/cli/agents.js +161 -0
  5. package/dist/cli/agents.js.map +1 -0
  6. package/dist/cli/baselines.d.ts +3 -0
  7. package/dist/cli/baselines.d.ts.map +1 -0
  8. package/dist/cli/baselines.js +128 -0
  9. package/dist/cli/baselines.js.map +1 -0
  10. package/dist/cli/convoy/dashboard-types.d.ts +146 -0
  11. package/dist/cli/convoy/dashboard-types.d.ts.map +1 -0
  12. package/dist/cli/convoy/dashboard-types.js +2 -0
  13. package/dist/cli/convoy/dashboard-types.js.map +1 -0
  14. package/dist/cli/convoy/engine.d.ts +67 -2
  15. package/dist/cli/convoy/engine.d.ts.map +1 -1
  16. package/dist/cli/convoy/engine.js +2036 -28
  17. package/dist/cli/convoy/engine.js.map +1 -1
  18. package/dist/cli/convoy/engine.test.js +1659 -70
  19. package/dist/cli/convoy/engine.test.js.map +1 -1
  20. package/dist/cli/convoy/event-schemas.d.ts +9 -0
  21. package/dist/cli/convoy/event-schemas.d.ts.map +1 -0
  22. package/dist/cli/convoy/event-schemas.js +185 -0
  23. package/dist/cli/convoy/event-schemas.js.map +1 -0
  24. package/dist/cli/convoy/events.d.ts +12 -1
  25. package/dist/cli/convoy/events.d.ts.map +1 -1
  26. package/dist/cli/convoy/events.js +186 -13
  27. package/dist/cli/convoy/events.js.map +1 -1
  28. package/dist/cli/convoy/events.test.js +325 -28
  29. package/dist/cli/convoy/events.test.js.map +1 -1
  30. package/dist/cli/convoy/expertise.d.ts +16 -0
  31. package/dist/cli/convoy/expertise.d.ts.map +1 -0
  32. package/dist/cli/convoy/expertise.js +121 -0
  33. package/dist/cli/convoy/expertise.js.map +1 -0
  34. package/dist/cli/convoy/expertise.test.d.ts +2 -0
  35. package/dist/cli/convoy/expertise.test.d.ts.map +1 -0
  36. package/dist/cli/convoy/expertise.test.js +96 -0
  37. package/dist/cli/convoy/expertise.test.js.map +1 -0
  38. package/dist/cli/convoy/export.test.js +1 -0
  39. package/dist/cli/convoy/export.test.js.map +1 -1
  40. package/dist/cli/convoy/formula.d.ts +19 -0
  41. package/dist/cli/convoy/formula.d.ts.map +1 -0
  42. package/dist/cli/convoy/formula.js +142 -0
  43. package/dist/cli/convoy/formula.js.map +1 -0
  44. package/dist/cli/convoy/formula.test.d.ts +2 -0
  45. package/dist/cli/convoy/formula.test.d.ts.map +1 -0
  46. package/dist/cli/convoy/formula.test.js +342 -0
  47. package/dist/cli/convoy/formula.test.js.map +1 -0
  48. package/dist/cli/convoy/gates.d.ts +128 -0
  49. package/dist/cli/convoy/gates.d.ts.map +1 -0
  50. package/dist/cli/convoy/gates.js +606 -0
  51. package/dist/cli/convoy/gates.js.map +1 -0
  52. package/dist/cli/convoy/gates.test.d.ts +2 -0
  53. package/dist/cli/convoy/gates.test.d.ts.map +1 -0
  54. package/dist/cli/convoy/gates.test.js +976 -0
  55. package/dist/cli/convoy/gates.test.js.map +1 -0
  56. package/dist/cli/convoy/health.d.ts +11 -0
  57. package/dist/cli/convoy/health.d.ts.map +1 -1
  58. package/dist/cli/convoy/health.js +54 -0
  59. package/dist/cli/convoy/health.js.map +1 -1
  60. package/dist/cli/convoy/health.test.js +56 -1
  61. package/dist/cli/convoy/health.test.js.map +1 -1
  62. package/dist/cli/convoy/issues.d.ts +8 -0
  63. package/dist/cli/convoy/issues.d.ts.map +1 -0
  64. package/dist/cli/convoy/issues.js +98 -0
  65. package/dist/cli/convoy/issues.js.map +1 -0
  66. package/dist/cli/convoy/issues.test.d.ts +2 -0
  67. package/dist/cli/convoy/issues.test.d.ts.map +1 -0
  68. package/dist/cli/convoy/issues.test.js +107 -0
  69. package/dist/cli/convoy/issues.test.js.map +1 -0
  70. package/dist/cli/convoy/knowledge.d.ts +5 -0
  71. package/dist/cli/convoy/knowledge.d.ts.map +1 -0
  72. package/dist/cli/convoy/knowledge.js +116 -0
  73. package/dist/cli/convoy/knowledge.js.map +1 -0
  74. package/dist/cli/convoy/knowledge.test.d.ts +2 -0
  75. package/dist/cli/convoy/knowledge.test.d.ts.map +1 -0
  76. package/dist/cli/convoy/knowledge.test.js +87 -0
  77. package/dist/cli/convoy/knowledge.test.js.map +1 -0
  78. package/dist/cli/convoy/lessons.d.ts +17 -0
  79. package/dist/cli/convoy/lessons.d.ts.map +1 -0
  80. package/dist/cli/convoy/lessons.js +149 -0
  81. package/dist/cli/convoy/lessons.js.map +1 -0
  82. package/dist/cli/convoy/lessons.test.d.ts +2 -0
  83. package/dist/cli/convoy/lessons.test.d.ts.map +1 -0
  84. package/dist/cli/convoy/lessons.test.js +135 -0
  85. package/dist/cli/convoy/lessons.test.js.map +1 -0
  86. package/dist/cli/convoy/lock.d.ts +13 -0
  87. package/dist/cli/convoy/lock.d.ts.map +1 -0
  88. package/dist/cli/convoy/lock.js +88 -0
  89. package/dist/cli/convoy/lock.js.map +1 -0
  90. package/dist/cli/convoy/lock.test.d.ts +2 -0
  91. package/dist/cli/convoy/lock.test.d.ts.map +1 -0
  92. package/dist/cli/convoy/lock.test.js +136 -0
  93. package/dist/cli/convoy/lock.test.js.map +1 -0
  94. package/dist/cli/convoy/log-merge.test.d.ts +2 -0
  95. package/dist/cli/convoy/log-merge.test.d.ts.map +1 -0
  96. package/dist/cli/convoy/log-merge.test.js +147 -0
  97. package/dist/cli/convoy/log-merge.test.js.map +1 -0
  98. package/dist/cli/convoy/merge.d.ts +4 -0
  99. package/dist/cli/convoy/merge.d.ts.map +1 -1
  100. package/dist/cli/convoy/merge.js +18 -1
  101. package/dist/cli/convoy/merge.js.map +1 -1
  102. package/dist/cli/convoy/merge.test.js +6 -7
  103. package/dist/cli/convoy/merge.test.js.map +1 -1
  104. package/dist/cli/convoy/partition.d.ts +51 -0
  105. package/dist/cli/convoy/partition.d.ts.map +1 -0
  106. package/dist/cli/convoy/partition.js +186 -0
  107. package/dist/cli/convoy/partition.js.map +1 -0
  108. package/dist/cli/convoy/partition.test.d.ts +2 -0
  109. package/dist/cli/convoy/partition.test.d.ts.map +1 -0
  110. package/dist/cli/convoy/partition.test.js +315 -0
  111. package/dist/cli/convoy/partition.test.js.map +1 -0
  112. package/dist/cli/convoy/pipeline.test.js +6 -0
  113. package/dist/cli/convoy/pipeline.test.js.map +1 -1
  114. package/dist/cli/convoy/store.d.ts +99 -7
  115. package/dist/cli/convoy/store.d.ts.map +1 -1
  116. package/dist/cli/convoy/store.js +764 -31
  117. package/dist/cli/convoy/store.js.map +1 -1
  118. package/dist/cli/convoy/store.test.js +1810 -18
  119. package/dist/cli/convoy/store.test.js.map +1 -1
  120. package/dist/cli/convoy/types.d.ts +427 -5
  121. package/dist/cli/convoy/types.d.ts.map +1 -1
  122. package/dist/cli/convoy/types.js +42 -1
  123. package/dist/cli/convoy/types.js.map +1 -1
  124. package/dist/cli/log.d.ts +11 -0
  125. package/dist/cli/log.d.ts.map +1 -1
  126. package/dist/cli/log.js +114 -2
  127. package/dist/cli/log.js.map +1 -1
  128. package/dist/cli/run/adapters/claude.d.ts +2 -0
  129. package/dist/cli/run/adapters/claude.d.ts.map +1 -1
  130. package/dist/cli/run/adapters/claude.js +89 -49
  131. package/dist/cli/run/adapters/claude.js.map +1 -1
  132. package/dist/cli/run/adapters/claude.test.d.ts +2 -0
  133. package/dist/cli/run/adapters/claude.test.d.ts.map +1 -0
  134. package/dist/cli/run/adapters/claude.test.js +205 -0
  135. package/dist/cli/run/adapters/claude.test.js.map +1 -0
  136. package/dist/cli/run/adapters/copilot.d.ts +1 -0
  137. package/dist/cli/run/adapters/copilot.d.ts.map +1 -1
  138. package/dist/cli/run/adapters/copilot.js +84 -46
  139. package/dist/cli/run/adapters/copilot.js.map +1 -1
  140. package/dist/cli/run/adapters/copilot.test.d.ts +2 -0
  141. package/dist/cli/run/adapters/copilot.test.d.ts.map +1 -0
  142. package/dist/cli/run/adapters/copilot.test.js +195 -0
  143. package/dist/cli/run/adapters/copilot.test.js.map +1 -0
  144. package/dist/cli/run/adapters/cursor.d.ts +1 -0
  145. package/dist/cli/run/adapters/cursor.d.ts.map +1 -1
  146. package/dist/cli/run/adapters/cursor.js +83 -47
  147. package/dist/cli/run/adapters/cursor.js.map +1 -1
  148. package/dist/cli/run/adapters/cursor.test.d.ts +2 -0
  149. package/dist/cli/run/adapters/cursor.test.d.ts.map +1 -0
  150. package/dist/cli/run/adapters/cursor.test.js +129 -0
  151. package/dist/cli/run/adapters/cursor.test.js.map +1 -0
  152. package/dist/cli/run/adapters/opencode.d.ts +1 -0
  153. package/dist/cli/run/adapters/opencode.d.ts.map +1 -1
  154. package/dist/cli/run/adapters/opencode.js +81 -47
  155. package/dist/cli/run/adapters/opencode.js.map +1 -1
  156. package/dist/cli/run/adapters/opencode.test.d.ts +2 -0
  157. package/dist/cli/run/adapters/opencode.test.d.ts.map +1 -0
  158. package/dist/cli/run/adapters/opencode.test.js +119 -0
  159. package/dist/cli/run/adapters/opencode.test.js.map +1 -0
  160. package/dist/cli/run/executor.js +1 -1
  161. package/dist/cli/run/executor.js.map +1 -1
  162. package/dist/cli/run/schema.d.ts.map +1 -1
  163. package/dist/cli/run/schema.js +245 -4
  164. package/dist/cli/run/schema.js.map +1 -1
  165. package/dist/cli/run/schema.test.js +669 -0
  166. package/dist/cli/run/schema.test.js.map +1 -1
  167. package/dist/cli/run.d.ts.map +1 -1
  168. package/dist/cli/run.js +362 -22
  169. package/dist/cli/run.js.map +1 -1
  170. package/dist/cli/types.d.ts +85 -2
  171. package/dist/cli/types.d.ts.map +1 -1
  172. package/dist/cli/types.js.map +1 -1
  173. package/dist/cli/watch.d.ts +15 -0
  174. package/dist/cli/watch.d.ts.map +1 -0
  175. package/dist/cli/watch.js +279 -0
  176. package/dist/cli/watch.js.map +1 -0
  177. package/package.json +5 -1
  178. package/src/cli/agents.ts +177 -0
  179. package/src/cli/baselines.ts +143 -0
  180. package/src/cli/convoy/TELEMETRY.md +203 -0
  181. package/src/cli/convoy/dashboard-types.ts +141 -0
  182. package/src/cli/convoy/engine.test.ts +1937 -70
  183. package/src/cli/convoy/engine.ts +2350 -40
  184. package/src/cli/convoy/event-schemas.ts +195 -0
  185. package/src/cli/convoy/events.test.ts +384 -39
  186. package/src/cli/convoy/events.ts +202 -16
  187. package/src/cli/convoy/expertise.test.ts +128 -0
  188. package/src/cli/convoy/expertise.ts +163 -0
  189. package/src/cli/convoy/export.test.ts +1 -0
  190. package/src/cli/convoy/formula.test.ts +405 -0
  191. package/src/cli/convoy/formula.ts +174 -0
  192. package/src/cli/convoy/gates.test.ts +1169 -0
  193. package/src/cli/convoy/gates.ts +774 -0
  194. package/src/cli/convoy/health.test.ts +64 -2
  195. package/src/cli/convoy/health.ts +80 -2
  196. package/src/cli/convoy/issues.test.ts +143 -0
  197. package/src/cli/convoy/issues.ts +136 -0
  198. package/src/cli/convoy/knowledge.test.ts +101 -0
  199. package/src/cli/convoy/knowledge.ts +132 -0
  200. package/src/cli/convoy/lessons.test.ts +188 -0
  201. package/src/cli/convoy/lessons.ts +164 -0
  202. package/src/cli/convoy/lock.test.ts +181 -0
  203. package/src/cli/convoy/lock.ts +103 -0
  204. package/src/cli/convoy/log-merge.test.ts +179 -0
  205. package/src/cli/convoy/merge.test.ts +6 -7
  206. package/src/cli/convoy/merge.ts +19 -1
  207. package/src/cli/convoy/partition.test.ts +423 -0
  208. package/src/cli/convoy/partition.ts +232 -0
  209. package/src/cli/convoy/pipeline.test.ts +6 -0
  210. package/src/cli/convoy/store.test.ts +2041 -20
  211. package/src/cli/convoy/store.ts +945 -46
  212. package/src/cli/convoy/types.ts +278 -4
  213. package/src/cli/log.ts +120 -2
  214. package/src/cli/run/adapters/claude.test.ts +234 -0
  215. package/src/cli/run/adapters/claude.ts +45 -5
  216. package/src/cli/run/adapters/copilot.test.ts +224 -0
  217. package/src/cli/run/adapters/copilot.ts +34 -4
  218. package/src/cli/run/adapters/cursor.test.ts +144 -0
  219. package/src/cli/run/adapters/cursor.ts +33 -2
  220. package/src/cli/run/adapters/opencode.test.ts +135 -0
  221. package/src/cli/run/adapters/opencode.ts +30 -2
  222. package/src/cli/run/executor.ts +1 -1
  223. package/src/cli/run/schema.test.ts +758 -0
  224. package/src/cli/run/schema.ts +300 -25
  225. package/src/cli/run.ts +341 -21
  226. package/src/cli/types.ts +86 -1
  227. package/src/cli/watch.ts +298 -0
  228. package/src/dashboard/dist/_astro/{index.DtnyD8a5.css → index.6L3_HsPT.css} +1 -1
  229. package/src/dashboard/dist/data/.gitkeep +0 -0
  230. package/src/dashboard/dist/data/convoy-list.json +1 -0
  231. package/src/dashboard/dist/data/overall-stats.json +24 -0
  232. package/src/dashboard/dist/index.html +701 -3
  233. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  234. package/src/dashboard/public/data/.gitkeep +0 -0
  235. package/src/dashboard/public/data/convoy-list.json +1 -0
  236. package/src/dashboard/public/data/overall-stats.json +24 -0
  237. package/src/dashboard/scripts/etl.test.ts +210 -0
  238. package/src/dashboard/scripts/etl.ts +108 -0
  239. package/src/dashboard/scripts/integration-test.ts +504 -0
  240. package/src/dashboard/src/pages/index.astro +854 -15
  241. package/src/dashboard/src/styles/dashboard.css +557 -1
  242. package/src/orchestrator/prompts/generate-convoy.prompt.md +212 -13
@@ -1,6 +1,10 @@
1
- <!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Observability Dashboard — OpenCastle</title><meta name="description" content="Real-time observability for OpenCastle multi-agent orchestration — sessions, delegations, model tiers, and quality gates."><meta name="theme-color" content="#0a0a0f"><link rel="icon" type="image/png" sizes="192x192" href="/icon-192.png"><link rel="stylesheet" href="/_astro/index.DtnyD8a5.css"></head> <body> <header class="dash-header"> <div class="dash-header__inner"> <div class="dash-header__brand"> <img class="dash-header__icon" src="/icon-192.png" alt="OpenCastle" width="32" height="32"> <h1 class="dash-header__title">Observability Dashboard</h1> </div> <div class="dash-header__actions"> <button class="dash-btn dash-btn--ghost" id="export-btn" type="button" title="Export data as JSON"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
1
+ <!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Observability Dashboard — OpenCastle</title><meta name="description" content="Real-time observability for OpenCastle multi-agent orchestration — sessions, delegations, model tiers, and quality gates."><meta name="theme-color" content="#0a0a0f"><link rel="icon" type="image/png" sizes="192x192" href="/icon-192.png"><link rel="stylesheet" href="/_astro/index.6L3_HsPT.css"></head> <body> <script>(function(){const overallStats = {"convoyCounts":{"total":0,"running":0,"done":0,"failed":0,"gate_failed":0},"durationStats":{"avg_sec":null,"p95_sec":null,"max_sec":null},"tokenCostTotals":{"total_tokens":0,"total_cost_usd":0},"topAgents":[],"topModels":[],"dlqSummary":{"count":0,"top_failure_types":[]}};
2
+ const convoyList = [];
3
+
4
+ window.__DASHBOARD_DATA__ = { overallStats, convoyList }
5
+ })();</script> <header class="dash-header"> <div class="dash-header__inner"> <div class="dash-header__brand"> <img class="dash-header__icon" src="/icon-192.png" alt="OpenCastle" width="32" height="32"> <h1 class="dash-header__title">Observability Dashboard</h1> </div> <div class="dash-header__actions"> <div class="convoy-selector"> <label class="convoy-selector__label" for="convoy-select">Convoy</label> <select class="convoy-selector__select" id="convoy-select" aria-label="Select a convoy to view its details"> <option value="">Select convoy…</option> </select> </div> <button class="dash-btn dash-btn--ghost" id="export-btn" type="button" title="Export data as JSON" aria-label="Export dashboard data as JSON"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
2
6
  Export
3
- </button> </div> </div> </header> <div class="dash-layout"> <!-- Sidebar Navigation --> <nav class="dash-sidebar" id="dash-sidebar"> <ul class="dash-sidebar__list"> <li><a class="dash-sidebar__link" href="#convoy-section" data-section="convoy-section">Convoy</a></li> <li><a class="dash-sidebar__link" href="#convoy-pipeline-section" data-section="convoy-pipeline-section">Convoy Chain</a></li> <li><a class="dash-sidebar__link dash-sidebar__link--active" href="#kpi-row" data-section="kpi-row">Overview</a></li> <li><a class="dash-sidebar__link" href="#pipeline-section" data-section="pipeline-section">Task Flow</a></li> <li><a class="dash-sidebar__link" href="#agent-section" data-section="agent-section">Agents</a></li> <li><a class="dash-sidebar__link" href="#tier-section" data-section="tier-section">Tiers</a></li> <li><a class="dash-sidebar__link" href="#delegation-section" data-section="delegation-section">Delegations</a></li> <li><a class="dash-sidebar__link" href="#timeline-section" data-section="timeline-section">Timeline</a></li> <li><a class="dash-sidebar__link" href="#model-section" data-section="model-section">Models</a></li> <li><a class="dash-sidebar__link" href="#execution-section" data-section="execution-section">Exec Log</a></li> <li><a class="dash-sidebar__link" href="#panel-section" data-section="panel-section">Panels</a></li> <li><a class="dash-sidebar__link" href="#reviews-section" data-section="reviews-section">Reviews</a></li> <li><a class="dash-sidebar__link" href="#sessions-section" data-section="sessions-section">Sessions</a></li> </ul> </nav> <main class="dash-main"> <!-- Filter Bar --> <div class="filter-bar" id="filter-bar"> <div class="filter-group"> <label class="filter-label" for="filter-date-from">From</label> <input class="filter-input" type="date" id="filter-date-from"> </div> <div class="filter-group"> <label class="filter-label" for="filter-date-to">To</label> <input class="filter-input" type="date" id="filter-date-to"> </div> <div class="filter-group"> <label class="filter-label" for="filter-agent">Agent</label> <select class="filter-select" id="filter-agent"> <option value="">All agents</option> </select> </div> <div class="filter-group"> <label class="filter-label" for="filter-outcome">Outcome</label> <select class="filter-select" id="filter-outcome"> <option value="">All outcomes</option> <option value="success">Success</option> <option value="partial">Partial</option> <option value="failed">Failed</option> </select> </div> <div class="filter-group"> <label class="filter-label" for="filter-convoy">Convoy</label> <select class="filter-select" id="filter-convoy"> <option value="">All convoys</option> </select> </div> <div class="filter-group"> <label class="filter-label" for="filter-pipeline">Pipeline</label> <select class="filter-select" id="filter-pipeline"> <option value="">All</option> </select> </div> <button class="dash-btn dash-btn--ghost filter-reset" id="filter-reset" type="button">Reset</button> </div> <!-- Convoy Status Section --> <section class="chart-card convoy-status" id="convoy-section" data-nav-section style="display:none"> <div class="chart-card__header"> <h2 class="chart-card__title">Convoy Status</h2> <p class="chart-card__desc" id="convoy-desc">Select a convoy to view details</p> </div> <div class="chart-card__body" id="convoy-body"></div> </section> <!-- Convoy Pipeline (Chaining) Section --> <section class="chart-card" id="convoy-pipeline-section" data-nav-section style="display:none"> <div class="chart-card__header"> <h2 class="chart-card__title">Convoy Pipeline</h2> <p class="chart-card__desc" id="convoy-pipeline-desc">Pipeline convoy chain progress</p> </div> <div class="chart-card__body" id="convoy-pipeline-body"></div> </section> <!-- KPI Row --> <section class="kpi-row" id="kpi-row" data-nav-section> <div class="kpi-card" id="kpi-sessions"> <span class="kpi-card__label">Total Sessions</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> <div class="kpi-card" id="kpi-success"> <span class="kpi-card__label">Success Rate</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> <div class="kpi-card" id="kpi-delegations"> <span class="kpi-card__label">Total Delegations</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> <div class="kpi-card" id="kpi-duration"> <span class="kpi-card__label">Avg Duration</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> <div class="kpi-card" id="kpi-retries"> <span class="kpi-card__label">Total Retries</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> <div class="kpi-card" id="kpi-lessons"> <span class="kpi-card__label">Lessons Added</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> </section> <!-- Pipeline View (Steroids-inspired) --> <section class="chart-card" id="pipeline-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Task Pipeline</h2> <p class="chart-card__desc">Delegation flow across execution phases</p> </div> <div class="chart-card__body" id="pipeline-view"> <div class="loading-skeleton"></div> </div> </section> <!-- Charts Row 1 --> <div class="charts-row" id="agent-section" data-nav-section> <section class="chart-card"> <div class="chart-card__header"> <h2 class="chart-card__title">Sessions by Agent</h2> <p class="chart-card__desc">Stacked by outcome</p> </div> <div class="chart-card__body" id="agent-chart"> <div class="loading-skeleton"></div> </div> </section> <section class="chart-card" id="tier-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Tier Distribution</h2> <p class="chart-card__desc">Delegation model tiers</p> </div> <div class="chart-card__body" id="tier-chart"> <div class="loading-skeleton"></div> </div> </section> </div> <!-- Charts Row: Delegation Insights --> <div class="charts-row" id="delegation-section" data-nav-section> <section class="chart-card"> <div class="chart-card__header"> <h2 class="chart-card__title">Delegation Mechanism</h2> <p class="chart-card__desc">Sub-agent vs background split</p> </div> <div class="chart-card__body" id="mechanism-chart"> <div class="loading-skeleton"></div> </div> </section> <section class="chart-card"> <div class="chart-card__header"> <h2 class="chart-card__title">Delegation Outcomes</h2> <p class="chart-card__desc">Success rate by delegation</p> </div> <div class="chart-card__body" id="delegation-outcome-chart"> <div class="loading-skeleton"></div> </div> </section> </div> <!-- Charts Row 2 --> <div class="charts-row" id="timeline-section" data-nav-section> <section class="chart-card"> <div class="chart-card__header"> <h2 class="chart-card__title">Timeline</h2> <p class="chart-card__desc">Sessions and delegations over time</p> </div> <div class="chart-card__body" id="timeline-chart"> <div class="loading-skeleton"></div> </div> </section> <section class="chart-card" id="model-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Model Usage</h2> <p class="chart-card__desc">Sessions by model</p> </div> <div class="chart-card__body" id="model-chart"> <div class="loading-skeleton"></div> </div> </section> </div> <!-- Execution Log (Duvo-inspired) --> <section class="chart-card" id="execution-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Execution Log</h2> <p class="chart-card__desc">Recent agent activity, step by step</p> </div> <div class="chart-card__body" id="execution-log"> <div class="loading-skeleton"></div> </div> </section> <!-- Panel Results --> <section class="chart-card" id="panel-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Panel Reviews</h2> <p class="chart-card__desc">Quality gate verdicts and fix items</p> </div> <div class="chart-card__body" id="panel-chart"> <div class="loading-skeleton"></div> </div> </section> <!-- Fast Reviews --> <section class="chart-card" id="reviews-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Fast Reviews</h2> <p class="chart-card__desc">Single-reviewer quality gate results</p> </div> <div class="chart-card__body chart-card__body--table" id="reviews-table"> <div class="loading-skeleton"></div> </div> </section> <!-- Sessions Table --> <section class="chart-card" id="sessions-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Recent Sessions</h2> <p class="chart-card__desc">Last 15 sessions by timestamp</p> </div> <div class="chart-card__body chart-card__body--table" id="sessions-table"> <div class="loading-skeleton"></div> </div> </section> </main> </div> </body></html> <script>(function(){const base = "/";
7
+ </button> </div> </div> </header> <div class="dash-layout"> <!-- Sidebar Navigation --> <nav class="dash-sidebar" id="dash-sidebar"> <ul class="dash-sidebar__list"> <li><a class="dash-sidebar__link dash-sidebar__link--active" href="#overall-section" data-section="overall-section" aria-label="Overview section">Overview</a></li> <li><a class="dash-sidebar__link" href="#convoy-section" data-section="convoy-section" aria-label="Convoy section">Convoy</a></li> <li><a class="dash-sidebar__link" href="#tasks-section" data-section="tasks-section" aria-label="Tasks section">Tasks</a></li> <li><a class="dash-sidebar__link" href="#quality-section" data-section="quality-section" aria-label="Quality section">Quality</a></li> <li><a class="dash-sidebar__link" href="#reliability-section" data-section="reliability-section" aria-label="Reliability section">Reliability</a></li> <li><a class="dash-sidebar__link" href="#drift-section" data-section="drift-section" aria-label="Drift section">Drift</a></li> <li><a class="dash-sidebar__link" href="#outputs-section" data-section="outputs-section" aria-label="Outputs section">Outputs</a></li> <li><a class="dash-sidebar__link" href="#event-timeline-section" data-section="event-timeline-section" aria-label="Event Log section">Event Log</a></li> </ul> </nav> <main class="dash-main"> <!-- Overall Stats Section --> <section class="overall-stats" id="overall-section" data-nav-section> <div class="overall-stats__header"> <h2 class="overall-stats__title">Overall Stats</h2> <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: How all runs behave across your project." data-tooltip="How all runs behave across your project.">ℹ️</span> </div> <div class="overall-stats__grid"> <div class="overall-kpi" id="overall-total-runs"> <span class="overall-kpi__label">Total Runs <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Number of convoy runs executed" data-tooltip="Number of convoy runs executed">ℹ️</span></span> <span class="overall-kpi__value">&mdash;</span> </div> <div class="overall-kpi" id="overall-running"> <span class="overall-kpi__label">Running Now</span> <span class="overall-kpi__value">&mdash;</span> </div> <div class="overall-kpi" id="overall-success-rate"> <span class="overall-kpi__label">Success Rate</span> <span class="overall-kpi__value">&mdash;</span> </div> <div class="overall-kpi" id="overall-avg-duration"> <span class="overall-kpi__label">Avg Duration</span> <span class="overall-kpi__value">&mdash;</span> </div> <div class="overall-kpi" id="overall-total-tokens"> <span class="overall-kpi__label">Total Tokens</span> <span class="overall-kpi__value">&mdash;</span> </div> <div class="overall-kpi" id="overall-total-cost"> <span class="overall-kpi__label">Total Cost</span> <span class="overall-kpi__value">&mdash;</span> </div> </div> </section> <!-- Selected Convoy Header --> <section class="convoy-detail-header" id="convoy-detail-header"> <div class="convoy-detail-header__top"> <h2 class="convoy-detail-header__name" id="selected-convoy-name">No convoy selected</h2> <span class="status-badge" id="selected-convoy-status" role="status"></span> <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: A convoy is a set of AI tasks working together on one goal." data-tooltip="A convoy is a set of AI tasks working together on one goal.">ℹ️</span> </div> <p class="convoy-status-explanation" id="convoy-status-explanation" role="status" aria-live="polite"></p> <div class="convoy-detail-header__meta" id="selected-convoy-meta"> <!-- Populated by JS: branch, timestamps, duration, tokens, cost --> </div> </section> <!-- Tasks Section --> <section class="chart-card" id="tasks-section" data-nav-section style="display:none"> <div class="chart-card__header"> <h2 class="chart-card__title">Tasks</h2> <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Individual units of work in this convoy." data-tooltip="Individual units of work in this convoy.">ℹ️</span> <p class="chart-card__desc" id="tasks-section-desc">Task breakdown for the selected convoy</p> </div> <div class="chart-card__body" id="tasks-section-body"> <div id="task-summary-cards" class="task-summary-cards"></div> <div id="phase-breakdown" class="phase-breakdown"></div> <div id="task-table-wrap" class="task-table-wrap"></div> </div> </section> <!-- Quality Section --> <section class="chart-card" id="quality-section" data-nav-section style="display:none"> <div class="chart-card__header"> <h2 class="chart-card__title">Quality / Review</h2> <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Code review results and dispute resolution." data-tooltip="Code review results and dispute resolution.">ℹ️</span> <p class="chart-card__desc">Code review results and quality gate outcomes for the selected convoy</p> </div> <div class="chart-card__body"> <div id="quality-cards" class="task-summary-cards"></div> <div id="quality-review-table"></div> </div> </section> <!-- Reliability Section --> <section class="chart-card" id="reliability-section" data-nav-section style="display:none"> <div class="chart-card__header"> <h2 class="chart-card__title">Reliability</h2> <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Errors, retry attempts, and safety mechanisms." data-tooltip="Errors, retry attempts, and safety mechanisms.">ℹ️</span> <p class="chart-card__desc">Retry queue and error overview for the selected convoy</p> </div> <div class="chart-card__body"> <div id="reliability-dlq-card"></div> <div id="reliability-dlq-table"></div> <div style="margin-top:24px"> <h3 style="font-size:1rem;font-weight:600;color:var(--text-secondary);margin:0 0 12px">Error Overview</h3> <div id="reliability-error-overview"></div> </div> </div> </section> <!-- Drift Section --> <section class="chart-card" id="drift-section" data-nav-section style="display:none"> <div class="chart-card__header"> <h2 class="chart-card__title">Drift</h2> <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: How far the actual work deviated from the original plan." data-tooltip="How far the actual work deviated from the original plan.">ℹ️</span> <p class="chart-card__desc">Plan adherence and deviation metrics for the selected convoy</p> </div> <div class="chart-card__body"> <div id="drift-cards" class="task-summary-cards"></div> <div id="drift-secret-banner" style="display:none"></div> </div> </section> <!-- Outputs Section --> <section class="chart-card" id="outputs-section" data-nav-section style="display:none"> <div class="chart-card__header"> <h2 class="chart-card__title">Outputs &amp; Artifacts</h2> <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Files, data, and summaries produced by this convoy." data-tooltip="Files, data, and summaries produced by this convoy.">ℹ️</span> <p class="chart-card__desc">Files, summaries, and structured data produced by tasks in this convoy</p> </div> <div class="chart-card__body"> <div id="outputs-cards" class="task-summary-cards"></div> <div id="artifact-table-wrap"></div> </div> </section> <!-- Event Timeline Section --> <section class="chart-card" id="event-timeline-section" data-nav-section style="display:none"> <div class="chart-card__header"> <h2 class="chart-card__title">Event Timeline</h2> <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.">ℹ️</span> <p class="chart-card__desc">Convoy events in reverse chronological order</p> </div> <div class="chart-card__body"> <div id="event-timeline-filters" class="timeline-filters"></div> <div id="event-timeline-list"></div> <div id="event-timeline-load-more" style="display:none;text-align:center;padding:16px"> <button class="dash-btn dash-btn--ghost" id="event-timeline-more-btn" type="button">Load more</button> </div> </div> </section> <!-- Filter Bar --> <div class="filter-bar" id="filter-bar"> <div class="filter-group"> <label class="filter-label" for="filter-date-from">From</label> <input class="filter-input" type="date" id="filter-date-from"> </div> <div class="filter-group"> <label class="filter-label" for="filter-date-to">To</label> <input class="filter-input" type="date" id="filter-date-to"> </div> <div class="filter-group"> <label class="filter-label" for="filter-agent">Agent</label> <select class="filter-select" id="filter-agent"> <option value="">All agents</option> </select> </div> <div class="filter-group"> <label class="filter-label" for="filter-outcome">Outcome</label> <select class="filter-select" id="filter-outcome"> <option value="">All outcomes</option> <option value="success">Success</option> <option value="partial">Partial</option> <option value="failed">Failed</option> </select> </div> <div class="filter-group"> <label class="filter-label" for="filter-convoy">Convoy</label> <select class="filter-select" id="filter-convoy"> <option value="">All convoys</option> </select> </div> <div class="filter-group"> <label class="filter-label" for="filter-pipeline">Pipeline</label> <select class="filter-select" id="filter-pipeline"> <option value="">All</option> </select> </div> <button class="dash-btn dash-btn--ghost filter-reset" id="filter-reset" type="button">Reset</button> </div> <!-- Convoy Status Section --> <section class="chart-card convoy-status" id="convoy-section" data-nav-section style="display:none"> <div class="chart-card__header"> <h2 class="chart-card__title">Convoy Status</h2> <p class="chart-card__desc" id="convoy-desc">Select a convoy to view details</p> </div> <div class="chart-card__body" id="convoy-body"></div> </section> <!-- Convoy Pipeline (Chaining) Section --> <section class="chart-card" id="convoy-pipeline-section" data-nav-section style="display:none"> <div class="chart-card__header"> <h2 class="chart-card__title">Convoy Pipeline</h2> <p class="chart-card__desc" id="convoy-pipeline-desc">Pipeline convoy chain progress</p> </div> <div class="chart-card__body" id="convoy-pipeline-body"></div> </section> <!-- KPI Row --> <section class="kpi-row" id="kpi-row" data-nav-section> <div class="kpi-card" id="kpi-sessions"> <span class="kpi-card__label">Total Sessions</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> <div class="kpi-card" id="kpi-success"> <span class="kpi-card__label">Success Rate</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> <div class="kpi-card" id="kpi-delegations"> <span class="kpi-card__label">Total Delegations</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> <div class="kpi-card" id="kpi-duration"> <span class="kpi-card__label">Avg Duration</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> <div class="kpi-card" id="kpi-retries"> <span class="kpi-card__label">Total Retries</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> <div class="kpi-card" id="kpi-lessons"> <span class="kpi-card__label">Lessons Added</span> <span class="kpi-card__value">&mdash;</span> <span class="kpi-card__sub"></span> </div> </section> <!-- Pipeline View (Steroids-inspired) --> <section class="chart-card" id="pipeline-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Task Pipeline</h2> <p class="chart-card__desc">Delegation flow across execution phases</p> </div> <div class="chart-card__body" id="pipeline-view"> <div class="loading-skeleton"></div> </div> </section> <!-- Charts Row 1 --> <div class="charts-row" id="agent-section" data-nav-section> <section class="chart-card"> <div class="chart-card__header"> <h2 class="chart-card__title">Sessions by Agent</h2> <p class="chart-card__desc">Stacked by outcome</p> </div> <div class="chart-card__body" id="agent-chart"> <div class="loading-skeleton"></div> </div> </section> <section class="chart-card" id="tier-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Tier Distribution</h2> <p class="chart-card__desc">Delegation model tiers</p> </div> <div class="chart-card__body" id="tier-chart"> <div class="loading-skeleton"></div> </div> </section> </div> <!-- Charts Row: Delegation Insights --> <div class="charts-row" id="delegation-section" data-nav-section> <section class="chart-card"> <div class="chart-card__header"> <h2 class="chart-card__title">Delegation Mechanism</h2> <p class="chart-card__desc">Sub-agent vs background split</p> </div> <div class="chart-card__body" id="mechanism-chart"> <div class="loading-skeleton"></div> </div> </section> <section class="chart-card"> <div class="chart-card__header"> <h2 class="chart-card__title">Delegation Outcomes</h2> <p class="chart-card__desc">Success rate by delegation</p> </div> <div class="chart-card__body" id="delegation-outcome-chart"> <div class="loading-skeleton"></div> </div> </section> </div> <!-- Charts Row 2 --> <div class="charts-row" id="timeline-section" data-nav-section> <section class="chart-card"> <div class="chart-card__header"> <h2 class="chart-card__title">Timeline</h2> <p class="chart-card__desc">Sessions and delegations over time</p> </div> <div class="chart-card__body" id="timeline-chart"> <div class="loading-skeleton"></div> </div> </section> <section class="chart-card" id="model-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Model Usage</h2> <p class="chart-card__desc">Sessions by model</p> </div> <div class="chart-card__body" id="model-chart"> <div class="loading-skeleton"></div> </div> </section> </div> <!-- Execution Log (Duvo-inspired) --> <section class="chart-card" id="execution-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Execution Log</h2> <p class="chart-card__desc">Recent agent activity, step by step</p> </div> <div class="chart-card__body" id="execution-log"> <div class="loading-skeleton"></div> </div> </section> <!-- Panel Results --> <section class="chart-card" id="panel-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Panel Reviews</h2> <p class="chart-card__desc">Quality gate verdicts and fix items</p> </div> <div class="chart-card__body" id="panel-chart"> <div class="loading-skeleton"></div> </div> </section> <!-- Fast Reviews --> <section class="chart-card" id="reviews-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Fast Reviews</h2> <p class="chart-card__desc">Single-reviewer quality gate results</p> </div> <div class="chart-card__body chart-card__body--table" id="reviews-table"> <div class="loading-skeleton"></div> </div> </section> <!-- Sessions Table --> <section class="chart-card" id="sessions-section" data-nav-section> <div class="chart-card__header"> <h2 class="chart-card__title">Recent Sessions</h2> <p class="chart-card__desc">Last 15 sessions by timestamp</p> </div> <div class="chart-card__body chart-card__body--table" id="sessions-table"> <div class="loading-skeleton"></div> </div> </section> </main> </div> </body></html> <script>(function(){const base = "/";
4
8
 
5
9
  // ── Data Loading ──────────────────────────────────────────
6
10
 
@@ -1414,9 +1418,684 @@ Export
1414
1418
  });
1415
1419
  }
1416
1420
 
1421
+ // ── Overall Stats Rendering ──────────────────────────────
1422
+
1423
+ function renderOverallStats() {
1424
+ const data = window.__DASHBOARD_DATA__;
1425
+ if (!data || !data.overallStats) return;
1426
+ const stats = data.overallStats;
1427
+ const cc = stats.convoyCounts || {};
1428
+ const tc = stats.tokenCostTotals || {};
1429
+ const ds = stats.durationStats || {};
1430
+
1431
+ setKpiValue('overall-total-runs', String(cc.total || 0));
1432
+ setKpiValue('overall-running', String(cc.running || 0));
1433
+
1434
+ const total = cc.total || 0;
1435
+ const success = cc.done || 0;
1436
+ const rate = total > 0 ? ((success / total) * 100).toFixed(1) + '%' : '\u2014';
1437
+ setKpiValue('overall-success-rate', rate);
1438
+
1439
+ const avgSec = ds.avg_sec;
1440
+ setKpiValue('overall-avg-duration', avgSec != null ? formatDurationSec(avgSec) : '\u2014');
1441
+ setKpiValue('overall-total-tokens', formatTokens(tc.total_tokens || 0));
1442
+ setKpiValue('overall-total-cost', '$' + (tc.total_cost_usd || 0).toFixed(2));
1443
+ }
1444
+
1445
+ function setKpiValue(id, value) {
1446
+ const el = document.getElementById(id);
1447
+ if (el) {
1448
+ const valEl = el.querySelector('.overall-kpi__value');
1449
+ if (valEl) valEl.textContent = value;
1450
+ }
1451
+ }
1452
+
1453
+ function formatDurationSec(sec) {
1454
+ if (sec < 60) return Math.round(sec) + 's';
1455
+ const min = Math.floor(sec / 60);
1456
+ const remSec = Math.round(sec % 60);
1457
+ if (min < 60) return min + 'm ' + remSec + 's';
1458
+ const hr = Math.floor(min / 60);
1459
+ const remMin = min % 60;
1460
+ return hr + 'h ' + remMin + 'm';
1461
+ }
1462
+
1463
+ // ── Convoy Selector ──────────────────────────────────────
1464
+
1465
+ function populateConvoySelector() {
1466
+ const data = window.__DASHBOARD_DATA__;
1467
+ const select = document.getElementById('convoy-select');
1468
+ if (!select || !data || !data.convoyList) return;
1469
+
1470
+ select.innerHTML = '<option value="">Select convoy\u2026</option>';
1471
+ const list = data.convoyList;
1472
+ for (const c of list) {
1473
+ const opt = document.createElement('option');
1474
+ opt.value = c.id;
1475
+ const dateStr = c.created_at ? c.created_at.slice(0, 10) : '';
1476
+ opt.textContent = (c.name || c.id) + ' \u2014 ' + c.status + ' (' + dateStr + ')';
1477
+ select.appendChild(opt);
1478
+ }
1479
+
1480
+ // Default: select latest (first in list)
1481
+ if (list.length > 0) {
1482
+ select.value = list[0].id;
1483
+ loadConvoyDetail(list[0].id);
1484
+ }
1485
+ }
1486
+
1487
+ async function loadConvoyDetail(convoyId) {
1488
+ if (!convoyId) {
1489
+ renderConvoyDetailHeader(null);
1490
+ ['quality-section', 'reliability-section', 'drift-section', 'outputs-section', 'event-timeline-section'].forEach(function(id) {
1491
+ var el = document.getElementById(id);
1492
+ if (el) el.style.display = 'none';
1493
+ });
1494
+ return;
1495
+ }
1496
+ try {
1497
+ const resp = await fetch(base + 'data/convoys/' + encodeURIComponent(convoyId) + '.json');
1498
+ if (!resp.ok) throw new Error('Failed to load convoy detail');
1499
+ const detail = await resp.json();
1500
+ renderConvoyDetailHeader(detail);
1501
+ window.__SELECTED_CONVOY__ = detail;
1502
+ renderTaskSummaryCards(detail.tasks || []);
1503
+ renderTaskTable(detail.tasks || []);
1504
+ renderPhaseBreakdown(detail.tasks || []);
1505
+ const tasksSection = document.getElementById('tasks-section');
1506
+ if (tasksSection) tasksSection.style.display = '';
1507
+ renderQualitySection(detail);
1508
+ renderReliabilitySection(detail);
1509
+ renderDriftSection(detail);
1510
+ renderOutputsSection(detail);
1511
+ renderEventTimeline(detail);
1512
+ } catch (e) {
1513
+ console.error('Failed to load convoy detail:', e);
1514
+ renderConvoyDetailHeader(null);
1515
+ ['quality-section', 'reliability-section', 'drift-section', 'outputs-section', 'event-timeline-section'].forEach(function(id) {
1516
+ var el = document.getElementById(id);
1517
+ if (el) el.style.display = 'none';
1518
+ });
1519
+ }
1520
+ }
1521
+
1522
+ function renderConvoyDetailHeader(detail) {
1523
+ const nameEl = document.getElementById('selected-convoy-name');
1524
+ const statusEl = document.getElementById('selected-convoy-status');
1525
+ const metaEl = document.getElementById('selected-convoy-meta');
1526
+
1527
+ if (!detail || !detail.convoy) {
1528
+ if (nameEl) nameEl.textContent = 'No convoy selected';
1529
+ if (statusEl) { statusEl.textContent = ''; statusEl.className = 'status-badge'; }
1530
+ if (metaEl) metaEl.innerHTML = '';
1531
+ ['tasks-section', 'quality-section', 'reliability-section', 'drift-section', 'outputs-section', 'event-timeline-section'].forEach(function(id) {
1532
+ var el = document.getElementById(id);
1533
+ if (el) el.style.display = 'none';
1534
+ });
1535
+ return;
1536
+ }
1537
+
1538
+ const c = detail.convoy;
1539
+ if (nameEl) nameEl.textContent = c.name || c.id;
1540
+ if (statusEl) {
1541
+ statusEl.textContent = c.status;
1542
+ statusEl.className = 'status-badge status-badge--' + c.status;
1543
+ }
1544
+ // Status explanation
1545
+ const statusExplanations = {
1546
+ 'running': 'This run is still in progress; some tasks are being executed.',
1547
+ 'done': 'This run completed successfully; all tasks finished.',
1548
+ 'failed': 'This run stopped because at least one task could not recover.',
1549
+ 'gate-failed': 'This run stopped because a quality check failed.',
1550
+ 'hook-failed': 'This run stopped because a lifecycle script failed.',
1551
+ };
1552
+ const explanationEl = document.getElementById('convoy-status-explanation');
1553
+ if (explanationEl) {
1554
+ explanationEl.textContent = statusExplanations[c.status] || '';
1555
+ explanationEl.style.display = statusExplanations[c.status] ? '' : 'none';
1556
+ }
1557
+ if (metaEl) {
1558
+ let html = '';
1559
+ if (c.branch) html += '<span class="convoy-meta__item">🌿 ' + escapeHtml(c.branch) + '</span>';
1560
+ if (c.created_at) html += '<span class="convoy-meta__item">\u25B6 ' + formatTime(c.created_at) + '</span>';
1561
+ if (c.finished_at) html += '<span class="convoy-meta__item">\u25A0 ' + formatTime(c.finished_at) + '</span>';
1562
+ if (c.created_at && c.finished_at) {
1563
+ const dur = formatDuration(c.created_at, c.finished_at);
1564
+ if (dur) html += '<span class="convoy-meta__item">\u23F1 ' + dur + '</span>';
1565
+ }
1566
+ if (c.total_tokens != null) html += '<span class="convoy-meta__item">\uD83D\uDD24 ' + formatTokens(c.total_tokens) + ' tokens</span>';
1567
+ if (c.total_cost_usd != null) html += '<span class="convoy-meta__item">\uD83D\uDCB0 $' + c.total_cost_usd.toFixed(2) + '</span>';
1568
+ metaEl.innerHTML = html;
1569
+ }
1570
+ }
1571
+
1572
+ // ── Task Section ─────────────────────────────────────────
1573
+
1574
+ const STATUS_TOOLTIPS = {
1575
+ 'done': 'Finished successfully.',
1576
+ 'running': 'Currently executing.',
1577
+ 'pending': 'Waiting to start.',
1578
+ 'assigned': 'Assigned to a worker, starting soon.',
1579
+ 'failed': 'Stopped due to an error.',
1580
+ 'gate-failed': 'A quality check did not pass.',
1581
+ 'timed-out': 'Exceeded the time limit.',
1582
+ 'review-blocked': 'Paused because a reviewer requested changes.',
1583
+ 'skipped': 'Skipped because a dependency failed.',
1584
+ 'hook-failed': 'A pre/post-task script encountered an error.',
1585
+ 'disputed': 'Under dispute after review disagreement.',
1586
+ 'wait-for-input': 'Paused \u2014 needs your input.',
1587
+ };
1588
+
1589
+ let taskSortCol = 'phase';
1590
+ let taskSortAsc = true;
1591
+
1592
+ function renderTaskSummaryCards(tasks) {
1593
+ const el = document.getElementById('task-summary-cards');
1594
+ if (!el) return;
1595
+ if (!tasks || tasks.length === 0) { el.innerHTML = ''; return; }
1596
+ const done = tasks.filter(t => t.status === 'done').length;
1597
+ const waiting = tasks.filter(t => t.status === 'pending' || t.status === 'assigned').length;
1598
+ const running = tasks.filter(t => t.status === 'running').length;
1599
+ const errors = tasks.filter(t => ['failed', 'gate-failed', 'timed-out', 'hook-failed'].includes(t.status)).length;
1600
+ const waitInput = tasks.filter(t => t.status === 'wait-for-input').length;
1601
+ const cards = [
1602
+ { label: 'Tasks Completed', value: done, tooltip: 'Tasks that finished successfully.', mod: 'done' },
1603
+ { label: 'Tasks Waiting', value: waiting, tooltip: 'Tasks not yet started.', mod: 'waiting' },
1604
+ { label: 'Tasks Running', value: running, tooltip: 'Tasks currently being executed.', mod: 'running' },
1605
+ { label: 'Tasks With Errors', value: errors, tooltip: 'Tasks that stopped due to errors.', mod: 'errors' },
1606
+ { label: 'Waiting For Input', value: waitInput, tooltip: 'Tasks paused until you provide input.', mod: 'input' },
1607
+ ];
1608
+ el.innerHTML = cards.map(card =>
1609
+ '<div class="task-summary-card task-summary-card--' + card.mod + '">' +
1610
+ '<span class="task-summary-card__label">' + escapeHtml(card.label) +
1611
+ ' <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: ' + escapeHtml(card.tooltip) + '" data-tooltip="' + escapeHtml(card.tooltip) + '">\u2139\uFE0F</span>' +
1612
+ '</span>' +
1613
+ '<span class="task-summary-card__value">' + card.value + '</span>' +
1614
+ '</div>'
1615
+ ).join('');
1616
+ }
1617
+
1618
+ function renderTaskTable(tasks) {
1619
+ const el = document.getElementById('task-table-wrap');
1620
+ if (!el) return;
1621
+ if (!tasks || tasks.length === 0) {
1622
+ el.innerHTML = emptyStateHtml('pipeline', 'No tasks', 'This convoy has no tasks recorded.');
1623
+ return;
1624
+ }
1625
+ const cols = [
1626
+ { key: 'phase', label: 'Phase' },
1627
+ { key: 'agent', label: 'Agent' },
1628
+ { key: 'status', label: 'Status' },
1629
+ { key: 'duration', label: 'Duration' },
1630
+ { key: 'retries', label: 'Retries' },
1631
+ { key: 'files', label: 'Files' },
1632
+ { key: 'total_tokens', label: 'Tokens' },
1633
+ ];
1634
+ const sorted = tasks.slice().sort((a, b) => {
1635
+ let av, bv;
1636
+ if (taskSortCol === 'duration') {
1637
+ av = (a.started_at && a.finished_at) ? (new Date(a.finished_at) - new Date(a.started_at)) : -1;
1638
+ bv = (b.started_at && b.finished_at) ? (new Date(b.finished_at) - new Date(b.started_at)) : -1;
1639
+ } else if (taskSortCol === 'files') {
1640
+ av = a.files ? a.files.length : 0;
1641
+ bv = b.files ? b.files.length : 0;
1642
+ } else {
1643
+ av = a[taskSortCol] != null ? a[taskSortCol] : '';
1644
+ bv = b[taskSortCol] != null ? b[taskSortCol] : '';
1645
+ }
1646
+ if (av < bv) return taskSortAsc ? -1 : 1;
1647
+ if (av > bv) return taskSortAsc ? 1 : -1;
1648
+ return 0;
1649
+ });
1650
+ const thead = '<thead><tr>' + cols.map(col => {
1651
+ const isActive = col.key === taskSortCol;
1652
+ const indicator = isActive ? (taskSortAsc ? ' \u25B2' : ' \u25BC') : ' \u25B6';
1653
+ const ariaSort = isActive ? (taskSortAsc ? 'ascending' : 'descending') : 'none';
1654
+ return '<th class="sortable-th' + (isActive ? ' sortable-th--active' : '') + '" data-sort-key="' + col.key + '" scope="col" aria-sort="' + ariaSort + '">' +
1655
+ escapeHtml(col.label) +
1656
+ '<span class="sort-indicator">' + indicator + '</span>' +
1657
+ '</th>';
1658
+ }).join('') + '</tr></thead>';
1659
+ const tbody = '<tbody>' + sorted.map(t => {
1660
+ const statusTip = STATUS_TOOLTIPS[t.status] || '';
1661
+ const statusBadge = '<span class="status-badge status-badge--' + escapeHtml(t.status) + '" title="' + escapeHtml(statusTip) + '" role="status">' + escapeHtml(t.status) + '</span>';
1662
+ const dur = formatDuration(t.started_at, t.finished_at) || '\u2014';
1663
+ const fileCount = t.files ? t.files.length : 0;
1664
+ const fileTip = t.files && t.files.length > 0 ? t.files.join('\n') : '';
1665
+ const filesCell = fileCount > 0
1666
+ ? '<span class="tooltip-trigger" data-tooltip="' + escapeHtml(fileTip) + '">' + fileCount + ' file' + (fileCount !== 1 ? 's' : '') + '</span>'
1667
+ : '<span style="opacity:0.4">\u2014</span>';
1668
+ const tokens = t.total_tokens != null ? formatTokens(t.total_tokens) : '\u2014';
1669
+ return '<tr>' +
1670
+ '<td>' + (t.phase != null ? t.phase : '\u2014') + '</td>' +
1671
+ '<td class="td-agent">' + escapeHtml(t.agent || '\u2014') + '</td>' +
1672
+ '<td>' + statusBadge + '</td>' +
1673
+ '<td>' + dur + '</td>' +
1674
+ '<td class="td-num">' + t.retries + '</td>' +
1675
+ '<td>' + filesCell + '</td>' +
1676
+ '<td class="td-num">' + tokens + '</td>' +
1677
+ '</tr>';
1678
+ }).join('') + '</tbody>';
1679
+ el.innerHTML = '<table class="sessions-table task-table">' + thead + tbody + '</table>';
1680
+ el.querySelectorAll('.sortable-th').forEach(th => {
1681
+ th.addEventListener('click', function() {
1682
+ const key = this.dataset.sortKey;
1683
+ if (key === taskSortCol) {
1684
+ taskSortAsc = !taskSortAsc;
1685
+ } else {
1686
+ taskSortCol = key;
1687
+ taskSortAsc = true;
1688
+ }
1689
+ const convoy = window.__SELECTED_CONVOY__;
1690
+ renderTaskTable((convoy && convoy.tasks) ? convoy.tasks : []);
1691
+ });
1692
+ });
1693
+ }
1694
+
1695
+ function renderPhaseBreakdown(tasks) {
1696
+ const el = document.getElementById('phase-breakdown');
1697
+ if (!el) return;
1698
+ if (!tasks || tasks.length === 0) { el.innerHTML = ''; return; }
1699
+ const phaseMap = {};
1700
+ for (const t of tasks) {
1701
+ const p = t.phase != null ? String(t.phase) : '0';
1702
+ if (!phaseMap[p]) phaseMap[p] = { done: 0, running: 0, waiting: 0, failed: 0 };
1703
+ if (t.status === 'done') phaseMap[p].done++;
1704
+ else if (t.status === 'running') phaseMap[p].running++;
1705
+ else if (t.status === 'pending' || t.status === 'assigned') phaseMap[p].waiting++;
1706
+ else phaseMap[p].failed++;
1707
+ }
1708
+ const phases = Object.keys(phaseMap).sort((a, b) => Number(a) - Number(b));
1709
+ if (phases.length === 0) { el.innerHTML = ''; return; }
1710
+ const html = phases.map(p => {
1711
+ const d = phaseMap[p];
1712
+ const total = d.done + d.running + d.waiting + d.failed;
1713
+ if (total === 0) return '';
1714
+ const pct = v => Math.round((v / total) * 100);
1715
+ const segments = [
1716
+ { cls: 'done', v: d.done },
1717
+ { cls: 'running', v: d.running },
1718
+ { cls: 'waiting', v: d.waiting },
1719
+ { cls: 'failed', v: d.failed },
1720
+ ].filter(s => s.v > 0);
1721
+ return '<div class="phase-breakdown__row">' +
1722
+ '<span class="phase-breakdown__label">Phase ' + escapeHtml(p) + '</span>' +
1723
+ '<div class="phase-breakdown__bar">' +
1724
+ segments.map(s =>
1725
+ '<div class="phase-breakdown__seg phase-breakdown__seg--' + s.cls + '" style="width:' + pct(s.v) + '%" title="' + s.cls + ': ' + s.v + '"></div>'
1726
+ ).join('') +
1727
+ '</div>' +
1728
+ '<span class="phase-breakdown__count">' + total + ' task' + (total !== 1 ? 's' : '') + '</span>' +
1729
+ '</div>';
1730
+ }).join('');
1731
+ el.innerHTML = html;
1732
+ }
1733
+
1734
+ // ── Quality Section ──────────────────────────────────────
1735
+
1736
+ function renderQualitySection(detail) {
1737
+ var section = document.getElementById('quality-section');
1738
+ if (!section) return;
1739
+ section.style.display = '';
1740
+
1741
+ var q = detail.quality || {};
1742
+ var qCards = [
1743
+ { label: 'Tasks Reviewed', value: q.reviewed_tasks != null ? q.reviewed_tasks : 0, tooltip: 'Tasks that completed a code review check.', mod: 'done' },
1744
+ { label: 'Blocked by Review', value: q.review_blocked_tasks != null ? q.review_blocked_tasks : 0, tooltip: 'Tasks paused because a reviewer requested changes.', mod: 'errors' },
1745
+ { label: 'Disputes Opened', value: q.disputed_tasks != null ? q.disputed_tasks : 0, tooltip: 'Tasks where reviewers disagreed and opened a formal dispute.', mod: 'waiting' },
1746
+ { label: 'Panel Reviews', value: q.panel_reviews != null ? q.panel_reviews : 0, tooltip: 'Tasks that went to a 3-reviewer panel vote for a final decision.', mod: 'running' },
1747
+ ];
1748
+
1749
+ var cardsEl = document.getElementById('quality-cards');
1750
+ if (cardsEl) {
1751
+ cardsEl.innerHTML = qCards.map(function(card) {
1752
+ return '<div class="task-summary-card task-summary-card--' + card.mod + '">' +
1753
+ '<span class="task-summary-card__label">' + escapeHtml(card.label) +
1754
+ ' <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: ' + escapeHtml(card.tooltip) + '" data-tooltip="' + escapeHtml(card.tooltip) + '">\u2139\uFE0F</span>' +
1755
+ '</span>' +
1756
+ '<span class="task-summary-card__value">' + card.value + '</span>' +
1757
+ '</div>';
1758
+ }).join('');
1759
+ }
1760
+
1761
+ var reviewTasks = (detail.tasks || []).filter(function(t) { return t.review_level != null; });
1762
+ var tableEl = document.getElementById('quality-review-table');
1763
+ if (!tableEl) return;
1764
+
1765
+ if (reviewTasks.length === 0) {
1766
+ tableEl.innerHTML = emptyStateHtml('panels', 'No reviewed tasks', 'Tasks with code reviews will appear here.');
1767
+ return;
1768
+ }
1769
+
1770
+ tableEl.innerHTML =
1771
+ '<table class="sessions-table" style="margin-top:16px">' +
1772
+ '<thead><tr>' +
1773
+ '<th scope="col">Task ID</th>' +
1774
+ '<th scope="col">Review Level</th>' +
1775
+ '<th scope="col">Verdict</th>' +
1776
+ '<th scope="col">Reviewer Model</th>' +
1777
+ '<th scope="col">Review Tokens</th>' +
1778
+ '<th scope="col">Panel Attempts</th>' +
1779
+ '</tr></thead>' +
1780
+ '<tbody>' +
1781
+ reviewTasks.map(function(t) {
1782
+ var verdictUpper = t.review_verdict ? t.review_verdict.toUpperCase() : '';
1783
+ var verdictBadge = t.review_verdict
1784
+ ? '<span class="status-badge status-badge--' + (verdictUpper === 'PASS' ? 'done' : 'failed') + '">' + escapeHtml(t.review_verdict) + '</span>'
1785
+ : '<span style="opacity:0.4">\u2014</span>';
1786
+ return '<tr>' +
1787
+ '<td class="td-task">' + escapeHtml(t.id || '\u2014') + '</td>' +
1788
+ '<td>' + escapeHtml(t.review_level || '\u2014') + '</td>' +
1789
+ '<td>' + verdictBadge + '</td>' +
1790
+ '<td>' + escapeHtml(t.review_model || '\u2014') + '</td>' +
1791
+ '<td class="td-num">' + (t.review_tokens != null ? formatTokens(t.review_tokens) : '\u2014') + '</td>' +
1792
+ '<td class="td-num">' + (t.panel_attempts != null ? t.panel_attempts : '\u2014') + '</td>' +
1793
+ '</tr>';
1794
+ }).join('') +
1795
+ '</tbody></table>';
1796
+ }
1797
+
1798
+ // ── Reliability Section ──────────────────────────────────
1799
+
1800
+ function renderReliabilitySection(detail) {
1801
+ var section = document.getElementById('reliability-section');
1802
+ if (!section) return;
1803
+ section.style.display = '';
1804
+
1805
+ var dlqCount = detail.dlq_count || 0;
1806
+ var dlqEntries = detail.dlq_entries || [];
1807
+
1808
+ var dlqCardEl = document.getElementById('reliability-dlq-card');
1809
+ if (dlqCardEl) {
1810
+ dlqCardEl.innerHTML =
1811
+ '<div class="task-summary-card task-summary-card--' + (dlqCount > 0 ? 'errors' : 'done') + '">' +
1812
+ '<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).">\u2139\uFE0F</span></span>' +
1813
+ '<span class="task-summary-card__value">' + dlqCount + '</span>' +
1814
+ '</div>';
1815
+ }
1816
+
1817
+ var dlqTableEl = document.getElementById('reliability-dlq-table');
1818
+ if (dlqTableEl) {
1819
+ if (dlqEntries.length === 0) {
1820
+ dlqTableEl.innerHTML = '<p class="reliability-empty">No retry queue entries \u2014 all tasks completed without exceeding retry limits.</p>';
1821
+ } else {
1822
+ dlqTableEl.innerHTML =
1823
+ '<table class="sessions-table" style="margin-top:16px">' +
1824
+ '<thead><tr>' +
1825
+ '<th scope="col">Task ID</th><th scope="col">Agent</th><th scope="col">Failure Type</th><th scope="col">Attempts</th><th scope="col">Resolved</th>' +
1826
+ '</tr></thead>' +
1827
+ '<tbody>' +
1828
+ dlqEntries.map(function(e) {
1829
+ return '<tr>' +
1830
+ '<td class="td-task">' + escapeHtml(e.task_id || '\u2014') + '</td>' +
1831
+ '<td class="td-agent">' + escapeHtml(e.agent || '\u2014') + '</td>' +
1832
+ '<td>' + escapeHtml(e.failure_type || '\u2014') + '</td>' +
1833
+ '<td class="td-num">' + (e.attempts != null ? e.attempts : '\u2014') + '</td>' +
1834
+ '<td class="td-num">' + (e.resolved ? '\u2713' : '\u2014') + '</td>' +
1835
+ '</tr>';
1836
+ }).join('') +
1837
+ '</tbody></table>';
1838
+ }
1839
+ }
1840
+
1841
+ var errorEl = document.getElementById('reliability-error-overview');
1842
+ if (errorEl) {
1843
+ var tasks = detail.tasks || [];
1844
+ var errorCategories = [
1845
+ { key: 'failed', label: 'Failed', color: '#ef4444' },
1846
+ { key: 'gate-failed', label: 'Quality Check Failed', color: '#f59e0b' },
1847
+ { key: 'timed-out', label: 'Timed Out', color: '#a78bfa' },
1848
+ { key: 'hook-failed', label: 'Lifecycle Script Failed', color: '#64748b' },
1849
+ ];
1850
+ var counts = errorCategories.map(function(cat) {
1851
+ return { key: cat.key, label: cat.label, color: cat.color, count: tasks.filter(function(t) { return t.status === cat.key; }).length };
1852
+ });
1853
+ var maxCount = Math.max(1, counts.reduce(function(m, c) { return Math.max(m, c.count); }, 0));
1854
+ var hasErrors = counts.some(function(c) { return c.count > 0; });
1855
+
1856
+ if (!hasErrors) {
1857
+ errorEl.innerHTML = '<p class="reliability-empty">\u2713 No errors in this convoy.</p>';
1858
+ } else {
1859
+ errorEl.innerHTML = counts.map(function(cat) {
1860
+ return '<div class="bar-row">' +
1861
+ '<span class="bar-label">' + escapeHtml(cat.label) + '</span>' +
1862
+ '<div class="bar-track">' +
1863
+ '<div class="bar-segment" style="width: ' + ((cat.count / maxCount) * 100).toFixed(1) + '%; background: ' + cat.color + '"></div>' +
1864
+ '</div>' +
1865
+ '<span class="bar-value">' + cat.count + '</span>' +
1866
+ '</div>';
1867
+ }).join('');
1868
+ }
1869
+ }
1870
+ }
1871
+
1872
+ // ── Drift Section ────────────────────────────────────────
1873
+
1874
+ function renderDriftSection(detail) {
1875
+ var section = document.getElementById('drift-section');
1876
+ if (!section) return;
1877
+ section.style.display = '';
1878
+
1879
+ var d = detail.drift || {};
1880
+ var maxScore = d.max_drift_score != null ? Math.round(d.max_drift_score * 100) : 0;
1881
+ var driftCards = [
1882
+ { label: 'Tasks With Drift', value: d.tasks_with_drift != null ? d.tasks_with_drift : 0, tooltip: 'Tasks where the agent deviated from the original plan or instructions.', mod: 'waiting' },
1883
+ { label: 'Max Drift Score', value: maxScore + '%', tooltip: 'The highest drift score recorded. 0% = on-track, 100% = fully off-plan.', mod: 'errors' },
1884
+ { label: 'Drift Retried', value: d.drift_retried_tasks != null ? d.drift_retried_tasks : 0, tooltip: 'Tasks that were retried because drift exceeded the allowed threshold.', mod: 'running' },
1885
+ ];
1886
+
1887
+ var cardsEl = document.getElementById('drift-cards');
1888
+ if (cardsEl) {
1889
+ cardsEl.innerHTML = driftCards.map(function(card) {
1890
+ return '<div class="task-summary-card task-summary-card--' + card.mod + '">' +
1891
+ '<span class="task-summary-card__label">' + escapeHtml(String(card.label)) +
1892
+ ' <span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: ' + escapeHtml(card.tooltip) + '" data-tooltip="' + escapeHtml(card.tooltip) + '">\u2139\uFE0F</span>' +
1893
+ '</span>' +
1894
+ '<span class="task-summary-card__value">' + card.value + '</span>' +
1895
+ '</div>';
1896
+ }).join('');
1897
+ }
1898
+
1899
+ var bannerEl = document.getElementById('drift-secret-banner');
1900
+ if (bannerEl) {
1901
+ var events = detail.events || [];
1902
+ var leakEvents = events.filter(function(e) { return e.type === 'secret_leak_prevented'; });
1903
+ if (leakEvents.length > 0) {
1904
+ bannerEl.style.display = '';
1905
+ bannerEl.innerHTML =
1906
+ '<div class="secret-leak-banner">' +
1907
+ '<span class="secret-leak-banner__icon">\u26A0\uFE0F</span>' +
1908
+ '<div class="secret-leak-banner__text">' +
1909
+ '<strong>' + leakEvents.length + ' secret leak' + (leakEvents.length !== 1 ? 's' : '') + ' prevented</strong>' +
1910
+ '<span>The security gate blocked credentials or tokens from being exposed. No action needed \u2014 the agent handled it correctly.</span>' +
1911
+ '</div>' +
1912
+ '</div>';
1913
+ } else {
1914
+ bannerEl.style.display = 'none';
1915
+ }
1916
+ }
1917
+ }
1918
+
1919
+ // ── Outputs Section ────────────────────────────────────
1920
+ function renderOutputsSection(detail) {
1921
+ var section = document.getElementById('outputs-section');
1922
+ if (!section) return;
1923
+ section.style.display = '';
1924
+
1925
+ var artifactCount = detail.artifact_count != null ? detail.artifact_count : 0;
1926
+ var artifacts = detail.artifacts || [];
1927
+
1928
+ var cardsEl = document.getElementById('outputs-cards');
1929
+ if (cardsEl) {
1930
+ cardsEl.innerHTML =
1931
+ '<div class="task-summary-card task-summary-card--done">' +
1932
+ '<span class="task-summary-card__label">Outputs Produced ' +
1933
+ '<span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Files, data, and summaries produced by this convoy." data-tooltip="Files, data, and summaries produced by this convoy.">\u2139\uFE0F</span>' +
1934
+ '</span>' +
1935
+ '<span class="task-summary-card__value">' + artifactCount + '</span>' +
1936
+ '</div>';
1937
+ }
1938
+
1939
+ var tableEl = document.getElementById('artifact-table-wrap');
1940
+ if (!tableEl) return;
1941
+
1942
+ if (artifacts.length === 0) {
1943
+ tableEl.innerHTML = emptyStateHtml('pipeline', 'No artifacts', 'Artifacts produced by tasks will appear here.');
1944
+ return;
1945
+ }
1946
+
1947
+ var TYPE_BADGE_COLORS = { file: '#64748b', summary: '#3b82f6', json: '#a78bfa' };
1948
+
1949
+ tableEl.innerHTML =
1950
+ '<table class="sessions-table" style="margin-top:16px">' +
1951
+ '<thead><tr><th scope="col">Name</th><th scope="col">Type</th><th scope="col">Task</th><th scope="col">Created At</th></tr></thead>' +
1952
+ '<tbody>' +
1953
+ artifacts.map(function(a) {
1954
+ var badgeColor = TYPE_BADGE_COLORS[a.type] || '#64748b';
1955
+ var typeBadge = '<span class="artifact-type-badge" style="background:' + badgeColor + '">' + escapeHtml(a.type) + '</span>';
1956
+ var taskCell = a.task_id ? escapeHtml(a.task_id) : '<span style="opacity:0.4">—</span>';
1957
+ var created = a.created_at ? formatTime(a.created_at) : '—';
1958
+ return '<tr>' +
1959
+ '<td>' + escapeHtml(a.name) + '</td>' +
1960
+ '<td>' + typeBadge + '</td>' +
1961
+ '<td class="td-task">' + taskCell + '</td>' +
1962
+ '<td>' + created + '</td>' +
1963
+ '</tr>';
1964
+ }).join('') +
1965
+ '</tbody></table>';
1966
+ }
1967
+
1968
+ // ── Event Timeline Section ─────────────────────────────
1969
+ var eventTimelinePageSize = 50;
1970
+ var eventTimelineCurrentPage = 1;
1971
+ var eventTimelineActiveFilter = 'all';
1972
+ var eventTimelineAllEvents = [];
1973
+
1974
+ var EVENT_CATEGORIES = {
1975
+ lifecycle: ['convoy_started', 'convoy_finished', 'convoy_failed'],
1976
+ tasks: ['task_started', 'task_done', 'task_failed', 'task_assigned', 'task_skipped', 'task_retried', 'task_timed_out', 'task_gate_failed', 'task_hook_failed', 'task_wait_input'],
1977
+ reviews: ['review_started', 'review_verdict', 'panel_started', 'panel_verdict'],
1978
+ system: ['circuit_breaker_open', 'circuit_breaker_close', 'circuit_breaker_half_open', 'merge_conflict_detected', 'merge_conflict_resolved', 'watch_started', 'watch_stopped', 'watch_cycle_start', 'watch_cycle_end', 'discovered_issue', 'secret_leak_prevented'],
1979
+ errors: []
1980
+ };
1981
+
1982
+ var EVENT_TYPE_COLORS = {};
1983
+ EVENT_CATEGORIES.lifecycle.forEach(function(t) { EVENT_TYPE_COLORS[t] = '#3b82f6'; });
1984
+ EVENT_CATEGORIES.tasks.forEach(function(t) { EVENT_TYPE_COLORS[t] = t.includes('fail') || t.includes('timed') ? '#ef4444' : '#10b981'; });
1985
+ EVENT_CATEGORIES.reviews.forEach(function(t) { EVENT_TYPE_COLORS[t] = '#a78bfa'; });
1986
+ EVENT_CATEGORIES.system.forEach(function(t) { EVENT_TYPE_COLORS[t] = '#f59e0b'; });
1987
+
1988
+ function getEventCategory(type) {
1989
+ if (EVENT_CATEGORIES.lifecycle.includes(type)) return 'lifecycle';
1990
+ if (EVENT_CATEGORIES.tasks.includes(type)) return 'tasks';
1991
+ if (EVENT_CATEGORIES.reviews.includes(type)) return 'reviews';
1992
+ if (EVENT_CATEGORIES.system.includes(type)) return 'system';
1993
+ return 'meta';
1994
+ }
1995
+
1996
+ function isErrorEvent(evt) {
1997
+ var type = evt.type || '';
1998
+ return type.includes('fail') || type.includes('block') || type.includes('error') || type.includes('timed_out') || type.includes('circuit_breaker_open');
1999
+ }
2000
+
2001
+ function renderEventTimeline(detail) {
2002
+ var section = document.getElementById('event-timeline-section');
2003
+ if (!section) return;
2004
+ section.style.display = '';
2005
+
2006
+ eventTimelineAllEvents = detail.events || [];
2007
+ eventTimelineCurrentPage = 1;
2008
+ eventTimelineActiveFilter = 'all';
2009
+
2010
+ renderEventTimelineFilters();
2011
+ renderEventTimelineList();
2012
+ }
2013
+
2014
+ function renderEventTimelineFilters() {
2015
+ var el = document.getElementById('event-timeline-filters');
2016
+ if (!el) return;
2017
+
2018
+ var filters = [
2019
+ { key: 'all', label: 'All' },
2020
+ { key: 'lifecycle', label: 'Lifecycle' },
2021
+ { key: 'tasks', label: 'Tasks' },
2022
+ { key: 'reviews', label: 'Reviews' },
2023
+ { key: 'system', label: 'System' },
2024
+ { key: 'errors', label: 'Errors' },
2025
+ ];
2026
+
2027
+ el.innerHTML = filters.map(function(f) {
2028
+ var active = f.key === eventTimelineActiveFilter ? ' timeline-filter-chip--active' : '';
2029
+ return '<button class="timeline-filter-chip' + active + '" data-filter="' + f.key + '" type="button" aria-label="Filter events: ' + escapeHtml(f.label) + '">' + escapeHtml(f.label) + '</button>';
2030
+ }).join('');
2031
+
2032
+ el.querySelectorAll('.timeline-filter-chip').forEach(function(btn) {
2033
+ btn.addEventListener('click', function() {
2034
+ eventTimelineActiveFilter = this.dataset.filter;
2035
+ eventTimelineCurrentPage = 1;
2036
+ renderEventTimelineFilters();
2037
+ renderEventTimelineList();
2038
+ });
2039
+ });
2040
+ }
2041
+
2042
+ function getFilteredEvents() {
2043
+ if (eventTimelineActiveFilter === 'all') return eventTimelineAllEvents;
2044
+ if (eventTimelineActiveFilter === 'errors') return eventTimelineAllEvents.filter(isErrorEvent);
2045
+ var cats = EVENT_CATEGORIES[eventTimelineActiveFilter];
2046
+ if (!cats) return eventTimelineAllEvents;
2047
+ return eventTimelineAllEvents.filter(function(evt) { return cats.includes(evt.type); });
2048
+ }
2049
+
2050
+ function renderEventTimelineList() {
2051
+ var el = document.getElementById('event-timeline-list');
2052
+ if (!el) return;
2053
+
2054
+ var filtered = getFilteredEvents();
2055
+ var visible = filtered.slice(0, eventTimelineCurrentPage * eventTimelinePageSize);
2056
+
2057
+ if (filtered.length === 0) {
2058
+ el.innerHTML = emptyStateHtml('timeline', 'No events', 'Events from this convoy will appear here.');
2059
+ document.getElementById('event-timeline-load-more').style.display = 'none';
2060
+ return;
2061
+ }
2062
+
2063
+ el.innerHTML = visible.map(function(evt, idx) {
2064
+ var typeColor = EVENT_TYPE_COLORS[evt.type] || '#64748b';
2065
+ var ts = evt.created_at ? formatTime(evt.created_at) : '—';
2066
+ var taskInfo = evt.task_id ? '<span class="event-timeline-context">' + escapeHtml(evt.task_id) + '</span>' : '';
2067
+ var dataJson = evt.data ? JSON.stringify(evt.data, null, 2) : null;
2068
+
2069
+ return '<div class="event-timeline-row" data-event-idx="' + idx + '" role="button" tabindex="0" aria-label="Event: ' + escapeHtml(evt.type) + (evt.task_id ? ', task: ' + escapeHtml(evt.task_id) : '') + '">' +
2070
+ '<div class="event-timeline-row__main">' +
2071
+ '<span class="event-timeline-ts">' + ts + '</span>' +
2072
+ '<span class="event-type-badge" style="background:' + typeColor + '">' + escapeHtml(evt.type) + '</span>' +
2073
+ taskInfo +
2074
+ '</div>' +
2075
+ (dataJson
2076
+ ? '<div class="event-timeline-detail" style="display:none"><pre class="event-timeline-json">' + escapeHtml(dataJson) + '</pre></div>'
2077
+ : '') +
2078
+ '</div>';
2079
+ }).join('');
2080
+
2081
+ el.querySelectorAll('.event-timeline-row').forEach(function(row) {
2082
+ row.addEventListener('click', function() {
2083
+ var detailEl = this.querySelector('.event-timeline-detail');
2084
+ if (detailEl) {
2085
+ detailEl.style.display = detailEl.style.display === 'none' ? '' : 'none';
2086
+ this.classList.toggle('event-timeline-row--expanded');
2087
+ }
2088
+ });
2089
+ });
2090
+
2091
+ var loadMoreEl = document.getElementById('event-timeline-load-more');
2092
+ if (loadMoreEl) {
2093
+ loadMoreEl.style.display = visible.length < filtered.length ? '' : 'none';
2094
+ }
2095
+ }
2096
+
1417
2097
  async function main() {
1418
2098
  const events = await loadNdjson(base + 'data/events.ndjson');
1419
- const convoys = await loadNdjson(base + 'data/convoys.ndjson');
1420
2099
  const pipelines = await loadNdjson(base + 'data/pipelines.ndjson');
1421
2100
 
1422
2101
  const sessions = events.filter((e) => e.type === 'session');
@@ -1456,6 +2135,25 @@ Export
1456
2135
  // Apply convoy param after initial render (shows convoy section if needed)
1457
2136
  if (convoyParam) applyFilters();
1458
2137
 
2138
+ // ── Overall stats + convoy selector ──────────────────
2139
+ renderOverallStats();
2140
+ populateConvoySelector();
2141
+
2142
+ const convoySelectEl = document.getElementById('convoy-select');
2143
+ if (convoySelectEl) {
2144
+ convoySelectEl.addEventListener('change', function() {
2145
+ loadConvoyDetail(this.value);
2146
+ });
2147
+ }
2148
+
2149
+ var loadMoreBtn = document.getElementById('event-timeline-more-btn');
2150
+ if (loadMoreBtn) {
2151
+ loadMoreBtn.addEventListener('click', function() {
2152
+ eventTimelineCurrentPage++;
2153
+ renderEventTimelineList();
2154
+ });
2155
+ }
2156
+
1459
2157
  // ── Filter event listeners ────────────────────────────
1460
2158
  document.getElementById('filter-date-from')?.addEventListener('change', applyFilters);
1461
2159
  document.getElementById('filter-date-to')?.addEventListener('change', applyFilters);