@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
package/dist/index.js CHANGED
@@ -5,13 +5,18 @@
5
5
  * Uses BridgeCore for shared infrastructure.
6
6
  */
7
7
  import { BridgeCore } from './bridge-core.js';
8
- import { initApme } from './apme/index.js';
8
+ import { buildDisplayStateEvent } from './display-dim.js';
9
+ import { SERIAL_FORWARDED_EVENTS } from '@agentdeck/shared/protocol';
10
+ import { prepareForSerial } from './esp32-serial.js';
11
+ import { initApme, isTimelineProjectionEnabled } from './apme/index.js';
12
+ import { FallbackTaskTimeline } from './fallback-task-timeline.js';
9
13
  import { readLastTurn as readClaudeTranscriptLastTurn } from './apme/claude-transcript-reader.js';
10
14
  import { claudeHookToSpans } from './apme/adapters/claude-hook.js';
15
+ import { codexHookToSpans } from './apme/adapters/codex-hook.js';
16
+ import { CodexTurnManager } from './apme/adapters/codex-turn-manager.js';
11
17
  import { claudePtyParserEventToSpans, claudePtyResponseToSpan } from './apme/adapters/claude-pty.js';
12
18
  import { timelineEntryToSpans } from './apme/adapters/timeline.js';
13
- import { spanNameForKind } from '@agentdeck/shared';
14
- import { randomUUID } from 'crypto';
19
+ import { classifyAndEnqueueTurn } from './apme/classify-turn.js';
15
20
  import { VoiceManager } from './voice.js';
16
21
  import { checkDependencies } from './check-deps.js';
17
22
  import { enableDebugLog, log, debug, setPtyMode } from './logger.js';
@@ -22,8 +27,8 @@ import { createAdapter, ClaudeCodeAdapter, CodexCliAdapter, OpenCodeAdapter } fr
22
27
  import { MonitorAdapter } from './adapters/monitor.js';
23
28
  import { UtilityProxy } from './utility-proxy.js';
24
29
  import { BridgeLogStream } from './log-stream.js';
25
- import { extractTopicHint, summarizeResponse } from './timeline-summarizer.js';
26
- import { cleanDetailText, cleanRawText } from '@agentdeck/shared';
30
+ import { cleanRawText, prepareMarkdownDetail, extractTopicHintWithKind, promptSnippetFallback, } from '@agentdeck/shared';
31
+ import { injectOpenClawSession } from './openclaw-session.js';
27
32
  import { VoiceAssistantManager } from './voice-assistant.js';
28
33
  import { TerminalStatus } from './terminal-status.js';
29
34
  import { readFileSync } from 'fs';
@@ -142,6 +147,15 @@ export async function startSession(opts) {
142
147
  log(`Starting AgentDeck bridge on port ${port} (agent: ${agentType})...`);
143
148
  // ===== Create adapter =====
144
149
  const adapter = createAdapter(agentType, opts.gatewayUrl);
150
+ // Seed the resolved (git-aware) name into the Claude output parser so its
151
+ // legacy PROJECT_DIR scrape doesn't override it: the scrape captures the
152
+ // bare basename of ANY path-like terminal line (first match sticks) and
153
+ // flows through the state-machine snapshot, which used to win over the
154
+ // resolver — a monorepo-subdir session would flip from the repo name to
155
+ // the subdir (or to an unrelated printed path).
156
+ if (adapter instanceof ClaudeCodeAdapter) {
157
+ adapter.seedProjectName(projectName);
158
+ }
145
159
  // ===== Start adapter (creates HTTP server, spawns process) =====
146
160
  try {
147
161
  await adapter.start({ port, command: opts.command, gatewayUrl: opts.gatewayUrl });
@@ -162,36 +176,47 @@ export async function startSession(opts) {
162
176
  projectName,
163
177
  httpServer: adapter.getHttpServer(),
164
178
  });
179
+ const esp32WifiEvents = new Set([
180
+ ...SERIAL_FORWARDED_EVENTS,
181
+ 'esp32_ota_begin',
182
+ 'esp32_ota_chunk',
183
+ 'esp32_ota_end',
184
+ 'esp32_ota_abort',
185
+ ]);
186
+ core.wsServer.setEventTransformer((event, client) => {
187
+ if (!core.wsServer.isEsp32Client(client))
188
+ return event;
189
+ if (!esp32WifiEvents.has(event.type))
190
+ return null;
191
+ return prepareForSerial(event);
192
+ });
165
193
  // ===== APME (Agent Performance Monitoring & Evaluation) =====
166
194
  // Optional: degrades to no-op if better-sqlite3 isn't installed.
167
- const apme = await initApme();
195
+ // emitTimeline forwards task_start/task_end so the dashboard sees the task
196
+ // hierarchy header rows that group turns by APME boundary signal.
197
+ const projectTimeline = isTimelineProjectionEnabled();
198
+ const apme = await initApme(undefined, {
199
+ emitTimeline: (entry) => core.bridgeTimeline.addEntry(entry),
200
+ projectSampleTimeline: projectTimeline,
201
+ emitProjectedTimeline: (entry) => core.bridgeTimeline.addEntry(entry, { bypassSuppression: true }),
202
+ });
168
203
  if (apme) {
169
204
  core.setApme(apme, process.cwd());
205
+ if (projectTimeline) {
206
+ core.bridgeTimeline.setSuppressLocalChatTool(true);
207
+ log('APME timeline projection ENABLED (AGENTDECK_TIMELINE_PROJECTION=1) — chat/tool rows derive from SessionSample');
208
+ }
170
209
  log('APME enabled — runs will be logged to ~/.agentdeck/apme.sqlite');
171
- // Wire APME eval results timeline entries (session bridge mode).
172
- // Daemon has its own richer wiring; this covers standalone session bridges.
173
- apme.runner.onResult(({ runId, turnId }) => {
174
- if (turnId) {
175
- // Turn-level eval completed
176
- const run = apme.store.getRun(runId);
177
- if (!run)
178
- return;
179
- const turnEvals = apme.store.listEvalsForTurn(turnId);
180
- const overall = turnEvals.find(e => e.metric === 'overall');
181
- if (!overall)
182
- return;
183
- const pct = Math.round(overall.score * 100);
184
- const axes = turnEvals.filter(e => e.metric !== 'overall');
185
- const axisStr = axes.map(e => `${e.metric}=${Math.round(e.score * 100)}%`).join(' ');
186
- core.bridgeTimeline.addEntry({
187
- ts: Date.now(), type: 'eval_result',
188
- raw: `★ turn ${pct}% [${run.taskCategory ?? '?'}]`,
189
- detail: axisStr || `Turn eval · ${run.taskPrompt?.slice(0, 80) ?? ''}`,
190
- agentType: run.agentType,
191
- });
192
- }
193
- });
210
+ // Eval results are no longer projected onto the timeline (the timeline is an
211
+ // activity log only). Scores are stored in apme.sqlite and surfaced via the
212
+ // scorecard / `apme_eval` WS event, keeping the timeline noise-free.
194
213
  }
214
+ // APME down (better-sqlite3 failed to load): keep the timeline's task
215
+ // hierarchy alive by minting fallback task_start/task_end rows from the
216
+ // hook boundary signals. No storage, no eval — structure only.
217
+ const fallbackTasks = apme
218
+ ? null
219
+ : new FallbackTaskTimeline((entry) => core.bridgeTimeline.addEntry(entry), { agentType });
195
220
  // ===== Session-specific components =====
196
221
  const voiceManager = new VoiceManager();
197
222
  const utilityProxy = new UtilityProxy();
@@ -260,26 +285,29 @@ export async function startSession(opts) {
260
285
  hookServer.setMeta({ agentType, projectName });
261
286
  hookServer.setVoiceManager(voiceManager);
262
287
  hookServer.onApiUsage(() => ({ usage: core.cachedApiUsage, fetchedAt: core.lastApiFetchTime }));
263
- hookServer.pairingToken = core.authToken;
264
288
  }
265
289
  else if (adapter instanceof CodexCliAdapter) {
266
290
  hookServer = adapter.getCodexHookServer();
267
291
  hookServer.setMeta({ agentType, projectName });
268
292
  hookServer.setVoiceManager(voiceManager);
269
- hookServer.pairingToken = core.authToken;
270
293
  }
271
294
  else if (adapter instanceof OpenCodeAdapter) {
272
295
  hookServer = adapter.getHookServer();
273
296
  hookServer.setMeta({ agentType, projectName });
274
297
  hookServer.setVoiceManager(voiceManager);
275
- hookServer.pairingToken = core.authToken;
298
+ // OpenCode has no CLI-level hooks; SSE is the only event channel. Bind
299
+ // the adapter to the APME session so SSE-derived spans (tool_call /
300
+ // tool_result / task_boundary / turn_*) can reach the collector. Without
301
+ // this OpenCode sessions land in APME's runs table but never get
302
+ // per-task evaluation — every session collapses to a single session_end
303
+ // task boundary, defeating per-task scoring.
304
+ adapter.setApmeSession(core.sessionId, process.cwd());
276
305
  }
277
306
  else if (adapter instanceof MonitorAdapter) {
278
307
  hookServer = adapter.getHookServer();
279
308
  hookServer.setMeta({ agentType, projectName });
280
309
  hookServer.setVoiceManager(voiceManager);
281
310
  hookServer.onApiUsage(() => ({ usage: core.cachedApiUsage, fetchedAt: core.lastApiFetchTime }));
282
- hookServer.pairingToken = core.authToken;
283
311
  }
284
312
  const broadcastSse = (event) => hookServer?.broadcastSse(event);
285
313
  core.setSseBroadcast(broadcastSse);
@@ -305,6 +333,7 @@ export async function startSession(opts) {
305
333
  adb: 'auto',
306
334
  serial: false, // daemon-only — session bridges never talk to ESP32
307
335
  pixoo: false, // daemon-only — session bridges never talk to Pixoo
336
+ timebox: false, // daemon-only — session bridges never talk to Timebox
308
337
  };
309
338
  const deviceModules = createDefaultModules(agentType);
310
339
  // Set up ESP32 state provider before module init
@@ -318,7 +347,7 @@ export async function startSession(opts) {
318
347
  if (lastStateEvent)
319
348
  events.push(lastStateEvent);
320
349
  events.push(core.buildUsage());
321
- events.push({ type: 'display_state', displayOn: core.displayMonitor.isDisplayOn() });
350
+ events.push(buildDisplayStateEvent(core.displayMonitor.isDisplayOn()));
322
351
  core.broadcastSessionsList().catch(() => { });
323
352
  return events;
324
353
  });
@@ -337,8 +366,12 @@ export async function startSession(opts) {
337
366
  if (wifiConfig?.autoProvision) {
338
367
  const lanIp = getLanIp();
339
368
  onESP32Message((portPath, msg) => {
340
- if (msg.type === 'device_info' && !msg.wifiConnected) {
341
- sendWifiProvisionToAll({
369
+ const shouldRefreshIps10Endpoint = msg.type === 'device_info' &&
370
+ msg.board === 'ips_10' &&
371
+ msg.wifiConnected &&
372
+ !msg.wifiRadioParked;
373
+ if (msg.type === 'device_info' && (!msg.wifiConnected || shouldRefreshIps10Endpoint)) {
374
+ const sent = sendWifiProvisionToAll({
342
375
  type: 'wifi_provision',
343
376
  ssid: wifiConfig.ssid,
344
377
  password: wifiConfig.password,
@@ -346,7 +379,8 @@ export async function startSession(opts) {
346
379
  bridgePort: port,
347
380
  authToken: core.authToken,
348
381
  });
349
- log(`WiFi provision sent to ESP32 on ${portPath}`);
382
+ if (sent > 0)
383
+ log(`WiFi provision sent to ${sent} ESP32 device(s) after ${portPath}`);
350
384
  }
351
385
  else if (msg.type === 'wifi_provision_ack') {
352
386
  log(msg.success ? `ESP32 WiFi connected: ${msg.ip} \u2713` : `ESP32 WiFi failed: ${msg.error || 'unknown'}`);
@@ -365,7 +399,11 @@ export async function startSession(opts) {
365
399
  ],
366
400
  }));
367
401
  // Pixoo live preview frame getter for GET /pixoo/frame
368
- hookServer?.setPixooFrameGetter(() => getLastFrame() ?? renderPreviewFrame());
402
+ hookServer?.setPixooFrameGetter((size, layout) =>
403
+ // The frame cache holds the standard terrarium, so render micro fresh.
404
+ layout === 'micro'
405
+ ? renderPreviewFrame(size, 'micro')
406
+ : (getLastFrame(size) ?? renderPreviewFrame(size)));
369
407
  // ===== Diagnostics =====
370
408
  adapter.onDiag((tail) => createDiagDump(core.stateMachine, core.wsServer, journal, ptyRingBuffer, tail));
371
409
  adapter.onRawData((data) => {
@@ -400,14 +438,17 @@ export async function startSession(opts) {
400
438
  switch (evt.source) {
401
439
  case 'hook':
402
440
  journal.write('hook', 'hook', { event: evt.event, data: evt.data });
403
- // APME ingestion via the shared TelemetrySpan envelope. The Claude
404
- // hook adapter handles `/clear` (emits a `task_boundary` span) so
405
- // there's no separate splitRun branch here. Path B/C below preserve
406
- // the existing PTY-vs-hook race coordination that's about timing,
407
- // not ingestion semantics, so it stays in this file.
441
+ // APME ingestion via the shared TelemetrySpan envelope. Codex
442
+ // CLI sessions go through codexHookToSpans (codex_* event prefix);
443
+ // Claude Code goes through claudeHookToSpans. Both adapters detect
444
+ // `/clear` and emit a `task_boundary` span so there's no separate
445
+ // splitRun branch here.
408
446
  if (apme) {
409
447
  const ctx = makeApmeAdapterCtx(apme, core.sessionId, agentType);
410
- for (const span of claudeHookToSpans(ctx, evt.event, evt.data ?? {})) {
448
+ const spans = agentType === 'codex-cli'
449
+ ? codexHookToSpans(ctx, evt.event, evt.data ?? {})
450
+ : claudeHookToSpans(ctx, evt.event, evt.data ?? {});
451
+ for (const span of spans) {
411
452
  apme.collector.ingestSpan(core.sessionId, span);
412
453
  }
413
454
  }
@@ -433,6 +474,9 @@ export async function startSession(opts) {
433
474
  }
434
475
  if (evt.event === 'Stop')
435
476
  pendingPtyResponse = null; // Stop has cleaner response
477
+ // APME-off fallback: task rows from boundary hooks alone.
478
+ if (fallbackTasks)
479
+ fallbackTasks.ingestHook(core.sessionId, evt.event, evt.data ?? {});
436
480
  if (evt.event === 'shutdown') {
437
481
  shutdown();
438
482
  return;
@@ -613,10 +657,15 @@ export async function startSession(opts) {
613
657
  if (core.wsServer.getClientCount() > 0 && Date.now() - core.lastApiFetchTime > 10_000) {
614
658
  core.fetchAndUpdateUsage().catch(() => { });
615
659
  }
616
- // Voice assistant: if processing a wake-word prompt, speak the response
660
+ // Voice assistant: if processing a wake-word prompt, speak the response.
661
+ // Prefer chat_response (the turn's primary completion row now that a
662
+ // paired chat_end is no longer emitted for turns that have response
663
+ // text); fall back to chat_end for response-less turns. Read detail
664
+ // (full markdown) first, then the raw snippet.
617
665
  if (voiceAssistant?.getState() === 'processing') {
618
- const lastEntry = core.bridgeTimeline.getLastEntry('chat_end');
619
- const responseText = lastEntry?.detail;
666
+ const lastEntry = core.bridgeTimeline.getLastEntry('chat_response')
667
+ ?? core.bridgeTimeline.getLastEntry('chat_end');
668
+ const responseText = lastEntry?.detail ?? lastEntry?.raw;
620
669
  if (responseText) {
621
670
  voiceAssistant.handleResponse(responseText).catch((err) => {
622
671
  debug('agentdeck', `Voice assistant TTS error: ${err}`);
@@ -752,11 +801,17 @@ export async function startSession(opts) {
752
801
  });
753
802
  // Deck slot map relay
754
803
  core.wsServer.onRawMessage((msg, sender) => {
804
+ if (msg.type === 'device_info') {
805
+ core.wsServer.markEsp32Client(sender);
806
+ return false;
807
+ }
755
808
  if (msg.type === 'deck_slot_map') {
756
809
  cachedSlotMap = msg;
757
810
  core.wsServer.broadcastExcept(cachedSlotMap, sender);
758
811
  broadcastSse(cachedSlotMap);
759
812
  core.broadcast(computeButtonState());
813
+ // Re-broadcast sessions_list so slot buttons populate after plugin startup race
814
+ core.broadcastSessionsList().catch(() => { });
760
815
  return true;
761
816
  }
762
817
  return false;
@@ -843,20 +898,12 @@ export async function startSession(opts) {
843
898
  core.startGatewayProbe(800);
844
899
  core.startGatewayHealthCheck();
845
900
  core.startSessionsListPolling();
846
- // Inject virtual OpenClaw session when Gateway is detected (same as daemon-server.ts)
847
- core.setSessionsEnricher((sessions) => {
848
- if (!core.cachedGatewayAvailable)
849
- return sessions;
850
- if (sessions.some(s => s.agentType === 'openclaw'))
851
- return sessions;
852
- return [...sessions, {
853
- id: 'openclaw-gateway',
854
- port: 18789,
855
- projectName: 'OpenClaw',
856
- agentType: 'openclaw',
857
- alive: true,
858
- }];
859
- });
901
+ // Inject virtual OpenClaw session only after Gateway authentication succeeds
902
+ // (gatewayConnected). Reachability alone (cachedGatewayAvailable) is a
903
+ // topology signal, not proof commands can route — gating on it kept a phantom
904
+ // OpenClaw session alive whenever anything held port 18789 open. Shared
905
+ // injector with daemon-server.ts; mirror of Swift buildSessionsListEvent.
906
+ core.setSessionsEnricher((sessions) => injectOpenClawSession(sessions, { gatewayConnected: core.cachedGatewayConnected }));
860
907
  // ===== Encoder state computation =====
861
908
  function computeEncoderState() {
862
909
  const snapshot = core.stateMachine.getSnapshot();
@@ -1133,110 +1180,60 @@ export async function startSession(opts) {
1133
1180
  core.shutdown();
1134
1181
  }
1135
1182
  }
1136
- // Mid-session classify + turn-level eval enqueue.
1137
- // Shared between Claude spinner_stop, OpenClaw/OpenCode chat_response, and Codex spinner_stop
1138
- // so all agents get the same turns.task_category stamping and non-code turn_judge evals.
1139
- async function classifyAndEnqueueTurn(apme, sid) {
1140
- const turnId = apme.collector.getActiveTurnId(sid);
1141
- const runId = apme.collector.getRunId(sid);
1142
- if (!turnId || !runId)
1143
- return;
1144
- const run = apme.store.getRun(runId);
1145
- if (!run)
1146
- return;
1147
- let category = run.taskCategory ?? null;
1148
- if (!category) {
1149
- try {
1150
- const { classifyRun } = await import('./apme/classifier.js');
1151
- const { category: c, signals } = classifyRun(apme.store, run.id);
1152
- if (c && c !== 'unknown') {
1153
- category = c;
1154
- apme.store.updateRun(run.id, {
1155
- taskCategory: c,
1156
- taskSignals: JSON.stringify(signals),
1157
- taskCategorySource: 'rule',
1158
- });
1159
- }
1160
- }
1161
- catch (err) {
1162
- debug('APME', `mid-session classify failed: ${String(err)}`);
1163
- }
1164
- }
1165
- if (category) {
1166
- try {
1167
- apme.store.updateTurn(turnId, { taskCategory: category });
1168
- }
1169
- catch { /* ignore */ }
1170
- }
1171
- const NON_CODE = new Set(['conversation', 'planning', 'research', 'review']);
1172
- if (category && NON_CODE.has(category)) {
1173
- apme.runner.enqueueTurn({ runId: run.id, turnId, category });
1174
- }
1175
- }
1176
1183
  // ===== Non-Claude agent APME wiring =====
1177
1184
  // For OpenCode, Codex, OpenClaw: intercepts adapter timeline events to create
1178
1185
  // APME turns with prompts and responses. Claude Code uses hooks instead.
1179
1186
  function wireAgentApme(adapter, agentType, apme, core, ptyRingBuffer) {
1180
1187
  const sid = core.sessionId;
1188
+ // Codex turn boundaries (hook-primary, PTY-fallback) live in their own
1189
+ // module. The class encapsulates the seven-state-variable PTY-only
1190
+ // machine plus the hook-fresh shortcut so non-codex sessions allocate
1191
+ // nothing here.
1192
+ const codexManager = agentType === 'codex-cli'
1193
+ ? new CodexTurnManager(core, apme, ptyRingBuffer, sid, agentType)
1194
+ : null;
1181
1195
  adapter.on('event', (evt) => {
1182
1196
  // ── Timeline events (OpenCode / OpenClaw) ──
1183
1197
  // Both adapters emit { source: 'timeline', entry: TimelineEntry }.
1184
1198
  // The timeline adapter normalizes them to AgentDeck telemetry spans.
1185
1199
  if (evt.source === 'timeline' && evt.entry) {
1186
1200
  const entry = evt.entry;
1187
- const ctx = makeApmeAdapterCtx(apme, sid, agentType);
1188
- for (const span of timelineEntryToSpans(ctx, entry)) {
1189
- apme.collector.ingestSpan(sid, span);
1201
+ // Upserts refine an already-ingested row (chat_start topic hint, async
1202
+ // LLM summary) converting them to spans again would open a phantom
1203
+ // turn or overwrite the captured response with a summary label.
1204
+ if (evt.upsert)
1205
+ return;
1206
+ // Adapters bound to APME directly (OpenCode via setApmeSession) ingest
1207
+ // richer spans from their native event stream; converting their
1208
+ // timeline rows as well double-counted every turn and tool (the tool
1209
+ // sample dedupCore is counter-based, so storage-time dedup can't
1210
+ // collapse the second copy). Timeline conversion is the fallback for
1211
+ // sessions without a direct binding.
1212
+ const directApme = adapter
1213
+ .hasDirectApmeIngestion?.() === true;
1214
+ if (!directApme) {
1215
+ const ctx = makeApmeAdapterCtx(apme, sid, agentType);
1216
+ for (const span of timelineEntryToSpans(ctx, entry)) {
1217
+ apme.collector.ingestSpan(sid, span);
1218
+ }
1190
1219
  }
1191
- // Mid-session classification still runs once we've captured a response.
1220
+ // Mid-session classification still runs once we've captured a response
1221
+ // (both ingestion paths write the turn response before this fires).
1192
1222
  if (entry.type === 'chat_response') {
1193
1223
  void classifyAndEnqueueTurn(apme, sid);
1194
1224
  }
1195
1225
  }
1196
- // ── Codex: PTY parser fallback ──
1197
- if (evt.source === 'parser' && agentType === 'codex-cli') {
1198
- const ctx = makeApmeAdapterCtx(apme, sid, agentType);
1199
- if (evt.event === 'user_prompt') {
1200
- const text = evt.data?.text;
1201
- if (text) {
1202
- // PTY-derived prompt = synthetic turn_start span. We construct it
1203
- // inline rather than reusing claudeHookToSpans because the source
1204
- // is a parser event, not a hook payload.
1205
- const span = {
1206
- traceId: ctx.traceId,
1207
- spanId: randomUUID(),
1208
- parentSpanId: ctx.activeTurnId,
1209
- name: spanNameForKind('turn_start'),
1210
- kind: 'turn_start',
1211
- ts: Date.now(),
1212
- attributes: {
1213
- 'agentdeck.agent_type': ctx.agentType,
1214
- 'agentdeck.prompt_text': text,
1215
- ...(ctx.cwd ? { 'agentdeck.cwd': ctx.cwd } : {}),
1216
- },
1217
- };
1218
- apme.collector.ingestSpan(sid, span);
1219
- }
1220
- }
1221
- if (evt.event === 'spinner_stop') {
1222
- setTimeout(() => {
1223
- const tail = ptyRingBuffer.getTail(5000);
1224
- const lines = tail.split('\n').map(l => l.trim()).filter(Boolean);
1225
- // Filter out spinner/UI artifacts from tail (Codex PTY output)
1226
- const clean = lines.filter(l => !/^[✢✳✶✻✽⏸⏵❯─>]/.test(l) &&
1227
- !/planmode|plan\s*mode|shift\+tab|accept\s*edits/i.test(l) &&
1228
- !/\?\s*for\s*shortcuts/.test(l));
1229
- const response = clean.slice(-5).join('\n');
1230
- if (response.length > 2) {
1231
- const span = claudePtyResponseToSpan(ctx, response);
1232
- if (span)
1233
- apme.collector.ingestSpan(sid, span);
1234
- void classifyAndEnqueueTurn(apme, sid);
1235
- }
1236
- }, 500);
1237
- }
1226
+ // ── Codex: hook (primary) + PTY-parser fallback ──
1227
+ if (codexManager) {
1228
+ if (evt.source === 'hook')
1229
+ codexManager.onHookEvent(evt);
1230
+ else if (evt.source === 'parser')
1231
+ codexManager.onParserEvent(evt);
1238
1232
  }
1239
1233
  });
1234
+ if (codexManager) {
1235
+ core.onShutdown(() => codexManager.cleanup());
1236
+ }
1240
1237
  }
1241
1238
  /** Build the AdapterContext used by every APME ingestion adapter. The
1242
1239
  * trace id mirrors the active run id so future OTLP exports tie spans
@@ -1251,6 +1248,9 @@ function makeApmeAdapterCtx(apme, sid, agentType) {
1251
1248
  activeTurnId: apme.collector.getActiveTurnId(sid) ?? undefined,
1252
1249
  };
1253
1250
  }
1251
+ function isClaudeTaskNotificationPrompt(text) {
1252
+ return text.trim().toLowerCase().startsWith('<task-notification>');
1253
+ }
1254
1254
  // ===== Claude Code timeline wiring =====
1255
1255
  function wireClaudeCodeTimeline(adapter, core, journal, ptyRingBuffer) {
1256
1256
  let ccChatStart = null;
@@ -1266,6 +1266,10 @@ function wireClaudeCodeTimeline(adapter, core, journal, ptyRingBuffer) {
1266
1266
  ccPendingChatStartTimer = null;
1267
1267
  }
1268
1268
  ccPendingChatStart = false;
1269
+ if (isClaudeTaskNotificationPrompt(text)) {
1270
+ ccLastPromptText = null;
1271
+ return;
1272
+ }
1269
1273
  ccLastPromptText = text || null;
1270
1274
  const snippet = text.length > 500 ? text.slice(0, 497) + '...' : text;
1271
1275
  const detail = text.length > 100 ? (text.length > 1000 ? text.slice(0, 1000) + '...' : text) : undefined;
@@ -1274,11 +1278,16 @@ function wireClaudeCodeTimeline(adapter, core, journal, ptyRingBuffer) {
1274
1278
  const proj = core.stateMachine.getSnapshot().projectName;
1275
1279
  return proj ? `Prompt \u00B7 ${proj}` : 'Prompt sent';
1276
1280
  })();
1281
+ // taskId/runId are filled in by BridgeCore's timeline attributor
1282
+ // (wireTimeline → setAttributor) from the active APME task for this
1283
+ // session, so managed-session chat rows nest under their task header
1284
+ // without threading the id through here.
1277
1285
  core.bridgeTimeline.addEntry({
1278
1286
  ts: ccChatStart ?? Date.now(), type: 'chat_start',
1279
1287
  raw: snippet || fallbackRaw,
1280
1288
  ...(detail ? { detail } : {}),
1281
1289
  agentType: 'claude-code',
1290
+ startedAt: ccChatStart ?? Date.now(),
1282
1291
  });
1283
1292
  };
1284
1293
  /** Extract response text from PTY ringbuffer after ⏺ marker (same pattern as APME) */
@@ -1306,59 +1315,86 @@ function wireClaudeCodeTimeline(adapter, core, journal, ptyRingBuffer) {
1306
1315
  }
1307
1316
  return clean.join('\n').trim();
1308
1317
  };
1309
- /** Emit chat_response + chat_end from response text (shared by Stop hook and PTY fallback) */
1318
+ /**
1319
+ * Emit a single Claude turn-close row (shared by Stop hook and PTY fallback):
1320
+ * a `chat_response` when there is meaningful response text, otherwise a
1321
+ * `chat_end`. The two are mutually exclusive so a turn never produces both —
1322
+ * the paired dimmed `chat_end` used to fragment every turn into three rows on
1323
+ * the flat surfaces (plugin / TUI / timeline.json).
1324
+ */
1310
1325
  const emitCompletion = (responseText, duration, toolSummary) => {
1311
1326
  const now = Date.now();
1327
+ const startedAt = ccChatStart ?? undefined;
1312
1328
  // Change 5: emit chat_response if meaningful response text exists
1313
1329
  if (responseText.length > 20) {
1314
1330
  const respRaw = responseText.length > 200 ? responseText.slice(0, 197) + '...' : responseText;
1315
1331
  core.bridgeTimeline.addEntry({
1316
1332
  ts: now - 1, type: 'chat_response',
1317
1333
  raw: cleanRawText(respRaw),
1318
- detail: cleanDetailText(responseText.slice(0, 3000)) || undefined,
1334
+ // Preserve markdown so the dashboard renderer can apply heading /
1335
+ // table / bold / code styles. cleanDetailText would have stripped
1336
+ // all of that before it ever reached the client.
1337
+ detail: prepareMarkdownDetail(responseText.slice(0, 3000)) || undefined,
1319
1338
  agentType: 'claude-code',
1339
+ ...(startedAt ? { startedAt } : {}),
1340
+ endedAt: now,
1320
1341
  });
1321
1342
  }
1322
- const topicHint = responseText ? extractTopicHint(responseText) : null;
1323
- const promptTopic = ccLastPromptText ? extractTopicHint(ccLastPromptText) : null;
1324
- const completedLabel = topicHint || promptTopic || 'Completed';
1343
+ // Pick a summary label.
1344
+ // - 'topic' kind from response \u2192 real heuristic content
1345
+ // - 'topic' kind from prompt \u2192 still meaningful, mark as heuristic
1346
+ // - 'fallback' kind (politeness-only) or prompt snippet \u2192 mark as 'none'
1347
+ // (clients use this to decide whether the detail pane is worth showing)
1348
+ // - literal "Completed" only when prompt is also empty
1349
+ const respHint = responseText ? extractTopicHintWithKind(responseText) : { hint: null, kind: null };
1350
+ const promptHint = ccLastPromptText ? extractTopicHintWithKind(ccLastPromptText) : { hint: null, kind: null };
1351
+ let completedLabel;
1352
+ let summaryKind;
1353
+ if (respHint.kind === 'topic' && respHint.hint) {
1354
+ completedLabel = respHint.hint;
1355
+ summaryKind = 'heuristic';
1356
+ }
1357
+ else if (promptHint.kind === 'topic' && promptHint.hint) {
1358
+ completedLabel = promptHint.hint;
1359
+ summaryKind = 'heuristic';
1360
+ }
1361
+ else if (respHint.hint || promptHint.hint) {
1362
+ completedLabel = (respHint.hint || promptHint.hint);
1363
+ summaryKind = 'none';
1364
+ }
1365
+ else {
1366
+ const fallback = promptSnippetFallback(ccLastPromptText, 60);
1367
+ completedLabel = fallback ?? 'Completed';
1368
+ summaryKind = 'none';
1369
+ }
1325
1370
  let summary = duration != null ? `${completedLabel} \u00B7 ${duration}s` : completedLabel;
1326
1371
  if (toolSummary)
1327
1372
  summary += ` \u00B7 ${toolSummary}`;
1328
1373
  const chatEndDetail = responseText
1329
- ? (() => { const c = cleanDetailText(responseText); return c ? (c.length > 1000 ? c.slice(0, 1000) + '...' : c) : undefined; })()
1374
+ ? (() => { const c = prepareMarkdownDetail(responseText); return c ? (c.length > 1000 ? c.slice(0, 1000) + '...' : c) : undefined; })()
1330
1375
  : ccLastPromptText
1331
1376
  ? `Prompt: ${ccLastPromptText.length > 200 ? ccLastPromptText.slice(0, 200) + '...' : ccLastPromptText}`
1332
1377
  : undefined;
1333
1378
  const chatEndTs = now;
1334
1379
  ccChatStart = null;
1335
1380
  ccLastPromptText = null;
1336
- core.bridgeTimeline.addEntry({
1337
- ts: chatEndTs, type: 'chat_end', raw: summary,
1338
- ...(chatEndDetail ? { detail: chatEndDetail } : {}),
1339
- agentType: 'claude-code',
1340
- });
1341
- // Async LLM summarization fire-and-forget, upsert chat_end when ready
1342
- if (responseText && responseText.length > 30) {
1343
- const savedDuration = duration;
1344
- const savedToolSummary = toolSummary;
1345
- const savedDetail = chatEndDetail;
1346
- summarizeResponse(responseText).then((llmSummary) => {
1347
- if (llmSummary) {
1348
- const enrichedParts = [llmSummary];
1349
- if (savedDuration != null)
1350
- enrichedParts.push(`${savedDuration}s`);
1351
- if (savedToolSummary)
1352
- enrichedParts.push(savedToolSummary);
1353
- debug('timeline', `CC LLM summary: ${llmSummary}`);
1354
- core.bridgeTimeline.upsertEntry({
1355
- ts: chatEndTs, type: 'chat_end',
1356
- raw: enrichedParts.join(' \u00B7 '),
1357
- ...(savedDetail ? { detail: savedDetail } : {}),
1358
- agentType: 'claude-code',
1359
- });
1360
- }
1361
- }).catch(() => { });
1381
+ // Only emit a chat_end turn-close row when there is NO chat_response to act
1382
+ // as the turn's completion row (a tool-only turn, or a Stop hook with no
1383
+ // last_assistant_message). When a chat_response exists it already marks the
1384
+ // turn complete; the extra dimmed "Completed · Ns · topic" row is redundant
1385
+ // metadata — the dashboard already drops it on render — and on the flat
1386
+ // surfaces (Stream Deck plugin / TUI / persisted timeline.json) it
1387
+ // fragmented every Claude turn into three rows. Keeping chat_start +
1388
+ // chat_response matches the cleaner OpenClaw turn shape.
1389
+ if (responseText.length <= 20) {
1390
+ core.bridgeTimeline.addEntry({
1391
+ ts: chatEndTs, type: 'chat_end', raw: summary,
1392
+ ...(chatEndDetail ? { detail: chatEndDetail } : {}),
1393
+ agentType: 'claude-code',
1394
+ ...(startedAt ? { startedAt } : {}),
1395
+ endedAt: chatEndTs,
1396
+ summaryKind,
1397
+ });
1362
1398
  }
1363
1399
  };
1364
1400
  // Change 4: late upsert — user_prompt metadata arriving after 500ms timer