@polderlabs/bizar 6.2.5 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/.claude/CLAUDE.md +15 -0
  2. package/.claude/agents/_shared/AGENT_BASELINE.md +168 -0
  3. package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
  4. package/.claude/agents/_shared/SKILLS.md +109 -0
  5. package/.claude/agents/agent-browser.md +80 -0
  6. package/.claude/agents/baldr.md +49 -0
  7. package/.claude/agents/forseti.md +51 -0
  8. package/.claude/agents/frigg.md +42 -0
  9. package/.claude/agents/heimdall.md +33 -0
  10. package/.claude/agents/hermod.md +53 -0
  11. package/.claude/agents/mimir.md +49 -0
  12. package/.claude/agents/odin.md +287 -0
  13. package/.claude/agents/quick.md +34 -0
  14. package/.claude/agents/semble-search.md +50 -0
  15. package/.claude/agents/thor.md +53 -0
  16. package/.claude/agents/tyr.md +56 -0
  17. package/.claude/agents/vidarr.md +54 -0
  18. package/.claude/agents/vor.md +53 -0
  19. package/.claude/commands/audit.md +25 -0
  20. package/.claude/commands/bizar.md +22 -0
  21. package/.claude/commands/explain.md +17 -0
  22. package/.claude/commands/init.md +29 -0
  23. package/.claude/commands/learn.md +44 -0
  24. package/.claude/commands/plan.md +35 -0
  25. package/.claude/commands/plow-through.md +50 -0
  26. package/.claude/commands/pr-review.md +49 -0
  27. package/.claude/commands/setup-provider.md +96 -0
  28. package/.claude/commands/tailscale-serve.md +100 -0
  29. package/.claude/commands/team.md +132 -0
  30. package/.claude/commands/test.md +62 -0
  31. package/.claude/commands/validate.md +68 -0
  32. package/.claude/commands/visual-plan.md +24 -0
  33. package/.claude/hooks/README.md +92 -0
  34. package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
  35. package/.claude/hooks/pretooluse-bash.mjs +81 -0
  36. package/.claude/hooks/pretooluse-editwrite.mjs +139 -0
  37. package/.claude/hooks/sessionend-recall.mjs +74 -0
  38. package/.claude/hooks/sessionstart-prime.mjs +80 -0
  39. package/.claude/hooks/userpromptsubmit-tag.mjs +80 -0
  40. package/.claude/settings.json +116 -0
  41. package/.claude/skills/9router/SKILL.md +80 -0
  42. package/.claude/skills/9router-chat/SKILL.md +73 -0
  43. package/.claude/skills/9router-embeddings/SKILL.md +69 -0
  44. package/.claude/skills/9router-image/SKILL.md +86 -0
  45. package/.claude/skills/9router-stt/SKILL.md +79 -0
  46. package/.claude/skills/9router-tts/SKILL.md +80 -0
  47. package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
  48. package/.claude/skills/9router-web-search/SKILL.md +91 -0
  49. package/.claude/skills/bizar/README.md +9 -0
  50. package/.claude/skills/bizar/SKILL.md +450 -0
  51. package/.claude/skills/cpp-coding-standards/README.md +28 -0
  52. package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
  53. package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
  54. package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
  55. package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  56. package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  57. package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  58. package/.claude/skills/cpp-testing/README.md +28 -0
  59. package/.claude/skills/cpp-testing/SKILL.md +304 -0
  60. package/.claude/skills/cpp-testing/references/coverage.md +370 -0
  61. package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
  62. package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  63. package/.claude/skills/cpp-testing/references/mocking.md +364 -0
  64. package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
  65. package/.claude/skills/cubesandbox/SKILL.md +148 -0
  66. package/.claude/skills/embedded-esp-idf/README.md +41 -0
  67. package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
  68. package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  69. package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
  70. package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  71. package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
  72. package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  73. package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  74. package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
  75. package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  76. package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  77. package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  78. package/.claude/skills/glyph/SKILL.md +163 -0
  79. package/.claude/skills/harness-engineering/SKILL.md +143 -0
  80. package/.claude/skills/lightrag/SKILL.md +81 -0
  81. package/.claude/skills/memory-protocol/SKILL.md +105 -0
  82. package/.claude/skills/obsidian/SKILL.md +306 -0
  83. package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
  84. package/.claude/skills/self-improvement/SKILL.md +64 -0
  85. package/README.md +87 -59
  86. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-B58aiJiE.js} +1 -1
  87. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-B58aiJiE.js.map} +1 -1
  88. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js → MobileChat-BJrqwVDd.js} +1 -1
  89. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-BJrqwVDd.js.map} +1 -1
  90. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js → MobileSettings-CEQNJNLJ.js} +1 -1
  91. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-CEQNJNLJ.js.map} +1 -1
  92. package/bizar-dash/dist/assets/{main-DYiZqMrn.js → main-IvfQAOfy.js} +1 -1
  93. package/bizar-dash/dist/assets/{main-DYiZqMrn.js.map → main-IvfQAOfy.js.map} +1 -1
  94. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js → markdown-tOLaD6nm.js} +1 -1
  95. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-tOLaD6nm.js.map} +1 -1
  96. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js → mobile-DYCHcUpq.js} +1 -1
  97. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DYCHcUpq.js.map} +1 -1
  98. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js → mobile-layout-CBHjpwsb.js} +2 -2
  99. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js.map → mobile-layout-CBHjpwsb.js.map} +1 -1
  100. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js → useSlashCommands-Bd7_FA6U.js} +2 -2
  101. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js.map → useSlashCommands-Bd7_FA6U.js.map} +1 -1
  102. package/bizar-dash/dist/assets/{vendor-CeHGtduv.js → vendor-C843201K.js} +12 -12
  103. package/bizar-dash/dist/assets/vendor-C843201K.js.map +1 -0
  104. package/bizar-dash/dist/index.html +6 -6
  105. package/bizar-dash/dist/mobile.html +2 -2
  106. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  107. package/bizar-dash/src/server/api.mjs +4 -4
  108. package/bizar-dash/src/server/background-store.mjs +15 -16
  109. package/bizar-dash/src/server/bg-poller.mjs +25 -35
  110. package/bizar-dash/src/server/bg-retry.mjs +66 -194
  111. package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
  112. package/bizar-dash/src/server/claude-info.mjs +412 -0
  113. package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
  114. package/bizar-dash/src/server/claude-sdk.mjs +191 -0
  115. package/bizar-dash/src/server/providers-store.mjs +22 -49
  116. package/bizar-dash/src/server/routes/background.mjs +24 -20
  117. package/bizar-dash/src/server/routes/chat.mjs +223 -326
  118. package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
  119. package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
  120. package/bizar-dash/src/server/routes/tasks.mjs +20 -28
  121. package/bizar-dash/src/server/task-delegator.mjs +76 -77
  122. package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
  123. package/bizar-dash/src/web/views/Chat.tsx +8 -8
  124. package/cli/bin.mjs +17 -18
  125. package/cli/commands/claude-cmd.mjs +348 -0
  126. package/cli/commands/install.mjs +12 -11
  127. package/cli/commands/validate.mjs +252 -325
  128. package/cli/dev-link.test.mjs +11 -11
  129. package/cli/doctor.mjs +131 -213
  130. package/cli/install.mjs +33 -37
  131. package/cli/install.test.mjs +9 -9
  132. package/cli/provision-claude.mjs +893 -0
  133. package/cli/provision.mjs +224 -227
  134. package/cli/utils.mjs +72 -58
  135. package/config/AGENTS.md +28 -28
  136. package/install.sh +66 -44
  137. package/package.json +19 -11
  138. package/packages/sdk/ARCHITECTURE.md +40 -36
  139. package/packages/sdk/package-lock.json +32 -0
  140. package/packages/sdk/package.json +15 -5
  141. package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
  142. package/packages/sdk/src/fingerprint.ts +0 -0
  143. package/packages/sdk/src/index.ts +38 -73
  144. package/packages/sdk/src/mcp/bin.ts +51 -0
  145. package/packages/sdk/src/mcp/server.ts +498 -0
  146. package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
  147. package/packages/sdk/tests/sdk.test.mjs +148 -0
  148. package/packages/sdk/vitest.config.ts +1 -1
  149. package/scripts/bh-full-e2e.mjs +166 -363
  150. package/scripts/check-deps.mjs +28 -46
  151. package/scripts/mirror-claude-md.sh +78 -0
  152. package/scripts/test-in-container.sh +31 -9
  153. package/templates/clean-state-checklist.md +3 -3
  154. package/templates/sprint-contract.md +2 -2
  155. package/bizar-dash/.bizar/activity.log +0 -3
  156. package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
  157. package/bizar-dash/.omx/state/session.json +0 -10
  158. package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
  159. package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
  160. package/bizar-dash/bizar-design/canvas.html +0 -281
  161. package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
  162. package/bizar-dash/bizar-design/components.css +0 -1665
  163. package/bizar-dash/bizar-design/components.html +0 -557
  164. package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
  165. package/bizar-dash/bizar-design/desktop/agents.html +0 -74
  166. package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
  167. package/bizar-dash/bizar-design/desktop/memory.html +0 -75
  168. package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
  169. package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
  170. package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
  171. package/bizar-dash/bizar-design/desktop/overview.html +0 -95
  172. package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
  173. package/bizar-dash/bizar-design/desktop/settings.html +0 -70
  174. package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
  175. package/bizar-dash/bizar-design/icons.svg +0 -44
  176. package/bizar-dash/bizar-design/index.html +0 -192
  177. package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
  178. package/bizar-dash/bizar-design/mobile/agents.html +0 -29
  179. package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
  180. package/bizar-dash/bizar-design/mobile/memory.html +0 -28
  181. package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
  182. package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
  183. package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
  184. package/bizar-dash/bizar-design/mobile/overview.html +0 -33
  185. package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
  186. package/bizar-dash/bizar-design/mobile/settings.html +0 -28
  187. package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
  188. package/bizar-dash/bizar-design/prototype.js +0 -114
  189. package/bizar-dash/bizar-design/tokens.css +0 -106
  190. package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
  191. package/bizar-dash/node_modules/.package-lock.json +0 -6
  192. package/bizar-dash/package-lock.json +0 -6
  193. package/bizar-dash/src/server/cline-sdk.mjs +0 -132
  194. package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
  195. package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
  196. package/cli/commands/cline-cmd.mjs +0 -289
  197. package/cli/commands/validate.test.mjs +0 -379
  198. package/cli/doctor.test.mjs +0 -350
  199. package/config/cline.json.template +0 -342
  200. package/packages/sdk/src/client.ts +0 -188
  201. package/packages/sdk/src/cline-events.ts +0 -134
  202. package/packages/sdk/src/cline-types.ts +0 -66
  203. package/packages/sdk/src/cline.ts +0 -339
  204. package/packages/sdk/src/errors.ts +0 -129
  205. package/packages/sdk/src/events.ts +0 -153
  206. package/packages/sdk/src/types.ts +0 -176
  207. package/packages/sdk/tests/client.test.ts +0 -217
  208. package/packages/sdk/tests/errors.test.ts +0 -108
  209. package/packages/sdk/tests/events.test.ts +0 -139
  210. package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
  211. package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
  212. package/plugins/bizar/ARCHITECTURE.md +0 -142
  213. package/plugins/bizar/CONSTRAINTS.md +0 -67
  214. package/plugins/bizar/LICENSE +0 -21
  215. package/plugins/bizar/README.md +0 -448
  216. package/plugins/bizar/index.ts +0 -925
  217. package/plugins/bizar/package.json +0 -39
  218. package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
  219. package/plugins/bizar/src/background-state.ts +0 -641
  220. package/plugins/bizar/src/background.ts +0 -1806
  221. package/plugins/bizar/src/cline-runner.ts +0 -203
  222. package/plugins/bizar/src/clineruntime.ts +0 -227
  223. package/plugins/bizar/src/commands-impl.ts +0 -151
  224. package/plugins/bizar/src/commands.ts +0 -1799
  225. package/plugins/bizar/src/compaction.d.mts +0 -48
  226. package/plugins/bizar/src/compaction.mjs +0 -192
  227. package/plugins/bizar/src/dashboard-client.ts +0 -233
  228. package/plugins/bizar/src/event-stream.ts +0 -606
  229. package/plugins/bizar/src/fingerprint.ts +0 -120
  230. package/plugins/bizar/src/handoff.ts +0 -79
  231. package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
  232. package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
  233. package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
  234. package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
  235. package/plugins/bizar/src/http-client.ts +0 -467
  236. package/plugins/bizar/src/key-rotation.ts +0 -218
  237. package/plugins/bizar/src/logger.ts +0 -144
  238. package/plugins/bizar/src/loop-engineering.ts +0 -241
  239. package/plugins/bizar/src/loop.ts +0 -176
  240. package/plugins/bizar/src/mistake-recovery.ts +0 -98
  241. package/plugins/bizar/src/odin.ts +0 -227
  242. package/plugins/bizar/src/options.ts +0 -470
  243. package/plugins/bizar/src/plan-fs.ts +0 -323
  244. package/plugins/bizar/src/reasoning-clean.ts +0 -454
  245. package/plugins/bizar/src/report.ts +0 -178
  246. package/plugins/bizar/src/research-prompt.ts +0 -35
  247. package/plugins/bizar/src/serve-info.ts +0 -228
  248. package/plugins/bizar/src/serve.ts +0 -496
  249. package/plugins/bizar/src/settings.ts +0 -349
  250. package/plugins/bizar/src/state.ts +0 -298
  251. package/plugins/bizar/src/tool-discipline.ts +0 -105
  252. package/plugins/bizar/src/tools/agent-browser.ts +0 -315
  253. package/plugins/bizar/src/tools/bg-collect.ts +0 -131
  254. package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
  255. package/plugins/bizar/src/tools/bg-kill.ts +0 -116
  256. package/plugins/bizar/src/tools/bg-pause.ts +0 -99
  257. package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
  258. package/plugins/bizar/src/tools/bg-resume.ts +0 -94
  259. package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
  260. package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
  261. package/plugins/bizar/src/tools/bg-status.ts +0 -130
  262. package/plugins/bizar/src/tools/graph-query.ts +0 -278
  263. package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
  264. package/plugins/bizar/src/tools/memory-list.ts +0 -43
  265. package/plugins/bizar/src/tools/memory-read.ts +0 -69
  266. package/plugins/bizar/src/tools/memory-search.ts +0 -47
  267. package/plugins/bizar/src/tools/memory-write.ts +0 -54
  268. package/plugins/bizar/src/tools/open-kb.ts +0 -198
  269. package/plugins/bizar/src/tools/plan-action.ts +0 -785
  270. package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
  271. package/plugins/bizar/src/tools/sandbox.ts +0 -232
  272. package/plugins/bizar/src/tools/team-spawn.ts +0 -73
  273. package/plugins/bizar/src/tools/team-status.ts +0 -76
  274. package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
  275. package/plugins/bizar/src/trajectory.ts +0 -104
  276. package/plugins/bizar/tests/README.md +0 -99
  277. package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
  278. package/plugins/bizar/tests/background-state.test.ts +0 -277
  279. package/plugins/bizar/tests/background.test.ts +0 -402
  280. package/plugins/bizar/tests/block.test.ts +0 -195
  281. package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
  282. package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
  283. package/plugins/bizar/tests/commands-impl.test.ts +0 -316
  284. package/plugins/bizar/tests/commands.test.ts +0 -584
  285. package/plugins/bizar/tests/compaction.test.ts +0 -264
  286. package/plugins/bizar/tests/config.test.ts +0 -128
  287. package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
  288. package/plugins/bizar/tests/dispose.test.ts +0 -336
  289. package/plugins/bizar/tests/event-stream.test.ts +0 -409
  290. package/plugins/bizar/tests/event.test.ts +0 -262
  291. package/plugins/bizar/tests/fingerprint.test.ts +0 -190
  292. package/plugins/bizar/tests/http-client.test.ts +0 -404
  293. package/plugins/bizar/tests/init-helpers.test.ts +0 -203
  294. package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
  295. package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
  296. package/plugins/bizar/tests/key-rotation.test.ts +0 -396
  297. package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
  298. package/plugins/bizar/tests/loop.test.ts +0 -397
  299. package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
  300. package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
  301. package/plugins/bizar/tests/odin.test.ts +0 -125
  302. package/plugins/bizar/tests/options.test.ts +0 -329
  303. package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
  304. package/plugins/bizar/tests/safety.test.ts +0 -281
  305. package/plugins/bizar/tests/serve.test.ts +0 -339
  306. package/plugins/bizar/tests/settings.test.ts +0 -351
  307. package/plugins/bizar/tests/stall-think.test.ts +0 -750
  308. package/plugins/bizar/tests/state.test.ts +0 -276
  309. package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
  310. package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
  311. package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
  312. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
  313. package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
  314. package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
  315. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
  316. package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
  317. package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
  318. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
  319. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
  320. package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
  321. package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
  322. package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
  323. package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
  324. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
  325. package/plugins/bizar/tests/tools/sandbox.test.ts +0 -117
  326. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
  327. package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
  328. package/plugins/bizar/tsconfig.json +0 -29
  329. package/scripts/mirror-agents-md.sh +0 -69
@@ -1,606 +0,0 @@
1
- /**
2
- * event-stream.ts
3
- *
4
- * Global SSE subscription for cline serve events (v0.4.2 spec §2.1, §4).
5
- *
6
- * Design contract:
7
- * - ONE `GET /event?directory=<worktree>` connection per plugin process.
8
- * - Events are filtered in-memory by `sessionID` and dispatched to
9
- * per-session handlers registered via `onSessionEvent`.
10
- * - The connection auto-reconnects with exponential backoff
11
- * (1s, 2s, 4s, 8s, 16s, capped at 30s) on unexpected drops.
12
- * - During the gap between drop and reconnect, in-flight instances are
13
- * NOT marked failed (they may still complete once we reconnect).
14
- * - `disconnect()` closes the stream and prevents further reconnects.
15
- *
16
- * v0.4.3 — CloudEvents-style schema support (see
17
- * `.bizar/cline-sse-investigation.md`):
18
- * - The actual event schema on cline serve 1.17.7 has two flavors:
19
- *
20
- * 1) **Direct events** (flat JSON, native `type` field):
21
- * ```
22
- * { id, type: "session.idle", properties: { sessionID } }
23
- * { id, type: "message.part.delta", properties: { sessionID, ... } }
24
- * ```
25
- *
26
- * 2) **Sync events** (CloudEvents-style, wrapped):
27
- * ```
28
- * { type: "sync", syncEvent: {
29
- * type: "session.created.1", // `.1` version suffix
30
- * id, seq, aggregateID,
31
- * data: { sessionID, info }
32
- * }}
33
- * ```
34
- * Sync events are the primary delivery mechanism for session and
35
- * message lifecycle events. Event names like `session.created.1`,
36
- * `message.part.updated.1` carry a numeric version suffix.
37
- *
38
- * - This module unwraps sync events and strips the version suffix so
39
- * downstream consumers (InstanceManager) see the same logical
40
- * `StreamEvent` shape regardless of which wire format arrived.
41
- *
42
- * This module is a pure transport. The `InstanceManager` registers a
43
- * handler per instance that does the actual state work (tool counting,
44
- * loop-guard detection, status transitions, awaiting collect).
45
- */
46
-
47
- import type { HttpClient, HttpResult } from "./http-client.js";
48
-
49
- // --- Logger interface -----------------------------------------------------
50
-
51
- /**
52
- * Minimal Logger interface — matches the shape in `state.ts` / `logger.ts`.
53
- */
54
- export interface Logger {
55
- log(opts: { level: "debug" | "info" | "warn" | "error"; message: string }): void;
56
- debug(message: string): void;
57
- info(message: string): void;
58
- warn(message: string): void;
59
- error(message: string): void;
60
- }
61
-
62
- // --- Public event types ---------------------------------------------------
63
-
64
- /**
65
- * The subset of session lifecycle events the plugin cares about. Each
66
- * variant carries a `sessionID` plus the event-specific payload.
67
- *
68
- * The `type` field follows cline's event namespacing
69
- * (`session.created`, `session.idle`, `message.part.updated`, …).
70
- *
71
- * Note: there is no catch-all variant on purpose. The catch-all would
72
- * have `type: string` which would defeat type narrowing on
73
- * `ev.type === "session.idle"` checks. Untyped events are dropped
74
- * silently in `dispatchEvent`.
75
- */
76
- export type StreamEvent =
77
- | { type: "session.created"; sessionID: string; raw: unknown }
78
- | { type: "session.updated"; sessionID: string; raw: unknown }
79
- | { type: "session.deleted"; sessionID: string; raw: unknown }
80
- | { type: "session.idle"; sessionID: string; raw: unknown }
81
- | { type: "session.error"; sessionID: string; error?: string; raw: unknown }
82
- | { type: "message.updated"; sessionID: string; messageID: string; raw: unknown }
83
- | {
84
- type: "message.part.updated";
85
- sessionID: string;
86
- messageID: string;
87
- partID: string;
88
- part: { type: string; text?: string; error?: string; state?: { status?: string; error?: string } };
89
- raw: unknown;
90
- };
91
-
92
- /**
93
- * A per-session event handler. Called for every event whose `sessionID`
94
- * matches the registration. Handlers are called synchronously; long
95
- * work should be scheduled (await / setImmediate) inside the handler.
96
- */
97
- export type SessionEventHandler = (event: StreamEvent) => void;
98
-
99
- // --- Stream class ---------------------------------------------------------
100
-
101
- /**
102
- * Owns the single global SSE connection. The plugin constructs one
103
- * instance at init time and disposes it on plugin shutdown.
104
- *
105
- * Public surface (interface contract for Thor's tests):
106
- * - `connect()` — open the SSE stream. Idempotent (no-op if already connected).
107
- * - `disconnect()` — close the stream; stop reconnecting.
108
- * - `onSessionEvent(sessionId, handler)` — register a per-session handler.
109
- * Returns an unsubscribe function.
110
- *
111
- * Internal lifecycle (not part of the contract):
112
- * - `readLoop()` runs until disconnect; on error, schedules a reconnect.
113
- * - `parseSseChunk()` splits a raw chunk into individual events.
114
- * - `dispatch()` routes each event to handlers whose `sessionID` matches.
115
- */
116
- export class EventStream {
117
- private _baseUrl: string;
118
- private _directory: string;
119
- private _authHeader: string;
120
- private _logger: Logger;
121
- private _http: HttpClient;
122
- private _handlers = new Map<string, Set<SessionEventHandler>>();
123
- /** Global handler invoked for every event regardless of session (v0.7.0). */
124
- private _globalHandler: SessionEventHandler | null = null;
125
- private _connected = false;
126
- private _aborted = false;
127
- private _abortController: AbortController | null = null;
128
- private _reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
129
- private _reconnectAttempt = 0;
130
- private _reconnectTimer: ReturnType<typeof setTimeout> | null = null;
131
- private _connectPromise: Promise<void> | null = null;
132
-
133
- /**
134
- * Register a global handler invoked for every event (regardless of
135
- * session). Used to forward events to the dashboard publisher. Returns
136
- * an unsubscribe function. (v0.7.0-alpha.1 — wired into the v2
137
- * SDK dashboard-client.ts bridge.)
138
- */
139
- onEvent(handler: SessionEventHandler): () => void {
140
- this._globalHandler = handler;
141
- return () => {
142
- if (this._globalHandler === handler) {
143
- this._globalHandler = null;
144
- }
145
- };
146
- }
147
-
148
- constructor(opts: {
149
- baseUrl: string;
150
- directory: string;
151
- authHeader: string;
152
- logger: Logger;
153
- http: HttpClient;
154
- }) {
155
- this._baseUrl = opts.baseUrl.replace(/\/+$/, "");
156
- this._directory = opts.directory;
157
- this._authHeader = opts.authHeader;
158
- this._logger = opts.logger;
159
- this._http = opts.http;
160
- }
161
-
162
- // --- Getters ------------------------------------------------------------
163
-
164
- get connected(): boolean {
165
- return this._connected;
166
- }
167
-
168
- get directory(): string {
169
- return this._directory;
170
- }
171
-
172
- // --- Lifecycle ----------------------------------------------------------
173
-
174
- /**
175
- * Open the SSE connection. Idempotent: a second call while a connection
176
- * is in progress is a no-op that returns the in-flight promise.
177
- *
178
- * The first read block-waits up to 5s for the initial event so the
179
- * caller can confirm the connection is alive (spec §2.1).
180
- */
181
- async connect(): Promise<void> {
182
- if (this._aborted) {
183
- throw new Error("EventStream: cannot connect after disconnect()");
184
- }
185
- if (this._connectPromise !== null) {
186
- return this._connectPromise;
187
- }
188
- this._connectPromise = this.openConnection();
189
- try {
190
- await this._connectPromise;
191
- } finally {
192
- this._connectPromise = null;
193
- }
194
- }
195
-
196
- /**
197
- * Close the stream and stop further reconnects. Idempotent.
198
- */
199
- async disconnect(): Promise<void> {
200
- this._aborted = true;
201
- if (this._reconnectTimer !== null) {
202
- clearTimeout(this._reconnectTimer);
203
- this._reconnectTimer = null;
204
- }
205
- if (this._abortController !== null) {
206
- try {
207
- this._abortController.abort();
208
- } catch {
209
- // ignore
210
- }
211
- this._abortController = null;
212
- }
213
- if (this._reader !== null) {
214
- try {
215
- await this._reader.cancel();
216
- } catch {
217
- // ignore
218
- }
219
- try {
220
- this._reader.releaseLock();
221
- } catch {
222
- // ignore
223
- }
224
- this._reader = null;
225
- }
226
- this._connected = false;
227
- }
228
-
229
- /**
230
- * Register a handler for events of one session. Returns an unsubscribe
231
- * function. Multiple handlers per session are allowed.
232
- */
233
- onSessionEvent(sessionId: string, handler: SessionEventHandler): () => void {
234
- if (typeof sessionId !== "string" || sessionId.length === 0) {
235
- throw new Error("EventStream.onSessionEvent: sessionId must be non-empty");
236
- }
237
- let set = this._handlers.get(sessionId);
238
- if (!set) {
239
- set = new Set();
240
- this._handlers.set(sessionId, set);
241
- }
242
- set.add(handler);
243
- return () => {
244
- const s = this._handlers.get(sessionId);
245
- if (!s) return;
246
- s.delete(handler);
247
- if (s.size === 0) this._handlers.delete(sessionId);
248
- };
249
- }
250
-
251
- // --- Internal: connection lifecycle ------------------------------------
252
-
253
- private async openConnection(): Promise<void> {
254
- this._abortController = new AbortController();
255
- const result: HttpResult<ReadableStream> = await this._http.fetchEventStream(
256
- this._directory,
257
- this._abortController.signal,
258
- );
259
- if (!result.ok) {
260
- this._connected = false;
261
- throw new Error(`EventStream: failed to open SSE: ${result.error}`);
262
- }
263
- this._connected = true;
264
- this._reconnectAttempt = 0;
265
- this._logger.info(
266
- `bizar: SSE event stream open (directory=${this._directory})`,
267
- );
268
- // Start the read loop in the background.
269
- void this.readLoop(result.value);
270
- }
271
-
272
- /**
273
- * Read the SSE stream chunk by chunk, parse events, and dispatch them.
274
- * On error, schedule a reconnect (unless we're aborted).
275
- */
276
- private async readLoop(stream: ReadableStream<Uint8Array>): Promise<void> {
277
- const reader = stream.getReader() as ReadableStreamDefaultReader<Uint8Array>;
278
- this._reader = reader;
279
- const decoder = new TextDecoder("utf-8");
280
- let buffer = "";
281
- try {
282
- while (!this._aborted) {
283
- const { done, value } = await reader.read();
284
- if (done) {
285
- // Stream closed cleanly. This is unexpected unless we initiated it.
286
- if (!this._aborted) {
287
- this._logger.warn("bizar: SSE stream closed unexpectedly; will reconnect");
288
- this.scheduleReconnect();
289
- }
290
- return;
291
- }
292
- const chunk = decoder.decode(value, { stream: true });
293
- buffer += chunk;
294
- // Process as many complete events as we have.
295
- let sep: number;
296
- // SSE events are separated by a blank line (\n\n or \r\n\r\n).
297
- while ((sep = buffer.indexOf("\n\n")) >= 0 || (sep = buffer.indexOf("\r\n\r\n")) >= 0) {
298
- const rawEvent = buffer.slice(0, sep);
299
- buffer = buffer.slice(sep + (buffer[sep] === "\r" ? 4 : 2));
300
- this.processSseBlock(rawEvent);
301
- }
302
- }
303
- } catch (err: unknown) {
304
- if (this._aborted) return;
305
- const msg = err instanceof Error ? err.message : String(err);
306
- this._logger.warn(`bizar: SSE read error: ${msg}; will reconnect`);
307
- this.scheduleReconnect();
308
- } finally {
309
- this._connected = false;
310
- try {
311
- this._reader?.releaseLock();
312
- } catch {
313
- // ignore
314
- }
315
- this._reader = null;
316
- }
317
- }
318
-
319
- private processSseBlock(block: string): void {
320
- if (block.trim() === "") return;
321
- // An SSE block has lines like `event: <name>` and `data: <payload>`.
322
- // Some servers only send `data:`. We treat unknown prefix lines as
323
- // comments and skip them.
324
- let eventName: string | null = null;
325
- const dataLines: string[] = [];
326
- for (const line of block.split(/\r?\n/)) {
327
- if (line === "" || line.startsWith(":")) continue;
328
- const colon = line.indexOf(":");
329
- if (colon < 0) continue;
330
- const field = line.slice(0, colon);
331
- let value = line.slice(colon + 1);
332
- if (value.startsWith(" ")) value = value.slice(1);
333
- if (field === "event") {
334
- eventName = value;
335
- } else if (field === "data") {
336
- dataLines.push(value);
337
- }
338
- }
339
- if (dataLines.length === 0) return;
340
- const data = dataLines.join("\n");
341
- let parsed: unknown;
342
- try {
343
- parsed = JSON.parse(data);
344
- } catch {
345
- this._logger.debug(`bizar: SSE: dropping non-JSON data (${data.slice(0, 80)}…)`);
346
- return;
347
- }
348
- this.dispatchEvent(eventName, parsed);
349
- }
350
-
351
- /**
352
- * Map a raw cline event to a `StreamEvent` and dispatch to handlers.
353
- *
354
- * v0.4.3: handles two wire formats.
355
- * 1) Direct events — `{type, properties: {sessionID, ...}}`.
356
- * 2) Sync events — `{type: "sync", syncEvent: {type: "x.y.1", data: {...}}}`.
357
- * We unwrap the sync wrapper and set `obj` to `syncEvent.data` so
358
- * downstream code can read `obj.sessionID`, `obj.part`, etc.
359
- *
360
- * After unwrapping, the event type from sync events has a version
361
- * suffix (e.g. `session.created.1`). We strip the suffix so
362
- * downstream code can match on `session.created` (the version is
363
- * for the wire format, not the plugin's logical event name).
364
- */
365
- private dispatchEvent(eventName: string | null, raw: unknown): void {
366
- let obj = raw as Record<string, unknown> | null;
367
- if (obj === null || typeof obj !== "object") return;
368
-
369
- // Step 1: Detect sync event wrapper, unwrap to the inner data.
370
- // Sync events have shape: {type: "sync", syncEvent: {type: "x.y.1", data: {...}}}
371
- // After unwrap, obj points to the inner data so the rest of this
372
- // method can read fields directly (obj.sessionID, obj.part, etc.).
373
- let innerType: string | null = null;
374
- if (obj.type === "sync" && obj.syncEvent) {
375
- const syncEvent = obj.syncEvent as Record<string, unknown> | null;
376
- if (syncEvent && typeof syncEvent === "object") {
377
- if (typeof syncEvent.type === "string") innerType = syncEvent.type;
378
- const syncData = syncEvent.data;
379
- if (syncData && typeof syncData === "object") {
380
- obj = syncData as Record<string, unknown>;
381
- }
382
- }
383
- }
384
-
385
- // Step 2: Resolve the event type and strip the version suffix.
386
- // Sync event types look like "session.created.1"; we strip to "session.created".
387
- const typeFromObj = innerType ?? (typeof obj.type === "string" ? obj.type : null);
388
- const rawType = typeFromObj ?? eventName ?? "unknown";
389
- const type = stripVersionSuffix(rawType);
390
-
391
- const sessionID = extractSessionId(obj);
392
- const messageID = extractMessageId(obj);
393
- // After unwrap, `obj.part` exists for `message.part.updated.*` events
394
- // (sync) and may exist on the top-level payload. We also fall back to
395
- // `obj.partID` on the part object itself for sync events whose
396
- // `data` block does not carry `partID` separately.
397
- const part = obj.part as { type?: string; text?: string; error?: string; state?: { status?: string; error?: string }; id?: string } | undefined;
398
- const partID = extractPartId(obj, part);
399
-
400
- // Build the typed event. We use a temporary permissive shape and only
401
- // assign to a strongly-typed StreamEvent at the end of each branch.
402
- let event: StreamEvent | null = null;
403
- if (type === "message.part.updated" && sessionID && part) {
404
- const partShape: { type: string; text?: string; error?: string; state?: { status?: string; error?: string } } = {
405
- type: part.type ?? "unknown",
406
- };
407
- if (part.text !== undefined) partShape.text = part.text;
408
- if (part.error !== undefined) partShape.error = part.error;
409
- if (part.state !== undefined) partShape.state = part.state;
410
- event = {
411
- type: "message.part.updated",
412
- sessionID,
413
- messageID: messageID ?? "",
414
- partID,
415
- part: partShape,
416
- raw,
417
- };
418
- } else if (type === "message.updated" && sessionID) {
419
- event = { type: "message.updated", sessionID, messageID: messageID ?? "", raw };
420
- } else if (type === "session.error" && sessionID) {
421
- // session.error may carry the error string on `properties.error`
422
- // (direct event) or on `data.error` (sync, but we already
423
- // unwrapped, so both paths land at `obj.error`).
424
- const props = obj.properties;
425
- let errorField: string | undefined =
426
- typeof obj.error === "string" ? obj.error : undefined;
427
- if (errorField === undefined && props && typeof props === "object") {
428
- const p = props as Record<string, unknown>;
429
- if (typeof p.error === "string") errorField = p.error;
430
- }
431
- event = errorField !== undefined
432
- ? { type: "session.error", sessionID, error: errorField, raw }
433
- : { type: "session.error", sessionID, raw };
434
- } else if (type === "session.idle" && sessionID) {
435
- event = { type: "session.idle", sessionID, raw };
436
- } else if (type === "session.created" && sessionID) {
437
- event = { type: "session.created", sessionID, raw };
438
- } else if (type === "session.updated" && sessionID) {
439
- event = { type: "session.updated", sessionID, raw };
440
- } else if (type === "session.deleted" && sessionID) {
441
- event = { type: "session.deleted", sessionID, raw };
442
- } else {
443
- // Unknown / untyped event — drop silently. Logged at debug level.
444
- // We log the *raw* type (with version suffix) for diagnostics.
445
- this._logger.debug(
446
- `bizar: SSE: dropping untyped event (rawType=${rawType}${sessionID ? ` sessionID=${sessionID}` : ""})`,
447
- );
448
- return;
449
- }
450
-
451
- if (event === null) {
452
- this._logger.debug(`bizar: SSE: event without sessionID (rawType=${rawType})`);
453
- return;
454
- }
455
- this.dispatchToHandlers(sessionID, event);
456
- }
457
-
458
- private dispatchToHandlers(sessionID: string, event: StreamEvent): void {
459
- // v0.7.0-alpha.1 — Forward every event to the global handler
460
- // (typically the dashboard publisher) BEFORE the per-session dispatch.
461
- // Failures here are isolated so one bad handler doesn't break the
462
- // dispatch chain for other subscribers.
463
- if (this._globalHandler) {
464
- try {
465
- this._globalHandler(event);
466
- } catch (err: unknown) {
467
- const msg = err instanceof Error ? err.message : String(err);
468
- this._logger.warn(
469
- `bizar: SSE global handler threw for session ${sessionID} (type=${event.type}): ${msg}`,
470
- );
471
- }
472
- }
473
-
474
- const set = this._handlers.get(sessionID);
475
- if (!set || set.size === 0) {
476
- this._logger.debug(
477
- `bizar: SSE event for untracked session ${sessionID} (type=${event.type})`,
478
- );
479
- return;
480
- }
481
- // Snapshot to avoid mutation during iteration.
482
- for (const handler of [...set]) {
483
- try {
484
- handler(event);
485
- } catch (err: unknown) {
486
- const msg = err instanceof Error ? err.message : String(err);
487
- this._logger.warn(
488
- `bizar: SSE handler threw for session ${sessionID} (type=${event.type}): ${msg}`,
489
- );
490
- }
491
- }
492
- }
493
-
494
- // --- Reconnect ----------------------------------------------------------
495
-
496
- private scheduleReconnect(): void {
497
- if (this._aborted) return;
498
- if (this._reconnectTimer !== null) return;
499
- // Exponential backoff: 1s, 2s, 4s, 8s, 16s, capped at 30s.
500
- const base = 1000 * Math.pow(2, this._reconnectAttempt);
501
- const delay = Math.min(30_000, base);
502
- this._reconnectAttempt += 1;
503
- this._logger.warn(`bizar: SSE reconnecting in ${delay}ms (attempt ${this._reconnectAttempt})`);
504
- this._reconnectTimer = setTimeout(() => {
505
- this._reconnectTimer = null;
506
- if (this._aborted) return;
507
- void this.attemptReconnect();
508
- }, delay);
509
- }
510
-
511
- private async attemptReconnect(): Promise<void> {
512
- try {
513
- this._abortController = new AbortController();
514
- const result = await this._http.fetchEventStream(
515
- this._directory,
516
- this._abortController.signal,
517
- );
518
- if (!result.ok) {
519
- this._logger.warn(`bizar: SSE reconnect failed: ${result.error}`);
520
- this.scheduleReconnect();
521
- return;
522
- }
523
- this._connected = true;
524
- this._reconnectAttempt = 0;
525
- this._logger.info("bizar: SSE reconnected");
526
- void this.readLoop(result.value);
527
- } catch (err: unknown) {
528
- if (this._aborted) return;
529
- const msg = err instanceof Error ? err.message : String(err);
530
- this._logger.warn(`bizar: SSE reconnect threw: ${msg}`);
531
- this.scheduleReconnect();
532
- }
533
- }
534
- }
535
-
536
- // --- Helpers --------------------------------------------------------------
537
-
538
- /**
539
- * Extract the `sessionID` from an cline event payload.
540
- *
541
- * v0.4.3: this is called AFTER sync unwrapping, so the `obj` we receive
542
- * is either:
543
- * - A direct event: `{properties: {sessionID}}` or `{sessionID}`.
544
- * - A sync event's `data` block: `{sessionID, ...}` (top-level).
545
- *
546
- * We check (in order):
547
- * 1. `properties.sessionID` / `properties.sessionId` (direct events)
548
- * 2. Top-level `sessionID` / `sessionId` (direct or post-unwrapped sync)
549
- *
550
- * Returns `undefined` if none of those is present.
551
- */
552
- function extractSessionId(obj: Record<string, unknown>): string | undefined {
553
- const props = obj.properties;
554
- if (props && typeof props === "object") {
555
- const p = props as Record<string, unknown>;
556
- if (typeof p.sessionID === "string" && p.sessionID.length > 0) return p.sessionID;
557
- if (typeof p.sessionId === "string" && p.sessionId.length > 0) return p.sessionId;
558
- }
559
- if (typeof obj.sessionID === "string" && obj.sessionID.length > 0) return obj.sessionID;
560
- if (typeof obj.sessionId === "string" && obj.sessionId.length > 0) return obj.sessionId;
561
- return undefined;
562
- }
563
-
564
- function extractMessageId(obj: Record<string, unknown>): string | undefined {
565
- const props = obj.properties;
566
- if (props && typeof props === "object") {
567
- const p = props as Record<string, unknown>;
568
- if (typeof p.messageID === "string") return p.messageID;
569
- if (typeof p.messageId === "string") return p.messageId;
570
- }
571
- if (typeof obj.messageID === "string") return obj.messageID;
572
- if (typeof obj.messageId === "string") return obj.messageId;
573
- return undefined;
574
- }
575
-
576
- /**
577
- * Extract the `partID` from an event payload. We check (in order):
578
- * 1. `properties.partID` (direct events like `message.part.delta`)
579
- * 2. Top-level `partID` (after sync unwrap, if the data block carries it)
580
- * 3. `part.id` (sync `message.part.updated.1` — the part object itself
581
- * carries its own id)
582
- */
583
- function extractPartId(
584
- obj: Record<string, unknown>,
585
- part: { id?: string } | undefined,
586
- ): string {
587
- const props = obj.properties;
588
- if (props && typeof props === "object") {
589
- const p = props as Record<string, unknown>;
590
- if (typeof p.partID === "string" && p.partID.length > 0) return p.partID;
591
- if (typeof p.partId === "string" && p.partId.length > 0) return p.partId;
592
- }
593
- if (typeof obj.partID === "string" && obj.partID.length > 0) return obj.partID;
594
- if (typeof obj.partId === "string" && obj.partId.length > 0) return obj.partId;
595
- if (part && typeof part.id === "string" && part.id.length > 0) return part.id;
596
- return "";
597
- }
598
-
599
- /**
600
- * Strip a trailing version suffix from an event type.
601
- * e.g. `"session.created.1"` → `"session.created"`, `"foo.bar.baz.12"` → `"foo.bar.baz"`.
602
- * If the type has no `.N` suffix at the end, it is returned unchanged.
603
- */
604
- function stripVersionSuffix(type: string): string {
605
- return type.replace(/\.\d+$/, "");
606
- }