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.
- package/.env.example +36 -51
- package/README.md +19 -3
- package/agent/agent-custom-tools.mjs +138 -26
- package/agent/agent-endpoint.mjs +1 -2
- package/agent/agent-event-bus.mjs +33 -2
- package/agent/agent-hooks.mjs +1 -1
- package/agent/agent-launcher.mjs +6210 -0
- package/agent/agent-pool.mjs +7 -4018
- package/agent/agent-prompt-catalog.mjs +5 -6
- package/agent/agent-prompts.mjs +62 -6
- package/agent/agent-sdk.mjs +130 -0
- package/agent/agent-supervisor.mjs +30 -6
- package/agent/auth/_shared.mjs +129 -0
- package/agent/auth/anthropic-api-key.mjs +13 -0
- package/agent/auth/azure-openai.mjs +17 -0
- package/agent/auth/cerebras.mjs +14 -0
- package/agent/auth/chatgpt-codex-subscription.mjs +15 -0
- package/agent/auth/claude-subscription.mjs +15 -0
- package/agent/auth/copilot-oauth.mjs +13 -0
- package/agent/auth/deepinfra.mjs +14 -0
- package/agent/auth/fireworks.mjs +14 -0
- package/agent/auth/gemini-api-key.mjs +14 -0
- package/agent/auth/groq.mjs +14 -0
- package/agent/auth/index.mjs +85 -0
- package/agent/auth/nebius.mjs +14 -0
- package/agent/auth/ollama.mjs +14 -0
- package/agent/auth/openai-api-key.mjs +13 -0
- package/agent/auth/openai-compatible.mjs +15 -0
- package/agent/auth/openrouter.mjs +14 -0
- package/agent/auth/perplexity.mjs +14 -0
- package/agent/auth/sambanova.mjs +14 -0
- package/agent/auth/together.mjs +14 -0
- package/agent/auth/xai.mjs +14 -0
- package/agent/autofix-prompts.mjs +2 -2
- package/agent/autofix.mjs +2 -2
- package/agent/bosun-skills.mjs +215 -86
- package/agent/fleet-coordinator.mjs +161 -32
- package/agent/harness/agent-loop.mjs +26 -0
- package/agent/harness/event-contract.mjs +125 -0
- package/agent/harness/followup-queue.mjs +33 -0
- package/agent/harness/message-normalizer.mjs +43 -0
- package/agent/harness/module-boundaries.md +73 -0
- package/agent/harness/run-contract.mjs +122 -0
- package/agent/harness/runtime-config.mjs +132 -0
- package/agent/harness/session-state.mjs +80 -0
- package/agent/harness/steering-queue.mjs +35 -0
- package/agent/harness/tool-runner.mjs +95 -0
- package/agent/harness/turn-runner.mjs +135 -0
- package/agent/harness-agent-service.mjs +852 -0
- package/agent/harness-executor-config.mjs +384 -0
- package/agent/hook-library.mjs +141 -2
- package/agent/hook-profiles.mjs +15 -2
- package/agent/internal-harness-control-plane.mjs +672 -0
- package/agent/internal-harness-profile.mjs +519 -0
- package/agent/internal-harness-runtime.mjs +1219 -0
- package/agent/lineage-graph.mjs +141 -0
- package/agent/primary-agent.mjs +602 -706
- package/agent/provider-auth-manager.mjs +830 -0
- package/agent/provider-auth-state.mjs +440 -0
- package/agent/provider-capabilities.mjs +116 -0
- package/agent/provider-kernel.mjs +596 -0
- package/agent/provider-message-transform.mjs +583 -0
- package/agent/provider-model-catalog.mjs +163 -0
- package/agent/provider-registry.mjs +657 -0
- package/agent/provider-runtime-discovery.mjs +147 -0
- package/agent/provider-session.mjs +767 -0
- package/agent/providers/_shared.mjs +397 -0
- package/agent/providers/anthropic-messages.mjs +64 -0
- package/agent/providers/azure-openai-responses.mjs +69 -0
- package/agent/providers/cerebras.mjs +66 -0
- package/agent/providers/claude-subscription-shim.mjs +68 -0
- package/agent/providers/copilot-oauth.mjs +66 -0
- package/agent/providers/deepinfra.mjs +66 -0
- package/agent/providers/fireworks.mjs +66 -0
- package/agent/providers/gemini-generate-content.mjs +66 -0
- package/agent/providers/groq.mjs +66 -0
- package/agent/providers/index.mjs +208 -0
- package/agent/providers/nebius.mjs +66 -0
- package/agent/providers/ollama.mjs +66 -0
- package/agent/providers/openai-codex-subscription.mjs +75 -0
- package/agent/providers/openai-compatible.mjs +65 -0
- package/agent/providers/openai-responses.mjs +67 -0
- package/agent/providers/openrouter.mjs +66 -0
- package/agent/providers/perplexity.mjs +66 -0
- package/agent/providers/provider-contract.mjs +138 -0
- package/agent/providers/provider-errors.mjs +63 -0
- package/agent/providers/provider-model-pricing.mjs +246 -0
- package/agent/providers/provider-stream-normalizer.mjs +7 -0
- package/agent/providers/provider-usage-normalizer.mjs +48 -0
- package/agent/providers/sambanova.mjs +66 -0
- package/agent/providers/together.mjs +66 -0
- package/agent/providers/xai.mjs +66 -0
- package/agent/query-engine.mjs +260 -0
- package/agent/retry-queue.mjs +1 -0
- package/agent/review-agent.mjs +1 -1
- package/agent/session-contract.mjs +127 -0
- package/agent/session-manager.mjs +1859 -0
- package/agent/session-replay.mjs +617 -0
- package/agent/session-snapshot-store.mjs +379 -0
- package/agent/skills/agent-coordination.md +6 -0
- package/agent/skills/background-task-execution.md +6 -0
- package/agent/skills/bosun-agent-api.md +6 -0
- package/agent/skills/code-quality-anti-patterns.md +7 -0
- package/agent/skills/commit-conventions.md +6 -0
- package/agent/skills/custom-tool-creation.md +6 -0
- package/agent/skills/error-recovery.md +6 -0
- package/agent/skills/pr-workflow.md +6 -0
- package/agent/skills/tdd-pattern.md +6 -0
- package/agent/subagent-contract.mjs +104 -0
- package/agent/subagent-control.mjs +633 -0
- package/agent/subagent-pool.mjs +260 -0
- package/agent/thread-contract.mjs +88 -0
- package/agent/thread-registry.mjs +552 -0
- package/agent/tool-approval-manager.mjs +259 -0
- package/agent/tool-builtin-catalog.mjs +855 -0
- package/agent/tool-contract.mjs +101 -0
- package/agent/tool-event-contract.mjs +99 -0
- package/agent/tool-execution-ledger.mjs +32 -0
- package/agent/tool-network-policy.mjs +86 -0
- package/agent/tool-orchestrator.mjs +382 -0
- package/agent/tool-output-truncation.mjs +70 -0
- package/agent/tool-registry.mjs +200 -0
- package/agent/tool-retry-policy.mjs +57 -0
- package/agent/tool-runtime-context.mjs +220 -0
- package/bench/harness-load-bench.mjs +281 -0
- package/bench/harness-parity-bench.mjs +214 -0
- package/bench/swebench/bosun-swebench.mjs +21 -6
- package/bosun-tui.mjs +59 -13
- package/bosun.config.example.json +55 -2
- package/bosun.schema.json +598 -5
- package/cli.mjs +656 -160
- package/config/config-doctor.mjs +80 -26
- package/config/config-editor.mjs +417 -0
- package/config/config.mjs +489 -144
- package/config/repo-config.mjs +125 -49
- package/config/repo-root.mjs +33 -1
- package/desktop/main.mjs +554 -115
- package/desktop/package.json +1 -1
- package/git/diff-stats.mjs +7 -5
- package/git/git-editor-fix.mjs +2 -42
- package/github/github-app-auth.mjs +6 -0
- package/github/github-oauth-portal.mjs +20 -0
- package/infra/anomaly-detector.mjs +122 -22
- package/infra/approval-projection-store.mjs +75 -0
- package/infra/config-reload-bus.mjs +33 -0
- package/infra/container-runner.mjs +37 -4
- package/infra/error-detector.mjs +110 -35
- package/infra/event-schema.mjs +353 -0
- package/infra/guardrails.mjs +383 -0
- package/infra/heartbeat-monitor.mjs +432 -0
- package/infra/library-manager.mjs +367 -19
- package/infra/live-event-projector.mjs +197 -0
- package/infra/maintenance.mjs +202 -51
- package/infra/monitor.mjs +1749 -2027
- package/infra/preflight.mjs +107 -6
- package/infra/presence.mjs +33 -9
- package/infra/projection-contract.mjs +27 -0
- package/infra/provider-usage-ledger.mjs +73 -0
- package/infra/replay-reader.mjs +140 -0
- package/infra/runtime-accumulator.mjs +303 -8
- package/infra/runtime-metrics.mjs +156 -0
- package/infra/session-projection-store.mjs +169 -0
- package/infra/session-telemetry-runtime.mjs +580 -0
- package/infra/session-telemetry.mjs +338 -0
- package/infra/session-tracker.mjs +1613 -228
- package/infra/startup-service.mjs +0 -2
- package/infra/storage-janitor.mjs +1046 -0
- package/infra/subagent-projection-store.mjs +89 -0
- package/infra/test-runtime.mjs +53 -20
- package/infra/trace-export.mjs +103 -0
- package/infra/tui-bridge.mjs +607 -5
- package/infra/update-check.mjs +7 -8
- package/infra/windows-hidden-child-processes.mjs +99 -0
- package/infra/worktree-recovery-state.mjs +20 -7
- package/kanban/kanban-adapter.mjs +702 -310
- package/kanban/repo-mirror-projection-store.mjs +871 -0
- package/lib/agent-configuration-guide.mjs +280 -0
- package/lib/hot-path-runtime.mjs +1061 -0
- package/lib/integrations-registry.mjs +294 -0
- package/lib/log-tail.mjs +101 -0
- package/lib/logger.mjs +21 -25
- package/lib/mojibake-repair.mjs +40 -0
- package/lib/repo-map.mjs +137 -24
- package/lib/request-json-api.mjs +59 -0
- package/lib/safe-box.mjs +56 -0
- package/lib/session-insights.mjs +3 -1
- package/lib/skill-markdown-safety.mjs +394 -0
- package/lib/state-ledger-sqlite.mjs +4462 -0
- package/lib/vault-keychain.mjs +259 -0
- package/lib/vault.mjs +374 -0
- package/lib/workflow-flowchart-utils.mjs +326 -0
- package/monitor-tail-sanitizer.mjs +1 -2
- package/native/bosun-telemetry/Cargo.toml +9 -0
- package/native/bosun-telemetry/src/export.rs +151 -0
- package/native/bosun-telemetry/src/main.rs +76 -0
- package/native/bosun-telemetry/src/metrics.rs +114 -0
- package/native/bosun-telemetry/src/session_telemetry.rs +178 -0
- package/native/bosun-unified-exec/Cargo.lock +107 -0
- package/native/bosun-unified-exec/Cargo.toml +8 -0
- package/native/bosun-unified-exec/src/async_watcher.rs +145 -0
- package/native/bosun-unified-exec/src/head_tail_buffer.rs +241 -0
- package/native/bosun-unified-exec/src/main.rs +86 -0
- package/native/bosun-unified-exec/src/process_manager.rs +308 -0
- package/native/bosun-unified-exec/src/tool_orchestrator.rs +187 -0
- package/package.json +230 -59
- package/postinstall.mjs +182 -13
- package/server/bosun-mcp-server.mjs +348 -10
- package/server/routes/harness-agent-bridge.mjs +128 -0
- package/server/routes/harness-approvals.mjs +290 -0
- package/server/routes/harness-events.mjs +469 -0
- package/server/routes/harness-providers.mjs +385 -0
- package/server/routes/harness-sessions.mjs +2230 -0
- package/server/routes/harness-subagents.mjs +138 -0
- package/server/routes/harness-surface-payload.mjs +74 -0
- package/server/setup-web-server.mjs +468 -39
- package/server/ui-server.mjs +13041 -4418
- package/setup.mjs +206 -298
- package/shared-workspaces.json +1 -1
- package/shell/anthropic-native-adapter.mjs +1218 -0
- package/shell/auth-resolver.mjs +247 -0
- package/shell/claude-shell.mjs +85 -2
- package/shell/codex-config-file.mjs +9 -0
- package/shell/codex-config.mjs +192 -249
- package/shell/codex-model-profiles.mjs +76 -12
- package/shell/codex-sdk-import.mjs +7 -0
- package/shell/codex-shell.mjs +708 -170
- package/shell/context-compaction.mjs +898 -0
- package/shell/copilot-shell.mjs +359 -109
- package/shell/gemini-native-adapter.mjs +411 -0
- package/shell/gemini-shell.mjs +121 -13
- package/shell/mcp-client.mjs +401 -0
- package/shell/mcp-registry.mjs +72 -0
- package/shell/message-pruner.mjs +248 -0
- package/shell/openai-native-adapter.mjs +1975 -0
- package/shell/opencode-providers.mjs +16 -531
- package/shell/opencode-shell.mjs +180 -9
- package/shell/provider-transform.mjs +386 -0
- package/shell/pwsh-runtime.mjs +9 -2
- package/shell/retry-fetch.mjs +244 -0
- package/shell/session-resume.mjs +97 -0
- package/shell/session-store.mjs +215 -0
- package/shell/shell-adapter-registry.mjs +346 -0
- package/shell/shell-session-compat.mjs +442 -0
- package/shell/smooth-stream.mjs +233 -0
- package/shell/stop-condition.mjs +238 -0
- package/shell/tool-call-repair.mjs +345 -0
- package/shell/tool-executor.mjs +571 -0
- package/task/pipeline.mjs +3 -1
- package/task/task-assessment.mjs +312 -6
- package/task/task-claims.mjs +312 -48
- package/task/task-cli.mjs +90 -14
- package/task/task-complexity.mjs +6 -6
- package/task/task-context.mjs +37 -0
- package/task/task-debt-ledger.mjs +110 -0
- package/task/task-executor.mjs +1104 -119
- package/task/task-replanner.mjs +553 -0
- package/task/task-simulate-cli.mjs +1481 -0
- package/task/task-store.mjs +996 -68
- package/telegram/executor-health-region-cache.mjs +75 -0
- package/telegram/get-telegram-chat-id.mjs +0 -0
- package/telegram/harness-api-client.mjs +124 -0
- package/telegram/sticky-menu-state.mjs +384 -0
- package/telegram/telegram-bot.mjs +548 -865
- package/telegram/telegram-sentinel.mjs +25 -14
- package/telegram/telegram-surface-runtime.mjs +53 -0
- package/tools/generate-demo-defaults.mjs +23 -4
- package/tools/harness-hotpath-bench.mjs +246 -0
- package/tools/import-check.mjs +279 -234
- package/tools/install-git-hooks.mjs +96 -20
- package/tools/native-rust.mjs +124 -0
- package/tools/packed-cli-smoke.mjs +147 -0
- package/tools/prepublish-check.mjs +53 -1
- package/tools/run-workflow-guaranteed-suite.mjs +56 -0
- package/tools/site-serve.mjs +112 -0
- package/tools/sync-demo-ui.mjs +188 -0
- package/tools/syntax-check.mjs +32 -28
- package/tools/test-kanban-enhancement.mjs +7 -7
- package/tools/test-shared-state-integration.mjs +5 -19
- package/tools/vite-windows-realpath-shim.mjs +274 -0
- package/tools/vitest-esbuild-shim.mjs +45 -0
- package/tools/vitest-full-suite.mjs +310 -0
- package/tools/vitest-runner.mjs +505 -11
- package/tools/workflow-orphan-worktree-recovery.mjs +24 -7
- package/tui/CommandPalette.js +87 -0
- package/tui/app.mjs +463 -37
- package/tui/components/status-header.mjs +43 -1
- package/tui/lib/command-palette.mjs +191 -0
- package/tui/lib/connection-target.mjs +577 -0
- package/tui/lib/header-config.mjs +0 -2
- package/tui/lib/navigation.mjs +8 -3
- package/tui/lib/ws-bridge.mjs +141 -51
- package/tui/screens/agents-screen-helpers.mjs +87 -3
- package/tui/screens/agents.mjs +1074 -202
- package/tui/screens/connection-setup.mjs +363 -0
- package/tui/screens/harness-approvals.mjs +7 -0
- package/tui/screens/harness-sessions.mjs +109 -0
- package/tui/screens/harness-subagents.mjs +18 -0
- package/tui/screens/harness-telemetry.mjs +67 -0
- package/tui/screens/logs.mjs +325 -0
- package/tui/screens/settings-screen-helpers.mjs +75 -0
- package/tui/screens/settings.mjs +397 -0
- package/tui/screens/status.mjs +130 -5
- package/tui/screens/telemetry-screen-helpers.mjs +158 -0
- package/tui/screens/telemetry.mjs +246 -0
- package/tui/screens/workflows.mjs +984 -0
- package/ui/app.js +746 -189
- package/ui/app.monolith.js +2 -3
- package/ui/assets/toastui-editor-all.min.js +24 -0
- package/ui/components/agent-selector.js +706 -49
- package/ui/components/charts.js +16 -12
- package/ui/components/chat-view.js +536 -35
- package/ui/components/commit-graph.js +648 -0
- package/ui/components/context-menu.js +89 -0
- package/ui/components/diff-viewer.js +169 -53
- package/ui/components/forms.js +13 -2
- package/ui/components/kanban-board.js +541 -92
- package/ui/components/session-list.js +303 -66
- package/ui/components/shared.js +9 -1
- package/ui/components/task-markdown.js +272 -0
- package/ui/components/workspace-executor-settings.js +142 -0
- package/ui/components/workspace-switcher.js +35 -79
- package/ui/demo-defaults.js +17278 -7297
- package/ui/demo.html +7058 -5338
- package/ui/index.html +189 -112
- package/ui/modules/agent-events.js +309 -36
- package/ui/modules/api.js +236 -13
- package/ui/modules/chat-turn-groups.js +101 -0
- package/ui/modules/harness-client.js +56 -0
- package/ui/modules/icon-utils.js +9 -1
- package/ui/modules/icons.js +26 -2
- package/ui/modules/repo-area-contention.js +97 -0
- package/ui/modules/router.js +2 -0
- package/ui/modules/session-api.js +158 -14
- package/ui/modules/session-insights-worker.js +28 -0
- package/ui/modules/session-insights.js +173 -4
- package/ui/modules/session-surface.js +221 -0
- package/ui/modules/settings-schema.js +148 -27
- package/ui/modules/state.js +122 -13
- package/ui/modules/streaming.js +196 -60
- package/ui/modules/structured-values.js +47 -0
- package/ui/modules/task-hierarchy.js +374 -0
- package/ui/modules/worktree-recovery.js +10 -1
- package/ui/setup.html +3327 -2538
- package/ui/styles/components.css +983 -99
- package/ui/styles/kanban.css +229 -0
- package/ui/styles/layout.css +578 -106
- package/ui/styles/toastui-editor-dark.css +1 -0
- package/ui/styles/toastui-editor-viewer.css +6 -0
- package/ui/styles/toastui-editor.css +6 -0
- package/ui/styles/variables.css +14 -2
- package/ui/styles/workspace-switcher.css +22 -0
- package/ui/styles.css +20 -5
- package/ui/tabs/agents.js +1222 -86
- package/ui/tabs/chat.js +588 -146
- package/ui/tabs/context-compression-lab.js +962 -0
- package/ui/tabs/control.js +347 -61
- package/ui/tabs/dashboard.js +372 -105
- package/ui/tabs/guardrails.js +1140 -0
- package/ui/tabs/infra.js +91 -12
- package/ui/tabs/integrations.js +388 -0
- package/ui/tabs/library.js +161 -21
- package/ui/tabs/logs.js +410 -52
- package/ui/tabs/manual-flows.js +268 -52
- package/ui/tabs/settings.js +2117 -105
- package/ui/tabs/tasks.js +2851 -303
- package/ui/tabs/telemetry.js +246 -8
- package/ui/tabs/workflow-canvas-utils.mjs +172 -15
- package/ui/tabs/workflows.js +2632 -348
- package/ui/tui/App.js +127 -119
- package/ui/tui/HelpScreen.js +201 -0
- package/ui/tui/SettingsScreen.js +388 -0
- package/ui/tui/TasksScreen.js +30 -7
- package/ui/tui/TelemetryScreen.js +155 -0
- package/ui/tui/WorkflowsScreen.js +350 -0
- package/ui/tui/config-events.js +13 -0
- package/ui/tui/constants.js +1 -1
- package/ui/tui/logs-screen-helpers.js +292 -0
- package/ui/tui/tasks-screen-helpers.js +52 -0
- package/ui/tui/telemetry-helpers.js +158 -0
- package/ui/tui/useWorkflows.js +126 -6
- package/ui/tui/workflows-screen-helpers.js +220 -0
- package/ui/vendor/preact-jsx-runtime.js +5 -0
- package/utils.mjs +2 -2
- package/voice/vision-session-state.mjs +257 -0
- package/voice/voice-action-dispatcher.mjs +57 -12
- package/voice/voice-agents-sdk.mjs +1 -1
- package/voice/voice-auth-manager.mjs +102 -123
- package/voice/voice-tool-definitions.mjs +7 -7
- package/voice/voice-tools.mjs +837 -101
- package/workflow/action-approval.mjs +415 -0
- package/workflow/approval-queue.mjs +1254 -0
- package/workflow/credential-store.mjs +553 -0
- package/workflow/cron-scheduler.mjs +512 -0
- package/workflow/declarative-workflows.mjs +21 -2
- package/workflow/delegation-runtime.mjs +557 -0
- package/workflow/execution-ledger.mjs +1317 -33
- package/workflow/harness-approval-node.mjs +237 -0
- package/workflow/harness-output-contract.mjs +86 -0
- package/workflow/harness-session-node.mjs +160 -0
- package/workflow/harness-subagent-node.mjs +483 -0
- package/workflow/harness-tool-node.mjs +176 -0
- package/workflow/heavy-runner-pool.mjs +546 -0
- package/workflow/manual-flows.mjs +969 -28
- package/workflow/mcp-discovery-proxy.mjs +363 -143
- package/workflow/mcp-registry.mjs +507 -65
- package/workflow/meeting-workflow-service.mjs +24 -12
- package/workflow/pipeline-workflows.mjs +44 -2
- package/workflow/pipeline.mjs +72 -28
- package/workflow/project-detection.mjs +31 -6
- package/workflow/research-evidence-sidecar.mjs +1246 -0
- package/workflow/run-evaluator.mjs +2155 -0
- package/workflow/workflow-cli.mjs +229 -2
- package/workflow/workflow-contract.mjs +130 -2
- package/workflow/workflow-engine.mjs +5636 -331
- package/workflow/workflow-migration.mjs +0 -1
- package/workflow/workflow-nodes/actions.mjs +15526 -0
- package/workflow/workflow-nodes/agent.mjs +1863 -0
- package/workflow/workflow-nodes/conditions.mjs +307 -0
- package/workflow/workflow-nodes/definitions.mjs +210 -29
- package/workflow/workflow-nodes/flow.mjs +749 -0
- package/workflow/workflow-nodes/loop.mjs +449 -0
- package/workflow/workflow-nodes/meetings.mjs +456 -0
- package/workflow/workflow-nodes/notifications.mjs +169 -0
- package/workflow/workflow-nodes/transforms.mjs +83 -30
- package/workflow/workflow-nodes/triggers.mjs +1405 -0
- package/workflow/workflow-nodes/validation.mjs +722 -0
- package/workflow/workflow-nodes.mjs +43 -14965
- package/workflow/workflow-serializer.mjs +294 -0
- package/workflow/workflow-templates.mjs +304 -9
- package/workflow-templates/_helpers.mjs +1 -3
- package/workflow-templates/agents.mjs +235 -27
- package/workflow-templates/bosun-native.mjs +3 -1
- package/workflow-templates/code-quality.mjs +1 -1
- package/workflow-templates/continuation-loop.mjs +21 -5
- package/workflow-templates/coverage.mjs +6 -2
- package/workflow-templates/github.mjs +2565 -177
- package/workflow-templates/reliability.mjs +463 -35
- package/workflow-templates/research-evidence.mjs +389 -0
- package/workflow-templates/security.mjs +75 -62
- package/workflow-templates/sub-workflows.mjs +11 -3
- package/workflow-templates/task-batch.mjs +102 -20
- package/workflow-templates/task-lifecycle.mjs +333 -298
- package/workspace/command-diagnostics.mjs +111 -0
- package/workspace/context-cache.mjs +1308 -124
- package/workspace/context-indexer.mjs +915 -9
- package/workspace/context-injector.mjs +144 -0
- package/workspace/execution-journal.mjs +255 -0
- package/workspace/scope-locks.mjs +481 -0
- package/workspace/shared-knowledge.mjs +496 -91
- package/workspace/shared-state-manager.mjs +344 -1
- package/workspace/shared-workspace-cli.mjs +0 -0
- package/workspace/shared-workspace-registry.mjs +2 -2
- package/workspace/skillbook-store.mjs +681 -0
- package/workspace/workspace-manager.mjs +14 -8
- package/workspace/workspace-monitor.mjs +4 -4
- package/workspace/worktree-manager.mjs +146 -55
- package/workspace/worktree-setup.mjs +822 -0
- package/agent/rotate-agent-logs.sh +0 -134
- package/git/sdk-conflict-resolver.mjs +0 -971
- package/infra/sync-engine.mjs +0 -1160
- package/kanban/ve-kanban.mjs +0 -664
- package/kanban/ve-kanban.ps1 +0 -1365
- package/kanban/ve-kanban.sh +0 -18
- package/kanban/ve-orchestrator.mjs +0 -340
- package/kanban/ve-orchestrator.ps1 +0 -6762
- package/kanban/ve-orchestrator.sh +0 -18
- package/kanban/vibe-kanban-wrapper.mjs +0 -41
- package/kanban/vk-error-resolver.mjs +0 -474
- package/kanban/vk-log-stream.mjs +0 -932
- package/task/task-archiver.mjs +0 -813
- package/tools/publish.mjs +0 -239
- package/ui/components/chat-view.js.bak +0 -1
- package/ui/tabs/infra.js.bak +0 -1
|
@@ -10,6 +10,7 @@ const _visionSessionState = new Map();
|
|
|
10
10
|
const MAX_TRACE_TURNS = 12;
|
|
11
11
|
const MAX_TURN_EVENTS = 40;
|
|
12
12
|
const MAX_TURN_FINGERPRINTS = 32;
|
|
13
|
+
const MAX_MULTIMODAL_FALLBACK_HISTORY = 8;
|
|
13
14
|
const SECRET_KEY_PATTERN = /(token|key|secret|password|authorization|credential|cookie|client_secret|access_token)/i;
|
|
14
15
|
|
|
15
16
|
function getSessionKey(sessionId) {
|
|
@@ -20,6 +21,47 @@ function nowIso() {
|
|
|
20
21
|
return new Date().toISOString();
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
function cloneValue(value) {
|
|
25
|
+
if (value == null) return value;
|
|
26
|
+
return JSON.parse(JSON.stringify(value));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function uniqueStrings(values) {
|
|
30
|
+
return [...new Set(
|
|
31
|
+
(Array.isArray(values) ? values : [values])
|
|
32
|
+
.map((entry) => String(entry ?? "").trim())
|
|
33
|
+
.filter(Boolean),
|
|
34
|
+
)];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildProfileSlug(value, fallback = "session") {
|
|
38
|
+
const src = String(value || "").trim().toLowerCase();
|
|
39
|
+
let result = "";
|
|
40
|
+
for (const ch of src) {
|
|
41
|
+
if ((ch >= "a" && ch <= "z") || (ch >= "0" && ch <= "9") || ch === "." || ch === "_") {
|
|
42
|
+
result += ch;
|
|
43
|
+
} else if (result.length > 0 && result[result.length - 1] !== "-") {
|
|
44
|
+
result += "-";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
while (result.endsWith("-")) result = result.slice(0, -1);
|
|
48
|
+
while (result.startsWith("-")) result = result.slice(1);
|
|
49
|
+
return result || fallback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeText(value) {
|
|
53
|
+
return String(value ?? "").trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeBoolean(value, fallback = false) {
|
|
57
|
+
if (value === true || value === false) return value;
|
|
58
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
59
|
+
if (!normalized) return fallback;
|
|
60
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
61
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
62
|
+
return fallback;
|
|
63
|
+
}
|
|
64
|
+
|
|
23
65
|
function redactSecretLikeText(value) {
|
|
24
66
|
const raw = String(value || "").trim();
|
|
25
67
|
if (!raw) return raw;
|
|
@@ -128,6 +170,87 @@ function annotateTurnFromEvent(turn, event) {
|
|
|
128
170
|
}
|
|
129
171
|
}
|
|
130
172
|
|
|
173
|
+
function normalizeBrowserWorkerIsolation(input = {}, fallback = {}) {
|
|
174
|
+
const source = {
|
|
175
|
+
...(fallback && typeof fallback === "object" ? fallback : {}),
|
|
176
|
+
...(input && typeof input === "object" ? input : {}),
|
|
177
|
+
};
|
|
178
|
+
const sessionId = getSessionKey(source.sessionId || source.ownerSessionId || fallback?.sessionId);
|
|
179
|
+
const parentSessionId = getSessionKey(source.parentSessionId || fallback?.parentSessionId) || null;
|
|
180
|
+
const rootSessionId = getSessionKey(source.rootSessionId || fallback?.rootSessionId) || parentSessionId || sessionId || null;
|
|
181
|
+
const profileScope = normalizeText(source.profileScope || fallback?.profileScope || "isolated-subagent") || "isolated-subagent";
|
|
182
|
+
const profileId = normalizeText(
|
|
183
|
+
source.profileId
|
|
184
|
+
|| fallback?.profileId
|
|
185
|
+
|| `${buildProfileSlug(rootSessionId || "root", "root")}--${buildProfileSlug(sessionId || "session")}`,
|
|
186
|
+
) || `${buildProfileSlug(rootSessionId || "root", "root")}--${buildProfileSlug(sessionId || "session")}`;
|
|
187
|
+
const multimodalFallback = normalizeMultimodalFallback(source.multimodalFallback, fallback?.multimodalFallback);
|
|
188
|
+
return {
|
|
189
|
+
workerId: normalizeText(source.workerId || fallback?.workerId || `browser-worker:${profileId}`) || `browser-worker:${profileId}`,
|
|
190
|
+
sessionId: sessionId || null,
|
|
191
|
+
ownerSessionId: sessionId || null,
|
|
192
|
+
parentSessionId,
|
|
193
|
+
rootSessionId,
|
|
194
|
+
profileId,
|
|
195
|
+
profileDir: normalizeText(source.profileDir || fallback?.profileDir || `.bosun/.cache/browser-workers/${profileId}`) || `.bosun/.cache/browser-workers/${profileId}`,
|
|
196
|
+
profileScope,
|
|
197
|
+
status: normalizeText(source.status || fallback?.status || "attached") || "attached",
|
|
198
|
+
requestedCapabilities: uniqueStrings(source.requestedCapabilities || fallback?.requestedCapabilities),
|
|
199
|
+
toolHints: uniqueStrings(source.toolHints || fallback?.toolHints),
|
|
200
|
+
multimodalFallback,
|
|
201
|
+
metadata: sanitizeTraceValue(source.metadata || fallback?.metadata || {}),
|
|
202
|
+
assignedAt: normalizeText(source.assignedAt || fallback?.assignedAt || "") || nowIso(),
|
|
203
|
+
updatedAt: nowIso(),
|
|
204
|
+
releasedAt: normalizeText(source.releasedAt || fallback?.releasedAt || "") || null,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function normalizeMultimodalFallback(input = {}, fallback = {}) {
|
|
209
|
+
const source = {
|
|
210
|
+
...(fallback && typeof fallback === "object" ? fallback : {}),
|
|
211
|
+
...(input && typeof input === "object" ? input : {}),
|
|
212
|
+
};
|
|
213
|
+
const history = Array.isArray(source.history)
|
|
214
|
+
? source.history
|
|
215
|
+
.filter((entry) => entry && typeof entry === "object")
|
|
216
|
+
.map((entry) => ({
|
|
217
|
+
at: normalizeText(entry.at || entry.timestamp || "") || nowIso(),
|
|
218
|
+
reason: normalizeText(entry.reason || "") || null,
|
|
219
|
+
summary: normalizeText(entry.summary || entry.description || "") || null,
|
|
220
|
+
source: normalizeText(entry.source || "") || null,
|
|
221
|
+
}))
|
|
222
|
+
.slice(-MAX_MULTIMODAL_FALLBACK_HISTORY)
|
|
223
|
+
: [];
|
|
224
|
+
return {
|
|
225
|
+
enabled: normalizeBoolean(source.enabled, true),
|
|
226
|
+
mode: normalizeText(source.mode || "vision_summary_to_text") || "vision_summary_to_text",
|
|
227
|
+
available: normalizeBoolean(source.available, history.length > 0 || Boolean(normalizeText(source.summary || source.description || source.lastDescription))),
|
|
228
|
+
reason: normalizeText(source.reason || "") || null,
|
|
229
|
+
summary: normalizeText(source.summary || source.description || source.lastDescription || "") || null,
|
|
230
|
+
source: normalizeText(source.source || "") || null,
|
|
231
|
+
frameHash: normalizeText(source.frameHash || "") || null,
|
|
232
|
+
width: Number.isFinite(Number(source.width)) ? Number(source.width) : null,
|
|
233
|
+
height: Number.isFinite(Number(source.height)) ? Number(source.height) : null,
|
|
234
|
+
updatedAt: normalizeText(source.updatedAt || source.lastUpdatedAt || "") || null,
|
|
235
|
+
history,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function buildMultimodalFallbackDescription(state, fallback = {}) {
|
|
240
|
+
const summary = normalizeText(fallback.summary || state?.lastSummary || "");
|
|
241
|
+
const source = normalizeText(fallback.source || state?.lastFrameSource || "") || "screen";
|
|
242
|
+
const width = Number.isFinite(Number(fallback.width)) ? Number(fallback.width) : state?.lastFrameWidth;
|
|
243
|
+
const height = Number.isFinite(Number(fallback.height)) ? Number(fallback.height) : state?.lastFrameHeight;
|
|
244
|
+
const dimension = width && height ? ` (${width}x${height})` : "";
|
|
245
|
+
if (summary) {
|
|
246
|
+
return `[${source}${dimension}] ${summary}`;
|
|
247
|
+
}
|
|
248
|
+
if (state?.lastFrameHash) {
|
|
249
|
+
return `Visual context from ${source}${dimension} is available for text fallback.`;
|
|
250
|
+
}
|
|
251
|
+
return "";
|
|
252
|
+
}
|
|
253
|
+
|
|
131
254
|
export function getVisionSessionState(sessionId) {
|
|
132
255
|
const key = getSessionKey(sessionId);
|
|
133
256
|
if (!key) return null;
|
|
@@ -143,6 +266,20 @@ export function getVisionSessionState(sessionId) {
|
|
|
143
266
|
lastFrameSource: "screen",
|
|
144
267
|
lastFrameWidth: null,
|
|
145
268
|
lastFrameHeight: null,
|
|
269
|
+
browserWorker: null,
|
|
270
|
+
multimodalFallback: {
|
|
271
|
+
enabled: true,
|
|
272
|
+
mode: "vision_summary_to_text",
|
|
273
|
+
available: false,
|
|
274
|
+
reason: null,
|
|
275
|
+
summary: null,
|
|
276
|
+
source: null,
|
|
277
|
+
frameHash: null,
|
|
278
|
+
width: null,
|
|
279
|
+
height: null,
|
|
280
|
+
updatedAt: null,
|
|
281
|
+
history: [],
|
|
282
|
+
},
|
|
146
283
|
voiceTurnTrace: null,
|
|
147
284
|
});
|
|
148
285
|
}
|
|
@@ -155,6 +292,126 @@ export function clearVisionSessionState(sessionId) {
|
|
|
155
292
|
return _visionSessionState.delete(key);
|
|
156
293
|
}
|
|
157
294
|
|
|
295
|
+
export function ensureBrowserWorkerIsolation(sessionId, options = {}) {
|
|
296
|
+
const state = getVisionSessionState(sessionId);
|
|
297
|
+
if (!state) return null;
|
|
298
|
+
state.browserWorker = normalizeBrowserWorkerIsolation({
|
|
299
|
+
sessionId,
|
|
300
|
+
...options,
|
|
301
|
+
}, state.browserWorker || {});
|
|
302
|
+
if (!state.multimodalFallback || typeof state.multimodalFallback !== "object") {
|
|
303
|
+
state.multimodalFallback = normalizeMultimodalFallback();
|
|
304
|
+
}
|
|
305
|
+
if (options.multimodalFallback || state.browserWorker?.multimodalFallback) {
|
|
306
|
+
state.multimodalFallback = normalizeMultimodalFallback(
|
|
307
|
+
state.browserWorker?.multimodalFallback || options.multimodalFallback,
|
|
308
|
+
state.multimodalFallback,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return cloneValue(state.browserWorker);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function getBrowserWorkerIsolation(sessionId) {
|
|
315
|
+
const state = getVisionSessionState(sessionId);
|
|
316
|
+
if (!state?.browserWorker) return null;
|
|
317
|
+
return cloneValue(state.browserWorker);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function listBrowserWorkerIsolations(options = {}) {
|
|
321
|
+
const rootSessionId = getSessionKey(options.rootSessionId);
|
|
322
|
+
const parentSessionId = getSessionKey(options.parentSessionId);
|
|
323
|
+
return [..._visionSessionState.values()]
|
|
324
|
+
.map((state) => state?.browserWorker || null)
|
|
325
|
+
.filter(Boolean)
|
|
326
|
+
.filter((worker) => {
|
|
327
|
+
if (rootSessionId && getSessionKey(worker.rootSessionId) !== rootSessionId) return false;
|
|
328
|
+
if (parentSessionId && getSessionKey(worker.parentSessionId) !== parentSessionId) return false;
|
|
329
|
+
return true;
|
|
330
|
+
})
|
|
331
|
+
.map((worker) => cloneValue(worker));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function releaseBrowserWorkerIsolation(sessionId, reason = "released") {
|
|
335
|
+
const state = getVisionSessionState(sessionId);
|
|
336
|
+
if (!state?.browserWorker) return null;
|
|
337
|
+
const released = normalizeBrowserWorkerIsolation({
|
|
338
|
+
...state.browserWorker,
|
|
339
|
+
status: "released",
|
|
340
|
+
releasedAt: nowIso(),
|
|
341
|
+
metadata: {
|
|
342
|
+
...(state.browserWorker.metadata && typeof state.browserWorker.metadata === "object" ? state.browserWorker.metadata : {}),
|
|
343
|
+
releaseReason: normalizeText(reason) || "released",
|
|
344
|
+
},
|
|
345
|
+
}, state.browserWorker);
|
|
346
|
+
state.browserWorker = null;
|
|
347
|
+
return cloneValue(released);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function recordMultimodalFallback(sessionId, input = {}) {
|
|
351
|
+
const state = getVisionSessionState(sessionId);
|
|
352
|
+
if (!state) return null;
|
|
353
|
+
const description = buildMultimodalFallbackDescription(state, input);
|
|
354
|
+
const nextHistoryEntry = {
|
|
355
|
+
at: nowIso(),
|
|
356
|
+
reason: normalizeText(input.reason || "") || null,
|
|
357
|
+
summary: normalizeText(input.summary || input.description || description) || null,
|
|
358
|
+
source: normalizeText(input.source || state.lastFrameSource || "") || null,
|
|
359
|
+
};
|
|
360
|
+
const fallback = normalizeMultimodalFallback({
|
|
361
|
+
...state.multimodalFallback,
|
|
362
|
+
...input,
|
|
363
|
+
available: Boolean(description),
|
|
364
|
+
summary: description || normalizeText(input.summary || input.description || ""),
|
|
365
|
+
source: normalizeText(input.source || state.lastFrameSource || ""),
|
|
366
|
+
frameHash: normalizeText(input.frameHash || state.lastFrameHash || ""),
|
|
367
|
+
width: Number.isFinite(Number(input.width)) ? Number(input.width) : state.lastFrameWidth,
|
|
368
|
+
height: Number.isFinite(Number(input.height)) ? Number(input.height) : state.lastFrameHeight,
|
|
369
|
+
updatedAt: nowIso(),
|
|
370
|
+
history: [
|
|
371
|
+
...(Array.isArray(state.multimodalFallback?.history) ? state.multimodalFallback.history : []),
|
|
372
|
+
nextHistoryEntry,
|
|
373
|
+
].slice(-MAX_MULTIMODAL_FALLBACK_HISTORY),
|
|
374
|
+
}, state.multimodalFallback);
|
|
375
|
+
state.multimodalFallback = fallback;
|
|
376
|
+
if (state.browserWorker) {
|
|
377
|
+
state.browserWorker = normalizeBrowserWorkerIsolation({
|
|
378
|
+
...state.browserWorker,
|
|
379
|
+
multimodalFallback: fallback,
|
|
380
|
+
}, state.browserWorker);
|
|
381
|
+
}
|
|
382
|
+
return cloneValue(fallback);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function describeMultimodalFallback(sessionId, options = {}) {
|
|
386
|
+
const state = getVisionSessionState(sessionId);
|
|
387
|
+
if (!state) {
|
|
388
|
+
return {
|
|
389
|
+
sessionId: getSessionKey(sessionId),
|
|
390
|
+
available: false,
|
|
391
|
+
description: "",
|
|
392
|
+
browserWorker: null,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const fallback = normalizeMultimodalFallback(options, state.multimodalFallback);
|
|
396
|
+
const description = normalizeText(
|
|
397
|
+
options.description
|
|
398
|
+
|| options.summary
|
|
399
|
+
|| fallback.summary
|
|
400
|
+
|| buildMultimodalFallbackDescription(state, fallback),
|
|
401
|
+
);
|
|
402
|
+
return {
|
|
403
|
+
sessionId: getSessionKey(sessionId),
|
|
404
|
+
available: Boolean(description),
|
|
405
|
+
description,
|
|
406
|
+
browserWorker: cloneValue(state.browserWorker),
|
|
407
|
+
fallback: cloneValue({
|
|
408
|
+
...fallback,
|
|
409
|
+
summary: description || fallback.summary,
|
|
410
|
+
available: Boolean(description),
|
|
411
|
+
}),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
158
415
|
export function beginVoiceTurnTrace(sessionId, metadata = {}) {
|
|
159
416
|
const state = getVisionSessionState(sessionId);
|
|
160
417
|
if (!state) return null;
|
|
@@ -376,6 +376,7 @@ registerAction("agent.delegate", async (params, context) => {
|
|
|
376
376
|
model,
|
|
377
377
|
cwd,
|
|
378
378
|
sessionId,
|
|
379
|
+
scope: `voice-dispatch:${sessionId}`,
|
|
379
380
|
sessionType: "voice-dispatch",
|
|
380
381
|
timeoutMs: 5 * 60 * 1000,
|
|
381
382
|
});
|
|
@@ -554,19 +555,20 @@ registerAction("system.fleet", async () => {
|
|
|
554
555
|
registerAction("system.config", async (params) => {
|
|
555
556
|
const cfg = loadConfig();
|
|
556
557
|
const key = String(params.key || "").trim();
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
primaryAgent: cfg.primaryAgent,
|
|
563
|
-
mode: cfg.mode,
|
|
564
|
-
kanbanBackend: cfg.kanbanBackend || cfg.kanban?.backend,
|
|
565
|
-
projectName: cfg.projectName,
|
|
558
|
+
const normalizedConfig = {
|
|
559
|
+
primaryAgent: cfg.primaryAgent || getPrimaryAgentName(),
|
|
560
|
+
mode: cfg.mode || "generic",
|
|
561
|
+
kanbanBackend: cfg.kanbanBackend || cfg.kanban?.backend || "internal",
|
|
562
|
+
projectName: cfg.projectName || cfg.project || process.env.PROJECT_NAME || "unknown",
|
|
566
563
|
autoFixEnabled: cfg.autoFixEnabled,
|
|
567
564
|
watchEnabled: cfg.watchEnabled,
|
|
568
565
|
voiceEnabled: cfg.voice?.enabled !== false,
|
|
569
566
|
};
|
|
567
|
+
if (key) {
|
|
568
|
+
const value = normalizedConfig[key] ?? cfg[key];
|
|
569
|
+
return value !== undefined ? { [key]: value } : { error: `Config key "${key}" not found.` };
|
|
570
|
+
}
|
|
571
|
+
return normalizedConfig;
|
|
570
572
|
});
|
|
571
573
|
|
|
572
574
|
registerAction("system.health", async () => {
|
|
@@ -854,10 +856,54 @@ registerAction("workflow.retry", async (params) => {
|
|
|
854
856
|
const currentRun = engine?.getRunDetail ? engine.getRunDetail(runId) : null;
|
|
855
857
|
if (!currentRun) throw new Error(`Workflow run "${runId}" not found`);
|
|
856
858
|
const currentStatus = String(currentRun?.status || "").trim().toLowerCase();
|
|
857
|
-
|
|
859
|
+
const retryOptions = typeof engine.getRetryOptions === "function"
|
|
860
|
+
? engine.getRetryOptions(runId)
|
|
861
|
+
: null;
|
|
862
|
+
const safeInterruptedResume =
|
|
863
|
+
mode === "from_failed" &&
|
|
864
|
+
retryOptions?.guardedState?.code === "create_tasks_pending" &&
|
|
865
|
+
retryOptions?.guardedState?.safeResume === true &&
|
|
866
|
+
retryOptions?.recommendedMode === "from_failed";
|
|
867
|
+
const createTasksPendingGuard = retryOptions?.guardedState?.code === "create_tasks_pending";
|
|
868
|
+
if (mode === "from_failed" && currentStatus !== "failed" && !createTasksPendingGuard) {
|
|
858
869
|
throw new Error(`retry mode "from_failed" requires a failed run (current=${currentRun?.status || "unknown"})`);
|
|
859
870
|
}
|
|
860
|
-
|
|
871
|
+
const resolvedRetry = safeInterruptedResume
|
|
872
|
+
? {
|
|
873
|
+
mode,
|
|
874
|
+
operatorAction: "resume",
|
|
875
|
+
decisionReason: retryOptions?.recommendedReason || "create_tasks_pending.resume_only",
|
|
876
|
+
blocked: false,
|
|
877
|
+
guardedState: retryOptions?.guardedState || null,
|
|
878
|
+
retryArgs: {
|
|
879
|
+
mode,
|
|
880
|
+
_resumeInterrupted: true,
|
|
881
|
+
...(retryOptions?.recommendedReason
|
|
882
|
+
? { _decisionReason: retryOptions.recommendedReason }
|
|
883
|
+
: {}),
|
|
884
|
+
},
|
|
885
|
+
}
|
|
886
|
+
: (
|
|
887
|
+
createTasksPendingGuard && typeof engine.resolveOperatorRetry === "function"
|
|
888
|
+
? engine.resolveOperatorRetry(runId, mode)
|
|
889
|
+
: null
|
|
890
|
+
);
|
|
891
|
+
if (resolvedRetry?.blocked) {
|
|
892
|
+
throw new Error(resolvedRetry.blockedMessage || "Workflow retry is blocked for this run state.");
|
|
893
|
+
}
|
|
894
|
+
const retryArgs = resolvedRetry?.retryArgs || { mode };
|
|
895
|
+
if (safeInterruptedResume && !resolvedRetry?.retryArgs) {
|
|
896
|
+
retryArgs._resumeInterrupted = true;
|
|
897
|
+
if (retryOptions?.recommendedReason) retryArgs._decisionReason = retryOptions.recommendedReason;
|
|
898
|
+
}
|
|
899
|
+
const result = await engine.retryRun(runId, retryArgs);
|
|
900
|
+
return {
|
|
901
|
+
...result,
|
|
902
|
+
operatorAction:
|
|
903
|
+
resolvedRetry?.operatorAction || (mode === "from_scratch" ? "restart" : "retry"),
|
|
904
|
+
decisionReason: resolvedRetry?.decisionReason || null,
|
|
905
|
+
guardedState: resolvedRetry?.guardedState || retryOptions?.guardedState || null,
|
|
906
|
+
};
|
|
861
907
|
});
|
|
862
908
|
|
|
863
909
|
// ── Skill/prompt actions ────────────────────────────────────────────────────
|
|
@@ -1241,4 +1287,3 @@ export function getVoiceActionPromptSection() {
|
|
|
1241
1287
|
lines.push("");
|
|
1242
1288
|
return lines.join("\n");
|
|
1243
1289
|
}
|
|
1244
|
-
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { loadConfig } from "../config/config.mjs";
|
|
20
|
-
import { resolveVoiceOAuthToken } from "
|
|
20
|
+
import { resolveSharedOAuthToken as resolveVoiceOAuthToken } from "../agent/provider-auth-state.mjs";
|
|
21
21
|
|
|
22
22
|
// ── Module-scope lazy imports ───────────────────────────────────────────────
|
|
23
23
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { exec as childExec } from "node:child_process";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
3
|
import { createServer } from "node:http";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
import { CredentialStore } from "../workflow/credential-store.mjs";
|
|
5
|
+
import {
|
|
6
|
+
clearSharedOAuthToken,
|
|
7
|
+
getProviderAuthStatePath,
|
|
8
|
+
hasSharedOAuthToken,
|
|
9
|
+
readProviderAuthState,
|
|
10
|
+
resolveSharedOAuthToken,
|
|
11
|
+
saveSharedOAuthToken,
|
|
12
|
+
} from "../agent/provider-auth-state.mjs";
|
|
13
13
|
|
|
14
14
|
function normalizeProvider(provider) {
|
|
15
15
|
return String(provider || "").trim().toLowerCase();
|
|
@@ -32,27 +32,6 @@ function escapeHtml(value) {
|
|
|
32
32
|
.replace(/'/g, ''');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function readStateFile() {
|
|
36
|
-
if (!existsSync(VOICE_AUTH_STATE_PATH)) return {};
|
|
37
|
-
const raw = readFileSync(VOICE_AUTH_STATE_PATH, "utf8");
|
|
38
|
-
const parsed = JSON.parse(raw);
|
|
39
|
-
if (!parsed || typeof parsed !== "object") return {};
|
|
40
|
-
return parsed;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function getCachedState(forceReload = false) {
|
|
44
|
-
const isFresh = !forceReload && _cachedState && Date.now() - _cachedStateAt < STATE_TTL_MS;
|
|
45
|
-
if (isFresh) return _cachedState;
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
_cachedState = readStateFile();
|
|
49
|
-
} catch {
|
|
50
|
-
_cachedState = {};
|
|
51
|
-
}
|
|
52
|
-
_cachedStateAt = Date.now();
|
|
53
|
-
return _cachedState;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
35
|
function isExpired(expiresAt) {
|
|
57
36
|
if (!expiresAt) return false;
|
|
58
37
|
const ts = Number(new Date(expiresAt).getTime());
|
|
@@ -60,6 +39,19 @@ function isExpired(expiresAt) {
|
|
|
60
39
|
return ts <= Date.now() + 30_000;
|
|
61
40
|
}
|
|
62
41
|
|
|
42
|
+
function resolveCredentialStore(options = {}) {
|
|
43
|
+
if (options.credentialStore) return options.credentialStore;
|
|
44
|
+
if (!options.configDir) return null;
|
|
45
|
+
try {
|
|
46
|
+
return new CredentialStore({
|
|
47
|
+
configDir: options.configDir,
|
|
48
|
+
secretKey: options.secretKey,
|
|
49
|
+
});
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
63
55
|
function getProviderEnvCandidates(provider) {
|
|
64
56
|
switch (provider) {
|
|
65
57
|
case "openai":
|
|
@@ -89,98 +81,22 @@ function getProviderEnvCandidates(provider) {
|
|
|
89
81
|
}
|
|
90
82
|
}
|
|
91
83
|
|
|
92
|
-
function getStateTokenCandidates(provider, state) {
|
|
93
|
-
const byProvider = state?.providers?.[provider] || state?.[provider] || {};
|
|
94
|
-
return [
|
|
95
|
-
{
|
|
96
|
-
token: byProvider?.accessToken,
|
|
97
|
-
expiresAt: byProvider?.expiresAt,
|
|
98
|
-
source: "state",
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
token: byProvider?.access_token,
|
|
102
|
-
expiresAt: byProvider?.expires_at,
|
|
103
|
-
source: "state",
|
|
104
|
-
},
|
|
105
|
-
];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
84
|
export function resolveVoiceOAuthToken(provider, forceReload = false) {
|
|
109
85
|
const normalizedProvider = normalizeProvider(provider);
|
|
110
86
|
if (!normalizedProvider) return null;
|
|
111
|
-
|
|
112
|
-
const envToken = getProviderEnvCandidates(normalizedProvider)
|
|
113
|
-
.map((token) => normalizeEnvValue(token))
|
|
114
|
-
.find(Boolean);
|
|
115
|
-
if (envToken) {
|
|
116
|
-
return {
|
|
117
|
-
token: envToken,
|
|
118
|
-
source: "env",
|
|
119
|
-
provider: normalizedProvider,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const state = getCachedState(forceReload);
|
|
124
|
-
const candidates = getStateTokenCandidates(normalizedProvider, state);
|
|
125
|
-
for (const candidate of candidates) {
|
|
126
|
-
const token = String(candidate?.token || "").trim();
|
|
127
|
-
if (!token) continue;
|
|
128
|
-
if (isExpired(candidate?.expiresAt)) continue;
|
|
129
|
-
return {
|
|
130
|
-
token,
|
|
131
|
-
source: candidate.source,
|
|
132
|
-
provider: normalizedProvider,
|
|
133
|
-
expiresAt: candidate?.expiresAt || null,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return null;
|
|
87
|
+
return resolveSharedOAuthToken(normalizedProvider, forceReload);
|
|
138
88
|
}
|
|
139
89
|
|
|
140
90
|
export function hasVoiceOAuthToken(provider, forceReload = false) {
|
|
141
|
-
return
|
|
91
|
+
return hasSharedOAuthToken(provider, forceReload);
|
|
142
92
|
}
|
|
143
93
|
|
|
144
94
|
export function saveVoiceOAuthToken(provider, payload = {}) {
|
|
145
|
-
|
|
146
|
-
if (!normalizedProvider) {
|
|
147
|
-
throw new Error("provider is required");
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const token = String(payload?.accessToken || payload?.access_token || "").trim();
|
|
151
|
-
if (!token) {
|
|
152
|
-
throw new Error("access token is required");
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const current = getCachedState(true);
|
|
156
|
-
const next = {
|
|
157
|
-
...current,
|
|
158
|
-
providers: {
|
|
159
|
-
...(current?.providers || {}),
|
|
160
|
-
[normalizedProvider]: {
|
|
161
|
-
accessToken: token,
|
|
162
|
-
expiresAt: payload?.expiresAt || payload?.expires_at || null,
|
|
163
|
-
refreshToken: payload?.refreshToken || payload?.refresh_token || null,
|
|
164
|
-
tokenType: payload?.tokenType || payload?.token_type || "Bearer",
|
|
165
|
-
updatedAt: new Date().toISOString(),
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
mkdirSync(dirname(VOICE_AUTH_STATE_PATH), { recursive: true });
|
|
171
|
-
writeFileSync(VOICE_AUTH_STATE_PATH, JSON.stringify(next, null, 2));
|
|
172
|
-
_cachedState = next;
|
|
173
|
-
_cachedStateAt = Date.now();
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
ok: true,
|
|
177
|
-
path: VOICE_AUTH_STATE_PATH,
|
|
178
|
-
provider: normalizedProvider,
|
|
179
|
-
};
|
|
95
|
+
return saveSharedOAuthToken(provider, payload);
|
|
180
96
|
}
|
|
181
97
|
|
|
182
98
|
export function getVoiceAuthStatePath() {
|
|
183
|
-
return
|
|
99
|
+
return getProviderAuthStatePath();
|
|
184
100
|
}
|
|
185
101
|
|
|
186
102
|
// ── Generic OAuth PKCE provider registry ─────────────────────────────────────
|
|
@@ -286,6 +202,80 @@ const OAUTH_PROVIDERS = {
|
|
|
286
202
|
},
|
|
287
203
|
};
|
|
288
204
|
|
|
205
|
+
function resolveVoiceProviderCredentialRecord(provider, options = {}) {
|
|
206
|
+
const credentialStore = resolveCredentialStore(options);
|
|
207
|
+
if (!credentialStore) return { record: null, validation: null };
|
|
208
|
+
const candidates = credentialStore.listByProvider(provider);
|
|
209
|
+
const record = candidates.find((entry) => {
|
|
210
|
+
const authMode = normalizeProvider(entry?.lifecycle?.authMode || entry?.metadata?.authMode || "oauth");
|
|
211
|
+
return !authMode || authMode === "oauth";
|
|
212
|
+
}) || null;
|
|
213
|
+
if (!record) return { record: null, validation: null };
|
|
214
|
+
return {
|
|
215
|
+
record,
|
|
216
|
+
validation: credentialStore.validate(record.name, {
|
|
217
|
+
env: options.env || process.env,
|
|
218
|
+
config: options.config || null,
|
|
219
|
+
workflowId: options.workflowId,
|
|
220
|
+
}),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function describeVoiceProviderLifecycle(provider, options = {}) {
|
|
225
|
+
const normalizedProvider = normalizeProvider(provider);
|
|
226
|
+
const cfg = OAUTH_PROVIDERS[normalizedProvider];
|
|
227
|
+
if (!cfg) {
|
|
228
|
+
return {
|
|
229
|
+
provider: normalizedProvider,
|
|
230
|
+
status: "unknown",
|
|
231
|
+
missingActions: [],
|
|
232
|
+
envKeys: [],
|
|
233
|
+
refreshable: false,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const token = resolveVoiceOAuthToken(normalizedProvider, options.forceReload === true);
|
|
238
|
+
const loginStatus = _providerPendingLogin.get(normalizedProvider)?.status || "idle";
|
|
239
|
+
const clientIdConfigured = normalizeEnvValue(cfg.clientId).length >= 8;
|
|
240
|
+
const { record, validation } = resolveVoiceProviderCredentialRecord(normalizedProvider, options);
|
|
241
|
+
const refreshable = Boolean(token?.refreshToken || validation?.refreshable);
|
|
242
|
+
const missingActions = [];
|
|
243
|
+
|
|
244
|
+
if (!clientIdConfigured) missingActions.push("configure_client");
|
|
245
|
+
if (!token?.token && !record?.name) missingActions.push("sign_in");
|
|
246
|
+
if (token?.expiresAt && isExpired(token.expiresAt) && refreshable) missingActions.push("refresh_token");
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
provider: normalizedProvider,
|
|
250
|
+
status: token?.token
|
|
251
|
+
? (isExpired(token.expiresAt) ? "expired" : "connected")
|
|
252
|
+
: (loginStatus === "pending" ? "pending" : "idle"),
|
|
253
|
+
hasToken: Boolean(token?.token),
|
|
254
|
+
expiresAt: token?.expiresAt || validation?.expiresAt || null,
|
|
255
|
+
refreshable,
|
|
256
|
+
connectedSource: token?.source || (record?.name ? "credential-store" : null),
|
|
257
|
+
pendingStatus: loginStatus,
|
|
258
|
+
clientIdConfigured,
|
|
259
|
+
requiredEnvKeys: cfg.clientSecret
|
|
260
|
+
? [`BOSUN_${normalizedProvider.toUpperCase()}_OAUTH_CLIENT_ID`, `BOSUN_${normalizedProvider.toUpperCase()}_OAUTH_CLIENT_SECRET`]
|
|
261
|
+
: [`BOSUN_${normalizedProvider.toUpperCase()}_OAUTH_CLIENT_ID`],
|
|
262
|
+
missingActions,
|
|
263
|
+
credentialName: record?.name || null,
|
|
264
|
+
validationErrors: validation?.errors || [],
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function validateVoiceProviderLifecycle(provider, options = {}) {
|
|
269
|
+
const lifecycle = describeVoiceProviderLifecycle(provider, options);
|
|
270
|
+
return {
|
|
271
|
+
ok: lifecycle.missingActions.length === 0 && lifecycle.validationErrors.length === 0,
|
|
272
|
+
status: lifecycle.status,
|
|
273
|
+
missingActions: lifecycle.missingActions,
|
|
274
|
+
validationErrors: lifecycle.validationErrors,
|
|
275
|
+
refreshable: lifecycle.refreshable,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
289
279
|
// Module-scope per-provider pending login state (never inside a function — hard rule).
|
|
290
280
|
const _providerPendingLogin = new Map();
|
|
291
281
|
|
|
@@ -498,25 +488,14 @@ function _cancelProviderLogin(provider) {
|
|
|
498
488
|
}
|
|
499
489
|
|
|
500
490
|
function _logoutProvider(provider) {
|
|
501
|
-
|
|
502
|
-
if (!curr?.providers?.[provider]) return { ok: true, wasLoggedIn: false };
|
|
503
|
-
const next = {
|
|
504
|
-
...curr,
|
|
505
|
-
providers: { ...(curr.providers || {}) },
|
|
506
|
-
};
|
|
507
|
-
delete next.providers[provider];
|
|
508
|
-
mkdirSync(dirname(VOICE_AUTH_STATE_PATH), { recursive: true });
|
|
509
|
-
writeFileSync(VOICE_AUTH_STATE_PATH, JSON.stringify(next, null, 2));
|
|
510
|
-
_cachedState = next;
|
|
511
|
-
_cachedStateAt = Date.now();
|
|
512
|
-
return { ok: true, wasLoggedIn: true };
|
|
491
|
+
return clearSharedOAuthToken(provider);
|
|
513
492
|
}
|
|
514
493
|
|
|
515
494
|
async function _refreshProviderToken(provider) {
|
|
516
495
|
const cfg = OAUTH_PROVIDERS[provider];
|
|
517
496
|
if (!cfg) throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
518
497
|
|
|
519
|
-
const state =
|
|
498
|
+
const state = readProviderAuthState(true);
|
|
520
499
|
const providerData = state?.providers?.[provider] || {};
|
|
521
500
|
const refreshToken = providerData.refreshToken || providerData.refresh_token;
|
|
522
501
|
if (!refreshToken) throw new Error(`No refresh token stored for ${provider}`);
|