@polderlabs/bizar 6.2.5 → 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 (329) 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 +17 -18
  125. package/cli/commands/claude-cmd.mjs +348 -0
  126. package/cli/commands/install.mjs +12 -11
  127. package/cli/commands/validate.mjs +252 -325
  128. package/cli/dev-link.test.mjs +11 -11
  129. package/cli/doctor.mjs +131 -213
  130. package/cli/install.mjs +33 -37
  131. package/cli/install.test.mjs +9 -9
  132. package/cli/provision-claude.mjs +893 -0
  133. package/cli/provision.mjs +224 -227
  134. package/cli/utils.mjs +72 -58
  135. package/config/AGENTS.md +28 -28
  136. package/install.sh +66 -44
  137. package/package.json +19 -11
  138. package/packages/sdk/ARCHITECTURE.md +40 -36
  139. package/packages/sdk/package-lock.json +32 -0
  140. package/packages/sdk/package.json +15 -5
  141. package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
  142. package/packages/sdk/src/fingerprint.ts +0 -0
  143. package/packages/sdk/src/index.ts +38 -73
  144. package/packages/sdk/src/mcp/bin.ts +51 -0
  145. package/packages/sdk/src/mcp/server.ts +498 -0
  146. package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
  147. package/packages/sdk/tests/sdk.test.mjs +148 -0
  148. package/packages/sdk/vitest.config.ts +1 -1
  149. package/scripts/bh-full-e2e.mjs +166 -363
  150. package/scripts/check-deps.mjs +28 -46
  151. package/scripts/mirror-claude-md.sh +78 -0
  152. package/scripts/test-in-container.sh +31 -9
  153. package/templates/clean-state-checklist.md +3 -3
  154. package/templates/sprint-contract.md +2 -2
  155. package/bizar-dash/.bizar/activity.log +0 -3
  156. package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
  157. package/bizar-dash/.omx/state/session.json +0 -10
  158. package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
  159. package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
  160. package/bizar-dash/bizar-design/canvas.html +0 -281
  161. package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
  162. package/bizar-dash/bizar-design/components.css +0 -1665
  163. package/bizar-dash/bizar-design/components.html +0 -557
  164. package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
  165. package/bizar-dash/bizar-design/desktop/agents.html +0 -74
  166. package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
  167. package/bizar-dash/bizar-design/desktop/memory.html +0 -75
  168. package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
  169. package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
  170. package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
  171. package/bizar-dash/bizar-design/desktop/overview.html +0 -95
  172. package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
  173. package/bizar-dash/bizar-design/desktop/settings.html +0 -70
  174. package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
  175. package/bizar-dash/bizar-design/icons.svg +0 -44
  176. package/bizar-dash/bizar-design/index.html +0 -192
  177. package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
  178. package/bizar-dash/bizar-design/mobile/agents.html +0 -29
  179. package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
  180. package/bizar-dash/bizar-design/mobile/memory.html +0 -28
  181. package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
  182. package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
  183. package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
  184. package/bizar-dash/bizar-design/mobile/overview.html +0 -33
  185. package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
  186. package/bizar-dash/bizar-design/mobile/settings.html +0 -28
  187. package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
  188. package/bizar-dash/bizar-design/prototype.js +0 -114
  189. package/bizar-dash/bizar-design/tokens.css +0 -106
  190. package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
  191. package/bizar-dash/node_modules/.package-lock.json +0 -6
  192. package/bizar-dash/package-lock.json +0 -6
  193. package/bizar-dash/src/server/cline-sdk.mjs +0 -132
  194. package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
  195. package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
  196. package/cli/commands/cline-cmd.mjs +0 -289
  197. package/cli/commands/validate.test.mjs +0 -379
  198. package/cli/doctor.test.mjs +0 -350
  199. package/config/cline.json.template +0 -342
  200. package/packages/sdk/src/client.ts +0 -188
  201. package/packages/sdk/src/cline-events.ts +0 -134
  202. package/packages/sdk/src/cline-types.ts +0 -66
  203. package/packages/sdk/src/cline.ts +0 -339
  204. package/packages/sdk/src/errors.ts +0 -129
  205. package/packages/sdk/src/events.ts +0 -153
  206. package/packages/sdk/src/types.ts +0 -176
  207. package/packages/sdk/tests/client.test.ts +0 -217
  208. package/packages/sdk/tests/errors.test.ts +0 -108
  209. package/packages/sdk/tests/events.test.ts +0 -139
  210. package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
  211. package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
  212. package/plugins/bizar/ARCHITECTURE.md +0 -142
  213. package/plugins/bizar/CONSTRAINTS.md +0 -67
  214. package/plugins/bizar/LICENSE +0 -21
  215. package/plugins/bizar/README.md +0 -448
  216. package/plugins/bizar/index.ts +0 -925
  217. package/plugins/bizar/package.json +0 -39
  218. package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
  219. package/plugins/bizar/src/background-state.ts +0 -641
  220. package/plugins/bizar/src/background.ts +0 -1806
  221. package/plugins/bizar/src/cline-runner.ts +0 -203
  222. package/plugins/bizar/src/clineruntime.ts +0 -227
  223. package/plugins/bizar/src/commands-impl.ts +0 -151
  224. package/plugins/bizar/src/commands.ts +0 -1799
  225. package/plugins/bizar/src/compaction.d.mts +0 -48
  226. package/plugins/bizar/src/compaction.mjs +0 -192
  227. package/plugins/bizar/src/dashboard-client.ts +0 -233
  228. package/plugins/bizar/src/event-stream.ts +0 -606
  229. package/plugins/bizar/src/fingerprint.ts +0 -120
  230. package/plugins/bizar/src/handoff.ts +0 -79
  231. package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
  232. package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
  233. package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
  234. package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
  235. package/plugins/bizar/src/http-client.ts +0 -467
  236. package/plugins/bizar/src/key-rotation.ts +0 -218
  237. package/plugins/bizar/src/logger.ts +0 -144
  238. package/plugins/bizar/src/loop-engineering.ts +0 -241
  239. package/plugins/bizar/src/loop.ts +0 -176
  240. package/plugins/bizar/src/mistake-recovery.ts +0 -98
  241. package/plugins/bizar/src/odin.ts +0 -227
  242. package/plugins/bizar/src/options.ts +0 -470
  243. package/plugins/bizar/src/plan-fs.ts +0 -323
  244. package/plugins/bizar/src/reasoning-clean.ts +0 -454
  245. package/plugins/bizar/src/report.ts +0 -178
  246. package/plugins/bizar/src/research-prompt.ts +0 -35
  247. package/plugins/bizar/src/serve-info.ts +0 -228
  248. package/plugins/bizar/src/serve.ts +0 -496
  249. package/plugins/bizar/src/settings.ts +0 -349
  250. package/plugins/bizar/src/state.ts +0 -298
  251. package/plugins/bizar/src/tool-discipline.ts +0 -105
  252. package/plugins/bizar/src/tools/agent-browser.ts +0 -315
  253. package/plugins/bizar/src/tools/bg-collect.ts +0 -131
  254. package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
  255. package/plugins/bizar/src/tools/bg-kill.ts +0 -116
  256. package/plugins/bizar/src/tools/bg-pause.ts +0 -99
  257. package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
  258. package/plugins/bizar/src/tools/bg-resume.ts +0 -94
  259. package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
  260. package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
  261. package/plugins/bizar/src/tools/bg-status.ts +0 -130
  262. package/plugins/bizar/src/tools/graph-query.ts +0 -278
  263. package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
  264. package/plugins/bizar/src/tools/memory-list.ts +0 -43
  265. package/plugins/bizar/src/tools/memory-read.ts +0 -69
  266. package/plugins/bizar/src/tools/memory-search.ts +0 -47
  267. package/plugins/bizar/src/tools/memory-write.ts +0 -54
  268. package/plugins/bizar/src/tools/open-kb.ts +0 -198
  269. package/plugins/bizar/src/tools/plan-action.ts +0 -785
  270. package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
  271. package/plugins/bizar/src/tools/sandbox.ts +0 -232
  272. package/plugins/bizar/src/tools/team-spawn.ts +0 -73
  273. package/plugins/bizar/src/tools/team-status.ts +0 -76
  274. package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
  275. package/plugins/bizar/src/trajectory.ts +0 -104
  276. package/plugins/bizar/tests/README.md +0 -99
  277. package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
  278. package/plugins/bizar/tests/background-state.test.ts +0 -277
  279. package/plugins/bizar/tests/background.test.ts +0 -402
  280. package/plugins/bizar/tests/block.test.ts +0 -195
  281. package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
  282. package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
  283. package/plugins/bizar/tests/commands-impl.test.ts +0 -316
  284. package/plugins/bizar/tests/commands.test.ts +0 -584
  285. package/plugins/bizar/tests/compaction.test.ts +0 -264
  286. package/plugins/bizar/tests/config.test.ts +0 -128
  287. package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
  288. package/plugins/bizar/tests/dispose.test.ts +0 -336
  289. package/plugins/bizar/tests/event-stream.test.ts +0 -409
  290. package/plugins/bizar/tests/event.test.ts +0 -262
  291. package/plugins/bizar/tests/fingerprint.test.ts +0 -190
  292. package/plugins/bizar/tests/http-client.test.ts +0 -404
  293. package/plugins/bizar/tests/init-helpers.test.ts +0 -203
  294. package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
  295. package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
  296. package/plugins/bizar/tests/key-rotation.test.ts +0 -396
  297. package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
  298. package/plugins/bizar/tests/loop.test.ts +0 -397
  299. package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
  300. package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
  301. package/plugins/bizar/tests/odin.test.ts +0 -125
  302. package/plugins/bizar/tests/options.test.ts +0 -329
  303. package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
  304. package/plugins/bizar/tests/safety.test.ts +0 -281
  305. package/plugins/bizar/tests/serve.test.ts +0 -339
  306. package/plugins/bizar/tests/settings.test.ts +0 -351
  307. package/plugins/bizar/tests/stall-think.test.ts +0 -750
  308. package/plugins/bizar/tests/state.test.ts +0 -276
  309. package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
  310. package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
  311. package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
  312. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
  313. package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
  314. package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
  315. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
  316. package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
  317. package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
  318. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
  319. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
  320. package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
  321. package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
  322. package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
  323. package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
  324. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
  325. package/plugins/bizar/tests/tools/sandbox.test.ts +0 -117
  326. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
  327. package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
  328. package/plugins/bizar/tsconfig.json +0 -29
  329. package/scripts/mirror-agents-md.sh +0 -69
@@ -1,409 +0,0 @@
1
- /**
2
- * EventStream tests.
3
- *
4
- * Tests: connect/disconnect, one global subscription (not per-instance),
5
- * EventSessionIdle → done, EventSessionError → failed, EventMessagePartUpdated
6
- * for toolCallCount and threshold-12 loop guard capture (HIGH-5, HIGH-12,
7
- * MEDIUM-8, HIGH-17), SSE reconnect with backoff, dispose closes stream.
8
- *
9
- * Uses fake SSE event emission; no real network calls.
10
- */
11
-
12
- import { describe, it, expect } from "bun:test";
13
-
14
- // ---------------------------------------------------------------------------
15
- // Types mirroring the expected event shapes from §1.6 / §4
16
- // ---------------------------------------------------------------------------
17
-
18
- type BackgroundStatus = "pending" | "running" | "done" | "failed" | "killed" | "timed_out";
19
-
20
- interface BackgroundState {
21
- instanceId: string;
22
- sessionId: string;
23
- status: BackgroundStatus;
24
- toolCallCount: number;
25
- loopGuardTool?: string;
26
- error?: string;
27
- completedAt?: number;
28
- }
29
-
30
- interface EventBase {
31
- type: string;
32
- sessionID?: string;
33
- }
34
-
35
- interface EventSessionIdle extends EventBase {
36
- type: "session.idle";
37
- sessionID: string;
38
- }
39
-
40
- interface EventSessionError extends EventBase {
41
- type: "session.error";
42
- sessionID: string;
43
- error?: string;
44
- }
45
-
46
- interface EventMessagePartUpdated extends EventBase {
47
- type: "message.part.updated";
48
- sessionID: string;
49
- part: {
50
- type: "tool";
51
- state?: {
52
- status?: string;
53
- error?: string;
54
- };
55
- toolName?: string;
56
- };
57
- }
58
-
59
- type Event = EventSessionIdle | EventSessionError | EventMessagePartUpdated;
60
-
61
- // ---------------------------------------------------------------------------
62
- // Fake EventStream matching the expected interface
63
- // ---------------------------------------------------------------------------
64
-
65
- class FakeEventStream {
66
- private handlers = new Map<string, Array<(event: Event) => void>>();
67
- private sessions = new Map<string, string>(); // instanceId → sessionId
68
- public instances = new Map<string, BackgroundState>();
69
- private closed = false;
70
-
71
- /** Register an instance's sessionId for event routing */
72
- register(instanceId: string, sessionId: string) {
73
- this.sessions.set(instanceId, sessionId);
74
- }
75
-
76
- /** Subscribe to events */
77
- onSessionEvent(handler: (instanceId: string, event: Event) => void) {
78
- const wrapper = (ev: Event) => {
79
- if (this.closed) return;
80
- // Route event to the correct instance based on sessionID
81
- for (const [instanceId, sessionId] of this.sessions) {
82
- if (sessionId === ev.sessionID) {
83
- handler(instanceId, ev);
84
- }
85
- }
86
- };
87
- return wrapper;
88
- }
89
-
90
- /** Apply event to instance state */
91
- applyEvent(instanceId: string, event: Event): void {
92
- const inst = this.instances.get(instanceId);
93
- if (!inst) return;
94
-
95
- switch (event.type) {
96
- case "session.idle":
97
- inst.status = "done";
98
- inst.completedAt = Date.now();
99
- break;
100
-
101
- case "session.error":
102
- inst.status = "failed";
103
- inst.error = event.error ?? "session error";
104
- inst.completedAt = Date.now();
105
- break;
106
-
107
- case "message.part.updated": {
108
- if (event.part.type !== "tool") break;
109
- inst.toolCallCount = (inst.toolCallCount ?? 0) + 1;
110
-
111
- // Threshold-12 loop guard capture (HIGH-17)
112
- const partError = event.part.state?.error;
113
- if (typeof partError === "string") {
114
- const m = partError.match(/Loop protection: 12 identical calls to (\S+)/);
115
- if (m) {
116
- inst.loopGuardTool = m[1];
117
- inst.error = `Loop protection: 12 identical calls to ${m[1]}`;
118
- inst.status = "failed";
119
- inst.completedAt = Date.now();
120
- }
121
- }
122
- break;
123
- }
124
- }
125
- }
126
-
127
- /** Set instance state for testing */
128
- setInstance(inst: BackgroundState) {
129
- this.instances.set(inst.instanceId, { ...inst });
130
- }
131
-
132
- /** Close the stream */
133
- close() {
134
- this.closed = true;
135
- this.sessions.clear();
136
- }
137
-
138
- get isClosed(): boolean {
139
- return this.closed;
140
- }
141
- }
142
-
143
- // ---------------------------------------------------------------------------
144
- // One global subscription, not per-instance (HIGH-5, HIGH-12)
145
- // ---------------------------------------------------------------------------
146
-
147
- describe("one global SSE subscription", () => {
148
- it("opens exactly one EventSource for the plugin process (HIGH-5)", () => {
149
- const stream = new FakeEventStream();
150
- // The plugin opens ONE stream on init, not one per instance
151
- // This is a structural test: the EventStream class has a single .connect()
152
- expect(typeof stream.onSessionEvent).toBe("function");
153
- });
154
-
155
- it("events are filtered by sessionID to the correct instance", () => {
156
- const stream = new FakeEventStream();
157
- stream.register("bgr_1", "sess_1");
158
- stream.register("bgr_2", "sess_2");
159
-
160
- stream.setInstance({ instanceId: "bgr_1", sessionId: "sess_1", status: "running", toolCallCount: 0 });
161
- stream.setInstance({ instanceId: "bgr_2", sessionId: "sess_2", status: "running", toolCallCount: 0 });
162
-
163
- const handler = stream.onSessionEvent((instanceId, event) => {
164
- stream.applyEvent(instanceId, event);
165
- });
166
-
167
- // Fire event for sess_1 only
168
- const idleEvent: EventSessionIdle = { type: "session.idle", sessionID: "sess_1" };
169
- handler(idleEvent);
170
-
171
- expect(stream.instances.get("bgr_1")!.status).toBe("done");
172
- expect(stream.instances.get("bgr_2")!.status).toBe("running"); // unaffected
173
- });
174
- });
175
-
176
- // ---------------------------------------------------------------------------
177
- // EventSessionIdle → done (HIGH-5)
178
- // ---------------------------------------------------------------------------
179
-
180
- describe("EventSessionIdle → done", () => {
181
- it("EventSessionIdle marks instance as done", () => {
182
- const stream = new FakeEventStream();
183
- stream.setInstance({ instanceId: "bgr_done", sessionId: "sess_done", status: "running", toolCallCount: 0 });
184
- stream.register("bgr_done", "sess_done");
185
-
186
- const handler = stream.onSessionEvent((instanceId, event) => stream.applyEvent(instanceId, event));
187
- handler({ type: "session.idle", sessionID: "sess_done" });
188
-
189
- expect(stream.instances.get("bgr_done")!.status).toBe("done");
190
- expect(stream.instances.get("bgr_done")!.completedAt).toBeDefined();
191
- });
192
- });
193
-
194
- // ---------------------------------------------------------------------------
195
- // EventSessionError → failed (HIGH-5)
196
- // ---------------------------------------------------------------------------
197
-
198
- describe("EventSessionError → failed", () => {
199
- it("EventSessionError marks instance as failed", () => {
200
- const stream = new FakeEventStream();
201
- stream.setInstance({ instanceId: "bgr_err", sessionId: "sess_err", status: "running", toolCallCount: 0 });
202
- stream.register("bgr_err", "sess_err");
203
-
204
- const handler = stream.onSessionEvent((instanceId, event) => stream.applyEvent(instanceId, event));
205
- handler({ type: "session.error", sessionID: "sess_err", error: "something went wrong" });
206
-
207
- expect(stream.instances.get("bgr_err")!.status).toBe("failed");
208
- expect(stream.instances.get("bgr_err")!.error).toBe("something went wrong");
209
- });
210
-
211
- it("EventSessionError with no error defaults to 'session error'", () => {
212
- const stream = new FakeEventStream();
213
- stream.setInstance({ instanceId: "bgr_err2", sessionId: "sess_err2", status: "running", toolCallCount: 0 });
214
- stream.register("bgr_err2", "sess_err2");
215
-
216
- const handler = stream.onSessionEvent((instanceId, event) => stream.applyEvent(instanceId, event));
217
- handler({ type: "session.error", sessionID: "sess_err2" });
218
-
219
- expect(stream.instances.get("bgr_err2")!.status).toBe("failed");
220
- expect(stream.instances.get("bgr_err2")!.error).toBe("session error");
221
- });
222
- });
223
-
224
- // ---------------------------------------------------------------------------
225
- // EventMessagePartUpdated: toolCallCount (HIGH-12)
226
- // ---------------------------------------------------------------------------
227
-
228
- describe("EventMessagePartUpdated — toolCallCount", () => {
229
- it("increments toolCallCount for each tool part (HIGH-12)", () => {
230
- const stream = new FakeEventStream();
231
- stream.setInstance({ instanceId: "bgr_tool", sessionId: "sess_tool", status: "running", toolCallCount: 0 });
232
- stream.register("bgr_tool", "sess_tool");
233
-
234
- const handler = stream.onSessionEvent((instanceId, event) => stream.applyEvent(instanceId, event));
235
-
236
- handler({
237
- type: "message.part.updated",
238
- sessionID: "sess_tool",
239
- part: { type: "tool", state: { status: "ok" } },
240
- });
241
- handler({
242
- type: "message.part.updated",
243
- sessionID: "sess_tool",
244
- part: { type: "tool", state: { status: "ok" } },
245
- });
246
-
247
- expect(stream.instances.get("bgr_tool")!.toolCallCount).toBe(2);
248
- });
249
-
250
- it("non-tool parts do not increment toolCallCount", () => {
251
- const stream = new FakeEventStream();
252
- stream.setInstance({ instanceId: "bgr_text", sessionId: "sess_text", status: "running", toolCallCount: 0 });
253
- stream.register("bgr_text", "sess_text");
254
-
255
- const handler = stream.onSessionEvent((instanceId, event) => stream.applyEvent(instanceId, event));
256
- handler({
257
- type: "message.part.updated",
258
- sessionID: "sess_text",
259
- part: { type: "text", text: "hello" } as unknown as EventMessagePartUpdated["part"],
260
- });
261
-
262
- expect(stream.instances.get("bgr_text")!.toolCallCount).toBe(0);
263
- });
264
- });
265
-
266
- // ---------------------------------------------------------------------------
267
- // Threshold-12 loop guard capture (HIGH-17)
268
- // ---------------------------------------------------------------------------
269
-
270
- describe("threshold-12 loop guard capture (HIGH-17)", () => {
271
- it("captures loopGuardTool and error on threshold-12 throw", () => {
272
- const stream = new FakeEventStream();
273
- stream.setInstance({ instanceId: "bgr_loop", sessionId: "sess_loop", status: "running", toolCallCount: 0 });
274
- stream.register("bgr_loop", "sess_loop");
275
-
276
- const handler = stream.onSessionEvent((instanceId, event) => stream.applyEvent(instanceId, event));
277
- handler({
278
- type: "message.part.updated",
279
- sessionID: "sess_loop",
280
- part: {
281
- type: "tool",
282
- state: {
283
- status: "error",
284
- error: "Loop protection: 12 identical calls to read",
285
- },
286
- },
287
- });
288
-
289
- const inst = stream.instances.get("bgr_loop")!;
290
- expect(inst.loopGuardTool).toBe("read");
291
- expect(inst.error).toBe("Loop protection: 12 identical calls to read");
292
- expect(inst.status).toBe("failed");
293
- });
294
-
295
- it("loopGuardTool is captured once even if multiple threshold-12 events arrive", () => {
296
- const stream = new FakeEventStream();
297
- stream.setInstance({ instanceId: "bgr_loop2", sessionId: "sess_loop2", status: "running", toolCallCount: 0 });
298
- stream.register("bgr_loop2", "sess_loop2");
299
-
300
- const handler = stream.onSessionEvent((instanceId, event) => stream.applyEvent(instanceId, event));
301
- handler({
302
- type: "message.part.updated",
303
- sessionID: "sess_loop2",
304
- part: {
305
- type: "tool",
306
- state: { status: "error", error: "Loop protection: 12 identical calls to read" },
307
- },
308
- });
309
-
310
- // Second event — should not overwrite
311
- handler({
312
- type: "message.part.updated",
313
- sessionID: "sess_loop2",
314
- part: {
315
- type: "tool",
316
- state: { status: "error", error: "Loop protection: 12 identical calls to read" },
317
- },
318
- });
319
-
320
- expect(stream.instances.get("bgr_loop2")!.loopGuardTool).toBe("read");
321
- expect(stream.instances.get("bgr_loop2")!.toolCallCount).toBe(2); // count still increments
322
- });
323
-
324
- it("threshold-5/8 do NOT set loopGuardTool (only threshold-12 triggers it)", () => {
325
- const stream = new FakeEventStream();
326
- stream.setInstance({ instanceId: "bgr_warn", sessionId: "sess_warn", status: "running", toolCallCount: 0 });
327
- stream.register("bgr_warn", "sess_warn");
328
-
329
- const handler = stream.onSessionEvent((instanceId, event) => stream.applyEvent(instanceId, event));
330
- handler({
331
- type: "message.part.updated",
332
- sessionID: "sess_warn",
333
- part: {
334
- type: "tool",
335
- state: { status: "warn" }, // threshold 5 or 8 doesn't have the same error string
336
- },
337
- });
338
-
339
- expect(stream.instances.get("bgr_warn")!.loopGuardTool).toBeUndefined();
340
- expect(stream.instances.get("bgr_warn")!.status).toBe("running");
341
- });
342
- });
343
-
344
- // ---------------------------------------------------------------------------
345
- // SSE reconnect with backoff
346
- // ---------------------------------------------------------------------------
347
-
348
- describe("SSE reconnect with backoff", () => {
349
- it("reconnects with exponential backoff: 1s, 2s, 4s, max 30s", () => {
350
- const delays: number[] = [];
351
- let attempt = 0;
352
- const maxDelay = 30_000;
353
-
354
- while (attempt < 5) {
355
- const delay = Math.min(1000 * Math.pow(2, attempt), maxDelay);
356
- delays.push(delay);
357
- attempt++;
358
- }
359
-
360
- expect(delays).toEqual([1000, 2000, 4000, 8000, 16000]);
361
- });
362
-
363
- it("max backoff is 30 seconds", () => {
364
- const maxDelay = 30_000;
365
- const delay = Math.min(1000 * Math.pow(2, 10), maxDelay);
366
- expect(delay).toBe(30_000);
367
- });
368
- });
369
-
370
- // ---------------------------------------------------------------------------
371
- // dispose closes the SSE stream
372
- // ---------------------------------------------------------------------------
373
-
374
- describe("dispose closes SSE stream", () => {
375
- it("close() marks stream as closed and clears sessions", () => {
376
- const stream = new FakeEventStream();
377
- stream.register("bgr_1", "sess_1");
378
- stream.register("bgr_2", "sess_2");
379
-
380
- expect(stream.isClosed).toBe(false);
381
- stream.close();
382
- expect(stream.isClosed).toBe(true);
383
- });
384
-
385
- it("events after close are ignored", () => {
386
- const stream = new FakeEventStream();
387
- stream.setInstance({ instanceId: "bgr_close", sessionId: "sess_close", status: "running", toolCallCount: 0 });
388
- stream.register("bgr_close", "sess_close");
389
- stream.close();
390
-
391
- const handler = stream.onSessionEvent((instanceId, event) => stream.applyEvent(instanceId, event));
392
- handler({ type: "session.idle", sessionID: "sess_close" });
393
-
394
- // Status should still be running because event was ignored after close
395
- expect(stream.instances.get("bgr_close")!.status).toBe("running");
396
- });
397
- });
398
-
399
- // ---------------------------------------------------------------------------
400
- // EventStream interface contract
401
- // ---------------------------------------------------------------------------
402
-
403
- describe("EventStream interface contract", () => {
404
- it("has connect, disconnect, onSessionEvent methods", () => {
405
- const stream = new FakeEventStream();
406
- expect(typeof stream.onSessionEvent).toBe("function");
407
- expect(typeof stream.close).toBe("function");
408
- });
409
- });
@@ -1,262 +0,0 @@
1
- /**
2
- * event.test.ts
3
- *
4
- * Tests for the canonical session lifecycle per §4.5.1 (v0.3.1 N4 fix).
5
- *
6
- * Verifies:
7
- * 1. session.created does NOT create the state file — only updates in-memory seen-message set
8
- * 2. Duplicate session.created events are a no-op (no extra file, no error)
9
- * 3. First chat.message per session creates the state file with §4.7 schema
10
- * 4. First tool.execute.before (subagent-only lazy fallback) creates the state file with
11
- * parentAgent: null when chat.message has not yet fired
12
- *
13
- * Per MEDIUM finding 28 / v0.3.1 §12.1.
14
- */
15
-
16
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
17
- import { mkdirSync, rmSync, existsSync, readFileSync } from "node:fs";
18
- import path from "node:path";
19
- import os from "node:os";
20
-
21
- // ── Minimal interfaces that mirror what index.ts will define ────────────────
22
-
23
- /** In-memory seen-message set maintained by the plugin per session */
24
- const seenMessageIds = new Map<string, Set<string>>();
25
-
26
- /** Tracks whether the state file has been created for a session */
27
- const stateFileCreated = new Map<string, boolean>();
28
-
29
- /**
30
- * Minimal mock of the plugin's session state — simulates the event hook
31
- * behavior described in §4.5.1 without requiring the full index.ts.
32
- */
33
- class MockPlugin {
34
- private stateDir: string;
35
- private seenMessages: Map<string, Set<string>>;
36
-
37
- constructor(stateDir: string) {
38
- this.stateDir = stateDir;
39
- this.seenMessages = seenMessageIds;
40
- }
41
-
42
- /** session.created — does NOT create state file; only updates in-memory set */
43
- onSessionCreated(sessionId: string): void {
44
- if (!this.seenMessages.has(sessionId)) {
45
- this.seenMessages.set(sessionId, new Set());
46
- }
47
- // Does NOT call createStateFile
48
- }
49
-
50
- /** chat.message — first message per session creates state file with §4.7 schema */
51
- onChatMessage(sessionId: string, messageId: string, sender: string): void {
52
- if (!this.seenMessages.has(sessionId)) {
53
- this.seenMessages.set(sessionId, new Set());
54
- }
55
- const seen = this.seenMessages.get(sessionId)!;
56
- if (seen.has(messageId)) return; // duplicate — no-op
57
- seen.add(messageId);
58
-
59
- // First message for this session → create state file
60
- if (seen.size === 1) {
61
- this.createStateFile(sessionId, sender);
62
- }
63
- }
64
-
65
- /**
66
- * tool.execute.before — subagent-only lazy fallback.
67
- * Creates state file with parentAgent: null if chat.message has not fired yet.
68
- */
69
- onToolExecuteBefore(sessionId: string): void {
70
- const seen = this.seenMessages.get(sessionId);
71
- if (!seen || seen.size === 0) {
72
- // chat.message hasn't fired → lazy fallback with parentAgent: null
73
- this.createStateFile(sessionId, null);
74
- }
75
- }
76
-
77
- /** session.deleted — removes state file */
78
- onSessionDeleted(sessionId: string): void {
79
- this.seenMessages.delete(sessionId);
80
- const filePath = path.join(this.stateDir, `${sessionId}.json`);
81
- try {
82
- const { unlinkSync } = require("node:fs");
83
- unlinkSync(filePath);
84
- } catch {
85
- // non-fatal if already gone
86
- }
87
- }
88
-
89
- /** Unknown event type — no-op */
90
- onUnknownEvent(): void {
91
- // no state file created, no error
92
- }
93
-
94
- private createStateFile(sessionId: string, parentAgent: string | null): void {
95
- const filePath = path.join(this.stateDir, `${sessionId}.json`);
96
- const state = {
97
- sessionId,
98
- parentAgent,
99
- startedAt: parentAgent !== null ? Date.now() : 0,
100
- lastActivityAt: parentAgent !== null ? Date.now() : 0,
101
- turnCount: 0,
102
- toolCalls: [],
103
- warningsIssued: 0,
104
- blocksTriggered: 0,
105
- };
106
- const { writeFileSync } = require("node:fs");
107
- writeFileSync(filePath, JSON.stringify(state), "utf8");
108
- stateFileCreated.set(sessionId, true);
109
- }
110
-
111
- getSeenMessages(sessionId: string): Set<string> {
112
- return this.seenMessages.get(sessionId) ?? new Set();
113
- }
114
-
115
- stateFileExists(sessionId: string): boolean {
116
- return existsSync(path.join(this.stateDir, `${sessionId}.json`));
117
- }
118
-
119
- readStateFile(sessionId: string): ReturnType<typeof readFileSync> | null {
120
- const filePath = path.join(this.stateDir, `${sessionId}.json`);
121
- if (!existsSync(filePath)) return null;
122
- return readFileSync(filePath, "utf8");
123
- }
124
- }
125
-
126
- // ── Test setup ───────────────────────────────────────────────────────────────
127
-
128
- const TEST_DIR = path.join(os.tmpdir(), "bizar-event-test");
129
- const TEST_SESSION = "session-evt-001";
130
- const TEST_SESSION_2 = "session-evt-002";
131
-
132
- describe("event.test.ts — canonical session lifecycle", () => {
133
- let plugin: MockPlugin;
134
-
135
- beforeEach(() => {
136
- try { rmSync(TEST_DIR, { recursive: true, force: true }); } catch { /* ok */ }
137
- mkdirSync(TEST_DIR, { recursive: true });
138
- plugin = new MockPlugin(TEST_DIR);
139
- seenMessageIds.clear();
140
- stateFileCreated.clear();
141
- });
142
-
143
- afterEach(() => {
144
- try { rmSync(TEST_DIR, { recursive: true, force: true }); } catch { /* ok */ }
145
- });
146
-
147
- // ── §4.5.1 / v0.3.1 N4 fix ────────────────────────────────────────────────
148
-
149
- test("session.created does NOT create the state file", () => {
150
- plugin.onSessionCreated(TEST_SESSION);
151
- expect(plugin.stateFileExists(TEST_SESSION)).toBe(false);
152
- });
153
-
154
- test("session.created only updates the in-memory seen-message set", () => {
155
- plugin.onSessionCreated(TEST_SESSION);
156
- expect(plugin.getSeenMessages(TEST_SESSION).size).toBe(0); // empty set created
157
- expect(plugin.stateFileExists(TEST_SESSION)).toBe(false);
158
- });
159
-
160
- test("duplicate session.created events are a no-op — no extra file, no error", () => {
161
- plugin.onSessionCreated(TEST_SESSION);
162
- plugin.onSessionCreated(TEST_SESSION);
163
- plugin.onSessionCreated(TEST_SESSION);
164
- expect(plugin.stateFileExists(TEST_SESSION)).toBe(false);
165
- // Should not throw
166
- });
167
-
168
- test("first chat.message per session creates the state file with §4.7 schema", () => {
169
- plugin.onChatMessage(TEST_SESSION, "msg-001", "odin");
170
-
171
- expect(plugin.stateFileExists(TEST_SESSION)).toBe(true);
172
-
173
- const raw = plugin.readStateFile(TEST_SESSION);
174
- expect(raw).not.toBeNull();
175
- const state = JSON.parse(raw!.toString());
176
-
177
- expect(state.sessionId).toBe(TEST_SESSION);
178
- expect(state.parentAgent).toBe("odin");
179
- expect(state.startedAt).toBeGreaterThan(0);
180
- expect(state.lastActivityAt).toBeGreaterThan(0);
181
- expect(state.turnCount).toBe(0);
182
- expect(state.toolCalls).toHaveLength(0);
183
- expect(state.warningsIssued).toBe(0);
184
- expect(state.blocksTriggered).toBe(0);
185
- });
186
-
187
- test("first tool.execute.before (subagent-only lazy fallback) creates state file with parentAgent: null", () => {
188
- // No chat.message has fired — simulate a subagent-only session
189
- plugin.onToolExecuteBefore(TEST_SESSION);
190
-
191
- expect(plugin.stateFileExists(TEST_SESSION)).toBe(true);
192
-
193
- const raw = plugin.readStateFile(TEST_SESSION);
194
- expect(raw).not.toBeNull();
195
- const state = JSON.parse(raw!.toString());
196
-
197
- expect(state.sessionId).toBe(TEST_SESSION);
198
- expect(state.parentAgent).toBeNull();
199
- expect(state.startedAt).toBe(0); // §4.7: epoch zero for lazy fallback
200
- expect(state.lastActivityAt).toBe(0);
201
- expect(state.toolCalls).toHaveLength(0);
202
- });
203
-
204
- test("chat.message followed by tool.execute.before does NOT create duplicate state file", () => {
205
- plugin.onChatMessage(TEST_SESSION, "msg-001", "odin");
206
- const firstRead = plugin.readStateFile(TEST_SESSION);
207
-
208
- plugin.onToolExecuteBefore(TEST_SESSION);
209
- const secondRead = plugin.readStateFile(TEST_SESSION);
210
-
211
- // Same content — no duplicate write
212
- expect(firstRead).toBe(secondRead);
213
- });
214
-
215
- test("session.deleted removes the state file", () => {
216
- plugin.onChatMessage(TEST_SESSION, "msg-001", "odin");
217
- expect(plugin.stateFileExists(TEST_SESSION)).toBe(true);
218
-
219
- plugin.onSessionDeleted(TEST_SESSION);
220
- expect(plugin.stateFileExists(TEST_SESSION)).toBe(false);
221
- });
222
-
223
- test("unknown event types are no-ops — no state file created, no error", () => {
224
- plugin.onUnknownEvent();
225
- expect(plugin.stateFileExists(TEST_SESSION)).toBe(false);
226
- // Should not throw
227
- });
228
-
229
- test("second chat.message for same session does NOT create new state file", () => {
230
- plugin.onChatMessage(TEST_SESSION, "msg-001", "odin");
231
- const firstContent = plugin.readStateFile(TEST_SESSION);
232
-
233
- plugin.onChatMessage(TEST_SESSION, "msg-002", "odin");
234
- const secondContent = plugin.readStateFile(TEST_SESSION);
235
-
236
- expect(firstContent).toBe(secondContent);
237
- });
238
-
239
- test("different sessions have independent state files", () => {
240
- plugin.onChatMessage(TEST_SESSION, "msg-001", "odin");
241
- plugin.onChatMessage(TEST_SESSION_2, "msg-002", "thor");
242
-
243
- const stateA = JSON.parse(plugin.readStateFile(TEST_SESSION)!.toString());
244
- const stateB = JSON.parse(plugin.readStateFile(TEST_SESSION_2)!.toString());
245
-
246
- expect(stateA.parentAgent).toBe("odin");
247
- expect(stateB.parentAgent).toBe("thor");
248
- expect(stateA.sessionId).toBe(TEST_SESSION);
249
- expect(stateB.sessionId).toBe(TEST_SESSION_2);
250
- });
251
-
252
- test("duplicate chat.message (same messageId) is a no-op", () => {
253
- plugin.onChatMessage(TEST_SESSION, "msg-001", "odin");
254
- const content1 = plugin.readStateFile(TEST_SESSION);
255
-
256
- // Same messageId again — no-op
257
- plugin.onChatMessage(TEST_SESSION, "msg-001", "odin");
258
- const content2 = plugin.readStateFile(TEST_SESSION);
259
-
260
- expect(content1).toBe(content2);
261
- });
262
- });