@agentdeck/bridge 0.2.1 → 0.2.3

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 (383) hide show
  1. package/assets/fonts/IBMPlexSans-Bold.ttf +0 -0
  2. package/assets/fonts/IBMPlexSans-Regular.ttf +0 -0
  3. package/assets/fonts/JetBrainsMono-Bold.ttf +0 -0
  4. package/assets/fonts/JetBrainsMono-Regular.ttf +0 -0
  5. package/assets/fonts/LICENSES.md +22 -0
  6. package/dist/adapters/claude-code.d.ts +6 -0
  7. package/dist/adapters/claude-code.d.ts.map +1 -1
  8. package/dist/adapters/claude-code.js +8 -0
  9. package/dist/adapters/claude-code.js.map +1 -1
  10. package/dist/adapters/openclaw.d.ts +24 -0
  11. package/dist/adapters/openclaw.d.ts.map +1 -1
  12. package/dist/adapters/openclaw.js +291 -35
  13. package/dist/adapters/openclaw.js.map +1 -1
  14. package/dist/adapters/opencode-adapter.d.ts +59 -0
  15. package/dist/adapters/opencode-adapter.d.ts.map +1 -1
  16. package/dist/adapters/opencode-adapter.js +297 -35
  17. package/dist/adapters/opencode-adapter.js.map +1 -1
  18. package/dist/adb-reverse.d.ts +1 -1
  19. package/dist/adb-reverse.js +1 -1
  20. package/dist/antigravity-local.d.ts +1 -0
  21. package/dist/antigravity-local.d.ts.map +1 -1
  22. package/dist/antigravity-local.js +33 -0
  23. package/dist/antigravity-local.js.map +1 -1
  24. package/dist/apme/adapters/claude-hook.d.ts.map +1 -1
  25. package/dist/apme/adapters/claude-hook.js +5 -0
  26. package/dist/apme/adapters/claude-hook.js.map +1 -1
  27. package/dist/apme/adapters/codex-hook.d.ts +17 -0
  28. package/dist/apme/adapters/codex-hook.d.ts.map +1 -0
  29. package/dist/apme/adapters/codex-hook.js +95 -0
  30. package/dist/apme/adapters/codex-hook.js.map +1 -0
  31. package/dist/apme/adapters/codex-turn-manager.d.ts +86 -0
  32. package/dist/apme/adapters/codex-turn-manager.d.ts.map +1 -0
  33. package/dist/apme/adapters/codex-turn-manager.js +496 -0
  34. package/dist/apme/adapters/codex-turn-manager.js.map +1 -0
  35. package/dist/apme/adapters/openclaw-hook.d.ts +99 -0
  36. package/dist/apme/adapters/openclaw-hook.d.ts.map +1 -0
  37. package/dist/apme/adapters/openclaw-hook.js +234 -0
  38. package/dist/apme/adapters/openclaw-hook.js.map +1 -0
  39. package/dist/apme/adapters/opencode-hook.d.ts +56 -0
  40. package/dist/apme/adapters/opencode-hook.d.ts.map +1 -0
  41. package/dist/apme/adapters/opencode-hook.js +182 -0
  42. package/dist/apme/adapters/opencode-hook.js.map +1 -0
  43. package/dist/apme/adapters/timeline.d.ts.map +1 -1
  44. package/dist/apme/adapters/timeline.js +6 -1
  45. package/dist/apme/adapters/timeline.js.map +1 -1
  46. package/dist/apme/antigravity-transcript.d.ts +18 -0
  47. package/dist/apme/antigravity-transcript.d.ts.map +1 -0
  48. package/dist/apme/antigravity-transcript.js +94 -0
  49. package/dist/apme/antigravity-transcript.js.map +1 -0
  50. package/dist/apme/classify-turn.d.ts +13 -0
  51. package/dist/apme/classify-turn.d.ts.map +1 -0
  52. package/dist/apme/classify-turn.js +49 -0
  53. package/dist/apme/classify-turn.js.map +1 -0
  54. package/dist/apme/claude-transcript-reader.d.ts +10 -0
  55. package/dist/apme/claude-transcript-reader.d.ts.map +1 -1
  56. package/dist/apme/claude-transcript-reader.js +35 -0
  57. package/dist/apme/claude-transcript-reader.js.map +1 -1
  58. package/dist/apme/collector.d.ts +129 -6
  59. package/dist/apme/collector.d.ts.map +1 -1
  60. package/dist/apme/collector.js +470 -23
  61. package/dist/apme/collector.js.map +1 -1
  62. package/dist/apme/dashboard-html.d.ts.map +1 -1
  63. package/dist/apme/dashboard-html.js +87 -11
  64. package/dist/apme/dashboard-html.js.map +1 -1
  65. package/dist/apme/http.d.ts.map +1 -1
  66. package/dist/apme/http.js +43 -6
  67. package/dist/apme/http.js.map +1 -1
  68. package/dist/apme/index.d.ts +26 -5
  69. package/dist/apme/index.d.ts.map +1 -1
  70. package/dist/apme/index.js +135 -9
  71. package/dist/apme/index.js.map +1 -1
  72. package/dist/apme/pareto.d.ts +39 -0
  73. package/dist/apme/pareto.d.ts.map +1 -0
  74. package/dist/apme/pareto.js +59 -0
  75. package/dist/apme/pareto.js.map +1 -0
  76. package/dist/apme/recommend.d.ts +3 -0
  77. package/dist/apme/recommend.d.ts.map +1 -1
  78. package/dist/apme/recommend.js +43 -1
  79. package/dist/apme/recommend.js.map +1 -1
  80. package/dist/apme/runner.d.ts +92 -4
  81. package/dist/apme/runner.d.ts.map +1 -1
  82. package/dist/apme/runner.js +586 -70
  83. package/dist/apme/runner.js.map +1 -1
  84. package/dist/apme/sample-to-timeline.d.ts +26 -0
  85. package/dist/apme/sample-to-timeline.d.ts.map +1 -0
  86. package/dist/apme/sample-to-timeline.js +95 -0
  87. package/dist/apme/sample-to-timeline.js.map +1 -0
  88. package/dist/apme/scorers/index.d.ts +47 -0
  89. package/dist/apme/scorers/index.d.ts.map +1 -0
  90. package/dist/apme/scorers/index.js +96 -0
  91. package/dist/apme/scorers/index.js.map +1 -0
  92. package/dist/apme/settings.d.ts +14 -10
  93. package/dist/apme/settings.d.ts.map +1 -1
  94. package/dist/apme/settings.js +34 -8
  95. package/dist/apme/settings.js.map +1 -1
  96. package/dist/apme/store.d.ts +33 -1
  97. package/dist/apme/store.d.ts.map +1 -1
  98. package/dist/apme/store.js +334 -25
  99. package/dist/apme/store.js.map +1 -1
  100. package/dist/awaiting-overlay.d.ts +99 -0
  101. package/dist/awaiting-overlay.d.ts.map +1 -0
  102. package/dist/awaiting-overlay.js +148 -0
  103. package/dist/awaiting-overlay.js.map +1 -0
  104. package/dist/ble-sync-spawn.d.ts +63 -0
  105. package/dist/ble-sync-spawn.d.ts.map +1 -0
  106. package/dist/ble-sync-spawn.js +196 -0
  107. package/dist/ble-sync-spawn.js.map +1 -0
  108. package/dist/bridge-core.d.ts +24 -1
  109. package/dist/bridge-core.d.ts.map +1 -1
  110. package/dist/bridge-core.js +157 -24
  111. package/dist/bridge-core.js.map +1 -1
  112. package/dist/broadcast.d.ts +3 -0
  113. package/dist/broadcast.d.ts.map +1 -0
  114. package/dist/broadcast.js +122 -0
  115. package/dist/broadcast.js.map +1 -0
  116. package/dist/check-deps.d.ts.map +1 -1
  117. package/dist/check-deps.js +25 -9
  118. package/dist/check-deps.js.map +1 -1
  119. package/dist/cli.d.ts +15 -1
  120. package/dist/cli.d.ts.map +1 -1
  121. package/dist/cli.js +1025 -94
  122. package/dist/cli.js.map +1 -1
  123. package/dist/codex-auth.d.ts +21 -0
  124. package/dist/codex-auth.d.ts.map +1 -1
  125. package/dist/codex-auth.js +52 -1
  126. package/dist/codex-auth.js.map +1 -1
  127. package/dist/codex-output-parser.d.ts +8 -0
  128. package/dist/codex-output-parser.d.ts.map +1 -1
  129. package/dist/codex-output-parser.js +110 -25
  130. package/dist/codex-output-parser.js.map +1 -1
  131. package/dist/codex-rate-limits.d.ts +16 -0
  132. package/dist/codex-rate-limits.d.ts.map +1 -0
  133. package/dist/codex-rate-limits.js +197 -0
  134. package/dist/codex-rate-limits.js.map +1 -0
  135. package/dist/codex-rollout-response.d.ts +31 -0
  136. package/dist/codex-rollout-response.d.ts.map +1 -0
  137. package/dist/codex-rollout-response.js +135 -0
  138. package/dist/codex-rollout-response.js.map +1 -0
  139. package/dist/daemon-server.d.ts +85 -0
  140. package/dist/daemon-server.d.ts.map +1 -1
  141. package/dist/daemon-server.js +1627 -159
  142. package/dist/daemon-server.js.map +1 -1
  143. package/dist/daemon.js +15 -6
  144. package/dist/daemon.js.map +1 -1
  145. package/dist/display-dim.d.ts +9 -0
  146. package/dist/display-dim.d.ts.map +1 -0
  147. package/dist/display-dim.js +53 -0
  148. package/dist/display-dim.js.map +1 -0
  149. package/dist/display-monitor.d.ts +15 -2
  150. package/dist/display-monitor.d.ts.map +1 -1
  151. package/dist/display-monitor.js +104 -26
  152. package/dist/display-monitor.js.map +1 -1
  153. package/dist/esp32-serial.d.ts +160 -1
  154. package/dist/esp32-serial.d.ts.map +1 -1
  155. package/dist/esp32-serial.js +950 -153
  156. package/dist/esp32-serial.js.map +1 -1
  157. package/dist/fallback-task-timeline.d.ts +42 -0
  158. package/dist/fallback-task-timeline.d.ts.map +1 -0
  159. package/dist/fallback-task-timeline.js +112 -0
  160. package/dist/fallback-task-timeline.js.map +1 -0
  161. package/dist/foundation-models-helper.d.ts +11 -0
  162. package/dist/foundation-models-helper.d.ts.map +1 -0
  163. package/dist/foundation-models-helper.js +241 -0
  164. package/dist/foundation-models-helper.js.map +1 -0
  165. package/dist/hook-migration.js +1 -1
  166. package/dist/hook-migration.js.map +1 -1
  167. package/dist/hook-server.d.ts +2 -3
  168. package/dist/hook-server.d.ts.map +1 -1
  169. package/dist/hook-server.js +22 -10
  170. package/dist/hook-server.js.map +1 -1
  171. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts +33 -0
  172. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -0
  173. package/dist/idotmatrix/idotmatrix-daemon-sync.js +164 -0
  174. package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -0
  175. package/dist/idotmatrix/idotmatrix-discover.d.ts +18 -0
  176. package/dist/idotmatrix/idotmatrix-discover.d.ts.map +1 -0
  177. package/dist/idotmatrix/idotmatrix-discover.js +85 -0
  178. package/dist/idotmatrix/idotmatrix-discover.js.map +1 -0
  179. package/dist/idotmatrix/idotmatrix-settings.d.ts +20 -0
  180. package/dist/idotmatrix/idotmatrix-settings.d.ts.map +1 -0
  181. package/dist/idotmatrix/idotmatrix-settings.js +57 -0
  182. package/dist/idotmatrix/idotmatrix-settings.js.map +1 -0
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.js +215 -179
  185. package/dist/index.js.map +1 -1
  186. package/dist/log-stream.d.ts +15 -16
  187. package/dist/log-stream.d.ts.map +1 -1
  188. package/dist/log-stream.js +18 -96
  189. package/dist/log-stream.js.map +1 -1
  190. package/dist/logger.d.ts.map +1 -1
  191. package/dist/logger.js +12 -3
  192. package/dist/logger.js.map +1 -1
  193. package/dist/mdns.d.ts +17 -0
  194. package/dist/mdns.d.ts.map +1 -1
  195. package/dist/mdns.js +52 -18
  196. package/dist/mdns.js.map +1 -1
  197. package/dist/modules/adb-module.d.ts +1 -1
  198. package/dist/modules/adb-module.d.ts.map +1 -1
  199. package/dist/modules/adb-module.js +4 -3
  200. package/dist/modules/adb-module.js.map +1 -1
  201. package/dist/modules/broadcast-module.d.ts +17 -0
  202. package/dist/modules/broadcast-module.d.ts.map +1 -0
  203. package/dist/modules/broadcast-module.js +26 -0
  204. package/dist/modules/broadcast-module.js.map +1 -0
  205. package/dist/modules/idotmatrix-module.d.ts +9 -0
  206. package/dist/modules/idotmatrix-module.d.ts.map +1 -0
  207. package/dist/modules/idotmatrix-module.js +46 -0
  208. package/dist/modules/idotmatrix-module.js.map +1 -0
  209. package/dist/modules/index.d.ts +3 -1
  210. package/dist/modules/index.d.ts.map +1 -1
  211. package/dist/modules/index.js +9 -3
  212. package/dist/modules/index.js.map +1 -1
  213. package/dist/modules/pixoo-module.d.ts.map +1 -1
  214. package/dist/modules/pixoo-module.js +14 -4
  215. package/dist/modules/pixoo-module.js.map +1 -1
  216. package/dist/modules/serial-module.d.ts +4 -0
  217. package/dist/modules/serial-module.d.ts.map +1 -1
  218. package/dist/modules/serial-module.js +14 -12
  219. package/dist/modules/serial-module.js.map +1 -1
  220. package/dist/modules/timebox-module.d.ts +9 -0
  221. package/dist/modules/timebox-module.d.ts.map +1 -0
  222. package/dist/modules/timebox-module.js +46 -0
  223. package/dist/modules/timebox-module.js.map +1 -0
  224. package/dist/modules/types.d.ts +4 -2
  225. package/dist/modules/types.d.ts.map +1 -1
  226. package/dist/openclaw-session.d.ts +18 -0
  227. package/dist/openclaw-session.d.ts.map +1 -0
  228. package/dist/openclaw-session.js +34 -0
  229. package/dist/openclaw-session.js.map +1 -0
  230. package/dist/output-parser.d.ts +30 -0
  231. package/dist/output-parser.d.ts.map +1 -1
  232. package/dist/output-parser.js +125 -9
  233. package/dist/output-parser.js.map +1 -1
  234. package/dist/passive-observer.d.ts +26 -0
  235. package/dist/passive-observer.d.ts.map +1 -1
  236. package/dist/passive-observer.js +258 -49
  237. package/dist/passive-observer.js.map +1 -1
  238. package/dist/permission-resolver.d.ts +47 -0
  239. package/dist/permission-resolver.d.ts.map +1 -0
  240. package/dist/permission-resolver.js +103 -0
  241. package/dist/permission-resolver.js.map +1 -0
  242. package/dist/pixoo/micro-glyphs.d.ts +49 -0
  243. package/dist/pixoo/micro-glyphs.d.ts.map +1 -0
  244. package/dist/pixoo/micro-glyphs.js +276 -0
  245. package/dist/pixoo/micro-glyphs.js.map +1 -0
  246. package/dist/pixoo/pixoo-bridge.d.ts +2 -2
  247. package/dist/pixoo/pixoo-bridge.d.ts.map +1 -1
  248. package/dist/pixoo/pixoo-bridge.js +93 -25
  249. package/dist/pixoo/pixoo-bridge.js.map +1 -1
  250. package/dist/pixoo/pixoo-camera.d.ts +19 -3
  251. package/dist/pixoo/pixoo-camera.d.ts.map +1 -1
  252. package/dist/pixoo/pixoo-camera.js +62 -27
  253. package/dist/pixoo/pixoo-camera.js.map +1 -1
  254. package/dist/pixoo/pixoo-client.d.ts +13 -3
  255. package/dist/pixoo/pixoo-client.d.ts.map +1 -1
  256. package/dist/pixoo/pixoo-client.js +51 -22
  257. package/dist/pixoo/pixoo-client.js.map +1 -1
  258. package/dist/pixoo/pixoo-discover.d.ts +37 -0
  259. package/dist/pixoo/pixoo-discover.d.ts.map +1 -0
  260. package/dist/pixoo/pixoo-discover.js +105 -0
  261. package/dist/pixoo/pixoo-discover.js.map +1 -0
  262. package/dist/pixoo/pixoo-renderer.d.ts +8 -3
  263. package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
  264. package/dist/pixoo/pixoo-renderer.js +189 -60
  265. package/dist/pixoo/pixoo-renderer.js.map +1 -1
  266. package/dist/pixoo/pixoo-settings.d.ts +6 -0
  267. package/dist/pixoo/pixoo-settings.d.ts.map +1 -1
  268. package/dist/pixoo/pixoo-settings.js +9 -0
  269. package/dist/pixoo/pixoo-settings.js.map +1 -1
  270. package/dist/pixoo/pixoo-sprites.d.ts +60 -11
  271. package/dist/pixoo/pixoo-sprites.d.ts.map +1 -1
  272. package/dist/pixoo/pixoo-sprites.js +512 -209
  273. package/dist/pixoo/pixoo-sprites.js.map +1 -1
  274. package/dist/pty-manager.d.ts.map +1 -1
  275. package/dist/pty-manager.js +8 -2
  276. package/dist/pty-manager.js.map +1 -1
  277. package/dist/session-activity.d.ts +14 -0
  278. package/dist/session-activity.d.ts.map +1 -0
  279. package/dist/session-activity.js +160 -0
  280. package/dist/session-activity.js.map +1 -0
  281. package/dist/session-aggregator.d.ts +17 -0
  282. package/dist/session-aggregator.d.ts.map +1 -1
  283. package/dist/session-aggregator.js +48 -9
  284. package/dist/session-aggregator.js.map +1 -1
  285. package/dist/session-registry.d.ts +71 -3
  286. package/dist/session-registry.d.ts.map +1 -1
  287. package/dist/session-registry.js +137 -8
  288. package/dist/session-registry.js.map +1 -1
  289. package/dist/session-timeline-relay.d.ts.map +1 -1
  290. package/dist/session-timeline-relay.js +13 -4
  291. package/dist/session-timeline-relay.js.map +1 -1
  292. package/dist/session-transcript-timeline.d.ts +49 -0
  293. package/dist/session-transcript-timeline.d.ts.map +1 -0
  294. package/dist/session-transcript-timeline.js +276 -0
  295. package/dist/session-transcript-timeline.js.map +1 -0
  296. package/dist/state-machine.d.ts +6 -1
  297. package/dist/state-machine.d.ts.map +1 -1
  298. package/dist/state-machine.js +86 -28
  299. package/dist/state-machine.js.map +1 -1
  300. package/dist/timebox/timebox-daemon-sync.d.ts +19 -0
  301. package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -0
  302. package/dist/timebox/timebox-daemon-sync.js +135 -0
  303. package/dist/timebox/timebox-daemon-sync.js.map +1 -0
  304. package/dist/timebox/timebox-discover.d.ts +19 -0
  305. package/dist/timebox/timebox-discover.d.ts.map +1 -0
  306. package/dist/timebox/timebox-discover.js +87 -0
  307. package/dist/timebox/timebox-discover.js.map +1 -0
  308. package/dist/timebox/timebox-settings.d.ts +32 -0
  309. package/dist/timebox/timebox-settings.d.ts.map +1 -0
  310. package/dist/timebox/timebox-settings.js +80 -0
  311. package/dist/timebox/timebox-settings.js.map +1 -0
  312. package/dist/timeline-store.d.ts +63 -3
  313. package/dist/timeline-store.d.ts.map +1 -1
  314. package/dist/timeline-store.js +292 -14
  315. package/dist/timeline-store.js.map +1 -1
  316. package/dist/timeline-summarizer.d.ts.map +1 -1
  317. package/dist/timeline-summarizer.js +29 -5
  318. package/dist/timeline-summarizer.js.map +1 -1
  319. package/dist/tui/ansi.d.ts +0 -4
  320. package/dist/tui/ansi.d.ts.map +1 -1
  321. package/dist/tui/ansi.js +14 -7
  322. package/dist/tui/ansi.js.map +1 -1
  323. package/dist/tui/dashboard.d.ts +1 -0
  324. package/dist/tui/dashboard.d.ts.map +1 -1
  325. package/dist/tui/dashboard.js +24 -7
  326. package/dist/tui/dashboard.js.map +1 -1
  327. package/dist/tui/renderer.d.ts +36 -0
  328. package/dist/tui/renderer.d.ts.map +1 -1
  329. package/dist/tui/renderer.js +303 -119
  330. package/dist/tui/renderer.js.map +1 -1
  331. package/dist/tui/terrarium.d.ts.map +1 -1
  332. package/dist/tui/terrarium.js +37 -49
  333. package/dist/tui/terrarium.js.map +1 -1
  334. package/dist/types.d.ts +2 -2
  335. package/dist/types.d.ts.map +1 -1
  336. package/dist/types.js +2 -2
  337. package/dist/types.js.map +1 -1
  338. package/dist/usage-api.d.ts.map +1 -1
  339. package/dist/usage-api.js +7 -1
  340. package/dist/usage-api.js.map +1 -1
  341. package/dist/usage-event.d.ts +10 -3
  342. package/dist/usage-event.d.ts.map +1 -1
  343. package/dist/usage-event.js +74 -12
  344. package/dist/usage-event.js.map +1 -1
  345. package/dist/utility-proxy.d.ts.map +1 -1
  346. package/dist/utility-proxy.js +8 -0
  347. package/dist/utility-proxy.js.map +1 -1
  348. package/dist/utils/project-name.d.ts +3 -0
  349. package/dist/utils/project-name.d.ts.map +1 -1
  350. package/dist/utils/project-name.js +44 -2
  351. package/dist/utils/project-name.js.map +1 -1
  352. package/dist/windows-service.d.ts +26 -0
  353. package/dist/windows-service.d.ts.map +1 -0
  354. package/dist/windows-service.js +145 -0
  355. package/dist/windows-service.js.map +1 -0
  356. package/dist/ws-server.d.ts +22 -1
  357. package/dist/ws-server.d.ts.map +1 -1
  358. package/dist/ws-server.js +89 -4
  359. package/dist/ws-server.js.map +1 -1
  360. package/fm-helper/AgentDeckFMHelper.swift +112 -0
  361. package/package.json +9 -3
  362. package/scripts/build-fm-helper.mjs +26 -0
  363. package/LICENSE +0 -21
  364. package/dist/apme/tuner.d.ts +0 -64
  365. package/dist/apme/tuner.d.ts.map +0 -1
  366. package/dist/apme/tuner.js +0 -297
  367. package/dist/apme/tuner.js.map +0 -1
  368. package/dist/d200h/hid-protocol.d.ts +0 -62
  369. package/dist/d200h/hid-protocol.d.ts.map +0 -1
  370. package/dist/d200h/hid-protocol.js +0 -124
  371. package/dist/d200h/hid-protocol.js.map +0 -1
  372. package/dist/d200h/image-renderer.d.ts +0 -36
  373. package/dist/d200h/image-renderer.d.ts.map +0 -1
  374. package/dist/d200h/image-renderer.js +0 -487
  375. package/dist/d200h/image-renderer.js.map +0 -1
  376. package/dist/modules/d200h-module.d.ts +0 -43
  377. package/dist/modules/d200h-module.d.ts.map +0 -1
  378. package/dist/modules/d200h-module.js +0 -360
  379. package/dist/modules/d200h-module.js.map +0 -1
  380. package/dist/modules/d200h-renderer.d.ts +0 -7
  381. package/dist/modules/d200h-renderer.d.ts.map +0 -1
  382. package/dist/modules/d200h-renderer.js +0 -321
  383. package/dist/modules/d200h-renderer.js.map +0 -1
@@ -92,6 +92,11 @@ function typeIcon(type) {
92
92
  case 'scheduled': return 's';
93
93
  case 'user_action': return 'u';
94
94
  case 'eval_result': return '#';
95
+ // Task hierarchy -- mirrors timelineIconKey() (shared/src/timeline-icons.ts):
96
+ // task_start/task_end -> task, task_milestone -> success.
97
+ case 'task_start':
98
+ case 'task_end': return '=';
99
+ case 'task_milestone': return '+';
95
100
  default: return '*';
96
101
  }
97
102
  }
@@ -108,6 +113,10 @@ function typeIcon(type) {
108
113
  case 'model_response': return '\u25C8';
109
114
  case 'memory_recall': return '\u25CC';
110
115
  case 'scheduled': return '\u25D1';
116
+ // Task hierarchy -- mirrors timelineIconKey() (shared/src/timeline-icons.ts).
117
+ case 'task_start':
118
+ case 'task_end': return '\u25A3'; // white square w/ centre
119
+ case 'task_milestone': return '\u2713'; // check mark
111
120
  case 'eval_result': return '\u2605'; // ★
112
121
  default: return '\u25C6';
113
122
  }
@@ -140,20 +149,24 @@ function creatureEmoji(agentType) {
140
149
  return 'D';
141
150
  if (agentType === 'openclaw')
142
151
  return 'C';
143
- if (agentType === 'codex-cli')
152
+ if (agentType === 'codex-cli' || agentType === 'codex-app')
144
153
  return 'X';
145
154
  if (agentType === 'opencode')
146
155
  return 'O';
156
+ if (agentType === 'antigravity')
157
+ return 'A';
147
158
  return '*';
148
159
  }
149
160
  if (agentType === 'daemon')
150
161
  return '\u2699\uFE0F'; // ⚙️
151
162
  if (agentType === 'openclaw')
152
163
  return '\uD83E\uDD9E'; // 🦞
153
- if (agentType === 'codex-cli')
164
+ if (agentType === 'codex-cli' || agentType === 'codex-app')
154
165
  return '\u2601'; // ☁ (cloud — matches creature)
155
166
  if (agentType === 'opencode')
156
167
  return '\u25A3'; // ▣ (nested square — matches creature)
168
+ if (agentType === 'antigravity')
169
+ return '\u25B2'; // ▲ (Antigravity peak)
157
170
  return '\u273B'; // ✻ (teardrop-spoked asterisk — Claude sparkle)
158
171
  }
159
172
  function creatureBrandColor(agentType) {
@@ -162,8 +175,10 @@ function creatureBrandColor(agentType) {
162
175
  switch (agentType) {
163
176
  case 'claude-code': return fg(192, 112, 88); // terracotta
164
177
  case 'openclaw': return fg(255, 77, 77); // red
165
- case 'codex-cli': return fg(177, 167, 255); // indigo
178
+ case 'codex-cli':
179
+ case 'codex-app': return fg(177, 167, 255); // indigo
166
180
  case 'opencode': return fg(241, 236, 236); // warm gray (#F1ECEC)
181
+ case 'antigravity': return fg(210, 214, 220); // Google gray
167
182
  default: return '';
168
183
  }
169
184
  }
@@ -197,8 +212,129 @@ function sessionHotkeyLabel(index) {
197
212
  return '·';
198
213
  return String(index + 1);
199
214
  }
200
- function sessionHotkeyIndex(session, nextIndex) {
201
- if (!session.port || session.controlMode === 'observed')
215
+ /**
216
+ * Build the unified left-HUD entry list shared with macOS / iOS / Android.
217
+ *
218
+ * Collapses primary + siblings + virtual OpenClaw into one array, sorts via
219
+ * the shared sortSessions (agentType → projectName → startedAt → id), and
220
+ * applies #N suffix via assignDisplayNames so the display order and #N
221
+ * numbering match every other surface.
222
+ *
223
+ * Primary handling mirrors apple/AgentDeck/UI/Monitor/SessionListPanel.swift:
224
+ * - if a sibling matches our connected port, that sibling becomes the
225
+ * primary anchor (its startedAt anchors the sort position)
226
+ * - otherwise primary is appended only when no sibling shares its
227
+ * agentType (duplicatePrimaryWithoutId guard)
228
+ * - daemon / openclaw primaries are never appended (they're virtual)
229
+ */
230
+ export function buildHudEntries(state) {
231
+ const items = [];
232
+ const portToItem = new Map();
233
+ for (const s of state.sessions) {
234
+ const ri = s;
235
+ const item = {
236
+ id: s.id,
237
+ projectName: s.projectName ?? 'unknown',
238
+ agentType: s.agentType ?? undefined,
239
+ state: s.state ?? 'idle',
240
+ modelName: s.modelName ?? undefined,
241
+ startedAt: s.startedAt ?? undefined,
242
+ port: s.port ?? undefined,
243
+ controlMode: ri.controlMode,
244
+ currentTask: ri.currentTask,
245
+ contextPercent: ri.contextPercent,
246
+ totalTokens: ri.totalTokens,
247
+ isPrimary: false,
248
+ isVirtualOpenClaw: false,
249
+ };
250
+ items.push(item);
251
+ if (item.port !== undefined)
252
+ portToItem.set(item.port, item);
253
+ }
254
+ if (state.agentType && state.agentType !== 'daemon' && state.agentType !== 'openclaw' && state.state) {
255
+ const anchor = state.currentPort != null ? portToItem.get(state.currentPort) : undefined;
256
+ if (anchor) {
257
+ // Patch anchor with primary's live fields. macOS / Android compose
258
+ // SessionEntry from primary state and only borrow the anchor sibling's
259
+ // startedAt; if we left the sibling's snapshot in place the row would
260
+ // render stale modelName / state / currentTask whenever the sibling
261
+ // payload lagged the primary state_update. startedAt, port, id, and
262
+ // controlMode stay with the anchor so sort position and hotkey
263
+ // identity match every other surface.
264
+ anchor.isPrimary = true;
265
+ anchor.projectName = state.projectName ?? anchor.projectName;
266
+ anchor.agentType = state.agentType;
267
+ anchor.state = state.state;
268
+ anchor.modelName = state.modelName ?? undefined;
269
+ anchor.currentTask = state.currentTool ?? undefined;
270
+ }
271
+ else {
272
+ const duplicateAgentType = state.sessions.some(s => s.agentType === state.agentType);
273
+ if (!duplicateAgentType) {
274
+ items.push({
275
+ id: '__self__',
276
+ projectName: state.projectName ?? 'unknown',
277
+ agentType: state.agentType,
278
+ state: state.state,
279
+ modelName: state.modelName ?? undefined,
280
+ startedAt: undefined,
281
+ port: state.currentPort ?? undefined,
282
+ controlMode: undefined,
283
+ currentTask: state.currentTool ?? undefined,
284
+ contextPercent: undefined,
285
+ totalTokens: undefined,
286
+ isPrimary: true,
287
+ isVirtualOpenClaw: false,
288
+ });
289
+ }
290
+ }
291
+ }
292
+ const hasOpenClaw = items.some(it => it.agentType === 'openclaw' || it.agentType === 'gateway');
293
+ if (state.gatewayAvailable && !hasOpenClaw) {
294
+ items.push({
295
+ id: '__virtual_openclaw__',
296
+ projectName: 'OpenClaw',
297
+ agentType: 'openclaw',
298
+ state: state.crayfishRouting ? 'processing' : 'idle',
299
+ modelName: undefined,
300
+ startedAt: undefined,
301
+ port: undefined,
302
+ controlMode: undefined,
303
+ currentTask: undefined,
304
+ contextPercent: undefined,
305
+ totalTokens: undefined,
306
+ isPrimary: false,
307
+ isVirtualOpenClaw: true,
308
+ });
309
+ }
310
+ const sorted = sortSessions(items);
311
+ const named = assignDisplayNames(sorted.map(it => ({
312
+ id: it.id,
313
+ projectName: it.projectName,
314
+ agentType: it.agentType,
315
+ state: it.state,
316
+ })));
317
+ return sorted.map((it, i) => ({
318
+ id: it.id,
319
+ displayName: named[i].displayName,
320
+ projectName: it.projectName,
321
+ agentType: it.agentType,
322
+ state: it.state,
323
+ modelName: it.modelName,
324
+ startedAt: it.startedAt,
325
+ port: it.port,
326
+ controlMode: it.controlMode,
327
+ currentTask: it.currentTask,
328
+ contextPercent: it.contextPercent,
329
+ totalTokens: it.totalTokens,
330
+ isPrimary: it.isPrimary,
331
+ isVirtualOpenClaw: it.isVirtualOpenClaw,
332
+ }));
333
+ }
334
+ function hudHotkeyIndex(entry, nextIndex) {
335
+ if (entry.isPrimary || entry.isVirtualOpenClaw)
336
+ return null;
337
+ if (!entry.port || entry.controlMode === 'observed')
202
338
  return null;
203
339
  return nextIndex;
204
340
  }
@@ -214,6 +350,85 @@ function observedDetailParts(session) {
214
350
  parts.push(session.currentTask);
215
351
  return parts;
216
352
  }
353
+ function asRecord(value) {
354
+ return value && typeof value === 'object' && !Array.isArray(value)
355
+ ? value
356
+ : undefined;
357
+ }
358
+ function asArray(value) {
359
+ return Array.isArray(value) ? value : [];
360
+ }
361
+ function asNumber(value) {
362
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
363
+ }
364
+ function moduleStatusIcon(ok, warning = false) {
365
+ if (!terminalCaps.unicode)
366
+ return ok ? 'o' : warning ? '!' : 'x';
367
+ return ok ? '\u25CF' : warning ? '\u25C6' : '\u25CB';
368
+ }
369
+ function renderModuleHealthLines(moduleHealth, width) {
370
+ const lines = [];
371
+ if (!moduleHealth)
372
+ return lines;
373
+ const push = (label, detail, ok, warning = false) => {
374
+ const color = ok ? colors.idle : warning ? colors.awaiting : colors.dim;
375
+ lines.push(` ${color}${moduleStatusIcon(ok, warning)}${RESET} ${truncText(`${label} ${detail}`.trim(), width - 4)}`);
376
+ };
377
+ const serial = asRecord(moduleHealth.serial);
378
+ if (serial) {
379
+ const connections = asArray(serial.connections).map(asRecord).filter(Boolean);
380
+ const connected = connections.filter((c) => c.connected === true || c.transportOpen === true);
381
+ const boards = connected
382
+ .map((c) => asRecord(c.deviceInfo)?.board)
383
+ .filter((b) => typeof b === 'string' && b.length > 0);
384
+ const count = asNumber(serial.connectionCount) ?? connected.length;
385
+ const shown = boards.slice(0, 3).join(', ');
386
+ const more = boards.length > 3 ? ` +${boards.length - 3}` : '';
387
+ const detail = count > 0 ? `${count}${shown ? `: ${shown}${more}` : ''}` : 'none';
388
+ push('Serial', detail, count > 0, Boolean(serial.lastError));
389
+ }
390
+ const pixoo = asRecord(moduleHealth.pixoo);
391
+ if (pixoo) {
392
+ const devices = asArray(pixoo.devices).map(asRecord).filter(Boolean);
393
+ const configured = asNumber(pixoo.configuredDeviceCount) ?? devices.length;
394
+ const online = devices.filter((d) => d.online === true && d.backedOff !== true).length;
395
+ const dimmed = pixoo.displayDimmed === true ? ' dim' : '';
396
+ push('Pixoo', `${online}/${configured}${dimmed}`, configured > 0 && online > 0, configured > 0);
397
+ }
398
+ // BLE matrix panels — Divoom Timebox Mini + iDotMatrix. The Swift daemon
399
+ // reports a live `connected`/`statusReason`; the Node daemon only knows a
400
+ // panel is configured (the Python BLE sync subprocess owns the link), so a
401
+ // configured-but-state-unknown panel reads as amber "cfg" rather than a red
402
+ // offline, which would be a false negative for a working Node-driven panel.
403
+ for (const [key, label] of [['timebox', 'Timebox'], ['idotmatrix', 'iDotMatrix']]) {
404
+ const m = asRecord(moduleHealth[key]);
405
+ if (!m)
406
+ continue;
407
+ const configured = asNumber(m.configuredDeviceCount) ?? asArray(m.devices).length;
408
+ if (configured <= 0)
409
+ continue;
410
+ const connected = m.connected === true;
411
+ const dimmed = m.displayDimmed === true ? ' dim' : '';
412
+ const reason = typeof m.statusReason === 'string' ? m.statusReason : '';
413
+ const detail = connected ? `ready${dimmed}` : (reason || `${configured} cfg`);
414
+ push(label, detail, connected, !connected);
415
+ }
416
+ const d200h = asRecord(moduleHealth.d200h);
417
+ if (d200h) {
418
+ const connected = d200h.connected === true || d200h.managerOpened === true;
419
+ const owner = d200h.externalOwner === true ? ' plugin' : '';
420
+ const detail = connected ? `ready${owner}` : 'offline';
421
+ push('D200H', detail, connected, Boolean(d200h.lastOpenError || d200h.lastWriteError));
422
+ }
423
+ const adb = asRecord(moduleHealth.adb);
424
+ if (adb) {
425
+ const devices = asArray(adb.devices);
426
+ const reverse = asNumber(adb.reverseReadyCount) ?? devices.length;
427
+ const available = adb.available === true;
428
+ push('ADB', available ? `${reverse} reverse` : 'missing', available && reverse > 0, available);
429
+ }
430
+ return lines;
431
+ }
217
432
  // ===== Panel Renderers =====
218
433
  function renderAgentLines(state, maxWidth, useLogo) {
219
434
  const lines = [];
@@ -241,68 +456,33 @@ function renderAgentLines(state, maxWidth, useLogo) {
241
456
  lines.push(` ${hotkey} ${emoji} ${col}${name}${RESET}`);
242
457
  lines.push(`${colors.dim} ${truncText(secondary, maxWidth - 4)}${RESET}`);
243
458
  };
244
- // #N suffix via shared assignDisplayNames
245
- // Daemon mode: sessions list already contains all agents (including virtual OpenClaw).
246
- // Also applies when daemon relays OpenClaw (agentType='openclaw' but sessions has the entry).
247
- // Session bridge mode: sessions has siblings only — prepend self.
248
- const isDaemonLike = state.agentType === 'daemon' ||
249
- (state.agentType && state.sessions.some(s => s.agentType === state.agentType));
250
- if (isDaemonLike) {
251
- const sorted = sortSessions(state.sessions);
252
- const displayed = assignDisplayNames(sorted.map(s => ({
253
- id: s.id, projectName: s.projectName || 'unknown', agentType: s.agentType, state: s.state,
254
- modelName: s.modelName,
255
- port: s.port, controlMode: s.controlMode,
256
- currentTask: s.currentTask,
257
- contextPercent: s.contextPercent,
258
- totalTokens: s.totalTokens,
259
- })));
260
- let focusableIndex = 0;
261
- for (const d of displayed) {
262
- const hotkeyIndex = sessionHotkeyIndex(d.session, focusableIndex);
263
- if (hotkeyIndex !== null)
264
- focusableIndex += 1;
265
- renderSession(d.displayName, d.session.modelName, d.session.state || 'idle', d.session.agentType, hotkeyIndex, d.session);
266
- }
267
- }
268
- else if (state.state) {
269
- // Self (primary session)
270
- renderSession(state.projectName || 'unknown', state.modelName ?? undefined, state.state, state.agentType ?? undefined, null);
271
- // Siblings — include self as virtual entry for correct #N numbering
272
- const sorted = sortSessions(state.sessions);
273
- const selfEntry = {
274
- id: '__self__', projectName: state.projectName || 'unknown',
275
- agentType: state.agentType ?? undefined, state: state.state,
276
- };
277
- const allForNumbering = [selfEntry, ...sorted.map(s => ({
278
- id: s.id, projectName: s.projectName || 'unknown', agentType: s.agentType, state: s.state,
279
- modelName: s.modelName,
280
- port: s.port, controlMode: s.controlMode,
281
- currentTask: s.currentTask,
282
- contextPercent: s.contextPercent,
283
- totalTokens: s.totalTokens,
284
- }))];
285
- const displayed = assignDisplayNames(allForNumbering);
286
- // Skip the self entry (index 0), render siblings with hotkey indices
287
- let focusableIndex = 0;
288
- for (let i = 1; i < displayed.length; i++) {
289
- const d = displayed[i];
290
- const sessionInfo = d.session;
291
- const hotkeyIndex = sessionHotkeyIndex(sessionInfo, focusableIndex);
292
- if (hotkeyIndex !== null)
293
- focusableIndex += 1;
294
- renderSession(d.displayName, d.session.modelName, d.session.state || 'idle', d.session.agentType, hotkeyIndex, sessionInfo);
295
- }
296
- }
297
- // Virtual OpenClaw entry when gateway detected but not in sessions list
298
- const hasOcSession = state.sessions.some(s => s.agentType === 'openclaw' || s.agentType === 'gateway');
299
- if (state.gatewayAvailable && !hasOcSession) {
300
- renderSession('OpenClaw', undefined, state.crayfishRouting ? 'processing' : 'idle', 'openclaw', null);
459
+ // Unified entry list (primary + siblings + virtual OpenClaw). Order and #N
460
+ // suffix matches macOS / iOS / Android via the shared sortSessions +
461
+ // assignDisplayNames pipeline inside buildHudEntries.
462
+ const entries = buildHudEntries(state);
463
+ let focusableIndex = 0;
464
+ for (const e of entries) {
465
+ const hotkeyIndex = hudHotkeyIndex(e, focusableIndex);
466
+ if (hotkeyIndex !== null)
467
+ focusableIndex += 1;
468
+ renderSession(e.displayName, e.modelName, e.state, e.agentType, hotkeyIndex, {
469
+ port: e.port,
470
+ controlMode: e.controlMode,
471
+ currentTask: e.currentTask,
472
+ contextPercent: e.contextPercent,
473
+ totalTokens: e.totalTokens,
474
+ });
301
475
  }
302
476
  // Gateway error warning
303
477
  if (state.gatewayHasError) {
304
478
  lines.push(`${colors.error} \u26A0 Gateway Error${RESET}`);
305
479
  }
480
+ const moduleLines = renderModuleHealthLines(state.moduleHealth, maxWidth);
481
+ if (moduleLines.length > 0) {
482
+ lines.push('');
483
+ lines.push(`${colors.header} DOWNSTREAM${RESET}`);
484
+ lines.push(...moduleLines);
485
+ }
306
486
  // Voice assistant indicator
307
487
  if (state.voiceAssistantState && state.voiceAssistantState !== 'disabled' && state.voiceAssistantState !== 'idle') {
308
488
  lines.push('');
@@ -391,9 +571,9 @@ function renderStatusLines(state, width) {
391
571
  }
392
572
  function renderOauthCatalogLines(modelCatalog, oauthConnected, width) {
393
573
  const lines = [];
394
- const models = modelCatalog.filter((m) => m.available).map((m) => m.name);
574
+ const models = (modelCatalog ?? []).filter((m) => m.available).map((m) => m.name);
395
575
  if (models.length > 0) {
396
- return wrapCommaList(' OAuth: ', models, width);
576
+ return wrapCommaList(' OAuth: ', models, width, 4);
397
577
  }
398
578
  if (oauthConnected === true) {
399
579
  lines.push(`${colors.dim}${truncText(' OAuth: connected', width)}${RESET}`);
@@ -403,22 +583,38 @@ function renderOauthCatalogLines(modelCatalog, oauthConnected, width) {
403
583
  }
404
584
  return lines;
405
585
  }
406
- function wrapCommaList(prefix, items, width) {
586
+ function wrapCommaList(prefix, items, width, maxLines = Number.POSITIVE_INFINITY) {
407
587
  const lines = [];
408
588
  const indent = ' '.repeat(prefix.length);
409
589
  let current = prefix;
590
+ let consumed = 0;
410
591
  for (let i = 0; i < items.length; i++) {
411
592
  const chunk = i === 0 ? items[i] : `, ${items[i]}`;
412
593
  if (visLen(current) + visLen(chunk) <= width) {
413
594
  current += chunk;
595
+ consumed = i + 1;
414
596
  continue;
415
597
  }
416
598
  if (visLen(current) > visLen(prefix)) {
417
599
  lines.push(`${colors.dim}${padRight(current, width)}${RESET}`.trimEnd());
600
+ if (lines.length >= maxLines) {
601
+ const hidden = items.length - consumed;
602
+ if (hidden > 0)
603
+ lines.push(`${colors.dim}${truncText(` ${hidden} more models`, width)}${RESET}`);
604
+ return lines;
605
+ }
418
606
  current = indent + items[i];
607
+ consumed = i + 1;
419
608
  continue;
420
609
  }
421
610
  lines.push(`${colors.dim}${truncText(current + chunk, width)}${RESET}`);
611
+ consumed = i + 1;
612
+ if (lines.length >= maxLines) {
613
+ const hidden = items.length - consumed;
614
+ if (hidden > 0)
615
+ lines.push(`${colors.dim}${truncText(` ${hidden} more models`, width)}${RESET}`);
616
+ return lines;
617
+ }
422
618
  current = indent;
423
619
  }
424
620
  if (visLen(current.trim()) > 0) {
@@ -451,12 +647,33 @@ function renderTimelineLines(state, width, maxLines, scrollOffset) {
451
647
  const e = entries[i];
452
648
  const time = new Date(e.ts);
453
649
  const timeStr = `${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}`;
454
- const raw = truncText(e.raw, width - 10);
650
+ // task_end rows append a score+outcome suffix once the LLM judge resolves
651
+ // (5–30 s after the boundary itself). Until then the `taskOutcome` field
652
+ // is undefined and we render only the boundary label.
653
+ const evalSuffix = (e.type === 'task_end' && e.taskOutcome)
654
+ ? formatTaskEvalSuffix(e.taskScore, e.taskOutcome)
655
+ : '';
656
+ const raw = truncText(`${e.raw}${evalSuffix}`, width - 10);
455
657
  const pending = e.status === 'pending' ? `${colors.dim} [PENDING]${RESET}` : '';
456
658
  lines.push(` ${colors.dim}${timeStr}${RESET} ${typeColor(e.type)}${typeIcon(e.type)}${RESET} ${raw}${pending}`);
457
659
  }
458
660
  return lines;
459
661
  }
662
+ export function formatTaskEvalSuffix(score, outcome) {
663
+ // `abandoned` flows from the manual `agentdeck task cancel` path and
664
+ // must render as its own glyph — not fall through to '' (which would
665
+ // make the task look pending) and not borrow the fail glyph (which
666
+ // would read as agent failure rather than user-initiated stop).
667
+ const glyph = outcome === 'success' ? '✓'
668
+ : outcome === 'fail' ? '✗'
669
+ : outcome === 'partial' ? '△'
670
+ : outcome === 'abandoned' ? '⊘'
671
+ : '';
672
+ if (!glyph)
673
+ return '';
674
+ const scoreText = typeof score === 'number' ? score.toFixed(2) : '?';
675
+ return ` · ${scoreText} ${glyph}`;
676
+ }
460
677
  function timelineHeader(state, width, maxLines, scrollOffset) {
461
678
  const total = state.timeline.length;
462
679
  if (total === 0)
@@ -649,64 +866,31 @@ function renderStandardLayout(state, cols, rows, terrariumLines, frame, scrollOf
649
866
  }
650
867
  function renderAgentCompactLines(state, width) {
651
868
  const lines = [];
652
- const isDaemonLikeCompact = state.agentType === 'daemon' ||
653
- (state.agentType && state.sessions.some(s => s.agentType === state.agentType));
654
- if (isDaemonLikeCompact) {
655
- let focusableIndex = 0;
656
- for (const s of sortSessions(state.sessions)) {
657
- const renderInfo = s;
658
- const st = s.state || 'idle';
659
- const col = stateColor(st);
660
- const emoji = `${creatureBrandColor(s.agentType)}${creatureEmoji(s.agentType)}${RESET}`;
661
- const status = `${col}${stateIcon(st)} ${compactStateLabel(st)}${RESET}`;
662
- const detail = renderInfo.currentTask || s.modelName;
663
- const model = detail ? `${colors.dim} · ${truncText(detail, Math.max(8, width - 24))}${RESET}` : '';
664
- const marker = renderInfo.controlMode === 'observed' ? `${colors.dim} · obs${RESET}` : '';
665
- const project = truncText(s.projectName || '?', Math.max(8, width - 20));
666
- const hotkeyIndex = sessionHotkeyIndex(renderInfo, focusableIndex);
667
- if (hotkeyIndex !== null)
668
- focusableIndex += 1;
669
- lines.push(` ${colors.dim}[${sessionHotkeyLabel(hotkeyIndex)}]${RESET} ${emoji} ${project} ${status}${marker}${model}`);
670
- }
671
- }
672
- else if (state.state) {
673
- // Self first
674
- const col = stateColor(state.state);
675
- const proj = truncText(state.projectName || '?', Math.max(8, width - 16));
676
- const status = `${col}${stateIcon(state.state)} ${compactStateLabel(state.state)}${RESET}`;
677
- const model = state.modelName ? `${colors.dim} · ${truncText(state.modelName, Math.max(8, width - 24))}${RESET}` : '';
678
- const emoji = `${creatureBrandColor(state.agentType ?? undefined)}${creatureEmoji(state.agentType ?? undefined)}${RESET}`;
679
- lines.push(` ${emoji} ${proj} ${status}${model}`);
680
- // Siblings
681
- let focusableIndex = 0;
682
- for (const s of sortSessions(state.sessions)) {
683
- const renderInfo = s;
684
- const st = s.state || 'idle';
685
- const sCol = stateColor(st);
686
- const sEmoji = `${creatureBrandColor(s.agentType)}${creatureEmoji(s.agentType)}${RESET}`;
687
- const sStatus = `${sCol}${stateIcon(st)} ${compactStateLabel(st)}${RESET}`;
688
- const detail = renderInfo.currentTask || s.modelName;
689
- const sModel = detail ? `${colors.dim} · ${truncText(detail, Math.max(8, width - 24))}${RESET}` : '';
690
- const marker = renderInfo.controlMode === 'observed' ? `${colors.dim} · obs${RESET}` : '';
691
- const sProject = truncText(s.projectName || '?', Math.max(8, width - 20));
692
- const hotkeyIndex = sessionHotkeyIndex(renderInfo, focusableIndex);
693
- if (hotkeyIndex !== null)
694
- focusableIndex += 1;
695
- lines.push(` ${colors.dim}[${sessionHotkeyLabel(hotkeyIndex)}]${RESET} ${sEmoji} ${sProject} ${sStatus}${marker}${sModel}`);
696
- }
697
- }
698
- // Virtual OpenClaw entry
699
- const hasOcCompact = state.sessions.some(s => s.agentType === 'openclaw' || s.agentType === 'gateway');
700
- if (state.gatewayAvailable && !hasOcCompact) {
701
- const ocState = state.crayfishRouting ? 'processing' : 'idle';
702
- const ocCol = stateColor(ocState);
703
- const ocEmoji = `${creatureBrandColor('openclaw')}${creatureEmoji('openclaw')}${RESET}`;
704
- lines.push(` ${ocEmoji} OpenClaw ${ocCol}${stateIcon(ocState)}${ocState.toUpperCase()}${RESET}`);
869
+ // Unified entry list same ordering and #N suffix as macOS / iOS / Android.
870
+ const entries = buildHudEntries(state);
871
+ let focusableIndex = 0;
872
+ for (const e of entries) {
873
+ const hotkeyIndex = hudHotkeyIndex(e, focusableIndex);
874
+ if (hotkeyIndex !== null)
875
+ focusableIndex += 1;
876
+ const col = stateColor(e.state);
877
+ const emoji = `${creatureBrandColor(e.agentType)}${creatureEmoji(e.agentType)}${RESET}`;
878
+ const status = `${col}${stateIcon(e.state)} ${compactStateLabel(e.state)}${RESET}`;
879
+ const detail = e.currentTask || e.modelName;
880
+ const model = detail ? `${colors.dim} · ${truncText(detail, Math.max(8, width - 24))}${RESET}` : '';
881
+ const marker = e.controlMode === 'observed' ? `${colors.dim} · obs${RESET}` : '';
882
+ const project = truncText(e.displayName, Math.max(8, width - 20));
883
+ lines.push(` ${colors.dim}[${sessionHotkeyLabel(hotkeyIndex)}]${RESET} ${emoji} ${project} ${status}${marker}${model}`);
705
884
  }
706
885
  // Gateway error warning
707
886
  if (state.gatewayHasError) {
708
887
  lines.push(`${colors.error} \u26A0 Gateway Error${RESET}`);
709
888
  }
889
+ const moduleLines = renderModuleHealthLines(state.moduleHealth, width);
890
+ if (moduleLines.length > 0) {
891
+ lines.push(`${colors.header} DOWNSTREAM${RESET}`);
892
+ lines.push(...moduleLines);
893
+ }
710
894
  // Voice assistant indicator (compact)
711
895
  if (state.voiceAssistantState && state.voiceAssistantState !== 'disabled' && state.voiceAssistantState !== 'idle') {
712
896
  if (state.voiceAssistantState === 'listening') {