subharness 0.0.3 → 0.0.5

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 (241) hide show
  1. package/README.md +39 -9
  2. package/dist/adapters/approval-context.d.ts +2 -0
  3. package/dist/adapters/approval-context.js +9 -0
  4. package/dist/adapters/approval-context.js.map +1 -0
  5. package/dist/adapters/approval-lifetime.d.ts +8 -0
  6. package/dist/adapters/approval-lifetime.js +50 -0
  7. package/dist/adapters/approval-lifetime.js.map +1 -0
  8. package/dist/adapters/claude-approvals.d.ts +4 -0
  9. package/dist/adapters/claude-approvals.js +32 -0
  10. package/dist/adapters/claude-approvals.js.map +1 -0
  11. package/dist/adapters/claude-permissions.js +1 -1
  12. package/dist/adapters/claude-permissions.js.map +1 -1
  13. package/dist/adapters/claude-process.d.ts +3 -0
  14. package/dist/adapters/claude-process.js +11 -1
  15. package/dist/adapters/claude-process.js.map +1 -1
  16. package/dist/adapters/claude-result.d.ts +2 -0
  17. package/dist/adapters/claude-result.js +22 -0
  18. package/dist/adapters/claude-result.js.map +1 -0
  19. package/dist/adapters/claude.js +60 -12
  20. package/dist/adapters/claude.js.map +1 -1
  21. package/dist/adapters/codex-approvals.d.ts +6 -0
  22. package/dist/adapters/codex-approvals.js +111 -0
  23. package/dist/adapters/codex-approvals.js.map +1 -0
  24. package/dist/adapters/codex.js +36 -3
  25. package/dist/adapters/codex.js.map +1 -1
  26. package/dist/adapters/copilot-permissions.d.ts +26 -0
  27. package/dist/adapters/copilot-permissions.js +121 -0
  28. package/dist/adapters/copilot-permissions.js.map +1 -0
  29. package/dist/adapters/copilot-tools.d.ts +12 -0
  30. package/dist/adapters/copilot-tools.js +61 -0
  31. package/dist/adapters/copilot-tools.js.map +1 -0
  32. package/dist/adapters/copilot.d.ts +108 -0
  33. package/dist/adapters/copilot.js +819 -0
  34. package/dist/adapters/copilot.js.map +1 -0
  35. package/dist/adapters/cursor-cli-approvals.d.ts +2 -0
  36. package/dist/adapters/cursor-cli-approvals.js +83 -0
  37. package/dist/adapters/cursor-cli-approvals.js.map +1 -0
  38. package/dist/adapters/cursor-cli-model.d.ts +4 -0
  39. package/dist/adapters/cursor-cli-model.js +64 -0
  40. package/dist/adapters/cursor-cli-model.js.map +1 -0
  41. package/dist/adapters/cursor-cli-session.d.ts +32 -0
  42. package/dist/adapters/cursor-cli-session.js +316 -0
  43. package/dist/adapters/cursor-cli-session.js.map +1 -0
  44. package/dist/adapters/cursor-cli-tools.d.ts +20 -0
  45. package/dist/adapters/cursor-cli-tools.js +181 -0
  46. package/dist/adapters/cursor-cli-tools.js.map +1 -0
  47. package/dist/adapters/cursor-cli.d.ts +2 -0
  48. package/dist/adapters/cursor-cli.js +306 -0
  49. package/dist/adapters/cursor-cli.js.map +1 -0
  50. package/dist/adapters/cursor-model.d.ts +7 -0
  51. package/dist/adapters/cursor-model.js +67 -0
  52. package/dist/adapters/cursor-model.js.map +1 -0
  53. package/dist/adapters/cursor-rpc.d.ts +42 -0
  54. package/dist/adapters/cursor-rpc.js +271 -0
  55. package/dist/adapters/cursor-rpc.js.map +1 -0
  56. package/dist/adapters/cursor-session.d.ts +4 -0
  57. package/dist/adapters/cursor-session.js +327 -0
  58. package/dist/adapters/cursor-session.js.map +1 -0
  59. package/dist/adapters/cursor-startup.d.ts +16 -0
  60. package/dist/adapters/cursor-startup.js +74 -0
  61. package/dist/adapters/cursor-startup.js.map +1 -0
  62. package/dist/adapters/cursor-tools.d.ts +11 -0
  63. package/dist/adapters/cursor-tools.js +49 -0
  64. package/dist/adapters/cursor-tools.js.map +1 -0
  65. package/dist/adapters/cursor.d.ts +6 -0
  66. package/dist/adapters/cursor.js +17 -0
  67. package/dist/adapters/cursor.js.map +1 -0
  68. package/dist/adapters/fx-approvals.d.ts +2 -0
  69. package/dist/adapters/fx-approvals.js +14 -0
  70. package/dist/adapters/fx-approvals.js.map +1 -0
  71. package/dist/adapters/fx-auth.d.ts +12 -2
  72. package/dist/adapters/fx-auth.js +51 -61
  73. package/dist/adapters/fx-auth.js.map +1 -1
  74. package/dist/adapters/fx-profile.d.ts +8 -0
  75. package/dist/adapters/fx-profile.js +101 -0
  76. package/dist/adapters/fx-profile.js.map +1 -0
  77. package/dist/adapters/fx-rpc.d.ts +2 -0
  78. package/dist/adapters/fx-rpc.js +30 -4
  79. package/dist/adapters/fx-rpc.js.map +1 -1
  80. package/dist/adapters/fx-status.d.ts +2 -0
  81. package/dist/adapters/fx-status.js +96 -0
  82. package/dist/adapters/fx-status.js.map +1 -0
  83. package/dist/adapters/fx.js +77 -30
  84. package/dist/adapters/fx.js.map +1 -1
  85. package/dist/adapters/opencode-access.d.ts +13 -0
  86. package/dist/adapters/opencode-access.js +76 -0
  87. package/dist/adapters/opencode-access.js.map +1 -0
  88. package/dist/adapters/opencode-config.d.ts +11 -0
  89. package/dist/adapters/opencode-config.js +238 -0
  90. package/dist/adapters/opencode-config.js.map +1 -0
  91. package/dist/adapters/opencode-http.d.ts +28 -0
  92. package/dist/adapters/opencode-http.js +297 -0
  93. package/dist/adapters/opencode-http.js.map +1 -0
  94. package/dist/adapters/opencode-tools.d.ts +19 -0
  95. package/dist/adapters/opencode-tools.js +127 -0
  96. package/dist/adapters/opencode-tools.js.map +1 -0
  97. package/dist/adapters/opencode.d.ts +2 -0
  98. package/dist/adapters/opencode.js +569 -0
  99. package/dist/adapters/opencode.js.map +1 -0
  100. package/dist/adapters/rpc.d.ts +3 -0
  101. package/dist/adapters/rpc.js +45 -4
  102. package/dist/adapters/rpc.js.map +1 -1
  103. package/dist/adapters/types.d.ts +7 -0
  104. package/dist/adapters/types.js.map +1 -1
  105. package/dist/approvals/schema.d.ts +4 -0
  106. package/dist/approvals/schema.js +70 -0
  107. package/dist/approvals/schema.js.map +1 -0
  108. package/dist/approvals/types.d.ts +47 -0
  109. package/dist/approvals/types.js +2 -0
  110. package/dist/approvals/types.js.map +1 -0
  111. package/dist/cli/args.d.ts +3 -2
  112. package/dist/cli/args.js +24 -3
  113. package/dist/cli/args.js.map +1 -1
  114. package/dist/cli/dashboard-client.d.ts +5 -1
  115. package/dist/cli/dashboard-client.js +162 -15
  116. package/dist/cli/dashboard-client.js.map +1 -1
  117. package/dist/cli/dashboard-controller.d.ts +7 -0
  118. package/dist/cli/dashboard-controller.js +27 -0
  119. package/dist/cli/dashboard-controller.js.map +1 -0
  120. package/dist/cli/dashboard-detail-view.d.ts +16 -0
  121. package/dist/cli/dashboard-detail-view.js +162 -0
  122. package/dist/cli/dashboard-detail-view.js.map +1 -0
  123. package/dist/cli/dashboard-history-view.d.ts +29 -0
  124. package/dist/cli/dashboard-history-view.js +211 -0
  125. package/dist/cli/dashboard-history-view.js.map +1 -0
  126. package/dist/cli/dashboard-input.d.ts +14 -0
  127. package/dist/cli/dashboard-input.js +128 -0
  128. package/dist/cli/dashboard-input.js.map +1 -0
  129. package/dist/cli/dashboard-layout.d.ts +12 -1
  130. package/dist/cli/dashboard-layout.js +32 -30
  131. package/dist/cli/dashboard-layout.js.map +1 -1
  132. package/dist/cli/dashboard-renderer.d.ts +30 -2
  133. package/dist/cli/dashboard-renderer.js +125 -7
  134. package/dist/cli/dashboard-renderer.js.map +1 -1
  135. package/dist/cli/dashboard-style.d.ts +9 -0
  136. package/dist/cli/dashboard-style.js +36 -0
  137. package/dist/cli/dashboard-style.js.map +1 -0
  138. package/dist/cli/dashboard.d.ts +5 -3
  139. package/dist/cli/dashboard.js +102 -12
  140. package/dist/cli/dashboard.js.map +1 -1
  141. package/dist/cli/help.d.ts +1 -1
  142. package/dist/cli/help.js +37 -12
  143. package/dist/cli/help.js.map +1 -1
  144. package/dist/cli/input.d.ts +1 -0
  145. package/dist/cli/input.js +23 -17
  146. package/dist/cli/input.js.map +1 -1
  147. package/dist/cli/main.js +6 -2
  148. package/dist/cli/main.js.map +1 -1
  149. package/dist/cli/output.js +17 -1
  150. package/dist/cli/output.js.map +1 -1
  151. package/dist/config/access-provenance.d.ts +10 -0
  152. package/dist/config/access-provenance.js +31 -0
  153. package/dist/config/access-provenance.js.map +1 -0
  154. package/dist/config/access.d.ts +12 -2
  155. package/dist/config/access.js +125 -27
  156. package/dist/config/access.js.map +1 -1
  157. package/dist/config/loader.js +1 -0
  158. package/dist/config/loader.js.map +1 -1
  159. package/dist/config/oidc.js +4 -2
  160. package/dist/config/oidc.js.map +1 -1
  161. package/dist/config/project.js +2 -1
  162. package/dist/config/project.js.map +1 -1
  163. package/dist/config/resolve-access.js +24 -10
  164. package/dist/config/resolve-access.js.map +1 -1
  165. package/dist/index.d.ts +2 -2
  166. package/dist/index.js +1 -1
  167. package/dist/index.js.map +1 -1
  168. package/dist/process-diagnostics.d.ts +2 -0
  169. package/dist/process-diagnostics.js +11 -0
  170. package/dist/process-diagnostics.js.map +1 -0
  171. package/dist/runtime/access-errors.d.ts +4 -0
  172. package/dist/runtime/access-errors.js +25 -0
  173. package/dist/runtime/access-errors.js.map +1 -0
  174. package/dist/runtime/approval-registry.d.ts +27 -0
  175. package/dist/runtime/approval-registry.js +126 -0
  176. package/dist/runtime/approval-registry.js.map +1 -0
  177. package/dist/runtime/client.js +54 -14
  178. package/dist/runtime/client.js.map +1 -1
  179. package/dist/runtime/coordinator.d.ts +17 -1
  180. package/dist/runtime/coordinator.js +124 -6
  181. package/dist/runtime/coordinator.js.map +1 -1
  182. package/dist/runtime/dashboard-sanitize.d.ts +2 -0
  183. package/dist/runtime/dashboard-sanitize.js +7 -0
  184. package/dist/runtime/dashboard-sanitize.js.map +1 -0
  185. package/dist/runtime/dashboard.d.ts +43 -1
  186. package/dist/runtime/dashboard.js +2 -3
  187. package/dist/runtime/dashboard.js.map +1 -1
  188. package/dist/runtime/definition.js +11 -1
  189. package/dist/runtime/definition.js.map +1 -1
  190. package/dist/runtime/select-native.d.ts +2 -1
  191. package/dist/runtime/select-native.js +28 -10
  192. package/dist/runtime/select-native.js.map +1 -1
  193. package/dist/runtime/service.js +24 -1
  194. package/dist/runtime/service.js.map +1 -1
  195. package/dist/runtime/types.d.ts +9 -1
  196. package/dist/runtime/types.js.map +1 -1
  197. package/dist/runtime/worker-approvals.d.ts +21 -0
  198. package/dist/runtime/worker-approvals.js +51 -0
  199. package/dist/runtime/worker-approvals.js.map +1 -0
  200. package/dist/runtime/worker-client.js +73 -20
  201. package/dist/runtime/worker-client.js.map +1 -1
  202. package/dist/runtime/worker-server.d.ts +2 -0
  203. package/dist/runtime/worker-server.js +39 -10
  204. package/dist/runtime/worker-server.js.map +1 -1
  205. package/dist/sdk/definitions.d.ts +4 -1
  206. package/dist/sdk/definitions.js +16 -2
  207. package/dist/sdk/definitions.js.map +1 -1
  208. package/dist/sdk/permission-validation.js +10 -1
  209. package/dist/sdk/permission-validation.js.map +1 -1
  210. package/dist/sdk/types.d.ts +29 -1
  211. package/dist/sdk/types.js.map +1 -1
  212. package/package.json +4 -1
  213. package/sdk/access-config.md +86 -4
  214. package/sdk/adapter-contract.md +7 -13
  215. package/sdk/additional-harnesses.md +43 -0
  216. package/sdk/agent-skill.md +7 -4
  217. package/sdk/agent.md +5 -3
  218. package/sdk/approvals.md +120 -0
  219. package/sdk/authentication.md +1 -1
  220. package/sdk/cli/dashboard-design.md +44 -4
  221. package/sdk/cli/dashboard-inspection.md +25 -0
  222. package/sdk/cli/index.md +20 -11
  223. package/sdk/cli/output.md +10 -4
  224. package/sdk/completion-notifications.md +2 -0
  225. package/sdk/config.md +1 -1
  226. package/sdk/copilot.md +53 -0
  227. package/sdk/cursor.md +68 -0
  228. package/sdk/diagnostics.md +29 -0
  229. package/sdk/distribution.md +53 -1
  230. package/sdk/evals.md +10 -4
  231. package/sdk/fx.md +51 -6
  232. package/sdk/harnesses.md +13 -13
  233. package/sdk/index.md +4 -2
  234. package/sdk/message-delivery.md +1 -1
  235. package/sdk/opencode.md +57 -0
  236. package/sdk/permissions.md +7 -5
  237. package/sdk/pr-integration.md +1 -1
  238. package/sdk/project-team.md +2 -0
  239. package/sdk/sessions.md +4 -1
  240. package/sdk/tools.md +1 -1
  241. package/sdk/v1-runtime.md +6 -6
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard-layout.js","sourceRoot":"","sources":["../../src/cli/dashboard-layout.ts"],"names":[],"mappings":"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","sourcesContent":["import { stripVTControlCharacters } from 'node:util';\nimport stringWidth from 'string-width';\nimport { dashboardTitle } from '../runtime/dashboard.js';\nimport type { DashboardRow, DashboardWorkspace } from '../runtime/dashboard.js';\n\nexport interface DashboardGroup {\n workspace: DashboardWorkspace;\n rows: DashboardRow[];\n updatedAt: number;\n}\n\nexport interface LabelledDashboardGroup extends DashboardGroup {\n label: string;\n}\n\nexport interface DashboardGrouping {\n groups: DashboardGroup[];\n firstObserved: ReadonlyMap<string, number>;\n}\n\n/** Groups a snapshot without changing coordinator-owned row order. */\nexport function groupDashboardRows(\n rows: DashboardRow[],\n launchWorkspace?: DashboardWorkspace,\n previousFirstObserved: ReadonlyMap<string, number> = new Map(),\n): DashboardGrouping {\n const firstObserved = new Map(previousFirstObserved);\n let nextObserved = firstObserved.size;\n const byDirectory = new Map<string, DashboardGroup>();\n\n for (const row of rows) {\n let group = byDirectory.get(row.workspace.directory);\n if (!group) {\n group = {workspace: row.workspace, rows: [], updatedAt: row.updatedAt};\n byDirectory.set(row.workspace.directory, group);\n if (!firstObserved.has(row.workspace.directory)) {\n firstObserved.set(row.workspace.directory, nextObserved++);\n }\n }\n group.rows.push(row);\n group.updatedAt = Math.max(group.updatedAt, row.updatedAt);\n }\n\n const pinnedDirectory = launchWorkspace?.directory;\n const groups = [...byDirectory.values()].sort((left, right) => {\n const leftPinned = left.workspace.directory === pinnedDirectory;\n const rightPinned = right.workspace.directory === pinnedDirectory;\n if (leftPinned !== rightPinned) return leftPinned ? -1 : 1;\n if (left.updatedAt !== right.updatedAt) return right.updatedAt - left.updatedAt;\n return firstObserved.get(left.workspace.directory)! - firstObserved.get(right.workspace.directory)!;\n });\n\n return {groups, firstObserved};\n}\n\nfunction sanitize(text: string): string {\n return stripVTControlCharacters(text)\n .replace(/[\\u0000-\\u001f\\u007f-\\u009f]/gu, '')\n .replace(/\\s+/gu, ' ')\n .trim();\n}\n\nfunction pathParts(path: string): string[] {\n return path.split(/[\\\\/]+/u).map(sanitize).filter(Boolean);\n}\n\nfunction suffix(parts: string[], depth: number): string {\n const result = parts.slice(-depth).join('/');\n return result || '/';\n}\n\nfunction distinguishingDepth(left: string[], right: string[]): number {\n const limit = Math.max(left.length, right.length, 1);\n for (let depth = 1; depth <= limit; depth++) {\n if (suffix(left, depth) !== suffix(right, depth)) return depth;\n }\n return limit;\n}\n\n/** Adds parent components only when short workspace labels would collide. */\nexport function labelDashboardGroups(groups: DashboardGroup[]): LabelledDashboardGroup[] {\n const paths = groups.map(group => ({\n directory: pathParts(group.workspace.directory),\n repository: group.workspace.repository ? pathParts(group.workspace.repository) : undefined,\n }));\n const depths = groups.map(() => ({directory: 1, repository: 1}));\n const baseLabels = paths.map(path => path.repository\n ? `${suffix(path.repository, 1)} / ${suffix(path.directory, 1)}`\n : suffix(path.directory, 1));\n const collisionBuckets = new Map<string, number[]>();\n for (let index = 0; index < baseLabels.length; index++) {\n const label = baseLabels[index]!;\n const bucket = collisionBuckets.get(label);\n if (bucket) bucket.push(index);\n else collisionBuckets.set(label, [index]);\n }\n\n for (const bucket of collisionBuckets.values()) {\n if (bucket.length < 2) continue;\n for (let leftIndex = 0; leftIndex < bucket.length; leftIndex++) {\n for (let rightIndex = leftIndex + 1; rightIndex < bucket.length; rightIndex++) {\n const left = bucket[leftIndex]!;\n const right = bucket[rightIndex]!;\n const leftPath = paths[left]!;\n const rightPath = paths[right]!;\n const leftDepth = depths[left]!;\n const rightDepth = depths[right]!;\n if (leftPath.repository && rightPath.repository\n && groups[left]!.workspace.repository !== groups[right]!.workspace.repository) {\n const depth = distinguishingDepth(leftPath.repository, rightPath.repository);\n leftDepth.repository = Math.max(leftDepth.repository, depth);\n rightDepth.repository = Math.max(rightDepth.repository, depth);\n } else {\n const depth = distinguishingDepth(leftPath.directory, rightPath.directory);\n leftDepth.directory = Math.max(leftDepth.directory, depth);\n rightDepth.directory = Math.max(rightDepth.directory, depth);\n }\n }\n }\n }\n\n return groups.map((group, index) => {\n const path = paths[index]!;\n const depth = depths[index]!;\n const label = path.repository\n ? `${suffix(path.repository, depth.repository)} / ${suffix(path.directory, depth.directory)}`\n : suffix(path.directory, depth.directory);\n return {...group, label};\n });\n}\n\nconst graphemes = new Intl.Segmenter(undefined, {granularity: 'grapheme'});\n\nexport function clipDashboardText(text: string, columns: number): string {\n if (columns <= 0) return '';\n let result = '';\n let width = 0;\n for (const {segment} of graphemes.segment(text)) {\n const next = stringWidth(segment);\n if (width + next > columns) break;\n result += segment;\n width += next;\n }\n return result;\n}\n\nfunction pad(text: string, columns: number): string {\n const clipped = clipDashboardText(text, columns);\n return `${clipped}${' '.repeat(Math.max(0, columns - stringWidth(clipped)))}`;\n}\n\nfunction maxWidth(values: string[]): number {\n let result = 0;\n for (const value of values) result = Math.max(result, stringWidth(value));\n return result;\n}\n\nexport function dashboardElapsed(row: DashboardRow, now: number): string {\n const end = row.state === 'failed' ? row.failedAt ?? row.finishedAt ?? now : row.finishedAt ?? now;\n const seconds = row.dispatchedAt === undefined ? 0\n : Math.max(0, Math.floor((end - row.dispatchedAt) / 1_000));\n const minutes = Math.floor(seconds / 60);\n const tail = `${String(minutes % 60).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;\n return minutes < 60 ? tail : `${Math.floor(minutes / 60)}:${tail}`;\n}\n\nfunction completionAge(row: DashboardRow, now: number): string | undefined {\n const completedAt = row.state === 'failed' ? row.failedAt ?? row.finishedAt\n : row.state === 'completed' || row.state === 'cancelled' || row.state === 'interrupted'\n ? row.finishedAt\n : undefined;\n if (completedAt === undefined) return undefined;\n const minutes = Math.floor(Math.max(0, now - completedAt) / 60_000);\n return minutes === 0 ? '<1m ago' : `${minutes}m ago`;\n}\n\nfunction dashboardMarker(row: DashboardRow): string {\n if (row.state === 'running') return '●';\n if (row.state === 'queued' || row.state === 'waiting') return '◌';\n if (row.state === 'failed' || row.state === 'interrupted') return '!';\n return ' ';\n}\n\nexport function formatDashboardRows(rows: DashboardRow[], columns: number, now: number): string[] {\n const harnesses = rows.map(row => sanitize(row.harness));\n const titles = rows.map(row => dashboardTitle(row.title));\n const states = rows.map(row => sanitize(row.state));\n const elapsed = rows.map(row => dashboardElapsed(row, now));\n const ages = rows.map(row => completionAge(row, now));\n const harnessWidth = maxWidth(harnesses);\n const titleNaturalWidth = maxWidth(titles);\n const stateWidth = maxWidth(states);\n const elapsedWidth = maxWidth(elapsed);\n const ageWidth = maxWidth(ages.filter((age): age is string => age !== undefined));\n const fixedWidth = 2 + 1 + 1 + harnessWidth + 2 + 2 + stateWidth + 2 + elapsedWidth;\n const showAge = ageWidth > 0 && fixedWidth + titleNaturalWidth + 2 + ageWidth <= columns;\n const ageSpace = showAge ? 2 + ageWidth : 0;\n const titleWidth = Math.min(titleNaturalWidth, Math.max(0, columns - fixedWidth - ageSpace));\n\n return rows.map((row, index) => {\n const age = showAge && ages[index] ? ` ${ages[index]}` : '';\n const line = ` ${dashboardMarker(row)} ${pad(harnesses[index]!, harnessWidth)} `\n + `${pad(titles[index]!, titleWidth)} ${pad(states[index]!, stateWidth)} `\n + `${pad(elapsed[index]!, elapsedWidth)}${age}`;\n return clipDashboardText(line, columns);\n });\n}\n\nexport function layoutDashboardGroups(\n groups: LabelledDashboardGroup[],\n columns: number,\n now: number,\n maxLines = Number.POSITIVE_INFINITY,\n): string[] {\n if (maxLines <= 0) return [];\n const visible: Array<{kind: 'blank'} | {kind: 'label'; text: string} | {kind: 'row'; row: DashboardRow}> = [];\n groupLoop:\n for (const [groupIndex, group] of groups.entries()) {\n if (groupIndex > 0) {\n visible.push({kind: 'blank'});\n if (visible.length >= maxLines) break;\n }\n visible.push({kind: 'label', text: group.label});\n if (visible.length >= maxLines) break;\n for (const row of group.rows) {\n visible.push({kind: 'row', row});\n if (visible.length >= maxLines) break groupLoop;\n }\n }\n const formattedRows = formatDashboardRows(\n visible.flatMap(slot => slot.kind === 'row' ? [slot.row] : []),\n columns,\n now,\n );\n let rowIndex = 0;\n return visible.map(slot => {\n if (slot.kind === 'blank') return '';\n if (slot.kind === 'label') return clipDashboardText(sanitize(slot.text), columns);\n return formattedRows[rowIndex++]!;\n });\n}\n"]}
1
+ {"version":3,"file":"dashboard-layout.js","sourceRoot":"","sources":["../../src/cli/dashboard-layout.ts"],"names":[],"mappings":"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","sourcesContent":["import stringWidth from 'string-width';\nimport {styledLine, stateStyle, workspaceLine} from './dashboard-style.ts';\nimport {sanitizeDashboardTerminalText} from '../runtime/dashboard-sanitize.ts';\nimport { dashboardTitle } from '../runtime/dashboard.ts';\nimport type { DashboardRow, DashboardWorkspace } from '../runtime/dashboard.js';\n\nexport interface DashboardGroup {\n workspace: DashboardWorkspace;\n rows: DashboardRow[];\n updatedAt: number;\n}\n\nexport interface LabelledDashboardGroup extends DashboardGroup {\n label: string;\n}\n\nexport interface DashboardGrouping {\n groups: DashboardGroup[];\n firstObserved: ReadonlyMap<string, number>;\n}\n\n/** Groups a snapshot without changing coordinator-owned row order. */\nexport function groupDashboardRows(\n rows: DashboardRow[],\n launchWorkspace?: DashboardWorkspace,\n previousFirstObserved: ReadonlyMap<string, number> = new Map(),\n): DashboardGrouping {\n const firstObserved = new Map(previousFirstObserved);\n let nextObserved = firstObserved.size;\n const byDirectory = new Map<string, DashboardGroup>();\n\n for (const row of rows) {\n let group = byDirectory.get(row.workspace.directory);\n if (!group) {\n group = {workspace: row.workspace, rows: [], updatedAt: row.updatedAt};\n byDirectory.set(row.workspace.directory, group);\n if (!firstObserved.has(row.workspace.directory)) {\n firstObserved.set(row.workspace.directory, nextObserved++);\n }\n }\n group.rows.push(row);\n group.updatedAt = Math.max(group.updatedAt, row.updatedAt);\n }\n\n const pinnedDirectory = launchWorkspace?.directory;\n const groups = [...byDirectory.values()].sort((left, right) => {\n const leftPinned = left.workspace.directory === pinnedDirectory;\n const rightPinned = right.workspace.directory === pinnedDirectory;\n if (leftPinned !== rightPinned) return leftPinned ? -1 : 1;\n if (left.updatedAt !== right.updatedAt) return right.updatedAt - left.updatedAt;\n return firstObserved.get(left.workspace.directory)! - firstObserved.get(right.workspace.directory)!;\n });\n\n return {groups, firstObserved};\n}\n\nfunction sanitize(text: string): string {\n return sanitizeDashboardTerminalText(text)\n .replace(/[\\t\\n\\r]/gu, '')\n .replace(/\\s+/gu, ' ')\n .trim();\n}\n\nfunction pathParts(path: string): string[] {\n return path.split(/[\\\\/]+/u).map(sanitize).filter(Boolean);\n}\n\nfunction suffix(parts: string[], depth: number): string {\n const result = parts.slice(-depth).join('/');\n return result || '/';\n}\n\nfunction distinguishingDepth(left: string[], right: string[]): number {\n const limit = Math.max(left.length, right.length, 1);\n for (let depth = 1; depth <= limit; depth++) {\n if (suffix(left, depth) !== suffix(right, depth)) return depth;\n }\n return limit;\n}\n\n/** Adds parent components only when short workspace labels would collide. */\nexport function labelDashboardGroups(groups: DashboardGroup[]): LabelledDashboardGroup[] {\n const paths = groups.map(group => ({\n directory: pathParts(group.workspace.directory),\n repository: group.workspace.repository ? pathParts(group.workspace.repository) : undefined,\n }));\n const depths = groups.map(() => ({directory: 1, repository: 1}));\n const baseLabels = paths.map(path => path.repository\n ? `${suffix(path.repository, 1)} / ${suffix(path.directory, 1)}`\n : suffix(path.directory, 1));\n const collisionBuckets = new Map<string, number[]>();\n for (let index = 0; index < baseLabels.length; index++) {\n const label = baseLabels[index]!;\n const bucket = collisionBuckets.get(label);\n if (bucket) bucket.push(index);\n else collisionBuckets.set(label, [index]);\n }\n\n for (const bucket of collisionBuckets.values()) {\n if (bucket.length < 2) continue;\n for (let leftIndex = 0; leftIndex < bucket.length; leftIndex++) {\n for (let rightIndex = leftIndex + 1; rightIndex < bucket.length; rightIndex++) {\n const left = bucket[leftIndex]!;\n const right = bucket[rightIndex]!;\n const leftPath = paths[left]!;\n const rightPath = paths[right]!;\n const leftDepth = depths[left]!;\n const rightDepth = depths[right]!;\n if (leftPath.repository && rightPath.repository\n && groups[left]!.workspace.repository !== groups[right]!.workspace.repository) {\n const depth = distinguishingDepth(leftPath.repository, rightPath.repository);\n leftDepth.repository = Math.max(leftDepth.repository, depth);\n rightDepth.repository = Math.max(rightDepth.repository, depth);\n } else {\n const depth = distinguishingDepth(leftPath.directory, rightPath.directory);\n leftDepth.directory = Math.max(leftDepth.directory, depth);\n rightDepth.directory = Math.max(rightDepth.directory, depth);\n }\n }\n }\n }\n\n return groups.map((group, index) => {\n const path = paths[index]!;\n const depth = depths[index]!;\n const label = path.repository\n ? `${suffix(path.repository, depth.repository)} / ${suffix(path.directory, depth.directory)}`\n : suffix(path.directory, depth.directory);\n return {...group, label};\n });\n}\n\nconst graphemes = new Intl.Segmenter(undefined, {granularity: 'grapheme'});\n\nexport function clipDashboardText(text: string, columns: number): string {\n if (columns <= 0) return '';\n let result = '';\n let width = 0;\n for (const {segment} of graphemes.segment(text)) {\n const next = stringWidth(segment);\n if (width + next > columns) break;\n result += segment;\n width += next;\n }\n return result;\n}\n\nfunction pad(text: string, columns: number): string {\n const clipped = clipDashboardText(text, columns);\n return `${clipped}${' '.repeat(Math.max(0, columns - stringWidth(clipped)))}`;\n}\n\nfunction maxWidth(values: string[]): number {\n let result = 0;\n for (const value of values) result = Math.max(result, stringWidth(value));\n return result;\n}\n\nexport function dashboardElapsed(row: DashboardRow, now: number): string {\n const end = row.state === 'failed' ? row.failedAt ?? row.finishedAt ?? now : row.finishedAt ?? now;\n const seconds = row.dispatchedAt === undefined ? 0\n : Math.max(0, Math.floor((end - row.dispatchedAt) / 1_000));\n const minutes = Math.floor(seconds / 60);\n const tail = `${String(minutes % 60).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;\n return minutes < 60 ? tail : `${Math.floor(minutes / 60)}:${tail}`;\n}\n\nfunction completionAge(row: DashboardRow, now: number): string | undefined {\n const completedAt = row.state === 'failed' ? row.failedAt ?? row.finishedAt\n : row.state === 'completed' || row.state === 'cancelled' || row.state === 'interrupted'\n ? row.finishedAt\n : undefined;\n if (completedAt === undefined) return undefined;\n const minutes = Math.floor(Math.max(0, now - completedAt) / 60_000);\n return minutes === 0 ? '<1m ago' : `${minutes}m ago`;\n}\n\nfunction dashboardMarker(row: DashboardRow): string {\n if (row.state === 'running') return '●';\n if (row.state === 'queued' || row.state === 'waiting' || row.state === 'awaiting_approval') return '◌';\n if (row.state === 'failed' || row.state === 'interrupted') return '!';\n return ' ';\n}\n\nexport function formatDashboardRows(rows: DashboardRow[], columns: number, now: number, selectedTaskId?: string, color = false): string[] {\n const harnesses = rows.map(row => sanitize(row.harness));\n const titles = rows.map(row => dashboardTitle(row.title));\n const states = rows.map(row => sanitize(row.state));\n const elapsed = rows.map(row => dashboardElapsed(row, now));\n const ages = rows.map(row => completionAge(row, now));\n const harnessWidth = maxWidth(harnesses);\n const titleNaturalWidth = maxWidth(titles);\n const stateWidth = maxWidth(states);\n const elapsedWidth = maxWidth(elapsed);\n const ageWidth = maxWidth(ages.filter((age): age is string => age !== undefined));\n const fixedWidth = 2 + 1 + 1 + harnessWidth + 2 + 2 + stateWidth + 2 + elapsedWidth;\n const showAge = ageWidth > 0 && fixedWidth + titleNaturalWidth + 2 + ageWidth <= columns;\n const ageSpace = showAge ? 2 + ageWidth : 0;\n const titleWidth = Math.min(titleNaturalWidth, Math.max(0, columns - fixedWidth - ageSpace));\n\n return rows.map((row, index) => {\n const age = showAge && ages[index] ? ` ${ages[index]}` : '';\n return styledLine([\n {text: `${row.taskId === selectedTaskId ? '› ' : ' '}${dashboardMarker(row)} `, style:stateStyle(row.state)},\n {text: pad(titles[index]!, titleWidth), style:row.taskId===selectedTaskId?'1':row.state==='completed'?'2':undefined},\n {text: ` ${pad(harnesses[index]!, harnessWidth)} `,style:'2'},\n {text: pad(states[index]!, stateWidth),style:stateStyle(row.state)},\n {text: ` ${pad(elapsed[index]!, elapsedWidth)}${age}`,style:'2'},\n ],columns,color,row.taskId===selectedTaskId);\n });\n}\n\nexport type DashboardOverviewSlot = {kind: 'blank'} | {kind: 'label'; text: string}\n | {kind: 'row'; row: DashboardRow};\n\nexport function dashboardOverviewSlots(groups: LabelledDashboardGroup[]): DashboardOverviewSlot[] {\n const slots: DashboardOverviewSlot[] = [];\n for (const [groupIndex, group] of groups.entries()) {\n if (groupIndex > 0) slots.push({kind: 'blank'});\n slots.push({kind: 'label', text: group.label}, {kind: 'blank'});\n for (const row of group.rows) slots.push({kind: 'row', row});\n }\n return slots;\n}\n\nexport function layoutDashboardSlots(\n slots: DashboardOverviewSlot[],\n columns: number,\n now: number,\n selectedTaskId?: string,\n color = false,\n): string[] {\n const formattedRows = formatDashboardRows(\n slots.flatMap(slot => slot.kind === 'row' ? [slot.row] : []),\n columns,\n now,\n selectedTaskId,\n color,\n );\n let rowIndex = 0;\n return slots.map(slot => {\n if (slot.kind === 'blank') return '';\n if (slot.kind === 'label') return workspaceLine(slot.text, columns, color);\n return formattedRows[rowIndex++]!;\n });\n}\n\nexport function layoutDashboardGroups(\n groups: LabelledDashboardGroup[],\n columns: number,\n now: number,\n maxLines = Number.POSITIVE_INFINITY,\n): string[] {\n if (maxLines <= 0) return [];\n return layoutDashboardSlots(dashboardOverviewSlots(groups).slice(0, maxLines), columns, now);\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { DashboardRow, DashboardWorkspace } from '../runtime/dashboard.js';
1
+ import type { DashboardDetail, DashboardRow, DashboardSession, DashboardWorkspace } from '../runtime/dashboard.js';
2
2
  export interface DashboardOutput {
3
3
  columns?: number;
4
4
  rows?: number;
@@ -11,6 +11,8 @@ export declare function formatDashboardRow(row: DashboardRow, columns: number, n
11
11
  export declare class DashboardRenderer {
12
12
  private readonly output;
13
13
  private readonly launchWorkspace?;
14
+ private readonly color;
15
+ private readonly onFailure?;
14
16
  private previous;
15
17
  private current;
16
18
  private now;
@@ -20,11 +22,37 @@ export declare class DashboardRenderer {
20
22
  private firstObserved;
21
23
  private labelMembership;
22
24
  private labels;
25
+ private groups;
26
+ private orderedTaskIds;
27
+ private selectedId?;
28
+ private overviewOffset;
29
+ private view;
30
+ private inspectionId?;
31
+ private history?;
23
32
  private readonly drain;
24
- constructor(output: DashboardOutput, launchWorkspace?: DashboardWorkspace | undefined);
33
+ constructor(output: DashboardOutput, launchWorkspace?: DashboardWorkspace | undefined, color?: boolean, onFailure?: ((error: unknown) => void) | undefined);
25
34
  start(): void;
26
35
  render(rows: DashboardRow[], now: number): void;
36
+ get selectedTaskId(): string | undefined;
37
+ get inspectedTaskId(): string | undefined;
38
+ get expandedTaskId(): string | undefined;
39
+ get historySelectedTaskId(): string | undefined;
40
+ get historyLoaded(): boolean;
41
+ get knownResponseId(): string | undefined;
42
+ get detailLoaded(): boolean;
43
+ get inspecting(): boolean;
44
+ moveSelection(delta: -1 | 1): void;
45
+ openDetail(): string | undefined;
46
+ updateSession(session: DashboardSession): boolean;
47
+ toggleExpansion(): boolean;
48
+ updateDetail(detail: DashboardDetail): boolean;
49
+ leaveDetail(): boolean;
50
+ scrollDetail(key: 'up' | 'down' | 'page-up' | 'page-down' | 'home' | 'end'): void;
51
+ redraw(): void;
52
+ private prepareOverview;
27
53
  private draw;
54
+ private overviewLines;
55
+ private detailLines;
28
56
  private labelGroups;
29
57
  close(): void;
30
58
  }
@@ -1,4 +1,5 @@
1
- import { formatDashboardRows, groupDashboardRows, labelDashboardGroups, layoutDashboardGroups, } from './dashboard-layout.js';
1
+ import { DashboardHistoryView } from './dashboard-history-view.js';
2
+ import { dashboardOverviewSlots, formatDashboardRows, groupDashboardRows, labelDashboardGroups, layoutDashboardSlots, } from './dashboard-layout.js';
2
3
  const enter = '\u001b[?1049h\u001b[?25l\u001b[2J';
3
4
  const leave = '\u001b[?25h\u001b[?1049l';
4
5
  export function formatDashboardRow(row, columns, now) {
@@ -8,6 +9,8 @@ export function formatDashboardRow(row, columns, now) {
8
9
  export class DashboardRenderer {
9
10
  output;
10
11
  launchWorkspace;
12
+ color;
13
+ onFailure;
11
14
  previous = [];
12
15
  current = [];
13
16
  now = 0;
@@ -17,10 +20,29 @@ export class DashboardRenderer {
17
20
  firstObserved = new Map();
18
21
  labelMembership = new Map();
19
22
  labels = new Map();
20
- drain = () => { this.blocked = false; this.draw(); };
21
- constructor(output, launchWorkspace) {
23
+ groups = [];
24
+ orderedTaskIds = [];
25
+ selectedId;
26
+ overviewOffset = 0;
27
+ view = 'overview';
28
+ inspectionId;
29
+ history;
30
+ drain = () => {
31
+ this.blocked = false;
32
+ try {
33
+ this.draw();
34
+ }
35
+ catch (error) {
36
+ if (!this.onFailure)
37
+ throw error;
38
+ this.onFailure(error);
39
+ }
40
+ };
41
+ constructor(output, launchWorkspace, color = false, onFailure) {
22
42
  this.output = output;
23
43
  this.launchWorkspace = launchWorkspace;
44
+ this.color = color;
45
+ this.onFailure = onFailure;
24
46
  }
25
47
  start() {
26
48
  this.active = true;
@@ -30,17 +52,94 @@ export class DashboardRenderer {
30
52
  render(rows, now) {
31
53
  this.current = rows;
32
54
  this.now = now;
55
+ this.prepareOverview();
56
+ this.draw();
57
+ }
58
+ get selectedTaskId() { return this.selectedId; }
59
+ get inspectedTaskId() { return this.view === 'detail' ? this.inspectionId : undefined; }
60
+ get expandedTaskId() { return this.history?.expandedTaskId; }
61
+ get historySelectedTaskId() { return this.history?.selectedTaskId; }
62
+ get historyLoaded() { return this.history !== undefined; }
63
+ get knownResponseId() { return this.history?.responseId; }
64
+ get detailLoaded() { return this.history?.detailLoaded ?? false; }
65
+ get inspecting() { return this.view === 'detail'; }
66
+ moveSelection(delta) {
67
+ if (this.view !== 'overview' || !this.selectedId)
68
+ return;
69
+ const index = this.orderedTaskIds.indexOf(this.selectedId);
70
+ const next = Math.max(0, Math.min(this.orderedTaskIds.length - 1, index + delta));
71
+ this.selectedId = this.orderedTaskIds[next];
72
+ this.draw();
73
+ }
74
+ openDetail() {
75
+ if (this.view !== 'overview' || !this.selectedId)
76
+ return;
77
+ this.view = 'detail';
78
+ this.inspectionId = this.selectedId;
79
+ this.history = undefined;
80
+ this.draw();
81
+ return this.inspectionId;
82
+ }
83
+ updateSession(session) {
84
+ if (this.view !== 'detail' || session.taskId !== this.inspectionId)
85
+ return false;
86
+ if (this.history)
87
+ this.history.update(session);
88
+ else
89
+ this.history = new DashboardHistoryView(session, this.inspectionId);
90
+ this.draw();
91
+ return true;
92
+ }
93
+ toggleExpansion() {
94
+ if (this.view !== 'detail' || !this.history)
95
+ return false;
96
+ this.history.toggleExpansion();
97
+ this.draw();
98
+ return true;
99
+ }
100
+ updateDetail(detail) {
101
+ if (!this.history?.updateDetail(detail))
102
+ return false;
33
103
  this.draw();
104
+ return true;
105
+ }
106
+ leaveDetail() {
107
+ if (this.view !== 'detail')
108
+ return false;
109
+ this.view = 'overview';
110
+ this.inspectionId = undefined;
111
+ this.history = undefined;
112
+ this.draw();
113
+ return true;
114
+ }
115
+ scrollDetail(key) {
116
+ if (this.view !== 'detail')
117
+ return;
118
+ this.history?.navigate(key, Math.max(0, this.output.columns ?? 80), Math.max(0, this.output.rows ?? 24));
119
+ this.draw();
120
+ }
121
+ redraw() { this.draw(); }
122
+ prepareOverview() {
123
+ const previousIds = this.orderedTaskIds;
124
+ const previousIndex = this.selectedId ? previousIds.indexOf(this.selectedId) : -1;
125
+ const grouping = groupDashboardRows(this.current, this.launchWorkspace, this.firstObserved);
126
+ this.firstObserved = grouping.firstObserved;
127
+ this.groups = this.labelGroups(grouping.groups);
128
+ this.orderedTaskIds = this.groups.flatMap(group => group.rows.map(row => row.taskId));
129
+ if (!this.orderedTaskIds.length)
130
+ this.selectedId = undefined;
131
+ else if (!this.selectedId || !this.orderedTaskIds.includes(this.selectedId)) {
132
+ this.selectedId = this.orderedTaskIds[Math.min(Math.max(0, previousIndex), this.orderedTaskIds.length - 1)];
133
+ }
34
134
  }
35
135
  draw() {
36
136
  if (!this.active || this.blocked)
37
137
  return;
38
138
  const height = Math.max(0, this.output.rows ?? 24);
39
139
  const width = Math.max(0, this.output.columns ?? 80);
40
- const grouping = groupDashboardRows(this.current, this.launchWorkspace, this.firstObserved);
41
- this.firstObserved = grouping.firstObserved;
42
- const labelled = this.labelGroups(grouping.groups);
43
- const lines = layoutDashboardGroups(labelled, width, this.now, height);
140
+ const lines = this.view === 'detail'
141
+ ? this.detailLines(width, height)
142
+ : this.overviewLines(width, height);
44
143
  const size = `${width}:${height}`;
45
144
  let frame = this.size && this.size !== size ? '\u001b[2J' : '';
46
145
  if (frame)
@@ -55,6 +154,25 @@ export class DashboardRenderer {
55
154
  if (frame)
56
155
  this.blocked = !this.output.write(frame);
57
156
  }
157
+ overviewLines(width, height) {
158
+ const slots = dashboardOverviewSlots(this.groups);
159
+ const selectedLine = slots.findIndex(slot => slot.kind === 'row' && slot.row.taskId === this.selectedId);
160
+ const max = Math.max(0, slots.length - height);
161
+ if (selectedLine >= 0 && selectedLine < this.overviewOffset)
162
+ this.overviewOffset = selectedLine;
163
+ else if (selectedLine >= this.overviewOffset + height)
164
+ this.overviewOffset = selectedLine - height + 1;
165
+ this.overviewOffset = Math.max(0, Math.min(max, this.overviewOffset));
166
+ return layoutDashboardSlots(slots.slice(this.overviewOffset, this.overviewOffset + height), width, this.now, this.selectedId, this.color);
167
+ }
168
+ detailLines(width, height) {
169
+ if (!this.history)
170
+ return [];
171
+ const workspace = this.history.workspace;
172
+ const label = this.labels.get(workspace.directory)
173
+ ?? labelDashboardGroups([{ workspace, rows: [], updatedAt: 0 }])[0].label;
174
+ return this.history.lines(label, width, height, this.now, this.color);
175
+ }
58
176
  labelGroups(groups) {
59
177
  const membershipChanged = groups.length !== this.labelMembership.size
60
178
  || groups.some(group => !this.labelMembership.has(group.workspace.directory)
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard-renderer.js","sourceRoot":"","sources":["../../src/cli/dashboard-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DashboardRow, DashboardWorkspace } from '../runtime/dashboard.js';\nimport {\n formatDashboardRows,\n groupDashboardRows,\n labelDashboardGroups,\n layoutDashboardGroups,\n type DashboardGroup,\n type LabelledDashboardGroup,\n} from './dashboard-layout.js';\n\nexport interface DashboardOutput {\n columns?: number;\n rows?: number;\n write(text: string): boolean;\n on(event: 'drain', listener: () => void): unknown;\n off(event: 'drain', listener: () => void): unknown;\n}\n\nconst enter = '\\u001b[?1049h\\u001b[?25l\\u001b[2J';\nconst leave = '\\u001b[?25h\\u001b[?1049l';\n\nexport function formatDashboardRow(row: DashboardRow, columns: number, now: number): string {\n return formatDashboardRows([row], columns, now)[0] ?? '';\n}\n\n/** Keeps one pending snapshot when the terminal applies backpressure. */\nexport class DashboardRenderer {\n private previous: string[] = [];\n private current: DashboardRow[] = [];\n private now = 0;\n private blocked = false;\n private active = false;\n private size = '';\n private firstObserved: ReadonlyMap<string, number> = new Map();\n private labelMembership = new Map<string, string | undefined>();\n private labels = new Map<string, string>();\n private readonly drain = () => { this.blocked = false; this.draw(); };\n\n constructor(\n private readonly output: DashboardOutput,\n private readonly launchWorkspace?: DashboardWorkspace,\n ) {}\n\n start() {\n this.active = true;\n this.output.on('drain', this.drain);\n this.blocked = !this.output.write(enter);\n }\n\n render(rows: DashboardRow[], now: number) {\n this.current = rows;\n this.now = now;\n this.draw();\n }\n\n private draw() {\n if (!this.active || this.blocked) return;\n const height = Math.max(0, this.output.rows ?? 24);\n const width = Math.max(0, this.output.columns ?? 80);\n const grouping = groupDashboardRows(this.current, this.launchWorkspace, this.firstObserved);\n this.firstObserved = grouping.firstObserved;\n const labelled = this.labelGroups(grouping.groups);\n const lines = layoutDashboardGroups(labelled, width, this.now, height);\n const size = `${width}:${height}`;\n let frame = this.size && this.size !== size ? '\\u001b[2J' : '';\n if (frame) this.previous = [];\n this.size = size;\n for (let index = 0; index < Math.min(height, Math.max(lines.length, this.previous.length)); index++) {\n if ((lines[index] ?? '') !== (this.previous[index] ?? '')) {\n frame += `\\u001b[${index + 1};1H\\u001b[2K${lines[index] ?? ''}`;\n }\n }\n this.previous = lines;\n if (frame) this.blocked = !this.output.write(frame);\n }\n\n private labelGroups(groups: DashboardGroup[]): LabelledDashboardGroup[] {\n const membershipChanged = groups.length !== this.labelMembership.size\n || groups.some(group => !this.labelMembership.has(group.workspace.directory)\n || this.labelMembership.get(group.workspace.directory) !== group.workspace.repository);\n if (membershipChanged) {\n this.labelMembership = new Map(groups.map(group => [\n group.workspace.directory,\n group.workspace.repository,\n ]));\n this.labels = new Map(labelDashboardGroups(groups)\n .map(group => [group.workspace.directory, group.label]));\n }\n return groups.map(group => ({...group, label: this.labels.get(group.workspace.directory)!}));\n }\n\n close() {\n if (!this.active) return;\n this.active = false;\n this.output.off('drain', this.drain);\n this.output.write(leave);\n }\n}\n"]}
1
+ {"version":3,"file":"dashboard-renderer.js","sourceRoot":"","sources":["../../src/cli/dashboard-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DashboardDetail, DashboardRow, DashboardSession, DashboardWorkspace } from '../runtime/dashboard.js';\nimport { DashboardHistoryView } from './dashboard-history-view.ts';\nimport {\n dashboardOverviewSlots,\n formatDashboardRows,\n groupDashboardRows,\n labelDashboardGroups,\n layoutDashboardSlots,\n type DashboardGroup,\n type LabelledDashboardGroup,\n} from './dashboard-layout.ts';\n\nexport interface DashboardOutput {\n columns?: number;\n rows?: number;\n write(text: string): boolean;\n on(event: 'drain', listener: () => void): unknown;\n off(event: 'drain', listener: () => void): unknown;\n}\n\nconst enter = '\\u001b[?1049h\\u001b[?25l\\u001b[2J';\nconst leave = '\\u001b[?25h\\u001b[?1049l';\n\nexport function formatDashboardRow(row: DashboardRow, columns: number, now: number): string {\n return formatDashboardRows([row], columns, now)[0] ?? '';\n}\n\n/** Keeps one pending snapshot when the terminal applies backpressure. */\nexport class DashboardRenderer {\n private previous: string[] = [];\n private current: DashboardRow[] = [];\n private now = 0;\n private blocked = false;\n private active = false;\n private size = '';\n private firstObserved: ReadonlyMap<string, number> = new Map();\n private labelMembership = new Map<string, string | undefined>();\n private labels = new Map<string, string>();\n private groups: LabelledDashboardGroup[] = [];\n private orderedTaskIds: string[] = [];\n private selectedId?: string;\n private overviewOffset = 0;\n private view: 'overview' | 'detail' = 'overview';\n private inspectionId?: string;\n private history?: DashboardHistoryView;\n private readonly drain = () => {\n this.blocked = false;\n try { this.draw(); } catch (error) {\n if (!this.onFailure) throw error;\n this.onFailure(error);\n }\n };\n\n constructor(\n private readonly output: DashboardOutput,\n private readonly launchWorkspace?: DashboardWorkspace,\n private readonly color = false,\n private readonly onFailure?: (error: unknown) => void,\n ) {}\n\n start() {\n this.active = true;\n this.output.on('drain', this.drain);\n this.blocked = !this.output.write(enter);\n }\n\n render(rows: DashboardRow[], now: number) {\n this.current = rows;\n this.now = now;\n this.prepareOverview();\n this.draw();\n }\n\n get selectedTaskId() { return this.selectedId; }\n get inspectedTaskId() { return this.view === 'detail' ? this.inspectionId : undefined; }\n get expandedTaskId() { return this.history?.expandedTaskId; }\n get historySelectedTaskId() { return this.history?.selectedTaskId; }\n get historyLoaded() { return this.history !== undefined; }\n get knownResponseId() { return this.history?.responseId; }\n get detailLoaded() { return this.history?.detailLoaded ?? false; }\n get inspecting() { return this.view === 'detail'; }\n\n moveSelection(delta: -1 | 1) {\n if (this.view !== 'overview' || !this.selectedId) return;\n const index = this.orderedTaskIds.indexOf(this.selectedId);\n const next = Math.max(0, Math.min(this.orderedTaskIds.length - 1, index + delta));\n this.selectedId = this.orderedTaskIds[next];\n this.draw();\n }\n\n openDetail(): string | undefined {\n if (this.view !== 'overview' || !this.selectedId) return;\n this.view = 'detail';\n this.inspectionId = this.selectedId;\n this.history = undefined;\n this.draw();\n return this.inspectionId;\n }\n\n updateSession(session: DashboardSession): boolean {\n if (this.view !== 'detail' || session.taskId !== this.inspectionId) return false;\n if (this.history) this.history.update(session);\n else this.history = new DashboardHistoryView(session, this.inspectionId);\n this.draw();\n return true;\n }\n\n toggleExpansion() {\n if (this.view !== 'detail' || !this.history) return false;\n this.history.toggleExpansion();\n this.draw();\n return true;\n }\n\n updateDetail(detail: DashboardDetail): boolean {\n if (!this.history?.updateDetail(detail)) return false;\n this.draw();\n return true;\n }\n\n leaveDetail(): boolean {\n if (this.view !== 'detail') return false;\n this.view = 'overview';\n this.inspectionId = undefined;\n this.history = undefined;\n this.draw();\n return true;\n }\n\n scrollDetail(key: 'up' | 'down' | 'page-up' | 'page-down' | 'home' | 'end') {\n if (this.view !== 'detail') return;\n this.history?.navigate(key, Math.max(0, this.output.columns ?? 80), Math.max(0, this.output.rows ?? 24));\n this.draw();\n }\n\n redraw() { this.draw(); }\n\n private prepareOverview() {\n const previousIds = this.orderedTaskIds;\n const previousIndex = this.selectedId ? previousIds.indexOf(this.selectedId) : -1;\n const grouping = groupDashboardRows(this.current, this.launchWorkspace, this.firstObserved);\n this.firstObserved = grouping.firstObserved;\n this.groups = this.labelGroups(grouping.groups);\n this.orderedTaskIds = this.groups.flatMap(group => group.rows.map(row => row.taskId));\n if (!this.orderedTaskIds.length) this.selectedId = undefined;\n else if (!this.selectedId || !this.orderedTaskIds.includes(this.selectedId)) {\n this.selectedId = this.orderedTaskIds[Math.min(Math.max(0, previousIndex), this.orderedTaskIds.length - 1)];\n }\n }\n\n private draw() {\n if (!this.active || this.blocked) return;\n const height = Math.max(0, this.output.rows ?? 24);\n const width = Math.max(0, this.output.columns ?? 80);\n const lines = this.view === 'detail'\n ? this.detailLines(width, height)\n : this.overviewLines(width, height);\n const size = `${width}:${height}`;\n let frame = this.size && this.size !== size ? '\\u001b[2J' : '';\n if (frame) this.previous = [];\n this.size = size;\n for (let index = 0; index < Math.min(height, Math.max(lines.length, this.previous.length)); index++) {\n if ((lines[index] ?? '') !== (this.previous[index] ?? '')) {\n frame += `\\u001b[${index + 1};1H\\u001b[2K${lines[index] ?? ''}`;\n }\n }\n this.previous = lines;\n if (frame) this.blocked = !this.output.write(frame);\n }\n\n private overviewLines(width: number, height: number): string[] {\n const slots = dashboardOverviewSlots(this.groups);\n const selectedLine = slots.findIndex(slot => slot.kind === 'row' && slot.row.taskId === this.selectedId);\n const max = Math.max(0, slots.length - height);\n if (selectedLine >= 0 && selectedLine < this.overviewOffset) this.overviewOffset = selectedLine;\n else if (selectedLine >= this.overviewOffset + height) this.overviewOffset = selectedLine - height + 1;\n this.overviewOffset = Math.max(0, Math.min(max, this.overviewOffset));\n return layoutDashboardSlots(\n slots.slice(this.overviewOffset, this.overviewOffset + height),\n width,\n this.now,\n this.selectedId,\n this.color,\n );\n }\n\n private detailLines(width: number, height: number): string[] {\n if (!this.history) return [];\n const workspace = this.history.workspace;\n const label = this.labels.get(workspace.directory)\n ?? labelDashboardGroups([{workspace, rows: [], updatedAt: 0}])[0]!.label;\n return this.history.lines(label, width, height, this.now, this.color);\n }\n\n private labelGroups(groups: DashboardGroup[]): LabelledDashboardGroup[] {\n const membershipChanged = groups.length !== this.labelMembership.size\n || groups.some(group => !this.labelMembership.has(group.workspace.directory)\n || this.labelMembership.get(group.workspace.directory) !== group.workspace.repository);\n if (membershipChanged) {\n this.labelMembership = new Map(groups.map(group => [\n group.workspace.directory,\n group.workspace.repository,\n ]));\n this.labels = new Map(labelDashboardGroups(groups)\n .map(group => [group.workspace.directory, group.label]));\n }\n return groups.map(group => ({...group, label: this.labels.get(group.workspace.directory)!}));\n }\n\n close() {\n if (!this.active) return;\n this.active = false;\n this.output.off('drain', this.drain);\n this.output.write(leave);\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ export declare function cleanDashboardLine(text: string): string;
2
+ export declare function stateStyle(state: string): string;
3
+ export interface DashboardSpan {
4
+ text: string;
5
+ style?: string;
6
+ }
7
+ /** Clip trusted, already sanitized text before adding application-owned ANSI. */
8
+ export declare function styledLine(spans: DashboardSpan[], columns: number, color?: boolean, selected?: boolean): string;
9
+ export declare function workspaceLine(label: string, columns: number, color?: boolean): string;
@@ -0,0 +1,36 @@
1
+ import stringWidth from 'string-width';
2
+ import { sanitizeDashboardTerminalText } from '../runtime/dashboard-sanitize.js';
3
+ const graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
4
+ export function cleanDashboardLine(text) {
5
+ return sanitizeDashboardTerminalText(text).replace(/[\t\n\r]/gu, '').replace(/\s+/gu, ' ').trim();
6
+ }
7
+ export function stateStyle(state) {
8
+ return { running: '36', completed: '32', failed: '31', interrupted: '33' }[state] ?? '2';
9
+ }
10
+ /** Clip trusted, already sanitized text before adding application-owned ANSI. */
11
+ export function styledLine(spans, columns, color = false, selected = false) {
12
+ let remaining = Math.max(0, columns), result = '';
13
+ for (const span of spans) {
14
+ let text = '';
15
+ for (const { segment } of graphemes.segment(span.text)) {
16
+ const width = stringWidth(segment);
17
+ if (width > remaining)
18
+ break;
19
+ text += segment;
20
+ remaining -= width;
21
+ }
22
+ if (text)
23
+ result += color && span.style ? `\u001b[${span.style}m${text}\u001b[0m${selected ? '\u001b[48;5;238m' : ''}` : text;
24
+ if (remaining === 0)
25
+ break;
26
+ }
27
+ if (color && selected)
28
+ return `\u001b[48;5;238m${result}${' '.repeat(remaining)}\u001b[0m`;
29
+ return result;
30
+ }
31
+ export function workspaceLine(label, columns, color = false) {
32
+ const text = cleanDashboardLine(label);
33
+ const split = text.lastIndexOf(' / ');
34
+ return styledLine(split < 0 ? [{ text, style: '1' }] : [{ text: text.slice(0, split + 3), style: '2' }, { text: text.slice(split + 3), style: '1' }], columns, color);
35
+ }
36
+ //# sourceMappingURL=dashboard-style.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-style.js","sourceRoot":"","sources":["../../src/cli/dashboard-style.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,EAAC,6BAA6B,EAAC,MAAM,kCAAkC,CAAC;AAE/E,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;AAC7E,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,6BAA6B,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACpG,CAAC;AACD,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAA6B,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC;AACvH,CAAC;AAKD,iFAAiF;AACjF,MAAM,UAAU,UAAU,CAAC,KAAsB,EAAE,OAAe,EAAE,KAAK,GAAG,KAAK,EAAE,QAAQ,GAAG,KAAK;IACjG,IAAI,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC;IAClD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvD,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;YACnC,IAAI,KAAK,GAAG,SAAS;gBACnB,MAAM;YACR,IAAI,IAAI,OAAO,CAAC;YAChB,SAAS,IAAI,KAAK,CAAC;QACrB,CAAC;QACD,IAAI,IAAI;YACN,MAAM,IAAI,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,KAAK,IAAI,IAAI,YAAY,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACtH,IAAI,SAAS,KAAK,CAAC;YACjB,MAAM;IACV,CAAC;IAED,IAAI,KAAK,IAAI,QAAQ;QACnB,OAAO,mBAAmB,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC;IACtE,OAAO,MAAM,CAAC;AAChB,CAAC;AACD,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,OAAe,EAAE,KAAK,GAAG,KAAK;IACzE,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACtC,OAAO,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;AACxK,CAAC","sourcesContent":["import stringWidth from 'string-width';\nimport {sanitizeDashboardTerminalText} from '../runtime/dashboard-sanitize.ts';\n\nconst graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\nexport function cleanDashboardLine(text: string): string {\n return sanitizeDashboardTerminalText(text).replace(/[\\t\\n\\r]/gu, '').replace(/\\s+/gu, ' ').trim();\n}\nexport function stateStyle(state: string): string {\n return ({ running: '36', completed: '32', failed: '31', interrupted: '33' } as Record<string, string>)[state] ?? '2';\n}\nexport interface DashboardSpan {\n text: string;\n style?: string;\n}\n/** Clip trusted, already sanitized text before adding application-owned ANSI. */\nexport function styledLine(spans: DashboardSpan[], columns: number, color = false, selected = false): string {\n let remaining = Math.max(0, columns), result = '';\n for (const span of spans) {\n let text = '';\n for (const { segment } of graphemes.segment(span.text)) {\n const width = stringWidth(segment);\n if (width > remaining)\n break;\n text += segment;\n remaining -= width;\n }\n if (text)\n result += color && span.style ? `\\u001b[${span.style}m${text}\\u001b[0m${selected ? '\\u001b[48;5;238m' : ''}` : text;\n if (remaining === 0)\n break;\n }\n\n if (color && selected)\n return `\\u001b[48;5;238m${result}${' '.repeat(remaining)}\\u001b[0m`;\n return result;\n}\nexport function workspaceLine(label: string, columns: number, color = false): string {\n const text = cleanDashboardLine(label);\n const split = text.lastIndexOf(' / ');\n return styledLine(split < 0 ? [{ text, style: '1' }] : [{ text: text.slice(0, split + 3), style: '2' }, { text: text.slice(split + 3), style: '1' }], columns, color);\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { EventEmitter } from 'node:events';
2
- import type { DashboardSnapshot } from '../runtime/dashboard.js';
2
+ import type { DashboardDetail, DashboardSession, DashboardSnapshot } from '../runtime/dashboard.js';
3
3
  import { type Endpoint } from '../runtime/state.js';
4
4
  import { type DashboardOutput } from './dashboard-renderer.js';
5
5
  interface Terminal extends DashboardOutput {
@@ -14,8 +14,8 @@ interface Keyboard {
14
14
  setRawMode(enabled: boolean): unknown;
15
15
  resume(): unknown;
16
16
  pause(): unknown;
17
- on(event: 'data', listener: (chunk: Buffer | string) => void): unknown;
18
- off(event: 'data', listener: (chunk: Buffer | string) => void): unknown;
17
+ on(event: 'data', listener: (chunk: Uint8Array | string) => void): unknown;
18
+ off(event: 'data', listener: (chunk: Uint8Array | string) => void): unknown;
19
19
  }
20
20
  interface DashboardOptions {
21
21
  cwd?: string;
@@ -26,6 +26,8 @@ interface DashboardOptions {
26
26
  intervalMs?: number;
27
27
  discover?: () => Promise<Endpoint | undefined>;
28
28
  snapshot?: (endpoint: Endpoint, signal: AbortSignal) => Promise<DashboardSnapshot>;
29
+ session?: (endpoint: Endpoint, id: string, signal: AbortSignal) => Promise<DashboardSession>;
30
+ detail?: (endpoint: Endpoint, id: string, signal: AbortSignal, includePrompt?: boolean, knownResponseId?: string) => Promise<DashboardDetail>;
29
31
  }
30
32
  export declare function runDashboard(options?: DashboardOptions): Promise<number>;
31
33
  export {};
@@ -1,8 +1,9 @@
1
- import { setTimeout as delay } from 'node:timers/promises';
2
1
  import { AgentError } from '../errors.js';
3
2
  import { resolveDashboardWorkspace } from '../runtime/dashboard-workspace.js';
4
3
  import { readEndpoint, stateDirectory } from '../runtime/state.js';
5
- import { fetchDashboardSnapshot } from './dashboard-client.js';
4
+ import { fetchDashboardDetail, fetchDashboardSession, fetchDashboardSnapshot } from './dashboard-client.js';
5
+ import { dispatchDashboardKey } from './dashboard-controller.js';
6
+ import { DashboardKeyDecoder } from './dashboard-input.js';
6
7
  import { DashboardRenderer } from './dashboard-renderer.js';
7
8
  export async function runDashboard(options = {}) {
8
9
  const output = options.output ?? process.stdout;
@@ -16,21 +17,80 @@ export async function runDashboard(options = {}) {
16
17
  const signals = options.signals ?? process;
17
18
  const discover = options.discover ?? (() => readEndpoint(stateDirectory()));
18
19
  const snapshot = options.snapshot ?? fetchDashboardSnapshot;
20
+ const detail = options.detail ?? fetchDashboardDetail;
21
+ const session = options.session ?? fetchDashboardSession;
19
22
  const interval = options.intervalMs ?? 1_000;
20
23
  const abort = new AbortController();
21
- const renderer = new DashboardRenderer(output, await resolveDashboardWorkspace(options.cwd ?? process.cwd()));
24
+ const workspace = await resolveDashboardWorkspace(options.cwd ?? process.cwd());
22
25
  let rows = [];
23
26
  let endpoint;
24
27
  let exitCode = 0;
25
28
  let outputError;
26
- const interrupt = () => { exitCode = 130; abort.abort(); };
27
- const terminate = () => { exitCode = 143; abort.abort(); };
28
- const keys = (chunk) => {
29
- if (typeof chunk === 'string' ? chunk.includes('\u0003') : chunk.includes(3))
29
+ let detailAbort;
30
+ let wake;
31
+ const stop = (code) => {
32
+ exitCode = code;
33
+ detailAbort?.abort();
34
+ abort.abort();
35
+ wake?.();
36
+ };
37
+ const failedOutput = (error) => {
38
+ outputError ??= error;
39
+ detailAbort?.abort();
40
+ abort.abort();
41
+ wake?.();
42
+ };
43
+ // Event listeners and the decoder's Escape timer run outside the polling try/finally.
44
+ const guarded = (callback) => (...args) => {
45
+ if (abort.signal.aborted)
46
+ return;
47
+ try {
48
+ callback(...args);
49
+ }
50
+ catch (error) {
51
+ failedOutput(error);
52
+ }
53
+ };
54
+ const renderer = new DashboardRenderer(output, workspace, !process.env.NO_COLOR, failedOutput);
55
+ const interrupt = () => stop(130);
56
+ const terminate = () => stop(143);
57
+ const key = (pressed) => {
58
+ const action = dispatchDashboardKey(renderer, pressed);
59
+ if (action.interrupt) {
30
60
  interrupt();
61
+ return;
62
+ }
63
+ if (action.refreshInspection) {
64
+ detailAbort?.abort();
65
+ detailAbort = renderer.inspecting ? new AbortController() : undefined;
66
+ wake?.();
67
+ }
68
+ };
69
+ const decoder = new DashboardKeyDecoder(guarded(key));
70
+ const keys = guarded((chunk) => decoder.push(chunk));
71
+ const resize = guarded(() => renderer.render(rows, Date.now()));
72
+ const waitForRefresh = (milliseconds) => {
73
+ if (abort.signal.aborted)
74
+ return Promise.resolve();
75
+ return new Promise(resolve => {
76
+ let timer;
77
+ const done = () => {
78
+ if (timer)
79
+ clearTimeout(timer);
80
+ abort.signal.removeEventListener('abort', done);
81
+ if (wake === done)
82
+ wake = undefined;
83
+ resolve();
84
+ };
85
+ abort.signal.addEventListener('abort', done, { once: true });
86
+ if (abort.signal.aborted) {
87
+ done();
88
+ return;
89
+ }
90
+ timer = setTimeout(done, milliseconds);
91
+ wake = done;
92
+ });
31
93
  };
32
- const resize = () => renderer.render(rows, Date.now());
33
- const failedOutput = (error) => { outputError = error; abort.abort(); };
34
94
  signals.on('SIGINT', interrupt);
35
95
  signals.on('SIGTERM', terminate);
36
96
  output.on('resize', resize);
@@ -48,12 +108,35 @@ export async function runDashboard(options = {}) {
48
108
  endpoint ??= await discover();
49
109
  if (abort.signal.aborted)
50
110
  break;
51
- if (endpoint)
111
+ if (endpoint && !renderer.inspecting)
52
112
  rows = (await snapshot(endpoint, abort.signal)).sessions;
53
113
  if (abort.signal.aborted)
54
114
  break;
55
115
  renderer.render(rows, Date.now());
56
- await delay(Math.max(0, next - Date.now()), undefined, { signal: abort.signal });
116
+ const inspectionId = renderer.inspectedTaskId;
117
+ const inspectionAbort = detailAbort;
118
+ if (endpoint && inspectionId && inspectionAbort) {
119
+ try {
120
+ const signal = AbortSignal.any([abort.signal, inspectionAbort.signal]);
121
+ const history = await session(endpoint, inspectionId, signal);
122
+ if (abort.signal.aborted || detailAbort !== inspectionAbort || renderer.inspectedTaskId !== inspectionId)
123
+ continue;
124
+ renderer.updateSession(history);
125
+ const expandedId = renderer.expandedTaskId;
126
+ if (expandedId) {
127
+ const record = await detail(endpoint, expandedId, signal, !renderer.detailLoaded, renderer.knownResponseId);
128
+ if (!abort.signal.aborted && detailAbort === inspectionAbort && renderer.expandedTaskId === expandedId) {
129
+ renderer.updateDetail(record);
130
+ }
131
+ }
132
+ }
133
+ catch (error) {
134
+ if (detailAbort !== inspectionAbort || renderer.inspectedTaskId !== inspectionId)
135
+ continue;
136
+ throw error;
137
+ }
138
+ }
139
+ await waitForRefresh(Math.max(0, next - Date.now()));
57
140
  }
58
141
  }
59
142
  catch (error) {
@@ -62,7 +145,14 @@ export async function runDashboard(options = {}) {
62
145
  }
63
146
  finally {
64
147
  abort.abort();
65
- renderer.close();
148
+ detailAbort?.abort();
149
+ decoder.close();
150
+ try {
151
+ renderer.close();
152
+ }
153
+ catch (error) {
154
+ outputError ??= error;
155
+ }
66
156
  if (inputEnabled) {
67
157
  input.off('data', keys);
68
158
  input.setRawMode(wasRaw);