bosun 0.42.6 → 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 (426) hide show
  1. package/.env.example +26 -15
  2. package/README.md +1 -11
  3. package/agent/agent-event-bus.mjs +33 -2
  4. package/agent/agent-hooks.mjs +4 -52
  5. package/agent/agent-launcher.mjs +6210 -0
  6. package/agent/agent-pool.mjs +7 -4182
  7. package/agent/agent-prompt-catalog.mjs +3 -4
  8. package/agent/agent-sdk.mjs +1 -4
  9. package/agent/agent-supervisor.mjs +30 -6
  10. package/agent/auth/_shared.mjs +129 -0
  11. package/agent/auth/anthropic-api-key.mjs +13 -0
  12. package/agent/auth/azure-openai.mjs +17 -0
  13. package/agent/auth/cerebras.mjs +14 -0
  14. package/agent/auth/chatgpt-codex-subscription.mjs +15 -0
  15. package/agent/auth/claude-subscription.mjs +15 -0
  16. package/agent/auth/copilot-oauth.mjs +13 -0
  17. package/agent/auth/deepinfra.mjs +14 -0
  18. package/agent/auth/fireworks.mjs +14 -0
  19. package/agent/auth/gemini-api-key.mjs +14 -0
  20. package/agent/auth/groq.mjs +14 -0
  21. package/agent/auth/index.mjs +85 -0
  22. package/agent/auth/nebius.mjs +14 -0
  23. package/agent/auth/ollama.mjs +14 -0
  24. package/agent/auth/openai-api-key.mjs +13 -0
  25. package/agent/auth/openai-compatible.mjs +15 -0
  26. package/agent/auth/openrouter.mjs +14 -0
  27. package/agent/auth/perplexity.mjs +14 -0
  28. package/agent/auth/sambanova.mjs +14 -0
  29. package/agent/auth/together.mjs +14 -0
  30. package/agent/auth/xai.mjs +14 -0
  31. package/agent/bosun-skills.mjs +177 -68
  32. package/agent/fleet-coordinator.mjs +153 -37
  33. package/agent/harness/agent-loop.mjs +26 -0
  34. package/agent/harness/event-contract.mjs +125 -0
  35. package/agent/harness/followup-queue.mjs +33 -0
  36. package/agent/harness/message-normalizer.mjs +43 -0
  37. package/agent/harness/module-boundaries.md +73 -0
  38. package/agent/harness/run-contract.mjs +122 -0
  39. package/agent/harness/runtime-config.mjs +132 -0
  40. package/agent/harness/session-state.mjs +80 -0
  41. package/agent/harness/steering-queue.mjs +35 -0
  42. package/agent/harness/tool-runner.mjs +95 -0
  43. package/agent/harness/turn-runner.mjs +135 -0
  44. package/agent/harness-agent-service.mjs +852 -0
  45. package/agent/harness-executor-config.mjs +384 -0
  46. package/agent/hook-library.mjs +139 -0
  47. package/agent/hook-profiles.mjs +46 -108
  48. package/agent/internal-harness-control-plane.mjs +672 -0
  49. package/agent/internal-harness-profile.mjs +519 -0
  50. package/agent/internal-harness-runtime.mjs +1219 -0
  51. package/agent/lineage-graph.mjs +141 -0
  52. package/agent/primary-agent.mjs +593 -646
  53. package/agent/provider-auth-manager.mjs +830 -0
  54. package/agent/provider-auth-state.mjs +440 -0
  55. package/agent/provider-capabilities.mjs +116 -0
  56. package/agent/provider-kernel.mjs +596 -0
  57. package/agent/provider-message-transform.mjs +583 -0
  58. package/agent/provider-model-catalog.mjs +163 -0
  59. package/agent/provider-registry.mjs +657 -0
  60. package/agent/provider-runtime-discovery.mjs +147 -0
  61. package/agent/provider-session.mjs +767 -0
  62. package/agent/providers/_shared.mjs +397 -0
  63. package/agent/providers/anthropic-messages.mjs +64 -0
  64. package/agent/providers/azure-openai-responses.mjs +69 -0
  65. package/agent/providers/cerebras.mjs +66 -0
  66. package/agent/providers/claude-subscription-shim.mjs +68 -0
  67. package/agent/providers/copilot-oauth.mjs +66 -0
  68. package/agent/providers/deepinfra.mjs +66 -0
  69. package/agent/providers/fireworks.mjs +66 -0
  70. package/agent/providers/gemini-generate-content.mjs +66 -0
  71. package/agent/providers/groq.mjs +66 -0
  72. package/agent/providers/index.mjs +208 -0
  73. package/agent/providers/nebius.mjs +66 -0
  74. package/agent/providers/ollama.mjs +66 -0
  75. package/agent/providers/openai-codex-subscription.mjs +75 -0
  76. package/agent/providers/openai-compatible.mjs +65 -0
  77. package/agent/providers/openai-responses.mjs +67 -0
  78. package/agent/providers/openrouter.mjs +66 -0
  79. package/agent/providers/perplexity.mjs +66 -0
  80. package/agent/providers/provider-contract.mjs +138 -0
  81. package/agent/providers/provider-errors.mjs +63 -0
  82. package/agent/providers/provider-model-pricing.mjs +246 -0
  83. package/agent/providers/provider-stream-normalizer.mjs +7 -0
  84. package/agent/providers/provider-usage-normalizer.mjs +48 -0
  85. package/agent/providers/sambanova.mjs +66 -0
  86. package/agent/providers/together.mjs +66 -0
  87. package/agent/providers/xai.mjs +66 -0
  88. package/agent/query-engine.mjs +260 -0
  89. package/agent/retry-queue.mjs +1 -0
  90. package/agent/session-contract.mjs +127 -0
  91. package/agent/session-manager.mjs +1859 -0
  92. package/agent/session-replay.mjs +617 -0
  93. package/agent/session-snapshot-store.mjs +379 -0
  94. package/agent/skills/agent-coordination.md +6 -0
  95. package/agent/skills/background-task-execution.md +6 -0
  96. package/agent/skills/bosun-agent-api.md +6 -0
  97. package/agent/skills/code-quality-anti-patterns.md +7 -0
  98. package/agent/skills/commit-conventions.md +6 -0
  99. package/agent/skills/custom-tool-creation.md +6 -0
  100. package/agent/skills/error-recovery.md +6 -0
  101. package/agent/skills/pr-workflow.md +6 -0
  102. package/agent/skills/tdd-pattern.md +6 -0
  103. package/agent/subagent-contract.mjs +104 -0
  104. package/agent/subagent-control.mjs +633 -0
  105. package/agent/subagent-pool.mjs +260 -0
  106. package/agent/thread-contract.mjs +88 -0
  107. package/agent/thread-registry.mjs +552 -0
  108. package/agent/tool-approval-manager.mjs +259 -0
  109. package/agent/tool-builtin-catalog.mjs +855 -0
  110. package/agent/tool-contract.mjs +101 -0
  111. package/agent/tool-event-contract.mjs +99 -0
  112. package/agent/tool-execution-ledger.mjs +32 -0
  113. package/agent/tool-network-policy.mjs +86 -0
  114. package/agent/tool-orchestrator.mjs +382 -0
  115. package/agent/tool-output-truncation.mjs +70 -0
  116. package/agent/tool-registry.mjs +200 -0
  117. package/agent/tool-retry-policy.mjs +57 -0
  118. package/agent/tool-runtime-context.mjs +220 -0
  119. package/bench/harness-load-bench.mjs +281 -0
  120. package/bench/harness-parity-bench.mjs +214 -0
  121. package/bench/swebench/bosun-swebench.mjs +1 -0
  122. package/bosun-tui.mjs +59 -13
  123. package/bosun.schema.json +358 -1
  124. package/cli.mjs +641 -190
  125. package/config/config-doctor.mjs +78 -4
  126. package/config/config-editor.mjs +417 -0
  127. package/config/config.mjs +217 -117
  128. package/config/repo-config.mjs +78 -10
  129. package/config/repo-root.mjs +33 -1
  130. package/desktop/main.mjs +438 -99
  131. package/desktop/package.json +1 -1
  132. package/git/diff-stats.mjs +7 -5
  133. package/infra/anomaly-detector.mjs +115 -15
  134. package/infra/approval-projection-store.mjs +75 -0
  135. package/infra/config-reload-bus.mjs +33 -0
  136. package/infra/container-runner.mjs +37 -3
  137. package/infra/error-detector.mjs +109 -34
  138. package/infra/event-schema.mjs +353 -0
  139. package/infra/guardrails.mjs +383 -0
  140. package/infra/heartbeat-monitor.mjs +432 -0
  141. package/infra/library-manager.mjs +359 -233
  142. package/infra/live-event-projector.mjs +197 -0
  143. package/infra/maintenance.mjs +176 -37
  144. package/infra/monitor.mjs +1280 -194
  145. package/infra/preflight.mjs +71 -5
  146. package/infra/presence.mjs +33 -9
  147. package/infra/projection-contract.mjs +27 -0
  148. package/infra/provider-usage-ledger.mjs +73 -0
  149. package/infra/replay-reader.mjs +140 -0
  150. package/infra/runtime-accumulator.mjs +303 -8
  151. package/infra/runtime-metrics.mjs +156 -0
  152. package/infra/session-projection-store.mjs +169 -0
  153. package/infra/session-telemetry-runtime.mjs +580 -0
  154. package/infra/session-telemetry.mjs +338 -0
  155. package/infra/session-tracker.mjs +1407 -174
  156. package/infra/startup-service.mjs +0 -2
  157. package/infra/storage-janitor.mjs +1046 -0
  158. package/infra/subagent-projection-store.mjs +89 -0
  159. package/infra/test-runtime.mjs +53 -20
  160. package/infra/trace-export.mjs +103 -0
  161. package/infra/tracing.mjs +13 -125
  162. package/infra/tui-bridge.mjs +607 -5
  163. package/infra/update-check.mjs +7 -8
  164. package/infra/windows-hidden-child-processes.mjs +99 -0
  165. package/infra/worktree-recovery-state.mjs +15 -5
  166. package/kanban/kanban-adapter.mjs +674 -41
  167. package/kanban/repo-mirror-projection-store.mjs +871 -0
  168. package/lib/agent-configuration-guide.mjs +280 -0
  169. package/lib/hot-path-runtime.mjs +1061 -0
  170. package/lib/integrations-registry.mjs +294 -0
  171. package/lib/log-tail.mjs +101 -0
  172. package/lib/mojibake-repair.mjs +40 -0
  173. package/lib/repo-map.mjs +137 -24
  174. package/lib/request-json-api.mjs +59 -0
  175. package/lib/safe-box.mjs +56 -0
  176. package/lib/session-insights.mjs +3 -65
  177. package/lib/state-ledger-sqlite.mjs +4462 -0
  178. package/lib/vault-keychain.mjs +259 -0
  179. package/lib/vault.mjs +374 -0
  180. package/lib/workflow-flowchart-utils.mjs +326 -0
  181. package/native/bosun-telemetry/Cargo.toml +9 -0
  182. package/native/bosun-telemetry/src/export.rs +151 -0
  183. package/native/bosun-telemetry/src/main.rs +76 -0
  184. package/native/bosun-telemetry/src/metrics.rs +114 -0
  185. package/native/bosun-telemetry/src/session_telemetry.rs +178 -0
  186. package/native/bosun-unified-exec/Cargo.lock +107 -0
  187. package/native/bosun-unified-exec/Cargo.toml +8 -0
  188. package/native/bosun-unified-exec/src/async_watcher.rs +145 -0
  189. package/native/bosun-unified-exec/src/head_tail_buffer.rs +241 -0
  190. package/native/bosun-unified-exec/src/main.rs +86 -0
  191. package/native/bosun-unified-exec/src/process_manager.rs +308 -0
  192. package/native/bosun-unified-exec/src/tool_orchestrator.rs +187 -0
  193. package/package.json +214 -36
  194. package/postinstall.mjs +182 -12
  195. package/server/bosun-mcp-server.mjs +333 -3
  196. package/server/routes/harness-agent-bridge.mjs +128 -0
  197. package/server/routes/harness-approvals.mjs +290 -0
  198. package/server/routes/harness-events.mjs +469 -0
  199. package/server/routes/harness-providers.mjs +385 -0
  200. package/server/routes/harness-sessions.mjs +2230 -0
  201. package/server/routes/harness-subagents.mjs +138 -0
  202. package/server/routes/harness-surface-payload.mjs +74 -0
  203. package/server/setup-web-server.mjs +199 -24
  204. package/server/ui-server.mjs +10983 -3421
  205. package/setup.mjs +25 -2
  206. package/shell/anthropic-native-adapter.mjs +1218 -0
  207. package/shell/auth-resolver.mjs +247 -0
  208. package/shell/claude-shell.mjs +85 -2
  209. package/shell/codex-config-file.mjs +9 -0
  210. package/shell/codex-config.mjs +49 -5
  211. package/shell/codex-model-profiles.mjs +29 -59
  212. package/shell/codex-sdk-import.mjs +7 -0
  213. package/shell/codex-shell.mjs +574 -159
  214. package/shell/context-compaction.mjs +898 -0
  215. package/shell/copilot-shell.mjs +362 -130
  216. package/shell/gemini-native-adapter.mjs +411 -0
  217. package/shell/gemini-shell.mjs +121 -13
  218. package/shell/mcp-client.mjs +401 -0
  219. package/shell/mcp-registry.mjs +72 -0
  220. package/shell/message-pruner.mjs +248 -0
  221. package/shell/openai-native-adapter.mjs +1975 -0
  222. package/shell/opencode-providers.mjs +16 -816
  223. package/shell/opencode-shell.mjs +180 -9
  224. package/shell/provider-transform.mjs +386 -0
  225. package/shell/retry-fetch.mjs +244 -0
  226. package/shell/session-resume.mjs +97 -0
  227. package/shell/session-store.mjs +215 -0
  228. package/shell/shell-adapter-registry.mjs +346 -0
  229. package/shell/shell-session-compat.mjs +442 -0
  230. package/shell/smooth-stream.mjs +233 -0
  231. package/shell/stop-condition.mjs +238 -0
  232. package/shell/tool-call-repair.mjs +345 -0
  233. package/shell/tool-executor.mjs +571 -0
  234. package/task/pipeline.mjs +3 -1
  235. package/task/task-assessment.mjs +312 -6
  236. package/task/task-claims.mjs +311 -47
  237. package/task/task-cli.mjs +79 -4
  238. package/task/task-context.mjs +37 -0
  239. package/task/task-debt-ledger.mjs +110 -0
  240. package/task/task-executor.mjs +1093 -107
  241. package/task/task-replanner.mjs +553 -0
  242. package/task/task-simulate-cli.mjs +1481 -0
  243. package/task/task-store.mjs +960 -64
  244. package/telegram/executor-health-region-cache.mjs +75 -0
  245. package/telegram/harness-api-client.mjs +124 -0
  246. package/telegram/sticky-menu-state.mjs +384 -0
  247. package/telegram/telegram-bot.mjs +418 -812
  248. package/telegram/telegram-sentinel.mjs +24 -13
  249. package/telegram/telegram-surface-runtime.mjs +53 -0
  250. package/tools/generate-demo-defaults.mjs +23 -4
  251. package/tools/harness-hotpath-bench.mjs +246 -0
  252. package/tools/import-check.mjs +56 -11
  253. package/tools/install-git-hooks.mjs +96 -20
  254. package/tools/native-rust.mjs +124 -0
  255. package/tools/packed-cli-smoke.mjs +139 -53
  256. package/tools/prepublish-check.mjs +53 -1
  257. package/tools/run-workflow-guaranteed-suite.mjs +56 -0
  258. package/tools/sync-demo-ui.mjs +188 -0
  259. package/tools/syntax-check.mjs +32 -28
  260. package/tools/vite-windows-realpath-shim.mjs +274 -0
  261. package/tools/vitest-esbuild-shim.mjs +45 -0
  262. package/tools/vitest-full-suite.mjs +310 -0
  263. package/tools/vitest-runner.mjs +474 -7
  264. package/tools/workflow-orphan-worktree-recovery.mjs +24 -7
  265. package/tui/CommandPalette.js +87 -0
  266. package/tui/app.mjs +422 -36
  267. package/tui/components/status-header.mjs +39 -0
  268. package/tui/lib/command-palette.mjs +191 -0
  269. package/tui/lib/connection-target.mjs +577 -0
  270. package/tui/lib/navigation.mjs +6 -2
  271. package/tui/lib/ws-bridge.mjs +141 -51
  272. package/tui/screens/agents-screen-helpers.mjs +87 -3
  273. package/tui/screens/agents.mjs +1068 -200
  274. package/tui/screens/connection-setup.mjs +363 -0
  275. package/tui/screens/harness-approvals.mjs +7 -0
  276. package/tui/screens/harness-sessions.mjs +109 -0
  277. package/tui/screens/harness-subagents.mjs +18 -0
  278. package/tui/screens/harness-telemetry.mjs +67 -0
  279. package/tui/screens/logs.mjs +1 -5
  280. package/tui/screens/settings-screen-helpers.mjs +75 -0
  281. package/tui/screens/settings.mjs +397 -0
  282. package/tui/screens/status.mjs +126 -4
  283. package/tui/screens/telemetry-screen-helpers.mjs +158 -0
  284. package/tui/screens/telemetry.mjs +246 -0
  285. package/tui/screens/workflows.mjs +984 -0
  286. package/ui/app.js +450 -183
  287. package/ui/assets/toastui-editor-all.min.js +24 -0
  288. package/ui/components/agent-selector.js +706 -49
  289. package/ui/components/charts.js +16 -12
  290. package/ui/components/chat-view.js +536 -35
  291. package/ui/components/context-menu.js +89 -0
  292. package/ui/components/diff-viewer.js +83 -34
  293. package/ui/components/kanban-board.js +513 -94
  294. package/ui/components/session-list.js +292 -65
  295. package/ui/components/task-markdown.js +272 -0
  296. package/ui/components/workspace-switcher.js +11 -2
  297. package/ui/demo-defaults.js +13806 -3965
  298. package/ui/demo.html +817 -3
  299. package/ui/index.html +32 -6
  300. package/ui/modules/agent-events.js +309 -36
  301. package/ui/modules/api.js +236 -13
  302. package/ui/modules/chat-turn-groups.js +101 -0
  303. package/ui/modules/harness-client.js +56 -0
  304. package/ui/modules/icons.js +18 -2
  305. package/ui/modules/router.js +2 -0
  306. package/ui/modules/session-api.js +158 -14
  307. package/ui/modules/session-insights-worker.js +28 -0
  308. package/ui/modules/session-insights.js +173 -4
  309. package/ui/modules/session-surface.js +221 -0
  310. package/ui/modules/settings-schema.js +146 -26
  311. package/ui/modules/state.js +56 -3
  312. package/ui/modules/streaming.js +196 -60
  313. package/ui/modules/structured-values.js +47 -0
  314. package/ui/modules/task-hierarchy.js +374 -0
  315. package/ui/modules/worktree-recovery.js +10 -1
  316. package/ui/setup.html +174 -6
  317. package/ui/styles/components.css +982 -130
  318. package/ui/styles/kanban.css +229 -0
  319. package/ui/styles/layout.css +404 -144
  320. package/ui/styles/toastui-editor-dark.css +1 -0
  321. package/ui/styles/toastui-editor-viewer.css +6 -0
  322. package/ui/styles/toastui-editor.css +6 -0
  323. package/ui/styles/variables.css +14 -2
  324. package/ui/styles/workspace-switcher.css +22 -0
  325. package/ui/styles.css +19 -0
  326. package/ui/tabs/agents.js +1032 -96
  327. package/ui/tabs/chat.js +588 -146
  328. package/ui/tabs/context-compression-lab.js +962 -0
  329. package/ui/tabs/control.js +121 -0
  330. package/ui/tabs/dashboard.js +302 -86
  331. package/ui/tabs/guardrails.js +1140 -0
  332. package/ui/tabs/integrations.js +388 -0
  333. package/ui/tabs/library.js +19 -4
  334. package/ui/tabs/manual-flows.js +268 -52
  335. package/ui/tabs/settings.js +1604 -104
  336. package/ui/tabs/tasks.js +2544 -300
  337. package/ui/tabs/telemetry.js +102 -6
  338. package/ui/tabs/workflow-canvas-utils.mjs +172 -15
  339. package/ui/tabs/workflows.js +2421 -196
  340. package/ui/tui/App.js +117 -117
  341. package/ui/tui/HelpScreen.js +201 -0
  342. package/ui/tui/SettingsScreen.js +388 -0
  343. package/ui/tui/TasksScreen.js +25 -11
  344. package/ui/tui/TelemetryScreen.js +155 -0
  345. package/ui/tui/WorkflowsScreen.js +350 -0
  346. package/ui/tui/config-events.js +13 -0
  347. package/ui/tui/constants.js +1 -1
  348. package/ui/tui/tasks-screen-helpers.js +52 -0
  349. package/ui/tui/telemetry-helpers.js +158 -0
  350. package/ui/tui/useTasks.js +1 -6
  351. package/ui/tui/useWebSocket.js +1 -7
  352. package/ui/tui/useWorkflows.js +120 -5
  353. package/ui/tui/workflows-screen-helpers.js +220 -0
  354. package/utils.mjs +9 -22
  355. package/voice/vision-session-state.mjs +257 -0
  356. package/voice/voice-action-dispatcher.mjs +57 -12
  357. package/voice/voice-agents-sdk.mjs +1 -1
  358. package/voice/voice-auth-manager.mjs +102 -123
  359. package/voice/voice-tool-definitions.mjs +7 -7
  360. package/voice/voice-tools.mjs +837 -101
  361. package/workflow/action-approval.mjs +415 -0
  362. package/workflow/approval-queue.mjs +1254 -0
  363. package/workflow/credential-store.mjs +553 -0
  364. package/workflow/cron-scheduler.mjs +512 -0
  365. package/workflow/declarative-workflows.mjs +21 -2
  366. package/workflow/delegation-runtime.mjs +557 -0
  367. package/workflow/execution-ledger.mjs +1317 -33
  368. package/workflow/harness-approval-node.mjs +237 -0
  369. package/workflow/harness-output-contract.mjs +86 -0
  370. package/workflow/harness-session-node.mjs +160 -0
  371. package/workflow/harness-subagent-node.mjs +483 -0
  372. package/workflow/harness-tool-node.mjs +176 -0
  373. package/workflow/heavy-runner-pool.mjs +71 -4
  374. package/workflow/manual-flows.mjs +969 -28
  375. package/workflow/mcp-discovery-proxy.mjs +349 -137
  376. package/workflow/mcp-registry.mjs +331 -27
  377. package/workflow/meeting-workflow-service.mjs +24 -12
  378. package/workflow/pipeline-workflows.mjs +44 -2
  379. package/workflow/pipeline.mjs +72 -28
  380. package/workflow/project-detection.mjs +31 -6
  381. package/workflow/research-evidence-sidecar.mjs +1246 -0
  382. package/workflow/run-evaluator.mjs +2155 -0
  383. package/workflow/workflow-cli.mjs +229 -2
  384. package/workflow/workflow-contract.mjs +130 -2
  385. package/workflow/workflow-engine.mjs +5520 -298
  386. package/workflow/workflow-nodes/actions.mjs +15526 -0
  387. package/workflow/workflow-nodes/agent.mjs +1863 -0
  388. package/workflow/workflow-nodes/conditions.mjs +307 -0
  389. package/workflow/workflow-nodes/definitions.mjs +176 -15
  390. package/workflow/workflow-nodes/flow.mjs +749 -0
  391. package/workflow/workflow-nodes/loop.mjs +449 -0
  392. package/workflow/workflow-nodes/meetings.mjs +456 -0
  393. package/workflow/workflow-nodes/notifications.mjs +169 -0
  394. package/workflow/workflow-nodes/transforms.mjs +50 -24
  395. package/workflow/workflow-nodes/triggers.mjs +1405 -0
  396. package/workflow/workflow-nodes/validation.mjs +722 -0
  397. package/workflow/workflow-nodes.mjs +38 -15781
  398. package/workflow/workflow-serializer.mjs +294 -0
  399. package/workflow/workflow-templates.mjs +197 -12
  400. package/workflow-templates/_helpers.mjs +1 -3
  401. package/workflow-templates/agents.mjs +235 -27
  402. package/workflow-templates/bosun-native.mjs +3 -1
  403. package/workflow-templates/code-quality.mjs +1 -217
  404. package/workflow-templates/continuation-loop.mjs +22 -7
  405. package/workflow-templates/coverage.mjs +6 -2
  406. package/workflow-templates/github.mjs +2263 -136
  407. package/workflow-templates/planning.mjs +1 -22
  408. package/workflow-templates/reliability.mjs +383 -12
  409. package/workflow-templates/research-evidence.mjs +389 -0
  410. package/workflow-templates/security.mjs +75 -62
  411. package/workflow-templates/sub-workflows.mjs +11 -3
  412. package/workflow-templates/task-batch.mjs +100 -10
  413. package/workflow-templates/task-lifecycle.mjs +313 -49
  414. package/workspace/context-cache.mjs +934 -102
  415. package/workspace/context-indexer.mjs +915 -9
  416. package/workspace/context-injector.mjs +144 -0
  417. package/workspace/execution-journal.mjs +255 -0
  418. package/workspace/scope-locks.mjs +481 -0
  419. package/workspace/shared-knowledge.mjs +496 -91
  420. package/workspace/shared-state-manager.mjs +344 -1
  421. package/workspace/skillbook-store.mjs +681 -0
  422. package/workspace/workspace-manager.mjs +14 -8
  423. package/workspace/workspace-monitor.mjs +3 -3
  424. package/workspace/worktree-manager.mjs +54 -12
  425. package/workspace/worktree-setup.mjs +634 -43
  426. package/agent/rotate-agent-logs.sh +0 -134
@@ -0,0 +1,577 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { request as httpRequest } from "node:http";
4
+ import { request as httpsRequest } from "node:https";
5
+ import { dirname, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const REMOTE_CONNECTION_FILE = "remote-connection.json";
9
+ const UI_INSTANCE_LOCK_FILE = "ui-server.instance.lock.json";
10
+ const DEFAULT_LOCAL_CONNECTION_NAME = "Local Backend";
11
+ const DEFAULT_LOCAL_HOST = "127.0.0.1";
12
+ const DEFAULT_LOCAL_PORT = 3080;
13
+
14
+ function toTrimmedString(value) {
15
+ return String(value ?? "").trim();
16
+ }
17
+
18
+ function toConnectionId(value, fallback = "") {
19
+ const src = toTrimmedString(value).toLowerCase();
20
+ let result = "";
21
+ for (const ch of src) {
22
+ if ((ch >= "a" && ch <= "z") || (ch >= "0" && ch <= "9")) {
23
+ result += ch;
24
+ } else if (result.length > 0 && result[result.length - 1] !== "-") {
25
+ result += "-";
26
+ }
27
+ }
28
+ while (result.endsWith("-")) result = result.slice(0, -1);
29
+ return result || fallback || `connection-${Date.now()}`;
30
+ }
31
+
32
+ function normalizeConnectionName(entry, index) {
33
+ return toTrimmedString(entry?.name)
34
+ || toTrimmedString(entry?.label)
35
+ || toTrimmedString(entry?.endpoint)
36
+ || `Connection ${index + 1}`;
37
+ }
38
+
39
+ export function buildLocalConnectionEntry(entry = {}) {
40
+ const name = toTrimmedString(entry?.name) || DEFAULT_LOCAL_CONNECTION_NAME;
41
+ const parsed = parseConnectionEndpoint(
42
+ entry?.endpoint || "",
43
+ entry?.httpProtocol || entry?.protocol || "http",
44
+ );
45
+ const port = parsed?.port || Number(entry?.port || 0);
46
+ if (!Number.isFinite(port) || port <= 0) {
47
+ return null;
48
+ }
49
+
50
+ const httpProtocol = parsed?.httpProtocol
51
+ || normalizeHttpProtocol(entry?.httpProtocol || entry?.protocol || "http");
52
+ const host = parsed?.host
53
+ || toTrimmedString(entry?.host || DEFAULT_LOCAL_HOST)
54
+ || DEFAULT_LOCAL_HOST;
55
+
56
+ return {
57
+ name,
58
+ endpoint: `${httpProtocol}://${host}:${port}`,
59
+ host,
60
+ port,
61
+ protocol: httpProtocol === "https" ? "wss" : "ws",
62
+ httpProtocol,
63
+ };
64
+ }
65
+
66
+ function normalizeLocalConnectionConfig(raw = {}) {
67
+ return buildLocalConnectionEntry(raw) || null;
68
+ }
69
+
70
+ export function normalizeRemoteConnectionConfig(raw = {}) {
71
+ const normalizedConnections = [];
72
+ const sourceConnections = Array.isArray(raw?.connections)
73
+ ? raw.connections
74
+ : (raw?.endpoint || raw?.apiKey)
75
+ ? [{
76
+ id: raw?.id || raw?.activeConnectionId || raw?.currentConnectionId || "primary",
77
+ name: raw?.name || raw?.label || raw?.endpoint || "Primary",
78
+ endpoint: raw?.endpoint,
79
+ apiKey: raw?.apiKey,
80
+ enabled: raw?.enabled !== false,
81
+ }]
82
+ : [];
83
+
84
+ for (let index = 0; index < sourceConnections.length; index += 1) {
85
+ const entry = sourceConnections[index];
86
+ const endpoint = toTrimmedString(entry?.endpoint);
87
+ if (!endpoint) continue;
88
+ const name = normalizeConnectionName(entry, index);
89
+ normalizedConnections.push({
90
+ id: toConnectionId(entry?.id || name, `connection-${index + 1}`),
91
+ name,
92
+ endpoint,
93
+ apiKey: toTrimmedString(entry?.apiKey),
94
+ enabled: entry?.enabled !== false,
95
+ });
96
+ }
97
+
98
+ const enabled = raw?.enabled !== false && normalizedConnections.length > 0;
99
+ let activeConnectionId = toTrimmedString(raw?.activeConnectionId || raw?.currentConnectionId);
100
+ if (!activeConnectionId && normalizedConnections.length > 0) {
101
+ const legacyEndpoint = toTrimmedString(raw?.endpoint);
102
+ const matched = legacyEndpoint
103
+ ? normalizedConnections.find((entry) => entry.endpoint === legacyEndpoint)
104
+ : null;
105
+ activeConnectionId = matched?.id || normalizedConnections.find((entry) => entry.enabled !== false)?.id || normalizedConnections[0].id;
106
+ }
107
+
108
+ const activeConnection = normalizedConnections.find((entry) => entry.id === activeConnectionId)
109
+ || normalizedConnections.find((entry) => entry.enabled !== false)
110
+ || normalizedConnections[0]
111
+ || null;
112
+
113
+ return {
114
+ enabled: Boolean(enabled && activeConnection),
115
+ activeConnectionId: activeConnection?.id || "",
116
+ endpoint: activeConnection?.endpoint || "",
117
+ apiKey: activeConnection?.apiKey || "",
118
+ name: activeConnection?.name || "",
119
+ connections: normalizedConnections,
120
+ localConnection: normalizeLocalConnectionConfig(raw?.localConnection || raw?.local || {}),
121
+ };
122
+ }
123
+
124
+ export function listRemoteConnections(config = {}) {
125
+ return normalizeRemoteConnectionConfig(config).connections;
126
+ }
127
+
128
+ export function setActiveRemoteConnection(config = {}, connectionId = "") {
129
+ const normalized = normalizeRemoteConnectionConfig(config);
130
+ const nextActive = normalized.connections.find((entry) => entry.id === connectionId)
131
+ || normalized.connections[0]
132
+ || null;
133
+ return normalizeRemoteConnectionConfig({
134
+ ...normalized,
135
+ activeConnectionId: nextActive?.id || "",
136
+ enabled: Boolean(nextActive),
137
+ });
138
+ }
139
+
140
+ export function upsertRemoteConnection(config = {}, entry = {}, options = {}) {
141
+ const normalized = normalizeRemoteConnectionConfig(config);
142
+ const endpoint = toTrimmedString(entry?.endpoint);
143
+ if (!endpoint) {
144
+ return normalized;
145
+ }
146
+ const requestedName = normalizeConnectionName(entry, normalized.connections.length);
147
+ const id = toConnectionId(entry?.id || requestedName, `connection-${normalized.connections.length + 1}`);
148
+ const nextEntry = {
149
+ id,
150
+ name: requestedName,
151
+ endpoint,
152
+ apiKey: toTrimmedString(entry?.apiKey),
153
+ enabled: entry?.enabled !== false,
154
+ };
155
+ const existingIndex = normalized.connections.findIndex((item) => item.id === id || item.endpoint === endpoint);
156
+ const nextConnections = [...normalized.connections];
157
+ if (existingIndex >= 0) {
158
+ nextConnections[existingIndex] = {
159
+ ...nextConnections[existingIndex],
160
+ ...nextEntry,
161
+ };
162
+ } else {
163
+ nextConnections.push(nextEntry);
164
+ }
165
+ return normalizeRemoteConnectionConfig({
166
+ ...normalized,
167
+ enabled: true,
168
+ activeConnectionId: options.makeActive === false ? normalized.activeConnectionId : nextEntry.id,
169
+ connections: nextConnections,
170
+ });
171
+ }
172
+
173
+ export function setLocalConnectionConfig(config = {}, entry = {}) {
174
+ const normalized = normalizeRemoteConnectionConfig(config);
175
+ const nextLocalConnection = buildLocalConnectionEntry({
176
+ ...normalized.localConnection,
177
+ ...entry,
178
+ });
179
+ return normalizeRemoteConnectionConfig({
180
+ ...normalized,
181
+ localConnection: nextLocalConnection,
182
+ });
183
+ }
184
+
185
+ export function defaultConfigDir() {
186
+ const explicit = toTrimmedString(
187
+ process.env.BOSUN_DIR
188
+ || process.env.BOSUN_HOME
189
+ || "",
190
+ );
191
+ if (explicit) return resolve(explicit);
192
+ return resolve(process.cwd(), ".bosun");
193
+ }
194
+
195
+ export function normalizeWsProtocol(protocol) {
196
+ const value = toTrimmedString(protocol).toLowerCase().replace(/:$/, "");
197
+ return value === "wss" || value === "https" ? "wss" : "ws";
198
+ }
199
+
200
+ export function normalizeHttpProtocol(protocol) {
201
+ const value = toTrimmedString(protocol).toLowerCase().replace(/:$/, "");
202
+ return value === "https" || value === "wss" ? "https" : "http";
203
+ }
204
+
205
+ export function readUiInstanceLock(configDir = defaultConfigDir()) {
206
+ try {
207
+ const lockPath = resolve(configDir, ".cache", UI_INSTANCE_LOCK_FILE);
208
+ if (!existsSync(lockPath)) return null;
209
+ const parsed = JSON.parse(readFileSync(lockPath, "utf8"));
210
+ return parsed && typeof parsed === "object" ? parsed : null;
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+
216
+ export function readRemoteConnectionConfig(configDir = defaultConfigDir()) {
217
+ try {
218
+ const filePath = resolve(configDir, REMOTE_CONNECTION_FILE);
219
+ if (!existsSync(filePath)) {
220
+ return normalizeRemoteConnectionConfig({});
221
+ }
222
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
223
+ return normalizeRemoteConnectionConfig(parsed);
224
+ } catch {
225
+ return normalizeRemoteConnectionConfig({});
226
+ }
227
+ }
228
+
229
+ export function saveRemoteConnectionConfig(
230
+ config = {},
231
+ configDir = defaultConfigDir(),
232
+ ) {
233
+ const dir = resolve(configDir);
234
+ mkdirSync(dir, { recursive: true });
235
+ const payload = normalizeRemoteConnectionConfig(config);
236
+ writeFileSync(
237
+ resolve(dir, REMOTE_CONNECTION_FILE),
238
+ JSON.stringify(payload, null, 2),
239
+ "utf8",
240
+ );
241
+ return payload;
242
+ }
243
+
244
+ export function clearRemoteConnectionConfig(configDir = defaultConfigDir()) {
245
+ const current = readRemoteConnectionConfig(configDir);
246
+ return saveRemoteConnectionConfig(
247
+ {
248
+ ...current,
249
+ enabled: false,
250
+ activeConnectionId: "",
251
+ endpoint: "",
252
+ apiKey: "",
253
+ name: "",
254
+ connections: [],
255
+ },
256
+ configDir,
257
+ );
258
+ }
259
+
260
+ export function parseConnectionEndpoint(endpoint, fallbackProtocol = "http") {
261
+ const raw = toTrimmedString(endpoint);
262
+ if (!raw) return null;
263
+ const normalizedFallback = normalizeHttpProtocol(fallbackProtocol);
264
+ const candidate = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw)
265
+ ? raw
266
+ : `${normalizedFallback}://${raw}`;
267
+ try {
268
+ const parsed = new URL(candidate);
269
+ const httpProtocol = normalizeHttpProtocol(parsed.protocol);
270
+ const wsProtocol = httpProtocol === "https" ? "wss" : "ws";
271
+ const port = Number(
272
+ parsed.port || (httpProtocol === "https" ? 443 : 80),
273
+ );
274
+ if (!Number.isFinite(port) || port <= 0) return null;
275
+ return {
276
+ endpoint: parsed.origin,
277
+ host: parsed.hostname,
278
+ port,
279
+ protocol: wsProtocol,
280
+ httpProtocol,
281
+ };
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ export function resolveLocalTuiConnectionTarget(options = {}) {
288
+ const configDir = options.configDir || defaultConfigDir();
289
+ const env = options.env || process.env;
290
+ const config = options.config || {};
291
+ const savedConfig = normalizeRemoteConnectionConfig(
292
+ options.connectionConfig || readRemoteConnectionConfig(configDir),
293
+ );
294
+ const defaultPort = Number(
295
+ env.TELEGRAM_UI_PORT
296
+ || env.BOSUN_PORT
297
+ || config?.telegramUiPort
298
+ || savedConfig.localConnection?.port
299
+ || DEFAULT_LOCAL_PORT,
300
+ ) || DEFAULT_LOCAL_PORT;
301
+
302
+ const instance = readUiInstanceLock(configDir);
303
+ if (instance?.url || instance?.host || instance?.port) {
304
+ const parsed = instance?.url
305
+ ? parseConnectionEndpoint(instance.url, instance.protocol || "http")
306
+ : null;
307
+ if (parsed) {
308
+ return {
309
+ ...parsed,
310
+ apiKey: "",
311
+ source: "ui-instance-lock",
312
+ };
313
+ }
314
+ const host = toTrimmedString(instance?.host || "127.0.0.1") || "127.0.0.1";
315
+ const port = Number(instance?.port || 0) || defaultPort;
316
+ const httpProtocol = normalizeHttpProtocol(instance?.protocol || "http");
317
+ return {
318
+ endpoint: `${httpProtocol}://${host}:${port}`,
319
+ host,
320
+ port,
321
+ protocol: httpProtocol === "https" ? "wss" : "ws",
322
+ httpProtocol,
323
+ apiKey: "",
324
+ source: "ui-instance-lock",
325
+ };
326
+ }
327
+
328
+ if (savedConfig.localConnection?.endpoint) {
329
+ return {
330
+ ...savedConfig.localConnection,
331
+ apiKey: "",
332
+ source: "saved-local",
333
+ };
334
+ }
335
+
336
+ return {
337
+ endpoint: `http://127.0.0.1:${defaultPort}`,
338
+ host: "127.0.0.1",
339
+ port: defaultPort,
340
+ protocol: "ws",
341
+ httpProtocol: "http",
342
+ apiKey: "",
343
+ source: "default-local",
344
+ };
345
+ }
346
+
347
+ function resolveBosunRuntimeRoot() {
348
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
349
+ }
350
+
351
+ function resolveBosunCliPath() {
352
+ return resolve(resolveBosunRuntimeRoot(), "cli.mjs");
353
+ }
354
+
355
+ function delay(ms) {
356
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
357
+ }
358
+
359
+ export async function ensureLocalBosunBackendRunning(options = {}) {
360
+ const configDir = options.configDir || defaultConfigDir();
361
+ const env = options.env || process.env;
362
+ const config = options.config || {};
363
+ const timeoutMs = Math.max(2000, Number(options.timeoutMs || 20000));
364
+ const probeTimeoutMs = Math.min(4000, timeoutMs);
365
+ const resolveTarget = () => resolveLocalTuiConnectionTarget({ configDir, config, env });
366
+
367
+ let target = resolveTarget();
368
+ let probe = await testConnectionTarget(target, "", { timeoutMs: probeTimeoutMs });
369
+ if (probe.ok) {
370
+ const saved = saveRemoteConnectionConfig(
371
+ setLocalConnectionConfig(readRemoteConnectionConfig(configDir), target),
372
+ configDir,
373
+ );
374
+ return {
375
+ ok: true,
376
+ started: false,
377
+ pid: null,
378
+ target: {
379
+ ...target,
380
+ source: target.source || (saved.localConnection?.endpoint ? "saved-local" : ""),
381
+ },
382
+ error: "",
383
+ };
384
+ }
385
+
386
+ const cliPath = resolveBosunCliPath();
387
+ if (!existsSync(cliPath)) {
388
+ return {
389
+ ok: false,
390
+ started: false,
391
+ pid: null,
392
+ target,
393
+ error: `CLI not found at ${cliPath}`,
394
+ };
395
+ }
396
+
397
+ let pid = null;
398
+ try {
399
+ const args = [cliPath, "--daemon", "--no-update-check"];
400
+ if (configDir) {
401
+ args.push("--config-dir", resolve(configDir));
402
+ }
403
+ const child = spawn(process.execPath, args, {
404
+ cwd: resolveBosunRuntimeRoot(),
405
+ detached: true,
406
+ stdio: "ignore",
407
+ windowsHide: true,
408
+ env: {
409
+ ...process.env,
410
+ ...env,
411
+ BOSUN_CONNECTION_AUTO_LAUNCH: "1",
412
+ },
413
+ });
414
+ child.unref();
415
+ pid = Number(child.pid || 0) || null;
416
+ } catch (error) {
417
+ return {
418
+ ok: false,
419
+ started: false,
420
+ pid: null,
421
+ target,
422
+ error: String(error?.message || "Failed to launch local backend"),
423
+ };
424
+ }
425
+
426
+ const deadline = Date.now() + timeoutMs;
427
+ while (Date.now() < deadline) {
428
+ await delay(500);
429
+ target = resolveTarget();
430
+ probe = await testConnectionTarget(target, "", { timeoutMs: 2000 });
431
+ if (probe.ok) {
432
+ saveRemoteConnectionConfig(
433
+ setLocalConnectionConfig(readRemoteConnectionConfig(configDir), target),
434
+ configDir,
435
+ );
436
+ return {
437
+ ok: true,
438
+ started: true,
439
+ pid,
440
+ target,
441
+ error: "",
442
+ };
443
+ }
444
+ }
445
+
446
+ return {
447
+ ok: false,
448
+ started: true,
449
+ pid,
450
+ target,
451
+ error: `Local backend did not become reachable within ${Math.round(timeoutMs / 1000)}s`,
452
+ };
453
+ }
454
+
455
+ export async function testConnectionTarget(endpoint, apiKey = "", options = {}) {
456
+ const parsed = typeof endpoint === "string"
457
+ ? parseConnectionEndpoint(endpoint, options.fallbackProtocol || "http")
458
+ : endpoint;
459
+ if (!parsed?.endpoint) {
460
+ return {
461
+ ok: false,
462
+ error: "Invalid endpoint URL",
463
+ target: null,
464
+ statusCode: 0,
465
+ };
466
+ }
467
+
468
+ const timeoutMs = Math.max(1000, Number(options.timeoutMs || 4000));
469
+ const targetUrl = new URL("/api/status", parsed.endpoint);
470
+ const headers = {};
471
+ const normalizedApiKey = toTrimmedString(apiKey);
472
+ if (normalizedApiKey) {
473
+ headers["x-api-key"] = normalizedApiKey;
474
+ }
475
+ const requester = targetUrl.protocol === "https:" ? httpsRequest : httpRequest;
476
+
477
+ return new Promise((resolveProbe) => {
478
+ const req = requester(targetUrl, {
479
+ method: "GET",
480
+ headers,
481
+ timeout: timeoutMs,
482
+ rejectUnauthorized: options.rejectUnauthorized ?? false,
483
+ }, (res) => {
484
+ const statusCode = Number(res.statusCode || 0);
485
+ res.resume();
486
+ if (statusCode >= 200 && statusCode < 400) {
487
+ resolveProbe({
488
+ ok: true,
489
+ error: "",
490
+ target: parsed,
491
+ statusCode,
492
+ });
493
+ return;
494
+ }
495
+ const authFailure = statusCode === 401 || statusCode === 403;
496
+ resolveProbe({
497
+ ok: false,
498
+ error: authFailure ? "Authentication failed" : `Request failed (${statusCode || "unknown"})`,
499
+ target: parsed,
500
+ statusCode,
501
+ });
502
+ });
503
+ req.on("error", (error) => {
504
+ resolveProbe({
505
+ ok: false,
506
+ error: String(error?.message || "Connection failed"),
507
+ target: parsed,
508
+ statusCode: 0,
509
+ });
510
+ });
511
+ req.on("timeout", () => {
512
+ req.destroy(new Error("Connection timed out"));
513
+ });
514
+ req.end();
515
+ });
516
+ }
517
+
518
+ export function resolveTuiConnectionTarget(options = {}) {
519
+ const configDir = options.configDir || defaultConfigDir();
520
+ const env = options.env || process.env;
521
+ const config = options.config || {};
522
+ const explicitEndpoint = toTrimmedString(options.endpoint);
523
+ const explicitHost = toTrimmedString(options.host);
524
+ const explicitPort = Number(options.port || 0);
525
+ const explicitProtocol = toTrimmedString(options.protocol);
526
+ const explicitApiKey = toTrimmedString(options.apiKey);
527
+ const defaultPort = Number(
528
+ env.TELEGRAM_UI_PORT
529
+ || env.BOSUN_PORT
530
+ || config?.telegramUiPort
531
+ || 3080,
532
+ ) || 3080;
533
+
534
+ if (explicitEndpoint) {
535
+ const parsed = parseConnectionEndpoint(explicitEndpoint, explicitProtocol || "http");
536
+ if (parsed) {
537
+ return {
538
+ ...parsed,
539
+ apiKey: explicitApiKey,
540
+ source: "cli-endpoint",
541
+ };
542
+ }
543
+ }
544
+
545
+ if (explicitHost || explicitPort > 0 || explicitProtocol) {
546
+ const wsProtocol = normalizeWsProtocol(explicitProtocol || "ws");
547
+ const httpProtocol = normalizeHttpProtocol(wsProtocol);
548
+ const host = explicitHost || "127.0.0.1";
549
+ const port = explicitPort > 0 ? explicitPort : defaultPort;
550
+ return {
551
+ endpoint: `${httpProtocol}://${host}:${port}`,
552
+ host,
553
+ port,
554
+ protocol: wsProtocol,
555
+ httpProtocol,
556
+ apiKey: explicitApiKey,
557
+ source: "cli-host-port",
558
+ };
559
+ }
560
+
561
+ const remote = readRemoteConnectionConfig(configDir);
562
+ if (remote.enabled && remote.endpoint) {
563
+ const parsed = parseConnectionEndpoint(remote.endpoint, "https");
564
+ if (parsed) {
565
+ return {
566
+ ...parsed,
567
+ apiKey: explicitApiKey || remote.apiKey || toTrimmedString(env.BOSUN_API_KEY),
568
+ source: "saved-remote",
569
+ };
570
+ }
571
+ }
572
+
573
+ return {
574
+ ...resolveLocalTuiConnectionTarget({ configDir, config, env }),
575
+ apiKey: explicitApiKey,
576
+ };
577
+ }
@@ -1,16 +1,20 @@
1
1
  const SCREEN_ORDER = ["status", "tasks", "agents", "logs"];
2
+ const EXTENDED_SCREEN_ORDER = [...SCREEN_ORDER, "workflows", "telemetry", "settings"];
2
3
  const SCREEN_BY_INPUT = new Map([
3
4
  ["1", "status"],
4
5
  ["2", "tasks"],
5
6
  ["3", "agents"],
6
7
  ["4", "logs"],
8
+ ["5", "workflows"],
9
+ ["6", "telemetry"],
10
+ ["7", "settings"],
7
11
  ]);
8
12
 
9
13
  export function getNextScreenForInput(currentScreen = "status", input = "") {
10
14
  const next = SCREEN_BY_INPUT.get(String(input || "").trim());
11
15
  if (next) return next;
12
- if (SCREEN_ORDER.includes(currentScreen)) return currentScreen;
16
+ if (EXTENDED_SCREEN_ORDER.includes(currentScreen)) return currentScreen;
13
17
  return "status";
14
18
  }
15
19
 
16
- export { SCREEN_ORDER };
20
+ export { SCREEN_ORDER, EXTENDED_SCREEN_ORDER };