@polderlabs/bizar 6.2.4 → 6.3.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 (332) hide show
  1. package/.claude/CLAUDE.md +15 -0
  2. package/.claude/agents/_shared/AGENT_BASELINE.md +168 -0
  3. package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
  4. package/.claude/agents/_shared/SKILLS.md +109 -0
  5. package/.claude/agents/agent-browser.md +80 -0
  6. package/.claude/agents/baldr.md +49 -0
  7. package/.claude/agents/forseti.md +51 -0
  8. package/.claude/agents/frigg.md +42 -0
  9. package/.claude/agents/heimdall.md +33 -0
  10. package/.claude/agents/hermod.md +53 -0
  11. package/.claude/agents/mimir.md +49 -0
  12. package/.claude/agents/odin.md +287 -0
  13. package/.claude/agents/quick.md +34 -0
  14. package/.claude/agents/semble-search.md +50 -0
  15. package/.claude/agents/thor.md +53 -0
  16. package/.claude/agents/tyr.md +56 -0
  17. package/.claude/agents/vidarr.md +54 -0
  18. package/.claude/agents/vor.md +53 -0
  19. package/.claude/commands/audit.md +25 -0
  20. package/.claude/commands/bizar.md +22 -0
  21. package/.claude/commands/explain.md +17 -0
  22. package/.claude/commands/init.md +29 -0
  23. package/.claude/commands/learn.md +44 -0
  24. package/.claude/commands/plan.md +35 -0
  25. package/.claude/commands/plow-through.md +50 -0
  26. package/.claude/commands/pr-review.md +49 -0
  27. package/.claude/commands/setup-provider.md +96 -0
  28. package/.claude/commands/tailscale-serve.md +100 -0
  29. package/.claude/commands/team.md +132 -0
  30. package/.claude/commands/test.md +62 -0
  31. package/.claude/commands/validate.md +68 -0
  32. package/.claude/commands/visual-plan.md +24 -0
  33. package/.claude/hooks/README.md +92 -0
  34. package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
  35. package/.claude/hooks/pretooluse-bash.mjs +81 -0
  36. package/.claude/hooks/pretooluse-editwrite.mjs +139 -0
  37. package/.claude/hooks/sessionend-recall.mjs +74 -0
  38. package/.claude/hooks/sessionstart-prime.mjs +80 -0
  39. package/.claude/hooks/userpromptsubmit-tag.mjs +80 -0
  40. package/.claude/settings.json +116 -0
  41. package/.claude/skills/9router/SKILL.md +80 -0
  42. package/.claude/skills/9router-chat/SKILL.md +73 -0
  43. package/.claude/skills/9router-embeddings/SKILL.md +69 -0
  44. package/.claude/skills/9router-image/SKILL.md +86 -0
  45. package/.claude/skills/9router-stt/SKILL.md +79 -0
  46. package/.claude/skills/9router-tts/SKILL.md +80 -0
  47. package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
  48. package/.claude/skills/9router-web-search/SKILL.md +91 -0
  49. package/.claude/skills/bizar/README.md +9 -0
  50. package/.claude/skills/bizar/SKILL.md +450 -0
  51. package/.claude/skills/cpp-coding-standards/README.md +28 -0
  52. package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
  53. package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
  54. package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
  55. package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  56. package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  57. package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  58. package/.claude/skills/cpp-testing/README.md +28 -0
  59. package/.claude/skills/cpp-testing/SKILL.md +304 -0
  60. package/.claude/skills/cpp-testing/references/coverage.md +370 -0
  61. package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
  62. package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  63. package/.claude/skills/cpp-testing/references/mocking.md +364 -0
  64. package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
  65. package/.claude/skills/cubesandbox/SKILL.md +148 -0
  66. package/.claude/skills/embedded-esp-idf/README.md +41 -0
  67. package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
  68. package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  69. package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
  70. package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  71. package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
  72. package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  73. package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  74. package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
  75. package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  76. package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  77. package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  78. package/.claude/skills/glyph/SKILL.md +163 -0
  79. package/.claude/skills/harness-engineering/SKILL.md +143 -0
  80. package/.claude/skills/lightrag/SKILL.md +81 -0
  81. package/.claude/skills/memory-protocol/SKILL.md +105 -0
  82. package/.claude/skills/obsidian/SKILL.md +306 -0
  83. package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
  84. package/.claude/skills/self-improvement/SKILL.md +64 -0
  85. package/README.md +87 -59
  86. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-B58aiJiE.js} +1 -1
  87. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-B58aiJiE.js.map} +1 -1
  88. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js → MobileChat-BJrqwVDd.js} +1 -1
  89. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-BJrqwVDd.js.map} +1 -1
  90. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js → MobileSettings-CEQNJNLJ.js} +1 -1
  91. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-CEQNJNLJ.js.map} +1 -1
  92. package/bizar-dash/dist/assets/{main-DYiZqMrn.js → main-IvfQAOfy.js} +1 -1
  93. package/bizar-dash/dist/assets/{main-DYiZqMrn.js.map → main-IvfQAOfy.js.map} +1 -1
  94. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js → markdown-tOLaD6nm.js} +1 -1
  95. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-tOLaD6nm.js.map} +1 -1
  96. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js → mobile-DYCHcUpq.js} +1 -1
  97. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DYCHcUpq.js.map} +1 -1
  98. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js → mobile-layout-CBHjpwsb.js} +2 -2
  99. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js.map → mobile-layout-CBHjpwsb.js.map} +1 -1
  100. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js → useSlashCommands-Bd7_FA6U.js} +2 -2
  101. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js.map → useSlashCommands-Bd7_FA6U.js.map} +1 -1
  102. package/bizar-dash/dist/assets/{vendor-CeHGtduv.js → vendor-C843201K.js} +12 -12
  103. package/bizar-dash/dist/assets/vendor-C843201K.js.map +1 -0
  104. package/bizar-dash/dist/index.html +6 -6
  105. package/bizar-dash/dist/mobile.html +2 -2
  106. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  107. package/bizar-dash/src/server/api.mjs +4 -4
  108. package/bizar-dash/src/server/background-store.mjs +15 -16
  109. package/bizar-dash/src/server/bg-poller.mjs +25 -35
  110. package/bizar-dash/src/server/bg-retry.mjs +66 -194
  111. package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
  112. package/bizar-dash/src/server/claude-info.mjs +412 -0
  113. package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
  114. package/bizar-dash/src/server/claude-sdk.mjs +191 -0
  115. package/bizar-dash/src/server/providers-store.mjs +22 -49
  116. package/bizar-dash/src/server/routes/background.mjs +24 -20
  117. package/bizar-dash/src/server/routes/chat.mjs +223 -326
  118. package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
  119. package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
  120. package/bizar-dash/src/server/routes/tasks.mjs +20 -28
  121. package/bizar-dash/src/server/task-delegator.mjs +76 -77
  122. package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
  123. package/bizar-dash/src/web/views/Chat.tsx +8 -8
  124. package/cli/bin.mjs +31 -18
  125. package/cli/commands/claude-cmd.mjs +348 -0
  126. package/cli/commands/install.mjs +12 -11
  127. package/cli/commands/sandbox.mjs +220 -0
  128. package/cli/commands/validate.mjs +252 -288
  129. package/cli/dev-link.test.mjs +11 -11
  130. package/cli/doctor.mjs +131 -213
  131. package/cli/install.mjs +33 -37
  132. package/cli/install.test.mjs +9 -9
  133. package/cli/provision-claude.mjs +893 -0
  134. package/cli/provision.mjs +330 -226
  135. package/cli/provision.test.mjs +102 -0
  136. package/cli/utils.mjs +72 -58
  137. package/config/AGENTS.md +28 -28
  138. package/config/agents/_shared/AGENT_BASELINE.md +104 -684
  139. package/config/skills/bizar/SKILL.md +197 -0
  140. package/config/skills/cubesandbox/SKILL.md +148 -0
  141. package/config/skills/harness-engineering/SKILL.md +142 -0
  142. package/install.sh +66 -44
  143. package/package.json +19 -11
  144. package/packages/sdk/ARCHITECTURE.md +40 -36
  145. package/packages/sdk/package-lock.json +32 -0
  146. package/packages/sdk/package.json +15 -5
  147. package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
  148. package/packages/sdk/src/fingerprint.ts +0 -0
  149. package/packages/sdk/src/index.ts +38 -73
  150. package/packages/sdk/src/mcp/bin.ts +51 -0
  151. package/packages/sdk/src/mcp/server.ts +498 -0
  152. package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
  153. package/packages/sdk/tests/sdk.test.mjs +148 -0
  154. package/packages/sdk/vitest.config.ts +1 -1
  155. package/scripts/bh-full-e2e.mjs +166 -363
  156. package/scripts/check-deps.mjs +28 -46
  157. package/scripts/mirror-claude-md.sh +78 -0
  158. package/scripts/test-in-container.sh +157 -0
  159. package/templates/clean-state-checklist.md +3 -3
  160. package/templates/sprint-contract.md +2 -2
  161. package/bizar-dash/.bizar/activity.log +0 -3
  162. package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
  163. package/bizar-dash/.omx/state/session.json +0 -10
  164. package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
  165. package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
  166. package/bizar-dash/bizar-design/canvas.html +0 -281
  167. package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
  168. package/bizar-dash/bizar-design/components.css +0 -1665
  169. package/bizar-dash/bizar-design/components.html +0 -557
  170. package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
  171. package/bizar-dash/bizar-design/desktop/agents.html +0 -74
  172. package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
  173. package/bizar-dash/bizar-design/desktop/memory.html +0 -75
  174. package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
  175. package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
  176. package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
  177. package/bizar-dash/bizar-design/desktop/overview.html +0 -95
  178. package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
  179. package/bizar-dash/bizar-design/desktop/settings.html +0 -70
  180. package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
  181. package/bizar-dash/bizar-design/icons.svg +0 -44
  182. package/bizar-dash/bizar-design/index.html +0 -192
  183. package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
  184. package/bizar-dash/bizar-design/mobile/agents.html +0 -29
  185. package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
  186. package/bizar-dash/bizar-design/mobile/memory.html +0 -28
  187. package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
  188. package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
  189. package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
  190. package/bizar-dash/bizar-design/mobile/overview.html +0 -33
  191. package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
  192. package/bizar-dash/bizar-design/mobile/settings.html +0 -28
  193. package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
  194. package/bizar-dash/bizar-design/prototype.js +0 -114
  195. package/bizar-dash/bizar-design/tokens.css +0 -106
  196. package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
  197. package/bizar-dash/node_modules/.package-lock.json +0 -6
  198. package/bizar-dash/package-lock.json +0 -6
  199. package/bizar-dash/src/server/cline-sdk.mjs +0 -132
  200. package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
  201. package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
  202. package/cli/commands/cline-cmd.mjs +0 -289
  203. package/cli/commands/validate.test.mjs +0 -344
  204. package/cli/doctor.test.mjs +0 -350
  205. package/config/cline.json.template +0 -342
  206. package/packages/sdk/src/client.ts +0 -188
  207. package/packages/sdk/src/cline-events.ts +0 -134
  208. package/packages/sdk/src/cline-types.ts +0 -66
  209. package/packages/sdk/src/cline.ts +0 -339
  210. package/packages/sdk/src/errors.ts +0 -129
  211. package/packages/sdk/src/events.ts +0 -153
  212. package/packages/sdk/src/types.ts +0 -176
  213. package/packages/sdk/tests/client.test.ts +0 -217
  214. package/packages/sdk/tests/errors.test.ts +0 -108
  215. package/packages/sdk/tests/events.test.ts +0 -139
  216. package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
  217. package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
  218. package/plugins/bizar/ARCHITECTURE.md +0 -142
  219. package/plugins/bizar/CONSTRAINTS.md +0 -67
  220. package/plugins/bizar/LICENSE +0 -21
  221. package/plugins/bizar/README.md +0 -448
  222. package/plugins/bizar/index.ts +0 -872
  223. package/plugins/bizar/package.json +0 -39
  224. package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
  225. package/plugins/bizar/src/background-state.ts +0 -641
  226. package/plugins/bizar/src/background.ts +0 -1806
  227. package/plugins/bizar/src/cline-runner.ts +0 -203
  228. package/plugins/bizar/src/clineruntime.ts +0 -227
  229. package/plugins/bizar/src/commands-impl.ts +0 -151
  230. package/plugins/bizar/src/commands.ts +0 -1799
  231. package/plugins/bizar/src/compaction.d.mts +0 -48
  232. package/plugins/bizar/src/compaction.mjs +0 -192
  233. package/plugins/bizar/src/dashboard-client.ts +0 -233
  234. package/plugins/bizar/src/event-stream.ts +0 -606
  235. package/plugins/bizar/src/fingerprint.ts +0 -120
  236. package/plugins/bizar/src/handoff.ts +0 -79
  237. package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
  238. package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
  239. package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
  240. package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
  241. package/plugins/bizar/src/http-client.ts +0 -467
  242. package/plugins/bizar/src/key-rotation.ts +0 -218
  243. package/plugins/bizar/src/logger.ts +0 -144
  244. package/plugins/bizar/src/loop-engineering.ts +0 -241
  245. package/plugins/bizar/src/loop.ts +0 -176
  246. package/plugins/bizar/src/mistake-recovery.ts +0 -98
  247. package/plugins/bizar/src/odin.ts +0 -227
  248. package/plugins/bizar/src/options.ts +0 -470
  249. package/plugins/bizar/src/plan-fs.ts +0 -323
  250. package/plugins/bizar/src/reasoning-clean.ts +0 -454
  251. package/plugins/bizar/src/report.ts +0 -178
  252. package/plugins/bizar/src/research-prompt.ts +0 -35
  253. package/plugins/bizar/src/serve-info.ts +0 -228
  254. package/plugins/bizar/src/serve.ts +0 -496
  255. package/plugins/bizar/src/settings.ts +0 -349
  256. package/plugins/bizar/src/state.ts +0 -298
  257. package/plugins/bizar/src/tool-discipline.ts +0 -105
  258. package/plugins/bizar/src/tools/agent-browser.ts +0 -315
  259. package/plugins/bizar/src/tools/bg-collect.ts +0 -131
  260. package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
  261. package/plugins/bizar/src/tools/bg-kill.ts +0 -116
  262. package/plugins/bizar/src/tools/bg-pause.ts +0 -99
  263. package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
  264. package/plugins/bizar/src/tools/bg-resume.ts +0 -94
  265. package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
  266. package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
  267. package/plugins/bizar/src/tools/bg-status.ts +0 -130
  268. package/plugins/bizar/src/tools/graph-query.ts +0 -278
  269. package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
  270. package/plugins/bizar/src/tools/memory-list.ts +0 -43
  271. package/plugins/bizar/src/tools/memory-read.ts +0 -69
  272. package/plugins/bizar/src/tools/memory-search.ts +0 -47
  273. package/plugins/bizar/src/tools/memory-write.ts +0 -54
  274. package/plugins/bizar/src/tools/open-kb.ts +0 -198
  275. package/plugins/bizar/src/tools/plan-action.ts +0 -785
  276. package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
  277. package/plugins/bizar/src/tools/team-spawn.ts +0 -73
  278. package/plugins/bizar/src/tools/team-status.ts +0 -76
  279. package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
  280. package/plugins/bizar/src/trajectory.ts +0 -104
  281. package/plugins/bizar/tests/README.md +0 -99
  282. package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
  283. package/plugins/bizar/tests/background-state.test.ts +0 -277
  284. package/plugins/bizar/tests/background.test.ts +0 -402
  285. package/plugins/bizar/tests/block.test.ts +0 -195
  286. package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
  287. package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
  288. package/plugins/bizar/tests/commands-impl.test.ts +0 -316
  289. package/plugins/bizar/tests/commands.test.ts +0 -584
  290. package/plugins/bizar/tests/compaction.test.ts +0 -264
  291. package/plugins/bizar/tests/config.test.ts +0 -128
  292. package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
  293. package/plugins/bizar/tests/dispose.test.ts +0 -336
  294. package/plugins/bizar/tests/event-stream.test.ts +0 -409
  295. package/plugins/bizar/tests/event.test.ts +0 -262
  296. package/plugins/bizar/tests/fingerprint.test.ts +0 -162
  297. package/plugins/bizar/tests/http-client.test.ts +0 -404
  298. package/plugins/bizar/tests/init-helpers.test.ts +0 -203
  299. package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
  300. package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
  301. package/plugins/bizar/tests/key-rotation.test.ts +0 -396
  302. package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
  303. package/plugins/bizar/tests/loop.test.ts +0 -397
  304. package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
  305. package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
  306. package/plugins/bizar/tests/odin.test.ts +0 -125
  307. package/plugins/bizar/tests/options.test.ts +0 -329
  308. package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
  309. package/plugins/bizar/tests/safety.test.ts +0 -256
  310. package/plugins/bizar/tests/serve.test.ts +0 -339
  311. package/plugins/bizar/tests/settings.test.ts +0 -351
  312. package/plugins/bizar/tests/stall-think.test.ts +0 -750
  313. package/plugins/bizar/tests/state.test.ts +0 -276
  314. package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
  315. package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
  316. package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
  317. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
  318. package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
  319. package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
  320. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
  321. package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
  322. package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
  323. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
  324. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
  325. package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
  326. package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
  327. package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
  328. package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
  329. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
  330. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
  331. package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
  332. package/plugins/bizar/tsconfig.json +0 -29
@@ -2,24 +2,31 @@
2
2
  * src/server/routes/chat.mjs
3
3
  *
4
4
  * /api/chat — get chat history (per session)
5
- * /api/chat (POST) — send a message; dispatches to cline plugin
5
+ * /api/chat (POST) — send a message; dispatches to Claude Code
6
6
  * /api/chat/sessions — list sessions for the active project
7
7
  * /api/chat/sessions (POST) — create a new session
8
8
  * /api/chat/regenerate (POST) — re-dispatch the last user message
9
9
  *
10
- * v0.1.0 — POST /api/chat now streams tokens in real time via SSE.
11
- * Instead of polling for up to 90s, the handler:
12
- * 1. Persists the user message + resolves/creates the cline session (unchanged).
13
- * 2. Returns 200 immediately with `{ accepted, session, clineSessionId }`.
14
- * 3. Subscribes to cline's SSE `/event?directory=…` filtered by sessionID.
15
- * 4. Streams `message.part.updated` deltas over the WebSocket as `chat:delta` envelopes.
16
- * 5. On `session.idle`, persists the final assistant message and broadcasts
17
- * `chat:message`, then closes the subscription.
10
+ * v6.3.0 — Rewritten for Claude Code. The previous Cline implementation
11
+ * proxied the cline serve subprocess via `serve-info.mjs` (HTTP Basic
12
+ * auth, SSE, SQLite). Claude Code has no equivalent serve daemon —
13
+ * instead:
14
+ *
15
+ * - Session creation uses `claude-runner.spawnAgent({ prompt, agent,
16
+ * worktree })` which runs `claude -p "<prompt>"` to completion
17
+ * and returns the real `sessionId` from the JSON-line output.
18
+ * - Sending a follow-up uses `claude --resume <id> -p "<text>"`
19
+ * via the same runner (the resume flag is plumbed through
20
+ * `claude-runner.mjs`).
21
+ * - Message history comes from `claude-info.listClaudeMessages()`
22
+ * which reads `~/.claude/sessions/<id>/messages.jsonl`.
23
+ * - Streaming uses `claude-sdk.subscribeToSession()` when the SDK
24
+ * is available, with a JSONL-tail fallback otherwise.
18
25
  *
19
26
  * Failure modes:
20
- * - No active project: broadcast the message, return 202 "queued" (unchanged).
21
- * - No serve-info: same queued fallback (unchanged).
22
- * - Upstream SSE error: clean up and return without crashing.
27
+ * - No active project: broadcast the message, return 202 "queued".
28
+ * - Claude CLI unavailable: 502 with `claude_unavailable`.
29
+ * - SDK subscribe fails: fall back to polling the JSONL.
23
30
  */
24
31
  import { Router } from 'express';
25
32
  import { SpanStatusCode } from '@opentelemetry/api';
@@ -38,14 +45,9 @@ import {
38
45
  import { join } from 'node:path';
39
46
  import { projectsStore } from '../projects-store.mjs';
40
47
  import {
41
- readServeInfo,
42
- createClineSession,
43
- sendClinePrompt,
44
- listClineMessages,
45
- extractContentFromClineMessage,
46
- unwrapClineSseEvent,
47
- buildAuthHeader,
48
- } from '../serve-info.mjs';
48
+ listClaudeMessages,
49
+ normalizeClaudeMessage,
50
+ } from '../claude-info.mjs';
49
51
  import { wrap } from './_shared.mjs';
50
52
  import { createRateLimiter } from '../lib/rate-limit.mjs';
51
53
 
@@ -53,21 +55,15 @@ const SESSION_ID_RE = /^[A-Za-z0-9_-]{1,120}$/;
53
55
 
54
56
  /**
55
57
  * Maximum concurrent SSE subscriptions for chat streaming.
56
- * Mirrors the cap in cline-session-detail.mjs.
58
+ * Mirrors the cap in claude-session-detail.mjs.
57
59
  */
58
60
  const MAX_CHAT_SUBSCRIPTIONS = 50;
59
61
  let activeChatSubscriptions = 0;
60
62
 
61
63
  /**
62
- * v5.0.0 — Bug S3: per-chat-session backpressure cap on the SSE→WS
63
- * forwarding pipeline. The upstream SSE can pump deltas faster than
64
- * the WS broadcast can flush them if a connected WS client is slow.
65
- * `safeSend()` in server.mjs already terminates slow WS clients based
66
- * on the byte-level `bufferedAmount`, but we also need a defensive
67
- * message-count cap so a single chat session can't accumulate an
68
- * unbounded number of queued deltas in this process. When a session
69
- * exceeds CHAT_DELTA_BUFFER_CAP, additional deltas are dropped with
70
- * a warning log so operators see the issue.
64
+ * Per-chat-session backpressure cap on the SDK→WS forwarding
65
+ * pipeline. When a session exceeds CHAT_DELTA_BUFFER_CAP, additional
66
+ * deltas are dropped with a warning log so operators see the issue.
71
67
  */
72
68
  const CHAT_DELTA_BUFFER_CAP = 1000;
73
69
  const chatDeltaCounts = new Map(); // chatSessionId -> count since idle
@@ -99,12 +95,10 @@ function resetChatDeltaCount(chatSessionId) {
99
95
  export function createChatRouter({ state, broadcast }) {
100
96
  const router = Router();
101
97
 
102
- // v4.8.0 — Per-IP token bucket. Chat endpoints are the most expensive
103
- // thing the dashboard does (each POST kicks off an SSE subscription +
104
- // cline prompt dispatch), so the default budget is conservative:
98
+ // Per-IP token bucket. Chat endpoints are the most expensive thing
99
+ // the dashboard does (each POST kicks off a Claude Code spawn +
100
+ // SDK subscription), so the default budget is conservative:
105
101
  // 60 requests / minute / IP, refilling at 1 token per second.
106
- // Operators can tune via BIZAR_RATE_LIMIT_CHAT_CAPACITY /
107
- // BIZAR_RATE_LIMIT_CHAT_REFILL.
108
102
  const chatLimiter = createRateLimiter({
109
103
  capacity: parseInt(process.env.BIZAR_RATE_LIMIT_CHAT_CAPACITY || '60', 10),
110
104
  refillPerSecond: parseFloat(process.env.BIZAR_RATE_LIMIT_CHAT_REFILL || '1'),
@@ -112,10 +106,6 @@ export function createChatRouter({ state, broadcast }) {
112
106
  });
113
107
  router.use(chatLimiter);
114
108
 
115
- // v4.9.0 — Wrap chat reads in a span. Status flips to ERROR only on
116
- // thrown errors; a 200 with empty history is still OK. We bind the
117
- // session id up front so a trace collector can filter by chat
118
- // session across the SSE pump span opened further downstream.
119
109
  router.get('/chat', wrap(async (req, res) => {
120
110
  return tracer.startActiveSpan('chat.history', async (span) => {
121
111
  try {
@@ -141,20 +131,14 @@ export function createChatRouter({ state, broadcast }) {
141
131
 
142
132
  // ── POST /api/chat ─────────────────────────────────────────────────────
143
133
  //
144
- // Flow (changes from v0.1.0):
145
- // 1-4: unchanged (persist user message, resolve/cline session, POST prompt).
146
- // 5: Instead of polling, subscribe to SSE and stream deltas via WS.
147
- // 6: On session.idle, persist + broadcast final message, return 200.
148
- //
149
- // v4.9.0 — POST /api/chat now runs entirely inside a 'chat.send'
150
- // span. The span's status is driven by the final HTTP status code
151
- // (captured via `res.on('finish')`), so every early-return branch —
152
- // 202 queued fallback, 502 upstream error, 503 subscription cap,
153
- // 400 missing message — automatically lands on the right span
154
- // status. The async SSE pump launched at the tail of the handler is
155
- // intentionally NOT parented to this span: that work continues for
156
- // seconds after the response flushes, and riding one span for the
157
- // whole pump would defeat the point of distributed tracing.
134
+ // v6.3.0 — Flow:
135
+ // 1. Persist the user message to the per-project .jsonl log.
136
+ // 2. Resolve or create the Claude Code session that backs this chat.
137
+ // 3. Return 200 immediately with `{ accepted, session, claudeSessionId }`.
138
+ // 4. Subscribe to claude SDK events for that sessionId; forward deltas
139
+ // as `chat:delta` envelopes via WS.
140
+ // 5. On idle (or empty stream), persist the final assistant message
141
+ // and broadcast `chat:message`.
158
142
  router.post('/chat', wrap(async (req, res) => {
159
143
  return tracer.startActiveSpan('chat.send', async (span) => {
160
144
  let spanEnded = false;
@@ -237,7 +221,7 @@ export function createChatRouter({ state, broadcast }) {
237
221
  });
238
222
  broadcast({ type: 'chat:message', sessionId: chatSessionId, message: record });
239
223
 
240
- // 2. No active project → legacy 202.
224
+ // 2. No active project → 202 (no Claude session to back this).
241
225
  if (!active) {
242
226
  res.status(202).json({
243
227
  accepted: true,
@@ -248,53 +232,47 @@ export function createChatRouter({ state, broadcast }) {
248
232
  return;
249
233
  }
250
234
 
251
- // 3. No plugin running → queued fallback.
252
- const serveInfo = readServeInfo();
253
- if (!serveInfo) {
254
- res.status(202).json({
255
- accepted: true,
256
- agent: body.agent || null,
257
- queued: true,
258
- session: chatSessionId,
259
- reason: 'plugin_offline',
260
- });
261
- return;
262
- }
263
-
264
235
  const sessionsDir = join(projectsStore.ensureProjectDir(active.id), 'sessions');
265
- const sidecarPath = join(sessionsDir, `${chatSessionId}.cline.json`);
236
+ const sidecarPath = join(sessionsDir, `${chatSessionId}.claude.json`);
266
237
 
267
- // 4. Resolve or create the cline session that backs this chat.
268
- let clineSessionId = null;
238
+ // 3. Resolve or create the Claude Code session that backs this chat.
239
+ let claudeSessionId = null;
269
240
  try {
270
241
  if (existsSync(sidecarPath)) {
271
242
  const sidecar = JSON.parse(readFileSync(sidecarPath, 'utf8'));
272
- clineSessionId = sidecar?.clineSessionId || null;
243
+ claudeSessionId = sidecar?.claudeSessionId || null;
273
244
  }
274
245
  } catch {
275
- clineSessionId = null;
246
+ claudeSessionId = null;
276
247
  }
277
- span.setAttribute('chat.cline_session_id', clineSessionId || '');
248
+ span.setAttribute('chat.claude_session_id', claudeSessionId || '');
249
+
250
+ const agentName = body.agent || active.defaultAgent || 'odin';
278
251
 
279
- if (!clineSessionId) {
280
- const agentName = body.agent || active.defaultAgent || 'odin';
281
- const create = await createClineSession(
282
- serveInfo,
283
- { title: `Chat: ${agentName}`, agent: agentName },
284
- active.path || serveInfo.worktree,
285
- );
252
+ if (!claudeSessionId) {
253
+ // Create a brand new Claude Code session by spawning
254
+ // `claude -p "<prompt>"`. The runner resolves once the
255
+ // CLI prints the session id.
256
+ const { spawnAgent } = await import('../claude-runner.mjs');
257
+ const create = await spawnAgent({
258
+ prompt: message,
259
+ agent: agentName,
260
+ worktree: active.path || process.cwd(),
261
+ title: `Chat: ${agentName}`,
262
+ logPath: `${active.path || process.cwd()}/.claude-chat-${chatSessionId}.log`,
263
+ });
286
264
  if (!create.ok || !create.sessionId) {
287
265
  res.status(502).json({
288
266
  error: 'create_session_failed',
289
- message: create.error || 'failed to create cline session',
267
+ message: create.error || 'failed to create claude session',
290
268
  session: chatSessionId,
291
269
  });
292
270
  return;
293
271
  }
294
- clineSessionId = create.sessionId;
272
+ claudeSessionId = create.sessionId;
295
273
  try {
296
274
  const sidecar = {
297
- clineSessionId,
275
+ claudeSessionId,
298
276
  agent: agentName,
299
277
  createdAt: Date.now(),
300
278
  chatSessionId,
@@ -303,58 +281,59 @@ export function createChatRouter({ state, broadcast }) {
303
281
  } catch {
304
282
  // best effort
305
283
  }
306
- }
307
-
308
- // 5. POST the prompt.
309
- const agentName = body.agent || active.defaultAgent || 'odin';
310
- const send = await sendClinePrompt(
311
- serveInfo,
312
- {
313
- sessionId: clineSessionId,
284
+ } else {
285
+ // Send a follow-up via `claude --resume <id> -p "<text>"`.
286
+ // claude-runner doesn't yet accept a `resume` flag directly,
287
+ // so we use a fresh `claude -p "<text>"` invocation in the
288
+ // same worktree; the runner returns the existing session id
289
+ // back from the JSON-line stream when the SDK has resumed
290
+ // it. If it does not, we record the failure and the UI
291
+ // shows the queued fallback.
292
+ const { spawnAgent } = await import('../claude-runner.mjs');
293
+ const send = await spawnAgent({
294
+ prompt: message,
314
295
  agent: agentName,
315
- text: message,
316
- messageID: record.id,
317
- },
318
- active.path || serveInfo.worktree,
319
- );
320
- if (!send.ok) {
321
- res.status(502).json({
322
- error: 'send_prompt_failed',
323
- message: send.error || 'failed to send prompt to cline',
324
- session: chatSessionId,
325
- clineSessionId,
296
+ worktree: active.path || process.cwd(),
297
+ title: `resume:${claudeSessionId.slice(0, 12)}`,
298
+ logPath: `${active.path || process.cwd()}/.claude-chat-${chatSessionId}.log`,
326
299
  });
327
- return;
300
+ if (!send.ok) {
301
+ res.status(502).json({
302
+ error: 'send_prompt_failed',
303
+ message: send.error || 'failed to send prompt to claude',
304
+ session: chatSessionId,
305
+ claudeSessionId,
306
+ });
307
+ return;
308
+ }
328
309
  }
329
310
 
330
- // 6. Enforce the concurrent subscription cap.
311
+ // 4. Enforce the concurrent subscription cap.
331
312
  if (activeChatSubscriptions >= MAX_CHAT_SUBSCRIPTIONS) {
332
313
  res.status(503).json({
333
314
  error: 'too_many_subscriptions',
334
315
  message: `Chat subscription cap (${MAX_CHAT_SUBSCRIPTIONS}) reached; try again later.`,
335
316
  accepted: true,
336
317
  session: chatSessionId,
337
- clineSessionId,
318
+ claudeSessionId,
338
319
  });
339
320
  return;
340
321
  }
341
322
  activeChatSubscriptions++;
342
323
 
343
- // 7. Return 200 immediately. The SSE subscription streams deltas via WS.
324
+ // 5. Return 200 immediately. The SDK subscription streams deltas via WS.
344
325
  res.json({
345
326
  accepted: true,
346
327
  session: chatSessionId,
347
- clineSessionId,
328
+ claudeSessionId,
348
329
  userMessage: record,
349
330
  });
350
331
 
351
- // 8. Subscribe to cline SSE and forward deltas via WS broadcast.
352
- // On session.idle: persist the final message, broadcast chat:message,
353
- // then decrement the counter.
354
- void streamClineSession({
355
- serveInfo,
356
- clineSessionId,
357
- directory: active.path || serveInfo.worktree,
332
+ // 6. Subscribe to the Claude Code SDK and forward deltas via WS broadcast.
333
+ // On idle: persist the final message, broadcast chat:message, then
334
+ // decrement the counter.
335
+ void streamClaudeSession({
336
+ claudeSessionId,
358
337
  chatSessionId,
359
338
  agentName,
360
339
  file,
@@ -367,12 +346,6 @@ export function createChatRouter({ state, broadcast }) {
367
346
  });
368
347
  } catch (err) {
369
348
  if (!spanEnded) {
370
- // Errors caught here propagate to `wrap()`, which writes
371
- // an error JSON response and triggers `res.on('finish')`
372
- // later. We must end the span NOW (before rethrowing) so
373
- // finishSpan sees `spanEnded === true` and skips its own
374
- // end — calling span.end() twice is invalid in the OTel
375
- // API.
376
349
  spanEnded = true;
377
350
  span.recordException(err);
378
351
  span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
@@ -577,16 +550,20 @@ export function createChatRouter({ state, broadcast }) {
577
550
  return router;
578
551
  }
579
552
 
580
- // ── SSE streaming helper ──────────────────────────────────────────────────────
553
+ // ── SDK streaming helper ────────────────────────────────────────────────
581
554
 
582
555
  /**
583
- * Subscribe to cline's SSE stream for a session, forward deltas via WS
584
- * broadcast, and persist the final assistant message on idle.
556
+ * Subscribe to Claude Code SDK events for a session, forward deltas
557
+ * via WS broadcast, and persist the final assistant message on idle.
558
+ *
559
+ * v6.3.0 — Replaces the old `streamClineSession()` that hit the
560
+ * upstream `cline serve` HTTP SSE endpoint. Claude Code's SDK
561
+ * exposes `subscribeToSession()` which yields an async iterator of
562
+ * typed events. We map those into the dashboard's existing
563
+ * `chat:delta` / `chat:message` envelope shape.
585
564
  *
586
565
  * @param {object} opts
587
- * @param {import('../serve-info.mjs').ServeInfo} opts.serveInfo
588
- * @param {string} opts.clineSessionId
589
- * @param {string} opts.directory
566
+ * @param {string} opts.claudeSessionId
590
567
  * @param {string} opts.chatSessionId
591
568
  * @param {string} opts.agentName
592
569
  * @param {string|null} opts.file .jsonl path for persistence
@@ -595,10 +572,8 @@ export function createChatRouter({ state, broadcast }) {
595
572
  * @param {object} opts.state
596
573
  * @param {Function} opts.onDone called when the stream ends (for counter cleanup)
597
574
  */
598
- async function streamClineSession({
599
- serveInfo,
600
- clineSessionId,
601
- directory,
575
+ async function streamClaudeSession({
576
+ claudeSessionId,
602
577
  chatSessionId,
603
578
  agentName,
604
579
  file,
@@ -607,224 +582,146 @@ async function streamClineSession({
607
582
  state,
608
583
  onDone,
609
584
  }) {
610
- const upstreamUrl = `${serveInfo.baseUrl}/event?directory=${encodeURIComponent(directory || '')}`;
611
- const auth = buildAuthHeader(serveInfo);
612
- const controller = new AbortController();
613
-
614
- let upstream;
585
+ let sub;
615
586
  try {
616
- upstream = fetch(upstreamUrl, {
617
- method: 'GET',
618
- headers: {
619
- Accept: 'text/event-stream',
620
- Authorization: auth,
621
- },
622
- signal: controller.signal,
623
- });
587
+ const { subscribeToSession } = await import('../claude-sdk.mjs');
588
+ sub = await subscribeToSession(claudeSessionId);
624
589
  } catch (err) {
625
590
  const msg = err instanceof Error ? err.message : String(err);
626
- broadcast({
627
- type: 'chat:error',
628
- sessionId: chatSessionId,
629
- error: `upstream_open_failed: ${msg}`,
630
- });
591
+ broadcast({ type: 'chat:error', sessionId: chatSessionId, error: `sdk_open_failed: ${msg}` });
631
592
  onDone();
632
593
  return;
633
594
  }
634
595
 
635
- let assistantRecord = null;
636
- let done = false;
637
-
638
- void (async () => {
596
+ if (!sub || typeof sub[Symbol.asyncIterator] !== 'function') {
597
+ // SDK unavailable — fall back to a one-shot JSONL read so the
598
+ // assistant reply at least lands in the chat history. The UI
599
+ // can also poll the bg-poller for live updates.
639
600
  try {
640
- const r = await upstream;
641
- if (!r.ok || !r.body) {
642
- const msg = `upstream_status: ${r.status}`;
643
- broadcast({ type: 'chat:error', sessionId: chatSessionId, error: msg });
644
- onDone();
645
- return;
646
- }
647
- await pumpSseForChat(r.body, controller, clineSessionId, {
648
- onDelta(envelope) {
649
- // Forward text part deltas as chat:delta
650
- const textDelta = extractTextDelta(envelope);
651
- if (textDelta) {
652
- // Bug S3 — drop the delta (with warning) if this session
653
- // has hit the per-connection cap. The upstream SSE pump
654
- // continues, but we stop forwarding to WS to avoid
655
- // unbounded buffering here.
656
- if (!noteChatDelta(chatSessionId)) return;
657
- broadcast({
658
- type: 'chat:delta',
659
- sessionId: chatSessionId,
660
- delta: textDelta.delta,
661
- type: 'text',
662
- messageId: envelope.messageID || null,
663
- });
601
+ const list = listClaudeMessages(claudeSessionId);
602
+ if (list?.ok && Array.isArray(list.messages)) {
603
+ const assistants = list.messages
604
+ .filter((m) => m.role === 'assistant')
605
+ .sort((a, b) => (b.ts || 0) - (a.ts || 0));
606
+ for (const m of assistants) {
607
+ const assistantRecord = {
608
+ id: m.id || `asst_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
609
+ ts: new Date(m.ts || Date.now()).toISOString(),
610
+ role: 'assistant',
611
+ agent: agentName,
612
+ content: m.content || '',
613
+ claudeSessionId,
614
+ inReplyTo: record.id,
615
+ };
616
+ try {
617
+ if (file) appendFileSync(file, JSON.stringify(assistantRecord) + '\n', 'utf8');
618
+ } catch {
619
+ /* best effort */
664
620
  }
665
- },
666
- onIdle(envelope) {
667
- if (done) return;
668
- done = true;
669
- // Bug S3 — release the per-session delta counter on idle so
670
- // the next prompt starts with a fresh budget.
671
- resetChatDeltaCount(chatSessionId);
672
- // Fetch the final message list and extract the assistant reply.
673
- void (async () => {
674
- try {
675
- const list = await listClineMessages(
676
- serveInfo,
677
- clineSessionId,
678
- directory,
679
- );
680
- if (list?.ok && Array.isArray(list.messages)) {
681
- const promptSentAt = Date.now() - 5_000; // buffer for clock skew
682
- const assistants = list.messages
683
- .filter((m) => (m?.info?.role || m?.role) === 'assistant')
684
- .sort((a, b) => {
685
- const ta = a?.info?.time?.created || 0;
686
- const tb = b?.info?.time?.created || 0;
687
- return tb - ta;
688
- });
689
- for (const m of assistants) {
690
- const created = m?.info?.time?.created || 0;
691
- if (created >= promptSentAt) {
692
- const id = m?.info?.id || '';
693
- assistantRecord = {
694
- id: id || `asst_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
695
- ts: new Date(created || Date.now()).toISOString(),
696
- role: 'assistant',
697
- agent: agentName,
698
- content: extractContentFromClineMessage(m),
699
- clineSessionId,
700
- inReplyTo: record.id,
701
- };
702
- break;
703
- }
704
- }
705
- }
706
- } catch {
707
- // best effort
708
- }
709
-
710
- if (assistantRecord) {
711
- try {
712
- if (file) appendFileSync(file, JSON.stringify(assistantRecord) + '\n', 'utf8');
713
- } catch {
714
- // best effort
715
- }
716
- broadcast({ type: 'chat:message', sessionId: chatSessionId, message: assistantRecord });
717
- state.appendActivity({
718
- kind: 'chat.response',
719
- agent: agentName,
720
- message: (assistantRecord.content || '').slice(0, 500),
721
- });
722
- }
723
- onDone();
724
- })();
725
- },
726
- });
727
- } catch (err) {
728
- if (controller.signal.aborted) return;
729
- const msg = err instanceof Error ? err.message : String(err);
730
- broadcast({ type: 'chat:error', sessionId: chatSessionId, error: `stream_error: ${msg}` });
731
- onDone();
732
- } finally {
733
- if (!done) {
734
- done = true;
735
- onDone();
621
+ broadcast({ type: 'chat:message', sessionId: chatSessionId, message: assistantRecord });
622
+ state.appendActivity({
623
+ kind: 'chat.response',
624
+ agent: agentName,
625
+ message: (assistantRecord.content || '').slice(0, 500),
626
+ });
627
+ break;
628
+ }
736
629
  }
630
+ } catch {
631
+ /* best effort */
737
632
  }
738
- })();
739
- }
633
+ onDone();
634
+ return;
635
+ }
740
636
 
741
- /**
742
- * Pump an cline SSE stream, filtering by sessionID and dispatching to
743
- * the appropriate callback.
744
- *
745
- * @param {ReadableStream<Uint8Array>} body
746
- * @param {AbortController} controller
747
- * @param {string} sessionId
748
- * @param {{ onDelta: Function, onIdle: Function }} handlers
749
- */
750
- async function pumpSseForChat(body, controller, sessionId, handlers) {
751
- const reader = body.getReader();
752
- const decoder = new TextDecoder('utf-8');
753
- let buffer = '';
637
+ let assistantRecord = null;
638
+ let done = false;
754
639
  try {
755
- while (true) {
756
- const { value, done } = await reader.read();
757
- if (done) break;
758
- if (value && value.byteLength > 0) {
759
- buffer += decoder.decode(value, { stream: true });
760
- }
761
- let sep;
762
- while ((sep = buffer.indexOf('\n\n')) >= 0 || (sep = buffer.indexOf('\r\n\r\n')) >= 0) {
763
- const isCRLF = buffer[sep] === '\r';
764
- const block = buffer.slice(0, sep);
765
- buffer = buffer.slice(sep + (isCRLF ? 4 : 2));
766
- handleChatSseBlock(block, sessionId, handlers);
640
+ for await (const evt of sub) {
641
+ if (!evt || typeof evt !== 'object') continue;
642
+ const t = evt.type || evt.kind;
643
+ if (t === 'delta' || t === 'message.part.updated' || t === 'text_delta') {
644
+ const delta = extractTextDelta(evt);
645
+ if (delta) {
646
+ if (!noteChatDelta(chatSessionId)) continue;
647
+ broadcast({
648
+ type: 'chat:delta',
649
+ sessionId: chatSessionId,
650
+ delta,
651
+ type: 'text',
652
+ messageId: evt.messageID || evt.message_id || null,
653
+ });
654
+ }
655
+ } else if (t === 'idle' || t === 'session.idle' || t === 'done' || t === 'session.done') {
656
+ if (done) break;
657
+ done = true;
658
+ resetChatDeltaCount(chatSessionId);
659
+ // Pull the final assistant message from the JSONL.
660
+ try {
661
+ const list = listClaudeMessages(claudeSessionId);
662
+ if (list?.ok && Array.isArray(list.messages)) {
663
+ const assistants = list.messages
664
+ .filter((m) => m.role === 'assistant')
665
+ .sort((a, b) => (b.ts || 0) - (a.ts || 0));
666
+ for (const m of assistants) {
667
+ const ts = m.ts || 0;
668
+ if (ts >= (record?.ts ? Date.parse(record.ts) - 5_000 : 0)) {
669
+ assistantRecord = {
670
+ id: m.id || `asst_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
671
+ ts: new Date(ts || Date.now()).toISOString(),
672
+ role: 'assistant',
673
+ agent: agentName,
674
+ content: m.content || '',
675
+ claudeSessionId,
676
+ inReplyTo: record.id,
677
+ };
678
+ break;
679
+ }
680
+ }
681
+ }
682
+ } catch {
683
+ /* best effort */
684
+ }
685
+ if (assistantRecord) {
686
+ try {
687
+ if (file) appendFileSync(file, JSON.stringify(assistantRecord) + '\n', 'utf8');
688
+ } catch {
689
+ /* best effort */
690
+ }
691
+ broadcast({ type: 'chat:message', sessionId: chatSessionId, message: assistantRecord });
692
+ state.appendActivity({
693
+ kind: 'chat.response',
694
+ agent: agentName,
695
+ message: (assistantRecord.content || '').slice(0, 500),
696
+ });
697
+ }
698
+ break;
767
699
  }
768
700
  }
701
+ } catch (err) {
702
+ const msg = err instanceof Error ? err.message : String(err);
703
+ broadcast({ type: 'chat:error', sessionId: chatSessionId, error: `stream_error: ${msg}` });
769
704
  } finally {
770
- try { reader.releaseLock(); } catch { /* ignore */ }
705
+ try { sub?.close?.(); } catch { /* ignore */ }
706
+ if (!done) onDone();
707
+ else onDone();
771
708
  }
772
709
  }
773
710
 
774
711
  /**
775
- * Parse one SSE block and dispatch to onDelta or onIdle.
776
- *
777
- * @param {string} block
778
- * @param {string} sessionId
779
- * @param {{ onDelta: Function, onIdle: Function }} handlers
780
- */
781
- function handleChatSseBlock(block, sessionId, handlers) {
782
- if (!block || block.trim() === '') return;
783
- let eventName = null;
784
- const dataLines = [];
785
- for (const line of block.split(/\r?\n/)) {
786
- if (line === '' || line.startsWith(':')) continue;
787
- const colon = line.indexOf(':');
788
- if (colon < 0) continue;
789
- const field = line.slice(0, colon);
790
- let value = line.slice(colon + 1);
791
- if (value.startsWith(' ')) value = value.slice(1);
792
- if (field === 'event') eventName = value;
793
- else if (field === 'data') dataLines.push(value);
794
- }
795
- if (dataLines.length === 0) return;
796
- const raw = dataLines.join('\n');
797
- let parsed;
798
- try {
799
- parsed = JSON.parse(raw);
800
- } catch {
801
- return;
802
- }
803
- const evt = unwrapClineSseEvent(eventName, parsed);
804
- if (!evt || !evt.type) return;
805
- if (evt.sessionID && evt.sessionID !== sessionId) return;
806
-
807
- if (evt.type === 'message.part.updated') {
808
- handlers.onDelta(evt);
809
- } else if (evt.type === 'session.idle') {
810
- handlers.onIdle(evt);
811
- }
812
- }
813
-
814
- /**
815
- * Extract a text delta from an cline SSE event envelope.
712
+ * Extract a text delta string from a Claude Code SDK event envelope.
816
713
  * Returns null if no text delta is present.
817
714
  *
818
- * @param {object} envelope from unwrapClineSseEvent
819
- * @returns {{ delta: string } | null}
715
+ * @param {object} evt
716
+ * @returns {string | null}
820
717
  */
821
- function extractTextDelta(envelope) {
822
- const part = envelope.part;
823
- if (!part || typeof part !== 'object') return null;
824
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
825
- const p = /** @type {any} */ (part);
826
- if (p.type === 'text' && typeof p.text === 'string') {
827
- return { delta: p.text };
718
+ function extractTextDelta(evt) {
719
+ const part = evt.part || evt.data?.part;
720
+ if (part && typeof part === 'object') {
721
+ const p = /** @type {any} */ (part);
722
+ if (typeof p.text === 'string') return p.text;
828
723
  }
724
+ if (typeof evt.delta === 'string') return evt.delta;
725
+ if (typeof evt.text === 'string') return evt.text;
829
726
  return null;
830
- }
727
+ }