bosun 0.42.5 → 0.43.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 (473) hide show
  1. package/.env.example +36 -51
  2. package/README.md +19 -3
  3. package/agent/agent-custom-tools.mjs +138 -26
  4. package/agent/agent-endpoint.mjs +1 -2
  5. package/agent/agent-event-bus.mjs +33 -2
  6. package/agent/agent-hooks.mjs +1 -1
  7. package/agent/agent-launcher.mjs +6210 -0
  8. package/agent/agent-pool.mjs +7 -4018
  9. package/agent/agent-prompt-catalog.mjs +5 -6
  10. package/agent/agent-prompts.mjs +62 -6
  11. package/agent/agent-sdk.mjs +130 -0
  12. package/agent/agent-supervisor.mjs +30 -6
  13. package/agent/auth/_shared.mjs +129 -0
  14. package/agent/auth/anthropic-api-key.mjs +13 -0
  15. package/agent/auth/azure-openai.mjs +17 -0
  16. package/agent/auth/cerebras.mjs +14 -0
  17. package/agent/auth/chatgpt-codex-subscription.mjs +15 -0
  18. package/agent/auth/claude-subscription.mjs +15 -0
  19. package/agent/auth/copilot-oauth.mjs +13 -0
  20. package/agent/auth/deepinfra.mjs +14 -0
  21. package/agent/auth/fireworks.mjs +14 -0
  22. package/agent/auth/gemini-api-key.mjs +14 -0
  23. package/agent/auth/groq.mjs +14 -0
  24. package/agent/auth/index.mjs +85 -0
  25. package/agent/auth/nebius.mjs +14 -0
  26. package/agent/auth/ollama.mjs +14 -0
  27. package/agent/auth/openai-api-key.mjs +13 -0
  28. package/agent/auth/openai-compatible.mjs +15 -0
  29. package/agent/auth/openrouter.mjs +14 -0
  30. package/agent/auth/perplexity.mjs +14 -0
  31. package/agent/auth/sambanova.mjs +14 -0
  32. package/agent/auth/together.mjs +14 -0
  33. package/agent/auth/xai.mjs +14 -0
  34. package/agent/autofix-prompts.mjs +2 -2
  35. package/agent/autofix.mjs +2 -2
  36. package/agent/bosun-skills.mjs +215 -86
  37. package/agent/fleet-coordinator.mjs +161 -32
  38. package/agent/harness/agent-loop.mjs +26 -0
  39. package/agent/harness/event-contract.mjs +125 -0
  40. package/agent/harness/followup-queue.mjs +33 -0
  41. package/agent/harness/message-normalizer.mjs +43 -0
  42. package/agent/harness/module-boundaries.md +73 -0
  43. package/agent/harness/run-contract.mjs +122 -0
  44. package/agent/harness/runtime-config.mjs +132 -0
  45. package/agent/harness/session-state.mjs +80 -0
  46. package/agent/harness/steering-queue.mjs +35 -0
  47. package/agent/harness/tool-runner.mjs +95 -0
  48. package/agent/harness/turn-runner.mjs +135 -0
  49. package/agent/harness-agent-service.mjs +852 -0
  50. package/agent/harness-executor-config.mjs +384 -0
  51. package/agent/hook-library.mjs +141 -2
  52. package/agent/hook-profiles.mjs +15 -2
  53. package/agent/internal-harness-control-plane.mjs +672 -0
  54. package/agent/internal-harness-profile.mjs +519 -0
  55. package/agent/internal-harness-runtime.mjs +1219 -0
  56. package/agent/lineage-graph.mjs +141 -0
  57. package/agent/primary-agent.mjs +602 -706
  58. package/agent/provider-auth-manager.mjs +830 -0
  59. package/agent/provider-auth-state.mjs +440 -0
  60. package/agent/provider-capabilities.mjs +116 -0
  61. package/agent/provider-kernel.mjs +596 -0
  62. package/agent/provider-message-transform.mjs +583 -0
  63. package/agent/provider-model-catalog.mjs +163 -0
  64. package/agent/provider-registry.mjs +657 -0
  65. package/agent/provider-runtime-discovery.mjs +147 -0
  66. package/agent/provider-session.mjs +767 -0
  67. package/agent/providers/_shared.mjs +397 -0
  68. package/agent/providers/anthropic-messages.mjs +64 -0
  69. package/agent/providers/azure-openai-responses.mjs +69 -0
  70. package/agent/providers/cerebras.mjs +66 -0
  71. package/agent/providers/claude-subscription-shim.mjs +68 -0
  72. package/agent/providers/copilot-oauth.mjs +66 -0
  73. package/agent/providers/deepinfra.mjs +66 -0
  74. package/agent/providers/fireworks.mjs +66 -0
  75. package/agent/providers/gemini-generate-content.mjs +66 -0
  76. package/agent/providers/groq.mjs +66 -0
  77. package/agent/providers/index.mjs +208 -0
  78. package/agent/providers/nebius.mjs +66 -0
  79. package/agent/providers/ollama.mjs +66 -0
  80. package/agent/providers/openai-codex-subscription.mjs +75 -0
  81. package/agent/providers/openai-compatible.mjs +65 -0
  82. package/agent/providers/openai-responses.mjs +67 -0
  83. package/agent/providers/openrouter.mjs +66 -0
  84. package/agent/providers/perplexity.mjs +66 -0
  85. package/agent/providers/provider-contract.mjs +138 -0
  86. package/agent/providers/provider-errors.mjs +63 -0
  87. package/agent/providers/provider-model-pricing.mjs +246 -0
  88. package/agent/providers/provider-stream-normalizer.mjs +7 -0
  89. package/agent/providers/provider-usage-normalizer.mjs +48 -0
  90. package/agent/providers/sambanova.mjs +66 -0
  91. package/agent/providers/together.mjs +66 -0
  92. package/agent/providers/xai.mjs +66 -0
  93. package/agent/query-engine.mjs +260 -0
  94. package/agent/retry-queue.mjs +1 -0
  95. package/agent/review-agent.mjs +1 -1
  96. package/agent/session-contract.mjs +127 -0
  97. package/agent/session-manager.mjs +1859 -0
  98. package/agent/session-replay.mjs +617 -0
  99. package/agent/session-snapshot-store.mjs +379 -0
  100. package/agent/skills/agent-coordination.md +6 -0
  101. package/agent/skills/background-task-execution.md +6 -0
  102. package/agent/skills/bosun-agent-api.md +6 -0
  103. package/agent/skills/code-quality-anti-patterns.md +7 -0
  104. package/agent/skills/commit-conventions.md +6 -0
  105. package/agent/skills/custom-tool-creation.md +6 -0
  106. package/agent/skills/error-recovery.md +6 -0
  107. package/agent/skills/pr-workflow.md +6 -0
  108. package/agent/skills/tdd-pattern.md +6 -0
  109. package/agent/subagent-contract.mjs +104 -0
  110. package/agent/subagent-control.mjs +633 -0
  111. package/agent/subagent-pool.mjs +260 -0
  112. package/agent/thread-contract.mjs +88 -0
  113. package/agent/thread-registry.mjs +552 -0
  114. package/agent/tool-approval-manager.mjs +259 -0
  115. package/agent/tool-builtin-catalog.mjs +855 -0
  116. package/agent/tool-contract.mjs +101 -0
  117. package/agent/tool-event-contract.mjs +99 -0
  118. package/agent/tool-execution-ledger.mjs +32 -0
  119. package/agent/tool-network-policy.mjs +86 -0
  120. package/agent/tool-orchestrator.mjs +382 -0
  121. package/agent/tool-output-truncation.mjs +70 -0
  122. package/agent/tool-registry.mjs +200 -0
  123. package/agent/tool-retry-policy.mjs +57 -0
  124. package/agent/tool-runtime-context.mjs +220 -0
  125. package/bench/harness-load-bench.mjs +281 -0
  126. package/bench/harness-parity-bench.mjs +214 -0
  127. package/bench/swebench/bosun-swebench.mjs +21 -6
  128. package/bosun-tui.mjs +59 -13
  129. package/bosun.config.example.json +55 -2
  130. package/bosun.schema.json +598 -5
  131. package/cli.mjs +656 -160
  132. package/config/config-doctor.mjs +80 -26
  133. package/config/config-editor.mjs +417 -0
  134. package/config/config.mjs +489 -144
  135. package/config/repo-config.mjs +125 -49
  136. package/config/repo-root.mjs +33 -1
  137. package/desktop/main.mjs +554 -115
  138. package/desktop/package.json +1 -1
  139. package/git/diff-stats.mjs +7 -5
  140. package/git/git-editor-fix.mjs +2 -42
  141. package/github/github-app-auth.mjs +6 -0
  142. package/github/github-oauth-portal.mjs +20 -0
  143. package/infra/anomaly-detector.mjs +122 -22
  144. package/infra/approval-projection-store.mjs +75 -0
  145. package/infra/config-reload-bus.mjs +33 -0
  146. package/infra/container-runner.mjs +37 -4
  147. package/infra/error-detector.mjs +110 -35
  148. package/infra/event-schema.mjs +353 -0
  149. package/infra/guardrails.mjs +383 -0
  150. package/infra/heartbeat-monitor.mjs +432 -0
  151. package/infra/library-manager.mjs +367 -19
  152. package/infra/live-event-projector.mjs +197 -0
  153. package/infra/maintenance.mjs +202 -51
  154. package/infra/monitor.mjs +1749 -2027
  155. package/infra/preflight.mjs +107 -6
  156. package/infra/presence.mjs +33 -9
  157. package/infra/projection-contract.mjs +27 -0
  158. package/infra/provider-usage-ledger.mjs +73 -0
  159. package/infra/replay-reader.mjs +140 -0
  160. package/infra/runtime-accumulator.mjs +303 -8
  161. package/infra/runtime-metrics.mjs +156 -0
  162. package/infra/session-projection-store.mjs +169 -0
  163. package/infra/session-telemetry-runtime.mjs +580 -0
  164. package/infra/session-telemetry.mjs +338 -0
  165. package/infra/session-tracker.mjs +1613 -228
  166. package/infra/startup-service.mjs +0 -2
  167. package/infra/storage-janitor.mjs +1046 -0
  168. package/infra/subagent-projection-store.mjs +89 -0
  169. package/infra/test-runtime.mjs +53 -20
  170. package/infra/trace-export.mjs +103 -0
  171. package/infra/tui-bridge.mjs +607 -5
  172. package/infra/update-check.mjs +7 -8
  173. package/infra/windows-hidden-child-processes.mjs +99 -0
  174. package/infra/worktree-recovery-state.mjs +20 -7
  175. package/kanban/kanban-adapter.mjs +702 -310
  176. package/kanban/repo-mirror-projection-store.mjs +871 -0
  177. package/lib/agent-configuration-guide.mjs +280 -0
  178. package/lib/hot-path-runtime.mjs +1061 -0
  179. package/lib/integrations-registry.mjs +294 -0
  180. package/lib/log-tail.mjs +101 -0
  181. package/lib/logger.mjs +21 -25
  182. package/lib/mojibake-repair.mjs +40 -0
  183. package/lib/repo-map.mjs +137 -24
  184. package/lib/request-json-api.mjs +59 -0
  185. package/lib/safe-box.mjs +56 -0
  186. package/lib/session-insights.mjs +3 -1
  187. package/lib/skill-markdown-safety.mjs +394 -0
  188. package/lib/state-ledger-sqlite.mjs +4462 -0
  189. package/lib/vault-keychain.mjs +259 -0
  190. package/lib/vault.mjs +374 -0
  191. package/lib/workflow-flowchart-utils.mjs +326 -0
  192. package/monitor-tail-sanitizer.mjs +1 -2
  193. package/native/bosun-telemetry/Cargo.toml +9 -0
  194. package/native/bosun-telemetry/src/export.rs +151 -0
  195. package/native/bosun-telemetry/src/main.rs +76 -0
  196. package/native/bosun-telemetry/src/metrics.rs +114 -0
  197. package/native/bosun-telemetry/src/session_telemetry.rs +178 -0
  198. package/native/bosun-unified-exec/Cargo.lock +107 -0
  199. package/native/bosun-unified-exec/Cargo.toml +8 -0
  200. package/native/bosun-unified-exec/src/async_watcher.rs +145 -0
  201. package/native/bosun-unified-exec/src/head_tail_buffer.rs +241 -0
  202. package/native/bosun-unified-exec/src/main.rs +86 -0
  203. package/native/bosun-unified-exec/src/process_manager.rs +308 -0
  204. package/native/bosun-unified-exec/src/tool_orchestrator.rs +187 -0
  205. package/package.json +230 -59
  206. package/postinstall.mjs +182 -13
  207. package/server/bosun-mcp-server.mjs +348 -10
  208. package/server/routes/harness-agent-bridge.mjs +128 -0
  209. package/server/routes/harness-approvals.mjs +290 -0
  210. package/server/routes/harness-events.mjs +469 -0
  211. package/server/routes/harness-providers.mjs +385 -0
  212. package/server/routes/harness-sessions.mjs +2230 -0
  213. package/server/routes/harness-subagents.mjs +138 -0
  214. package/server/routes/harness-surface-payload.mjs +74 -0
  215. package/server/setup-web-server.mjs +468 -39
  216. package/server/ui-server.mjs +13041 -4418
  217. package/setup.mjs +206 -298
  218. package/shared-workspaces.json +1 -1
  219. package/shell/anthropic-native-adapter.mjs +1218 -0
  220. package/shell/auth-resolver.mjs +247 -0
  221. package/shell/claude-shell.mjs +85 -2
  222. package/shell/codex-config-file.mjs +9 -0
  223. package/shell/codex-config.mjs +192 -249
  224. package/shell/codex-model-profiles.mjs +76 -12
  225. package/shell/codex-sdk-import.mjs +7 -0
  226. package/shell/codex-shell.mjs +708 -170
  227. package/shell/context-compaction.mjs +898 -0
  228. package/shell/copilot-shell.mjs +359 -109
  229. package/shell/gemini-native-adapter.mjs +411 -0
  230. package/shell/gemini-shell.mjs +121 -13
  231. package/shell/mcp-client.mjs +401 -0
  232. package/shell/mcp-registry.mjs +72 -0
  233. package/shell/message-pruner.mjs +248 -0
  234. package/shell/openai-native-adapter.mjs +1975 -0
  235. package/shell/opencode-providers.mjs +16 -531
  236. package/shell/opencode-shell.mjs +180 -9
  237. package/shell/provider-transform.mjs +386 -0
  238. package/shell/pwsh-runtime.mjs +9 -2
  239. package/shell/retry-fetch.mjs +244 -0
  240. package/shell/session-resume.mjs +97 -0
  241. package/shell/session-store.mjs +215 -0
  242. package/shell/shell-adapter-registry.mjs +346 -0
  243. package/shell/shell-session-compat.mjs +442 -0
  244. package/shell/smooth-stream.mjs +233 -0
  245. package/shell/stop-condition.mjs +238 -0
  246. package/shell/tool-call-repair.mjs +345 -0
  247. package/shell/tool-executor.mjs +571 -0
  248. package/task/pipeline.mjs +3 -1
  249. package/task/task-assessment.mjs +312 -6
  250. package/task/task-claims.mjs +312 -48
  251. package/task/task-cli.mjs +90 -14
  252. package/task/task-complexity.mjs +6 -6
  253. package/task/task-context.mjs +37 -0
  254. package/task/task-debt-ledger.mjs +110 -0
  255. package/task/task-executor.mjs +1104 -119
  256. package/task/task-replanner.mjs +553 -0
  257. package/task/task-simulate-cli.mjs +1481 -0
  258. package/task/task-store.mjs +996 -68
  259. package/telegram/executor-health-region-cache.mjs +75 -0
  260. package/telegram/get-telegram-chat-id.mjs +0 -0
  261. package/telegram/harness-api-client.mjs +124 -0
  262. package/telegram/sticky-menu-state.mjs +384 -0
  263. package/telegram/telegram-bot.mjs +548 -865
  264. package/telegram/telegram-sentinel.mjs +25 -14
  265. package/telegram/telegram-surface-runtime.mjs +53 -0
  266. package/tools/generate-demo-defaults.mjs +23 -4
  267. package/tools/harness-hotpath-bench.mjs +246 -0
  268. package/tools/import-check.mjs +279 -234
  269. package/tools/install-git-hooks.mjs +96 -20
  270. package/tools/native-rust.mjs +124 -0
  271. package/tools/packed-cli-smoke.mjs +147 -0
  272. package/tools/prepublish-check.mjs +53 -1
  273. package/tools/run-workflow-guaranteed-suite.mjs +56 -0
  274. package/tools/site-serve.mjs +112 -0
  275. package/tools/sync-demo-ui.mjs +188 -0
  276. package/tools/syntax-check.mjs +32 -28
  277. package/tools/test-kanban-enhancement.mjs +7 -7
  278. package/tools/test-shared-state-integration.mjs +5 -19
  279. package/tools/vite-windows-realpath-shim.mjs +274 -0
  280. package/tools/vitest-esbuild-shim.mjs +45 -0
  281. package/tools/vitest-full-suite.mjs +310 -0
  282. package/tools/vitest-runner.mjs +505 -11
  283. package/tools/workflow-orphan-worktree-recovery.mjs +24 -7
  284. package/tui/CommandPalette.js +87 -0
  285. package/tui/app.mjs +463 -37
  286. package/tui/components/status-header.mjs +43 -1
  287. package/tui/lib/command-palette.mjs +191 -0
  288. package/tui/lib/connection-target.mjs +577 -0
  289. package/tui/lib/header-config.mjs +0 -2
  290. package/tui/lib/navigation.mjs +8 -3
  291. package/tui/lib/ws-bridge.mjs +141 -51
  292. package/tui/screens/agents-screen-helpers.mjs +87 -3
  293. package/tui/screens/agents.mjs +1074 -202
  294. package/tui/screens/connection-setup.mjs +363 -0
  295. package/tui/screens/harness-approvals.mjs +7 -0
  296. package/tui/screens/harness-sessions.mjs +109 -0
  297. package/tui/screens/harness-subagents.mjs +18 -0
  298. package/tui/screens/harness-telemetry.mjs +67 -0
  299. package/tui/screens/logs.mjs +325 -0
  300. package/tui/screens/settings-screen-helpers.mjs +75 -0
  301. package/tui/screens/settings.mjs +397 -0
  302. package/tui/screens/status.mjs +130 -5
  303. package/tui/screens/telemetry-screen-helpers.mjs +158 -0
  304. package/tui/screens/telemetry.mjs +246 -0
  305. package/tui/screens/workflows.mjs +984 -0
  306. package/ui/app.js +746 -189
  307. package/ui/app.monolith.js +2 -3
  308. package/ui/assets/toastui-editor-all.min.js +24 -0
  309. package/ui/components/agent-selector.js +706 -49
  310. package/ui/components/charts.js +16 -12
  311. package/ui/components/chat-view.js +536 -35
  312. package/ui/components/commit-graph.js +648 -0
  313. package/ui/components/context-menu.js +89 -0
  314. package/ui/components/diff-viewer.js +169 -53
  315. package/ui/components/forms.js +13 -2
  316. package/ui/components/kanban-board.js +541 -92
  317. package/ui/components/session-list.js +303 -66
  318. package/ui/components/shared.js +9 -1
  319. package/ui/components/task-markdown.js +272 -0
  320. package/ui/components/workspace-executor-settings.js +142 -0
  321. package/ui/components/workspace-switcher.js +35 -79
  322. package/ui/demo-defaults.js +17278 -7297
  323. package/ui/demo.html +7058 -5338
  324. package/ui/index.html +189 -112
  325. package/ui/modules/agent-events.js +309 -36
  326. package/ui/modules/api.js +236 -13
  327. package/ui/modules/chat-turn-groups.js +101 -0
  328. package/ui/modules/harness-client.js +56 -0
  329. package/ui/modules/icon-utils.js +9 -1
  330. package/ui/modules/icons.js +26 -2
  331. package/ui/modules/repo-area-contention.js +97 -0
  332. package/ui/modules/router.js +2 -0
  333. package/ui/modules/session-api.js +158 -14
  334. package/ui/modules/session-insights-worker.js +28 -0
  335. package/ui/modules/session-insights.js +173 -4
  336. package/ui/modules/session-surface.js +221 -0
  337. package/ui/modules/settings-schema.js +148 -27
  338. package/ui/modules/state.js +122 -13
  339. package/ui/modules/streaming.js +196 -60
  340. package/ui/modules/structured-values.js +47 -0
  341. package/ui/modules/task-hierarchy.js +374 -0
  342. package/ui/modules/worktree-recovery.js +10 -1
  343. package/ui/setup.html +3327 -2538
  344. package/ui/styles/components.css +983 -99
  345. package/ui/styles/kanban.css +229 -0
  346. package/ui/styles/layout.css +578 -106
  347. package/ui/styles/toastui-editor-dark.css +1 -0
  348. package/ui/styles/toastui-editor-viewer.css +6 -0
  349. package/ui/styles/toastui-editor.css +6 -0
  350. package/ui/styles/variables.css +14 -2
  351. package/ui/styles/workspace-switcher.css +22 -0
  352. package/ui/styles.css +20 -5
  353. package/ui/tabs/agents.js +1222 -86
  354. package/ui/tabs/chat.js +588 -146
  355. package/ui/tabs/context-compression-lab.js +962 -0
  356. package/ui/tabs/control.js +347 -61
  357. package/ui/tabs/dashboard.js +372 -105
  358. package/ui/tabs/guardrails.js +1140 -0
  359. package/ui/tabs/infra.js +91 -12
  360. package/ui/tabs/integrations.js +388 -0
  361. package/ui/tabs/library.js +161 -21
  362. package/ui/tabs/logs.js +410 -52
  363. package/ui/tabs/manual-flows.js +268 -52
  364. package/ui/tabs/settings.js +2117 -105
  365. package/ui/tabs/tasks.js +2851 -303
  366. package/ui/tabs/telemetry.js +246 -8
  367. package/ui/tabs/workflow-canvas-utils.mjs +172 -15
  368. package/ui/tabs/workflows.js +2632 -348
  369. package/ui/tui/App.js +127 -119
  370. package/ui/tui/HelpScreen.js +201 -0
  371. package/ui/tui/SettingsScreen.js +388 -0
  372. package/ui/tui/TasksScreen.js +30 -7
  373. package/ui/tui/TelemetryScreen.js +155 -0
  374. package/ui/tui/WorkflowsScreen.js +350 -0
  375. package/ui/tui/config-events.js +13 -0
  376. package/ui/tui/constants.js +1 -1
  377. package/ui/tui/logs-screen-helpers.js +292 -0
  378. package/ui/tui/tasks-screen-helpers.js +52 -0
  379. package/ui/tui/telemetry-helpers.js +158 -0
  380. package/ui/tui/useWorkflows.js +126 -6
  381. package/ui/tui/workflows-screen-helpers.js +220 -0
  382. package/ui/vendor/preact-jsx-runtime.js +5 -0
  383. package/utils.mjs +2 -2
  384. package/voice/vision-session-state.mjs +257 -0
  385. package/voice/voice-action-dispatcher.mjs +57 -12
  386. package/voice/voice-agents-sdk.mjs +1 -1
  387. package/voice/voice-auth-manager.mjs +102 -123
  388. package/voice/voice-tool-definitions.mjs +7 -7
  389. package/voice/voice-tools.mjs +837 -101
  390. package/workflow/action-approval.mjs +415 -0
  391. package/workflow/approval-queue.mjs +1254 -0
  392. package/workflow/credential-store.mjs +553 -0
  393. package/workflow/cron-scheduler.mjs +512 -0
  394. package/workflow/declarative-workflows.mjs +21 -2
  395. package/workflow/delegation-runtime.mjs +557 -0
  396. package/workflow/execution-ledger.mjs +1317 -33
  397. package/workflow/harness-approval-node.mjs +237 -0
  398. package/workflow/harness-output-contract.mjs +86 -0
  399. package/workflow/harness-session-node.mjs +160 -0
  400. package/workflow/harness-subagent-node.mjs +483 -0
  401. package/workflow/harness-tool-node.mjs +176 -0
  402. package/workflow/heavy-runner-pool.mjs +546 -0
  403. package/workflow/manual-flows.mjs +969 -28
  404. package/workflow/mcp-discovery-proxy.mjs +363 -143
  405. package/workflow/mcp-registry.mjs +507 -65
  406. package/workflow/meeting-workflow-service.mjs +24 -12
  407. package/workflow/pipeline-workflows.mjs +44 -2
  408. package/workflow/pipeline.mjs +72 -28
  409. package/workflow/project-detection.mjs +31 -6
  410. package/workflow/research-evidence-sidecar.mjs +1246 -0
  411. package/workflow/run-evaluator.mjs +2155 -0
  412. package/workflow/workflow-cli.mjs +229 -2
  413. package/workflow/workflow-contract.mjs +130 -2
  414. package/workflow/workflow-engine.mjs +5636 -331
  415. package/workflow/workflow-migration.mjs +0 -1
  416. package/workflow/workflow-nodes/actions.mjs +15526 -0
  417. package/workflow/workflow-nodes/agent.mjs +1863 -0
  418. package/workflow/workflow-nodes/conditions.mjs +307 -0
  419. package/workflow/workflow-nodes/definitions.mjs +210 -29
  420. package/workflow/workflow-nodes/flow.mjs +749 -0
  421. package/workflow/workflow-nodes/loop.mjs +449 -0
  422. package/workflow/workflow-nodes/meetings.mjs +456 -0
  423. package/workflow/workflow-nodes/notifications.mjs +169 -0
  424. package/workflow/workflow-nodes/transforms.mjs +83 -30
  425. package/workflow/workflow-nodes/triggers.mjs +1405 -0
  426. package/workflow/workflow-nodes/validation.mjs +722 -0
  427. package/workflow/workflow-nodes.mjs +43 -14965
  428. package/workflow/workflow-serializer.mjs +294 -0
  429. package/workflow/workflow-templates.mjs +304 -9
  430. package/workflow-templates/_helpers.mjs +1 -3
  431. package/workflow-templates/agents.mjs +235 -27
  432. package/workflow-templates/bosun-native.mjs +3 -1
  433. package/workflow-templates/code-quality.mjs +1 -1
  434. package/workflow-templates/continuation-loop.mjs +21 -5
  435. package/workflow-templates/coverage.mjs +6 -2
  436. package/workflow-templates/github.mjs +2565 -177
  437. package/workflow-templates/reliability.mjs +463 -35
  438. package/workflow-templates/research-evidence.mjs +389 -0
  439. package/workflow-templates/security.mjs +75 -62
  440. package/workflow-templates/sub-workflows.mjs +11 -3
  441. package/workflow-templates/task-batch.mjs +102 -20
  442. package/workflow-templates/task-lifecycle.mjs +333 -298
  443. package/workspace/command-diagnostics.mjs +111 -0
  444. package/workspace/context-cache.mjs +1308 -124
  445. package/workspace/context-indexer.mjs +915 -9
  446. package/workspace/context-injector.mjs +144 -0
  447. package/workspace/execution-journal.mjs +255 -0
  448. package/workspace/scope-locks.mjs +481 -0
  449. package/workspace/shared-knowledge.mjs +496 -91
  450. package/workspace/shared-state-manager.mjs +344 -1
  451. package/workspace/shared-workspace-cli.mjs +0 -0
  452. package/workspace/shared-workspace-registry.mjs +2 -2
  453. package/workspace/skillbook-store.mjs +681 -0
  454. package/workspace/workspace-manager.mjs +14 -8
  455. package/workspace/workspace-monitor.mjs +4 -4
  456. package/workspace/worktree-manager.mjs +146 -55
  457. package/workspace/worktree-setup.mjs +822 -0
  458. package/agent/rotate-agent-logs.sh +0 -134
  459. package/git/sdk-conflict-resolver.mjs +0 -971
  460. package/infra/sync-engine.mjs +0 -1160
  461. package/kanban/ve-kanban.mjs +0 -664
  462. package/kanban/ve-kanban.ps1 +0 -1365
  463. package/kanban/ve-kanban.sh +0 -18
  464. package/kanban/ve-orchestrator.mjs +0 -340
  465. package/kanban/ve-orchestrator.ps1 +0 -6762
  466. package/kanban/ve-orchestrator.sh +0 -18
  467. package/kanban/vibe-kanban-wrapper.mjs +0 -41
  468. package/kanban/vk-error-resolver.mjs +0 -474
  469. package/kanban/vk-log-stream.mjs +0 -932
  470. package/task/task-archiver.mjs +0 -813
  471. package/tools/publish.mjs +0 -239
  472. package/ui/components/chat-view.js.bak +0 -1
  473. package/ui/tabs/infra.js.bak +0 -1
package/.env.example CHANGED
@@ -1,3 +1,6 @@
1
+ # Max characters of matched built-in/local skills injected into an agent task prompt.
2
+ # Skills are only injected when task title/description/labels match skill tags.
3
+ BOSUN_SKILLS_MAX_CHARS=4000
1
4
  # ─── Bosun — Environment Configuration ───────────────────────────────
2
5
  # Copy this file to .env and fill in your values.
3
6
  # Or run: bosun --setup
@@ -350,12 +353,8 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
350
353
  # FAILOVER_DISABLE_AFTER=3
351
354
 
352
355
  # ─── Internal Executor ───────────────────────────────────────────────────────
353
- # Controls whether tasks are executed locally via agent-pool instead of
354
- # (or alongside) VK's cloud executor. Modes:
355
- # "vk" — all tasks via VK executor (default, existing behavior)
356
- # "internal" — all tasks via local agent-pool (bypass wrapper orchestrator script)
357
- # "hybrid" — both VK and internal run simultaneously for overflow
358
- # EXECUTOR_MODE=vk
356
+ # Controls whether tasks are executed locally via agent-pool.
357
+ # EXECUTOR_MODE=internal
359
358
  # Max concurrent agent slots for internal executor (default: 3)
360
359
  # INTERNAL_EXECUTOR_PARALLEL=3
361
360
  # INTERNAL_EXECUTOR_BASE_BRANCH_PARALLEL=0
@@ -381,6 +380,12 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
381
380
  # INTERNAL_EXECUTOR_REVIEW_TIMEOUT_MS=300000
382
381
  # Experimental autonomous backlog replenishment (disabled by default)
383
382
  # INTERNAL_EXECUTOR_REPLENISH_ENABLED=false
383
+ # Enable deterministic internal harness compile/activate control-plane support
384
+ # BOSUN_HARNESS_ENABLED=false
385
+ # Path to harness profile source (JSON or markdown fenced JSON)
386
+ # BOSUN_HARNESS_SOURCE=.bosun/harness/internal-harness.md
387
+ # Validation mode: off | report | enforce
388
+ # BOSUN_HARNESS_VALIDATION_MODE=report
384
389
  # Minimum follow-up tasks to generate per completed task (1-2)
385
390
  # INTERNAL_EXECUTOR_REPLENISH_MIN_NEW_TASKS=1
386
391
  # Maximum follow-up tasks to generate per completed task (1-3)
@@ -495,9 +500,9 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
495
500
  # ─── Kanban Backend ──────────────────────────────────────────────────────────
496
501
  # Task-board backend:
497
502
  # internal - local task-store source of truth (recommended primary)
498
- # vk - Vibe-Kanban (secondary adapter)
499
503
  # github - GitHub Issues
500
504
  # jira - Jira Issues
505
+ # gnap - GNAP projection backend (off by default)
501
506
  # KANBAN_BACKEND=internal
502
507
  # Sync behavior:
503
508
  # internal-primary - internal task-store remains source-of-truth (recommended)
@@ -598,6 +603,20 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
598
603
  # Optional JSON custom field to store full shared state payload
599
604
  # JIRA_CUSTOM_FIELD_SHARED_STATE=customfield_10048
600
605
 
606
+ # GNAP backend (KANBAN_BACKEND=gnap)
607
+ # Master toggle for GNAP integration. Must be enabled before selecting gnap.
608
+ # GNAP_ENABLED=false
609
+ # Path to the repo or clone that will host GNAP projection data
610
+ # GNAP_REPO_PATH=
611
+ # Synchronization mode. Bosun currently supports projection-only GNAP wiring.
612
+ # GNAP_SYNC_MODE=projection
613
+ # Where to store GNAP run metadata: git|local
614
+ # GNAP_RUN_STORAGE=git
615
+ # Where to store GNAP message projections: off|git|local
616
+ # GNAP_MESSAGE_STORAGE=off
617
+ # Optional sanitized roadmap export for shared visibility
618
+ # GNAP_PUBLIC_ROADMAP_ENABLED=false
619
+
601
620
  # ─── Sandbox Policy ──────────────────────────────────────────────────────────
602
621
  # Controls agent sandbox isolation when using Codex SDK.
603
622
  # Options:
@@ -661,37 +680,6 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
661
680
  # Stop auto-restarts after this many instant failures in a row (default: 3)
662
681
  # BOSUN_DAEMON_MAX_INSTANT_RESTARTS=3
663
682
 
664
- # ─── Vibe-Kanban ──────────────────────────────────────────────────────────────
665
- # Base URL for the Vibe-Kanban API (default: http://127.0.0.1:54089)
666
- VK_BASE_URL=http://127.0.0.1:54089
667
- # Alternate endpoint URL for VK (overrides VK_BASE_URL if set)
668
- # VK_ENDPOINT_URL=http://127.0.0.1:54089
669
- # Port for vibe-kanban API (default: 54089)
670
- VK_RECOVERY_PORT=54089
671
- # Host for VK recovery (default: 0.0.0.0)
672
- # VK_RECOVERY_HOST=0.0.0.0
673
- # VK_HOST=0.0.0.0
674
- # Public URL shown in Telegram links (optional)
675
- # VK_PUBLIC_URL=https://kanban.yoursite.com
676
- # VK_WEB_URL=https://kanban.yoursite.com
677
- # VK HTTP timeout/retry controls (used by ve-kanban.ps1)
678
- # VK_HTTP_TIMEOUT_SEC=45
679
- # VK_HTTP_RETRIES=2
680
- # VK_HTTP_RETRY_DELAY_MS=1500
681
- # Set to true to prevent the monitor from spawning vibe-kanban automatically
682
- # VK_NO_SPAWN=false
683
- # Cooldown minutes between VK recovery attempts (default: 10)
684
- # VK_RECOVERY_COOLDOWN_MIN=10
685
- # VK health check interval in ms (default: 60000)
686
- # VK_ENSURE_INTERVAL=60000
687
- # VK project name (auto-detected)
688
- # VK_PROJECT_NAME=my-project
689
- # Explicit VK project/repo IDs (auto-detected if empty)
690
- # VK_PROJECT_ID=
691
- # VK_REPO_ID=
692
- # Override task URL template (optional)
693
- # VK_TASK_URL_TEMPLATE=https://kanban.yoursite.com/projects/{projectId}/tasks/{taskId}
694
-
695
683
  # ─── Shared Workspace Registry ───────────────────────────────────────────────
696
684
  # Optional registry path for shared workspace leasing
697
685
  # VE_SHARED_WORKSPACE_REGISTRY=.cache/bosun/shared-workspaces.json
@@ -713,13 +701,8 @@ VK_RECOVERY_PORT=54089
713
701
  # GITHUB_TOKEN=
714
702
  # GH_TOKEN=
715
703
  # GITHUB_PAT=
716
- # Owner/repo for gh CLI in ve-kanban
717
- # GH_OWNER=virtengine
718
- # GH_REPO=virtengine
719
704
  # Target branch for PR checks/merge (default: origin/main)
720
- # VK_TARGET_BRANCH=origin/main
721
- # Default upstream/base branch for bosun tasks (overrides VK_TARGET_BRANCH)
722
- # BOSUN_TASK_UPSTREAM=origin/ve/bosun-generic
705
+ # BOSUN_TASK_UPSTREAM=origin/main
723
706
 
724
707
  # ─── Codex / AI Provider ─────────────────────────────────────────────────────
725
708
  # The Codex SDK uses OpenAI-compatible configuration that has been setup in ~/.codex/config.toml -
@@ -1024,7 +1007,7 @@ COPILOT_CLOUD_DISABLED=true
1024
1007
 
1025
1008
  # ─── Task Planner ─────────────────────────────────────────────────────────────
1026
1009
  # How to plan new tasks when backlog is empty:
1027
- # "kanban" - (default) create a VK planning task for an agent to refine
1010
+ # "kanban" - (default) create a planning task for an agent to refine
1028
1011
  # "codex-sdk" - run Codex SDK directly to generate tasks
1029
1012
  # "disabled" - do nothing, wait for manual task creation
1030
1013
  # TASK_PLANNER_MODE=kanban
@@ -1056,6 +1039,8 @@ COPILOT_CLOUD_DISABLED=true
1056
1039
  # WORKFLOW_RECOVERY_BACKOFF_MAX_MS=60000
1057
1040
  # Random jitter ratio (0.0-0.9) applied to backoff to prevent retry storms.
1058
1041
  # WORKFLOW_RECOVERY_BACKOFF_JITTER_RATIO=0.2
1042
+ # Delay startup interrupted-run replay so the UI/API can become responsive first.
1043
+ # WORKFLOW_STARTUP_HISTORY_RECOVERY_DELAY_MS=120000
1059
1044
 
1060
1045
  # ─── GitHub Issue Reconciler ─────────────────────────────────────────────────
1061
1046
  # Periodically reconciles open GitHub issues against open/merged PRs.
@@ -1135,12 +1120,15 @@ COPILOT_CLOUD_DISABLED=true
1135
1120
  # Repository root (auto-detected from git; setup writes this)
1136
1121
  # REPO_ROOT=/path/to/repo
1137
1122
  # Watch path to trigger restarts (default: script path)
1138
- # WATCH_PATH=/path/to/ve-orchestrator.sh
1123
+ # WATCH_PATH=/path/to/orchestrator.sh
1139
1124
  # Monitor source hot-reload watcher. Default: enabled in devmode, disabled otherwise.
1140
1125
  # Set to true to force-enable monitor source hot-restart, false to force-disable.
1141
1126
  # SELF_RESTART_WATCH_ENABLED=true
1142
- # Status file path (default: .cache/ve-orchestrator-status.json)
1143
- # STATUS_FILE=.cache/ve-orchestrator-status.json
1127
+ # Quiet period after the last source-file change before self-restart is allowed.
1128
+ # Default: 180000 (3 minutes)
1129
+ # SELF_RESTART_QUIET_MS=180000
1130
+ # Status file path (default: .cache/orchestrator-status.json)
1131
+ # STATUS_FILE=.cache/orchestrator-status.json
1144
1132
  # Log directory (default: ./logs)
1145
1133
  # LOG_DIR=./logs
1146
1134
  # Max total log folder size in MB. Oldest logs are deleted when exceeded. 0 = unlimited.
@@ -1161,8 +1149,6 @@ COPILOT_CLOUD_DISABLED=true
1161
1149
  # AGENT_WORK_LOGGING_ENABLED=true
1162
1150
  # Enable/disable live stream analyzer (default: true)
1163
1151
  # AGENT_WORK_ANALYZER_ENABLED=true
1164
- # Enrich missing task metadata from VK for agent work logs (default: true)
1165
- # AGENT_WORK_LOGGING_ENRICH_VK=true
1166
1152
  # Task metadata cache (auto-managed): .cache/agent-work-logs/task-metadata.json
1167
1153
  # Log directory (default: .cache/agent-work-logs)
1168
1154
  # AGENT_WORK_LOG_DIR=.cache/agent-work-logs
@@ -1183,4 +1169,3 @@ COPILOT_CLOUD_DISABLED=true
1183
1169
 
1184
1170
  # OpenTelemetry tracing (optional)
1185
1171
  # BOSUN_OTEL_ENDPOINT=http://localhost:4318/v1/traces
1186
-
package/README.md CHANGED
@@ -65,7 +65,7 @@ Open `https://localhost:3080` to start the setup wizard.
65
65
 
66
66
  Requires:
67
67
 
68
- - Node.js 18+
68
+ - Node.js 22.13+
69
69
  - Git
70
70
  - Bash (for `.sh` wrappers) or PowerShell 7+ (for `.ps1` wrappers)
71
71
  - GitHub CLI (`gh`) recommended
@@ -108,7 +108,7 @@ Fallback admin auth (secondary path) is available and stores only Argon2id hash
108
108
  - Persists workflow runs to disk and auto-resumes on restart
109
109
  - Monitors runs and recovers from stalled or broken states
110
110
  - Provides Telegram control and a Mini App dashboard
111
- - Integrates with GitHub, Jira, and Vibe-Kanban boards
111
+ - Integrates with GitHub and Jira boards
112
112
 
113
113
  ## Autonomous Engineer Workflow Capabilities
114
114
 
@@ -162,6 +162,20 @@ Key places to start:
162
162
  - `docs/agent-logging-quickstart.md` - agent work logging quickstart
163
163
  - `docs/agent-work-logging-design.md` - logging design and event model
164
164
 
165
+ ## Troubleshooting
166
+
167
+ ### Preflight warns about an interactive git editor
168
+
169
+ If preflight reports an interactive git editor such as `code --wait`, `vim`, or `nano`, Bosun can deadlock while Git waits for an editor session to close.
170
+
171
+ Run this from the repo root to switch the local repo config to a non-interactive editor:
172
+
173
+ ```bash
174
+ node git-editor-fix.mjs
175
+ ```
176
+
177
+ Preflight checks both `GIT_EDITOR` and `git config --get core.editor`. No warning is shown when `core.editor` is already non-interactive, for example `:`.
178
+
165
179
  ---
166
180
 
167
181
  ## CI/CD and quality gates
@@ -228,7 +242,7 @@ npm run hooks:install
228
242
  - `server/` — setup server, Mini App backend, and API endpoints
229
243
  - `ui/` — Mini App frontend assets and operator dashboard modules
230
244
  - `telegram/` — Telegram bot, sentinel, and channel integrations
231
- - `github/` and `kanban/` — GitHub auth/webhooks and Vibe-Kanban adapters
245
+ - `github/` and `kanban/` — GitHub auth/webhooks and kanban adapters
232
246
  - `workspace/` — shared workspace registry, context indexing, and worktree lifecycle
233
247
  - `shell/` and `agent/` — executor integrations, prompts, hooks, and fleet coordination
234
248
  - `site/` — marketing site and generated docs website assets
@@ -251,3 +265,5 @@ If you find this project useful or would like to stay up to date with new releas
251
265
  ## License
252
266
 
253
267
  Apache-2.0
268
+
269
+
@@ -47,7 +47,7 @@ import {
47
47
  rmSync,
48
48
  writeFileSync,
49
49
  } from "node:fs";
50
- import { copyFile, writeFile } from "node:fs/promises";
50
+ import { copyFile } from "node:fs/promises";
51
51
  import { homedir } from "node:os";
52
52
  import { basename, dirname, extname, resolve } from "node:path";
53
53
  import { fileURLToPath } from "node:url";
@@ -212,7 +212,10 @@ function safeReadIndex(storeDir) {
212
212
  if (!existsSync(idx)) return [];
213
213
  try {
214
214
  const parsed = JSON.parse(readFileSync(idx, "utf8"));
215
- return Array.isArray(parsed) ? parsed : [];
215
+ if (!Array.isArray(parsed)) return [];
216
+ return parsed
217
+ .map((entry) => sanitizeIndexEntry(entry))
218
+ .filter(Boolean);
216
219
  } catch {
217
220
  return [];
218
221
  }
@@ -243,6 +246,77 @@ function scriptPath(storeDir, id, lang) {
243
246
  return resolve(storeDir, `${id}.${lang}`);
244
247
  }
245
248
 
249
+ function isPlainObject(value) {
250
+ return value != null && typeof value === "object" && !Array.isArray(value);
251
+ }
252
+
253
+ function normalizeStringList(values, { lowercase = true } = {}) {
254
+ if (!Array.isArray(values)) return [];
255
+ const normalized = values
256
+ .map((value) => String(value ?? "").trim())
257
+ .filter(Boolean)
258
+ .map((value) => (lowercase ? value.toLowerCase() : value));
259
+ return Array.from(new Set(normalized));
260
+ }
261
+
262
+ function normalizeUsageCount(value) {
263
+ const numeric = Number(value);
264
+ return Number.isFinite(numeric) && numeric >= 0 ? numeric : 0;
265
+ }
266
+
267
+ function normalizeToolId(toolId) {
268
+ if (typeof toolId !== "string") return null;
269
+ const trimmed = toolId.trim();
270
+ if (!trimmed) return null;
271
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,59}$/.test(trimmed)) return null;
272
+ return trimmed;
273
+ }
274
+
275
+ function sanitizeIndexEntry(raw) {
276
+ if (!isPlainObject(raw)) return null;
277
+
278
+ const id = normalizeToolId(raw.id);
279
+ const lang = VALID_LANGS.includes(raw.lang) ? raw.lang : null;
280
+ if (!id || !lang) return null;
281
+
282
+ const category = TOOL_CATEGORIES.includes(raw.category)
283
+ ? raw.category
284
+ : "utility";
285
+ const skills = normalizeStringList(raw.skills, { lowercase: false });
286
+ const agents = normalizeStringList(raw.agents, { lowercase: false });
287
+ const templates = normalizeStringList(raw.templates, { lowercase: false });
288
+
289
+ return {
290
+ ...raw,
291
+ id,
292
+ title: typeof raw.title === "string" && raw.title.trim() ? raw.title : id,
293
+ description: typeof raw.description === "string" ? raw.description : "",
294
+ tags: normalizeStringList(raw.tags),
295
+ category,
296
+ lang,
297
+ createdBy: typeof raw.createdBy === "string" && raw.createdBy.trim()
298
+ ? raw.createdBy
299
+ : "agent",
300
+ createdAt: typeof raw.createdAt === "string" && raw.createdAt.trim()
301
+ ? raw.createdAt
302
+ : nowISO(),
303
+ updatedAt: typeof raw.updatedAt === "string" && raw.updatedAt.trim()
304
+ ? raw.updatedAt
305
+ : nowISO(),
306
+ usageCount: normalizeUsageCount(raw.usageCount),
307
+ ...(typeof raw.lastUsed === "string" && raw.lastUsed.trim()
308
+ ? { lastUsed: raw.lastUsed }
309
+ : {}),
310
+ ...(skills.length > 0 ? { skills } : {}),
311
+ ...(agents.length > 0 ? { agents } : {}),
312
+ ...(templates.length > 0 ? { templates } : {}),
313
+ ...(raw.autoInject ? { autoInject: true } : {}),
314
+ ...(typeof raw.version === "string" && raw.version.trim()
315
+ ? { version: raw.version }
316
+ : {}),
317
+ };
318
+ }
319
+
246
320
  // ── Types ─────────────────────────────────────────────────────────────────────
247
321
 
248
322
  /**
@@ -302,6 +376,9 @@ export function listCustomTools(rootDir, opts = {}) {
302
376
  includeBuiltins = true,
303
377
  } = opts;
304
378
 
379
+ const requestedTags = normalizeStringList(tags);
380
+ const searchQuery = typeof search === "string" ? search.trim().toLowerCase() : "";
381
+
305
382
  let entries = [];
306
383
 
307
384
  // Workspace tools
@@ -339,23 +416,24 @@ export function listCustomTools(rootDir, opts = {}) {
339
416
  if (category) {
340
417
  entries = entries.filter((e) => e.category === category);
341
418
  }
342
- if (tags.length > 0) {
419
+ if (requestedTags.length > 0) {
343
420
  entries = entries.filter((e) =>
344
- tags.some((t) => (e.tags || []).includes(t)),
421
+ requestedTags.some((t) => (e.tags || []).includes(t)),
345
422
  );
346
423
  }
347
- if (search) {
348
- const q = search.toLowerCase();
424
+ if (searchQuery) {
349
425
  entries = entries.filter(
350
426
  (e) =>
351
- e.id.includes(q) ||
352
- e.title.toLowerCase().includes(q) ||
353
- e.description.toLowerCase().includes(q) ||
354
- (e.tags || []).some((t) => t.includes(q)),
427
+ e.id.toLowerCase().includes(searchQuery) ||
428
+ e.title.toLowerCase().includes(searchQuery) ||
429
+ e.description.toLowerCase().includes(searchQuery) ||
430
+ (e.tags || []).some((t) => t.toLowerCase().includes(searchQuery)),
355
431
  );
356
432
  }
357
433
 
358
- return entries.sort((a, b) => b.usageCount - a.usageCount);
434
+ return entries.sort(
435
+ (a, b) => normalizeUsageCount(b.usageCount) - normalizeUsageCount(a.usageCount),
436
+ );
359
437
  }
360
438
 
361
439
  /**
@@ -366,11 +444,14 @@ export function listCustomTools(rootDir, opts = {}) {
366
444
  * @returns {{ entry: CustomToolEntry, script: string }|null}
367
445
  */
368
446
  export function getCustomTool(rootDir, toolId) {
447
+ const normalizedToolId = normalizeToolId(toolId);
448
+ if (!normalizedToolId) return null;
449
+
369
450
  // Workspace-scoped takes precedence, then global, then builtin
370
451
  for (const isGlobal of [false, true]) {
371
452
  const storeDir = getToolStore(rootDir, { global: isGlobal });
372
453
  const index = safeReadIndex(storeDir);
373
- const entry = index.find((e) => e.id === toolId);
454
+ const entry = index.find((e) => e.id === normalizedToolId);
374
455
  if (!entry) continue;
375
456
 
376
457
  const sPath = scriptPath(storeDir, entry.id, entry.lang);
@@ -383,7 +464,7 @@ export function getCustomTool(rootDir, toolId) {
383
464
  }
384
465
 
385
466
  // Fall back to built-in tools shipped with bosun
386
- const builtinDef = BUILTIN_TOOLS.find((b) => b.id === toolId);
467
+ const builtinDef = BUILTIN_TOOLS.find((b) => b.id === normalizedToolId);
387
468
  if (builtinDef) {
388
469
  const sPath = resolve(BUILTIN_TOOLS_DIR, `${builtinDef.id}.${builtinDef.lang}`);
389
470
  if (existsSync(sPath)) {
@@ -414,6 +495,10 @@ export function getCustomTool(rootDir, toolId) {
414
495
  * @returns {CustomToolEntry}
415
496
  */
416
497
  export function registerCustomTool(rootDir, def) {
498
+ if (!isPlainObject(def)) {
499
+ throw new TypeError("registerCustomTool: definition object is required");
500
+ }
501
+
417
502
  const {
418
503
  title,
419
504
  description,
@@ -449,19 +534,29 @@ export function registerCustomTool(rootDir, def) {
449
534
  );
450
535
  }
451
536
 
537
+ const explicitId = def.id == null ? null : normalizeToolId(def.id);
538
+ if (def.id != null && !explicitId) {
539
+ throw new TypeError(
540
+ "registerCustomTool: id must match /^[A-Za-z0-9][A-Za-z0-9_-]{0,59}$/ and must not contain path separators",
541
+ );
542
+ }
543
+
452
544
  const storeDir = getToolStore(rootDir, { global: isGlobal });
453
545
  const index = safeReadIndex(storeDir);
454
546
 
455
- const id = def.id || slugify(title) || `tool-${Date.now()}`;
547
+ const id = explicitId || slugify(title) || `tool-${Date.now()}`;
456
548
  const existingIdx = index.findIndex((e) => e.id === id);
457
549
  const now = nowISO();
550
+ const normalizedSkills = normalizeStringList(skills, { lowercase: false });
551
+ const normalizedAgents = normalizeStringList(agents, { lowercase: false });
552
+ const normalizedTemplates = normalizeStringList(templates, { lowercase: false });
458
553
 
459
554
  /** @type {CustomToolEntry} */
460
555
  const entry = {
461
556
  id,
462
557
  title,
463
558
  description: description || "",
464
- tags: Array.from(new Set(tags.map((t) => String(t).toLowerCase()))),
559
+ tags: normalizeStringList(tags),
465
560
  category,
466
561
  lang,
467
562
  createdBy,
@@ -474,9 +569,9 @@ export function registerCustomTool(rootDir, def) {
474
569
  : {}),
475
570
  scope: isGlobal ? "global" : "workspace",
476
571
  // Affinity metadata (persisted for future skill/agent matching)
477
- ...(skills.length > 0 ? { skills } : {}),
478
- ...(agents.length > 0 ? { agents } : {}),
479
- ...(templates.length > 0 ? { templates } : {}),
572
+ ...(normalizedSkills.length > 0 ? { skills: normalizedSkills } : {}),
573
+ ...(normalizedAgents.length > 0 ? { agents: normalizedAgents } : {}),
574
+ ...(normalizedTemplates.length > 0 ? { templates: normalizedTemplates } : {}),
480
575
  ...(autoInject ? { autoInject } : {}),
481
576
  ...(version ? { version } : {}),
482
577
  };
@@ -518,6 +613,10 @@ export async function invokeCustomTool(rootDir, toolId, args = [], opts = {}) {
518
613
  throw new Error(`invokeCustomTool: tool "${toolId}" not found`);
519
614
  }
520
615
 
616
+ const cliArgs = Array.isArray(args)
617
+ ? args.map((arg) => String(arg))
618
+ : [String(args)];
619
+
521
620
  const { entry } = result;
522
621
  let sPath;
523
622
  if (entry.scope === "builtin") {
@@ -536,15 +635,15 @@ export async function invokeCustomTool(rootDir, toolId, args = [], opts = {}) {
536
635
  switch (entry.lang) {
537
636
  case "mjs":
538
637
  cmd = process.execPath; // use same node binary
539
- cmdArgs = [sPath, ...args];
638
+ cmdArgs = [sPath, ...cliArgs];
540
639
  break;
541
640
  case "sh":
542
641
  cmd = process.platform === "win32" ? "bash" : "/bin/sh";
543
- cmdArgs = [sPath, ...args];
642
+ cmdArgs = [sPath, ...cliArgs];
544
643
  break;
545
644
  case "py":
546
- cmd = "python3";
547
- cmdArgs = [sPath, ...args];
645
+ cmd = process.platform === "win32" ? "python" : "python3";
646
+ cmdArgs = [sPath, ...cliArgs];
548
647
  break;
549
648
  default:
550
649
  throw new Error(`invokeCustomTool: unsupported lang "${entry.lang}"`);
@@ -601,10 +700,13 @@ export async function invokeCustomTool(rootDir, toolId, args = [], opts = {}) {
601
700
  * @returns {Promise<void>}
602
701
  */
603
702
  export async function recordToolUsage(rootDir, toolId) {
703
+ const normalizedToolId = normalizeToolId(toolId);
704
+ if (!normalizedToolId) return;
705
+
604
706
  for (const isGlobal of [false, true]) {
605
707
  const storeDir = getToolStore(rootDir, { global: isGlobal });
606
708
  const index = safeReadIndex(storeDir);
607
- const idx = index.findIndex((e) => e.id === toolId);
709
+ const idx = index.findIndex((e) => e.id === normalizedToolId);
608
710
  if (idx < 0) continue;
609
711
  index[idx].usageCount = (index[idx].usageCount ?? 0) + 1;
610
712
  index[idx].lastUsed = nowISO();
@@ -622,9 +724,12 @@ export async function recordToolUsage(rootDir, toolId) {
622
724
  * @returns {boolean} true if the tool was found and removed
623
725
  */
624
726
  export function deleteCustomTool(rootDir, toolId, { global: isGlobal = false } = {}) {
727
+ const normalizedToolId = normalizeToolId(toolId);
728
+ if (!normalizedToolId) return false;
729
+
625
730
  const storeDir = getToolStore(rootDir, { global: isGlobal });
626
731
  const index = safeReadIndex(storeDir);
627
- const idx = index.findIndex((e) => e.id === toolId);
732
+ const idx = index.findIndex((e) => e.id === normalizedToolId);
628
733
  if (idx < 0) return false;
629
734
 
630
735
  const entry = index[idx];
@@ -651,9 +756,14 @@ export function deleteCustomTool(rootDir, toolId, { global: isGlobal = false } =
651
756
  * @returns {Promise<CustomToolEntry>} the entry as it now exists in global scope
652
757
  */
653
758
  export async function promoteToGlobal(rootDir, toolId) {
759
+ const normalizedToolId = normalizeToolId(toolId);
760
+ if (!normalizedToolId) {
761
+ throw new Error(`promoteToGlobal: workspace tool "${toolId}" not found`);
762
+ }
763
+
654
764
  const wsStore = getToolStore(rootDir, { global: false });
655
765
  const wsIndex = safeReadIndex(wsStore);
656
- const wsEntry = wsIndex.find((e) => e.id === toolId);
766
+ const wsEntry = wsIndex.find((e) => e.id === normalizedToolId);
657
767
  if (!wsEntry) {
658
768
  throw new Error(
659
769
  `promoteToGlobal: workspace tool "${toolId}" not found`,
@@ -676,7 +786,7 @@ export async function promoteToGlobal(rootDir, toolId) {
676
786
 
677
787
  // Upsert in global index
678
788
  const globalEntry = { ...wsEntry, scope: "global", updatedAt: nowISO() };
679
- const existingIdx = globalIndex.findIndex((e) => e.id === toolId);
789
+ const existingIdx = globalIndex.findIndex((e) => e.id === normalizedToolId);
680
790
  if (existingIdx >= 0) {
681
791
  globalIndex[existingIdx] = globalEntry;
682
792
  } else {
@@ -722,6 +832,8 @@ export function getToolsPromptBlock(rootDir, opts = {}) {
722
832
  template,
723
833
  limit,
724
834
  includeBuiltins,
835
+ category,
836
+ tags,
725
837
  });
726
838
  const affinityIds = new Set(affinityTools.map((t) => t.id));
727
839
  const remaining = listCustomTools(rootDir, { category, tags, includeBuiltins })
@@ -201,8 +201,7 @@ function isLikelyBosunCommandLine(commandLine) {
201
201
  normalized.includes("/bosun/") &&
202
202
  (normalized.includes("monitor.mjs") ||
203
203
  normalized.includes("cli.mjs") ||
204
- normalized.includes("agent-endpoint.mjs") ||
205
- normalized.includes("ve-orchestrator"))
204
+ normalized.includes("agent-endpoint.mjs"))
206
205
  ) {
207
206
  return true;
208
207
  }
@@ -18,6 +18,12 @@
18
18
  * createAgentEventBus(options) → AgentEventBus instance
19
19
  * AgentEventBus class
20
20
  * AGENT_EVENT — Frozen enum of all event types
21
+ *
22
+ * Canonical architecture note:
23
+ * This bus is the canonical runtime ingress for live agent events into the
24
+ * observability spine. Surfaces may subscribe or rebroadcast, but canonical
25
+ * event normalization and harness telemetry recording must remain centralized
26
+ * here plus `infra/session-telemetry.mjs`.
21
27
  */
22
28
 
23
29
  import {
@@ -26,6 +32,12 @@ import {
26
32
  snapshotRetryQueue,
27
33
  } from "./retry-queue.mjs";
28
34
  import { addSpanEvent, recordAgentError, recordIntervention } from "../infra/tracing.mjs";
35
+ import {
36
+ getHarnessTelemetrySummary,
37
+ listHarnessTelemetryEvents,
38
+ recordHarnessTelemetryEvent,
39
+ } from "../infra/session-telemetry.mjs";
40
+ import { normalizeCanonicalBusEvent } from "../infra/event-schema.mjs";
29
41
 
30
42
  const TAG = "[agent-event-bus]";
31
43
 
@@ -147,6 +159,7 @@ export class AgentEventBus {
147
159
  : (typeof globalThis.__bosun_setRetryQueueData === "function"
148
160
  ? globalThis.__bosun_setRetryQueueData
149
161
  : null);
162
+ this._configDir = options.configDir || process.cwd();
150
163
 
151
164
  /** @type {Array<{type: string, taskId: string, payload: object, ts: number}>} ring buffer */
152
165
  this._eventLog = [];
@@ -259,6 +272,7 @@ export class AgentEventBus {
259
272
  if (this._eventLog.length > this._maxEventLogSize) {
260
273
  this._eventLog.shift();
261
274
  }
275
+ this._recordCanonicalEvent(event);
262
276
 
263
277
  // ── WS broadcast
264
278
  if (!opts.skipBroadcast) {
@@ -589,6 +603,7 @@ export class AgentEventBus {
589
603
  */
590
604
  getStatus() {
591
605
  const retryQueue = snapshotRetryQueue(this._retryQueueState);
606
+ const observability = getHarnessTelemetrySummary({ configDir: this._configDir });
592
607
  return {
593
608
  started: this._started,
594
609
  eventLogSize: this._eventLog.length,
@@ -599,6 +614,10 @@ export class AgentEventBus {
599
614
  retryQueue,
600
615
  liveness: this.getAgentLiveness(),
601
616
  errorPatterns: this.getErrorPatternSummary(),
617
+ observability: {
618
+ eventCount: Number(observability?.eventCount || 0),
619
+ lastEventAt: observability?.lastEventAt || null,
620
+ },
602
621
  };
603
622
  }
604
623
 
@@ -606,6 +625,13 @@ export class AgentEventBus {
606
625
  return snapshotRetryQueue(this._retryQueueState);
607
626
  }
608
627
 
628
+ getCanonicalEventLog(filter = {}) {
629
+ return listHarnessTelemetryEvents({
630
+ ...filter,
631
+ source: filter.source || "agent-event-bus",
632
+ }, { configDir: this._configDir });
633
+ }
634
+
609
635
  clearRetryQueueTask(taskId, reason = "manual") {
610
636
  const id = String(taskId || "").trim();
611
637
  if (!id) return;
@@ -637,6 +663,13 @@ export class AgentEventBus {
637
663
  }
638
664
  }
639
665
 
666
+ _recordCanonicalEvent(event) {
667
+ recordHarnessTelemetryEvent(
668
+ normalizeCanonicalBusEvent(event),
669
+ { configDir: this._configDir },
670
+ );
671
+ }
672
+
640
673
  _updateRetryQueue(action, meta = {}) {
641
674
  this._retryQueueState = reduceRetryQueue(this._retryQueueState, action);
642
675
  const snapshot = snapshotRetryQueue(this._retryQueueState);
@@ -1100,5 +1133,3 @@ export class AgentEventBus {
1100
1133
  export function createAgentEventBus(options) {
1101
1134
  return new AgentEventBus(options);
1102
1135
  }
1103
-
1104
-
@@ -643,7 +643,7 @@ export function registerBuiltinHooks(options = {}) {
643
643
 
644
644
  // ── PrePush: agent preflight quality gate ──
645
645
  if (!skipPrePush) {
646
- const preflightScript = "node preflight.mjs";
646
+ const preflightScript = "node infra/preflight.mjs";
647
647
 
648
648
  registerHook("PrePush", {
649
649
  id: "builtin-prepush-preflight",