@agentdeck/bridge 0.2.2 → 1.0.0

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 (435) hide show
  1. package/assets/fm-helper/agentdeck-fm-helper +0 -0
  2. package/assets/fonts/IBMPlexSans-Bold.ttf +0 -0
  3. package/assets/fonts/IBMPlexSans-Regular.ttf +0 -0
  4. package/assets/fonts/JetBrainsMono-Bold.ttf +0 -0
  5. package/assets/fonts/JetBrainsMono-Regular.ttf +0 -0
  6. package/assets/fonts/LICENSES.md +22 -0
  7. package/dist/adapters/claude-code.d.ts +6 -0
  8. package/dist/adapters/claude-code.d.ts.map +1 -1
  9. package/dist/adapters/claude-code.js +8 -0
  10. package/dist/adapters/claude-code.js.map +1 -1
  11. package/dist/adapters/openclaw.d.ts +24 -0
  12. package/dist/adapters/openclaw.d.ts.map +1 -1
  13. package/dist/adapters/openclaw.js +291 -35
  14. package/dist/adapters/openclaw.js.map +1 -1
  15. package/dist/adapters/opencode-adapter.d.ts +59 -0
  16. package/dist/adapters/opencode-adapter.d.ts.map +1 -1
  17. package/dist/adapters/opencode-adapter.js +297 -35
  18. package/dist/adapters/opencode-adapter.js.map +1 -1
  19. package/dist/adb-reverse.d.ts +1 -1
  20. package/dist/adb-reverse.js +1 -1
  21. package/dist/antigravity-local.d.ts +1 -0
  22. package/dist/antigravity-local.d.ts.map +1 -1
  23. package/dist/antigravity-local.js +33 -0
  24. package/dist/antigravity-local.js.map +1 -1
  25. package/dist/apme/adapters/claude-hook.d.ts.map +1 -1
  26. package/dist/apme/adapters/claude-hook.js +5 -0
  27. package/dist/apme/adapters/claude-hook.js.map +1 -1
  28. package/dist/apme/adapters/codex-hook.d.ts +17 -0
  29. package/dist/apme/adapters/codex-hook.d.ts.map +1 -0
  30. package/dist/apme/adapters/codex-hook.js +95 -0
  31. package/dist/apme/adapters/codex-hook.js.map +1 -0
  32. package/dist/apme/adapters/codex-turn-manager.d.ts +86 -0
  33. package/dist/apme/adapters/codex-turn-manager.d.ts.map +1 -0
  34. package/dist/apme/adapters/codex-turn-manager.js +496 -0
  35. package/dist/apme/adapters/codex-turn-manager.js.map +1 -0
  36. package/dist/apme/adapters/openclaw-hook.d.ts +99 -0
  37. package/dist/apme/adapters/openclaw-hook.d.ts.map +1 -0
  38. package/dist/apme/adapters/openclaw-hook.js +234 -0
  39. package/dist/apme/adapters/openclaw-hook.js.map +1 -0
  40. package/dist/apme/adapters/opencode-hook.d.ts +56 -0
  41. package/dist/apme/adapters/opencode-hook.d.ts.map +1 -0
  42. package/dist/apme/adapters/opencode-hook.js +182 -0
  43. package/dist/apme/adapters/opencode-hook.js.map +1 -0
  44. package/dist/apme/adapters/timeline.d.ts.map +1 -1
  45. package/dist/apme/adapters/timeline.js +6 -1
  46. package/dist/apme/adapters/timeline.js.map +1 -1
  47. package/dist/apme/antigravity-transcript.d.ts +18 -0
  48. package/dist/apme/antigravity-transcript.d.ts.map +1 -0
  49. package/dist/apme/antigravity-transcript.js +94 -0
  50. package/dist/apme/antigravity-transcript.js.map +1 -0
  51. package/dist/apme/classify-turn.d.ts +13 -0
  52. package/dist/apme/classify-turn.d.ts.map +1 -0
  53. package/dist/apme/classify-turn.js +49 -0
  54. package/dist/apme/classify-turn.js.map +1 -0
  55. package/dist/apme/claude-transcript-reader.d.ts +10 -0
  56. package/dist/apme/claude-transcript-reader.d.ts.map +1 -1
  57. package/dist/apme/claude-transcript-reader.js +35 -0
  58. package/dist/apme/claude-transcript-reader.js.map +1 -1
  59. package/dist/apme/collector.d.ts +133 -6
  60. package/dist/apme/collector.d.ts.map +1 -1
  61. package/dist/apme/collector.js +471 -23
  62. package/dist/apme/collector.js.map +1 -1
  63. package/dist/apme/dashboard-html.d.ts.map +1 -1
  64. package/dist/apme/dashboard-html.js +117 -11
  65. package/dist/apme/dashboard-html.js.map +1 -1
  66. package/dist/apme/http.d.ts.map +1 -1
  67. package/dist/apme/http.js +52 -6
  68. package/dist/apme/http.js.map +1 -1
  69. package/dist/apme/index.d.ts +26 -5
  70. package/dist/apme/index.d.ts.map +1 -1
  71. package/dist/apme/index.js +146 -9
  72. package/dist/apme/index.js.map +1 -1
  73. package/dist/apme/judge-detect.d.ts +26 -0
  74. package/dist/apme/judge-detect.d.ts.map +1 -0
  75. package/dist/apme/judge-detect.js +69 -0
  76. package/dist/apme/judge-detect.js.map +1 -0
  77. package/dist/apme/pareto.d.ts +39 -0
  78. package/dist/apme/pareto.d.ts.map +1 -0
  79. package/dist/apme/pareto.js +59 -0
  80. package/dist/apme/pareto.js.map +1 -0
  81. package/dist/apme/recommend.d.ts +3 -0
  82. package/dist/apme/recommend.d.ts.map +1 -1
  83. package/dist/apme/recommend.js +43 -1
  84. package/dist/apme/recommend.js.map +1 -1
  85. package/dist/apme/runner.d.ts +99 -4
  86. package/dist/apme/runner.d.ts.map +1 -1
  87. package/dist/apme/runner.js +743 -72
  88. package/dist/apme/runner.js.map +1 -1
  89. package/dist/apme/sample-to-timeline.d.ts +26 -0
  90. package/dist/apme/sample-to-timeline.d.ts.map +1 -0
  91. package/dist/apme/sample-to-timeline.js +95 -0
  92. package/dist/apme/sample-to-timeline.js.map +1 -0
  93. package/dist/apme/scorers/index.d.ts +47 -0
  94. package/dist/apme/scorers/index.d.ts.map +1 -0
  95. package/dist/apme/scorers/index.js +96 -0
  96. package/dist/apme/scorers/index.js.map +1 -0
  97. package/dist/apme/settings.d.ts +17 -11
  98. package/dist/apme/settings.d.ts.map +1 -1
  99. package/dist/apme/settings.js +30 -9
  100. package/dist/apme/settings.js.map +1 -1
  101. package/dist/apme/store.d.ts +33 -1
  102. package/dist/apme/store.d.ts.map +1 -1
  103. package/dist/apme/store.js +334 -25
  104. package/dist/apme/store.js.map +1 -1
  105. package/dist/awaiting-overlay.d.ts +112 -0
  106. package/dist/awaiting-overlay.d.ts.map +1 -0
  107. package/dist/awaiting-overlay.js +179 -0
  108. package/dist/awaiting-overlay.js.map +1 -0
  109. package/dist/ble-sync-spawn.d.ts +63 -0
  110. package/dist/ble-sync-spawn.d.ts.map +1 -0
  111. package/dist/ble-sync-spawn.js +196 -0
  112. package/dist/ble-sync-spawn.js.map +1 -0
  113. package/dist/bridge-core.d.ts +34 -1
  114. package/dist/bridge-core.d.ts.map +1 -1
  115. package/dist/bridge-core.js +207 -24
  116. package/dist/bridge-core.js.map +1 -1
  117. package/dist/broadcast.d.ts +3 -0
  118. package/dist/broadcast.d.ts.map +1 -0
  119. package/dist/broadcast.js +122 -0
  120. package/dist/broadcast.js.map +1 -0
  121. package/dist/check-deps.d.ts.map +1 -1
  122. package/dist/check-deps.js +25 -9
  123. package/dist/check-deps.js.map +1 -1
  124. package/dist/claude-permission-rules.d.ts +55 -0
  125. package/dist/claude-permission-rules.d.ts.map +1 -0
  126. package/dist/claude-permission-rules.js +197 -0
  127. package/dist/claude-permission-rules.js.map +1 -0
  128. package/dist/cli.d.ts +15 -1
  129. package/dist/cli.d.ts.map +1 -1
  130. package/dist/cli.js +1030 -93
  131. package/dist/cli.js.map +1 -1
  132. package/dist/codex-auth.d.ts +21 -0
  133. package/dist/codex-auth.d.ts.map +1 -1
  134. package/dist/codex-auth.js +52 -1
  135. package/dist/codex-auth.js.map +1 -1
  136. package/dist/codex-output-parser.d.ts +8 -0
  137. package/dist/codex-output-parser.d.ts.map +1 -1
  138. package/dist/codex-output-parser.js +110 -25
  139. package/dist/codex-output-parser.js.map +1 -1
  140. package/dist/codex-rate-limits.d.ts +16 -0
  141. package/dist/codex-rate-limits.d.ts.map +1 -0
  142. package/dist/codex-rate-limits.js +197 -0
  143. package/dist/codex-rate-limits.js.map +1 -0
  144. package/dist/codex-rollout-response.d.ts +31 -0
  145. package/dist/codex-rollout-response.d.ts.map +1 -0
  146. package/dist/codex-rollout-response.js +135 -0
  147. package/dist/codex-rollout-response.js.map +1 -0
  148. package/dist/d200h/image-renderer.d.ts +4 -16
  149. package/dist/d200h/image-renderer.d.ts.map +1 -1
  150. package/dist/d200h/image-renderer.js +46 -212
  151. package/dist/d200h/image-renderer.js.map +1 -1
  152. package/dist/daemon-server.d.ts +85 -0
  153. package/dist/daemon-server.d.ts.map +1 -1
  154. package/dist/daemon-server.js +1975 -159
  155. package/dist/daemon-server.js.map +1 -1
  156. package/dist/daemon.js +15 -6
  157. package/dist/daemon.js.map +1 -1
  158. package/dist/display-dim.d.ts +9 -0
  159. package/dist/display-dim.d.ts.map +1 -0
  160. package/dist/display-dim.js +53 -0
  161. package/dist/display-dim.js.map +1 -0
  162. package/dist/display-monitor.d.ts +15 -2
  163. package/dist/display-monitor.d.ts.map +1 -1
  164. package/dist/display-monitor.js +104 -26
  165. package/dist/display-monitor.js.map +1 -1
  166. package/dist/esp32-serial.d.ts +190 -1
  167. package/dist/esp32-serial.d.ts.map +1 -1
  168. package/dist/esp32-serial.js +1090 -156
  169. package/dist/esp32-serial.js.map +1 -1
  170. package/dist/fallback-task-timeline.d.ts +42 -0
  171. package/dist/fallback-task-timeline.d.ts.map +1 -0
  172. package/dist/fallback-task-timeline.js +119 -0
  173. package/dist/fallback-task-timeline.js.map +1 -0
  174. package/dist/foundation-models-helper.d.ts +11 -0
  175. package/dist/foundation-models-helper.d.ts.map +1 -0
  176. package/dist/foundation-models-helper.js +241 -0
  177. package/dist/foundation-models-helper.js.map +1 -0
  178. package/dist/hook-migration.js +1 -1
  179. package/dist/hook-migration.js.map +1 -1
  180. package/dist/hook-server.d.ts +2 -3
  181. package/dist/hook-server.d.ts.map +1 -1
  182. package/dist/hook-server.js +23 -10
  183. package/dist/hook-server.js.map +1 -1
  184. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts +33 -0
  185. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -0
  186. package/dist/idotmatrix/idotmatrix-daemon-sync.js +164 -0
  187. package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -0
  188. package/dist/idotmatrix/idotmatrix-discover.d.ts +18 -0
  189. package/dist/idotmatrix/idotmatrix-discover.d.ts.map +1 -0
  190. package/dist/idotmatrix/idotmatrix-discover.js +85 -0
  191. package/dist/idotmatrix/idotmatrix-discover.js.map +1 -0
  192. package/dist/idotmatrix/idotmatrix-settings.d.ts +20 -0
  193. package/dist/idotmatrix/idotmatrix-settings.d.ts.map +1 -0
  194. package/dist/idotmatrix/idotmatrix-settings.js +57 -0
  195. package/dist/idotmatrix/idotmatrix-settings.js.map +1 -0
  196. package/dist/index.d.ts.map +1 -1
  197. package/dist/index.js +217 -179
  198. package/dist/index.js.map +1 -1
  199. package/dist/log-stream.d.ts +15 -16
  200. package/dist/log-stream.d.ts.map +1 -1
  201. package/dist/log-stream.js +18 -96
  202. package/dist/log-stream.js.map +1 -1
  203. package/dist/logger.d.ts +3 -1
  204. package/dist/logger.d.ts.map +1 -1
  205. package/dist/logger.js +18 -5
  206. package/dist/logger.js.map +1 -1
  207. package/dist/mdns.d.ts +17 -0
  208. package/dist/mdns.d.ts.map +1 -1
  209. package/dist/mdns.js +52 -18
  210. package/dist/mdns.js.map +1 -1
  211. package/dist/model-catalog.d.ts +1 -1
  212. package/dist/model-catalog.d.ts.map +1 -1
  213. package/dist/model-catalog.js +2 -4
  214. package/dist/model-catalog.js.map +1 -1
  215. package/dist/modules/adb-module.d.ts +1 -1
  216. package/dist/modules/adb-module.d.ts.map +1 -1
  217. package/dist/modules/adb-module.js +4 -3
  218. package/dist/modules/adb-module.js.map +1 -1
  219. package/dist/modules/broadcast-module.d.ts +17 -0
  220. package/dist/modules/broadcast-module.d.ts.map +1 -0
  221. package/dist/modules/broadcast-module.js +26 -0
  222. package/dist/modules/broadcast-module.js.map +1 -0
  223. package/dist/modules/d200h-module.d.ts +22 -0
  224. package/dist/modules/d200h-module.d.ts.map +1 -1
  225. package/dist/modules/d200h-module.js +200 -65
  226. package/dist/modules/d200h-module.js.map +1 -1
  227. package/dist/modules/idotmatrix-module.d.ts +9 -0
  228. package/dist/modules/idotmatrix-module.d.ts.map +1 -0
  229. package/dist/modules/idotmatrix-module.js +46 -0
  230. package/dist/modules/idotmatrix-module.js.map +1 -0
  231. package/dist/modules/index.d.ts +3 -1
  232. package/dist/modules/index.d.ts.map +1 -1
  233. package/dist/modules/index.js +9 -3
  234. package/dist/modules/index.js.map +1 -1
  235. package/dist/modules/pixoo-module.d.ts.map +1 -1
  236. package/dist/modules/pixoo-module.js +14 -4
  237. package/dist/modules/pixoo-module.js.map +1 -1
  238. package/dist/modules/serial-module.d.ts +4 -0
  239. package/dist/modules/serial-module.d.ts.map +1 -1
  240. package/dist/modules/serial-module.js +14 -12
  241. package/dist/modules/serial-module.js.map +1 -1
  242. package/dist/modules/timebox-module.d.ts +9 -0
  243. package/dist/modules/timebox-module.d.ts.map +1 -0
  244. package/dist/modules/timebox-module.js +46 -0
  245. package/dist/modules/timebox-module.js.map +1 -0
  246. package/dist/modules/trmnl-module.d.ts +38 -0
  247. package/dist/modules/trmnl-module.d.ts.map +1 -0
  248. package/dist/modules/trmnl-module.js +111 -0
  249. package/dist/modules/trmnl-module.js.map +1 -0
  250. package/dist/modules/types.d.ts +4 -2
  251. package/dist/modules/types.d.ts.map +1 -1
  252. package/dist/observed-steering.d.ts +93 -0
  253. package/dist/observed-steering.d.ts.map +1 -0
  254. package/dist/observed-steering.js +247 -0
  255. package/dist/observed-steering.js.map +1 -0
  256. package/dist/openclaw-session.d.ts +18 -0
  257. package/dist/openclaw-session.d.ts.map +1 -0
  258. package/dist/openclaw-session.js +34 -0
  259. package/dist/openclaw-session.js.map +1 -0
  260. package/dist/opencode-steering.d.ts +36 -0
  261. package/dist/opencode-steering.d.ts.map +1 -0
  262. package/dist/opencode-steering.js +84 -0
  263. package/dist/opencode-steering.js.map +1 -0
  264. package/dist/output-parser.d.ts +30 -0
  265. package/dist/output-parser.d.ts.map +1 -1
  266. package/dist/output-parser.js +172 -18
  267. package/dist/output-parser.js.map +1 -1
  268. package/dist/passive-observer.d.ts +32 -0
  269. package/dist/passive-observer.d.ts.map +1 -1
  270. package/dist/passive-observer.js +288 -49
  271. package/dist/passive-observer.js.map +1 -1
  272. package/dist/permission-resolver.d.ts +55 -0
  273. package/dist/permission-resolver.d.ts.map +1 -0
  274. package/dist/permission-resolver.js +112 -0
  275. package/dist/permission-resolver.js.map +1 -0
  276. package/dist/pixoo/micro-glyphs.d.ts +18 -0
  277. package/dist/pixoo/micro-glyphs.d.ts.map +1 -0
  278. package/dist/pixoo/micro-glyphs.js +143 -0
  279. package/dist/pixoo/micro-glyphs.js.map +1 -0
  280. package/dist/pixoo/official-dot-glyphs.generated.d.ts +8 -0
  281. package/dist/pixoo/official-dot-glyphs.generated.d.ts.map +1 -0
  282. package/dist/pixoo/official-dot-glyphs.generated.js +247 -0
  283. package/dist/pixoo/official-dot-glyphs.generated.js.map +1 -0
  284. package/dist/pixoo/pixoo-bridge.d.ts +16 -8
  285. package/dist/pixoo/pixoo-bridge.d.ts.map +1 -1
  286. package/dist/pixoo/pixoo-bridge.js +127 -35
  287. package/dist/pixoo/pixoo-bridge.js.map +1 -1
  288. package/dist/pixoo/pixoo-camera.d.ts +19 -3
  289. package/dist/pixoo/pixoo-camera.d.ts.map +1 -1
  290. package/dist/pixoo/pixoo-camera.js +62 -27
  291. package/dist/pixoo/pixoo-camera.js.map +1 -1
  292. package/dist/pixoo/pixoo-client.d.ts +13 -3
  293. package/dist/pixoo/pixoo-client.d.ts.map +1 -1
  294. package/dist/pixoo/pixoo-client.js +51 -22
  295. package/dist/pixoo/pixoo-client.js.map +1 -1
  296. package/dist/pixoo/pixoo-discover.d.ts +37 -0
  297. package/dist/pixoo/pixoo-discover.d.ts.map +1 -0
  298. package/dist/pixoo/pixoo-discover.js +105 -0
  299. package/dist/pixoo/pixoo-discover.js.map +1 -0
  300. package/dist/pixoo/pixoo-renderer.d.ts +27 -3
  301. package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
  302. package/dist/pixoo/pixoo-renderer.js +533 -135
  303. package/dist/pixoo/pixoo-renderer.js.map +1 -1
  304. package/dist/pixoo/pixoo-settings.d.ts +6 -0
  305. package/dist/pixoo/pixoo-settings.d.ts.map +1 -1
  306. package/dist/pixoo/pixoo-settings.js +9 -0
  307. package/dist/pixoo/pixoo-settings.js.map +1 -1
  308. package/dist/pixoo/pixoo-sprites.d.ts +84 -11
  309. package/dist/pixoo/pixoo-sprites.d.ts.map +1 -1
  310. package/dist/pixoo/pixoo-sprites.js +620 -209
  311. package/dist/pixoo/pixoo-sprites.js.map +1 -1
  312. package/dist/pty-manager.d.ts.map +1 -1
  313. package/dist/pty-manager.js +8 -2
  314. package/dist/pty-manager.js.map +1 -1
  315. package/dist/review-runner.d.ts +113 -0
  316. package/dist/review-runner.d.ts.map +1 -0
  317. package/dist/review-runner.js +428 -0
  318. package/dist/review-runner.js.map +1 -0
  319. package/dist/session-activity.d.ts +14 -0
  320. package/dist/session-activity.d.ts.map +1 -0
  321. package/dist/session-activity.js +160 -0
  322. package/dist/session-activity.js.map +1 -0
  323. package/dist/session-aggregator.d.ts +25 -0
  324. package/dist/session-aggregator.d.ts.map +1 -1
  325. package/dist/session-aggregator.js +48 -9
  326. package/dist/session-aggregator.js.map +1 -1
  327. package/dist/session-focus-relay.d.ts.map +1 -1
  328. package/dist/session-focus-relay.js +7 -1
  329. package/dist/session-focus-relay.js.map +1 -1
  330. package/dist/session-registry.d.ts +74 -3
  331. package/dist/session-registry.d.ts.map +1 -1
  332. package/dist/session-registry.js +140 -8
  333. package/dist/session-registry.js.map +1 -1
  334. package/dist/session-timeline-relay.d.ts.map +1 -1
  335. package/dist/session-timeline-relay.js +13 -4
  336. package/dist/session-timeline-relay.js.map +1 -1
  337. package/dist/session-transcript-timeline.d.ts +49 -0
  338. package/dist/session-transcript-timeline.d.ts.map +1 -0
  339. package/dist/session-transcript-timeline.js +276 -0
  340. package/dist/session-transcript-timeline.js.map +1 -0
  341. package/dist/state-machine.d.ts +6 -1
  342. package/dist/state-machine.d.ts.map +1 -1
  343. package/dist/state-machine.js +86 -28
  344. package/dist/state-machine.js.map +1 -1
  345. package/dist/timebox/timebox-daemon-sync.d.ts +19 -0
  346. package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -0
  347. package/dist/timebox/timebox-daemon-sync.js +135 -0
  348. package/dist/timebox/timebox-daemon-sync.js.map +1 -0
  349. package/dist/timebox/timebox-discover.d.ts +19 -0
  350. package/dist/timebox/timebox-discover.d.ts.map +1 -0
  351. package/dist/timebox/timebox-discover.js +87 -0
  352. package/dist/timebox/timebox-discover.js.map +1 -0
  353. package/dist/timebox/timebox-settings.d.ts +32 -0
  354. package/dist/timebox/timebox-settings.d.ts.map +1 -0
  355. package/dist/timebox/timebox-settings.js +80 -0
  356. package/dist/timebox/timebox-settings.js.map +1 -0
  357. package/dist/timeline-store.d.ts +112 -3
  358. package/dist/timeline-store.d.ts.map +1 -1
  359. package/dist/timeline-store.js +460 -14
  360. package/dist/timeline-store.js.map +1 -1
  361. package/dist/timeline-summarizer.d.ts.map +1 -1
  362. package/dist/timeline-summarizer.js +29 -5
  363. package/dist/timeline-summarizer.js.map +1 -1
  364. package/dist/trmnl/byos-server.d.ts +39 -0
  365. package/dist/trmnl/byos-server.d.ts.map +1 -0
  366. package/dist/trmnl/byos-server.js +301 -0
  367. package/dist/trmnl/byos-server.js.map +1 -0
  368. package/dist/trmnl/frame-cache.d.ts +37 -0
  369. package/dist/trmnl/frame-cache.d.ts.map +1 -0
  370. package/dist/trmnl/frame-cache.js +157 -0
  371. package/dist/trmnl/frame-cache.js.map +1 -0
  372. package/dist/trmnl/image-renderer.d.ts +27 -0
  373. package/dist/trmnl/image-renderer.d.ts.map +1 -0
  374. package/dist/trmnl/image-renderer.js +235 -0
  375. package/dist/trmnl/image-renderer.js.map +1 -0
  376. package/dist/trmnl/trmnl-settings.d.ts +96 -0
  377. package/dist/trmnl/trmnl-settings.d.ts.map +1 -0
  378. package/dist/trmnl/trmnl-settings.js +177 -0
  379. package/dist/trmnl/trmnl-settings.js.map +1 -0
  380. package/dist/trmnl/trmnl-telemetry.d.ts +47 -0
  381. package/dist/trmnl/trmnl-telemetry.d.ts.map +1 -0
  382. package/dist/trmnl/trmnl-telemetry.js +56 -0
  383. package/dist/trmnl/trmnl-telemetry.js.map +1 -0
  384. package/dist/tui/ansi.d.ts +0 -4
  385. package/dist/tui/ansi.d.ts.map +1 -1
  386. package/dist/tui/ansi.js +14 -7
  387. package/dist/tui/ansi.js.map +1 -1
  388. package/dist/tui/dashboard.d.ts +1 -0
  389. package/dist/tui/dashboard.d.ts.map +1 -1
  390. package/dist/tui/dashboard.js +38 -11
  391. package/dist/tui/dashboard.js.map +1 -1
  392. package/dist/tui/renderer.d.ts +36 -0
  393. package/dist/tui/renderer.d.ts.map +1 -1
  394. package/dist/tui/renderer.js +355 -121
  395. package/dist/tui/renderer.js.map +1 -1
  396. package/dist/tui/terrarium.d.ts.map +1 -1
  397. package/dist/tui/terrarium.js +44 -51
  398. package/dist/tui/terrarium.js.map +1 -1
  399. package/dist/types.d.ts +2 -2
  400. package/dist/types.d.ts.map +1 -1
  401. package/dist/types.js +2 -2
  402. package/dist/types.js.map +1 -1
  403. package/dist/usage-api.d.ts.map +1 -1
  404. package/dist/usage-api.js +7 -1
  405. package/dist/usage-api.js.map +1 -1
  406. package/dist/usage-event.d.ts +10 -3
  407. package/dist/usage-event.d.ts.map +1 -1
  408. package/dist/usage-event.js +98 -13
  409. package/dist/usage-event.js.map +1 -1
  410. package/dist/utility-proxy.d.ts.map +1 -1
  411. package/dist/utility-proxy.js +8 -0
  412. package/dist/utility-proxy.js.map +1 -1
  413. package/dist/utils/project-name.d.ts +3 -0
  414. package/dist/utils/project-name.d.ts.map +1 -1
  415. package/dist/utils/project-name.js +44 -2
  416. package/dist/utils/project-name.js.map +1 -1
  417. package/dist/version-check.d.ts.map +1 -1
  418. package/dist/version-check.js +1 -17
  419. package/dist/version-check.js.map +1 -1
  420. package/dist/windows-service.d.ts +26 -0
  421. package/dist/windows-service.d.ts.map +1 -0
  422. package/dist/windows-service.js +145 -0
  423. package/dist/windows-service.js.map +1 -0
  424. package/dist/ws-server.d.ts +17 -3
  425. package/dist/ws-server.d.ts.map +1 -1
  426. package/dist/ws-server.js +78 -8
  427. package/dist/ws-server.js.map +1 -1
  428. package/fm-helper/AgentDeckFMHelper.swift +112 -0
  429. package/package.json +10 -4
  430. package/scripts/build-fm-helper.mjs +26 -0
  431. package/LICENSE +0 -21
  432. package/dist/apme/tuner.d.ts +0 -64
  433. package/dist/apme/tuner.d.ts.map +0 -1
  434. package/dist/apme/tuner.js +0 -297
  435. package/dist/apme/tuner.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}`);
@@ -653,6 +702,7 @@ export async function startSession(opts) {
653
702
  }
654
703
  core.broadcast({
655
704
  type: 'prompt_options',
705
+ sessionId: core.sessionId,
656
706
  promptType,
657
707
  question: snapshot.question ?? undefined,
658
708
  options: snapshot.options,
@@ -752,11 +802,17 @@ export async function startSession(opts) {
752
802
  });
753
803
  // Deck slot map relay
754
804
  core.wsServer.onRawMessage((msg, sender) => {
805
+ if (msg.type === 'device_info') {
806
+ core.wsServer.markEsp32Client(sender);
807
+ return false;
808
+ }
755
809
  if (msg.type === 'deck_slot_map') {
756
810
  cachedSlotMap = msg;
757
811
  core.wsServer.broadcastExcept(cachedSlotMap, sender);
758
812
  broadcastSse(cachedSlotMap);
759
813
  core.broadcast(computeButtonState());
814
+ // Re-broadcast sessions_list so slot buttons populate after plugin startup race
815
+ core.broadcastSessionsList().catch(() => { });
760
816
  return true;
761
817
  }
762
818
  return false;
@@ -809,6 +865,7 @@ export async function startSession(opts) {
809
865
  }
810
866
  extraEvents.push({
811
867
  type: 'prompt_options',
868
+ sessionId: core.sessionId,
812
869
  promptType: initPromptType,
813
870
  question: snapshot.question ?? undefined,
814
871
  options: snapshot.options,
@@ -843,20 +900,12 @@ export async function startSession(opts) {
843
900
  core.startGatewayProbe(800);
844
901
  core.startGatewayHealthCheck();
845
902
  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
- });
903
+ // Inject virtual OpenClaw session only after Gateway authentication succeeds
904
+ // (gatewayConnected). Reachability alone (cachedGatewayAvailable) is a
905
+ // topology signal, not proof commands can route — gating on it kept a phantom
906
+ // OpenClaw session alive whenever anything held port 18789 open. Shared
907
+ // injector with daemon-server.ts; mirror of Swift buildSessionsListEvent.
908
+ core.setSessionsEnricher((sessions) => injectOpenClawSession(sessions, { gatewayConnected: core.cachedGatewayConnected }));
860
909
  // ===== Encoder state computation =====
861
910
  function computeEncoderState() {
862
911
  const snapshot = core.stateMachine.getSnapshot();
@@ -1133,110 +1182,60 @@ export async function startSession(opts) {
1133
1182
  core.shutdown();
1134
1183
  }
1135
1184
  }
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
1185
  // ===== Non-Claude agent APME wiring =====
1177
1186
  // For OpenCode, Codex, OpenClaw: intercepts adapter timeline events to create
1178
1187
  // APME turns with prompts and responses. Claude Code uses hooks instead.
1179
1188
  function wireAgentApme(adapter, agentType, apme, core, ptyRingBuffer) {
1180
1189
  const sid = core.sessionId;
1190
+ // Codex turn boundaries (hook-primary, PTY-fallback) live in their own
1191
+ // module. The class encapsulates the seven-state-variable PTY-only
1192
+ // machine plus the hook-fresh shortcut so non-codex sessions allocate
1193
+ // nothing here.
1194
+ const codexManager = agentType === 'codex-cli'
1195
+ ? new CodexTurnManager(core, apme, ptyRingBuffer, sid, agentType)
1196
+ : null;
1181
1197
  adapter.on('event', (evt) => {
1182
1198
  // ── Timeline events (OpenCode / OpenClaw) ──
1183
1199
  // Both adapters emit { source: 'timeline', entry: TimelineEntry }.
1184
1200
  // The timeline adapter normalizes them to AgentDeck telemetry spans.
1185
1201
  if (evt.source === 'timeline' && evt.entry) {
1186
1202
  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);
1203
+ // Upserts refine an already-ingested row (chat_start topic hint, async
1204
+ // LLM summary) converting them to spans again would open a phantom
1205
+ // turn or overwrite the captured response with a summary label.
1206
+ if (evt.upsert)
1207
+ return;
1208
+ // Adapters bound to APME directly (OpenCode via setApmeSession) ingest
1209
+ // richer spans from their native event stream; converting their
1210
+ // timeline rows as well double-counted every turn and tool (the tool
1211
+ // sample dedupCore is counter-based, so storage-time dedup can't
1212
+ // collapse the second copy). Timeline conversion is the fallback for
1213
+ // sessions without a direct binding.
1214
+ const directApme = adapter
1215
+ .hasDirectApmeIngestion?.() === true;
1216
+ if (!directApme) {
1217
+ const ctx = makeApmeAdapterCtx(apme, sid, agentType);
1218
+ for (const span of timelineEntryToSpans(ctx, entry)) {
1219
+ apme.collector.ingestSpan(sid, span);
1220
+ }
1190
1221
  }
1191
- // Mid-session classification still runs once we've captured a response.
1222
+ // Mid-session classification still runs once we've captured a response
1223
+ // (both ingestion paths write the turn response before this fires).
1192
1224
  if (entry.type === 'chat_response') {
1193
1225
  void classifyAndEnqueueTurn(apme, sid);
1194
1226
  }
1195
1227
  }
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
- }
1228
+ // ── Codex: hook (primary) + PTY-parser fallback ──
1229
+ if (codexManager) {
1230
+ if (evt.source === 'hook')
1231
+ codexManager.onHookEvent(evt);
1232
+ else if (evt.source === 'parser')
1233
+ codexManager.onParserEvent(evt);
1238
1234
  }
1239
1235
  });
1236
+ if (codexManager) {
1237
+ core.onShutdown(() => codexManager.cleanup());
1238
+ }
1240
1239
  }
1241
1240
  /** Build the AdapterContext used by every APME ingestion adapter. The
1242
1241
  * trace id mirrors the active run id so future OTLP exports tie spans
@@ -1251,6 +1250,9 @@ function makeApmeAdapterCtx(apme, sid, agentType) {
1251
1250
  activeTurnId: apme.collector.getActiveTurnId(sid) ?? undefined,
1252
1251
  };
1253
1252
  }
1253
+ function isClaudeTaskNotificationPrompt(text) {
1254
+ return text.trim().toLowerCase().startsWith('<task-notification>');
1255
+ }
1254
1256
  // ===== Claude Code timeline wiring =====
1255
1257
  function wireClaudeCodeTimeline(adapter, core, journal, ptyRingBuffer) {
1256
1258
  let ccChatStart = null;
@@ -1266,6 +1268,10 @@ function wireClaudeCodeTimeline(adapter, core, journal, ptyRingBuffer) {
1266
1268
  ccPendingChatStartTimer = null;
1267
1269
  }
1268
1270
  ccPendingChatStart = false;
1271
+ if (isClaudeTaskNotificationPrompt(text)) {
1272
+ ccLastPromptText = null;
1273
+ return;
1274
+ }
1269
1275
  ccLastPromptText = text || null;
1270
1276
  const snippet = text.length > 500 ? text.slice(0, 497) + '...' : text;
1271
1277
  const detail = text.length > 100 ? (text.length > 1000 ? text.slice(0, 1000) + '...' : text) : undefined;
@@ -1274,11 +1280,16 @@ function wireClaudeCodeTimeline(adapter, core, journal, ptyRingBuffer) {
1274
1280
  const proj = core.stateMachine.getSnapshot().projectName;
1275
1281
  return proj ? `Prompt \u00B7 ${proj}` : 'Prompt sent';
1276
1282
  })();
1283
+ // taskId/runId are filled in by BridgeCore's timeline attributor
1284
+ // (wireTimeline → setAttributor) from the active APME task for this
1285
+ // session, so managed-session chat rows nest under their task header
1286
+ // without threading the id through here.
1277
1287
  core.bridgeTimeline.addEntry({
1278
1288
  ts: ccChatStart ?? Date.now(), type: 'chat_start',
1279
1289
  raw: snippet || fallbackRaw,
1280
1290
  ...(detail ? { detail } : {}),
1281
1291
  agentType: 'claude-code',
1292
+ startedAt: ccChatStart ?? Date.now(),
1282
1293
  });
1283
1294
  };
1284
1295
  /** Extract response text from PTY ringbuffer after ⏺ marker (same pattern as APME) */
@@ -1306,59 +1317,86 @@ function wireClaudeCodeTimeline(adapter, core, journal, ptyRingBuffer) {
1306
1317
  }
1307
1318
  return clean.join('\n').trim();
1308
1319
  };
1309
- /** Emit chat_response + chat_end from response text (shared by Stop hook and PTY fallback) */
1320
+ /**
1321
+ * Emit a single Claude turn-close row (shared by Stop hook and PTY fallback):
1322
+ * a `chat_response` when there is meaningful response text, otherwise a
1323
+ * `chat_end`. The two are mutually exclusive so a turn never produces both —
1324
+ * the paired dimmed `chat_end` used to fragment every turn into three rows on
1325
+ * the flat surfaces (plugin / TUI / timeline.json).
1326
+ */
1310
1327
  const emitCompletion = (responseText, duration, toolSummary) => {
1311
1328
  const now = Date.now();
1329
+ const startedAt = ccChatStart ?? undefined;
1312
1330
  // Change 5: emit chat_response if meaningful response text exists
1313
1331
  if (responseText.length > 20) {
1314
1332
  const respRaw = responseText.length > 200 ? responseText.slice(0, 197) + '...' : responseText;
1315
1333
  core.bridgeTimeline.addEntry({
1316
1334
  ts: now - 1, type: 'chat_response',
1317
1335
  raw: cleanRawText(respRaw),
1318
- detail: cleanDetailText(responseText.slice(0, 3000)) || undefined,
1336
+ // Preserve markdown so the dashboard renderer can apply heading /
1337
+ // table / bold / code styles. cleanDetailText would have stripped
1338
+ // all of that before it ever reached the client.
1339
+ detail: prepareMarkdownDetail(responseText.slice(0, 3000)) || undefined,
1319
1340
  agentType: 'claude-code',
1341
+ ...(startedAt ? { startedAt } : {}),
1342
+ endedAt: now,
1320
1343
  });
1321
1344
  }
1322
- const topicHint = responseText ? extractTopicHint(responseText) : null;
1323
- const promptTopic = ccLastPromptText ? extractTopicHint(ccLastPromptText) : null;
1324
- const completedLabel = topicHint || promptTopic || 'Completed';
1345
+ // Pick a summary label.
1346
+ // - 'topic' kind from response \u2192 real heuristic content
1347
+ // - 'topic' kind from prompt \u2192 still meaningful, mark as heuristic
1348
+ // - 'fallback' kind (politeness-only) or prompt snippet \u2192 mark as 'none'
1349
+ // (clients use this to decide whether the detail pane is worth showing)
1350
+ // - literal "Completed" only when prompt is also empty
1351
+ const respHint = responseText ? extractTopicHintWithKind(responseText) : { hint: null, kind: null };
1352
+ const promptHint = ccLastPromptText ? extractTopicHintWithKind(ccLastPromptText) : { hint: null, kind: null };
1353
+ let completedLabel;
1354
+ let summaryKind;
1355
+ if (respHint.kind === 'topic' && respHint.hint) {
1356
+ completedLabel = respHint.hint;
1357
+ summaryKind = 'heuristic';
1358
+ }
1359
+ else if (promptHint.kind === 'topic' && promptHint.hint) {
1360
+ completedLabel = promptHint.hint;
1361
+ summaryKind = 'heuristic';
1362
+ }
1363
+ else if (respHint.hint || promptHint.hint) {
1364
+ completedLabel = (respHint.hint || promptHint.hint);
1365
+ summaryKind = 'none';
1366
+ }
1367
+ else {
1368
+ const fallback = promptSnippetFallback(ccLastPromptText, 60);
1369
+ completedLabel = fallback ?? 'Completed';
1370
+ summaryKind = 'none';
1371
+ }
1325
1372
  let summary = duration != null ? `${completedLabel} \u00B7 ${duration}s` : completedLabel;
1326
1373
  if (toolSummary)
1327
1374
  summary += ` \u00B7 ${toolSummary}`;
1328
1375
  const chatEndDetail = responseText
1329
- ? (() => { const c = cleanDetailText(responseText); return c ? (c.length > 1000 ? c.slice(0, 1000) + '...' : c) : undefined; })()
1376
+ ? (() => { const c = prepareMarkdownDetail(responseText); return c ? (c.length > 1000 ? c.slice(0, 1000) + '...' : c) : undefined; })()
1330
1377
  : ccLastPromptText
1331
1378
  ? `Prompt: ${ccLastPromptText.length > 200 ? ccLastPromptText.slice(0, 200) + '...' : ccLastPromptText}`
1332
1379
  : undefined;
1333
1380
  const chatEndTs = now;
1334
1381
  ccChatStart = null;
1335
1382
  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(() => { });
1383
+ // Only emit a chat_end turn-close row when there is NO chat_response to act
1384
+ // as the turn's completion row (a tool-only turn, or a Stop hook with no
1385
+ // last_assistant_message). When a chat_response exists it already marks the
1386
+ // turn complete; the extra dimmed "Completed · Ns · topic" row is redundant
1387
+ // metadata — the dashboard already drops it on render — and on the flat
1388
+ // surfaces (Stream Deck plugin / TUI / persisted timeline.json) it
1389
+ // fragmented every Claude turn into three rows. Keeping chat_start +
1390
+ // chat_response matches the cleaner OpenClaw turn shape.
1391
+ if (responseText.length <= 20) {
1392
+ core.bridgeTimeline.addEntry({
1393
+ ts: chatEndTs, type: 'chat_end', raw: summary,
1394
+ ...(chatEndDetail ? { detail: chatEndDetail } : {}),
1395
+ agentType: 'claude-code',
1396
+ ...(startedAt ? { startedAt } : {}),
1397
+ endedAt: chatEndTs,
1398
+ summaryKind,
1399
+ });
1362
1400
  }
1363
1401
  };
1364
1402
  // Change 4: late upsert — user_prompt metadata arriving after 500ms timer