@polderlabs/bizar 6.2.4 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/.claude/CLAUDE.md +15 -0
  2. package/.claude/agents/_shared/AGENT_BASELINE.md +168 -0
  3. package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
  4. package/.claude/agents/_shared/SKILLS.md +109 -0
  5. package/.claude/agents/agent-browser.md +80 -0
  6. package/.claude/agents/baldr.md +49 -0
  7. package/.claude/agents/forseti.md +51 -0
  8. package/.claude/agents/frigg.md +42 -0
  9. package/.claude/agents/heimdall.md +33 -0
  10. package/.claude/agents/hermod.md +53 -0
  11. package/.claude/agents/mimir.md +49 -0
  12. package/.claude/agents/odin.md +287 -0
  13. package/.claude/agents/quick.md +34 -0
  14. package/.claude/agents/semble-search.md +50 -0
  15. package/.claude/agents/thor.md +53 -0
  16. package/.claude/agents/tyr.md +56 -0
  17. package/.claude/agents/vidarr.md +54 -0
  18. package/.claude/agents/vor.md +53 -0
  19. package/.claude/commands/audit.md +25 -0
  20. package/.claude/commands/bizar.md +22 -0
  21. package/.claude/commands/explain.md +17 -0
  22. package/.claude/commands/init.md +29 -0
  23. package/.claude/commands/learn.md +44 -0
  24. package/.claude/commands/plan.md +35 -0
  25. package/.claude/commands/plow-through.md +50 -0
  26. package/.claude/commands/pr-review.md +49 -0
  27. package/.claude/commands/setup-provider.md +96 -0
  28. package/.claude/commands/tailscale-serve.md +100 -0
  29. package/.claude/commands/team.md +132 -0
  30. package/.claude/commands/test.md +62 -0
  31. package/.claude/commands/validate.md +68 -0
  32. package/.claude/commands/visual-plan.md +24 -0
  33. package/.claude/hooks/README.md +92 -0
  34. package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
  35. package/.claude/hooks/pretooluse-bash.mjs +81 -0
  36. package/.claude/hooks/pretooluse-editwrite.mjs +139 -0
  37. package/.claude/hooks/sessionend-recall.mjs +74 -0
  38. package/.claude/hooks/sessionstart-prime.mjs +80 -0
  39. package/.claude/hooks/userpromptsubmit-tag.mjs +80 -0
  40. package/.claude/settings.json +116 -0
  41. package/.claude/skills/9router/SKILL.md +80 -0
  42. package/.claude/skills/9router-chat/SKILL.md +73 -0
  43. package/.claude/skills/9router-embeddings/SKILL.md +69 -0
  44. package/.claude/skills/9router-image/SKILL.md +86 -0
  45. package/.claude/skills/9router-stt/SKILL.md +79 -0
  46. package/.claude/skills/9router-tts/SKILL.md +80 -0
  47. package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
  48. package/.claude/skills/9router-web-search/SKILL.md +91 -0
  49. package/.claude/skills/bizar/README.md +9 -0
  50. package/.claude/skills/bizar/SKILL.md +450 -0
  51. package/.claude/skills/cpp-coding-standards/README.md +28 -0
  52. package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
  53. package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
  54. package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
  55. package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  56. package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  57. package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  58. package/.claude/skills/cpp-testing/README.md +28 -0
  59. package/.claude/skills/cpp-testing/SKILL.md +304 -0
  60. package/.claude/skills/cpp-testing/references/coverage.md +370 -0
  61. package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
  62. package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  63. package/.claude/skills/cpp-testing/references/mocking.md +364 -0
  64. package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
  65. package/.claude/skills/cubesandbox/SKILL.md +148 -0
  66. package/.claude/skills/embedded-esp-idf/README.md +41 -0
  67. package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
  68. package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  69. package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
  70. package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  71. package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
  72. package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  73. package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  74. package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
  75. package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  76. package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  77. package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  78. package/.claude/skills/glyph/SKILL.md +163 -0
  79. package/.claude/skills/harness-engineering/SKILL.md +143 -0
  80. package/.claude/skills/lightrag/SKILL.md +81 -0
  81. package/.claude/skills/memory-protocol/SKILL.md +105 -0
  82. package/.claude/skills/obsidian/SKILL.md +306 -0
  83. package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
  84. package/.claude/skills/self-improvement/SKILL.md +64 -0
  85. package/README.md +87 -59
  86. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-B58aiJiE.js} +1 -1
  87. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-B58aiJiE.js.map} +1 -1
  88. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js → MobileChat-BJrqwVDd.js} +1 -1
  89. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-BJrqwVDd.js.map} +1 -1
  90. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js → MobileSettings-CEQNJNLJ.js} +1 -1
  91. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-CEQNJNLJ.js.map} +1 -1
  92. package/bizar-dash/dist/assets/{main-DYiZqMrn.js → main-IvfQAOfy.js} +1 -1
  93. package/bizar-dash/dist/assets/{main-DYiZqMrn.js.map → main-IvfQAOfy.js.map} +1 -1
  94. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js → markdown-tOLaD6nm.js} +1 -1
  95. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-tOLaD6nm.js.map} +1 -1
  96. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js → mobile-DYCHcUpq.js} +1 -1
  97. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DYCHcUpq.js.map} +1 -1
  98. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js → mobile-layout-CBHjpwsb.js} +2 -2
  99. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js.map → mobile-layout-CBHjpwsb.js.map} +1 -1
  100. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js → useSlashCommands-Bd7_FA6U.js} +2 -2
  101. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js.map → useSlashCommands-Bd7_FA6U.js.map} +1 -1
  102. package/bizar-dash/dist/assets/{vendor-CeHGtduv.js → vendor-C843201K.js} +12 -12
  103. package/bizar-dash/dist/assets/vendor-C843201K.js.map +1 -0
  104. package/bizar-dash/dist/index.html +6 -6
  105. package/bizar-dash/dist/mobile.html +2 -2
  106. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  107. package/bizar-dash/src/server/api.mjs +4 -4
  108. package/bizar-dash/src/server/background-store.mjs +15 -16
  109. package/bizar-dash/src/server/bg-poller.mjs +25 -35
  110. package/bizar-dash/src/server/bg-retry.mjs +66 -194
  111. package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
  112. package/bizar-dash/src/server/claude-info.mjs +412 -0
  113. package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
  114. package/bizar-dash/src/server/claude-sdk.mjs +191 -0
  115. package/bizar-dash/src/server/providers-store.mjs +22 -49
  116. package/bizar-dash/src/server/routes/background.mjs +24 -20
  117. package/bizar-dash/src/server/routes/chat.mjs +223 -326
  118. package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
  119. package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
  120. package/bizar-dash/src/server/routes/tasks.mjs +20 -28
  121. package/bizar-dash/src/server/task-delegator.mjs +76 -77
  122. package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
  123. package/bizar-dash/src/web/views/Chat.tsx +8 -8
  124. package/cli/bin.mjs +31 -18
  125. package/cli/commands/claude-cmd.mjs +348 -0
  126. package/cli/commands/install.mjs +12 -11
  127. package/cli/commands/sandbox.mjs +220 -0
  128. package/cli/commands/validate.mjs +252 -288
  129. package/cli/dev-link.test.mjs +11 -11
  130. package/cli/doctor.mjs +131 -213
  131. package/cli/install.mjs +33 -37
  132. package/cli/install.test.mjs +9 -9
  133. package/cli/provision-claude.mjs +893 -0
  134. package/cli/provision.mjs +330 -226
  135. package/cli/provision.test.mjs +102 -0
  136. package/cli/utils.mjs +72 -58
  137. package/config/AGENTS.md +28 -28
  138. package/config/agents/_shared/AGENT_BASELINE.md +104 -684
  139. package/config/skills/bizar/SKILL.md +197 -0
  140. package/config/skills/cubesandbox/SKILL.md +148 -0
  141. package/config/skills/harness-engineering/SKILL.md +142 -0
  142. package/install.sh +66 -44
  143. package/package.json +19 -11
  144. package/packages/sdk/ARCHITECTURE.md +40 -36
  145. package/packages/sdk/package-lock.json +32 -0
  146. package/packages/sdk/package.json +15 -5
  147. package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
  148. package/packages/sdk/src/fingerprint.ts +0 -0
  149. package/packages/sdk/src/index.ts +38 -73
  150. package/packages/sdk/src/mcp/bin.ts +51 -0
  151. package/packages/sdk/src/mcp/server.ts +498 -0
  152. package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
  153. package/packages/sdk/tests/sdk.test.mjs +148 -0
  154. package/packages/sdk/vitest.config.ts +1 -1
  155. package/scripts/bh-full-e2e.mjs +166 -363
  156. package/scripts/check-deps.mjs +28 -46
  157. package/scripts/mirror-claude-md.sh +78 -0
  158. package/scripts/test-in-container.sh +157 -0
  159. package/templates/clean-state-checklist.md +3 -3
  160. package/templates/sprint-contract.md +2 -2
  161. package/bizar-dash/.bizar/activity.log +0 -3
  162. package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
  163. package/bizar-dash/.omx/state/session.json +0 -10
  164. package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
  165. package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
  166. package/bizar-dash/bizar-design/canvas.html +0 -281
  167. package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
  168. package/bizar-dash/bizar-design/components.css +0 -1665
  169. package/bizar-dash/bizar-design/components.html +0 -557
  170. package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
  171. package/bizar-dash/bizar-design/desktop/agents.html +0 -74
  172. package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
  173. package/bizar-dash/bizar-design/desktop/memory.html +0 -75
  174. package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
  175. package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
  176. package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
  177. package/bizar-dash/bizar-design/desktop/overview.html +0 -95
  178. package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
  179. package/bizar-dash/bizar-design/desktop/settings.html +0 -70
  180. package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
  181. package/bizar-dash/bizar-design/icons.svg +0 -44
  182. package/bizar-dash/bizar-design/index.html +0 -192
  183. package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
  184. package/bizar-dash/bizar-design/mobile/agents.html +0 -29
  185. package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
  186. package/bizar-dash/bizar-design/mobile/memory.html +0 -28
  187. package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
  188. package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
  189. package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
  190. package/bizar-dash/bizar-design/mobile/overview.html +0 -33
  191. package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
  192. package/bizar-dash/bizar-design/mobile/settings.html +0 -28
  193. package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
  194. package/bizar-dash/bizar-design/prototype.js +0 -114
  195. package/bizar-dash/bizar-design/tokens.css +0 -106
  196. package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
  197. package/bizar-dash/node_modules/.package-lock.json +0 -6
  198. package/bizar-dash/package-lock.json +0 -6
  199. package/bizar-dash/src/server/cline-sdk.mjs +0 -132
  200. package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
  201. package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
  202. package/cli/commands/cline-cmd.mjs +0 -289
  203. package/cli/commands/validate.test.mjs +0 -344
  204. package/cli/doctor.test.mjs +0 -350
  205. package/config/cline.json.template +0 -342
  206. package/packages/sdk/src/client.ts +0 -188
  207. package/packages/sdk/src/cline-events.ts +0 -134
  208. package/packages/sdk/src/cline-types.ts +0 -66
  209. package/packages/sdk/src/cline.ts +0 -339
  210. package/packages/sdk/src/errors.ts +0 -129
  211. package/packages/sdk/src/events.ts +0 -153
  212. package/packages/sdk/src/types.ts +0 -176
  213. package/packages/sdk/tests/client.test.ts +0 -217
  214. package/packages/sdk/tests/errors.test.ts +0 -108
  215. package/packages/sdk/tests/events.test.ts +0 -139
  216. package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
  217. package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
  218. package/plugins/bizar/ARCHITECTURE.md +0 -142
  219. package/plugins/bizar/CONSTRAINTS.md +0 -67
  220. package/plugins/bizar/LICENSE +0 -21
  221. package/plugins/bizar/README.md +0 -448
  222. package/plugins/bizar/index.ts +0 -872
  223. package/plugins/bizar/package.json +0 -39
  224. package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
  225. package/plugins/bizar/src/background-state.ts +0 -641
  226. package/plugins/bizar/src/background.ts +0 -1806
  227. package/plugins/bizar/src/cline-runner.ts +0 -203
  228. package/plugins/bizar/src/clineruntime.ts +0 -227
  229. package/plugins/bizar/src/commands-impl.ts +0 -151
  230. package/plugins/bizar/src/commands.ts +0 -1799
  231. package/plugins/bizar/src/compaction.d.mts +0 -48
  232. package/plugins/bizar/src/compaction.mjs +0 -192
  233. package/plugins/bizar/src/dashboard-client.ts +0 -233
  234. package/plugins/bizar/src/event-stream.ts +0 -606
  235. package/plugins/bizar/src/fingerprint.ts +0 -120
  236. package/plugins/bizar/src/handoff.ts +0 -79
  237. package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
  238. package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
  239. package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
  240. package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
  241. package/plugins/bizar/src/http-client.ts +0 -467
  242. package/plugins/bizar/src/key-rotation.ts +0 -218
  243. package/plugins/bizar/src/logger.ts +0 -144
  244. package/plugins/bizar/src/loop-engineering.ts +0 -241
  245. package/plugins/bizar/src/loop.ts +0 -176
  246. package/plugins/bizar/src/mistake-recovery.ts +0 -98
  247. package/plugins/bizar/src/odin.ts +0 -227
  248. package/plugins/bizar/src/options.ts +0 -470
  249. package/plugins/bizar/src/plan-fs.ts +0 -323
  250. package/plugins/bizar/src/reasoning-clean.ts +0 -454
  251. package/plugins/bizar/src/report.ts +0 -178
  252. package/plugins/bizar/src/research-prompt.ts +0 -35
  253. package/plugins/bizar/src/serve-info.ts +0 -228
  254. package/plugins/bizar/src/serve.ts +0 -496
  255. package/plugins/bizar/src/settings.ts +0 -349
  256. package/plugins/bizar/src/state.ts +0 -298
  257. package/plugins/bizar/src/tool-discipline.ts +0 -105
  258. package/plugins/bizar/src/tools/agent-browser.ts +0 -315
  259. package/plugins/bizar/src/tools/bg-collect.ts +0 -131
  260. package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
  261. package/plugins/bizar/src/tools/bg-kill.ts +0 -116
  262. package/plugins/bizar/src/tools/bg-pause.ts +0 -99
  263. package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
  264. package/plugins/bizar/src/tools/bg-resume.ts +0 -94
  265. package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
  266. package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
  267. package/plugins/bizar/src/tools/bg-status.ts +0 -130
  268. package/plugins/bizar/src/tools/graph-query.ts +0 -278
  269. package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
  270. package/plugins/bizar/src/tools/memory-list.ts +0 -43
  271. package/plugins/bizar/src/tools/memory-read.ts +0 -69
  272. package/plugins/bizar/src/tools/memory-search.ts +0 -47
  273. package/plugins/bizar/src/tools/memory-write.ts +0 -54
  274. package/plugins/bizar/src/tools/open-kb.ts +0 -198
  275. package/plugins/bizar/src/tools/plan-action.ts +0 -785
  276. package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
  277. package/plugins/bizar/src/tools/team-spawn.ts +0 -73
  278. package/plugins/bizar/src/tools/team-status.ts +0 -76
  279. package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
  280. package/plugins/bizar/src/trajectory.ts +0 -104
  281. package/plugins/bizar/tests/README.md +0 -99
  282. package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
  283. package/plugins/bizar/tests/background-state.test.ts +0 -277
  284. package/plugins/bizar/tests/background.test.ts +0 -402
  285. package/plugins/bizar/tests/block.test.ts +0 -195
  286. package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
  287. package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
  288. package/plugins/bizar/tests/commands-impl.test.ts +0 -316
  289. package/plugins/bizar/tests/commands.test.ts +0 -584
  290. package/plugins/bizar/tests/compaction.test.ts +0 -264
  291. package/plugins/bizar/tests/config.test.ts +0 -128
  292. package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
  293. package/plugins/bizar/tests/dispose.test.ts +0 -336
  294. package/plugins/bizar/tests/event-stream.test.ts +0 -409
  295. package/plugins/bizar/tests/event.test.ts +0 -262
  296. package/plugins/bizar/tests/fingerprint.test.ts +0 -162
  297. package/plugins/bizar/tests/http-client.test.ts +0 -404
  298. package/plugins/bizar/tests/init-helpers.test.ts +0 -203
  299. package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
  300. package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
  301. package/plugins/bizar/tests/key-rotation.test.ts +0 -396
  302. package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
  303. package/plugins/bizar/tests/loop.test.ts +0 -397
  304. package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
  305. package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
  306. package/plugins/bizar/tests/odin.test.ts +0 -125
  307. package/plugins/bizar/tests/options.test.ts +0 -329
  308. package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
  309. package/plugins/bizar/tests/safety.test.ts +0 -256
  310. package/plugins/bizar/tests/serve.test.ts +0 -339
  311. package/plugins/bizar/tests/settings.test.ts +0 -351
  312. package/plugins/bizar/tests/stall-think.test.ts +0 -750
  313. package/plugins/bizar/tests/state.test.ts +0 -276
  314. package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
  315. package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
  316. package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
  317. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
  318. package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
  319. package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
  320. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
  321. package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
  322. package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
  323. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
  324. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
  325. package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
  326. package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
  327. package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
  328. package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
  329. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
  330. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
  331. package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
  332. package/plugins/bizar/tsconfig.json +0 -29
@@ -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
- }