@polderlabs/bizar 6.2.5 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (540) 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/hooks/worker-suggest.mjs +110 -0
  41. package/.claude/settings.json +125 -0
  42. package/.claude/skills/9router/SKILL.md +80 -0
  43. package/.claude/skills/9router-chat/SKILL.md +73 -0
  44. package/.claude/skills/9router-embeddings/SKILL.md +69 -0
  45. package/.claude/skills/9router-image/SKILL.md +86 -0
  46. package/.claude/skills/9router-stt/SKILL.md +79 -0
  47. package/.claude/skills/9router-tts/SKILL.md +80 -0
  48. package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
  49. package/.claude/skills/9router-web-search/SKILL.md +91 -0
  50. package/.claude/skills/bizar/README.md +9 -0
  51. package/.claude/skills/bizar/SKILL.md +450 -0
  52. package/.claude/skills/cpp-coding-standards/README.md +28 -0
  53. package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
  54. package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
  55. package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
  56. package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  57. package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  58. package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  59. package/.claude/skills/cpp-testing/README.md +28 -0
  60. package/.claude/skills/cpp-testing/SKILL.md +304 -0
  61. package/.claude/skills/cpp-testing/references/coverage.md +370 -0
  62. package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
  63. package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  64. package/.claude/skills/cpp-testing/references/mocking.md +364 -0
  65. package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
  66. package/.claude/skills/cubesandbox/SKILL.md +148 -0
  67. package/.claude/skills/embedded-esp-idf/README.md +41 -0
  68. package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
  69. package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  70. package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
  71. package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  72. package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
  73. package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  74. package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  75. package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
  76. package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  77. package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  78. package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  79. package/.claude/skills/glyph/SKILL.md +163 -0
  80. package/.claude/skills/harness-engineering/SKILL.md +143 -0
  81. package/.claude/skills/lightrag/SKILL.md +81 -0
  82. package/.claude/skills/memory-protocol/SKILL.md +105 -0
  83. package/.claude/skills/obsidian/SKILL.md +306 -0
  84. package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
  85. package/.claude/skills/self-improvement/SKILL.md +64 -0
  86. package/README.md +87 -59
  87. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-CW6fWKds.js} +3 -3
  88. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-CW6fWKds.js.map} +1 -1
  89. package/bizar-dash/dist/assets/MobileChat-D7zRhU9K.js +1 -0
  90. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-D7zRhU9K.js.map} +1 -1
  91. package/bizar-dash/dist/assets/MobileSettings-K-sNsn_r.js +1 -0
  92. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-K-sNsn_r.js.map} +1 -1
  93. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
  94. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
  95. package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
  96. package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
  97. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
  98. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
  99. package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
  100. package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
  101. package/bizar-dash/dist/assets/main-C-Md3DaY.css +1 -0
  102. package/bizar-dash/dist/assets/main-CO8fIxQK.js +18 -0
  103. package/bizar-dash/dist/assets/main-CO8fIxQK.js.map +1 -0
  104. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
  105. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-w2bDDkAH.js.map} +1 -1
  106. package/bizar-dash/dist/assets/mobile-DXYCA0hC.js +1 -0
  107. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DXYCA0hC.js.map} +1 -1
  108. package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js +2 -0
  109. package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js.map +1 -0
  110. package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
  111. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
  112. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
  113. package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js +2 -0
  114. package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js.map +1 -0
  115. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
  116. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
  117. package/bizar-dash/dist/index.html +12 -10
  118. package/bizar-dash/dist/mobile.html +7 -4
  119. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  120. package/bizar-dash/src/server/api.mjs +15 -4
  121. package/bizar-dash/src/server/background-store.mjs +15 -16
  122. package/bizar-dash/src/server/bg-poller.mjs +25 -35
  123. package/bizar-dash/src/server/bg-retry.mjs +66 -194
  124. package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
  125. package/bizar-dash/src/server/claude-info.mjs +412 -0
  126. package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
  127. package/bizar-dash/src/server/claude-sdk.mjs +191 -0
  128. package/bizar-dash/src/server/memory-consolidator.mjs +218 -0
  129. package/bizar-dash/src/server/memory-distillation.mjs +296 -0
  130. package/bizar-dash/src/server/providers-store.mjs +22 -49
  131. package/bizar-dash/src/server/routes/background.mjs +24 -20
  132. package/bizar-dash/src/server/routes/chat.mjs +223 -326
  133. package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
  134. package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
  135. package/bizar-dash/src/server/routes/distill.mjs +129 -0
  136. package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
  137. package/bizar-dash/src/server/routes/tasks.mjs +20 -28
  138. package/bizar-dash/src/server/task-delegator.mjs +76 -77
  139. package/bizar-dash/src/web/App.tsx +4 -0
  140. package/bizar-dash/src/web/components/Tag.tsx +18 -5
  141. package/bizar-dash/src/web/components/Topbar.tsx +3 -0
  142. package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
  143. package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
  144. package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
  145. package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
  146. package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
  147. package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
  148. package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
  149. package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
  150. package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
  151. package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
  152. package/bizar-dash/src/web/lib/types.ts +25 -0
  153. package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
  154. package/bizar-dash/src/web/styles/main.css +251 -0
  155. package/bizar-dash/src/web/styles/tasks-redesign.css +231 -0
  156. package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
  157. package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
  158. package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
  159. package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
  160. package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
  161. package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
  162. package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
  163. package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
  164. package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
  165. package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
  166. package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
  167. package/bizar-dash/src/web/ui/controls/controls.css +454 -0
  168. package/bizar-dash/src/web/ui/controls/index.ts +33 -0
  169. package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
  170. package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
  171. package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
  172. package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
  173. package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
  174. package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
  175. package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
  176. package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
  177. package/bizar-dash/src/web/ui/data/data.css +535 -0
  178. package/bizar-dash/src/web/ui/data/index.ts +31 -0
  179. package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
  180. package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
  181. package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
  182. package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
  183. package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
  184. package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
  185. package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
  186. package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
  187. package/bizar-dash/src/web/ui/index.ts +56 -0
  188. package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
  189. package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
  190. package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
  191. package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
  192. package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
  193. package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
  194. package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
  195. package/bizar-dash/src/web/ui/layout/index.ts +33 -0
  196. package/bizar-dash/src/web/ui/layout/layout.css +413 -0
  197. package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
  198. package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
  199. package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
  200. package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
  201. package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
  202. package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
  203. package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
  204. package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
  205. package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
  206. package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
  207. package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
  208. package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
  209. package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
  210. package/bizar-dash/src/web/ui/styles/globals.css +86 -0
  211. package/bizar-dash/src/web/ui/styles/reset.css +125 -0
  212. package/bizar-dash/src/web/ui/styles/tokens.css +195 -0
  213. package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
  214. package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
  215. package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
  216. package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
  217. package/bizar-dash/src/web/views/Agents.tsx +506 -266
  218. package/bizar-dash/src/web/views/Chat.tsx +8 -8
  219. package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
  220. package/bizar-dash/src/web/views/Overview.tsx +996 -448
  221. package/bizar-dash/src/web/views/Tasks.tsx +277 -273
  222. package/bizar-dash/tests/communication-log.test.tsx +67 -0
  223. package/bizar-dash/tests/dep-graph.test.tsx +40 -0
  224. package/bizar-dash/tests/event-log.test.tsx +61 -0
  225. package/bizar-dash/tests/goal-input.test.tsx +72 -0
  226. package/bizar-dash/tests/goap-planner.test.ts +68 -0
  227. package/bizar-dash/tests/quality-gates.test.tsx +59 -0
  228. package/bizar-dash/tests/setup.ts +31 -1
  229. package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
  230. package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
  231. package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
  232. package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
  233. package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
  234. package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
  235. package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
  236. package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
  237. package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
  238. package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
  239. package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
  240. package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
  241. package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
  242. package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
  243. package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
  244. package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
  245. package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
  246. package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
  247. package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
  248. package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
  249. package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
  250. package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
  251. package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
  252. package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
  253. package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
  254. package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
  255. package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
  256. package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
  257. package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
  258. package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
  259. package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
  260. package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
  261. package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
  262. package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
  263. package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
  264. package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
  265. package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
  266. package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
  267. package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
  268. package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
  269. package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
  270. package/bizar-dash/tests/views/Agents.test.tsx +359 -0
  271. package/bizar-dash/tests/views/Overview.test.tsx +413 -0
  272. package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
  273. package/bizar-dash/vitest.config.ts +3 -1
  274. package/cli/__tests__/cost-gate.test.mjs +301 -0
  275. package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
  276. package/cli/bin.mjs +55 -18
  277. package/cli/commands/claim.mjs +282 -0
  278. package/cli/commands/claude-cmd.mjs +348 -0
  279. package/cli/commands/cost.mjs +279 -0
  280. package/cli/commands/install.mjs +12 -11
  281. package/cli/commands/validate.mjs +252 -325
  282. package/cli/cost-gate.mjs +501 -0
  283. package/cli/dev-link.test.mjs +11 -11
  284. package/cli/doctor.mjs +131 -213
  285. package/cli/feature-list-bridge.mjs +400 -0
  286. package/cli/install.mjs +33 -37
  287. package/cli/install.test.mjs +9 -9
  288. package/cli/memory-constants.mjs +17 -0
  289. package/cli/provision-claude.mjs +893 -0
  290. package/cli/provision.mjs +224 -227
  291. package/cli/utils.mjs +72 -58
  292. package/cli/worker-dispatcher.mjs +259 -0
  293. package/cli/worker-dispatcher.test.mjs +204 -0
  294. package/config/AGENTS.md +28 -28
  295. package/config/trigger-patterns.json +186 -0
  296. package/install.sh +66 -44
  297. package/package.json +19 -11
  298. package/packages/sdk/ARCHITECTURE.md +40 -36
  299. package/packages/sdk/package.json +15 -5
  300. package/packages/sdk/src/agent-registry.ts +304 -0
  301. package/packages/sdk/src/consensus/byzantine.ts +587 -0
  302. package/packages/sdk/src/consensus/index.ts +147 -0
  303. package/packages/sdk/src/consensus/queen.ts +173 -0
  304. package/packages/sdk/src/consensus/types.ts +199 -0
  305. package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
  306. package/packages/sdk/src/federation/audit.ts +156 -0
  307. package/packages/sdk/src/federation/budget.ts +269 -0
  308. package/packages/sdk/src/federation/envelope.ts +144 -0
  309. package/packages/sdk/src/federation/hmac.ts +160 -0
  310. package/packages/sdk/src/federation/index.ts +443 -0
  311. package/packages/sdk/src/federation/pii.ts +241 -0
  312. package/packages/sdk/src/federation/policy.ts +112 -0
  313. package/packages/sdk/src/federation/trust.ts +146 -0
  314. package/packages/sdk/src/fingerprint.ts +0 -0
  315. package/packages/sdk/src/index.ts +101 -69
  316. package/packages/sdk/src/mcp/bin.ts +51 -0
  317. package/packages/sdk/src/mcp/server.ts +848 -0
  318. package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
  319. package/packages/sdk/src/router/codemod-intent.ts +125 -0
  320. package/packages/sdk/src/router/index.ts +247 -0
  321. package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
  322. package/packages/sdk/src/router/memory-distillation.ts +249 -0
  323. package/packages/sdk/src/router/model-router.ts +235 -0
  324. package/packages/sdk/src/router/q-learning-router.ts +304 -0
  325. package/packages/sdk/src/swarm-topology.ts +279 -0
  326. package/packages/sdk/tests/agent-registry.test.ts +285 -0
  327. package/packages/sdk/tests/consensus.test.ts +376 -0
  328. package/packages/sdk/tests/federation/audit.test.ts +164 -0
  329. package/packages/sdk/tests/federation/budget.test.ts +189 -0
  330. package/packages/sdk/tests/federation/envelope.test.ts +120 -0
  331. package/packages/sdk/tests/federation/hmac.test.ts +194 -0
  332. package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
  333. package/packages/sdk/tests/federation/pii.test.ts +106 -0
  334. package/packages/sdk/tests/federation/policy.test.ts +140 -0
  335. package/packages/sdk/tests/federation/trust.test.ts +124 -0
  336. package/packages/sdk/tests/mcp-tools.test.ts +204 -0
  337. package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
  338. package/packages/sdk/tests/model-router.test.mjs +108 -0
  339. package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
  340. package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
  341. package/packages/sdk/tests/router.test.mjs +91 -0
  342. package/packages/sdk/tests/sdk.test.mjs +172 -0
  343. package/packages/sdk/tests/swarm-topology.test.ts +258 -0
  344. package/packages/sdk/vitest.config.ts +1 -1
  345. package/scripts/bh-full-e2e.mjs +167 -368
  346. package/scripts/check-deps.mjs +28 -46
  347. package/scripts/mirror-claude-md.sh +78 -0
  348. package/scripts/test-in-container.sh +31 -9
  349. package/templates/clean-state-checklist.md +3 -3
  350. package/templates/sprint-contract.md +2 -2
  351. package/bizar-dash/.bizar/activity.log +0 -3
  352. package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
  353. package/bizar-dash/.omx/state/session.json +0 -10
  354. package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
  355. package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
  356. package/bizar-dash/bizar-design/canvas.html +0 -281
  357. package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
  358. package/bizar-dash/bizar-design/components.css +0 -1665
  359. package/bizar-dash/bizar-design/components.html +0 -557
  360. package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
  361. package/bizar-dash/bizar-design/desktop/agents.html +0 -74
  362. package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
  363. package/bizar-dash/bizar-design/desktop/memory.html +0 -75
  364. package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
  365. package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
  366. package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
  367. package/bizar-dash/bizar-design/desktop/overview.html +0 -95
  368. package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
  369. package/bizar-dash/bizar-design/desktop/settings.html +0 -70
  370. package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
  371. package/bizar-dash/bizar-design/icons.svg +0 -44
  372. package/bizar-dash/bizar-design/index.html +0 -192
  373. package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
  374. package/bizar-dash/bizar-design/mobile/agents.html +0 -29
  375. package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
  376. package/bizar-dash/bizar-design/mobile/memory.html +0 -28
  377. package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
  378. package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
  379. package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
  380. package/bizar-dash/bizar-design/mobile/overview.html +0 -33
  381. package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
  382. package/bizar-dash/bizar-design/mobile/settings.html +0 -28
  383. package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
  384. package/bizar-dash/bizar-design/prototype.js +0 -114
  385. package/bizar-dash/bizar-design/tokens.css +0 -106
  386. package/bizar-dash/dist/assets/MobileChat-BnKN_Ks_.js +0 -1
  387. package/bizar-dash/dist/assets/MobileSettings-DjCPxC-Q.js +0 -1
  388. package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
  389. package/bizar-dash/dist/assets/main-DYiZqMrn.js +0 -18
  390. package/bizar-dash/dist/assets/main-DYiZqMrn.js.map +0 -1
  391. package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
  392. package/bizar-dash/dist/assets/markdown-C6mXtQxD.js +0 -1
  393. package/bizar-dash/dist/assets/mobile-CMHqtLV2.js +0 -1
  394. package/bizar-dash/dist/assets/mobile-layout-3jIhHX_p.js +0 -2
  395. package/bizar-dash/dist/assets/mobile-layout-3jIhHX_p.js.map +0 -1
  396. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
  397. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
  398. package/bizar-dash/dist/assets/useSlashCommands-DtITw8Xv.js +0 -2
  399. package/bizar-dash/dist/assets/useSlashCommands-DtITw8Xv.js.map +0 -1
  400. package/bizar-dash/dist/assets/vendor-CeHGtduv.js +0 -29
  401. package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
  402. package/bizar-dash/node_modules/.package-lock.json +0 -6
  403. package/bizar-dash/package-lock.json +0 -6
  404. package/bizar-dash/src/server/cline-sdk.mjs +0 -132
  405. package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
  406. package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
  407. package/cli/commands/cline-cmd.mjs +0 -289
  408. package/cli/commands/validate.test.mjs +0 -379
  409. package/cli/doctor.test.mjs +0 -350
  410. package/config/cline.json.template +0 -342
  411. package/packages/sdk/src/client.ts +0 -188
  412. package/packages/sdk/src/cline-events.ts +0 -134
  413. package/packages/sdk/src/cline-types.ts +0 -66
  414. package/packages/sdk/src/cline.ts +0 -339
  415. package/packages/sdk/src/errors.ts +0 -129
  416. package/packages/sdk/src/events.ts +0 -153
  417. package/packages/sdk/src/types.ts +0 -176
  418. package/packages/sdk/tests/client.test.ts +0 -217
  419. package/packages/sdk/tests/errors.test.ts +0 -108
  420. package/packages/sdk/tests/events.test.ts +0 -139
  421. package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
  422. package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
  423. package/plugins/bizar/ARCHITECTURE.md +0 -142
  424. package/plugins/bizar/CONSTRAINTS.md +0 -67
  425. package/plugins/bizar/LICENSE +0 -21
  426. package/plugins/bizar/README.md +0 -448
  427. package/plugins/bizar/index.ts +0 -925
  428. package/plugins/bizar/package.json +0 -39
  429. package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
  430. package/plugins/bizar/src/background-state.ts +0 -641
  431. package/plugins/bizar/src/background.ts +0 -1806
  432. package/plugins/bizar/src/cline-runner.ts +0 -203
  433. package/plugins/bizar/src/clineruntime.ts +0 -227
  434. package/plugins/bizar/src/commands-impl.ts +0 -151
  435. package/plugins/bizar/src/commands.ts +0 -1799
  436. package/plugins/bizar/src/compaction.d.mts +0 -48
  437. package/plugins/bizar/src/compaction.mjs +0 -192
  438. package/plugins/bizar/src/dashboard-client.ts +0 -233
  439. package/plugins/bizar/src/event-stream.ts +0 -606
  440. package/plugins/bizar/src/fingerprint.ts +0 -120
  441. package/plugins/bizar/src/handoff.ts +0 -79
  442. package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
  443. package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
  444. package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
  445. package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
  446. package/plugins/bizar/src/http-client.ts +0 -467
  447. package/plugins/bizar/src/key-rotation.ts +0 -218
  448. package/plugins/bizar/src/logger.ts +0 -144
  449. package/plugins/bizar/src/loop-engineering.ts +0 -241
  450. package/plugins/bizar/src/loop.ts +0 -176
  451. package/plugins/bizar/src/mistake-recovery.ts +0 -98
  452. package/plugins/bizar/src/odin.ts +0 -227
  453. package/plugins/bizar/src/options.ts +0 -470
  454. package/plugins/bizar/src/plan-fs.ts +0 -323
  455. package/plugins/bizar/src/reasoning-clean.ts +0 -454
  456. package/plugins/bizar/src/report.ts +0 -178
  457. package/plugins/bizar/src/research-prompt.ts +0 -35
  458. package/plugins/bizar/src/serve-info.ts +0 -228
  459. package/plugins/bizar/src/serve.ts +0 -496
  460. package/plugins/bizar/src/settings.ts +0 -349
  461. package/plugins/bizar/src/state.ts +0 -298
  462. package/plugins/bizar/src/tool-discipline.ts +0 -105
  463. package/plugins/bizar/src/tools/agent-browser.ts +0 -315
  464. package/plugins/bizar/src/tools/bg-collect.ts +0 -131
  465. package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
  466. package/plugins/bizar/src/tools/bg-kill.ts +0 -116
  467. package/plugins/bizar/src/tools/bg-pause.ts +0 -99
  468. package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
  469. package/plugins/bizar/src/tools/bg-resume.ts +0 -94
  470. package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
  471. package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
  472. package/plugins/bizar/src/tools/bg-status.ts +0 -130
  473. package/plugins/bizar/src/tools/graph-query.ts +0 -278
  474. package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
  475. package/plugins/bizar/src/tools/memory-list.ts +0 -43
  476. package/plugins/bizar/src/tools/memory-read.ts +0 -69
  477. package/plugins/bizar/src/tools/memory-search.ts +0 -47
  478. package/plugins/bizar/src/tools/memory-write.ts +0 -54
  479. package/plugins/bizar/src/tools/open-kb.ts +0 -198
  480. package/plugins/bizar/src/tools/plan-action.ts +0 -785
  481. package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
  482. package/plugins/bizar/src/tools/sandbox.ts +0 -232
  483. package/plugins/bizar/src/tools/team-spawn.ts +0 -73
  484. package/plugins/bizar/src/tools/team-status.ts +0 -76
  485. package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
  486. package/plugins/bizar/src/trajectory.ts +0 -104
  487. package/plugins/bizar/tests/README.md +0 -99
  488. package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
  489. package/plugins/bizar/tests/background-state.test.ts +0 -277
  490. package/plugins/bizar/tests/background.test.ts +0 -402
  491. package/plugins/bizar/tests/block.test.ts +0 -195
  492. package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
  493. package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
  494. package/plugins/bizar/tests/commands-impl.test.ts +0 -316
  495. package/plugins/bizar/tests/commands.test.ts +0 -584
  496. package/plugins/bizar/tests/compaction.test.ts +0 -264
  497. package/plugins/bizar/tests/config.test.ts +0 -128
  498. package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
  499. package/plugins/bizar/tests/dispose.test.ts +0 -336
  500. package/plugins/bizar/tests/event-stream.test.ts +0 -409
  501. package/plugins/bizar/tests/event.test.ts +0 -262
  502. package/plugins/bizar/tests/fingerprint.test.ts +0 -190
  503. package/plugins/bizar/tests/http-client.test.ts +0 -404
  504. package/plugins/bizar/tests/init-helpers.test.ts +0 -203
  505. package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
  506. package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
  507. package/plugins/bizar/tests/key-rotation.test.ts +0 -396
  508. package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
  509. package/plugins/bizar/tests/loop.test.ts +0 -397
  510. package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
  511. package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
  512. package/plugins/bizar/tests/odin.test.ts +0 -125
  513. package/plugins/bizar/tests/options.test.ts +0 -329
  514. package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
  515. package/plugins/bizar/tests/safety.test.ts +0 -281
  516. package/plugins/bizar/tests/serve.test.ts +0 -339
  517. package/plugins/bizar/tests/settings.test.ts +0 -351
  518. package/plugins/bizar/tests/stall-think.test.ts +0 -750
  519. package/plugins/bizar/tests/state.test.ts +0 -276
  520. package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
  521. package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
  522. package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
  523. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
  524. package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
  525. package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
  526. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
  527. package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
  528. package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
  529. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
  530. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
  531. package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
  532. package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
  533. package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
  534. package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
  535. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
  536. package/plugins/bizar/tests/tools/sandbox.test.ts +0 -117
  537. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
  538. package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
  539. package/plugins/bizar/tsconfig.json +0 -29
  540. package/scripts/mirror-agents-md.sh +0 -69
@@ -1,750 +0,0 @@
1
- /**
2
- * stall-think.test.ts
3
- *
4
- * v0.3.0 stall timeout + thinking-loop protection tests.
5
- *
6
- * Groups:
7
- * 1. researchInterventionPrompt() — 5 tests
8
- * 2. normalizeOptions() v0.3.0 fields — 6 tests
9
- * 3. BackgroundState schema backfill — 3 tests
10
- * 4. Stall + thinking-loop detection logic — 7 tests
11
- * 5. bg-status toView v0.3.0 fields — 2 tests
12
- *
13
- * Total: 23 tests
14
- */
15
-
16
- import { describe, it, expect, beforeEach, afterEach } from "bun:test";
17
- import { writeFileSync, mkdirSync, unlinkSync, rmSync } from "node:fs";
18
- import path from "node:path";
19
- import os from "node:os";
20
-
21
- // ---------------------------------------------------------------------------
22
- // Group 1 — researchInterventionPrompt
23
- // ---------------------------------------------------------------------------
24
-
25
- import { researchInterventionPrompt } from "../src/research-prompt.js";
26
-
27
- describe("researchInterventionPrompt", () => {
28
- it("returns a string containing '[SYSTEM REMINDER — Thinking Loop Detected]'", () => {
29
- const result = researchInterventionPrompt(60_000);
30
- expect(result).toContain("[SYSTEM REMINDER — Thinking Loop Detected]");
31
- });
32
-
33
- it("includes the duration formatted as 'Xm Ys' for multi-minute durations", () => {
34
- const result = researchInterventionPrompt(330_000); // 5m 30s
35
- expect(result).toContain("5m 30s");
36
- });
37
-
38
- it("formats under-a-minute durations as 'Ys' (not '0m Ys')", () => {
39
- const result = researchInterventionPrompt(45_000); // 45s
40
- expect(result).toContain("45s");
41
- // Must NOT contain "0m" in the duration portion
42
- expect(result).not.toMatch(/0m \d+s/);
43
- });
44
-
45
- it("clamps negative or zero duration to 0s", () => {
46
- const neg = researchInterventionPrompt(-99_000);
47
- const zero = researchInterventionPrompt(0);
48
- // Both should produce "0s" (Math.max(0, …) in the function)
49
- expect(neg).toContain("0s");
50
- expect(zero).toContain("0s");
51
- });
52
-
53
- it("mentions 'task tool', 'Mimir', and 'bash' — the three action options", () => {
54
- const result = researchInterventionPrompt(60_000);
55
- expect(result).toContain("task tool");
56
- expect(result).toContain("Mimir");
57
- expect(result).toContain("bash");
58
- });
59
- });
60
-
61
- // ---------------------------------------------------------------------------
62
- // Group 2 — normalizeOptions v0.3.0 fields
63
- // ---------------------------------------------------------------------------
64
-
65
- import { normalizeOptions } from "../src/options.js";
66
-
67
- describe("normalizeOptions — v0.3.0 fields", () => {
68
- it("defaults: backgroundStallTimeoutMs === 180_000, backgroundThinkingLoopTimeoutMs === 300_000, backgroundMaxInterventions === 1", () => {
69
- const { options } = normalizeOptions(undefined);
70
- expect(options.backgroundStallTimeoutMs).toBe(180_000);
71
- expect(options.backgroundThinkingLoopTimeoutMs).toBe(300_000);
72
- expect(options.backgroundMaxInterventions).toBe(1);
73
- });
74
-
75
- it("clamps backgroundStallTimeoutMs < 10000 to 10000 and pushes a note", () => {
76
- const { options, notes } = normalizeOptions({ backgroundStallTimeoutMs: 999 });
77
- expect(options.backgroundStallTimeoutMs).toBe(10_000);
78
- expect(notes.some((n) => n.includes("backgroundStallTimeoutMs") && n.includes("clamped"))).toBe(true);
79
- });
80
-
81
- it("clamps backgroundStallTimeoutMs > 600000 to 600000 and pushes a note", () => {
82
- const { options, notes } = normalizeOptions({ backgroundStallTimeoutMs: 999_999 });
83
- expect(options.backgroundStallTimeoutMs).toBe(600_000);
84
- expect(notes.some((n) => n.includes("backgroundStallTimeoutMs") && n.includes("clamped"))).toBe(true);
85
- });
86
-
87
- it("clamps backgroundMaxInterventions < 1 to 1", () => {
88
- const { options, notes } = normalizeOptions({ backgroundMaxInterventions: 0 });
89
- expect(options.backgroundMaxInterventions).toBe(1);
90
- expect(notes.some((n) => n.includes("backgroundMaxInterventions") && n.includes("clamped"))).toBe(true);
91
- });
92
-
93
- it("clamps backgroundMaxInterventions > 3 to 3", () => {
94
- const { options, notes } = normalizeOptions({ backgroundMaxInterventions: 99 });
95
- expect(options.backgroundMaxInterventions).toBe(3);
96
- expect(notes.some((n) => n.includes("backgroundMaxInterventions") && n.includes("clamped"))).toBe(true);
97
- });
98
-
99
- it("honors BIZAR_STALL_TIMEOUT_MS env var when no option is set", async () => {
100
- // Set the env var before importing/evaluating normalizeOptions.
101
- // Static imports are hoisted and evaluated before test code runs,
102
- // so we must use dynamic import AFTER setting the env var.
103
- process.env.BIZAR_STALL_TIMEOUT_MS = "42000";
104
- try {
105
- // Dynamically import AFTER the env var is set so the module's
106
- // function closure picks up the updated process.env at evaluation time.
107
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
- const mod = await import("../src/options.js") as any;
109
- const { options } = mod.normalizeOptions(undefined);
110
- expect(options.backgroundStallTimeoutMs).toBe(42000);
111
- } finally {
112
- delete process.env.BIZAR_STALL_TIMEOUT_MS;
113
- }
114
- });
115
- });
116
-
117
- // ---------------------------------------------------------------------------
118
- // Group 3 — BackgroundState schema backfill
119
- // ---------------------------------------------------------------------------
120
-
121
- import { BackgroundStateStore } from "../src/background-state.js";
122
- import { TERMINAL_STATUSES } from "../src/background-state.js";
123
-
124
- // Minimal Logger for tests
125
- const silentLogger = {
126
- log(_opts: { level: "debug" | "info" | "warn" | "error"; message: string }) {},
127
- debug(_m: string) {},
128
- info(_m: string) {},
129
- warn(_m: string) {},
130
- error(_m: string) {},
131
- };
132
-
133
- function makeTempDir(prefix: string): string {
134
- const dir = path.join(os.tmpdir(), `bizar-stall-test-${prefix}-${process.pid}`);
135
- mkdirSync(dir, { recursive: true });
136
- return dir;
137
- }
138
-
139
- function writeOldStateFile(dir: string, instanceId: string, state: Record<string, unknown>): void {
140
- const bgDir = path.join(dir, "bg");
141
- mkdirSync(bgDir, { recursive: true });
142
- writeFileSync(path.join(bgDir, `${instanceId}.json`), JSON.stringify(state), "utf8");
143
- }
144
-
145
- describe("BackgroundState schema backfill", () => {
146
- let tmpDir: string;
147
-
148
- beforeEach(() => {
149
- tmpDir = makeTempDir("bg-state-backfill");
150
- });
151
-
152
- afterEach(() => {
153
- rmSync(tmpDir, { recursive: true, force: true });
154
- });
155
-
156
- it("loads an old-format state file (no lastEventAt) and backfills it to startedAt", async () => {
157
- const instanceId = "bgr_old_no_lastEventAt";
158
- const startedAt = 1_700_000_000_000;
159
- writeOldStateFile(tmpDir, instanceId, {
160
- instanceId,
161
- sessionId: "sess_abc",
162
- agent: "mimir",
163
- status: "running",
164
- startedAt,
165
- model: "minimax/minimax-m3",
166
- promptPreview: "Do the thing",
167
- toolCallCount: 0,
168
- parentAgent: "odin",
169
- logPath: path.join(os.tmpdir(), "test.log"),
170
- timeoutMs: 300_000,
171
- // lastEventAt is intentionally absent
172
- lastToolOrTextAt: startedAt,
173
- interventionCount: 0,
174
- });
175
-
176
- const store = new BackgroundStateStore(tmpDir, silentLogger);
177
- const loaded = await store.load(instanceId);
178
-
179
- expect(loaded).not.toBeNull();
180
- expect(loaded!.lastEventAt).toBe(startedAt);
181
- });
182
-
183
- it("loads an old-format state file (no lastToolOrTextAt) and backfills it to startedAt", async () => {
184
- const instanceId = "bgr_old_no_lastToolOrTextAt";
185
- const startedAt = 1_700_000_000_000;
186
- writeOldStateFile(tmpDir, instanceId, {
187
- instanceId,
188
- sessionId: "sess_def",
189
- agent: "mimir",
190
- status: "running",
191
- startedAt,
192
- model: "minimax/minimax-m3",
193
- promptPreview: "Do the thing",
194
- toolCallCount: 0,
195
- parentAgent: "odin",
196
- logPath: path.join(os.tmpdir(), "test.log"),
197
- timeoutMs: 300_000,
198
- lastEventAt: startedAt,
199
- // lastToolOrTextAt is intentionally absent
200
- interventionCount: 0,
201
- });
202
-
203
- const store = new BackgroundStateStore(tmpDir, silentLogger);
204
- const loaded = await store.load(instanceId);
205
-
206
- expect(loaded).not.toBeNull();
207
- expect(loaded!.lastToolOrTextAt).toBe(startedAt);
208
- });
209
-
210
- it("loads an old-format state file (no interventionCount) and backfills it to 0", async () => {
211
- const instanceId = "bgr_old_no_interventionCount";
212
- const startedAt = 1_700_000_000_000;
213
- writeOldStateFile(tmpDir, instanceId, {
214
- instanceId,
215
- sessionId: "sess_ghi",
216
- agent: "mimir",
217
- status: "running",
218
- startedAt,
219
- model: "minimax/minimax-m3",
220
- promptPreview: "Do the thing",
221
- toolCallCount: 0,
222
- parentAgent: "odin",
223
- logPath: path.join(os.tmpdir(), "test.log"),
224
- timeoutMs: 300_000,
225
- lastEventAt: startedAt,
226
- lastToolOrTextAt: startedAt,
227
- // interventionCount is intentionally absent
228
- });
229
-
230
- const store = new BackgroundStateStore(tmpDir, silentLogger);
231
- const loaded = await store.load(instanceId);
232
-
233
- expect(loaded).not.toBeNull();
234
- expect(loaded!.interventionCount).toBe(0);
235
- });
236
- });
237
-
238
- // ---------------------------------------------------------------------------
239
- // Group 4 — Stall + thinking-loop detection logic
240
- //
241
- // FakeInstanceManager replicates the v0.3.0 stall and thinking-loop
242
- // detection algorithm from the real InstanceManager. It mirrors the
243
- // real class surface and implements the exact same logic.
244
- // ---------------------------------------------------------------------------
245
-
246
- import type { BackgroundState, BackgroundStatus } from "../src/background-state.js";
247
- import { TERMINAL_STATUSES as TERMINAL } from "../src/background-state.js";
248
-
249
- /** Format duration as the checker does internally. */
250
- function formatDuration(ms: number): string {
251
- const safeMs = Math.max(0, Math.floor(ms));
252
- const minutes = Math.floor(safeMs / 60_000);
253
- const seconds = Math.floor((safeMs % 60_000) / 1000);
254
- return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
255
- }
256
-
257
- /** Minimal Logger for tests */
258
- const noopLogger = {
259
- log(_opts: { level: "debug" | "info" | "warn" | "error"; message: string }) {},
260
- debug(_m: string) {},
261
- info(_m: string) {},
262
- warn(_m: string) {},
263
- error(_m: string) {},
264
- };
265
-
266
- /**
267
- * FakeInstanceManager — mirrors the v0.3.0 InstanceManager surface with
268
- * full stall and thinking-loop detection logic. Uses in-memory state only
269
- * (no HTTP, no EventStream, no serve child). Exposes sentPrompts and
270
- * abortedSessions for test assertions.
271
- */
272
- class FakeInstanceManagerForStall {
273
- /** Exposed for test assertions */
274
- sentPrompts: Array<{ sessionId: string; text: string }> = [];
275
- abortedSessions: string[] = [];
276
-
277
- private instances = new Map<string, BackgroundState>();
278
- private stallTimeoutMs: number;
279
- private thinkingLoopTimeoutMs: number;
280
- private maxInterventions: number;
281
- private stallCheckerDisabled = false;
282
-
283
- constructor(opts: {
284
- stallTimeoutMs?: number;
285
- thinkingLoopTimeoutMs?: number;
286
- maxInterventions?: number;
287
- } = {}) {
288
- this.stallTimeoutMs = opts.stallTimeoutMs ?? 180_000;
289
- this.thinkingLoopTimeoutMs = opts.thinkingLoopTimeoutMs ?? 300_000;
290
- this.maxInterventions = opts.maxInterventions ?? 1;
291
- }
292
-
293
- get stallTimeoutMsValue(): number {
294
- return this.stallTimeoutMs;
295
- }
296
-
297
- get thinkingLoopTimeoutMsValue(): number {
298
- return this.thinkingLoopTimeoutMs;
299
- }
300
-
301
- get maxInterventionsValue(): number {
302
- return this.maxInterventions;
303
- }
304
-
305
- disablePeriodicChecks(): void {
306
- this.stallCheckerDisabled = true;
307
- }
308
-
309
- // ---------------------------------------------------------------------------
310
- // Fake public API used by tests to set up instance state
311
- // ---------------------------------------------------------------------------
312
-
313
- /** Add a fake instance directly to the in-memory map */
314
- addInstance(state: BackgroundState): void {
315
- this.instances.set(state.instanceId, { ...state });
316
- }
317
-
318
- getInstance(instanceId: string): BackgroundState | undefined {
319
- return this.instances.get(instanceId);
320
- }
321
-
322
- // ---------------------------------------------------------------------------
323
- // runStallAndLoopChecks — exact replica of InstanceManager.runStallAndLoopChecks
324
- // ---------------------------------------------------------------------------
325
-
326
- async runStallAndLoopChecks(): Promise<void> {
327
- if (this.stallCheckerDisabled) return;
328
-
329
- const ids: string[] = [];
330
- for (const inst of this.instances.values()) {
331
- if (TERMINAL.has(inst.status)) continue;
332
- ids.push(inst.instanceId);
333
- }
334
-
335
- for (const id of ids) {
336
- const inst = this.instances.get(id);
337
- if (!inst || TERMINAL.has(inst.status)) continue;
338
-
339
- const now = Date.now();
340
- const lastEventAt = inst.lastEventAt ?? 0;
341
- const lastToolOrTextAt = inst.lastToolOrTextAt ?? 0;
342
-
343
- // Stall check
344
- if (now - lastEventAt > this.stallTimeoutMs) {
345
- await this._abortAsStalled(inst);
346
- continue;
347
- }
348
-
349
- // Thinking-loop check — only for running instances
350
- if (inst.status === "running") {
351
- const since = now - lastToolOrTextAt;
352
- if (since > this.thinkingLoopTimeoutMs) {
353
- const currentCount = inst.interventionCount ?? 0;
354
- if (currentCount < this.maxInterventions) {
355
- await this._sendIntervention(inst, since);
356
- } else {
357
- await this._abortAsThinkingLoop(inst, since);
358
- }
359
- }
360
- }
361
- }
362
- }
363
-
364
- // ---------------------------------------------------------------------------
365
- // simulatePartUpdated — replicates InstanceManager.onPartUpdated
366
- // ---------------------------------------------------------------------------
367
-
368
- simulatePartUpdated(
369
- instanceId: string,
370
- partType: "tool" | "text" | "thinking",
371
- ): void {
372
- const inst = this.instances.get(instanceId);
373
- if (!inst) return;
374
-
375
- // Every event advances lastEventAt (heartbeat)
376
- inst.lastEventAt = Date.now();
377
-
378
- if (partType === "tool" || partType === "text") {
379
- inst.lastToolOrTextAt = Date.now();
380
- // Reset intervention counter after progress
381
- if ((inst.interventionCount ?? 0) > 0) {
382
- inst.interventionCount = 0;
383
- delete inst.interventionAt;
384
- delete inst.interventionReason;
385
- }
386
- }
387
- // 'thinking' parts do NOT advance lastToolOrTextAt — that is intentional
388
- }
389
-
390
- // ---------------------------------------------------------------------------
391
- // Private helpers — exact replicas of InstanceManager private methods
392
- // ---------------------------------------------------------------------------
393
-
394
- private async _abortAsStalled(inst: BackgroundState): Promise<void> {
395
- const lastEventAt = inst.lastEventAt ?? 0;
396
- const sinceMs = Date.now() - lastEventAt;
397
- noopLogger.warn(
398
- `bizar: instance ${inst.instanceId} stalled (no event for ${sinceMs}ms); aborting`,
399
- );
400
- this.abortedSessions.push(inst.sessionId);
401
- inst.status = "failed";
402
- inst.error = `No activity for ${this.stallTimeoutMs}ms — LLM appears stalled`;
403
- inst.completedAt = Date.now();
404
- }
405
-
406
- private async _sendIntervention(inst: BackgroundState, sinceMs: number): Promise<void> {
407
- const prompt = researchInterventionPrompt(sinceMs);
408
- const currentCount = inst.interventionCount ?? 0;
409
- noopLogger.warn(
410
- `bizar: instance ${inst.instanceId} thinking loop (${sinceMs}ms without tool/text); sending intervention #${currentCount + 1}/${this.maxInterventions}`,
411
- );
412
- this.sentPrompts.push({ sessionId: inst.sessionId, text: prompt });
413
- const reason = `thinking loop (${formatDuration(sinceMs)} without tool/text)`;
414
- inst.interventionCount = currentCount + 1;
415
- inst.interventionAt = Date.now();
416
- inst.interventionReason = reason;
417
- // Bumping lastEventAt here is intentional (mirrors real impl)
418
- inst.lastEventAt = Date.now();
419
- }
420
-
421
- private async _abortAsThinkingLoop(inst: BackgroundState, sinceMs: number): Promise<void> {
422
- noopLogger.warn(
423
- `bizar: instance ${inst.instanceId} thinking loop exhausted ${this.maxInterventions} intervention(s) over ${sinceMs}ms; aborting`,
424
- );
425
- this.abortedSessions.push(inst.sessionId);
426
- inst.status = "failed";
427
- inst.error = `Thinking loop detected: ${formatDuration(sinceMs)} of thinking without tool calls or output. Spawn a Mimir agent for research.`;
428
- inst.completedAt = Date.now();
429
- }
430
- }
431
-
432
- function makeBgState(overrides: Partial<BackgroundState> = {}): BackgroundState {
433
- const now = Date.now();
434
- return {
435
- instanceId: `bgr_${Math.random().toString(36).slice(2, 10)}`,
436
- sessionId: `sess_${Math.random().toString(36).slice(2, 10)}`,
437
- agent: "mimir",
438
- status: "running",
439
- startedAt: now,
440
- model: "minimax/minimax-m3",
441
- promptPreview: "Do the thing",
442
- resultPreview: undefined,
443
- resultMessageIds: [],
444
- error: undefined,
445
- parentAgent: "odin",
446
- parentInstanceId: undefined,
447
- logPath: "~/.cache/bizar/logs/test.log",
448
- timeoutMs: 300_000,
449
- toolCallCount: 0,
450
- loopGuardTool: undefined,
451
- lastEventAt: now,
452
- lastToolOrTextAt: now,
453
- interventionCount: 0,
454
- ...overrides,
455
- };
456
- }
457
-
458
- describe("Stall + thinking-loop detection logic", () => {
459
- // Test 1: Stall timeout fires when no events
460
- it("stall timeout fires when no events (lastEventAt is old)", async () => {
461
- const mgr = new FakeInstanceManagerForStall({
462
- stallTimeoutMs: 180_000,
463
- thinkingLoopTimeoutMs: 300_000,
464
- maxInterventions: 1,
465
- });
466
-
467
- const oldTime = Date.now() - (180_000 + 1000); // past the stall threshold
468
- mgr.addInstance(
469
- makeBgState({
470
- instanceId: "bgr_stall_fire",
471
- status: "running",
472
- lastEventAt: oldTime,
473
- lastToolOrTextAt: oldTime,
474
- }),
475
- );
476
-
477
- await mgr.runStallAndLoopChecks();
478
-
479
- expect(mgr.abortedSessions).toContain(
480
- mgr.getInstance("bgr_stall_fire")!.sessionId,
481
- );
482
- const inst = mgr.getInstance("bgr_stall_fire")!;
483
- expect(inst.status).toBe("failed");
484
- expect(inst.error!).toContain("No activity for");
485
- });
486
-
487
- // Test 2: Stall timeout does NOT fire when events are recent
488
- it("stall timeout does NOT fire when events are recent", async () => {
489
- const mgr = new FakeInstanceManagerForStall({ stallTimeoutMs: 180_000 });
490
-
491
- const recentTime = Date.now();
492
- mgr.addInstance(
493
- makeBgState({
494
- instanceId: "bgr_stall_recent",
495
- status: "running",
496
- lastEventAt: recentTime,
497
- lastToolOrTextAt: recentTime,
498
- }),
499
- );
500
-
501
- await mgr.runStallAndLoopChecks();
502
-
503
- expect(mgr.abortedSessions).toHaveLength(0);
504
- const inst = mgr.getInstance("bgr_stall_recent")!;
505
- expect(inst.status).toBe("running");
506
- });
507
-
508
- // Test 3: Stall timeout does NOT fire for terminal instances
509
- it("stall timeout does NOT fire for terminal instances", async () => {
510
- const mgr = new FakeInstanceManagerForStall({ stallTimeoutMs: 180_000 });
511
-
512
- const oldTime = Date.now() - (180_000 + 1000);
513
- mgr.addInstance(
514
- makeBgState({
515
- instanceId: "bgr_stall_terminal",
516
- status: "done",
517
- lastEventAt: oldTime, // would fire if not terminal
518
- lastToolOrTextAt: oldTime,
519
- }),
520
- );
521
-
522
- await mgr.runStallAndLoopChecks();
523
-
524
- expect(mgr.abortedSessions).toHaveLength(0);
525
- const inst = mgr.getInstance("bgr_stall_terminal")!;
526
- expect(inst.status).toBe("done");
527
- });
528
-
529
- // Test 4: Thinking loop detection fires after threshold
530
- it("thinking loop detection fires after threshold, sends intervention, increments counter", async () => {
531
- const mgr = new FakeInstanceManagerForStall({
532
- stallTimeoutMs: 180_000,
533
- thinkingLoopTimeoutMs: 300_000,
534
- maxInterventions: 1,
535
- });
536
-
537
- // lastEventAt must be within stall timeout so we reach the thinking-loop
538
- // check. lastToolOrTextAt must be beyond the thinking-loop threshold.
539
- const now = Date.now();
540
- const lastEventAt = now - 60_000; // 60s ago — within 180s stall timeout
541
- const lastToolOrTextAt = now - 301_000; // 301s ago — beyond 300s loop threshold
542
-
543
- mgr.addInstance(
544
- makeBgState({
545
- instanceId: "bgr_thinking_loop",
546
- status: "running",
547
- lastEventAt,
548
- lastToolOrTextAt,
549
- interventionCount: 0,
550
- }),
551
- );
552
-
553
- await mgr.runStallAndLoopChecks();
554
-
555
- expect(mgr.sentPrompts).toHaveLength(1);
556
- expect(mgr.sentPrompts[0]!.text).toContain("[SYSTEM REMINDER — Thinking Loop Detected]");
557
-
558
- const inst = mgr.getInstance("bgr_thinking_loop")!;
559
- expect(inst.interventionCount).toBe(1);
560
- expect(inst.interventionAt).toBeDefined();
561
- expect(inst.interventionReason).toContain("thinking loop");
562
- expect(inst.status).toBe("running"); // still running — not failed yet
563
- });
564
-
565
- // Test 5: Thinking loop intervention respects maxInterventions
566
- it("when interventionCount >= maxInterventions, instance is aborted as thinking loop", async () => {
567
- const mgr = new FakeInstanceManagerForStall({
568
- stallTimeoutMs: 180_000,
569
- thinkingLoopTimeoutMs: 300_000,
570
- maxInterventions: 1,
571
- });
572
-
573
- // lastEventAt recent (within stall timeout) so we reach the thinking-loop check
574
- const now = Date.now();
575
- const lastEventAt = now - 60_000; // 60s ago — within 180s stall timeout
576
- const lastToolOrTextAt = now - 301_000; // 301s ago — beyond 300s loop threshold
577
-
578
- mgr.addInstance(
579
- makeBgState({
580
- instanceId: "bgr_max_interventions",
581
- status: "running",
582
- lastEventAt,
583
- lastToolOrTextAt,
584
- // Already at max interventions — should abort immediately without sending prompt
585
- interventionCount: 1,
586
- }),
587
- );
588
-
589
- await mgr.runStallAndLoopChecks();
590
-
591
- expect(mgr.sentPrompts).toHaveLength(0); // No new prompt sent
592
- expect(mgr.abortedSessions).toContain(
593
- mgr.getInstance("bgr_max_interventions")!.sessionId,
594
- );
595
- const inst = mgr.getInstance("bgr_max_interventions")!;
596
- expect(inst.status).toBe("failed");
597
- expect(inst.error!).toMatch(/thinking loop/i); // case-insensitive
598
- });
599
-
600
- // Test 6: Tool/text events reset intervention counter
601
- it("a tool or text part after intervention resets interventionCount to 0", async () => {
602
- const mgr = new FakeInstanceManagerForStall({
603
- stallTimeoutMs: 180_000,
604
- thinkingLoopTimeoutMs: 300_000,
605
- maxInterventions: 1,
606
- });
607
-
608
- const oldTime = Date.now() - (300_000 + 1000);
609
- mgr.addInstance(
610
- makeBgState({
611
- instanceId: "bgr_reset_counter",
612
- status: "running",
613
- lastEventAt: oldTime,
614
- lastToolOrTextAt: oldTime,
615
- interventionCount: 1,
616
- interventionAt: oldTime,
617
- interventionReason: "thinking loop (1m 0s without tool/text)",
618
- }),
619
- );
620
-
621
- // Simulate a text part arriving (progress signal)
622
- mgr.simulatePartUpdated("bgr_reset_counter", "text");
623
-
624
- const inst = mgr.getInstance("bgr_reset_counter")!;
625
- expect(inst.interventionCount).toBe(0);
626
- expect(inst.interventionAt).toBeUndefined();
627
- expect(inst.interventionReason).toBeUndefined();
628
- expect(inst.lastToolOrTextAt).toBeGreaterThanOrEqual(oldTime);
629
- });
630
-
631
- // Test 7: Thinking-only events do NOT reset intervention counter
632
- it("a thinking part does NOT update lastToolOrTextAt or reset interventionCount", async () => {
633
- const mgr = new FakeInstanceManagerForStall({
634
- stallTimeoutMs: 180_000,
635
- thinkingLoopTimeoutMs: 300_000,
636
- maxInterventions: 1,
637
- });
638
-
639
- const baseTime = Date.now() - 60_000;
640
- mgr.addInstance(
641
- makeBgState({
642
- instanceId: "bgr_thinking_only",
643
- status: "running",
644
- lastEventAt: baseTime,
645
- lastToolOrTextAt: baseTime, // set to 60s ago
646
- interventionCount: 0,
647
- }),
648
- );
649
-
650
- // Simulate a thinking part arriving
651
- mgr.simulatePartUpdated("bgr_thinking_only", "thinking");
652
-
653
- const inst = mgr.getInstance("bgr_thinking_only")!;
654
- // lastToolOrTextAt should NOT have been updated by the thinking part
655
- expect(inst.lastToolOrTextAt).toBe(baseTime);
656
- // interventionCount stays at 0 (was already 0 in this case)
657
- expect(inst.interventionCount).toBe(0);
658
- });
659
- });
660
-
661
- // ---------------------------------------------------------------------------
662
- // Group 5 — bg-status toView includes v0.3.0 fields
663
- // ---------------------------------------------------------------------------
664
-
665
- import type { InstanceView } from "../src/background.js";
666
-
667
- function toViewForTest(inst: import("../src/background-state.js").BackgroundState): InstanceView {
668
- const v: InstanceView = {
669
- instanceId: inst.instanceId,
670
- agent: inst.agent,
671
- status: inst.status,
672
- startedAt: inst.startedAt,
673
- toolCallCount: inst.toolCallCount,
674
- promptPreview: inst.promptPreview,
675
- parentAgent: inst.parentAgent,
676
- sessionId: inst.sessionId,
677
- lastEventAt: inst.lastEventAt,
678
- };
679
- if (inst.completedAt !== undefined) v.completedAt = inst.completedAt;
680
- if (inst.resultPreview !== undefined) v.resultPreview = inst.resultPreview;
681
- if (inst.error !== undefined) v.error = inst.error;
682
- if (inst.parentInstanceId !== undefined) v.parentInstanceId = inst.parentInstanceId;
683
- // v0.3.0: only surface intervention metadata when at least one
684
- // intervention has actually been sent.
685
- const interventionCount = inst.interventionCount ?? 0;
686
- if (interventionCount > 0) {
687
- v.interventionCount = interventionCount;
688
- if (inst.interventionAt !== undefined) v.interventionAt = inst.interventionAt;
689
- if (inst.interventionReason !== undefined) v.interventionReason = inst.interventionReason;
690
- }
691
- return v;
692
- }
693
-
694
- describe("bg-status toView — v0.3.0 fields", () => {
695
- it("toView includes lastEventAt, interventionCount, interventionAt, interventionReason after intervention", () => {
696
- const now = Date.now();
697
- const inst: import("../src/background-state.js").BackgroundState = {
698
- instanceId: "bgr_view_test",
699
- sessionId: "sess_view",
700
- agent: "mimir",
701
- status: "running",
702
- startedAt: now - 600_000,
703
- model: "minimax/minimax-m3",
704
- promptPreview: "Research X",
705
- toolCallCount: 0,
706
- parentAgent: "odin",
707
- logPath: path.join(os.tmpdir(), "test.log"),
708
- timeoutMs: 300_000,
709
- lastEventAt: now - 60_000,
710
- lastToolOrTextAt: now - 60_000,
711
- interventionCount: 2,
712
- interventionAt: now - 60_000,
713
- interventionReason: "thinking loop (5m 0s without tool/text)",
714
- };
715
-
716
- const view = toViewForTest(inst);
717
-
718
- expect(view.lastEventAt).toBe(now - 60_000);
719
- expect(view.interventionCount).toBe(2);
720
- expect(view.interventionAt).toBe(now - 60_000);
721
- expect(view.interventionReason).toBe("thinking loop (5m 0s without tool/text)");
722
- });
723
-
724
- it("toView omits intervention fields (not null) when interventionCount is 0 or undefined", () => {
725
- const now = Date.now();
726
- const inst: import("../src/background-state.js").BackgroundState = {
727
- instanceId: "bgr_view_no_intervention",
728
- sessionId: "sess_view2",
729
- agent: "mimir",
730
- status: "running",
731
- startedAt: now,
732
- model: "minimax/minimax-m3",
733
- promptPreview: "Research Y",
734
- toolCallCount: 0,
735
- parentAgent: "odin",
736
- logPath: path.join(os.tmpdir(), "test.log"),
737
- timeoutMs: 300_000,
738
- // v0.3.0 fields are absent (no intervention yet)
739
- };
740
-
741
- const view = toViewForTest(inst);
742
-
743
- // lastEventAt should still be present (always surfaced)
744
- expect(view.lastEventAt).toBeUndefined(); // not set in this instance
745
- // Intervention fields should be absent — not null, not 0, not present
746
- expect("interventionCount" in view).toBe(false);
747
- expect("interventionAt" in view).toBe(false);
748
- expect("interventionReason" in view).toBe(false);
749
- });
750
- });