@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,641 +0,0 @@
1
- /**
2
- * background-state.ts
3
- *
4
- * Per-instance state management for background agents (v0.4.2 spec §3).
5
- *
6
- * Why a separate file from `state.ts` (spec §3.1):
7
- * - The existing per-session `SessionState` is keyed on `sessionId` and
8
- * contains loop-detection fields tied to the cline session lifecycle.
9
- * - Background instances have a different lifecycle: `instanceId`
10
- * (plugin-generated `bgr_<ulid>`), spawn source, parent, model override,
11
- * terminal status separate from session status.
12
- * - Coupling these into `SessionState` would require 8 new fields on a
13
- * schema that already has 8 existing fields — exactly the field
14
- * proliferation Forseti flagged.
15
- * - The new `BackgroundState` lives at
16
- * `~/.cache/bizar/bg/<instanceId>.json`. The existing `state.ts`
17
- * is unchanged.
18
- *
19
- * Concurrency model (spec §3.3):
20
- * - Per-instance async mutex, keyed on `instanceId` (NOT on `sessionId`).
21
- * - Independent of the per-session mutex in `state.ts` — they do not
22
- * block each other.
23
- *
24
- * File I/O:
25
- * - Atomic write via `writeFile(tmp)` + `renameSync(tmp, final)`.
26
- * - Best-effort cleanup of `.tmp` on failure.
27
- * - Corrupt JSON → `load()` returns null; caller decides what to do
28
- * (logger warning, skip, etc.).
29
- */
30
-
31
- import {
32
- readFileSync,
33
- writeFileSync,
34
- renameSync,
35
- unlinkSync,
36
- existsSync,
37
- mkdirSync,
38
- readdirSync,
39
- statSync,
40
- } from "node:fs";
41
- import path from "node:path";
42
- import os from "node:os";
43
-
44
- // --- Public types (spec §3.2) ---------------------------------------------
45
-
46
- /**
47
- * v5.x — Per-tool-call history entry. One row per tool invocation.
48
- * `result` is the truncated text returned by the tool; `error` is the
49
- * error message if the tool threw.
50
- *
51
- * Persistence: stored as `toolCalls` on `BackgroundState`, capped at
52
- * {@link MAX_TOOL_CALL_HISTORY} entries to keep the JSON state file
53
- * small. The `args` and `result` fields are truncated to
54
- * {@link MAX_TOOL_ARG_CHARS} characters each.
55
- */
56
- export interface ToolCallEntry {
57
- /** Plugin-generated tool call id. */
58
- id: string;
59
- /** Tool name (e.g. "bash", "edit", "read"). */
60
- name: string;
61
- /** JSON-serialized args (truncated). */
62
- args: string;
63
- /** "running" while the tool is in-flight; "ok" or "error" after. */
64
- status: "running" | "ok" | "error";
65
- /** Epoch ms when the tool call started. */
66
- startedAt: number;
67
- /** Epoch ms when the tool call completed (omitted while running). */
68
- endedAt?: number;
69
- /** Result preview (truncated). */
70
- result?: string;
71
- /** Error message (if status === "error"). */
72
- error?: string;
73
- }
74
-
75
- /** v5.x — Cap on how many tool calls we remember per instance. */
76
- export const MAX_TOOL_CALL_HISTORY = 100;
77
- /** v5.x — Cap on per-tool args/result length (chars). */
78
- export const MAX_TOOL_ARG_CHARS = 1_000;
79
-
80
- /**
81
- * Background instance status. Maps cline events and lifecycle transitions
82
- * to a small, stable set of terminal and in-flight states.
83
- *
84
- * v5.x — `paused` added for pause/resume support. `steered` added for
85
- * mid-flight redirect (kill + restart with new prompt). Both are
86
- * informational and don't change the wire-state contract: `paused` is
87
- * a non-terminal in-flight status (the underlying subprocess is alive
88
- * but suspended), and `steered` is treated as terminal for cleanup
89
- * purposes because the original subprocess has exited.
90
- */
91
- export type BackgroundStatus =
92
- | "pending"
93
- | "running"
94
- | "paused"
95
- | "done"
96
- | "failed"
97
- | "killed"
98
- | "timed_out"
99
- | "steered";
100
-
101
- /**
102
- * Per-instance state for a background agent.
103
- * Field-by-field notes (spec §3.2):
104
- * - `instanceId` — plugin identifier, `bgr_<ulid>`. Returned to callers.
105
- * Used as the filename stem.
106
- * - `sessionId` — cline session ID returned from POST /session. Used
107
- * for all HTTP calls. Also the key for the existing per-session state,
108
- * log, and loop detection.
109
- * - `status` — see {@link BackgroundStatus} for the lifecycle.
110
- * - `startedAt` — epoch ms when the instance was added.
111
- * - `completedAt` — epoch ms when the instance reached a terminal state.
112
- * - `model` — resolved model ID, `"<providerID>/<modelID>"` format.
113
- * - `promptPreview` — first 200 chars of the prompt.
114
- * - `resultPreview` — last 200 chars of result, refreshed on
115
- * `EventMessagePartUpdated` for assistant text parts.
116
- * - `resultMessageIds` — incrementally populated as
117
- * `EventMessagePartUpdated` events arrive. The full text is NOT
118
- * stored; reconstructed on `bizar_collect`.
119
- * - `error` — set on terminal failure; cleared on retry from
120
- * `failed` to `running` (retry is not in v0.4).
121
- * - `parentAgent` — who spawned it (always "odin" in v0.4 per §6.3).
122
- * - `parentInstanceId` — for nested spawns (reserved, not in v0.4).
123
- * - `logPath` — path to `~/.cache/bizar/logs/<sessionId>.log`.
124
- * - `toolCallCount` — updated via `EventMessagePartUpdated` events.
125
- * For tool call history with names/args/results, see `toolCalls`.
126
- * - `loopGuardTool` — set when threshold-12 throw is captured. Used at
127
- * `bizar_collect` to prepend the marker.
128
- * - `timeoutMs` — collect-time timeout requested by caller.
129
- * - `lastEventAt` — epoch ms when the most recent SSE event arrived for
130
- * this instance. Set on every event the manager observes (tool/text
131
- * part updates, session.idle, session.error, session.created/updated/
132
- * deleted). Used by the stall checker (§v0.3.0). Optional in the
133
- * type but always populated by `InstanceManager.add()` (the manager
134
- * seeds it from `startedAt`); older on-disk files written before
135
- * v0.3.0 are backfilled in `readState`.
136
- * - `lastToolOrTextAt` — epoch ms when the most recent `tool` or `text`
137
- * part arrived. `thinking` parts do NOT advance this timestamp; that
138
- * is the whole point — repeated `thinking` parts with no `tool` or
139
- * `text` is what we detect as a thinking loop (§v0.3.0).
140
- * - `interventionCount` — number of research interventions sent for
141
- * this instance. Reset to 0 when the agent makes progress (tool or
142
- * text part after an intervention). Default 0.
143
- * - `interventionAt` — epoch ms of the most recent intervention.
144
- * - `interventionReason` — short human-readable description of the
145
- * intervention, e.g. `"thinking loop (5m 12s without tool/text)"`.
146
- *
147
- * v0.5.5 — persistent auto-restart. These are typed as optional so
148
- * existing state files on disk remain valid after upgrade.
149
- * - `persistent` — when true, the manager auto-restarts on terminal
150
- * failure (up to maxRestarts). Default false.
151
- * - `restartCount` — number of times this instance has been
152
- * auto-restarted (not including the original spawn). Default 0.
153
- * - `maxRestarts` — cap; default 3.
154
- * - `lastRestartAt` — epoch ms of the most recent auto-restart.
155
- * - `processId` (v0.8.0) — PID of the cline run subprocess. Set
156
- * by the cline-runner after `Bun.spawn` returns. Optional
157
- * for backward compat.
158
- * - `exitCode` (v0.8.0) — exit code of the cline run
159
- * subprocess. Populated when the process exits.
160
- * - `runnerState` (v0.8.0) — free-form status from the runner
161
- * ("starting" | "running" | "done" | "failed" | "killed").
162
- * Allows the dashboard to show runner-level state separately
163
- * from the higher-level `status`.
164
- * - `runnerError` (v0.8.0) — cline run subprocess error (e.g.
165
- * "cline run exited with code 1").
166
- * - `spawnMessage` (v0.8.0) — "you can continue" message returned
167
- * to the LLM by the spawn tool. Surfaces in the dashboard.
168
- * - `spawnNextSteps` (v0.8.0) — list of next-step hints returned
169
- * to the LLM by the spawn tool.
170
- * - `sessionIdAt` (v0.8.0) — when the cline sessionId was
171
- * first observed in the subprocess stderr.
172
- * - `runnerStartedAt` / `runnerEndedAt` — when the subprocess
173
- * started / ended (subset of `startedAt`/`completedAt`).
174
- * - `spawnedAt` — when the bg-spawn tool recorded the instance.
175
- * Distinct from `startedAt` because the runner starts a
176
- * tick or two later.
177
- * - `exitSignal` — exit signal name (e.g. "SIGTERM",
178
- * "SIGKILL") if the subprocess was killed.
179
- */
180
- export interface BackgroundState {
181
- instanceId: string;
182
- sessionId: string;
183
- agent: string;
184
- status: BackgroundStatus;
185
- startedAt: number;
186
- completedAt?: number;
187
- model: string;
188
- promptPreview: string;
189
- resultPreview?: string;
190
- resultMessageIds?: string[];
191
- error?: string;
192
- parentAgent: string;
193
- parentInstanceId?: string;
194
- logPath: string;
195
- timeoutMs: number;
196
- toolCallCount: number;
197
- loopGuardTool?: string;
198
- /**
199
- * v5.x — Per-tool-call history. Records each tool invocation with its
200
- * args, status, timing, and (when available) the tool result or
201
- * error. Capped at the last 100 entries per instance (see
202
- * `MAX_TOOL_CALL_HISTORY`). Exposed via `InstanceView.toolCalls` so
203
- * the dashboard can render a tool-call list.
204
- */
205
- toolCalls?: ToolCallEntry[];
206
- /**
207
- * v5.x — Progress reporting. Set by `bizar_report_progress` from the
208
- * running agent. Range 0..100; -1 means "indeterminate". The
209
- * dashboard renders this as a progress bar.
210
- */
211
- progress?: number;
212
- /** v5.x — Free-form progress message from the agent. */
213
- progressMessage?: string;
214
- /** v5.x — When the progress was last updated. */
215
- progressAt?: number;
216
- /** v5.x — Pause timestamp (when the subprocess was SIGSTOPed). */
217
- pausedAt?: number;
218
- /** v5.x — Optional list of tags supplied at spawn time. */
219
- tags?: string[];
220
- // v0.3.0 — stall and thinking-loop protection. These are typed as
221
- // optional in the schema because (a) the field can be absent in older
222
- // files on disk, and (b) the `InstanceManager.add()` input (AddDraft)
223
- // does not require them — the manager seeds them itself.
224
- lastEventAt?: number;
225
- lastToolOrTextAt?: number;
226
- interventionCount?: number;
227
- interventionAt?: number;
228
- interventionReason?: string;
229
- // v0.5.5 — persistent auto-restart
230
- persistent?: boolean;
231
- restartCount?: number;
232
- maxRestarts?: number;
233
- lastRestartAt?: string;
234
- /**
235
- * Full original prompt text, stored so the instance can be restarted
236
- * with the same input. Optional for backward compat; always set on
237
- * new spawns from v0.5.5+.
238
- */
239
- prompt?: string;
240
- /**
241
- * Human-readable error from the last failed restart attempt.
242
- * Set only when `_maybeAutoRestart` calls `restart()` and it returns
243
- * `{ ok: false }`. Cleared on a successful restart.
244
- */
245
- restartError?: string;
246
- // v0.8.0 — process tracking (see cline-runner.ts).
247
- processId?: number;
248
- exitCode?: number;
249
- // v5.5.1 — `liveSession: true` means this instance is backed by an
250
- // cline serve SDK session managed by the dashboard (not by an OS
251
- // subprocess). The plugin's bg-kill/bg-pause/bg-resume tools check
252
- // this flag and delegate to the dashboard HTTP API instead of
253
- // touching the (now-stubbed) cline-runner. Optional for backward
254
- // compat — pre-v5.5.1 state files leave it unset / false.
255
- liveSession?: boolean;
256
- // v5.5.1 — instanceId assigned by the dashboard (may differ from the
257
- // plugin's locally-allocated `instanceId` when the dashboard mints its
258
- // own). Cross-reference for cross-system debugging.
259
- dashboardInstanceId?: string;
260
- runnerState?: string;
261
- runnerError?: string;
262
- spawnMessage?: string;
263
- spawnNextSteps?: string[];
264
- sessionIdAt?: number;
265
- runnerStartedAt?: number;
266
- runnerEndedAt?: number;
267
- spawnedAt?: number;
268
- exitSignal?: string;
269
- }
270
-
271
- /**
272
- * Partial state used during a `pending` insert, before the HTTP call has
273
- * assigned a `sessionId`. After the call, the full state is updated.
274
- *
275
- * Note: the spec contract (see task description) types `EMPTY_BACKGROUND_STATE`
276
- * as `Omit<BackgroundState, "instanceId" | "sessionId" | "agent" | "parentAgent"
277
- * | "logPath" | "timeoutMs" | "model" | "promptPreview" | "startedAt">`.
278
- */
279
- export const EMPTY_BACKGROUND_STATE: Omit<
280
- BackgroundState,
281
- | "instanceId"
282
- | "sessionId"
283
- | "agent"
284
- | "parentAgent"
285
- | "logPath"
286
- | "timeoutMs"
287
- | "model"
288
- | "promptPreview"
289
- | "startedAt"
290
- > = {
291
- status: "pending",
292
- toolCallCount: 0,
293
- // v0.3.0 — stall and thinking-loop protection defaults. The manager
294
- // seeds `lastEventAt` and `lastToolOrTextAt` from `Date.now()` at
295
- // `add()` time; we set them to 0 here so a draft shape remains valid
296
- // even if it is constructed without the manager (the manager always
297
- // overwrites them).
298
- lastEventAt: 0,
299
- lastToolOrTextAt: 0,
300
- interventionCount: 0,
301
- // v0.5.5 — persistent auto-restart defaults
302
- persistent: false,
303
- restartCount: 0,
304
- maxRestarts: 3,
305
- };
306
-
307
- /**
308
- * Terminal states per spec §1.6 / §4.4. Used by collect/await logic.
309
- *
310
- * v5.x — `steered` is treated as terminal because the original
311
- * subprocess has exited; a new instance is created to carry the
312
- * steered work. Collect on a steered instance returns whatever text
313
- * the subprocess emitted before the kill.
314
- */
315
- export const TERMINAL_STATUSES: ReadonlySet<BackgroundStatus> = new Set<BackgroundStatus>([
316
- "done",
317
- "failed",
318
- "killed",
319
- "timed_out",
320
- "steered",
321
- ]);
322
-
323
- // --- Logger interface -----------------------------------------------------
324
-
325
- /**
326
- * Minimal Logger interface — matches the shape in `state.ts` / `logger.ts`.
327
- * Synchronous, void-returning. We intentionally use a structural type so
328
- * the plugin's `Logger` (which has additional convenience methods) is
329
- * assignable.
330
- */
331
- export interface Logger {
332
- log(opts: { level: "debug" | "info" | "warn" | "error"; message: string }): void;
333
- debug(message: string): void;
334
- info(message: string): void;
335
- warn(message: string): void;
336
- error(message: string): void;
337
- }
338
-
339
- // --- File-path helpers ----------------------------------------------------
340
-
341
- function expandHome(p: string): string {
342
- if (p === "~") return os.homedir();
343
- if (p.startsWith("~/") || p.startsWith("~\\")) {
344
- return path.join(os.homedir(), p.slice(2));
345
- }
346
- return p;
347
- }
348
-
349
- function backgroundStateDir(stateDir: string): string {
350
- // Background state lives in a subdirectory to keep it isolated from the
351
- // per-session `state.ts` files (which sit directly in `stateDir`).
352
- return path.join(expandHome(stateDir), "bg");
353
- }
354
-
355
- function backgroundStateFilePath(stateDir: string, instanceId: string): string {
356
- return path.join(backgroundStateDir(stateDir), `${instanceId}.json`);
357
- }
358
-
359
- // --- Mutex (spec §3.3) ----------------------------------------------------
360
-
361
- /**
362
- * Per-instance async mutex, identical pattern to the one in `state.ts`
363
- * but keyed on `instanceId`. Each `BackgroundStateStore` has its own
364
- * lock domain.
365
- */
366
- async function withLock<T>(
367
- locks: Map<string, Promise<unknown>>,
368
- instanceId: string,
369
- fn: () => Promise<T>,
370
- ): Promise<T> {
371
- const prev = locks.get(instanceId) ?? Promise.resolve();
372
- const next = prev.then(fn, fn);
373
- locks.set(instanceId, next.catch(() => {}));
374
- return next;
375
- }
376
-
377
- // --- Directory bootstrap --------------------------------------------------
378
-
379
- /**
380
- * Recursive mkdir with EACCES/EROFS handling per spec §8.2 (pattern borrowed
381
- * from `state.ts` / `report.ts`).
382
- */
383
- function ensureDir(dir: string, logger: Logger): boolean {
384
- try {
385
- mkdirSync(dir, { recursive: true });
386
- return true;
387
- } catch (err: unknown) {
388
- const code = (err as NodeJS.ErrnoException).code;
389
- if (code === "EACCES" || code === "EROFS") {
390
- logger.log({
391
- level: "error",
392
- message: `bizar: cannot create background state directory ${dir}: ${String(err)}`,
393
- });
394
- return false;
395
- }
396
- throw err;
397
- }
398
- }
399
-
400
- // --- Read / write ---------------------------------------------------------
401
-
402
- /**
403
- * Atomic write: write to `.tmp` then rename. Best-effort cleanup of `.tmp`
404
- * on failure. Mirrors `state.ts` / `report.ts`.
405
- */
406
- function writeStateAtomic(
407
- filePath: string,
408
- state: BackgroundState,
409
- logger: Logger,
410
- ): void {
411
- const tmp = `${filePath}.tmp`;
412
- try {
413
- writeFileSync(tmp, JSON.stringify(state), "utf8");
414
- renameSync(tmp, filePath);
415
- } catch (err: unknown) {
416
- logger.log({
417
- level: "warn",
418
- message: `bizar: failed to write background state file ${filePath}: ${String(err)}`,
419
- });
420
- try {
421
- if (existsSync(tmp)) unlinkSync(tmp);
422
- } catch {
423
- // non-fatal
424
- }
425
- }
426
- }
427
-
428
- /**
429
- * Read & validate a single background state file. Returns null on missing
430
- * or corrupt input. Logs a warning on corrupt files.
431
- *
432
- * Backward compatibility (v0.3.0): state files written before the stall
433
- * and thinking-loop fields existed may not carry `lastEventAt` /
434
- * `lastToolOrTextAt` / `interventionCount`. We backfill them here from
435
- * `startedAt` so the stall and thinking-loop checkers have a sensible
436
- * baseline (and so a plugin restart does not immediately flag a
437
- * pre-existing live instance as stalled).
438
- */
439
- function readState(
440
- filePath: string,
441
- instanceId: string,
442
- logger: Logger,
443
- ): BackgroundState | null {
444
- if (!existsSync(filePath)) return null;
445
- try {
446
- const raw = readFileSync(filePath, "utf8");
447
- const parsed = JSON.parse(raw) as BackgroundState;
448
- if (
449
- typeof parsed.instanceId !== "string" ||
450
- typeof parsed.sessionId !== "string" ||
451
- typeof parsed.agent !== "string" ||
452
- typeof parsed.status !== "string"
453
- ) {
454
- throw new Error("schema mismatch");
455
- }
456
- // Backfill v0.3.0 fields for files written by older versions.
457
- if (typeof parsed.lastEventAt !== "number") {
458
- parsed.lastEventAt = parsed.startedAt;
459
- }
460
- if (typeof parsed.lastToolOrTextAt !== "number") {
461
- parsed.lastToolOrTextAt = parsed.startedAt;
462
- }
463
- if (typeof parsed.interventionCount !== "number") {
464
- parsed.interventionCount = 0;
465
- }
466
- // v0.5.5 — backfill persistent/restart fields for files written by
467
- // older versions. These are optional but the restarter needs them.
468
- if (typeof parsed.persistent !== "boolean") {
469
- parsed.persistent = false;
470
- }
471
- if (typeof parsed.restartCount !== "number") {
472
- parsed.restartCount = 0;
473
- }
474
- if (typeof parsed.maxRestarts !== "number") {
475
- parsed.maxRestarts = 3;
476
- }
477
- // v5.x — backfill tool call history and progress for old files.
478
- if (!Array.isArray(parsed.toolCalls)) {
479
- parsed.toolCalls = [];
480
- }
481
- if (typeof parsed.progress !== "number") {
482
- parsed.progress = 0;
483
- }
484
- return parsed;
485
- } catch (err: unknown) {
486
- logger.log({
487
- level: "warn",
488
- message: `bizar: corrupt background state file for instance ${instanceId}: ${String(err)}`,
489
- });
490
- return null;
491
- }
492
- }
493
-
494
- // --- Public store ---------------------------------------------------------
495
-
496
- /**
497
- * Persistent store for `BackgroundState`. File-backed, with a per-instance
498
- * mutex and best-effort corrupt-file handling.
499
- *
500
- * Public surface (the interface contract for Thor's tests):
501
- * - `load(instanceId)` — read one instance (or null if missing/corrupt).
502
- * - `save(state)` — atomic write.
503
- * - `list()` — read all instances in the bg directory.
504
- * - `cleanup(maxAgeDays)` — delete terminal instances older than the
505
- * threshold. Returns the count deleted.
506
- * - `withLock(instanceId, fn)` — per-instance async mutex.
507
- *
508
- * The directory is created lazily on first use. If creation fails, all
509
- * read/write operations are silent no-ops and the caller should disable
510
- * background agents for this session (spec §8.2).
511
- */
512
- export class BackgroundStateStore {
513
- private stateDir: string;
514
- private logger: Logger;
515
- private initialized = false;
516
- private locks = new Map<string, Promise<unknown>>();
517
- /** True if the bg directory became unusable (EACCES/EROFS on init). */
518
- private dirUsable: boolean | null = null;
519
-
520
- constructor(stateDir: string, logger: Logger) {
521
- this.stateDir = stateDir;
522
- this.logger = logger;
523
- }
524
-
525
- /**
526
- * Lazily ensure the bg directory exists. Caches the result so the
527
- * EACCES/EROFS branch is taken at most once per process.
528
- */
529
- private ensureDir(): boolean {
530
- if (this.dirUsable !== null) return this.dirUsable;
531
- const ok = ensureDir(backgroundStateDir(this.stateDir), this.logger);
532
- this.dirUsable = ok;
533
- this.initialized = true;
534
- return ok;
535
- }
536
-
537
- /** Resolve the bg directory for callers that need to enumerate it. */
538
- get bgDir(): string {
539
- return backgroundStateDir(this.stateDir);
540
- }
541
-
542
- /**
543
- * Load a single instance. Returns null if the file is missing or corrupt.
544
- * Does not throw. The corrupt-file warning is logged inside `readState`.
545
- */
546
- async load(instanceId: string): Promise<BackgroundState | null> {
547
- if (!this.ensureDir()) return null;
548
- const filePath = backgroundStateFilePath(this.stateDir, instanceId);
549
- return withLock(this.locks, instanceId, () =>
550
- Promise.resolve(readState(filePath, instanceId, this.logger)),
551
- );
552
- }
553
-
554
- /**
555
- * Persist a `BackgroundState` atomically.
556
- */
557
- async save(state: BackgroundState): Promise<void> {
558
- if (!this.ensureDir()) return;
559
- return withLock(this.locks, state.instanceId, () => this.saveUnlocked(state));
560
- }
561
-
562
- /**
563
- * Persist a `BackgroundState` without acquiring the per-instance mutex.
564
- *
565
- * Callers must already hold the lock for `state.instanceId`. This exists
566
- * for internal code paths such as `InstanceManager.update()` that need to
567
- * mutate in-memory state while holding the same lock; calling `save()`
568
- * there would re-enter the mutex and deadlock the promise chain.
569
- */
570
- saveUnlocked(state: BackgroundState): Promise<void> {
571
- if (!this.ensureDir()) return Promise.resolve();
572
- const filePath = backgroundStateFilePath(this.stateDir, state.instanceId);
573
- writeStateAtomic(filePath, state, this.logger);
574
- return Promise.resolve();
575
- }
576
-
577
- /**
578
- * Read every instance in the bg directory. Corrupt files are skipped
579
- * (warning already logged by `readState`). Best-effort; returns [] on
580
- * directory read error.
581
- */
582
- async list(): Promise<BackgroundState[]> {
583
- if (!this.ensureDir()) return [];
584
- const dir = backgroundStateDir(this.stateDir);
585
- let files: string[];
586
- try {
587
- files = readdirSync(dir).filter((f) => f.endsWith(".json"));
588
- } catch {
589
- return [];
590
- }
591
- const out: BackgroundState[] = [];
592
- for (const f of files) {
593
- const id = f.replace(/\.json$/, "");
594
- const filePath = path.join(dir, f);
595
- const state = readState(filePath, id, this.logger);
596
- if (state !== null) out.push(state);
597
- }
598
- return out;
599
- }
600
-
601
- /**
602
- * Delete terminal instances whose `completedAt` (or `startedAt` as a
603
- * fallback) is older than `maxAgeDays`. Returns the number of files
604
- * removed. Non-terminal instances are NEVER removed.
605
- */
606
- async cleanup(maxAgeDays: number): Promise<number> {
607
- if (!this.ensureDir()) return 0;
608
- const all = await this.list();
609
- const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000;
610
- const now = Date.now();
611
- let deleted = 0;
612
- for (const s of all) {
613
- if (!TERMINAL_STATUSES.has(s.status)) continue;
614
- const ts = s.completedAt ?? s.startedAt;
615
- if (now - ts <= maxAgeMs) continue;
616
- const filePath = backgroundStateFilePath(this.stateDir, s.instanceId);
617
- try {
618
- if (existsSync(filePath)) {
619
- unlinkSync(filePath);
620
- deleted += 1;
621
- }
622
- } catch (err: unknown) {
623
- this.logger.log({
624
- level: "warn",
625
- message: `bizar: failed to delete background state file ${filePath}: ${String(err)}`,
626
- });
627
- }
628
- }
629
- // touch statSync to silence the unused import if no other consumer
630
- void statSync;
631
- return deleted;
632
- }
633
-
634
- /**
635
- * Acquire the per-instance mutex. The callback is serialized with all
636
- * other state operations for the same `instanceId`.
637
- */
638
- withLock<T>(instanceId: string, fn: () => Promise<T>): Promise<T> {
639
- return withLock(this.locks, instanceId, fn);
640
- }
641
- }